diff --git a/app/availableplugins/ApiConnector/src/Controller/ApiSourceEndpointsController.php b/app/availableplugins/ApiConnector/src/Controller/ApiSourceEndpointsController.php index 35885ec48..132466a68 100644 --- a/app/availableplugins/ApiConnector/src/Controller/ApiSourceEndpointsController.php +++ b/app/availableplugins/ApiConnector/src/Controller/ApiSourceEndpointsController.php @@ -33,7 +33,7 @@ use App\Controller\StandardPluginController; class ApiSourceEndpointsController extends StandardPluginController { - public $paginate = [ + public array $paginate = [ 'order' => [ 'ApiSourceEndpoints.id' => 'asc' ] diff --git a/app/availableplugins/ApiConnector/src/Controller/ApiSourcesController.php b/app/availableplugins/ApiConnector/src/Controller/ApiSourcesController.php index 19b998274..a74d0427d 100644 --- a/app/availableplugins/ApiConnector/src/Controller/ApiSourcesController.php +++ b/app/availableplugins/ApiConnector/src/Controller/ApiSourcesController.php @@ -33,7 +33,7 @@ use App\Controller\StandardPluginController; class ApiSourcesController extends StandardPluginController { - public $paginate = [ + public array $paginate = [ 'order' => [ 'ApiSources.id' => 'asc' ] diff --git a/app/availableplugins/ApiConnector/src/Model/Entity/ApiSource.php b/app/availableplugins/ApiConnector/src/Model/Entity/ApiSource.php index 87b958bfc..b05cd80ae 100644 --- a/app/availableplugins/ApiConnector/src/Model/Entity/ApiSource.php +++ b/app/availableplugins/ApiConnector/src/Model/Entity/ApiSource.php @@ -41,7 +41,7 @@ class ApiSource extends Entity { * * @var array */ - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/availableplugins/ApiConnector/src/Model/Entity/ApiSourceEndpoint.php b/app/availableplugins/ApiConnector/src/Model/Entity/ApiSourceEndpoint.php index 3d87f18ac..da833c35f 100644 --- a/app/availableplugins/ApiConnector/src/Model/Entity/ApiSourceEndpoint.php +++ b/app/availableplugins/ApiConnector/src/Model/Entity/ApiSourceEndpoint.php @@ -41,7 +41,7 @@ class ApiSourceEndpoint extends Entity { * * @var array */ - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/availableplugins/ApiConnector/src/Model/Entity/ApiSourceRecord.php b/app/availableplugins/ApiConnector/src/Model/Entity/ApiSourceRecord.php index 815a62655..d6ab1e064 100644 --- a/app/availableplugins/ApiConnector/src/Model/Entity/ApiSourceRecord.php +++ b/app/availableplugins/ApiConnector/src/Model/Entity/ApiSourceRecord.php @@ -41,7 +41,7 @@ class ApiSourceRecord extends Entity { * * @var array */ - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/availableplugins/FileConnector/src/Controller/FileProvisionersController.php b/app/availableplugins/FileConnector/src/Controller/FileProvisionersController.php index 471b38074..9335c579c 100644 --- a/app/availableplugins/FileConnector/src/Controller/FileProvisionersController.php +++ b/app/availableplugins/FileConnector/src/Controller/FileProvisionersController.php @@ -32,7 +32,7 @@ use App\Controller\StandardPluginController; class FileProvisionersController extends StandardPluginController { - public $paginate = [ + public array $paginate = [ 'order' => [ 'FileProvisioners.id' => 'asc' ] diff --git a/app/availableplugins/FileConnector/src/Controller/FileSourcesController.php b/app/availableplugins/FileConnector/src/Controller/FileSourcesController.php index 0e5371d43..103943b28 100644 --- a/app/availableplugins/FileConnector/src/Controller/FileSourcesController.php +++ b/app/availableplugins/FileConnector/src/Controller/FileSourcesController.php @@ -34,7 +34,7 @@ use Cake\Http\Response; class FileSourcesController extends StandardPluginController { - public $paginate = [ + public array $paginate = [ 'order' => [ 'FileSources.id' => 'asc' ] diff --git a/app/availableplugins/FileConnector/src/Model/Entity/FileProvisioner.php b/app/availableplugins/FileConnector/src/Model/Entity/FileProvisioner.php index 36fc9b3e3..7499334f2 100644 --- a/app/availableplugins/FileConnector/src/Model/Entity/FileProvisioner.php +++ b/app/availableplugins/FileConnector/src/Model/Entity/FileProvisioner.php @@ -41,7 +41,7 @@ class FileProvisioner extends Entity { * * @var array */ - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/availableplugins/FileConnector/src/Model/Entity/FileSource.php b/app/availableplugins/FileConnector/src/Model/Entity/FileSource.php index ca49ad543..662131667 100644 --- a/app/availableplugins/FileConnector/src/Model/Entity/FileSource.php +++ b/app/availableplugins/FileConnector/src/Model/Entity/FileSource.php @@ -41,7 +41,7 @@ class FileSource extends Entity { * * @var array */ - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/availableplugins/LdapConnector/.gitignore b/app/availableplugins/LdapConnector/.gitignore new file mode 100644 index 000000000..244d127b1 --- /dev/null +++ b/app/availableplugins/LdapConnector/.gitignore @@ -0,0 +1,8 @@ +/composer.lock +/composer.phar +/phpunit.xml +/.phpunit.result.cache +/phpunit.phar +/config/Migrations/schema-dump-default.lock +/vendor/ +/.idea/ diff --git a/app/availableplugins/LdapConnector/README.md b/app/availableplugins/LdapConnector/README.md new file mode 100644 index 000000000..b81f2bfbe --- /dev/null +++ b/app/availableplugins/LdapConnector/README.md @@ -0,0 +1,11 @@ +# LdapConnector plugin for CakePHP + +## Installation + +You can install this plugin into your CakePHP application using [composer](https://getcomposer.org). + +The recommended way to install composer packages is: + +``` +composer require your-name-here/ldap-connector +``` diff --git a/app/availableplugins/LdapConnector/composer.json b/app/availableplugins/LdapConnector/composer.json new file mode 100644 index 000000000..9d69b5ceb --- /dev/null +++ b/app/availableplugins/LdapConnector/composer.json @@ -0,0 +1,24 @@ +{ + "name": "your-name-here/ldap-connector", + "description": "LdapConnector plugin for CakePHP", + "type": "cakephp-plugin", + "license": "MIT", + "require": { + "php": ">=7.2", + "cakephp/cakephp": "4.6.*" + }, + "require-dev": { + "phpunit/phpunit": "^8.5 || ^9.3" + }, + "autoload": { + "psr-4": { + "LdapConnector\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "LdapConnector\\Test\\": "tests/", + "Cake\\Test\\": "vendor/cakephp/cakephp/tests/" + } + } +} diff --git a/app/availableplugins/LdapConnector/phpunit.xml.dist b/app/availableplugins/LdapConnector/phpunit.xml.dist new file mode 100644 index 000000000..255397c7b --- /dev/null +++ b/app/availableplugins/LdapConnector/phpunit.xml.dist @@ -0,0 +1,30 @@ + + + + + + + + + + + tests/TestCase/ + + + + + + + + + + + src/ + + + diff --git a/app/availableplugins/LdapConnector/src/Controller/AppController.php b/app/availableplugins/LdapConnector/src/Controller/AppController.php new file mode 100644 index 000000000..9de965fce --- /dev/null +++ b/app/availableplugins/LdapConnector/src/Controller/AppController.php @@ -0,0 +1,10 @@ +plugin( + 'LdapConnector', + ['path' => '/ldap-connector'], + function (RouteBuilder $builder) { + // Add custom routes here + + $builder->fallbacks(); + } + ); + parent::routes($routes); + } + + /** + * Add middleware for the plugin. + * + * @param \Cake\Http\MiddlewareQueue $middlewareQueue The middleware queue to update. + * @return \Cake\Http\MiddlewareQueue + */ + public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue + { + // Add your middlewares here + + return $middlewareQueue; + } + + /** + * Add commands for the plugin. + * + * @param \Cake\Console\CommandCollection $commands The command collection to update. + * @return \Cake\Console\CommandCollection + */ + public function console(CommandCollection $commands): CommandCollection + { + // Add your commands here + + $commands = parent::console($commands); + + return $commands; + } + + /** + * Register application container services. + * + * @param \Cake\Core\ContainerInterface $container The Container to update. + * @return void + * @link https://book.cakephp.org/4/en/development/dependency-injection.html#dependency-injection + */ + public function services(ContainerInterface $container): void + { + // Add your services here + } +} diff --git a/app/availableplugins/LdapConnector/tests/bootstrap.php b/app/availableplugins/LdapConnector/tests/bootstrap.php new file mode 100644 index 000000000..b664cb392 --- /dev/null +++ b/app/availableplugins/LdapConnector/tests/bootstrap.php @@ -0,0 +1,55 @@ +loadSqlFiles('tests/schema.sql', 'test'); diff --git a/app/availableplugins/LdapConnector/tests/schema.sql b/app/availableplugins/LdapConnector/tests/schema.sql new file mode 100644 index 000000000..1728c9d7b --- /dev/null +++ b/app/availableplugins/LdapConnector/tests/schema.sql @@ -0,0 +1 @@ +-- Test database schema for LdapConnector diff --git a/app/availableplugins/LdapConnector/webroot/.gitkeep b/app/availableplugins/LdapConnector/webroot/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/app/availableplugins/PipelineToolkit/src/Controller/IdentifierMappersController.php b/app/availableplugins/PipelineToolkit/src/Controller/IdentifierMappersController.php index 619e72252..92375b5ad 100644 --- a/app/availableplugins/PipelineToolkit/src/Controller/IdentifierMappersController.php +++ b/app/availableplugins/PipelineToolkit/src/Controller/IdentifierMappersController.php @@ -32,7 +32,7 @@ use App\Controller\StandardPluginController; class IdentifierMappersController extends StandardPluginController { - public $paginate = [ + public array $paginate = [ 'order' => [ 'IdentifierMappers.id' => 'asc' ] diff --git a/app/availableplugins/PipelineToolkit/src/Controller/LoginIdentifierTypesController.php b/app/availableplugins/PipelineToolkit/src/Controller/LoginIdentifierTypesController.php index 43f7d0950..183d1ad25 100644 --- a/app/availableplugins/PipelineToolkit/src/Controller/LoginIdentifierTypesController.php +++ b/app/availableplugins/PipelineToolkit/src/Controller/LoginIdentifierTypesController.php @@ -32,7 +32,7 @@ use App\Controller\StandardPluginController; class LoginIdentifierTypesController extends StandardPluginController { - public $paginate = [ + public array $paginate = [ 'order' => [ 'LoginIdentifierTypes.id' => 'asc' ] diff --git a/app/availableplugins/PipelineToolkit/src/Controller/PersonRoleMappersController.php b/app/availableplugins/PipelineToolkit/src/Controller/PersonRoleMappersController.php index a434aa4a1..5f605f30c 100644 --- a/app/availableplugins/PipelineToolkit/src/Controller/PersonRoleMappersController.php +++ b/app/availableplugins/PipelineToolkit/src/Controller/PersonRoleMappersController.php @@ -32,7 +32,7 @@ use App\Controller\StandardPluginController; class PersonRoleMappersController extends StandardPluginController { - public $paginate = [ + public array $paginate = [ 'order' => [ 'PersonRoleMappers.id' => 'asc' ] diff --git a/app/availableplugins/PipelineToolkit/src/Controller/PersonRoleMappingsController.php b/app/availableplugins/PipelineToolkit/src/Controller/PersonRoleMappingsController.php index 00b3b16b3..f4b9feee1 100644 --- a/app/availableplugins/PipelineToolkit/src/Controller/PersonRoleMappingsController.php +++ b/app/availableplugins/PipelineToolkit/src/Controller/PersonRoleMappingsController.php @@ -32,7 +32,7 @@ use App\Controller\StandardPluginController; class PersonRoleMappingsController extends StandardPluginController { - public $paginate = [ + public array $paginate = [ 'order' => [ 'PersonRoleMappings.id' => 'asc' ] diff --git a/app/availableplugins/PipelineToolkit/src/Model/Entity/IdentifierMapper.php b/app/availableplugins/PipelineToolkit/src/Model/Entity/IdentifierMapper.php index da62bb347..e9687f174 100644 --- a/app/availableplugins/PipelineToolkit/src/Model/Entity/IdentifierMapper.php +++ b/app/availableplugins/PipelineToolkit/src/Model/Entity/IdentifierMapper.php @@ -41,7 +41,7 @@ class IdentifierMapper extends Entity { * * @var array */ - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/availableplugins/PipelineToolkit/src/Model/Entity/LoginIdentifierType.php b/app/availableplugins/PipelineToolkit/src/Model/Entity/LoginIdentifierType.php index 5e24a0756..6d5092b2b 100644 --- a/app/availableplugins/PipelineToolkit/src/Model/Entity/LoginIdentifierType.php +++ b/app/availableplugins/PipelineToolkit/src/Model/Entity/LoginIdentifierType.php @@ -43,7 +43,7 @@ class LoginIdentifierType extends Entity { * * @var array */ - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/availableplugins/PipelineToolkit/src/Model/Entity/PersonRoleMapper.php b/app/availableplugins/PipelineToolkit/src/Model/Entity/PersonRoleMapper.php index 3629f6a4f..eb42d7458 100644 --- a/app/availableplugins/PipelineToolkit/src/Model/Entity/PersonRoleMapper.php +++ b/app/availableplugins/PipelineToolkit/src/Model/Entity/PersonRoleMapper.php @@ -41,7 +41,7 @@ class PersonRoleMapper extends Entity { * * @var array */ - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/availableplugins/PipelineToolkit/src/Model/Entity/PersonRoleMapping.php b/app/availableplugins/PipelineToolkit/src/Model/Entity/PersonRoleMapping.php index c817649c3..4e6b33e59 100644 --- a/app/availableplugins/PipelineToolkit/src/Model/Entity/PersonRoleMapping.php +++ b/app/availableplugins/PipelineToolkit/src/Model/Entity/PersonRoleMapping.php @@ -43,7 +43,7 @@ class PersonRoleMapping extends Entity { * * @var array */ - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/availableplugins/PipelineToolkit/src/Model/Table/PersonRoleMappersTable.php b/app/availableplugins/PipelineToolkit/src/Model/Table/PersonRoleMappersTable.php index 9bdd1bf90..9dc38c1b4 100644 --- a/app/availableplugins/PipelineToolkit/src/Model/Table/PersonRoleMappersTable.php +++ b/app/availableplugins/PipelineToolkit/src/Model/Table/PersonRoleMappersTable.php @@ -109,7 +109,7 @@ public function buildPersonRole( // We need our Mapping configuration, which won't be in $flange $mappings = $this->PersonRoleMappings->find() ->where(['person_role_mapper_id' => $flange->person_role_mapper->id]) - ->order(['ordr' => 'ASC']) + ->orderBy(['ordr' => 'ASC']) ->all(); foreach($mappings as $mapping) { diff --git a/app/availableplugins/SqlConnector/src/Controller/SqlProvisionersController.php b/app/availableplugins/SqlConnector/src/Controller/SqlProvisionersController.php index 40b1ae658..8b4c93c5e 100644 --- a/app/availableplugins/SqlConnector/src/Controller/SqlProvisionersController.php +++ b/app/availableplugins/SqlConnector/src/Controller/SqlProvisionersController.php @@ -32,7 +32,7 @@ use App\Controller\StandardPluginController; class SqlProvisionersController extends StandardPluginController { - public $paginate = [ + public array $paginate = [ 'order' => [ 'SqlProvisioners.id' => 'asc' ] diff --git a/app/availableplugins/SqlConnector/src/Controller/SqlSourcesController.php b/app/availableplugins/SqlConnector/src/Controller/SqlSourcesController.php index 091938103..0a117608e 100644 --- a/app/availableplugins/SqlConnector/src/Controller/SqlSourcesController.php +++ b/app/availableplugins/SqlConnector/src/Controller/SqlSourcesController.php @@ -32,7 +32,7 @@ use App\Controller\StandardPluginController; class SqlSourcesController extends StandardPluginController { - public $paginate = [ + public array $paginate = [ 'order' => [ 'SqlSources.id' => 'asc' ] diff --git a/app/availableplugins/SqlConnector/src/Model/Entity/SqlProvisioner.php b/app/availableplugins/SqlConnector/src/Model/Entity/SqlProvisioner.php index 5d5ff2247..78e20c05c 100644 --- a/app/availableplugins/SqlConnector/src/Model/Entity/SqlProvisioner.php +++ b/app/availableplugins/SqlConnector/src/Model/Entity/SqlProvisioner.php @@ -41,7 +41,7 @@ class SqlProvisioner extends Entity { * * @var array */ - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/availableplugins/SqlConnector/src/Model/Entity/SqlSource.php b/app/availableplugins/SqlConnector/src/Model/Entity/SqlSource.php index ce1bd4e61..1015f2f08 100644 --- a/app/availableplugins/SqlConnector/src/Model/Entity/SqlSource.php +++ b/app/availableplugins/SqlConnector/src/Model/Entity/SqlSource.php @@ -41,7 +41,7 @@ class SqlSource extends Entity { * * @var array */ - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/composer.json b/app/composer.json index 2139c370c..1f4a9f199 100644 --- a/app/composer.json +++ b/app/composer.json @@ -5,22 +5,25 @@ "type": "project", "license": "MIT", "require": { - "php": ">=8.0", - "cakephp/cakephp": "4.6.*", - "cakephp/migrations": "^3.2", - "cakephp/plugin-installer": "^1.3", - "doctrine/dbal": "^3.3", + "php": ">=8.1", + "ext-intl": "*", + "ext-json": "*", + "ext-mbstring": "*", + "cakephp/cakephp": "5.2.*", + "cakephp/migrations": "^4.0.0", + "cakephp/plugin-installer": "^2.0", + "mobiledetect/mobiledetectlib": "^4.8.03", + "doctrine/dbal": "^3.10.1", "league/container": "^4.2.0", - "mobiledetect/mobiledetectlib": "^2.8", - "psr/log": "^2.0", + "psr/log": "^3.0", "symfony/html-sanitizer": "^7.2" }, "require-dev": { - "cakephp/bake": "^2.6", - "cakephp/cakephp-codesniffer": "^4.5", - "cakephp/debug_kit": "^4.5", - "josegonzalez/dotenv": "^3.2", - "phpunit/phpunit": "~8.5.0 || ^9.3", + "cakephp/bake": "^3.0.0", + "cakephp/cakephp-codesniffer": "^5.0", + "cakephp/debug_kit": "^5.0.0", + "josegonzalez/dotenv": "^4.0", + "phpunit/phpunit": "^10.5.5 || ^11.1.3 || ^12.1", "psy/psysh": "@stable" }, "suggest": { diff --git a/app/composer.lock b/app/composer.lock index 686d5e1da..4aa111ecd 100644 --- a/app/composer.lock +++ b/app/composer.lock @@ -4,46 +4,49 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "0903212b8cb5b43bd6eaa080bf0d70a4", + "content-hash": "48cb87d4e3c420d2552d651dee6ee046", "packages": [ { "name": "cakephp/cakephp", - "version": "4.6.2", + "version": "5.2.7", "source": { "type": "git", "url": "https://github.com/cakephp/cakephp.git", - "reference": "db79f771fa3f0e7492840ad9fb5b89c01519a500" + "reference": "6912570c76b7c27410ab7524d119e5a97f851508" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/cakephp/cakephp/zipball/db79f771fa3f0e7492840ad9fb5b89c01519a500", - "reference": "db79f771fa3f0e7492840ad9fb5b89c01519a500", + "url": "https://api.github.com/repos/cakephp/cakephp/zipball/6912570c76b7c27410ab7524d119e5a97f851508", + "reference": "6912570c76b7c27410ab7524d119e5a97f851508", "shasum": "" }, "require": { - "cakephp/chronos": "^2.4.0", - "composer/ca-bundle": "^1.2", + "cakephp/chronos": "^3.1", + "composer/ca-bundle": "^1.5", "ext-intl": "*", "ext-json": "*", "ext-mbstring": "*", - "laminas/laminas-diactoros": "^2.2.2", - "laminas/laminas-httphandlerrunner": "^1.1 || ^2.0", - "league/container": "^4.2.0", - "php": ">=7.4.0,<9", + "laminas/laminas-diactoros": "^3.3", + "laminas/laminas-httphandlerrunner": "^2.6", + "league/container": "^4.2", + "php": ">=8.1", "psr/container": "^1.1 || ^2.0", - "psr/http-client": "^1.0", - "psr/http-server-handler": "^1.0", - "psr/http-server-middleware": "^1.0", - "psr/log": "^1.0 || ^2.0", - "psr/simple-cache": "^1.0 || ^2.0" + "psr/http-client": "^1.0.2", + "psr/http-factory": "^1.1", + "psr/http-message": "^1.1 || ^2.0", + "psr/http-server-handler": "^1.0.2", + "psr/http-server-middleware": "^1.0.2", + "psr/log": "^3.0", + "psr/simple-cache": "^2.0 || ^3.0" }, "provide": { - "psr/container-implementation": "^1.0 || ^2.0", + "psr/container-implementation": "^2.0", "psr/http-client-implementation": "^1.0", + "psr/http-factory-implementation": "^1.0", "psr/http-server-handler-implementation": "^1.0", "psr/http-server-middleware-implementation": "^1.0", - "psr/log-implementation": "^1.0 || ^2.0", - "psr/simple-cache-implementation": "^1.0 || ^2.0" + "psr/log-implementation": "^3.0", + "psr/simple-cache-implementation": "^3.0" }, "replace": { "cakephp/cache": "self.version", @@ -53,7 +56,6 @@ "cakephp/database": "self.version", "cakephp/datasource": "self.version", "cakephp/event": "self.version", - "cakephp/filesystem": "self.version", "cakephp/form": "self.version", "cakephp/http": "self.version", "cakephp/i18n": "self.version", @@ -63,24 +65,31 @@ "cakephp/validation": "self.version" }, "require-dev": { - "cakephp/cakephp-codesniffer": "^4.5", + "cakephp/cakephp-codesniffer": "^5.2", + "http-interop/http-factory-tests": "dev-main", "mikey179/vfsstream": "^1.6.10", + "mockery/mockery": "^1.6", "paragonie/csp-builder": "^2.3 || ^3.0", - "phpunit/phpunit": "^8.5 || ^9.3" + "phpunit/phpunit": "^10.5.5 || ^11.1.3 || ^12.0.9" }, "suggest": { "ext-curl": "To enable more efficient network calls in Http\\Client.", "ext-openssl": "To use Security::encrypt() or have secure CSRF token generation.", - "lib-ICU": "To use locale-aware features in the I18n and Database packages", "paragonie/csp-builder": "CSP builder, to use the CSP Middleware" }, "type": "library", + "extra": { + "branch-alias": { + "dev-5.next": "5.2.x-dev" + } + }, "autoload": { "files": [ "src/Core/functions.php", "src/Error/functions.php", "src/Collection/functions.php", "src/I18n/functions.php", + "src/ORM/bootstrap.php", "src/Routing/functions.php", "src/Utility/bootstrap.php" ], @@ -112,39 +121,39 @@ "validation" ], "support": { - "forum": "https://stackoverflow.com/tags/cakephp", - "irc": "irc://irc.freenode.org/cakephp", + "forum": "https://discourse.cakephp.org/", "issues": "https://github.com/cakephp/cakephp/issues", "source": "https://github.com/cakephp/cakephp" }, - "time": "2025-07-27T03:26:03+00:00" + "time": "2025-08-31T02:18:16+00:00" }, { "name": "cakephp/chronos", - "version": "2.4.5", + "version": "3.2.0", "source": { "type": "git", "url": "https://github.com/cakephp/chronos.git", - "reference": "b0321ab7658af9e7abcb3dd876f226e6f3dbb81f" + "reference": "6c820947bc1372a250288ab164ec1b3bb7afab39" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/cakephp/chronos/zipball/b0321ab7658af9e7abcb3dd876f226e6f3dbb81f", - "reference": "b0321ab7658af9e7abcb3dd876f226e6f3dbb81f", + "url": "https://api.github.com/repos/cakephp/chronos/zipball/6c820947bc1372a250288ab164ec1b3bb7afab39", + "reference": "6c820947bc1372a250288ab164ec1b3bb7afab39", "shasum": "" }, "require": { - "php": ">=7.2" + "php": ">=8.1", + "psr/clock": "^1.0" + }, + "provide": { + "psr/clock-implementation": "1.0" }, "require-dev": { - "cakephp/cakephp-codesniffer": "^4.5", - "phpunit/phpunit": "^8.0 || ^9.0" + "cakephp/cakephp-codesniffer": "^5.0", + "phpunit/phpunit": "^10.1.0 || ^11.1.3" }, "type": "library", "autoload": { - "files": [ - "src/carbon_compat.php" - ], "psr-4": { "Cake\\Chronos\\": "src/" } @@ -175,33 +184,33 @@ "issues": "https://github.com/cakephp/chronos/issues", "source": "https://github.com/cakephp/chronos" }, - "time": "2024-07-30T22:26:11+00:00" + "time": "2025-06-28T11:35:59+00:00" }, { "name": "cakephp/migrations", - "version": "3.9.0", + "version": "4.7.1", "source": { "type": "git", "url": "https://github.com/cakephp/migrations.git", - "reference": "58446fdd096087ddf7752c0317731b8725d1dc28" + "reference": "474c36c917c53894aa0d79ed72abe7fd3f0f3394" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/cakephp/migrations/zipball/58446fdd096087ddf7752c0317731b8725d1dc28", - "reference": "58446fdd096087ddf7752c0317731b8725d1dc28", + "url": "https://api.github.com/repos/cakephp/migrations/zipball/474c36c917c53894aa0d79ed72abe7fd3f0f3394", + "reference": "474c36c917c53894aa0d79ed72abe7fd3f0f3394", "shasum": "" }, "require": { - "cakephp/cache": "^4.3.0", - "cakephp/orm": "^4.3.0", - "php": ">=7.4.0", - "robmorgan/phinx": "^0.13.2" + "cakephp/cache": "^5.2", + "cakephp/orm": "^5.2", + "php": ">=8.1", + "robmorgan/phinx": "^0.16.10" }, "require-dev": { - "cakephp/bake": "^2.6.0", - "cakephp/cakephp": "^4.3.0", - "cakephp/cakephp-codesniffer": "^4.1", - "phpunit/phpunit": "^9.5.0" + "cakephp/bake": "^3.3", + "cakephp/cakephp": "^5.2.5", + "cakephp/cakephp-codesniffer": "^5.0", + "phpunit/phpunit": "^10.5.5 || ^11.1.3 || ^12.2.4" }, "suggest": { "cakephp/bake": "If you want to generate migrations.", @@ -227,6 +236,7 @@ "homepage": "https://github.com/cakephp/migrations", "keywords": [ "cakephp", + "cli", "migrations" ], "support": { @@ -235,30 +245,30 @@ "issues": "https://github.com/cakephp/migrations/issues", "source": "https://github.com/cakephp/migrations" }, - "time": "2023-09-22T08:39:18+00:00" + "time": "2025-08-04T12:09:00+00:00" }, { "name": "cakephp/plugin-installer", - "version": "1.3.1", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/cakephp/plugin-installer.git", - "reference": "e27027aa2d3d8ab64452c6817629558685a064cb" + "reference": "5420701fd47d82fe81805ebee34fbbcef34c52ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/cakephp/plugin-installer/zipball/e27027aa2d3d8ab64452c6817629558685a064cb", - "reference": "e27027aa2d3d8ab64452c6817629558685a064cb", + "url": "https://api.github.com/repos/cakephp/plugin-installer/zipball/5420701fd47d82fe81805ebee34fbbcef34c52ba", + "reference": "5420701fd47d82fe81805ebee34fbbcef34c52ba", "shasum": "" }, "require": { - "composer-plugin-api": "^1.0 || ^2.0", - "php": ">=5.6.0" + "composer-plugin-api": "^2.0", + "php": ">=8.1" }, "require-dev": { - "cakephp/cakephp-codesniffer": "^3.3", + "cakephp/cakephp-codesniffer": "^5.0", "composer/composer": "^2.0", - "phpunit/phpunit": "^5.7 || ^6.5 || ^8.5 || ^9.3" + "phpunit/phpunit": "^10.1.0" }, "type": "composer-plugin", "extra": { @@ -282,22 +292,22 @@ "description": "A composer installer for CakePHP 3.0+ plugins.", "support": { "issues": "https://github.com/cakephp/plugin-installer/issues", - "source": "https://github.com/cakephp/plugin-installer/tree/1.3.1" + "source": "https://github.com/cakephp/plugin-installer/tree/2.0.1" }, - "time": "2020-10-29T04:00:42+00:00" + "time": "2023-09-10T10:02:44+00:00" }, { "name": "composer/ca-bundle", - "version": "1.5.7", + "version": "1.5.8", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "d665d22c417056996c59019579f1967dfe5c1e82" + "reference": "719026bb30813accb68271fee7e39552a58e9f65" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/d665d22c417056996c59019579f1967dfe5c1e82", - "reference": "d665d22c417056996c59019579f1967dfe5c1e82", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/719026bb30813accb68271fee7e39552a58e9f65", + "reference": "719026bb30813accb68271fee7e39552a58e9f65", "shasum": "" }, "require": { @@ -344,7 +354,7 @@ "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.7" + "source": "https://github.com/composer/ca-bundle/tree/1.5.8" }, "funding": [ { @@ -354,26 +364,22 @@ { "url": "https://github.com/composer", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" } ], - "time": "2025-05-26T15:08:54+00:00" + "time": "2025-08-20T18:49:47+00:00" }, { "name": "doctrine/dbal", - "version": "3.10.0", + "version": "3.10.1", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "1cf840d696373ea0d58ad0a8875c0fadcfc67214" + "reference": "3626601014388095d3af9de7e9e958623b7ef005" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/1cf840d696373ea0d58ad0a8875c0fadcfc67214", - "reference": "1cf840d696373ea0d58ad0a8875c0fadcfc67214", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/3626601014388095d3af9de7e9e958623b7ef005", + "reference": "3626601014388095d3af9de7e9e958623b7ef005", "shasum": "" }, "require": { @@ -458,7 +464,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.10.0" + "source": "https://github.com/doctrine/dbal/tree/3.10.1" }, "funding": [ { @@ -474,7 +480,7 @@ "type": "tidelift" } ], - "time": "2025-07-10T21:11:04+00:00" + "time": "2025-08-05T12:18:06+00:00" }, { "name": "doctrine/deprecations", @@ -617,41 +623,41 @@ }, { "name": "laminas/laminas-diactoros", - "version": "2.26.0", + "version": "3.6.0", "source": { "type": "git", "url": "https://github.com/laminas/laminas-diactoros.git", - "reference": "6584d44eb8e477e89d453313b858daac6183cddc" + "reference": "b068eac123f21c0e592de41deeb7403b88e0a89f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/6584d44eb8e477e89d453313b858daac6183cddc", - "reference": "6584d44eb8e477e89d453313b858daac6183cddc", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/b068eac123f21c0e592de41deeb7403b88e0a89f", + "reference": "b068eac123f21c0e592de41deeb7403b88e0a89f", "shasum": "" }, "require": { - "php": "~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.1" + "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0", + "psr/http-factory": "^1.1", + "psr/http-message": "^1.1 || ^2.0" }, "conflict": { - "zendframework/zend-diactoros": "*" + "amphp/amp": "<2.6.4" }, "provide": { - "psr/http-factory-implementation": "1.0", - "psr/http-message-implementation": "1.0" + "psr/http-factory-implementation": "^1.0", + "psr/http-message-implementation": "^1.1 || ^2.0" }, "require-dev": { "ext-curl": "*", "ext-dom": "*", "ext-gd": "*", "ext-libxml": "*", - "http-interop/http-factory-tests": "^0.9.0", - "laminas/laminas-coding-standard": "^2.5", - "php-http/psr7-integration-tests": "^1.2", - "phpunit/phpunit": "^9.5.28", - "psalm/plugin-phpunit": "^0.18.4", - "vimeo/psalm": "^5.6" + "http-interop/http-factory-tests": "^2.2.0", + "laminas/laminas-coding-standard": "~3.0.0", + "php-http/psr7-integration-tests": "^1.4.0", + "phpunit/phpunit": "^10.5.36", + "psalm/plugin-phpunit": "^0.19.0", + "vimeo/psalm": "^5.26.1" }, "type": "library", "extra": { @@ -666,18 +672,9 @@ "src/functions/marshal_headers_from_sapi.php", "src/functions/marshal_method_from_sapi.php", "src/functions/marshal_protocol_version_from_sapi.php", - "src/functions/marshal_uri_from_sapi.php", "src/functions/normalize_server.php", "src/functions/normalize_uploaded_files.php", - "src/functions/parse_cookie_header.php", - "src/functions/create_uploaded_file.legacy.php", - "src/functions/marshal_headers_from_sapi.legacy.php", - "src/functions/marshal_method_from_sapi.legacy.php", - "src/functions/marshal_protocol_version_from_sapi.legacy.php", - "src/functions/marshal_uri_from_sapi.legacy.php", - "src/functions/normalize_server.legacy.php", - "src/functions/normalize_uploaded_files.legacy.php", - "src/functions/parse_cookie_header.legacy.php" + "src/functions/parse_cookie_header.php" ], "psr-4": { "Laminas\\Diactoros\\": "src/" @@ -710,7 +707,7 @@ "type": "community_bridge" } ], - "time": "2023-10-29T16:17:44+00:00" + "time": "2025-05-05T16:03:34+00:00" }, { "name": "laminas/laminas-httphandlerrunner", @@ -1104,32 +1101,35 @@ }, { "name": "mobiledetect/mobiledetectlib", - "version": "2.8.45", + "version": "4.8.09", "source": { "type": "git", "url": "https://github.com/serbanghita/Mobile-Detect.git", - "reference": "96aaebcf4f50d3d2692ab81d2c5132e425bca266" + "reference": "a06fe2e546a06bb8c2639d6823d5250b2efb3209" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/serbanghita/Mobile-Detect/zipball/96aaebcf4f50d3d2692ab81d2c5132e425bca266", - "reference": "96aaebcf4f50d3d2692ab81d2c5132e425bca266", + "url": "https://api.github.com/repos/serbanghita/Mobile-Detect/zipball/a06fe2e546a06bb8c2639d6823d5250b2efb3209", + "reference": "a06fe2e546a06bb8c2639d6823d5250b2efb3209", "shasum": "" }, "require": { - "php": ">=5.0.0" + "php": ">=8.0", + "psr/cache": "^3.0", + "psr/simple-cache": "^3" }, "require-dev": { - "phpunit/phpunit": "~4.8.36" + "friendsofphp/php-cs-fixer": "^v3.65.0", + "phpbench/phpbench": "^1.2", + "phpstan/phpstan": "^1.12.x-dev", + "phpunit/phpunit": "^9.6.18", + "squizlabs/php_codesniffer": "^3.11.1" }, "type": "library", "autoload": { - "psr-0": { - "Detection": "namespaced/" - }, - "classmap": [ - "Mobile_Detect.php" - ] + "psr-4": { + "Detection\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1154,7 +1154,7 @@ ], "support": { "issues": "https://github.com/serbanghita/Mobile-Detect/issues", - "source": "https://github.com/serbanghita/Mobile-Detect/tree/2.8.45" + "source": "https://github.com/serbanghita/Mobile-Detect/tree/4.8.09" }, "funding": [ { @@ -1162,7 +1162,7 @@ "type": "github" } ], - "time": "2023-11-07T21:57:25+00:00" + "time": "2024-12-10T15:32:06+00:00" }, { "name": "psr/cache", @@ -1213,6 +1213,54 @@ }, "time": "2021-02-03T23:26:27+00:00" }, + { + "name": "psr/clock", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/clock.git", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Clock\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for reading the clock.", + "homepage": "https://github.com/php-fig/clock", + "keywords": [ + "clock", + "now", + "psr", + "psr-20", + "time" + ], + "support": { + "issues": "https://github.com/php-fig/clock/issues", + "source": "https://github.com/php-fig/clock/tree/1.0.0" + }, + "time": "2022-11-25T14:36:26+00:00" + }, { "name": "psr/container", "version": "2.0.2", @@ -1375,16 +1423,16 @@ }, { "name": "psr/http-message", - "version": "1.1", + "version": "2.0", "source": { "type": "git", "url": "https://github.com/php-fig/http-message.git", - "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba" + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/cb6ce4845ce34a8ad9e68117c10ee90a29919eba", - "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", "shasum": "" }, "require": { @@ -1393,7 +1441,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { @@ -1408,7 +1456,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for HTTP messages", @@ -1422,9 +1470,9 @@ "response" ], "support": { - "source": "https://github.com/php-fig/http-message/tree/1.1" + "source": "https://github.com/php-fig/http-message/tree/2.0" }, - "time": "2023-04-04T09:50:52+00:00" + "time": "2023-04-04T09:54:51+00:00" }, { "name": "psr/http-server-handler", @@ -1541,16 +1589,16 @@ }, { "name": "psr/log", - "version": "2.0.0", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "ef29f6d262798707a9edd554e2b82517ef3a9376" + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/ef29f6d262798707a9edd554e2b82517ef3a9376", - "reference": "ef29f6d262798707a9edd554e2b82517ef3a9376", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", "shasum": "" }, "require": { @@ -1559,7 +1607,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "3.x-dev" } }, "autoload": { @@ -1585,22 +1633,22 @@ "psr-3" ], "support": { - "source": "https://github.com/php-fig/log/tree/2.0.0" + "source": "https://github.com/php-fig/log/tree/3.0.2" }, - "time": "2021-07-14T16:41:46+00:00" + "time": "2024-09-11T13:17:53+00:00" }, { "name": "psr/simple-cache", - "version": "2.0.0", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/php-fig/simple-cache.git", - "reference": "8707bf3cea6f710bf6ef05491234e3ab06f6432a" + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/8707bf3cea6f710bf6ef05491234e3ab06f6432a", - "reference": "8707bf3cea6f710bf6ef05491234e3ab06f6432a", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865", "shasum": "" }, "require": { @@ -1609,7 +1657,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "3.0.x-dev" } }, "autoload": { @@ -1636,38 +1684,39 @@ "simple-cache" ], "support": { - "source": "https://github.com/php-fig/simple-cache/tree/2.0.0" + "source": "https://github.com/php-fig/simple-cache/tree/3.0.0" }, - "time": "2021-10-29T13:22:09+00:00" + "time": "2021-10-29T13:26:27+00:00" }, { "name": "robmorgan/phinx", - "version": "0.13.4", + "version": "0.16.10", "source": { "type": "git", "url": "https://github.com/cakephp/phinx.git", - "reference": "18e06e4a2b18947663438afd2f467e17c62e867d" + "reference": "83f83ec105e55e3abba7acc23c0272b5fcf66929" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/cakephp/phinx/zipball/18e06e4a2b18947663438afd2f467e17c62e867d", - "reference": "18e06e4a2b18947663438afd2f467e17c62e867d", + "url": "https://api.github.com/repos/cakephp/phinx/zipball/83f83ec105e55e3abba7acc23c0272b5fcf66929", + "reference": "83f83ec105e55e3abba7acc23c0272b5fcf66929", "shasum": "" }, "require": { - "cakephp/database": "^4.0", - "php": ">=7.2", - "psr/container": "^1.0 || ^2.0", - "symfony/config": "^3.4|^4.0|^5.0|^6.0", - "symfony/console": "^3.4|^4.0|^5.0|^6.0" + "cakephp/database": "^5.0.2", + "composer-runtime-api": "^2.0", + "php-64bit": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/config": "^4.0|^5.0|^6.0|^7.0", + "symfony/console": "^6.0|^7.0" }, "require-dev": { - "cakephp/cakephp-codesniffer": "^4.0", + "cakephp/cakephp-codesniffer": "^5.0", + "cakephp/i18n": "^5.0", "ext-json": "*", "ext-pdo": "*", - "phpunit/phpunit": "^8.5|^9.3", - "sebastian/comparator": ">=1.2.3", - "symfony/yaml": "^3.4|^4.0|^5.0" + "phpunit/phpunit": "^9.5.19", + "symfony/yaml": "^4.0|^5.0|^6.0|^7.0" }, "suggest": { "ext-json": "Install if using JSON configuration format", @@ -1722,40 +1771,40 @@ ], "support": { "issues": "https://github.com/cakephp/phinx/issues", - "source": "https://github.com/cakephp/phinx/tree/0.13.4" + "source": "https://github.com/cakephp/phinx/tree/0.16.10" }, - "time": "2023-01-07T00:42:55+00:00" + "time": "2025-07-08T18:55:28+00:00" }, { "name": "symfony/config", - "version": "v6.4.24", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "80e2cf005cf17138c97193be0434cdcfd1b2212e" + "reference": "faef36e271bbeb74a9d733be4b56419b157762e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/80e2cf005cf17138c97193be0434cdcfd1b2212e", - "reference": "80e2cf005cf17138c97193be0434cdcfd1b2212e", + "url": "https://api.github.com/repos/symfony/config/zipball/faef36e271bbeb74a9d733be4b56419b157762e2", + "reference": "faef36e271bbeb74a9d733be4b56419b157762e2", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/filesystem": "^5.4|^6.0|^7.0", + "symfony/filesystem": "^7.1", "symfony/polyfill-ctype": "~1.8" }, "conflict": { - "symfony/finder": "<5.4", + "symfony/finder": "<6.4", "symfony/service-contracts": "<2.5" }, "require-dev": { - "symfony/event-dispatcher": "^5.4|^6.0|^7.0", - "symfony/finder": "^5.4|^6.0|^7.0", - "symfony/messenger": "^5.4|^6.0|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", "symfony/service-contracts": "^2.5|^3", - "symfony/yaml": "^5.4|^6.0|^7.0" + "symfony/yaml": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -1783,7 +1832,7 @@ "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/config/tree/v6.4.24" + "source": "https://github.com/symfony/config/tree/v7.3.2" }, "funding": [ { @@ -1803,51 +1852,51 @@ "type": "tidelift" } ], - "time": "2025-07-26T13:50:30+00:00" + "time": "2025-07-26T13:55:06+00:00" }, { "name": "symfony/console", - "version": "v6.4.24", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "59266a5bf6a596e3e0844fd95e6ad7ea3c1d3350" + "reference": "cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/59266a5bf6a596e3e0844fd95e6ad7ea3c1d3350", - "reference": "59266a5bf6a596e3e0844fd95e6ad7ea3c1d3350", + "url": "https://api.github.com/repos/symfony/console/zipball/cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7", + "reference": "cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.0", "symfony/service-contracts": "^2.5|^3", - "symfony/string": "^5.4|^6.0|^7.0" + "symfony/string": "^7.2" }, "conflict": { - "symfony/dependency-injection": "<5.4", - "symfony/dotenv": "<5.4", - "symfony/event-dispatcher": "<5.4", - "symfony/lock": "<5.4", - "symfony/process": "<5.4" + "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": "^5.4|^6.0|^7.0", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", - "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", "symfony/http-foundation": "^6.4|^7.0", "symfony/http-kernel": "^6.4|^7.0", - "symfony/lock": "^5.4|^6.0|^7.0", - "symfony/messenger": "^5.4|^6.0|^7.0", - "symfony/process": "^5.4|^6.0|^7.0", - "symfony/stopwatch": "^5.4|^6.0|^7.0", - "symfony/var-dumper": "^5.4|^6.0|^7.0" + "symfony/lock": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -1881,7 +1930,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.4.24" + "source": "https://github.com/symfony/console/tree/v7.3.3" }, "funding": [ { @@ -1901,7 +1950,7 @@ "type": "tidelift" } ], - "time": "2025-07-30T10:38:54+00:00" + "time": "2025-08-25T06:35:40+00:00" }, { "name": "symfony/deprecation-contracts", @@ -2042,16 +2091,16 @@ }, { "name": "symfony/html-sanitizer", - "version": "v7.3.2", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/html-sanitizer.git", - "reference": "3388e208450fcac57d24aef4d5ae41037b663630" + "reference": "8740fc48979f649dee8b8fc51a2698e5c190bf12" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/html-sanitizer/zipball/3388e208450fcac57d24aef4d5ae41037b663630", - "reference": "3388e208450fcac57d24aef4d5ae41037b663630", + "url": "https://api.github.com/repos/symfony/html-sanitizer/zipball/8740fc48979f649dee8b8fc51a2698e5c190bf12", + "reference": "8740fc48979f649dee8b8fc51a2698e5c190bf12", "shasum": "" }, "require": { @@ -2091,7 +2140,7 @@ "sanitizer" ], "support": { - "source": "https://github.com/symfony/html-sanitizer/tree/v7.3.2" + "source": "https://github.com/symfony/html-sanitizer/tree/v7.3.3" }, "funding": [ { @@ -2111,11 +2160,11 @@ "type": "tidelift" } ], - "time": "2025-07-10T08:29:33+00:00" + "time": "2025-08-12T10:34:03+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", @@ -2174,7 +2223,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0" }, "funding": [ { @@ -2185,6 +2234,10 @@ "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" @@ -2194,16 +2247,16 @@ }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", - "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", "shasum": "" }, "require": { @@ -2252,7 +2305,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0" }, "funding": [ { @@ -2263,16 +2316,20 @@ "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" + "time": "2025-06-27T09:58:17+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", @@ -2333,7 +2390,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" }, "funding": [ { @@ -2344,6 +2401,10 @@ "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" @@ -2353,7 +2414,7 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", @@ -2414,7 +2475,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" }, "funding": [ { @@ -2425,6 +2486,10 @@ "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" @@ -2517,16 +2582,16 @@ }, { "name": "symfony/string", - "version": "v7.3.2", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "42f505aff654e62ac7ac2ce21033818297ca89ca" + "reference": "17a426cce5fd1f0901fefa9b2a490d0038fd3c9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/42f505aff654e62ac7ac2ce21033818297ca89ca", - "reference": "42f505aff654e62ac7ac2ce21033818297ca89ca", + "url": "https://api.github.com/repos/symfony/string/zipball/17a426cce5fd1f0901fefa9b2a490d0038fd3c9c", + "reference": "17a426cce5fd1f0901fefa9b2a490d0038fd3c9c", "shasum": "" }, "require": { @@ -2584,7 +2649,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.3.2" + "source": "https://github.com/symfony/string/tree/v7.3.3" }, "funding": [ { @@ -2604,32 +2669,32 @@ "type": "tidelift" } ], - "time": "2025-07-10T08:47:49+00:00" + "time": "2025-08-25T06:35:40+00:00" } ], "packages-dev": [ { "name": "brick/varexporter", - "version": "0.3.8", + "version": "0.6.0", "source": { "type": "git", "url": "https://github.com/brick/varexporter.git", - "reference": "b5853edea6204ff8fa10633c3a4cccc4058410ed" + "reference": "af98bfc2b702a312abbcaff37656dbe419cec5bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/varexporter/zipball/b5853edea6204ff8fa10633c3a4cccc4058410ed", - "reference": "b5853edea6204ff8fa10633c3a4cccc4058410ed", + "url": "https://api.github.com/repos/brick/varexporter/zipball/af98bfc2b702a312abbcaff37656dbe419cec5bc", + "reference": "af98bfc2b702a312abbcaff37656dbe419cec5bc", "shasum": "" }, "require": { - "nikic/php-parser": "^4.0", - "php": "^7.2 || ^8.0" + "nikic/php-parser": "^5.0", + "php": "^8.1" }, "require-dev": { "php-coveralls/php-coveralls": "^2.2", - "phpunit/phpunit": "^8.5 || ^9.0", - "vimeo/psalm": "4.23.0" + "phpunit/phpunit": "^10.5", + "vimeo/psalm": "6.8.4" }, "type": "library", "autoload": { @@ -2647,7 +2712,7 @@ ], "support": { "issues": "https://github.com/brick/varexporter/issues", - "source": "https://github.com/brick/varexporter/tree/0.3.8" + "source": "https://github.com/brick/varexporter/tree/0.6.0" }, "funding": [ { @@ -2655,34 +2720,33 @@ "type": "github" } ], - "time": "2023-01-21T23:05:38+00:00" + "time": "2025-02-20T17:42:39+00:00" }, { "name": "cakephp/bake", - "version": "2.9.3", + "version": "3.4.0", "source": { "type": "git", "url": "https://github.com/cakephp/bake.git", - "reference": "a9b02fb6a5f96e8fb9887be55cccea501468907b" + "reference": "a0d2b96486dc5b8547b8e358cd1e218b9ed07d9d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/cakephp/bake/zipball/a9b02fb6a5f96e8fb9887be55cccea501468907b", - "reference": "a9b02fb6a5f96e8fb9887be55cccea501468907b", + "url": "https://api.github.com/repos/cakephp/bake/zipball/a0d2b96486dc5b8547b8e358cd1e218b9ed07d9d", + "reference": "a0d2b96486dc5b8547b8e358cd1e218b9ed07d9d", "shasum": "" }, "require": { - "brick/varexporter": "^0.3.5", - "cakephp/cakephp": "^4.3.0", - "cakephp/twig-view": "^1.0.2", - "nikic/php-parser": "^4.13.2", - "php": ">=7.2" + "brick/varexporter": "^0.6.0", + "cakephp/cakephp": "^5.1", + "cakephp/twig-view": "^2.0.0", + "nikic/php-parser": "^5.0.0", + "php": ">=8.1" }, "require-dev": { - "cakephp/cakephp-codesniffer": "^4.0", - "cakephp/debug_kit": "^4.1", - "cakephp/plugin-installer": "^1.3", - "phpunit/phpunit": "^8.5 || ^9.3" + "cakephp/cakephp-codesniffer": "^5.0.0", + "cakephp/debug_kit": "^5.0.0", + "phpunit/phpunit": "^10.5.5 || ^11.1.3" }, "type": "cakephp-plugin", "autoload": { @@ -2704,37 +2768,39 @@ "homepage": "https://github.com/cakephp/bake", "keywords": [ "bake", - "cakephp" + "cakephp", + "cli", + "dev" ], "support": { "forum": "https://stackoverflow.com/tags/cakephp", - "irc": "irc://irc.freenode.org/cakephp", "issues": "https://github.com/cakephp/bake/issues", "source": "https://github.com/cakephp/bake" }, - "time": "2023-03-18T19:26:16+00:00" + "time": "2025-06-25T19:18:09+00:00" }, { "name": "cakephp/cakephp-codesniffer", - "version": "4.7.1", + "version": "5.2.2", "source": { "type": "git", "url": "https://github.com/cakephp/cakephp-codesniffer.git", - "reference": "d09f5945bec4bf82581c52eabe7cea7b92cba6e3" + "reference": "3c62979556e98d0330f8af8b103ad1ade0a7067e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/cakephp/cakephp-codesniffer/zipball/d09f5945bec4bf82581c52eabe7cea7b92cba6e3", - "reference": "d09f5945bec4bf82581c52eabe7cea7b92cba6e3", + "url": "https://api.github.com/repos/cakephp/cakephp-codesniffer/zipball/3c62979556e98d0330f8af8b103ad1ade0a7067e", + "reference": "3c62979556e98d0330f8af8b103ad1ade0a7067e", "shasum": "" }, "require": { - "php": ">=7.2.0", - "slevomat/coding-standard": "^7.0 || ^8.0", - "squizlabs/php_codesniffer": "^3.6" + "php": ">=8.1.0", + "phpstan/phpdoc-parser": "^2.1.0", + "slevomat/coding-standard": "^8.16", + "squizlabs/php_codesniffer": "^3.9" }, "require-dev": { - "phpunit/phpunit": "^7.1" + "phpunit/phpunit": "^9.3.4" }, "type": "phpcodesniffer-standard", "autoload": { @@ -2764,33 +2830,32 @@ "issues": "https://github.com/cakephp/cakephp-codesniffer/issues", "source": "https://github.com/cakephp/cakephp-codesniffer" }, - "time": "2025-07-05T09:11:50+00:00" + "time": "2025-06-07T17:04:07+00:00" }, { "name": "cakephp/debug_kit", - "version": "4.10.2", + "version": "5.1.3", "source": { "type": "git", "url": "https://github.com/cakephp/debug_kit.git", - "reference": "49c841e4b2b89e4d1cb7c3ce00d27e3d5f2bdbd4" + "reference": "6c33eb2993d790d98a27a47a74a10330d699ac07" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/cakephp/debug_kit/zipball/49c841e4b2b89e4d1cb7c3ce00d27e3d5f2bdbd4", - "reference": "49c841e4b2b89e4d1cb7c3ce00d27e3d5f2bdbd4", + "url": "https://api.github.com/repos/cakephp/debug_kit/zipball/6c33eb2993d790d98a27a47a74a10330d699ac07", + "reference": "6c33eb2993d790d98a27a47a74a10330d699ac07", "shasum": "" }, "require": { - "cakephp/cakephp": "^4.5.0", - "cakephp/chronos": "^2.0", - "composer/composer": "^1.3 | ^2.0", + "cakephp/cakephp": "^5.1", + "composer/composer": "^2.0", "doctrine/sql-formatter": "^1.1.3", - "php": ">=7.4" + "php": ">=8.1" }, "require-dev": { - "cakephp/authorization": "^2.0", - "cakephp/cakephp-codesniffer": "^4.0", - "phpunit/phpunit": "~8.5.0 | ^9.3" + "cakephp/authorization": "^3.0", + "cakephp/cakephp-codesniffer": "^5.0", + "phpunit/phpunit": "^10.5.5 || ^11.1.3" }, "suggest": { "ext-pdo_sqlite": "DebugKit needs to store panel data in a database. SQLite is simple and easy to use." @@ -2798,8 +2863,7 @@ "type": "cakephp-plugin", "autoload": { "psr-4": { - "DebugKit\\": "src/", - "DebugKit\\Test\\Fixture\\": "tests/Fixture/" + "DebugKit\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -2831,38 +2895,37 @@ "issues": "https://github.com/cakephp/debug_kit/issues", "source": "https://github.com/cakephp/debug_kit" }, - "time": "2023-12-15T20:59:05+00:00" + "time": "2025-08-03T15:06:57+00:00" }, { "name": "cakephp/twig-view", - "version": "1.3.1", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/cakephp/twig-view.git", - "reference": "e4a18e91e004730ccbaf796bde60612e8afac0e8" + "reference": "b11df8e8734ae556d98b143192377dbc6a6f5360" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/cakephp/twig-view/zipball/e4a18e91e004730ccbaf796bde60612e8afac0e8", - "reference": "e4a18e91e004730ccbaf796bde60612e8afac0e8", + "url": "https://api.github.com/repos/cakephp/twig-view/zipball/b11df8e8734ae556d98b143192377dbc6a6f5360", + "reference": "b11df8e8734ae556d98b143192377dbc6a6f5360", "shasum": "" }, "require": { - "cakephp/cakephp": "^4.0", + "cakephp/cakephp": "^5.0.0", "jasny/twig-extensions": "^1.3", "twig/markdown-extra": "^3.0", - "twig/twig": "^3.11.0" + "twig/twig": "^3.11.1" }, "conflict": { "wyrihaximus/twig-view": "*" }, "require-dev": { - "cakephp/cakephp-codesniffer": "^4.0", - "cakephp/debug_kit": "^4.0", - "cakephp/plugin-installer": "^1.3", + "cakephp/cakephp-codesniffer": "^5.0", + "cakephp/debug_kit": "^5.0", "michelf/php-markdown": "^1.9", - "mikey179/vfsstream": "^1.6", - "phpunit/phpunit": "^8.5 || ^9.3" + "mikey179/vfsstream": "^1.6.10", + "phpunit/phpunit": "^10.5.5 || ^11.1.3" }, "type": "cakephp-plugin", "autoload": { @@ -2893,20 +2956,20 @@ "issues": "https://github.com/cakephp/twig-view/issues", "source": "https://github.com/cakephp/twig-view" }, - "time": "2024-10-11T06:25:59+00:00" + "time": "2024-10-11T07:53:08+00:00" }, { "name": "composer/class-map-generator", - "version": "1.6.1", + "version": "1.6.2", "source": { "type": "git", "url": "https://github.com/composer/class-map-generator.git", - "reference": "134b705ddb0025d397d8318a75825fe3c9d1da34" + "reference": "ba9f089655d4cdd64e762a6044f411ccdaec0076" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/class-map-generator/zipball/134b705ddb0025d397d8318a75825fe3c9d1da34", - "reference": "134b705ddb0025d397d8318a75825fe3c9d1da34", + "url": "https://api.github.com/repos/composer/class-map-generator/zipball/ba9f089655d4cdd64e762a6044f411ccdaec0076", + "reference": "ba9f089655d4cdd64e762a6044f411ccdaec0076", "shasum": "" }, "require": { @@ -2950,7 +3013,7 @@ ], "support": { "issues": "https://github.com/composer/class-map-generator/issues", - "source": "https://github.com/composer/class-map-generator/tree/1.6.1" + "source": "https://github.com/composer/class-map-generator/tree/1.6.2" }, "funding": [ { @@ -2960,26 +3023,22 @@ { "url": "https://github.com/composer", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" } ], - "time": "2025-03-24T13:50:44+00:00" + "time": "2025-08-20T18:52:43+00:00" }, { "name": "composer/composer", - "version": "2.8.10", + "version": "2.8.11", "source": { "type": "git", "url": "https://github.com/composer/composer.git", - "reference": "53834f587d7ab2527eb237459d7b94d1fb9d4c5a" + "reference": "00e1a3396eea67033775c4a49c772376f45acd73" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/composer/zipball/53834f587d7ab2527eb237459d7b94d1fb9d4c5a", - "reference": "53834f587d7ab2527eb237459d7b94d1fb9d4c5a", + "url": "https://api.github.com/repos/composer/composer/zipball/00e1a3396eea67033775c4a49c772376f45acd73", + "reference": "00e1a3396eea67033775c4a49c772376f45acd73", "shasum": "" }, "require": { @@ -2993,7 +3052,7 @@ "justinrainbow/json-schema": "^6.3.1", "php": "^7.2.5 || ^8.0", "psr/log": "^1.0 || ^2.0 || ^3.0", - "react/promise": "^2.11 || ^3.2", + "react/promise": "^2.11 || ^3.3", "seld/jsonlint": "^1.4", "seld/phar-utils": "^1.2", "seld/signal-handler": "^2.0", @@ -3064,7 +3123,7 @@ "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.8.10" + "source": "https://github.com/composer/composer/tree/2.8.11" }, "funding": [ { @@ -3074,13 +3133,9 @@ { "url": "https://github.com/composer", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" } ], - "time": "2025-07-10T17:08:33+00:00" + "time": "2025-08-21T09:29:39+00:00" }, { "name": "composer/metadata-minifier", @@ -3232,16 +3287,16 @@ }, { "name": "composer/semver", - "version": "3.4.3", + "version": "3.4.4", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12" + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", - "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95", "shasum": "" }, "require": { @@ -3293,7 +3348,7 @@ "support": { "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.4.3" + "source": "https://github.com/composer/semver/tree/3.4.4" }, "funding": [ { @@ -3303,13 +3358,9 @@ { "url": "https://github.com/composer", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" } ], - "time": "2024-09-19T14:15:21+00:00" + "time": "2025-08-20T19:15:30+00:00" }, { "name": "composer/spdx-licenses", @@ -3553,76 +3604,6 @@ ], "time": "2025-07-17T20:45:56+00:00" }, - { - "name": "doctrine/instantiator", - "version": "2.0.0", - "source": { - "type": "git", - "url": "https://github.com/doctrine/instantiator.git", - "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", - "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", - "shasum": "" - }, - "require": { - "php": "^8.1" - }, - "require-dev": { - "doctrine/coding-standard": "^11", - "ext-pdo": "*", - "ext-phar": "*", - "phpbench/phpbench": "^1.2", - "phpstan/phpstan": "^1.9.4", - "phpstan/phpstan-phpunit": "^1.3", - "phpunit/phpunit": "^9.5.27", - "vimeo/psalm": "^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/2.0.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:23:10+00:00" - }, { "name": "doctrine/sql-formatter", "version": "1.5.2", @@ -3746,16 +3727,16 @@ }, { "name": "josegonzalez/dotenv", - "version": "3.2.0", + "version": "4.0.0", "source": { "type": "git", "url": "https://github.com/josegonzalez/php-dotenv.git", - "reference": "f19174d9d7213a6c20e8e5e268aa7dd042d821ca" + "reference": "e97dbd3db53508dcd536e73ec787a7f11458d41d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/josegonzalez/php-dotenv/zipball/f19174d9d7213a6c20e8e5e268aa7dd042d821ca", - "reference": "f19174d9d7213a6c20e8e5e268aa7dd042d821ca", + "url": "https://api.github.com/repos/josegonzalez/php-dotenv/zipball/e97dbd3db53508dcd536e73ec787a7f11458d41d", + "reference": "e97dbd3db53508dcd536e73ec787a7f11458d41d", "shasum": "" }, "require": { @@ -3763,9 +3744,9 @@ "php": ">=5.5.0" }, "require-dev": { - "php-mock/php-mock-phpunit": "^1.1", - "satooshi/php-coveralls": "1.*", - "squizlabs/php_codesniffer": "2.*" + "php-coveralls/php-coveralls": "~2.0", + "php-mock/php-mock-phpunit": "~1.1||~2.0", + "squizlabs/php_codesniffer": "~2.9||~3.7" }, "type": "library", "autoload": { @@ -3797,22 +3778,22 @@ ], "support": { "issues": "https://github.com/josegonzalez/php-dotenv/issues", - "source": "https://github.com/josegonzalez/php-dotenv/tree/master" + "source": "https://github.com/josegonzalez/php-dotenv/tree/4.0.0" }, - "time": "2017-09-19T15:49:58+00:00" + "time": "2023-05-29T22:49:26+00:00" }, { "name": "justinrainbow/json-schema", - "version": "6.4.2", + "version": "6.5.1", "source": { "type": "git", "url": "https://github.com/jsonrainbow/json-schema.git", - "reference": "ce1fd2d47799bb60668643bc6220f6278a4c1d02" + "reference": "b5ab21e431594897e5bb86343c01f140ba862c26" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/ce1fd2d47799bb60668643bc6220f6278a4c1d02", - "reference": "ce1fd2d47799bb60668643bc6220f6278a4c1d02", + "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/b5ab21e431594897e5bb86343c01f140ba862c26", + "reference": "b5ab21e431594897e5bb86343c01f140ba862c26", "shasum": "" }, "require": { @@ -3822,7 +3803,7 @@ }, "require-dev": { "friendsofphp/php-cs-fixer": "3.3.0", - "json-schema/json-schema-test-suite": "1.2.0", + "json-schema/json-schema-test-suite": "^23.2", "marc-mabe/php-enum-phpstan": "^2.0", "phpspec/prophecy": "^1.19", "phpstan/phpstan": "^1.12", @@ -3872,9 +3853,9 @@ ], "support": { "issues": "https://github.com/jsonrainbow/json-schema/issues", - "source": "https://github.com/jsonrainbow/json-schema/tree/6.4.2" + "source": "https://github.com/jsonrainbow/json-schema/tree/6.5.1" }, - "time": "2025-06-03T18:27:04+00:00" + "time": "2025-08-29T10:58:11+00:00" }, { "name": "m1/env", @@ -4073,25 +4054,27 @@ }, { "name": "nikic/php-parser", - "version": "v4.19.4", + "version": "v5.6.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "715f4d25e225bc47b293a8b997fe6ce99bf987d2" + "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/715f4d25e225bc47b293a8b997fe6ce99bf987d2", - "reference": "715f4d25e225bc47b293a8b997fe6ce99bf987d2", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2", + "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2", "shasum": "" }, "require": { + "ext-ctype": "*", + "ext-json": "*", "ext-tokenizer": "*", - "php": ">=7.1" + "php": ">=7.4" }, "require-dev": { "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + "phpunit/phpunit": "^9.0" }, "bin": [ "bin/php-parse" @@ -4099,7 +4082,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.9-dev" + "dev-master": "5.x-dev" } }, "autoload": { @@ -4123,9 +4106,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.19.4" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.1" }, - "time": "2024-09-29T15:01:53+00:00" + "time": "2025-08-13T20:13:15+00:00" }, { "name": "phar-io/manifest", @@ -4247,16 +4230,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "2.2.0", + "version": "2.3.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "b9e61a61e39e02dd90944e9115241c7f7e76bfd8" + "reference": "1e0cd5370df5dd2e556a36b9c62f62e555870495" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/b9e61a61e39e02dd90944e9115241c7f7e76bfd8", - "reference": "b9e61a61e39e02dd90944e9115241c7f7e76bfd8", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/1e0cd5370df5dd2e556a36b9c62f62e555870495", + "reference": "1e0cd5370df5dd2e556a36b9c62f62e555870495", "shasum": "" }, "require": { @@ -4288,41 +4271,40 @@ "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.2.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.0" }, - "time": "2025-07-13T07:04:09+00:00" + "time": "2025-08-30T15:50:23+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.32", + "version": "12.3.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5" + "reference": "7ad0e9bdc72b147600badccd694a2e57ffc9297a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/85402a822d1ecf1db1096959413d35e1c37cf1a5", - "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7ad0e9bdc72b147600badccd694a2e57ffc9297a", + "reference": "7ad0e9bdc72b147600badccd694a2e57ffc9297a", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.19.1 || ^5.1.0", - "php": ">=7.3", - "phpunit/php-file-iterator": "^3.0.6", - "phpunit/php-text-template": "^2.0.4", - "sebastian/code-unit-reverse-lookup": "^2.0.3", - "sebastian/complexity": "^2.0.3", - "sebastian/environment": "^5.1.5", - "sebastian/lines-of-code": "^1.0.4", - "sebastian/version": "^3.0.2", + "nikic/php-parser": "^5.4.0", + "php": ">=8.3", + "phpunit/php-file-iterator": "^6.0", + "phpunit/php-text-template": "^5.0", + "sebastian/complexity": "^5.0", + "sebastian/environment": "^8.0", + "sebastian/lines-of-code": "^4.0", + "sebastian/version": "^6.0", "theseer/tokenizer": "^1.2.3" }, "require-dev": { - "phpunit/phpunit": "^9.6" + "phpunit/phpunit": "^12.1" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -4331,7 +4313,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "9.2.x-dev" + "dev-main": "12.3.x-dev" } }, "autoload": { @@ -4360,40 +4342,52 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.32" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.3.4" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-code-coverage", + "type": "tidelift" } ], - "time": "2024-08-22T04:23:01+00:00" + "time": "2025-08-29T11:32:44+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "3.0.6", + "version": "6.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" + "reference": "961bc913d42fe24a257bfff826a5068079ac7782" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", - "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/961bc913d42fe24a257bfff826a5068079ac7782", + "reference": "961bc913d42fe24a257bfff826a5068079ac7782", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^12.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -4420,7 +4414,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" + "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/6.0.0" }, "funding": [ { @@ -4428,28 +4423,28 @@ "type": "github" } ], - "time": "2021-12-02T12:48:52+00:00" + "time": "2025-02-07T04:58:37+00:00" }, { "name": "phpunit/php-invoker", - "version": "3.1.1", + "version": "6.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-invoker.git", - "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" + "reference": "12b54e689b07a25a9b41e57736dfab6ec9ae5406" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", - "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/12b54e689b07a25a9b41e57736dfab6ec9ae5406", + "reference": "12b54e689b07a25a9b41e57736dfab6ec9ae5406", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.3" }, "require-dev": { "ext-pcntl": "*", - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^12.0" }, "suggest": { "ext-pcntl": "*" @@ -4457,7 +4452,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -4483,7 +4478,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-invoker/issues", - "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/6.0.0" }, "funding": [ { @@ -4491,32 +4487,32 @@ "type": "github" } ], - "time": "2020-09-28T05:58:55+00:00" + "time": "2025-02-07T04:58:58+00:00" }, { "name": "phpunit/php-text-template", - "version": "2.0.4", + "version": "5.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" + "reference": "e1367a453f0eda562eedb4f659e13aa900d66c53" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", - "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/e1367a453f0eda562eedb4f659e13aa900d66c53", + "reference": "e1367a453f0eda562eedb4f659e13aa900d66c53", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^12.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -4542,7 +4538,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-text-template/issues", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/5.0.0" }, "funding": [ { @@ -4550,32 +4547,32 @@ "type": "github" } ], - "time": "2020-10-26T05:33:50+00:00" + "time": "2025-02-07T04:59:16+00:00" }, { "name": "phpunit/php-timer", - "version": "5.0.3", + "version": "8.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" + "reference": "f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", - "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc", + "reference": "f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^12.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-main": "8.0-dev" } }, "autoload": { @@ -4601,7 +4598,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + "security": "https://github.com/sebastianbergmann/php-timer/security/policy", + "source": "https://github.com/sebastianbergmann/php-timer/tree/8.0.0" }, "funding": [ { @@ -4609,54 +4607,48 @@ "type": "github" } ], - "time": "2020-10-26T13:16:10+00:00" + "time": "2025-02-07T04:59:38+00:00" }, { "name": "phpunit/phpunit", - "version": "9.6.23", + "version": "12.3.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "43d2cb18d0675c38bd44982a5d1d88f6d53d8d95" + "reference": "b8fa997c49682979ad6bfaa0d7fb25f54954965e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/43d2cb18d0675c38bd44982a5d1d88f6d53d8d95", - "reference": "43d2cb18d0675c38bd44982a5d1d88f6d53d8d95", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b8fa997c49682979ad6bfaa0d7fb25f54954965e", + "reference": "b8fa997c49682979ad6bfaa0d7fb25f54954965e", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.5.0 || ^2", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.13.1", + "myclabs/deep-copy": "^1.13.4", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", - "php": ">=7.3", - "phpunit/php-code-coverage": "^9.2.32", - "phpunit/php-file-iterator": "^3.0.6", - "phpunit/php-invoker": "^3.1.1", - "phpunit/php-text-template": "^2.0.4", - "phpunit/php-timer": "^5.0.3", - "sebastian/cli-parser": "^1.0.2", - "sebastian/code-unit": "^1.0.8", - "sebastian/comparator": "^4.0.8", - "sebastian/diff": "^4.0.6", - "sebastian/environment": "^5.1.5", - "sebastian/exporter": "^4.0.6", - "sebastian/global-state": "^5.0.7", - "sebastian/object-enumerator": "^4.0.4", - "sebastian/resource-operations": "^3.0.4", - "sebastian/type": "^3.2.1", - "sebastian/version": "^3.0.2" - }, - "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" + "php": ">=8.3", + "phpunit/php-code-coverage": "^12.3.3", + "phpunit/php-file-iterator": "^6.0.0", + "phpunit/php-invoker": "^6.0.0", + "phpunit/php-text-template": "^5.0.0", + "phpunit/php-timer": "^8.0.0", + "sebastian/cli-parser": "^4.0.0", + "sebastian/comparator": "^7.1.3", + "sebastian/diff": "^7.0.0", + "sebastian/environment": "^8.0.3", + "sebastian/exporter": "^7.0.0", + "sebastian/global-state": "^8.0.0", + "sebastian/object-enumerator": "^7.0.0", + "sebastian/type": "^6.0.3", + "sebastian/version": "^6.0.0", + "staabm/side-effects-detector": "^1.0.5" }, "bin": [ "phpunit" @@ -4664,7 +4656,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "9.6-dev" + "dev-main": "12.3-dev" } }, "autoload": { @@ -4696,7 +4688,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.23" + "source": "https://github.com/sebastianbergmann/phpunit/tree/12.3.7" }, "funding": [ { @@ -4720,20 +4712,20 @@ "type": "tidelift" } ], - "time": "2025-05-02T06:40:34+00:00" + "time": "2025-08-28T05:15:46+00:00" }, { "name": "psy/psysh", - "version": "v0.12.9", + "version": "v0.12.10", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "1b801844becfe648985372cb4b12ad6840245ace" + "reference": "6e80abe6f2257121f1eb9a4c55bf29d921025b22" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/1b801844becfe648985372cb4b12ad6840245ace", - "reference": "1b801844becfe648985372cb4b12ad6840245ace", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/6e80abe6f2257121f1eb9a4c55bf29d921025b22", + "reference": "6e80abe6f2257121f1eb9a4c55bf29d921025b22", "shasum": "" }, "require": { @@ -4783,12 +4775,11 @@ "authors": [ { "name": "Justin Hileman", - "email": "justin@justinhileman.info", - "homepage": "http://justinhileman.com" + "email": "justin@justinhileman.info" } ], "description": "An interactive shell for modern PHP.", - "homepage": "http://psysh.org", + "homepage": "https://psysh.org", "keywords": [ "REPL", "console", @@ -4797,29 +4788,29 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.12.9" + "source": "https://github.com/bobthecow/psysh/tree/v0.12.10" }, - "time": "2025-06-23T02:35:06+00:00" + "time": "2025-08-04T12:39:37+00:00" }, { "name": "react/promise", - "version": "v3.2.0", + "version": "v3.3.0", "source": { "type": "git", "url": "https://github.com/reactphp/promise.git", - "reference": "8a164643313c71354582dc850b42b33fa12a4b63" + "reference": "23444f53a813a3296c1368bb104793ce8d88f04a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/promise/zipball/8a164643313c71354582dc850b42b33fa12a4b63", - "reference": "8a164643313c71354582dc850b42b33fa12a4b63", + "url": "https://api.github.com/repos/reactphp/promise/zipball/23444f53a813a3296c1368bb104793ce8d88f04a", + "reference": "23444f53a813a3296c1368bb104793ce8d88f04a", "shasum": "" }, "require": { "php": ">=7.1.0" }, "require-dev": { - "phpstan/phpstan": "1.10.39 || 1.4.10", + "phpstan/phpstan": "1.12.28 || 1.4.10", "phpunit/phpunit": "^9.6 || ^7.5" }, "type": "library", @@ -4864,7 +4855,7 @@ ], "support": { "issues": "https://github.com/reactphp/promise/issues", - "source": "https://github.com/reactphp/promise/tree/v3.2.0" + "source": "https://github.com/reactphp/promise/tree/v3.3.0" }, "funding": [ { @@ -4872,32 +4863,32 @@ "type": "open_collective" } ], - "time": "2024-05-24T10:39:05+00:00" + "time": "2025-08-19T18:57:03+00:00" }, { "name": "sebastian/cli-parser", - "version": "1.0.2", + "version": "4.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b" + "reference": "6d584c727d9114bcdc14c86711cd1cad51778e7c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2b56bea83a09de3ac06bb18b92f068e60cc6f50b", - "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/6d584c727d9114bcdc14c86711cd1cad51778e7c", + "reference": "6d584c727d9114bcdc14c86711cd1cad51778e7c", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^12.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -4920,7 +4911,8 @@ "homepage": "https://github.com/sebastianbergmann/cli-parser", "support": { "issues": "https://github.com/sebastianbergmann/cli-parser/issues", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.2" + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/4.0.0" }, "funding": [ { @@ -4928,145 +4920,39 @@ "type": "github" } ], - "time": "2024-03-02T06:27:43+00:00" - }, - { - "name": "sebastian/code-unit", - "version": "1.0.8", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit.git", - "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", - "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.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": "Collection of value objects that represent the PHP code units", - "homepage": "https://github.com/sebastianbergmann/code-unit", - "support": { - "issues": "https://github.com/sebastianbergmann/code-unit/issues", - "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-10-26T13:08:54+00:00" - }, - { - "name": "sebastian/code-unit-reverse-lookup", - "version": "2.0.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", - "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "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": "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/2.0.3" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-09-28T05:30:19+00:00" + "time": "2025-02-07T04:53:50+00:00" }, { "name": "sebastian/comparator", - "version": "4.0.8", + "version": "7.1.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "fa0f136dd2334583309d32b62544682ee972b51a" + "reference": "dc904b4bb3ab070865fa4068cd84f3da8b945148" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", - "reference": "fa0f136dd2334583309d32b62544682ee972b51a", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/dc904b4bb3ab070865fa4068cd84f3da8b945148", + "reference": "dc904b4bb3ab070865fa4068cd84f3da8b945148", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/diff": "^4.0", - "sebastian/exporter": "^4.0" + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.3", + "sebastian/diff": "^7.0", + "sebastian/exporter": "^7.0" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^12.2" + }, + "suggest": { + "ext-bcmath": "For comparing BcMath\\Number objects" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "7.1-dev" } }, "autoload": { @@ -5105,41 +4991,54 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", - "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" + "security": "https://github.com/sebastianbergmann/comparator/security/policy", + "source": "https://github.com/sebastianbergmann/comparator/tree/7.1.3" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" } ], - "time": "2022-09-14T12:41:17+00:00" + "time": "2025-08-20T11:27:00+00:00" }, { "name": "sebastian/complexity", - "version": "2.0.3", + "version": "5.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a" + "reference": "bad4316aba5303d0221f43f8cee37eb58d384bbb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a", - "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/bad4316aba5303d0221f43f8cee37eb58d384bbb", + "reference": "bad4316aba5303d0221f43f8cee37eb58d384bbb", "shasum": "" }, "require": { - "nikic/php-parser": "^4.18 || ^5.0", - "php": ">=7.3" + "nikic/php-parser": "^5.0", + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^12.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -5162,7 +5061,8 @@ "homepage": "https://github.com/sebastianbergmann/complexity", "support": { "issues": "https://github.com/sebastianbergmann/complexity/issues", - "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.3" + "security": "https://github.com/sebastianbergmann/complexity/security/policy", + "source": "https://github.com/sebastianbergmann/complexity/tree/5.0.0" }, "funding": [ { @@ -5170,33 +5070,33 @@ "type": "github" } ], - "time": "2023-12-22T06:19:30+00:00" + "time": "2025-02-07T04:55:25+00:00" }, { "name": "sebastian/diff", - "version": "4.0.6", + "version": "7.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc" + "reference": "7ab1ea946c012266ca32390913653d844ecd085f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc", - "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7ab1ea946c012266ca32390913653d844ecd085f", + "reference": "7ab1ea946c012266ca32390913653d844ecd085f", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^9.3", - "symfony/process": "^4.2 || ^5" + "phpunit/phpunit": "^12.0", + "symfony/process": "^7.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -5228,7 +5128,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/4.0.6" + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/7.0.0" }, "funding": [ { @@ -5236,27 +5137,27 @@ "type": "github" } ], - "time": "2024-03-02T06:30:58+00:00" + "time": "2025-02-07T04:55:46+00:00" }, { "name": "sebastian/environment", - "version": "5.1.5", + "version": "8.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" + "reference": "24a711b5c916efc6d6e62aa65aa2ec98fef77f68" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", - "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/24a711b5c916efc6d6e62aa65aa2ec98fef77f68", + "reference": "24a711b5c916efc6d6e62aa65aa2ec98fef77f68", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^12.0" }, "suggest": { "ext-posix": "*" @@ -5264,7 +5165,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.1-dev" + "dev-main": "8.0-dev" } }, "autoload": { @@ -5283,7 +5184,7 @@ } ], "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "http://www.github.com/sebastianbergmann/environment", + "homepage": "https://github.com/sebastianbergmann/environment", "keywords": [ "Xdebug", "environment", @@ -5291,42 +5192,55 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", - "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" + "security": "https://github.com/sebastianbergmann/environment/security/policy", + "source": "https://github.com/sebastianbergmann/environment/tree/8.0.3" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/environment", + "type": "tidelift" } ], - "time": "2023-02-03T06:03:51+00:00" + "time": "2025-08-12T14:11:56+00:00" }, { "name": "sebastian/exporter", - "version": "4.0.6", + "version": "7.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72" + "reference": "76432aafc58d50691a00d86d0632f1217a47b688" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/78c00df8f170e02473b682df15bfcdacc3d32d72", - "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/76432aafc58d50691a00d86d0632f1217a47b688", + "reference": "76432aafc58d50691a00d86d0632f1217a47b688", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/recursion-context": "^4.0" + "ext-mbstring": "*", + "php": ">=8.3", + "sebastian/recursion-context": "^7.0" }, "require-dev": { - "ext-mbstring": "*", - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^12.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -5368,7 +5282,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.6" + "security": "https://github.com/sebastianbergmann/exporter/security/policy", + "source": "https://github.com/sebastianbergmann/exporter/tree/7.0.0" }, "funding": [ { @@ -5376,38 +5291,35 @@ "type": "github" } ], - "time": "2024-03-02T06:33:00+00:00" + "time": "2025-02-07T04:56:42+00:00" }, { "name": "sebastian/global-state", - "version": "5.0.7", + "version": "8.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9" + "reference": "ef1377171613d09edd25b7816f05be8313f9115d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", - "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/ef1377171613d09edd25b7816f05be8313f9115d", + "reference": "ef1377171613d09edd25b7816f05be8313f9115d", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/object-reflector": "^2.0", - "sebastian/recursion-context": "^4.0" + "php": ">=8.3", + "sebastian/object-reflector": "^5.0", + "sebastian/recursion-context": "^7.0" }, "require-dev": { "ext-dom": "*", - "phpunit/phpunit": "^9.3" - }, - "suggest": { - "ext-uopz": "*" + "phpunit/phpunit": "^12.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-main": "8.0-dev" } }, "autoload": { @@ -5426,47 +5338,60 @@ } ], "description": "Snapshotting of global state", - "homepage": "http://www.github.com/sebastianbergmann/global-state", + "homepage": "https://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/5.0.7" + "security": "https://github.com/sebastianbergmann/global-state/security/policy", + "source": "https://github.com/sebastianbergmann/global-state/tree/8.0.2" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/global-state", + "type": "tidelift" } ], - "time": "2024-03-02T06:35:11+00:00" + "time": "2025-08-29T11:29:25+00:00" }, { "name": "sebastian/lines-of-code", - "version": "1.0.4", + "version": "4.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5" + "reference": "97ffee3bcfb5805568d6af7f0f893678fc076d2f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5", - "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/97ffee3bcfb5805568d6af7f0f893678fc076d2f", + "reference": "97ffee3bcfb5805568d6af7f0f893678fc076d2f", "shasum": "" }, "require": { - "nikic/php-parser": "^4.18 || ^5.0", - "php": ">=7.3" + "nikic/php-parser": "^5.0", + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^12.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -5489,7 +5414,8 @@ "homepage": "https://github.com/sebastianbergmann/lines-of-code", "support": { "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4" + "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/4.0.0" }, "funding": [ { @@ -5497,34 +5423,34 @@ "type": "github" } ], - "time": "2023-12-22T06:20:34+00:00" + "time": "2025-02-07T04:57:28+00:00" }, { "name": "sebastian/object-enumerator", - "version": "4.0.4", + "version": "7.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" + "reference": "1effe8e9b8e068e9ae228e542d5d11b5d16db894" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", - "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/1effe8e9b8e068e9ae228e542d5d11b5d16db894", + "reference": "1effe8e9b8e068e9ae228e542d5d11b5d16db894", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/object-reflector": "^2.0", - "sebastian/recursion-context": "^4.0" + "php": ">=8.3", + "sebastian/object-reflector": "^5.0", + "sebastian/recursion-context": "^7.0" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^12.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -5546,7 +5472,8 @@ "homepage": "https://github.com/sebastianbergmann/object-enumerator/", "support": { "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/7.0.0" }, "funding": [ { @@ -5554,32 +5481,32 @@ "type": "github" } ], - "time": "2020-10-26T13:12:34+00:00" + "time": "2025-02-07T04:57:48+00:00" }, { "name": "sebastian/object-reflector", - "version": "2.0.4", + "version": "5.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" + "reference": "4bfa827c969c98be1e527abd576533293c634f6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", - "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/4bfa827c969c98be1e527abd576533293c634f6a", + "reference": "4bfa827c969c98be1e527abd576533293c634f6a", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^12.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -5601,7 +5528,8 @@ "homepage": "https://github.com/sebastianbergmann/object-reflector/", "support": { "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/5.0.0" }, "funding": [ { @@ -5609,32 +5537,32 @@ "type": "github" } ], - "time": "2020-10-26T13:14:26+00:00" + "time": "2025-02-07T04:58:17+00:00" }, { "name": "sebastian/recursion-context", - "version": "4.0.5", + "version": "7.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" + "reference": "0b01998a7d5b1f122911a66bebcb8d46f0c82d8c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", - "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/0b01998a7d5b1f122911a66bebcb8d46f0c82d8c", + "reference": "0b01998a7d5b1f122911a66bebcb8d46f0c82d8c", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^12.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -5664,94 +5592,53 @@ "homepage": "https://github.com/sebastianbergmann/recursion-context", "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/7.0.1" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" - } - ], - "time": "2023-02-03T06:07:39+00:00" - }, - { - "name": "sebastian/resource-operations", - "version": "3.0.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/05d5692a7993ecccd56a03e40cd7e5b09b1d404e", - "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "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": "Provides a list of PHP built-in functions that operate on resources", - "homepage": "https://www.github.com/sebastianbergmann/resource-operations", - "support": { - "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.4" - }, - "funding": [ + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, { - "url": "https://github.com/sebastianbergmann", - "type": "github" + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "type": "tidelift" } ], - "time": "2024-03-14T16:00:52+00:00" + "time": "2025-08-13T04:44:59+00:00" }, { "name": "sebastian/type", - "version": "3.2.1", + "version": "6.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" + "reference": "e549163b9760b8f71f191651d22acf32d56d6d4d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", - "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/e549163b9760b8f71f191651d22acf32d56d6d4d", + "reference": "e549163b9760b8f71f191651d22acf32d56d6d4d", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^9.5" + "phpunit/phpunit": "^12.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -5774,37 +5661,50 @@ "homepage": "https://github.com/sebastianbergmann/type", "support": { "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" + "security": "https://github.com/sebastianbergmann/type/security/policy", + "source": "https://github.com/sebastianbergmann/type/tree/6.0.3" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/type", + "type": "tidelift" } ], - "time": "2023-02-03T06:13:03+00:00" + "time": "2025-08-09T06:57:12+00:00" }, { "name": "sebastian/version", - "version": "3.0.2", + "version": "6.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/version.git", - "reference": "c6c1022351a901512170118436c764e473f6de8c" + "reference": "3e6ccf7657d4f0a59200564b08cead899313b53c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", - "reference": "c6c1022351a901512170118436c764e473f6de8c", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/3e6ccf7657d4f0a59200564b08cead899313b53c", + "reference": "3e6ccf7657d4f0a59200564b08cead899313b53c", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -5827,7 +5727,8 @@ "homepage": "https://github.com/sebastianbergmann/version", "support": { "issues": "https://github.com/sebastianbergmann/version/issues", - "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + "security": "https://github.com/sebastianbergmann/version/security/policy", + "source": "https://github.com/sebastianbergmann/version/tree/6.0.0" }, "funding": [ { @@ -5835,7 +5736,7 @@ "type": "github" } ], - "time": "2020-09-28T06:39:44+00:00" + "time": "2025-02-07T05:00:38+00:00" }, { "name": "seld/jsonlint", @@ -6012,32 +5913,32 @@ }, { "name": "slevomat/coding-standard", - "version": "8.20.0", + "version": "8.21.0", "source": { "type": "git", "url": "https://github.com/slevomat/coding-standard.git", - "reference": "b4f9f02edd4e6a586777f0cabe8d05574323f3eb" + "reference": "51da0acab0c0bcdc701efcf2a91bcd6477cbf405" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/b4f9f02edd4e6a586777f0cabe8d05574323f3eb", - "reference": "b4f9f02edd4e6a586777f0cabe8d05574323f3eb", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/51da0acab0c0bcdc701efcf2a91bcd6477cbf405", + "reference": "51da0acab0c0bcdc701efcf2a91bcd6477cbf405", "shasum": "" }, "require": { "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7 || ^1.1.2", "php": "^7.4 || ^8.0", - "phpstan/phpdoc-parser": "^2.2.0", + "phpstan/phpdoc-parser": "^2.3.0", "squizlabs/php_codesniffer": "^3.13.2" }, "require-dev": { "phing/phing": "3.0.1|3.1.0", "php-parallel-lint/php-parallel-lint": "1.4.0", - "phpstan/phpstan": "2.1.19", + "phpstan/phpstan": "2.1.22", "phpstan/phpstan-deprecation-rules": "2.0.3", "phpstan/phpstan-phpunit": "2.0.7", "phpstan/phpstan-strict-rules": "2.0.6", - "phpunit/phpunit": "9.6.8|10.5.48|11.4.4|11.5.27|12.2.7" + "phpunit/phpunit": "9.6.8|10.5.48|11.4.4|11.5.27|12.3.7" }, "type": "phpcodesniffer-standard", "extra": { @@ -6061,7 +5962,7 @@ ], "support": { "issues": "https://github.com/slevomat/coding-standard/issues", - "source": "https://github.com/slevomat/coding-standard/tree/8.20.0" + "source": "https://github.com/slevomat/coding-standard/tree/8.21.0" }, "funding": [ { @@ -6073,7 +5974,7 @@ "type": "tidelift" } ], - "time": "2025-07-26T15:35:10+00:00" + "time": "2025-08-31T07:06:13+00:00" }, { "name": "squizlabs/php_codesniffer", @@ -6159,6 +6060,58 @@ ], "time": "2025-06-17T22:17:01+00:00" }, + { + "name": "staabm/side-effects-detector", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/staabm/side-effects-detector.git", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.6", + "phpunit/phpunit": "^9.6.21", + "symfony/var-dumper": "^5.4.43", + "tomasvotruba/type-coverage": "1.0.0", + "tomasvotruba/unused-public": "1.0.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "lib/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A static analysis tool to detect side effects in PHP code", + "keywords": [ + "static analysis" + ], + "support": { + "issues": "https://github.com/staabm/side-effects-detector/issues", + "source": "https://github.com/staabm/side-effects-detector/tree/1.0.5" + }, + "funding": [ + { + "url": "https://github.com/staabm", + "type": "github" + } + ], + "time": "2024-10-20T05:08:20+00:00" + }, { "name": "symfony/finder", "version": "v7.3.2", @@ -6229,7 +6182,7 @@ }, { "name": "symfony/polyfill-php73", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", @@ -6285,7 +6238,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-php73/tree/v1.33.0" }, "funding": [ { @@ -6296,6 +6249,10 @@ "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" @@ -6305,7 +6262,7 @@ }, { "name": "symfony/polyfill-php80", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", @@ -6365,7 +6322,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0" }, "funding": [ { @@ -6376,6 +6333,10 @@ "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" @@ -6385,7 +6346,7 @@ }, { "name": "symfony/polyfill-php81", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php81.git", @@ -6441,7 +6402,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.33.0" }, "funding": [ { @@ -6452,6 +6413,10 @@ "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" @@ -6461,16 +6426,16 @@ }, { "name": "symfony/process", - "version": "v7.3.0", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "40c295f2deb408d5e9d2d32b8ba1dd61e36f05af" + "reference": "32241012d521e2e8a9d713adb0812bb773b907f1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/40c295f2deb408d5e9d2d32b8ba1dd61e36f05af", - "reference": "40c295f2deb408d5e9d2d32b8ba1dd61e36f05af", + "url": "https://api.github.com/repos/symfony/process/zipball/32241012d521e2e8a9d713adb0812bb773b907f1", + "reference": "32241012d521e2e8a9d713adb0812bb773b907f1", "shasum": "" }, "require": { @@ -6502,7 +6467,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.3.0" + "source": "https://github.com/symfony/process/tree/v7.3.3" }, "funding": [ { @@ -6513,25 +6478,29 @@ "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-04-17T09:11:12+00:00" + "time": "2025-08-18T09:42:54+00:00" }, { "name": "symfony/var-dumper", - "version": "v7.3.2", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "53205bea27450dc5c65377518b3275e126d45e75" + "reference": "34d8d4c4b9597347306d1ec8eb4e1319b1e6986f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/53205bea27450dc5c65377518b3275e126d45e75", - "reference": "53205bea27450dc5c65377518b3275e126d45e75", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/34d8d4c4b9597347306d1ec8eb4e1319b1e6986f", + "reference": "34d8d4c4b9597347306d1ec8eb4e1319b1e6986f", "shasum": "" }, "require": { @@ -6585,7 +6554,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.3.2" + "source": "https://github.com/symfony/var-dumper/tree/v7.3.3" }, "funding": [ { @@ -6605,7 +6574,7 @@ "type": "tidelift" } ], - "time": "2025-07-29T20:02:46+00:00" + "time": "2025-08-13T11:49:31+00:00" }, { "name": "theseer/tokenizer", @@ -6817,8 +6786,11 @@ "prefer-stable": true, "prefer-lowest": false, "platform": { - "php": ">=8.0" + "php": ">=8.1", + "ext-intl": "*", + "ext-json": "*", + "ext-mbstring": "*" }, - "platform-dev": [], - "plugin-api-version": "2.3.0" + "platform-dev": {}, + "plugin-api-version": "2.6.0" } diff --git a/app/config/app.php b/app/config/app.php index 58739ebd2..974c4a67b 100644 --- a/app/config/app.php +++ b/app/config/app.php @@ -134,13 +134,13 @@ * Duration will be set to '+2 minutes' in bootstrap.php when debug = true * If you set 'className' => 'Null' core cache will be disabled. */ - '_cake_core_' => [ + '_cake_translations_' => [ 'className' => 'Cake\Cache\Engine\FileEngine', - 'prefix' => 'myapp_cake_core_', + 'prefix' => 'myapp_cake_translations_', 'path' => CACHE . 'persistent/', 'serialize' => true, 'duration' => '+1 years', - 'url' => env('CACHE_CAKECORE_URL', null), + 'url' => env('CACHE_TRNASLATIONS_URL', null), ], /** @@ -215,8 +215,6 @@ */ 'Error' => [ 'errorLevel' => E_ALL, -// This is deprecated and could probably be completely removed -// 'exceptionRenderer' => 'Cake\Error\ExceptionRenderer', 'skipLog' => [], 'log' => true, 'trace' => true, @@ -318,7 +316,7 @@ 'flags' => [], 'cacheMetadata' => true, // Set to true to get query log for debugging - 'log' => false, + 'log' => null, /** * Set identifier quoting to true if you are using reserved words or @@ -358,7 +356,7 @@ 'timezone' => 'UTC', 'cacheMetadata' => true, 'quoteIdentifiers' => false, - 'log' => false, + 'log' => null, //'init' => ['SET GLOBAL innodb_stats_on_metadata = 0'], 'url' => env('DATABASE_TEST_URL', null), ], @@ -374,14 +372,14 @@ 'className' => 'Cake\Log\Engine\ConsoleLog', 'stream' => 'php://stdout', 'outputAs' => 0, - 'scopes' => false, + 'scopes' => null, 'levels' => ['notice', 'info', 'debug'], ], 'error' => [ 'className' => 'Cake\Log\Engine\ConsoleLog', 'stream' => 'php://stderr', 'outputAs' => 0, - 'scopes' => false, + 'scopes' => null, 'levels' => ['warning', 'error', 'critical', 'alert', 'emergency'], ], 'queries' => [ @@ -404,7 +402,7 @@ 'path' => LOGS, 'file' => 'debug', 'url' => env('LOG_DEBUG_URL', null), - 'scopes' => false, + 'scopes' => null, 'levels' => ['notice', 'info', 'debug'], ], 'error' => [ @@ -412,7 +410,7 @@ 'path' => LOGS, 'file' => 'error', 'url' => env('LOG_ERROR_URL', null), - 'scopes' => false, + 'scopes' => null, 'levels' => ['warning', 'error', 'critical', 'alert', 'emergency'], ], // To enable this dedicated query log, you need set your datasource's log flag to true diff --git a/app/config/bootstrap.php b/app/config/bootstrap.php index 2d59fca24..c6d698959 100644 --- a/app/config/bootstrap.php +++ b/app/config/bootstrap.php @@ -48,26 +48,9 @@ use Cake\Utility\Security; /* - * See https://github.com/josegonzalez/php-dotenv for API details. - * - * Uncomment block of code below if you want to use `.env` file during development. - * You should copy `config/.env.example` to `config/.env` and set/modify the - * variables as required. - * - * The purpose of the .env file is to emulate the presence of the environment - * variables like they would be present in production. - * - * If you use .env files, be careful to not commit them to source control to avoid - * security risks. See https://github.com/josegonzalez/php-dotenv#general-security-information - * for more information for recommended practices. -*/ -// if (!env('APP_NAME') && file_exists(CONFIG . '.env')) { -// $dotenv = new \josegonzalez\Dotenv\Loader([CONFIG . '.env']); -// $dotenv->parse() -// ->putenv() -// ->toEnv() -// ->toServer(); -// } + * Load global functions for collections, translations, debugging etc. + */ +require CAKE . 'functions.php'; /* * Read configuration file and inject configuration into various @@ -105,6 +88,7 @@ //Configure::write('Cache._cake_core_.duration', '+2 minutes'); // disable router cache during development //Configure::write('Cache._cake_routes_.duration', '+2 seconds'); + Configure::write('DebugKit.forceEnable', true); } /* @@ -127,29 +111,50 @@ /* * Register application error and exception handlers. */ -$isCli = PHP_SAPI === 'cli'; (new ErrorTrap(Configure::read('Error')))->register(); (new ExceptionTrap(Configure::read('Error')))->register(); + /* - * Include the CLI bootstrap overrides. + * CLI/Command specific configuration. */ -if ($isCli) { - require CONFIG . 'bootstrap_cli.php'; +if (PHP_SAPI === 'cli') { + // Set the fullBaseUrl to allow URLs to be generated in commands. + // This is useful when sending email from commands. + // Configure::write('App.fullBaseUrl', php_uname('n')); + + // Set logs to different files so they don't have permission conflicts. + if (Configure::check('Log.debug')) { + Configure::write('Log.debug.file', 'cli-debug'); + } + if (Configure::check('Log.error')) { + Configure::write('Log.error.file', 'cli-error'); + } } /* * Set the full base URL. * This URL is used as the base of all absolute links. + * Can be very useful for CLI/Commandline applications. */ $fullBaseUrl = Configure::read('App.fullBaseUrl'); if (!$fullBaseUrl) { + /* + * When using proxies or load balancers, SSL/TLS connections might + * get terminated before reaching the server. If you trust the proxy, + * you can enable `$trustProxy` to rely on the `X-Forwarded-Proto` + * header to determine whether to generate URLs using `https`. + * + * See also https://book.cakephp.org/5/en/controllers/request-response.html#trusting-proxy-headers + */ + $trustProxy = false; + $s = null; - if (env('HTTPS')) { + if (env('HTTPS') || ($trustProxy && env('HTTP_X_FORWARDED_PROTO') === 'https')) { $s = 's'; } $httpHost = env('HTTP_HOST'); - if (isset($httpHost)) { + if ($httpHost) { $fullBaseUrl = 'http' . $s . '://' . $httpHost; } unset($httpHost, $s); diff --git a/app/config/bootstrap_cli.php b/app/config/bootstrap_cli.php deleted file mode 100644 index fc0dc30bb..000000000 --- a/app/config/bootstrap_cli.php +++ /dev/null @@ -1,35 +0,0 @@ - [ 'MatchCallbacks.server_id' => 'asc' ] diff --git a/app/plugins/CoreApi/src/Model/Entity/MatchCallback.php b/app/plugins/CoreApi/src/Model/Entity/MatchCallback.php index a96b57d5a..fd21c1b6c 100644 --- a/app/plugins/CoreApi/src/Model/Entity/MatchCallback.php +++ b/app/plugins/CoreApi/src/Model/Entity/MatchCallback.php @@ -41,7 +41,7 @@ class MatchCallback extends Entity { * * @var array */ - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/plugins/CoreAssigner/src/Controller/FormatAssignersController.php b/app/plugins/CoreAssigner/src/Controller/FormatAssignersController.php index 24909d8ec..0eb911d2e 100644 --- a/app/plugins/CoreAssigner/src/Controller/FormatAssignersController.php +++ b/app/plugins/CoreAssigner/src/Controller/FormatAssignersController.php @@ -32,7 +32,7 @@ use App\Controller\StandardPluginController; class FormatAssignersController extends StandardPluginController { - public $paginate = [ + public array $paginate = [ 'order' => [ 'FormatAssigners.format' => 'asc' ] diff --git a/app/plugins/CoreAssigner/src/Controller/SqlAssignersController.php b/app/plugins/CoreAssigner/src/Controller/SqlAssignersController.php index 22609df97..e67b51503 100644 --- a/app/plugins/CoreAssigner/src/Controller/SqlAssignersController.php +++ b/app/plugins/CoreAssigner/src/Controller/SqlAssignersController.php @@ -32,7 +32,7 @@ use App\Controller\StandardPluginController; class SqlAssignersController extends StandardPluginController { - public $paginate = [ + public array $paginate = [ 'order' => [ 'SqlAssigners.server_id' => 'asc' ] diff --git a/app/plugins/CoreAssigner/src/Model/Entity/FormatAssigner.php b/app/plugins/CoreAssigner/src/Model/Entity/FormatAssigner.php index ee6d6c913..c15bbe0bd 100644 --- a/app/plugins/CoreAssigner/src/Model/Entity/FormatAssigner.php +++ b/app/plugins/CoreAssigner/src/Model/Entity/FormatAssigner.php @@ -41,7 +41,7 @@ class FormatAssigner extends Entity { * * @var array */ - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/plugins/CoreAssigner/src/Model/Entity/FormatAssignerSequence.php b/app/plugins/CoreAssigner/src/Model/Entity/FormatAssignerSequence.php index de4e99e81..a20808d83 100644 --- a/app/plugins/CoreAssigner/src/Model/Entity/FormatAssignerSequence.php +++ b/app/plugins/CoreAssigner/src/Model/Entity/FormatAssignerSequence.php @@ -41,7 +41,7 @@ class FormatAssignerSequence extends Entity { * * @var array */ - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/plugins/CoreAssigner/src/Model/Entity/SqlAssigner.php b/app/plugins/CoreAssigner/src/Model/Entity/SqlAssigner.php index 3e8e70149..582ac9cf0 100644 --- a/app/plugins/CoreAssigner/src/Model/Entity/SqlAssigner.php +++ b/app/plugins/CoreAssigner/src/Model/Entity/SqlAssigner.php @@ -41,7 +41,7 @@ class SqlAssigner extends Entity { * * @var array */ - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/plugins/CoreEnroller/src/Controller/ApprovalCollectorsController.php b/app/plugins/CoreEnroller/src/Controller/ApprovalCollectorsController.php index 72db30f1c..1c0765edf 100644 --- a/app/plugins/CoreEnroller/src/Controller/ApprovalCollectorsController.php +++ b/app/plugins/CoreEnroller/src/Controller/ApprovalCollectorsController.php @@ -35,7 +35,7 @@ use \App\Lib\Enum\StatusEnum; class ApprovalCollectorsController extends StandardEnrollerController { - public $paginate = [ + public array $paginate = [ 'order' => [ 'ApprovalCollectors.id' => 'asc' ] diff --git a/app/plugins/CoreEnroller/src/Controller/AttributeCollectorsController.php b/app/plugins/CoreEnroller/src/Controller/AttributeCollectorsController.php index 7e1631bd3..0cde99d3a 100644 --- a/app/plugins/CoreEnroller/src/Controller/AttributeCollectorsController.php +++ b/app/plugins/CoreEnroller/src/Controller/AttributeCollectorsController.php @@ -35,7 +35,7 @@ use Cake\ORM\TableRegistry; class AttributeCollectorsController extends StandardEnrollerController { - public $paginate = [ + public array $paginate = [ 'order' => [ 'AttributeCollectors.id' => 'asc' ] diff --git a/app/plugins/CoreEnroller/src/Controller/BasicAttributeCollectorsController.php b/app/plugins/CoreEnroller/src/Controller/BasicAttributeCollectorsController.php index 2e6a942d3..469b54ab2 100644 --- a/app/plugins/CoreEnroller/src/Controller/BasicAttributeCollectorsController.php +++ b/app/plugins/CoreEnroller/src/Controller/BasicAttributeCollectorsController.php @@ -33,7 +33,7 @@ use App\Controller\StandardEnrollerController; class BasicAttributeCollectorsController extends StandardEnrollerController { - public $paginate = [ + public array $paginate = [ 'order' => [ 'BasicAttributeCollectors.id' => 'asc' ] diff --git a/app/plugins/CoreEnroller/src/Controller/EmailVerifiersController.php b/app/plugins/CoreEnroller/src/Controller/EmailVerifiersController.php index ed54b76d0..4daf5d075 100644 --- a/app/plugins/CoreEnroller/src/Controller/EmailVerifiersController.php +++ b/app/plugins/CoreEnroller/src/Controller/EmailVerifiersController.php @@ -42,7 +42,7 @@ class EmailVerifiersController extends StandardEnrollerController { use ApplicationStatesTrait; - public $paginate = [ + public array $paginate = [ 'order' => [ 'EmailVerifiers.id' => 'asc' ] diff --git a/app/plugins/CoreEnroller/src/Controller/EnrollmentAttributesController.php b/app/plugins/CoreEnroller/src/Controller/EnrollmentAttributesController.php index 0f423e5e0..dd576a50f 100644 --- a/app/plugins/CoreEnroller/src/Controller/EnrollmentAttributesController.php +++ b/app/plugins/CoreEnroller/src/Controller/EnrollmentAttributesController.php @@ -34,7 +34,7 @@ use \App\Lib\Util\StringUtilities; class EnrollmentAttributesController extends StandardEnrollerController { - public $paginate = [ + public array $paginate = [ 'order' => [ 'EnrollmentAttributes.ordr' => 'asc' ] diff --git a/app/plugins/CoreEnroller/src/Controller/IdentifierCollectorsController.php b/app/plugins/CoreEnroller/src/Controller/IdentifierCollectorsController.php index 743bad07f..0ad68c035 100644 --- a/app/plugins/CoreEnroller/src/Controller/IdentifierCollectorsController.php +++ b/app/plugins/CoreEnroller/src/Controller/IdentifierCollectorsController.php @@ -33,7 +33,7 @@ use App\Controller\StandardEnrollerController; class IdentifierCollectorsController extends StandardEnrollerController { - public $paginate = [ + public array $paginate = [ 'order' => [ 'IdentifierCollectors.id' => 'asc' ] diff --git a/app/plugins/CoreEnroller/src/Controller/InvitationAcceptersController.php b/app/plugins/CoreEnroller/src/Controller/InvitationAcceptersController.php index 90d05ba78..ed27b98fd 100644 --- a/app/plugins/CoreEnroller/src/Controller/InvitationAcceptersController.php +++ b/app/plugins/CoreEnroller/src/Controller/InvitationAcceptersController.php @@ -34,7 +34,7 @@ use App\Lib\Enum\PetitionActionEnum; class InvitationAcceptersController extends StandardEnrollerController { - public $paginate = [ + public array $paginate = [ 'order' => [ 'InvitationAccepters.id' => 'asc' ] diff --git a/app/plugins/CoreEnroller/src/Model/Entity/ApprovalCollector.php b/app/plugins/CoreEnroller/src/Model/Entity/ApprovalCollector.php index 5e70d595c..d392a1508 100644 --- a/app/plugins/CoreEnroller/src/Model/Entity/ApprovalCollector.php +++ b/app/plugins/CoreEnroller/src/Model/Entity/ApprovalCollector.php @@ -43,7 +43,7 @@ class ApprovalCollector extends Entity { * * @var array */ - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/plugins/CoreEnroller/src/Model/Entity/AttributeCollector.php b/app/plugins/CoreEnroller/src/Model/Entity/AttributeCollector.php index 4a1432d03..40e296e6f 100644 --- a/app/plugins/CoreEnroller/src/Model/Entity/AttributeCollector.php +++ b/app/plugins/CoreEnroller/src/Model/Entity/AttributeCollector.php @@ -43,7 +43,7 @@ class AttributeCollector extends Entity { * * @var array */ - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/plugins/CoreEnroller/src/Model/Entity/BasicAttributeCollector.php b/app/plugins/CoreEnroller/src/Model/Entity/BasicAttributeCollector.php index 675282e93..5c7e6a630 100644 --- a/app/plugins/CoreEnroller/src/Model/Entity/BasicAttributeCollector.php +++ b/app/plugins/CoreEnroller/src/Model/Entity/BasicAttributeCollector.php @@ -43,7 +43,7 @@ class BasicAttributeCollector extends Entity { * * @var array */ - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/plugins/CoreEnroller/src/Model/Entity/EmailVerifier.php b/app/plugins/CoreEnroller/src/Model/Entity/EmailVerifier.php index b22c3ebae..3dc27771b 100644 --- a/app/plugins/CoreEnroller/src/Model/Entity/EmailVerifier.php +++ b/app/plugins/CoreEnroller/src/Model/Entity/EmailVerifier.php @@ -43,7 +43,7 @@ class EmailVerifier extends Entity { * * @var array */ - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/plugins/CoreEnroller/src/Model/Entity/EnrollmentAttribute.php b/app/plugins/CoreEnroller/src/Model/Entity/EnrollmentAttribute.php index 5e4976e9b..8801194e5 100644 --- a/app/plugins/CoreEnroller/src/Model/Entity/EnrollmentAttribute.php +++ b/app/plugins/CoreEnroller/src/Model/Entity/EnrollmentAttribute.php @@ -41,7 +41,7 @@ class EnrollmentAttribute extends Entity { * * @var array */ - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/plugins/CoreEnroller/src/Model/Entity/IdentifierCollector.php b/app/plugins/CoreEnroller/src/Model/Entity/IdentifierCollector.php index 024e76937..5c3068d2d 100644 --- a/app/plugins/CoreEnroller/src/Model/Entity/IdentifierCollector.php +++ b/app/plugins/CoreEnroller/src/Model/Entity/IdentifierCollector.php @@ -43,7 +43,7 @@ class IdentifierCollector extends Entity { * * @var array */ - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/plugins/CoreEnroller/src/Model/Entity/InvitationAccepter.php b/app/plugins/CoreEnroller/src/Model/Entity/InvitationAccepter.php index b5ac09d28..6a5f43375 100644 --- a/app/plugins/CoreEnroller/src/Model/Entity/InvitationAccepter.php +++ b/app/plugins/CoreEnroller/src/Model/Entity/InvitationAccepter.php @@ -43,7 +43,7 @@ class InvitationAccepter extends Entity { * * @var array */ - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/plugins/CoreEnroller/src/Model/Entity/PetitionAcceptance.php b/app/plugins/CoreEnroller/src/Model/Entity/PetitionAcceptance.php index cc3d84c55..7705a1ad8 100644 --- a/app/plugins/CoreEnroller/src/Model/Entity/PetitionAcceptance.php +++ b/app/plugins/CoreEnroller/src/Model/Entity/PetitionAcceptance.php @@ -41,7 +41,7 @@ class PetitionAcceptance extends Entity { * * @var array */ - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/plugins/CoreEnroller/src/Model/Entity/PetitionApproval.php b/app/plugins/CoreEnroller/src/Model/Entity/PetitionApproval.php index d59aad765..541f0b2c8 100644 --- a/app/plugins/CoreEnroller/src/Model/Entity/PetitionApproval.php +++ b/app/plugins/CoreEnroller/src/Model/Entity/PetitionApproval.php @@ -41,7 +41,7 @@ class PetitionApproval extends Entity { * * @var array */ - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/plugins/CoreEnroller/src/Model/Entity/PetitionAttribute.php b/app/plugins/CoreEnroller/src/Model/Entity/PetitionAttribute.php index 558ac97bc..2829228e7 100644 --- a/app/plugins/CoreEnroller/src/Model/Entity/PetitionAttribute.php +++ b/app/plugins/CoreEnroller/src/Model/Entity/PetitionAttribute.php @@ -41,7 +41,7 @@ class PetitionAttribute extends Entity { * * @var array */ - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/plugins/CoreEnroller/src/Model/Entity/PetitionBasicAttributeSet.php b/app/plugins/CoreEnroller/src/Model/Entity/PetitionBasicAttributeSet.php index d506679eb..2408e0a42 100644 --- a/app/plugins/CoreEnroller/src/Model/Entity/PetitionBasicAttributeSet.php +++ b/app/plugins/CoreEnroller/src/Model/Entity/PetitionBasicAttributeSet.php @@ -41,7 +41,7 @@ class PetitionBasicAttributeSet extends Entity { * * @var array */ - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/plugins/CoreEnroller/src/Model/Entity/PetitionIdentifier.php b/app/plugins/CoreEnroller/src/Model/Entity/PetitionIdentifier.php index 7045282fe..c10403990 100644 --- a/app/plugins/CoreEnroller/src/Model/Entity/PetitionIdentifier.php +++ b/app/plugins/CoreEnroller/src/Model/Entity/PetitionIdentifier.php @@ -41,7 +41,7 @@ class PetitionIdentifier extends Entity { * * @var array */ - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/plugins/CoreEnroller/src/Model/Entity/PetitionVerification.php b/app/plugins/CoreEnroller/src/Model/Entity/PetitionVerification.php index ee9e173a8..75e8f6ec7 100644 --- a/app/plugins/CoreEnroller/src/Model/Entity/PetitionVerification.php +++ b/app/plugins/CoreEnroller/src/Model/Entity/PetitionVerification.php @@ -41,7 +41,7 @@ class PetitionVerification extends Entity { * * @var array */ - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/plugins/CoreEnroller/src/Model/Table/AttributeCollectorsTable.php b/app/plugins/CoreEnroller/src/Model/Table/AttributeCollectorsTable.php index a37f486ba..930ee179d 100644 --- a/app/plugins/CoreEnroller/src/Model/Table/AttributeCollectorsTable.php +++ b/app/plugins/CoreEnroller/src/Model/Table/AttributeCollectorsTable.php @@ -442,7 +442,7 @@ public function verifiableEmailAddresses( 'attribute' => 'emailAddress', 'status' => StatusEnum::Active, ]) - ->order(['ordr' => 'ASC']) + ->orderBy(['ordr' => 'ASC']) ->toArray(); if (empty($vv_enrollment_attributes)) { diff --git a/app/plugins/CoreEnroller/src/Model/Table/EmailVerifiersTable.php b/app/plugins/CoreEnroller/src/Model/Table/EmailVerifiersTable.php index b0872a613..2626df8e1 100644 --- a/app/plugins/CoreEnroller/src/Model/Table/EmailVerifiersTable.php +++ b/app/plugins/CoreEnroller/src/Model/Table/EmailVerifiersTable.php @@ -189,7 +189,7 @@ public function assembleVerifiableAddresses( 'enrollment_flow_id' => $petition->enrollment_flow_id, 'status' => SuspendableStatusEnum::Active ]) - ->order(['EnrollmentFlowSteps.ordr' => 'ASC']) + ->orderBy(['EnrollmentFlowSteps.ordr' => 'ASC']) ->contain($this->EnrollmentFlowSteps->getPluginRelations()) ->all(); diff --git a/app/plugins/CoreEnroller/src/View/Cell/ApprovalCollectorsCell.php b/app/plugins/CoreEnroller/src/View/Cell/ApprovalCollectorsCell.php index 04839c430..3eaabd466 100644 --- a/app/plugins/CoreEnroller/src/View/Cell/ApprovalCollectorsCell.php +++ b/app/plugins/CoreEnroller/src/View/Cell/ApprovalCollectorsCell.php @@ -37,13 +37,28 @@ class ApprovalCollectorsCell extends Cell { + /** + * @var mixed + */ + public $vv_obj; + + /** + * @var mixed + */ + public $vv_step; + + /** + * @var mixed + */ + public $viewVars; + /** * List of valid options that can be passed into this * cell's constructor. * * @var array */ - protected $_validCellOptions = [ + protected array $_validCellOptions = [ 'vv_obj', 'vv_step', 'viewVars', diff --git a/app/plugins/CoreEnroller/src/View/Cell/AttributeCollectorsCell.php b/app/plugins/CoreEnroller/src/View/Cell/AttributeCollectorsCell.php index 61665e0d1..87665564a 100644 --- a/app/plugins/CoreEnroller/src/View/Cell/AttributeCollectorsCell.php +++ b/app/plugins/CoreEnroller/src/View/Cell/AttributeCollectorsCell.php @@ -43,13 +43,28 @@ */ class AttributeCollectorsCell extends Cell { + /** + * @var mixed + */ + public $vv_obj; + + /** + * @var mixed + */ + public $vv_step; + + /** + * @var mixed + */ + public $viewVars; + /** * List of valid options that can be passed into this * cell's constructor. * * @var array */ - protected $_validCellOptions = [ + protected array $_validCellOptions = [ 'vv_obj', 'vv_step', 'viewVars', @@ -81,7 +96,7 @@ public function display(int $petitionId): void $vv_enrollment_attributes = $this->fetchTable('EnrollmentAttributes') ->find() ->where(fn(QueryExpression $exp, Query $q) => $exp->in('id', $vv_enrollment_atttributes_ids)) - ->order(['ordr' => 'ASC']) + ->orderBy(['ordr' => 'ASC']) ->toArray(); } diff --git a/app/plugins/CoreEnroller/src/View/Cell/BasicAttributeCollectorsCell.php b/app/plugins/CoreEnroller/src/View/Cell/BasicAttributeCollectorsCell.php index 16e12361b..6a12f7a89 100644 --- a/app/plugins/CoreEnroller/src/View/Cell/BasicAttributeCollectorsCell.php +++ b/app/plugins/CoreEnroller/src/View/Cell/BasicAttributeCollectorsCell.php @@ -40,13 +40,28 @@ */ class BasicAttributeCollectorsCell extends Cell { + /** + * @var mixed + */ + public $vv_obj; + + /** + * @var mixed + */ + public $vv_step; + + /** + * @var mixed + */ + public $viewVars; + /** * List of valid options that can be passed into this * cell's constructor. * * @var array */ - protected $_validCellOptions = [ + protected array $_validCellOptions = [ 'vv_obj', 'vv_step', 'viewVars', diff --git a/app/plugins/CoreEnroller/src/View/Cell/EmailVerifiersCell.php b/app/plugins/CoreEnroller/src/View/Cell/EmailVerifiersCell.php index e855b6e2a..9fbaedad1 100644 --- a/app/plugins/CoreEnroller/src/View/Cell/EmailVerifiersCell.php +++ b/app/plugins/CoreEnroller/src/View/Cell/EmailVerifiersCell.php @@ -40,13 +40,28 @@ */ class EmailVerifiersCell extends Cell { + /** + * @var mixed + */ + public $vv_obj; + + /** + * @var mixed + */ + public $vv_step; + + /** + * @var mixed + */ + public $viewVars; + /** * List of valid options that can be passed into this * cell's constructor. * - * @var array + * @var array */ - protected $_validCellOptions = [ + protected array $_validCellOptions = [ 'vv_obj', 'vv_step', 'viewVars', diff --git a/app/plugins/CoreEnroller/src/View/Cell/IdentifierCollectorsCell.php b/app/plugins/CoreEnroller/src/View/Cell/IdentifierCollectorsCell.php index af18eb85b..be102d0e6 100644 --- a/app/plugins/CoreEnroller/src/View/Cell/IdentifierCollectorsCell.php +++ b/app/plugins/CoreEnroller/src/View/Cell/IdentifierCollectorsCell.php @@ -40,13 +40,28 @@ */ class IdentifierCollectorsCell extends Cell { + /** + * @var mixed + */ + public $vv_obj; + + /** + * @var mixed + */ + public $vv_step; + + /** + * @var mixed + */ + public $viewVars; + /** * List of valid options that can be passed into this * cell's constructor. * * @var array */ - protected $_validCellOptions = [ + protected array $_validCellOptions = [ 'vv_obj', 'vv_step', 'viewVars', diff --git a/app/plugins/CoreEnroller/src/View/Cell/InvitationAcceptersCell.php b/app/plugins/CoreEnroller/src/View/Cell/InvitationAcceptersCell.php index 320b8dc87..9b643ef83 100644 --- a/app/plugins/CoreEnroller/src/View/Cell/InvitationAcceptersCell.php +++ b/app/plugins/CoreEnroller/src/View/Cell/InvitationAcceptersCell.php @@ -40,13 +40,28 @@ */ class InvitationAcceptersCell extends Cell { + /** + * @var mixed + */ + public $vv_obj; + + /** + * @var mixed + */ + public $vv_step; + + /** + * @var mixed + */ + public $viewVars; + /** * List of valid options that can be passed into this * cell's constructor. * * @var array */ - protected $_validCellOptions = [ + protected array $_validCellOptions = [ 'vv_obj', 'vv_step', 'viewVars', diff --git a/app/plugins/CoreServer/src/Controller/HttpServersController.php b/app/plugins/CoreServer/src/Controller/HttpServersController.php index 06bbb5fd5..e0bbe9933 100644 --- a/app/plugins/CoreServer/src/Controller/HttpServersController.php +++ b/app/plugins/CoreServer/src/Controller/HttpServersController.php @@ -32,7 +32,7 @@ use App\Controller\StandardPluginController; class HttpServersController extends StandardPluginController { - public $paginate = [ + public array $paginate = [ 'order' => [ 'HttpServers.url' => 'asc' ] diff --git a/app/plugins/CoreServer/src/Controller/MatchServerAttributesController.php b/app/plugins/CoreServer/src/Controller/MatchServerAttributesController.php index cef8f47a8..f695523b7 100644 --- a/app/plugins/CoreServer/src/Controller/MatchServerAttributesController.php +++ b/app/plugins/CoreServer/src/Controller/MatchServerAttributesController.php @@ -33,7 +33,7 @@ use Cake\Event\EventInterface; class MatchServerAttributesController extends StandardPluginController { - public $paginate = [ + public array $paginate = [ 'order' => [ 'MatchServerAttributes.attribute' => 'asc' ] diff --git a/app/plugins/CoreServer/src/Controller/MatchServersController.php b/app/plugins/CoreServer/src/Controller/MatchServersController.php index 4b715ca3a..641a0494b 100644 --- a/app/plugins/CoreServer/src/Controller/MatchServersController.php +++ b/app/plugins/CoreServer/src/Controller/MatchServersController.php @@ -32,7 +32,7 @@ use App\Controller\StandardPluginController; class MatchServersController extends StandardPluginController { - public $paginate = [ + public array $paginate = [ 'order' => [ 'MatchServers.url' => 'asc' ] diff --git a/app/plugins/CoreServer/src/Controller/Oauth2ServersController.php b/app/plugins/CoreServer/src/Controller/Oauth2ServersController.php index 7b393ef0c..03a4922b5 100644 --- a/app/plugins/CoreServer/src/Controller/Oauth2ServersController.php +++ b/app/plugins/CoreServer/src/Controller/Oauth2ServersController.php @@ -35,7 +35,7 @@ class Oauth2ServersController extends StandardPluginController { - public $paginate = [ + public array $paginate = [ 'order' => [ 'OauthServers.url' => 'asc' ] diff --git a/app/plugins/CoreServer/src/Controller/SmtpServersController.php b/app/plugins/CoreServer/src/Controller/SmtpServersController.php index c472e18df..c293e5205 100644 --- a/app/plugins/CoreServer/src/Controller/SmtpServersController.php +++ b/app/plugins/CoreServer/src/Controller/SmtpServersController.php @@ -32,7 +32,7 @@ use App\Controller\StandardPluginController; class SmtpServersController extends StandardPluginController { - public $paginate = [ + public array $paginate = [ 'order' => [ 'SmtpServers.hostname' => 'asc' ] diff --git a/app/plugins/CoreServer/src/Controller/SqlServersController.php b/app/plugins/CoreServer/src/Controller/SqlServersController.php index f37690a7b..24c39a67e 100644 --- a/app/plugins/CoreServer/src/Controller/SqlServersController.php +++ b/app/plugins/CoreServer/src/Controller/SqlServersController.php @@ -32,7 +32,7 @@ use App\Controller\StandardPluginController; class SqlServersController extends StandardPluginController { - public $paginate = [ + public array $paginate = [ 'order' => [ 'SqlServers.hostname' => 'asc' ] diff --git a/app/plugins/CoreServer/src/Model/Entity/HttpServer.php b/app/plugins/CoreServer/src/Model/Entity/HttpServer.php index a3bee455f..b40dba1a4 100644 --- a/app/plugins/CoreServer/src/Model/Entity/HttpServer.php +++ b/app/plugins/CoreServer/src/Model/Entity/HttpServer.php @@ -41,7 +41,7 @@ class HttpServer extends Entity { * * @var array */ - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/plugins/CoreServer/src/Model/Entity/MatchServer.php b/app/plugins/CoreServer/src/Model/Entity/MatchServer.php index 688c77f6b..b80202698 100644 --- a/app/plugins/CoreServer/src/Model/Entity/MatchServer.php +++ b/app/plugins/CoreServer/src/Model/Entity/MatchServer.php @@ -41,7 +41,7 @@ class MatchServer extends Entity { * * @var array */ - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/plugins/CoreServer/src/Model/Entity/MatchServerAttribute.php b/app/plugins/CoreServer/src/Model/Entity/MatchServerAttribute.php index 84be4ce58..3f4be8a52 100644 --- a/app/plugins/CoreServer/src/Model/Entity/MatchServerAttribute.php +++ b/app/plugins/CoreServer/src/Model/Entity/MatchServerAttribute.php @@ -41,7 +41,7 @@ class MatchServerAttribute extends Entity { * * @var array */ - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/plugins/CoreServer/src/Model/Entity/Oauth2Server.php b/app/plugins/CoreServer/src/Model/Entity/Oauth2Server.php index 38c1352df..3881ee0c6 100644 --- a/app/plugins/CoreServer/src/Model/Entity/Oauth2Server.php +++ b/app/plugins/CoreServer/src/Model/Entity/Oauth2Server.php @@ -41,7 +41,7 @@ class Oauth2Server extends Entity { * * @var array */ - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/plugins/CoreServer/src/Model/Entity/SmtpServer.php b/app/plugins/CoreServer/src/Model/Entity/SmtpServer.php index f46d72386..4dd82c18d 100644 --- a/app/plugins/CoreServer/src/Model/Entity/SmtpServer.php +++ b/app/plugins/CoreServer/src/Model/Entity/SmtpServer.php @@ -41,7 +41,7 @@ class SmtpServer extends Entity { * * @var array */ - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/plugins/CoreServer/src/Model/Entity/SqlServer.php b/app/plugins/CoreServer/src/Model/Entity/SqlServer.php index 66ee6b241..081f28e03 100644 --- a/app/plugins/CoreServer/src/Model/Entity/SqlServer.php +++ b/app/plugins/CoreServer/src/Model/Entity/SqlServer.php @@ -41,7 +41,7 @@ class SqlServer extends Entity { * * @var array */ - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/plugins/CoreServer/src/Model/Table/MatchServersTable.php b/app/plugins/CoreServer/src/Model/Table/MatchServersTable.php index f11adea97..2805e0028 100644 --- a/app/plugins/CoreServer/src/Model/Table/MatchServersTable.php +++ b/app/plugins/CoreServer/src/Model/Table/MatchServersTable.php @@ -49,7 +49,7 @@ class MatchServersTable extends HttpServersTable { */ public function initialize(array $config): void { - parent::initialize($config); +// parent::initialize($config); $this->addBehavior('Changelog'); $this->addBehavior('Log'); @@ -230,9 +230,7 @@ protected function doRequest( $matchServer = $this->Servers->get( $serverId, - [ 'contain' => [ - 'MatchServers' => ['MatchServerAttributes' => 'Types'] - ]] + contain: ['MatchServers' => ['MatchServerAttributes' => 'Types']] ); if($matchServer->status != SuspendableStatusEnum::Active) { diff --git a/app/plugins/CoreServer/src/Model/Table/Oauth2ServersTable.php b/app/plugins/CoreServer/src/Model/Table/Oauth2ServersTable.php index b7dc576d4..473a73cb3 100644 --- a/app/plugins/CoreServer/src/Model/Table/Oauth2ServersTable.php +++ b/app/plugins/CoreServer/src/Model/Table/Oauth2ServersTable.php @@ -54,7 +54,8 @@ public function initialize(array $config): void { $this->setTableType(\App\Lib\Enum\TableTypeEnum::Configuration); // Define associations - $this->belongsTo('Servers'); + // XXX this is defined in HttpServersTable +// $this->belongsTo('Servers'); $this->setDisplayField('hostname'); diff --git a/app/plugins/EnvSource/src/Controller/EnvSourceCollectorsController.php b/app/plugins/EnvSource/src/Controller/EnvSourceCollectorsController.php index 167b8ef22..33d7bf9e9 100644 --- a/app/plugins/EnvSource/src/Controller/EnvSourceCollectorsController.php +++ b/app/plugins/EnvSource/src/Controller/EnvSourceCollectorsController.php @@ -36,7 +36,7 @@ use Cake\ORM\TableRegistry; class EnvSourceCollectorsController extends StandardEnrollerController { - public $paginate = [ + public array $paginate = [ 'order' => [ 'EnvSourceCollectors.id' => 'asc' ] diff --git a/app/plugins/EnvSource/src/Controller/EnvSourceDetoursController.php b/app/plugins/EnvSource/src/Controller/EnvSourceDetoursController.php index dbd5ffa7b..e0e7e24a1 100644 --- a/app/plugins/EnvSource/src/Controller/EnvSourceDetoursController.php +++ b/app/plugins/EnvSource/src/Controller/EnvSourceDetoursController.php @@ -35,7 +35,7 @@ use \App\Lib\Events\CoIdEventListener; class EnvSourceDetoursController extends StandardDetourController { - public $paginate = [ + public array $paginate = [ 'order' => [ 'EnvSourceDetours.id' => 'asc' ] diff --git a/app/plugins/EnvSource/src/Controller/EnvSourcesController.php b/app/plugins/EnvSource/src/Controller/EnvSourcesController.php index 81d6dd222..603831fac 100644 --- a/app/plugins/EnvSource/src/Controller/EnvSourcesController.php +++ b/app/plugins/EnvSource/src/Controller/EnvSourcesController.php @@ -34,7 +34,7 @@ use Cake\Http\Response; class EnvSourcesController extends StandardPluginController { - public $paginate = [ + public array $paginate = [ 'order' => [ 'EnvSources.id' => 'asc' ] diff --git a/app/plugins/EnvSource/src/Model/Entity/EnvSource.php b/app/plugins/EnvSource/src/Model/Entity/EnvSource.php index a739b2d30..70ad339ff 100644 --- a/app/plugins/EnvSource/src/Model/Entity/EnvSource.php +++ b/app/plugins/EnvSource/src/Model/Entity/EnvSource.php @@ -41,7 +41,7 @@ class EnvSource extends Entity { * * @var array */ - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/plugins/EnvSource/src/Model/Entity/EnvSourceCollector.php b/app/plugins/EnvSource/src/Model/Entity/EnvSourceCollector.php index 91989da2c..3b8099365 100644 --- a/app/plugins/EnvSource/src/Model/Entity/EnvSourceCollector.php +++ b/app/plugins/EnvSource/src/Model/Entity/EnvSourceCollector.php @@ -43,7 +43,7 @@ class EnvSourceCollector extends Entity { * * @var array */ - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/plugins/EnvSource/src/Model/Entity/EnvSourceDetour.php b/app/plugins/EnvSource/src/Model/Entity/EnvSourceDetour.php index 5b8717dc7..a73a1ebbd 100644 --- a/app/plugins/EnvSource/src/Model/Entity/EnvSourceDetour.php +++ b/app/plugins/EnvSource/src/Model/Entity/EnvSourceDetour.php @@ -43,7 +43,7 @@ class EnvSourceDetour extends Entity { * * @var array */ - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/plugins/EnvSource/src/Model/Entity/EnvSourceIdentity.php b/app/plugins/EnvSource/src/Model/Entity/EnvSourceIdentity.php index 2873ca37c..1d7bf04e6 100644 --- a/app/plugins/EnvSource/src/Model/Entity/EnvSourceIdentity.php +++ b/app/plugins/EnvSource/src/Model/Entity/EnvSourceIdentity.php @@ -41,7 +41,7 @@ class EnvSourceIdentity extends Entity { * * @var array */ - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/plugins/EnvSource/src/Model/Entity/PetitionEnvIdentity.php b/app/plugins/EnvSource/src/Model/Entity/PetitionEnvIdentity.php index beb919bfc..6f2548680 100644 --- a/app/plugins/EnvSource/src/Model/Entity/PetitionEnvIdentity.php +++ b/app/plugins/EnvSource/src/Model/Entity/PetitionEnvIdentity.php @@ -41,7 +41,7 @@ class PetitionEnvIdentity extends Entity { * * @var array */ - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/plugins/EnvSource/src/View/Cell/EnvSourceCollectorsCell.php b/app/plugins/EnvSource/src/View/Cell/EnvSourceCollectorsCell.php index 207b3c094..2e4e472e7 100644 --- a/app/plugins/EnvSource/src/View/Cell/EnvSourceCollectorsCell.php +++ b/app/plugins/EnvSource/src/View/Cell/EnvSourceCollectorsCell.php @@ -40,13 +40,28 @@ */ class EnvSourceCollectorsCell extends Cell { + /** + * @var mixed + */ + public $vv_obj; + + /** + * @var mixed + */ + public $vv_step; + + /** + * @var mixed + */ + public $viewVars; + /** * List of valid options that can be passed into this * cell's constructor. * * @var array */ - protected $_validCellOptions = [ + protected array $_validCellOptions = [ 'vv_obj', 'vv_step', 'viewVars', diff --git a/app/plugins/OrcidSource/src/Controller/OrcidSourceCollectorsController.php b/app/plugins/OrcidSource/src/Controller/OrcidSourceCollectorsController.php index b01ae0ed5..7b6492bff 100644 --- a/app/plugins/OrcidSource/src/Controller/OrcidSourceCollectorsController.php +++ b/app/plugins/OrcidSource/src/Controller/OrcidSourceCollectorsController.php @@ -41,7 +41,7 @@ class OrcidSourceCollectorsController extends StandardEnrollerController { protected $OrcidSources; protected $PetitionOrcids; - public $paginate = [ + public array $paginate = [ 'order' => [ 'OrcidSourceCollectors.id' => 'asc' ] @@ -58,7 +58,7 @@ public function beforeFilter(\Cake\Event\EventInterface $event) $this->OrcidSources = TableRegistry::getTableLocator()->get('OrcidSource.OrcidSources'); $this->PetitionOrcids = TableRegistry::getTableLocator()->get('OrcidSource.PetitionOrcids'); - return parent::beforeFilter($event); + parent::beforeFilter($event); } /** diff --git a/app/plugins/OrcidSource/src/Controller/OrcidSourcesController.php b/app/plugins/OrcidSource/src/Controller/OrcidSourcesController.php index 6d838a3db..401fb40a2 100644 --- a/app/plugins/OrcidSource/src/Controller/OrcidSourcesController.php +++ b/app/plugins/OrcidSource/src/Controller/OrcidSourcesController.php @@ -34,7 +34,7 @@ use Cake\Http\Response; class OrcidSourcesController extends StandardPluginController { - public $paginate = [ + public array $paginate = [ 'order' => [ 'EnvSources.id' => 'asc' ] diff --git a/app/plugins/OrcidSource/src/Model/Entity/OrcidSource.php b/app/plugins/OrcidSource/src/Model/Entity/OrcidSource.php index 5ac69f9b4..7d7dee042 100644 --- a/app/plugins/OrcidSource/src/Model/Entity/OrcidSource.php +++ b/app/plugins/OrcidSource/src/Model/Entity/OrcidSource.php @@ -41,7 +41,7 @@ class OrcidSource extends Entity { * * @var array */ - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/plugins/OrcidSource/src/Model/Entity/OrcidSourceCollector.php b/app/plugins/OrcidSource/src/Model/Entity/OrcidSourceCollector.php index f9935c44a..dfc309d06 100644 --- a/app/plugins/OrcidSource/src/Model/Entity/OrcidSourceCollector.php +++ b/app/plugins/OrcidSource/src/Model/Entity/OrcidSourceCollector.php @@ -43,7 +43,7 @@ class OrcidSourceCollector extends Entity { * * @var array */ - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/plugins/OrcidSource/src/Model/Entity/OrcidToken.php b/app/plugins/OrcidSource/src/Model/Entity/OrcidToken.php index 833bfab2d..a816fd3c8 100644 --- a/app/plugins/OrcidSource/src/Model/Entity/OrcidToken.php +++ b/app/plugins/OrcidSource/src/Model/Entity/OrcidToken.php @@ -41,7 +41,7 @@ class OrcidToken extends Entity { * * @var array */ - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/plugins/OrcidSource/src/Model/Entity/PetitionOrcid.php b/app/plugins/OrcidSource/src/Model/Entity/PetitionOrcid.php index a40df7369..91adda771 100644 --- a/app/plugins/OrcidSource/src/Model/Entity/PetitionOrcid.php +++ b/app/plugins/OrcidSource/src/Model/Entity/PetitionOrcid.php @@ -41,7 +41,7 @@ class PetitionOrcid extends Entity { * * @var array */ - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/plugins/OrcidSource/src/View/Cell/OrcidSourceCollectorsCell.php b/app/plugins/OrcidSource/src/View/Cell/OrcidSourceCollectorsCell.php index f15d25003..d073faf0b 100644 --- a/app/plugins/OrcidSource/src/View/Cell/OrcidSourceCollectorsCell.php +++ b/app/plugins/OrcidSource/src/View/Cell/OrcidSourceCollectorsCell.php @@ -40,13 +40,28 @@ */ class OrcidSourceCollectorsCell extends Cell { + /** + * @var mixed + */ + public $vv_obj; + + /** + * @var mixed + */ + public $vv_step; + + /** + * @var mixed + */ + public $viewVars; + /** * List of valid options that can be passed into this * cell's constructor. * * @var array */ - protected $_validCellOptions = [ + protected array $_validCellOptions = [ 'vv_obj', 'vv_step', 'viewVars', diff --git a/app/src/Command/ConsoleCommand.php b/app/src/Command/ConsoleCommand.php index 2ec000ef0..e18d601a8 100644 --- a/app/src/Command/ConsoleCommand.php +++ b/app/src/Command/ConsoleCommand.php @@ -17,7 +17,7 @@ namespace App\Command; use Cake\Console\Arguments; -use Cake\Console\Command; +use Cake\Console\BaseCommand; use Cake\Console\ConsoleIo; use Cake\Console\ConsoleOptionParser; use Cake\Log\Log; @@ -26,7 +26,7 @@ /** * Simple console wrapper around Psy\Shell. */ -class ConsoleCommand extends Command +class ConsoleCommand extends BaseCommand { /** * Start the Command and interactive console. diff --git a/app/src/Command/DatabaseCommand.php b/app/src/Command/DatabaseCommand.php index ea20bc9e8..b8f4219b4 100644 --- a/app/src/Command/DatabaseCommand.php +++ b/app/src/Command/DatabaseCommand.php @@ -30,14 +30,14 @@ namespace App\Command; use Cake\Console\Arguments; -use Cake\Console\Command; +use Cake\Console\BaseCommand; use Cake\Console\ConsoleIo; use Cake\Console\ConsoleOptionParser; use Cake\ORM\TableRegistry; use App\Lib\Util\SchemaManager; -class DatabaseCommand extends Command { +class DatabaseCommand extends BaseCommand { /** * Build an Option Parser. * diff --git a/app/src/Command/JobCommand.php b/app/src/Command/JobCommand.php index f4ef29831..4da6e9b8f 100644 --- a/app/src/Command/JobCommand.php +++ b/app/src/Command/JobCommand.php @@ -29,18 +29,21 @@ namespace App\Command; +use App\Lib\Enum\JobStatusEnum; +use App\Lib\Events\CoIdEventListener; use Cake\Console\Arguments; -use Cake\Console\Command; +use Cake\Console\BaseCommand; use Cake\Console\ConsoleIo; use Cake\Console\ConsoleOptionParser; use Cake\Datasource\ConnectionManager; use Cake\Event\EventManager; +use Cake\ORM\Locator\LocatorAwareTrait; use Cake\Utility\Security; -use App\Lib\Enum\JobStatusEnum; -use App\Lib\Events\CoIdEventListener; -class JobCommand extends Command +class JobCommand extends BaseCommand { + use LocatorAwareTrait; + /** * Register command specific options. * @@ -71,7 +74,7 @@ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionPar [ 'required' => false, 'short' => 'p', - 'default' => 1, + 'default' => '1', 'help' => __d('command', 'opt.job.parallel') ] )->addOption( @@ -79,7 +82,7 @@ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionPar [ 'required' => false, 'short' => 'm', - 'default' => 10, + 'default' => '10', 'help' => __d('command', 'opt.job.max') ] )->addOption( diff --git a/app/src/Command/NotificationCommand.php b/app/src/Command/NotificationCommand.php index b97bf4970..8f749130d 100644 --- a/app/src/Command/NotificationCommand.php +++ b/app/src/Command/NotificationCommand.php @@ -30,11 +30,11 @@ namespace App\Command; use Cake\Console\Arguments; -use Cake\Console\Command; +use Cake\Console\BaseCommand; use Cake\Console\ConsoleIo; use Cake\Console\ConsoleOptionParser; -class NotificationCommand extends Command +class NotificationCommand extends BaseCommand { /** * Register command specific options. diff --git a/app/src/Command/ResetMfaCommand.php b/app/src/Command/ResetMfaCommand.php index 81fb90d52..c12ec3ee3 100644 --- a/app/src/Command/ResetMfaCommand.php +++ b/app/src/Command/ResetMfaCommand.php @@ -30,14 +30,14 @@ namespace App\Command; use Cake\Console\Arguments; -use Cake\Console\Command; +use Cake\Console\BaseCommand; use Cake\Console\ConsoleIo; use Cake\Console\ConsoleOptionParser; use Cake\ORM\TableRegistry; use App\Lib\Util\SchemaManager; -class ResetMfaCommand extends Command { +class ResetMfaCommand extends BaseCommand { /** * Build an Option Parser. * diff --git a/app/src/Command/SetupCommand.php b/app/src/Command/SetupCommand.php index a273399d6..92fbe5b59 100644 --- a/app/src/Command/SetupCommand.php +++ b/app/src/Command/SetupCommand.php @@ -30,14 +30,14 @@ namespace App\Command; use Cake\Console\Arguments; -use Cake\Console\Command; +use Cake\Console\BaseCommand; use Cake\Console\ConsoleIo; use Cake\Console\ConsoleOptionParser; use Cake\Utility\Security; use App\Lib\Enum\PermissionEnum; use App\Lib\Enum\SuspendableStatusEnum; -class SetupCommand extends Command +class SetupCommand extends BaseCommand { /** * Register command specific options. diff --git a/app/src/Command/TestCommand.php b/app/src/Command/TestCommand.php index be346fa8a..8661c13ff 100644 --- a/app/src/Command/TestCommand.php +++ b/app/src/Command/TestCommand.php @@ -30,13 +30,13 @@ namespace App\Command; use Cake\Console\Arguments; -use Cake\Console\Command; +use Cake\Console\BaseCommand; use Cake\Console\ConsoleIo; use Cake\Console\ConsoleOptionParser; use Cake\Datasource\ConnectionManager; use \App\Lib\Util\DeliveryUtilities; -class TestCommand extends Command +class TestCommand extends BaseCommand { protected $io = null; diff --git a/app/src/Command/TransmogrifyCommand.php b/app/src/Command/TransmogrifyCommand.php index 93a72c00a..a3d24f886 100644 --- a/app/src/Command/TransmogrifyCommand.php +++ b/app/src/Command/TransmogrifyCommand.php @@ -30,7 +30,7 @@ namespace App\Command; use Cake\Console\Arguments; -use Cake\Console\Command; +use Cake\Console\BaseCommand; use Cake\Console\ConsoleIo; use Cake\Console\ConsoleOptionParser; use Cake\Datasource\ConnectionInterface; @@ -44,7 +44,7 @@ use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException; -class TransmogrifyCommand extends Command { +class TransmogrifyCommand extends BaseCommand { use \App\Lib\Traits\LabeledLogTrait; // Tables must be listed in order of primary key dependencies. diff --git a/app/src/Command/UpgradeCommand.php b/app/src/Command/UpgradeCommand.php index e341dd8e9..1026babc1 100644 --- a/app/src/Command/UpgradeCommand.php +++ b/app/src/Command/UpgradeCommand.php @@ -30,12 +30,12 @@ namespace App\Command; use Cake\Console\Arguments; -use Cake\Console\Command; +use Cake\Console\BaseCommand; use Cake\Console\ConsoleIo; use Cake\Console\ConsoleOptionParser; use Cake\Datasource\ConnectionManager; -class UpgradeCommand extends Command +class UpgradeCommand extends BaseCommand { protected $io = null; diff --git a/app/src/Controller/AdHocAttributesController.php b/app/src/Controller/AdHocAttributesController.php index a5898f686..6b7560592 100644 --- a/app/src/Controller/AdHocAttributesController.php +++ b/app/src/Controller/AdHocAttributesController.php @@ -33,8 +33,9 @@ use Cake\Log\Log; use Cake\ORM\TableRegistry; +#[\AllowDynamicProperties] class AdHocAttributesController extends MVEAController { - public $paginate = [ + protected array $paginate = [ 'order' => [ 'AdHocAttributes.tag' => 'asc' ] diff --git a/app/src/Controller/AddressesController.php b/app/src/Controller/AddressesController.php index 1db4f3218..1271fc995 100644 --- a/app/src/Controller/AddressesController.php +++ b/app/src/Controller/AddressesController.php @@ -33,8 +33,9 @@ use Cake\Log\Log; use Cake\ORM\TableRegistry; +#[\AllowDynamicProperties] class AddressesController extends MVEAController { - public $paginate = [ + protected array $paginate = [ 'order' => [ 'Addresses.street' => 'asc' ] diff --git a/app/src/Controller/ApiUsersController.php b/app/src/Controller/ApiUsersController.php index aa871b09b..a51f6794b 100644 --- a/app/src/Controller/ApiUsersController.php +++ b/app/src/Controller/ApiUsersController.php @@ -31,8 +31,9 @@ use App\Lib\Util\StringUtilities; +#[\AllowDynamicProperties] class ApiUsersController extends StandardController { - public $paginate = [ + protected array $paginate = [ 'order' => [ 'ApiUsers.username' => 'asc' ] diff --git a/app/src/Controller/ApiV2Controller.php b/app/src/Controller/ApiV2Controller.php index 435b156f7..82b707555 100644 --- a/app/src/Controller/ApiV2Controller.php +++ b/app/src/Controller/ApiV2Controller.php @@ -40,6 +40,7 @@ use \App\Lib\Enum\ProvisioningContextEnum; use \App\Lib\Enum\SuspendableStatusEnum; +#[\AllowDynamicProperties] class ApiV2Controller extends AppController { use \App\Lib\Traits\LabeledLogTrait; use \App\Lib\Traits\IndexQueryTrait; @@ -119,6 +120,7 @@ public function add() { $this->set('vv_results', $results); // Let the view render + $this->viewBuilder()->setLayout(null); $this->render('/Standard/api/v2/json/add-edit'); } @@ -206,6 +208,7 @@ public function delete($id) { } // Render an empty view + $this->viewBuilder()->setLayout('rest'); $this->render('/Standard/api/v2/json/delete'); } catch(\Exception $e) { @@ -251,6 +254,7 @@ protected function dispatchIndex(string $mode = 'default') { $this->set($this->tableName, $this->paginate($query)); // Let the view render + $this->viewBuilder()->setLayout('rest'); $this->render('/Standard/api/v2/json/index'); } @@ -296,6 +300,7 @@ public function edit($id) { } // Let the view render + $this->viewBuilder()->setLayout('rest'); $this->render('/Standard/api/v2/json/add-edit'); } catch(\Exception $e) { @@ -362,6 +367,7 @@ public function generateApiKey(string $id) { $this->set('vv_results', ['api_key' => $api_key]); // Let the view render + $this->viewBuilder()->setLayout('rest'); $this->render('/Standard/api/v2/json/add-edit'); } @@ -398,6 +404,7 @@ public function view($id = null) { $this->set($tableName, [$obj]); // Let the view render + $this->viewBuilder()->setLayout('rest'); $this->render('/Standard/api/v2/json/index'); } diff --git a/app/src/Controller/ApisController.php b/app/src/Controller/ApisController.php index c9a29ab85..cec1edc07 100644 --- a/app/src/Controller/ApisController.php +++ b/app/src/Controller/ApisController.php @@ -32,8 +32,9 @@ // XXX not doing anything with Log yet use Cake\Log\Log; +#[\AllowDynamicProperties] class ApisController extends StandardPluggableController { - public $paginate = [ + protected array $paginate = [ 'order' => [ 'Apis.description' => 'asc' ] diff --git a/app/src/Controller/AppController.php b/app/src/Controller/AppController.php index 975c34ec8..ab765bfe1 100644 --- a/app/src/Controller/AppController.php +++ b/app/src/Controller/AppController.php @@ -44,6 +44,7 @@ use Cake\ORM\TableRegistry; use Cake\Utility\Hash; use InvalidArgumentException; +use josegonzalez\Dotenv\Filter\NullFilter; use JsonSchema\Iterator\ObjectIterator; class AppController extends Controller { @@ -55,6 +56,12 @@ class AppController extends Controller { // If set, the current primary link. protected $cur_pl = null; + + protected $RegistryAuth = null; + protected $Breadcrumb = null; + + protected $Flash = null; + protected $FormProtection = null; /** * Initialization callback. @@ -64,11 +71,7 @@ class AppController extends Controller { public function initialize(): void { parent::initialize(); - - // Load Components used by most or all controllers - - $this->loadComponent('RequestHandler'); - + // Add a detector so we can tell restful from non-restful calls $request = $this->getRequest(); @@ -79,9 +82,11 @@ public function initialize(): void { // COmanage specific component that handles authn/z processintg $this->loadComponent('RegistryAuth'); + $this->RegistryAuth = $this->components()->get('RegistryAuth'); // Breadcrumb Manager $this->loadComponent('Breadcrumb'); + $this->Breadcrumb = $this->components()->get('Breadcrumb'); $ActorEventListener = new ActorEventListener($this->RegistryAuth); EventManager::instance()->on($ActorEventListener); @@ -92,6 +97,7 @@ public function initialize(): void { if(!$this->request->is('restful')) { // Initialization for non-RESTful $this->loadComponent('Flash'); + $this->Flash = $this->components()->get('Flash'); /* * Enable the following components for recommended CakePHP security settings. @@ -100,6 +106,7 @@ public function initialize(): void { * In general, we don't need these protections for transactional API calls. */ $this->loadComponent('FormProtection'); + $this->FormProtection = $this->components()->get('FormProtection'); // CSRF Protection is enabled via in Middleware via Application.php. } @@ -211,7 +218,7 @@ public function beforeFilter(\Cake\Event\EventInterface $event) { $this->getAppPrefs(); - return parent::beforeFilter($event); + parent::beforeFilter($event); } /** diff --git a/app/src/Controller/AuthenticationEventsController.php b/app/src/Controller/AuthenticationEventsController.php index c991d03ba..c6e7e59da 100644 --- a/app/src/Controller/AuthenticationEventsController.php +++ b/app/src/Controller/AuthenticationEventsController.php @@ -34,7 +34,7 @@ use Cake\ORM\TableRegistry; class AuthenticationEventsController extends StandardController { - public $paginate = [ + protected array $paginate = [ 'order' => [ 'AuthenticationEvents.id' => 'desc' ] diff --git a/app/src/Controller/CoSettingsController.php b/app/src/Controller/CoSettingsController.php index 0ca438200..0e1b4b4b4 100644 --- a/app/src/Controller/CoSettingsController.php +++ b/app/src/Controller/CoSettingsController.php @@ -32,6 +32,7 @@ // XXX not doing anything with Log yet use Cake\Log\Log; +#[\AllowDynamicProperties] class CoSettingsController extends StandardController { /** * Perform Controller initialization. diff --git a/app/src/Controller/Component/BreadcrumbComponent.php b/app/src/Controller/Component/BreadcrumbComponent.php index 349d16c7e..8d570e201 100644 --- a/app/src/Controller/Component/BreadcrumbComponent.php +++ b/app/src/Controller/Component/BreadcrumbComponent.php @@ -213,7 +213,7 @@ public function injectPrimaryLink(object $link, bool $index=true, string $linkLa $linkTable = TableRegistry::getTableLocator()->get($modelPath); $contain = method_exists($linkTable, $containsList) ? $linkTable->$containsList() : []; - $linkObj = $linkTable->get($link->value, ['contain' => $contain]); + $linkObj = $linkTable->get($link->value, contain: $contain); if($index) { // We need to determine the primary link of the parent, which might or might diff --git a/app/src/Controller/Component/RegistryAuthComponent.php b/app/src/Controller/Component/RegistryAuthComponent.php index 5df86abcc..48f3f6349 100644 --- a/app/src/Controller/Component/RegistryAuthComponent.php +++ b/app/src/Controller/Component/RegistryAuthComponent.php @@ -57,6 +57,7 @@ use \Cake\Http\Exception\ForbiddenException; use \Cake\Http\Exception\UnauthorizedException; use \Cake\ORM\ResultSet; +use \Cake\Datasource\Paging\PaginatedResultSet; use \Cake\ORM\TableRegistry; use \Cake\Utility\Inflector; use \App\Lib\Enum\AuthenticationEventEnum; @@ -151,7 +152,8 @@ public function beforeFilter(EventInterface $event) { break; case 'open': // The current request is open/public, no auth required - return true; + $event->setResult(true); + return; break; case 'no': // The controller will not do either authn or authz, so apply @@ -161,7 +163,8 @@ public function beforeFilter(EventInterface $event) { // The controller will handle both authn and authz, simply return // (The expectation is that the controller already performed the appropriate // checks before returning 'yes', on failure 'notauth' should be returned.) - return true; + $event->setResult(true); + return; break; case 'notauth': // The controller has rejected this request as unauthenticated or unauthorized @@ -227,8 +230,9 @@ public function beforeFilter(EventInterface $event) { eventType: AuthenticationEventEnum::ApiLogin, remoteIp: $_SERVER['REMOTE_ADDR']); } - - return true; + + $event->setResult(true); + return; } } @@ -268,11 +272,13 @@ public function beforeFilter(EventInterface $event) { // to the controller to determine if authz was met, and if not redirect // appropriately. We _don't_ want to call our own calculatePermission(). if($controller->calculatePermission()) { - return true; + $event->setResult(true); + return; } } elseif($this->calculatePermission($request->getParam('action'), $id)) { // Authorization successful - return true; + $event->setResult(true); + return; } if(Configure::read('debug')) { @@ -283,7 +289,8 @@ public function beforeFilter(EventInterface $event) { throw new ForbiddenException(__d('error', 'perm')); } $controller->Flash->error("Authorization Failed (RegistryAuthComponent)"); - return $controller->redirect("/"); + $event->setResult($controller->redirect("/")); + return; } } @@ -296,8 +303,8 @@ public function beforeFilter(EventInterface $event) { // We want to come back to where we started $session->write('Auth.target', $request->getRequestTarget()); - - return $controller->redirect("/traffic/prepare-login"); + + $event->setResult($controller->redirect("/traffic/prepare-login")); } } @@ -305,8 +312,8 @@ public function beforeFilter(EventInterface $event) { * Calculate permissions for this action. * * @since COmanage Registry v5.0.0 - * @param string $action Action requested - * @param int $id Subject id, if applicable + * @param string $action Action requested + * @param int|null $id Subject id, if applicable * @return bool true if the action is permitted, false otherwise * @throws UnauthorizedException */ @@ -399,7 +406,7 @@ protected function calculatePermissions(?int $id=null): array { // QueryModificationTrait $getActionMethod = "get{$reqAction}Contains"; - if(method_exists($table, $getActionMethod)) { + if(method_exists($table, $getActionMethod) && $table->$getActionMethod()) { $query = $query->contain($table->$getActionMethod()); } @@ -561,24 +568,24 @@ protected function calculatePermissions(?int $id=null): array { * Calculate permissions for a Result Set. * * @since COmanage Registry v5.0.0 - * @param ResultSet $rs Result Set + * @param ResultSet|PaginatedResultSet $rs Result Set * @return array Array of permissions keyed on record ID */ - public function calculatePermissionsForResultSet(ResultSet $rs): array { + public function calculatePermissionsForResultSet(ResultSet|PaginatedResultSet $rs): array { // We return an array since this is intended to be passed to a view $ret = []; // Note these are Cake ORM functions (rewind, current, etc), and not array // functions that PHP deprecated in 8.1.0. - $rs->rewind(); + $rs->items()->rewind(); - while($rs->valid()) { - $o = $rs->current(); + while($rs->items()->valid()) { + $o = $rs->items()->current(); $ret[ $o->id ] = $this->calculatePermissions($o->id); - $rs->next(); + $rs->items()->next(); } return $ret; @@ -818,7 +825,7 @@ public function isApprover(int $petitionId): bool { $Identifiers = TableRegistry::getTableLocator()->get('Identifiers'); // Pull the Petition to find its CO - $petition = $Petitions->get($petitionId, ['contain' => 'EnrollmentFlows']); + $petition = $Petitions->get($petitionId, contain: ['EnrollmentFlows']); try { // Map the authenticated user to a Person ID @@ -973,8 +980,10 @@ protected function isIdentifierAdmin(string $identifier, int $coId): bool { && $i->person->co->id == $coId) { // We found a Person in this CO, now see if it's an admin // (for which we'll need the admin group) - - $adminGroup = $Cos->Groups->find('adminGroup', ['co_id' => $i->person->co_id])->firstOrFail(); + $adminGroup = $Cos->Groups->find( + type: 'adminGroup', + co_id: $i->person->co_id + )->firstOrFail(); return $Cos->Groups->GroupMembers->isMember(groupId: $adminGroup->id, personId: $i->person->id); } diff --git a/app/src/Controller/CosController.php b/app/src/Controller/CosController.php index 5156077a1..7bfdf1ccc 100644 --- a/app/src/Controller/CosController.php +++ b/app/src/Controller/CosController.php @@ -36,12 +36,12 @@ use Cake\ORM\TableRegistry; class CosController extends StandardController { - public $paginate = [ + protected array $paginate = [ 'order' => [ 'Cos.name' => 'asc' ] ]; - + /** * Perform Controller initialization. * diff --git a/app/src/Controller/CousController.php b/app/src/Controller/CousController.php index 7520c505c..87ddd05f4 100644 --- a/app/src/Controller/CousController.php +++ b/app/src/Controller/CousController.php @@ -33,8 +33,9 @@ use Cake\Log\Log; //use \App\Lib\Enum\PermissionEnum; +#[\AllowDynamicProperties] class CousController extends StandardController { - public $paginate = [ + protected array $paginate = [ 'order' => [ 'Cous.name' => 'asc' ] diff --git a/app/src/Controller/DashboardsController.php b/app/src/Controller/DashboardsController.php index a17e138c5..6c3b66021 100644 --- a/app/src/Controller/DashboardsController.php +++ b/app/src/Controller/DashboardsController.php @@ -37,6 +37,7 @@ use Cake\Utility\Inflector; //use \App\Lib\Enum\PermissionEnum; +#[\AllowDynamicProperties] class DashboardsController extends StandardController { /** * Perform Controller initialization. diff --git a/app/src/Controller/EmailAddressesController.php b/app/src/Controller/EmailAddressesController.php index 9113c879d..54ab59722 100644 --- a/app/src/Controller/EmailAddressesController.php +++ b/app/src/Controller/EmailAddressesController.php @@ -33,8 +33,9 @@ use Cake\Log\Log; use Cake\ORM\TableRegistry; +#[\AllowDynamicProperties] class EmailAddressesController extends MVEAController { - public $paginate = [ + protected array $paginate = [ 'order' => [ 'EmailAddresses.mail' => 'asc' ] diff --git a/app/src/Controller/EnrollmentFlowStepsController.php b/app/src/Controller/EnrollmentFlowStepsController.php index 866c9a599..810fe61ae 100644 --- a/app/src/Controller/EnrollmentFlowStepsController.php +++ b/app/src/Controller/EnrollmentFlowStepsController.php @@ -34,8 +34,9 @@ use Cake\Http\Response; use Cake\Log\Log; +#[\AllowDynamicProperties] class EnrollmentFlowStepsController extends StandardPluggableController { - public $paginate = [ + protected array $paginate = [ 'order' => [ 'EnrollmentFlowSteps.ordr' => 'asc' ] diff --git a/app/src/Controller/EnrollmentFlowsController.php b/app/src/Controller/EnrollmentFlowsController.php index 848e3ae45..651c48f64 100644 --- a/app/src/Controller/EnrollmentFlowsController.php +++ b/app/src/Controller/EnrollmentFlowsController.php @@ -33,10 +33,11 @@ use Cake\Log\Log; use \App\Lib\Enum\EnrollmentAuthzEnum; +#[\AllowDynamicProperties] class EnrollmentFlowsController extends StandardController { use \App\Lib\Traits\EnrollmentControllerTrait; - public $paginate = [ + protected array $paginate = [ 'order' => [ 'EnrollmentFlows.name' => 'asc' ] diff --git a/app/src/Controller/ErrorController.php b/app/src/Controller/ErrorController.php index 27dfde501..d61b37f58 100644 --- a/app/src/Controller/ErrorController.php +++ b/app/src/Controller/ErrorController.php @@ -23,6 +23,7 @@ * * Controller used by ExceptionRenderer to render error responses. */ +#[\AllowDynamicProperties] class ErrorController extends AppController { /** @@ -32,7 +33,6 @@ class ErrorController extends AppController */ public function initialize(): void { - $this->loadComponent('RequestHandler'); } /** diff --git a/app/src/Controller/ExtIdentitySourceRecordsController.php b/app/src/Controller/ExtIdentitySourceRecordsController.php index d7e463c20..148a006e2 100644 --- a/app/src/Controller/ExtIdentitySourceRecordsController.php +++ b/app/src/Controller/ExtIdentitySourceRecordsController.php @@ -33,8 +33,9 @@ use Cake\Log\Log; use Cake\ORM\TableRegistry; +#[\AllowDynamicProperties] class ExtIdentitySourceRecordsController extends StandardController { - public $paginate = [ + protected array $paginate = [ 'order' => [ 'ExtIdentitySourceRecords.id' => 'asc' ] diff --git a/app/src/Controller/ExternalIdentitiesController.php b/app/src/Controller/ExternalIdentitiesController.php index 3fb9427ab..031072920 100644 --- a/app/src/Controller/ExternalIdentitiesController.php +++ b/app/src/Controller/ExternalIdentitiesController.php @@ -35,8 +35,9 @@ // Use extend MVEAController for breadcrumb rendering. ExternalIdentities is // sort of an MVEA, so maybe it makes sense to treat it as such. +#[\AllowDynamicProperties] class ExternalIdentitiesController extends MVEAController { - public $paginate = [ + protected array $paginate = [ 'order' => [ 'Name.family' => 'asc' ], diff --git a/app/src/Controller/ExternalIdentityRolesController.php b/app/src/Controller/ExternalIdentityRolesController.php index 28bb1e86d..7620c6c31 100644 --- a/app/src/Controller/ExternalIdentityRolesController.php +++ b/app/src/Controller/ExternalIdentityRolesController.php @@ -35,8 +35,9 @@ // Use extend MVEAController for breadcrumb rendering. ExternalIdentityRoles is sort of // an MVEA, so maybe it makes sense to treat it as such. +#[\AllowDynamicProperties] class ExternalIdentityRolesController extends MVEAController { - public $paginate = [ + protected array $paginate = [ 'order' => [ 'ExternalIdentityRoles.ordr' => 'asc', 'ExternalIdentityRoles.title' => 'asc' diff --git a/app/src/Controller/ExternalIdentitySourcesController.php b/app/src/Controller/ExternalIdentitySourcesController.php index 73ecd8604..78d67b025 100644 --- a/app/src/Controller/ExternalIdentitySourcesController.php +++ b/app/src/Controller/ExternalIdentitySourcesController.php @@ -34,9 +34,10 @@ use Cake\Log\Log; use Cake\ORM\TableRegistry; +#[\AllowDynamicProperties] class ExternalIdentitySourcesController extends StandardPluggableController { - public $paginate = [ + protected array $paginate = [ 'order' => [ 'ExternalIdentitySources.description' => 'asc' ] @@ -95,7 +96,7 @@ public function beforeFilter(\Cake\Event\EventInterface $event) { } } - return parent::beforeFilter($event); + parent::beforeFilter($event); } /** diff --git a/app/src/Controller/FlangesController.php b/app/src/Controller/FlangesController.php index 6dcffd261..17f4348ea 100644 --- a/app/src/Controller/FlangesController.php +++ b/app/src/Controller/FlangesController.php @@ -32,8 +32,9 @@ // XXX not doing anything with Log yet use Cake\Log\Log; +#[\AllowDynamicProperties] class FlangesController extends StandardPluggableController { - public $paginate = [ + protected array $paginate = [ 'order' => [ 'Flanges.description' => 'asc' ] diff --git a/app/src/Controller/GroupMembersController.php b/app/src/Controller/GroupMembersController.php index e10587dcb..9ae68ff77 100644 --- a/app/src/Controller/GroupMembersController.php +++ b/app/src/Controller/GroupMembersController.php @@ -34,8 +34,9 @@ use Cake\Http\Response; use Cake\Log\Log; +#[\AllowDynamicProperties] class GroupMembersController extends StandardController { - public $paginate = [ + protected array $paginate = [ 'order' => [ 'People.primary_name.name' => 'asc' ] diff --git a/app/src/Controller/GroupNestingsController.php b/app/src/Controller/GroupNestingsController.php index 69ca32bbc..52f97651c 100644 --- a/app/src/Controller/GroupNestingsController.php +++ b/app/src/Controller/GroupNestingsController.php @@ -32,8 +32,9 @@ // XXX not doing anything with Log yet use Cake\Log\Log; +#[\AllowDynamicProperties] class GroupNestingsController extends StandardController { - public $paginate = [ + protected array $paginate = [ 'order' => [ 'Group.name' => 'asc' ] diff --git a/app/src/Controller/GroupsController.php b/app/src/Controller/GroupsController.php index cbd9bae10..7b60de21b 100644 --- a/app/src/Controller/GroupsController.php +++ b/app/src/Controller/GroupsController.php @@ -32,8 +32,9 @@ // XXX not doing anything with Log yet use Cake\Log\Log; +#[\AllowDynamicProperties] class GroupsController extends StandardController { - public $paginate = [ + protected array $paginate = [ 'order' => [ 'Groups.name' => 'asc' ] diff --git a/app/src/Controller/HistoryRecordsController.php b/app/src/Controller/HistoryRecordsController.php index 2a84b79bd..b8835a37a 100644 --- a/app/src/Controller/HistoryRecordsController.php +++ b/app/src/Controller/HistoryRecordsController.php @@ -33,8 +33,9 @@ use Cake\Log\Log; use Cake\ORM\TableRegistry; +#[\AllowDynamicProperties] class HistoryRecordsController extends MVEAController { - public $paginate = [ + protected array $paginate = [ 'order' => [ 'HistoryRecords.id' => 'desc' ] diff --git a/app/src/Controller/IdentifierAssignmentsController.php b/app/src/Controller/IdentifierAssignmentsController.php index 64e010cd7..7e8fb3e59 100644 --- a/app/src/Controller/IdentifierAssignmentsController.php +++ b/app/src/Controller/IdentifierAssignmentsController.php @@ -34,8 +34,9 @@ use Cake\ORM\TableRegistry; use \App\Lib\Util\StringUtilities; +#[\AllowDynamicProperties] class IdentifierAssignmentsController extends StandardPluggableController { - public $paginate = [ + protected array $paginate = [ 'order' => [ 'IdentifierAssignments.description' => 'asc' ] diff --git a/app/src/Controller/IdentifiersController.php b/app/src/Controller/IdentifiersController.php index 73abf0152..6cd99047a 100644 --- a/app/src/Controller/IdentifiersController.php +++ b/app/src/Controller/IdentifiersController.php @@ -33,8 +33,9 @@ use Cake\Log\Log; use Cake\ORM\TableRegistry; +#[\AllowDynamicProperties] class IdentifiersController extends MVEAController { - public $paginate = [ + protected array $paginate = [ 'order' => [ 'Identifiers.identifier' => 'asc' ] diff --git a/app/src/Controller/JobHistoryRecordsController.php b/app/src/Controller/JobHistoryRecordsController.php index 95ed62b55..858215132 100644 --- a/app/src/Controller/JobHistoryRecordsController.php +++ b/app/src/Controller/JobHistoryRecordsController.php @@ -33,8 +33,9 @@ use Cake\Log\Log; use Cake\ORM\TableRegistry; +#[\AllowDynamicProperties] class JobHistoryRecordsController extends StandardController { - public $paginate = [ + protected array $paginate = [ 'order' => [ 'JobHistoryRecords.id' => 'desc' ] diff --git a/app/src/Controller/JobsController.php b/app/src/Controller/JobsController.php index 4d399ddc8..f940d4275 100644 --- a/app/src/Controller/JobsController.php +++ b/app/src/Controller/JobsController.php @@ -33,8 +33,9 @@ use Cake\Log\Log; use Cake\ORM\TableRegistry; +#[\AllowDynamicProperties] class JobsController extends StandardController { - public $paginate = [ + protected array $paginate = [ 'order' => [ 'Jobs.id' => 'desc' ] diff --git a/app/src/Controller/MVEAController.php b/app/src/Controller/MVEAController.php index 4a7ec6507..1806520f9 100644 --- a/app/src/Controller/MVEAController.php +++ b/app/src/Controller/MVEAController.php @@ -35,6 +35,7 @@ use Cake\Utility\Inflector; use \App\Lib\Util\StringUtilities; +#[\AllowDynamicProperties] class MVEAController extends StandardController { /** * Callback run prior to the request action. @@ -127,7 +128,7 @@ public function beforeFilter(\Cake\Event\EventInterface $event) { } } - return parent::beforeFilter($event); + parent::beforeFilter($event); } /** diff --git a/app/src/Controller/MessageTemplatesController.php b/app/src/Controller/MessageTemplatesController.php index 86b4e89b2..1dbc87b50 100644 --- a/app/src/Controller/MessageTemplatesController.php +++ b/app/src/Controller/MessageTemplatesController.php @@ -32,8 +32,9 @@ // XXX not doing anything with Log yet use Cake\Log\Log; +#[\AllowDynamicProperties] class MessageTemplatesController extends StandardController { - public $paginate = [ + protected array $paginate = [ 'order' => [ 'MessageTemplates.description' => 'asc' ] diff --git a/app/src/Controller/MostlyStaticPagesController.php b/app/src/Controller/MostlyStaticPagesController.php index ad1ee8171..cb8fd3e23 100644 --- a/app/src/Controller/MostlyStaticPagesController.php +++ b/app/src/Controller/MostlyStaticPagesController.php @@ -32,8 +32,9 @@ // XXX not doing anything with Log yet use Cake\Log\Log; +#[\AllowDynamicProperties] class MostlyStaticPagesController extends StandardController { - public $paginate = [ + protected array $paginate = [ 'order' => [ 'MostlyStaticPages.title' => 'asc' ] diff --git a/app/src/Controller/NamesController.php b/app/src/Controller/NamesController.php index f56b5e789..40e66ed7e 100644 --- a/app/src/Controller/NamesController.php +++ b/app/src/Controller/NamesController.php @@ -34,7 +34,7 @@ use Cake\ORM\TableRegistry; class NamesController extends MVEAController { - public $paginate = [ + protected array $paginate = [ 'order' => [ 'Names.family' => 'asc', 'Names.given' => 'asc' diff --git a/app/src/Controller/NotificationsController.php b/app/src/Controller/NotificationsController.php index c6a388608..f699d6168 100644 --- a/app/src/Controller/NotificationsController.php +++ b/app/src/Controller/NotificationsController.php @@ -32,8 +32,9 @@ // XXX not doing anything with Log yet use Cake\Log\Log; +#[\AllowDynamicProperties] class NotificationsController extends StandardController { - public $paginate = [ + protected array $paginate = [ 'order' => [ 'Notifications.modified' => 'desc' ] diff --git a/app/src/Controller/PeopleController.php b/app/src/Controller/PeopleController.php index 5d529a590..f08175bbc 100644 --- a/app/src/Controller/PeopleController.php +++ b/app/src/Controller/PeopleController.php @@ -34,8 +34,9 @@ use Cake\ORM\TableRegistry; use http\QueryString; +#[\AllowDynamicProperties] class PeopleController extends StandardController { - public $paginate = [ + protected array $paginate = [ 'order' => [ // XXX this will sort by family name, but it this universally correct? // so we need a configuration, or can we do something automagic? @@ -53,7 +54,22 @@ class PeopleController extends StandardController { ], 'finder' => 'indexed' ]; - + + /** + * Perform Cake Controller initialization. + * + * @since COmanage Registry v5.2.0 + */ + public function initialize(): void + { + parent::initialize(); + + if (!$this->request->is('restful') && !$this->request->is('ajax')) { + unset($this->paginate['finder']); + } + } + + /** * Callback run prior to the request render. * diff --git a/app/src/Controller/PersonRolesController.php b/app/src/Controller/PersonRolesController.php index 95aeb67fb..63e150bb7 100644 --- a/app/src/Controller/PersonRolesController.php +++ b/app/src/Controller/PersonRolesController.php @@ -35,8 +35,9 @@ // Use extend MVEAController for breadcrumb rendering. PersonRoles is sort of // an MVEA, so maybe it makes sense to treat it as such. +#[\AllowDynamicProperties] class PersonRolesController extends MVEAController { - public $paginate = [ + protected array $paginate = [ 'order' => [ 'PersonRoles.ordr' => 'asc', 'PersonRoles.title' => 'asc' @@ -61,7 +62,7 @@ public function beforeFilter(\Cake\Event\EventInterface $event) { $this->cachedPerson = $this->PersonRoles->People->get($this->request->getData('person_id')); } - return parent::beforeFilter($event); + parent::beforeFilter($event); } /** diff --git a/app/src/Controller/PetitionsController.php b/app/src/Controller/PetitionsController.php index 90f129c35..dfeb68ac2 100644 --- a/app/src/Controller/PetitionsController.php +++ b/app/src/Controller/PetitionsController.php @@ -39,10 +39,11 @@ use \App\Lib\Enum\SuspendableStatusEnum; use \App\Lib\Util\StringUtilities; +#[\AllowDynamicProperties] class PetitionsController extends StandardController { use \App\Lib\Traits\EnrollmentControllerTrait; - public $paginate = [ + protected array $paginate = [ 'order' => [ 'Petitions.modified' => 'desc' ] @@ -329,7 +330,7 @@ public function resume(string $id) { ['PetitionStepResults' => ['conditions' => ['PetitionStepResults.petition_id' => $petition->id]]], $this->Petitions->EnrollmentFlows->EnrollmentFlowSteps->getPluginRelations() )) - ->order(['EnrollmentFlowSteps.ordr']) + ->orderBy(['EnrollmentFlowSteps.ordr']) ->all(); $this->set('vv_steps', $steps); diff --git a/app/src/Controller/PipelinesController.php b/app/src/Controller/PipelinesController.php index bd841023f..44696d1d3 100644 --- a/app/src/Controller/PipelinesController.php +++ b/app/src/Controller/PipelinesController.php @@ -32,8 +32,9 @@ // XXX not doing anything with Log yet use Cake\Log\Log; +#[\AllowDynamicProperties] class PipelinesController extends StandardController { - public $paginate = [ + protected array $paginate = [ 'order' => [ 'Pipelines.description' => 'asc' ] diff --git a/app/src/Controller/PluginsController.php b/app/src/Controller/PluginsController.php index c8255cab6..6e4e5589c 100644 --- a/app/src/Controller/PluginsController.php +++ b/app/src/Controller/PluginsController.php @@ -32,8 +32,9 @@ // XXX not doing anything with Log yet use Cake\Log\Log; +#[\AllowDynamicProperties] class PluginsController extends StandardController { - public $paginate = [ + protected array $paginate = [ 'order' => [ 'Plugins.plugin' => 'asc' ] diff --git a/app/src/Controller/PronounsController.php b/app/src/Controller/PronounsController.php index 36f0c3606..e3b32810c 100644 --- a/app/src/Controller/PronounsController.php +++ b/app/src/Controller/PronounsController.php @@ -33,8 +33,9 @@ use Cake\Log\Log; use Cake\ORM\TableRegistry; +#[\AllowDynamicProperties] class PronounsController extends MVEAController { - public $paginate = [ + protected array $paginate = [ 'order' => [ 'Pronouns.pronouns' => 'asc' ] diff --git a/app/src/Controller/ProvisioningHistoryRecordsController.php b/app/src/Controller/ProvisioningHistoryRecordsController.php index 4a6aa9b1f..6b7d6b966 100644 --- a/app/src/Controller/ProvisioningHistoryRecordsController.php +++ b/app/src/Controller/ProvisioningHistoryRecordsController.php @@ -33,8 +33,9 @@ use Cake\Log\Log; use Cake\ORM\TableRegistry; +#[\AllowDynamicProperties] class ProvisioningHistoryRecordsController extends StandardController { - public $paginate = [ + protected array $paginate = [ 'order' => [ 'ProvisioningHistoryRecords.id' => 'desc' ] diff --git a/app/src/Controller/ProvisioningTargetsController.php b/app/src/Controller/ProvisioningTargetsController.php index 26542e44b..1855bcad3 100644 --- a/app/src/Controller/ProvisioningTargetsController.php +++ b/app/src/Controller/ProvisioningTargetsController.php @@ -35,8 +35,9 @@ use Cake\Log\Log; use Cake\ORM\TableRegistry; +#[\AllowDynamicProperties] class ProvisioningTargetsController extends StandardPluggableController { - public $paginate = [ + protected array $paginate = [ 'order' => [ 'ProvisioningTargets.description' => 'asc' ] @@ -74,7 +75,7 @@ public function beforeFilter(\Cake\Event\EventInterface $event) { } } - return parent::beforeFilter($event); + parent::beforeFilter($event); } /** diff --git a/app/src/Controller/ServersController.php b/app/src/Controller/ServersController.php index 9cb5931f2..8003c5fdd 100644 --- a/app/src/Controller/ServersController.php +++ b/app/src/Controller/ServersController.php @@ -32,8 +32,9 @@ // XXX not doing anything with Log yet use Cake\Log\Log; +#[\AllowDynamicProperties] class ServersController extends StandardPluggableController { - public $paginate = [ + protected array $paginate = [ 'order' => [ 'Servers.description' => 'asc' ] diff --git a/app/src/Controller/StandardApiController.php b/app/src/Controller/StandardApiController.php index 49dd9fb10..b3a096109 100644 --- a/app/src/Controller/StandardApiController.php +++ b/app/src/Controller/StandardApiController.php @@ -33,6 +33,7 @@ use Cake\Utility\Inflector; use App\Lib\Enum\SuspendableStatusEnum; +#[\AllowDynamicProperties] class StandardApiController extends AppController { /** * Perform Cake Controller initialization. diff --git a/app/src/Controller/StandardController.php b/app/src/Controller/StandardController.php index 7d3583784..d8617c9dc 100644 --- a/app/src/Controller/StandardController.php +++ b/app/src/Controller/StandardController.php @@ -42,6 +42,7 @@ use \App\Lib\Util\{StringUtilities, FunctionUtilities}; use \Cake\Http\Exception\BadRequestException; +#[\AllowDynamicProperties] class StandardController extends AppController { use IndexQueryTrait; use ApplicationStatesTrait; @@ -162,7 +163,7 @@ public function beforeFilter(\Cake\Event\EventInterface $event) { } } - return parent::beforeFilter($event); + parent::beforeFilter($event); } /** diff --git a/app/src/Controller/StandardDetourController.php b/app/src/Controller/StandardDetourController.php index b822a911b..3239aedd0 100644 --- a/app/src/Controller/StandardDetourController.php +++ b/app/src/Controller/StandardDetourController.php @@ -35,6 +35,7 @@ use \App\Lib\Enum\EnrollmentActorEnum; use \App\Lib\Util\StringUtilities; +#[\AllowDynamicProperties] class StandardDetourController extends StandardPluginController { /** * Generate the redirect for the completion of a Detour. diff --git a/app/src/Controller/StandardEnrollerController.php b/app/src/Controller/StandardEnrollerController.php index e61423499..ad41e85c9 100644 --- a/app/src/Controller/StandardEnrollerController.php +++ b/app/src/Controller/StandardEnrollerController.php @@ -35,6 +35,7 @@ use \App\Lib\Enum\EnrollmentActorEnum; use \App\Lib\Util\StringUtilities; +#[\AllowDynamicProperties] class StandardEnrollerController extends StandardPluginController { use \App\Lib\Traits\EnrollmentControllerTrait; diff --git a/app/src/Controller/StandardPluggableController.php b/app/src/Controller/StandardPluggableController.php index e8f33c287..30e344936 100644 --- a/app/src/Controller/StandardPluggableController.php +++ b/app/src/Controller/StandardPluggableController.php @@ -33,6 +33,7 @@ use Cake\Log\Log; use App\Lib\Util\StringUtilities; +#[\AllowDynamicProperties] class StandardPluggableController extends StandardController { /** * Redirect into the edit view of the instantiated plugin. diff --git a/app/src/Controller/StandardPluginController.php b/app/src/Controller/StandardPluginController.php index 27def58d4..3a1a7bf3b 100644 --- a/app/src/Controller/StandardPluginController.php +++ b/app/src/Controller/StandardPluginController.php @@ -36,6 +36,7 @@ use \App\Lib\Util\StringUtilities; use \App\Lib\Enum\SuspendableStatusEnum; +#[\AllowDynamicProperties] class StandardPluginController extends StandardController { /** * Callback run prior to the request action. @@ -71,7 +72,7 @@ public function beforeFilter(\Cake\Event\EventInterface $event) { } } - return parent::beforeFilter($event); + parent::beforeFilter($event); } /** diff --git a/app/src/Controller/TelephoneNumbersController.php b/app/src/Controller/TelephoneNumbersController.php index 25227cd6c..325d427fa 100644 --- a/app/src/Controller/TelephoneNumbersController.php +++ b/app/src/Controller/TelephoneNumbersController.php @@ -33,8 +33,9 @@ use Cake\Log\Log; use Cake\ORM\TableRegistry; +#[\AllowDynamicProperties] class TelephoneNumbersController extends MVEAController { - public $paginate = [ + protected array $paginate = [ 'order' => [ 'TelephoneNumbers.number' => 'asc' ] diff --git a/app/src/Controller/TrafficController.php b/app/src/Controller/TrafficController.php index e3b8e1b89..dcc5de078 100644 --- a/app/src/Controller/TrafficController.php +++ b/app/src/Controller/TrafficController.php @@ -38,6 +38,7 @@ // XXX not doing anything with Log yet use \Cake\Log\Log; +#[\AllowDynamicProperties] class TrafficController extends Controller { /** * Initiate a login transaction. diff --git a/app/src/Controller/TrafficDetoursController.php b/app/src/Controller/TrafficDetoursController.php index d7d5200a9..1132d438c 100644 --- a/app/src/Controller/TrafficDetoursController.php +++ b/app/src/Controller/TrafficDetoursController.php @@ -32,8 +32,9 @@ // XXX not doing anything with Log yet use Cake\Log\Log; +#[\AllowDynamicProperties] class TrafficDetoursController extends StandardPluggableController { - public $paginate = [ + protected array $paginate = [ 'order' => [ 'TrafficDetours.description' => 'asc' ] diff --git a/app/src/Controller/TypesController.php b/app/src/Controller/TypesController.php index 87fa53861..65eac75f5 100644 --- a/app/src/Controller/TypesController.php +++ b/app/src/Controller/TypesController.php @@ -32,8 +32,9 @@ // XXX not doing anything with Log yet use Cake\Log\Log; +#[\AllowDynamicProperties] class TypesController extends StandardController { - public $paginate = [ + protected array $paginate = [ 'order' => [ 'Types.attribute' => 'asc', 'Types.display_name' => 'asc' diff --git a/app/src/Controller/UrlsController.php b/app/src/Controller/UrlsController.php index d608ce95f..2dbb62287 100644 --- a/app/src/Controller/UrlsController.php +++ b/app/src/Controller/UrlsController.php @@ -33,8 +33,9 @@ use Cake\Log\Log; use Cake\ORM\TableRegistry; +#[\AllowDynamicProperties] class UrlsController extends MVEAController { - public $paginate = [ + protected array $paginate = [ 'order' => [ 'Urls.url' => 'asc' ] diff --git a/app/src/Lib/Events/RuleBuilderEventListener.php b/app/src/Lib/Events/RuleBuilderEventListener.php index a600c7e43..674a52159 100644 --- a/app/src/Lib/Events/RuleBuilderEventListener.php +++ b/app/src/Lib/Events/RuleBuilderEventListener.php @@ -29,16 +29,20 @@ namespace App\Lib\Events; +use App\Lib\Traits\LabeledLogTrait; use ArrayObject; +use Cake\Cache\Exception\InvalidArgumentException; +use Cake\Core\Exception\CakeException; use Cake\Datasource\EntityInterface; use Cake\Event\Event; use Cake\Event\EventListenerInterface; use Cake\ORM\RulesChecker; use Cake\ORM\TableRegistry; use Cake\Utility\Inflector; +use PHPUnit\Event\RuntimeException; class RuleBuilderEventListener Implements EventListenerInterface { - use \App\Lib\Traits\LabeledLogTrait; + use LabeledLogTrait; /** * Build rules event listener. @@ -64,7 +68,9 @@ public function buildRules(Event $event, RulesChecker $rules) { if(strncmp($subjectTable->getRegistryAlias(), "DebugKit.", 9)==0) { // Skip DebugKit calls - return $rules; + $event->setResult($rules); + return; + } $schema = $subjectTable->getSchema(); @@ -81,7 +87,7 @@ public function buildRules(Event $event, RulesChecker $rules) { if(method_exists($subjectTable, "getPrimaryLinks")) { $primaryLinks = $subjectTable->getPrimaryLinks(); } - + foreach($schema->columns() as $col) { if(in_array($col, [$cl, $eis])) { // Skip the changelog key since it will only every have pointed to a @@ -93,7 +99,7 @@ public function buildRules(Event $event, RulesChecker $rules) { if(in_array($col, $primaryLinks)) { $rules->addUpdate( [$this, 'ruleFreezePrimaryLink'], - 'freezePrimaryLink', + 'freezePrimaryLink_' . $col, ['errorField' => $col] ); } elseif(preg_match('/^.*_id$/', $col)) { @@ -101,7 +107,7 @@ public function buildRules(Event $event, RulesChecker $rules) { // XXX still need to handle whatever "unfreeze" is going to become $rules->add( [$this, 'ruleValidateCO'], - 'validateCO', + 'validateCO_' . $col, ['errorField' => $col] ); } @@ -109,7 +115,7 @@ public function buildRules(Event $event, RulesChecker $rules) { // The documentation is ambiguous as to whether or not we need to return $rules. // The API docs say yes but the example doesn't have it. - return $rules; + $event->setResult($rules); } /** diff --git a/app/src/Lib/Traits/AutoViewVarsTrait.php b/app/src/Lib/Traits/AutoViewVarsTrait.php index f477b590d..e4731d855 100644 --- a/app/src/Lib/Traits/AutoViewVarsTrait.php +++ b/app/src/Lib/Traits/AutoViewVarsTrait.php @@ -191,9 +191,9 @@ public function calculateAutoViewVars(int|null $coId, Object $obj = null): \Gene // Sort the list by display field if(!empty($avv['model']) && method_exists($AModel, "getDisplayField")) { - $query->order([$AModel->getDisplayField() => 'ASC']); + $query->orderBy([$AModel->getDisplayField() => 'ASC']); } elseif(method_exists($table, "getDisplayField")) { - $query->order([$table->getDisplayField() => 'ASC']); + $query->orderBy([$table->getDisplayField() => 'ASC']); } $generatedValue = $query->toArray(); @@ -231,10 +231,10 @@ public function calculateAutoViewVars(int|null $coId, Object $obj = null): \Gene // we require all Pluggable Models to have a status field, and define "S" as the // value that will remove values from this list. (The recommendation is to use "X" // to disable but allow the value to still appear.) - $generatedValue = $PluggableTable->find('list', [ - 'keyField' => 'id', - 'valueField' => 'description' - ]) + $generatedValue = $PluggableTable->find('list', + keyField: 'id', + valueField: 'description' + ) // We assume Pluggable tables always FK to co_id which // is currently true but might not always be true ->where([ diff --git a/app/src/Lib/Traits/ChangelogBehaviorTrait.php b/app/src/Lib/Traits/ChangelogBehaviorTrait.php index 42bc7cc39..f7ed2476b 100644 --- a/app/src/Lib/Traits/ChangelogBehaviorTrait.php +++ b/app/src/Lib/Traits/ChangelogBehaviorTrait.php @@ -29,18 +29,26 @@ namespace App\Lib\Traits; +use Cake\Datasource\EntityInterface; +use Cake\Event\EventInterface; + trait ChangelogBehaviorTrait { /** * Dispatch the local afterSave if it should be invoked. * + * @param EventInterface $event Event + * @param EntityInterface $entity Entity (ie: Co) + * @param \ArrayObject $options Save options + * @return void * @since COmanage Registry v5.0.0 - * @param EventInterface $event Event - * @param EntityInterface $entity Entity (ie: Co) - * @param ArrayObject $options Save options - * @return bool True on success */ - public function afterSave(\Cake\Event\EventInterface $event, \Cake\Datasource\EntityInterface $entity, \ArrayObject $options): bool { + public function afterSave( + \Cake\Event\EventInterface $event, + \Cake\Datasource\EntityInterface $entity, + \ArrayObject $options + ): void + { // We don't want to trigger any callbacks when we're saving the archive copy. // In Cake 2, we could do this by disabling callbacks, but this feature was // removed in Cake 3 because it "was a common source of bugs in applications" @@ -56,10 +64,11 @@ public function afterSave(\Cake\Event\EventInterface $event, \Cake\Datasource\En $table = $event->getSubject(); if(method_exists($table, "localAfterSave")) { - return $table->localAfterSave($event, $entity, $options); + $result = $table->localAfterSave($event, $entity, $options); + $event->setResult($result); } } - - return true; + + $event->setResult(true); } } diff --git a/app/src/Lib/Traits/PluggableModelTrait.php b/app/src/Lib/Traits/PluggableModelTrait.php index cbf0458d9..630e9568a 100644 --- a/app/src/Lib/Traits/PluggableModelTrait.php +++ b/app/src/Lib/Traits/PluggableModelTrait.php @@ -162,11 +162,11 @@ public function pluginInUse(string $plugin): ResultSet { * * @since COmanage Registry v5.0.0 * @param int $id Entity ID - * @param array $options Options, as supported by get() + * @param array $options Options, as supported by get(). Array has to be associative! */ public function pluginModelForEntityId(int $id, array $options=[]) { - $entity = $this->get($id, $options); + $entity = $this->get($id, ...$options); $pModel = StringUtilities::pluginModel($entity->plugin); return $this->$pModel; diff --git a/app/src/Lib/Traits/PrimaryLinkTrait.php b/app/src/Lib/Traits/PrimaryLinkTrait.php index ddf347925..47dd551d3 100644 --- a/app/src/Lib/Traits/PrimaryLinkTrait.php +++ b/app/src/Lib/Traits/PrimaryLinkTrait.php @@ -162,7 +162,7 @@ public function findCoForRecord(int $id): ?int { // Pull the object to examine the primary links. We might be asked to find the // CO for a deleted object (eg: to add a history record, or to show an older // value for an entity), so accept archived records. - return $this->calculateCoForRecord($this->get($id, ['archived' => true])); + return $this->calculateCoForRecord($this->get($id, archived: true)); } /** @@ -189,7 +189,7 @@ public function findFilterPrimaryLink(\Cake\ORM\Query $query, array $options) { */ public function findPrimaryLink(int $id, bool $archived=false): object { - $obj = $this->get($id, ['archived' => $archived]); //->firstOrFail(); + $obj = $this->get($id, archived: $archived); //->firstOrFail(); // We might have multiple primary link keys (eg for MVEAs), but only one // should be set. Return the first one we find. diff --git a/app/src/Lib/Traits/TypeTrait.php b/app/src/Lib/Traits/TypeTrait.php index 21906e9bc..2a8a06afa 100644 --- a/app/src/Lib/Traits/TypeTrait.php +++ b/app/src/Lib/Traits/TypeTrait.php @@ -54,7 +54,7 @@ public function availableTypes(int $coId, string $attribute) { ->where(['co_id' => $coId, 'attribute' => $attribute, 'status' => SuspendableStatusEnum::Active]) - ->order(['Types.display_name' => 'ASC']); + ->orderBy(['Types.display_name' => 'ASC']); return $query->toArray(); } diff --git a/app/src/Lib/Traits/ValidationTrait.php b/app/src/Lib/Traits/ValidationTrait.php index 08d135b97..5514fd21e 100644 --- a/app/src/Lib/Traits/ValidationTrait.php +++ b/app/src/Lib/Traits/ValidationTrait.php @@ -124,7 +124,7 @@ public function registerStringValidation( * @return mixed True if $value validates, or an error string otherwise */ - public function validateCO($value, array $context) { + public function validateCO(string $value, array $context) { // Verify that $value is a valid record in the current CO // We read the CO ID as set in Configure via AppController. Alternately, we @@ -207,7 +207,7 @@ public function validateCO($value, array $context) { * @return mixed True if $value validates, or an error string otherwise */ - public function validateConditionalRequire($value, array $context) { + public function validateConditionalRequire(string $value, array $context) { if(!empty($value) && in_array($value, $context['providers']['conditionalRequire']['inArray']) && empty($context['data'][ $context['providers']['conditionalRequire']['require'] ])) { @@ -243,7 +243,7 @@ public function validateIncreaseStep(string $value, int $step, array $context) { * @return mixed True if $value validates, or an error string otherwise */ - public function validateInput($value, array $context) { + public function validateInput(string $value, array $context) { // By default, we'll accept anything except < and >. Arguably, we should accept // anything at all for input (and filter only on output), but this was agreed to // as an extra "line of defense" against unsanitized HTML output. Where user supplied @@ -284,7 +284,7 @@ public function validateInput($value, array $context) { } // We require at least one non-whitespace character (CO-1551) - $notBlankValidation = $this->validateNotBlank($value, $context); + $notBlankValidation = $this->validateNotBlank($value, $context); if ($notBlankValidation !== true) { return $notBlankValidation; } @@ -296,24 +296,25 @@ public function validateInput($value, array $context) { /** * Validate the maximum length of a field. * - * @param string $value Value to validate - * @param array $context Validation context, which must include the schema definition + * @param string $value Value to validate + * @param array $columnMetadata + * @param array $context Validation context, which must include the schema definition * * @return bool|string True if $value validates, or an error string otherwise * @since COmanage Registry v5.0.0 */ - public function validateMaxLength(string $value, array $context): bool|string { + public function validateMaxLength(string $value, array $columnMetadata, array $context): bool|string { // We use our own so we can introspect the field's max length from the // provided table schema object, and use our own error message (without // having to copy it to every table definition). // Text has no limit. - if ($context['column']['type'] === 'text') { + if ($columnMetadata['column']['type'] === 'text') { return true; } - $maxLength = $context['column']['length']; + $maxLength = $columnMetadata['column']['length']; if(!empty($value) && mb_strlen($value) > $maxLength) { return __d('error', 'input.length', [$maxLength]); @@ -349,7 +350,7 @@ public function validateNotBlank(mixed $value, array $context): mixed * @return mixed True if $value validates, or an error string otherwise */ - public function validateSqlIdentifier($value, array $context) { + public function validateSqlIdentifier(string $value, array $context) { // Valid (portable) SQL identifiers begin with a letter or underscore, and // subsequent characters can also include digits. We'll be a little stricter // than we need to be for now by only accepting A-Z, when in fact certain @@ -372,7 +373,7 @@ public function validateSqlIdentifier($value, array $context) { * @return mixed True if $value validates, or an error string otherwise */ - public function validateTimeZone($value, array $context) { + public function validateTimeZone(string $value, array $context) { if(!in_array($value, array_values(timezone_identifiers_list()))) { return __d('error', 'input.invalid'); } diff --git a/app/src/Lib/Util/PaginatedSqlIterator.php b/app/src/Lib/Util/PaginatedSqlIterator.php index 581a2430d..3dd83a9f7 100644 --- a/app/src/Lib/Util/PaginatedSqlIterator.php +++ b/app/src/Lib/Util/PaginatedSqlIterator.php @@ -161,14 +161,14 @@ protected function loadPage(): void { $this->position = 0; - $query = $this->table->find('all', $this->options) + $query = $this->table->find('all', options: $this->options) ->where([$this->keyField . ' >' => $this->maxid]); if($this->conditions) { $query = $query->where($this->conditions); } - $query = $query->order([$this->keyField => 'ASC']) + $query = $query->orderBy([$this->keyField => 'ASC']) ->limit($this->pageSize) // We always request exactly one page, starting from $this->maxid. // We don't use Cake's pagination because the resultset could diff --git a/app/src/Model/Behavior/ChangelogBehavior.php b/app/src/Model/Behavior/ChangelogBehavior.php index 2788271cc..24a26aa42 100644 --- a/app/src/Model/Behavior/ChangelogBehavior.php +++ b/app/src/Model/Behavior/ChangelogBehavior.php @@ -29,6 +29,7 @@ namespace App\Model\Behavior; +use Cake\Core\Exception\CakeException; use Cake\Datasource\EntityInterface; use Cake\Event\Event; use Cake\ORM\Behavior; @@ -81,17 +82,20 @@ public function beforeDelete(Event $event, $entity, \ArrayObject $options) { /** * Adjust find query conditions for changelog. * + * @param Event $event The beforeFind event + * @param Query $query Query + * @param \ArrayObject $options The options for the query + * @param boolean $primary Whether or not this is the root query (vs an associated query) + * @return void + * @throws CakeException * @since COmanage Registry v5.0.0 - * @param Event $event The beforeFind event - * @param Query $query Query - * @param ArrayObject $options The options for the query - * @param boolean $primary Whether or not this is the root query (vs an associated query) */ public function beforeFind(Event $event, Query $query, \ArrayObject $options, bool $primary) { if(isset($options['archived']) && $options['archived']) { // Archived records requested (including possiblf expunge), so just return - return true; + $event->setResult(true); + return; } $subject = $event->getSubject(); @@ -170,43 +174,41 @@ public function beforeSave(Event $event, EntityInterface $entity, \ArrayObject $ $entity->deleted = false; $entity->revision = 0; $entity->actor_identifier = $actor; - + return; - } else { - // This is an edit, so copy on write - - LogBehavior::strace($alias, 'Changelog creating archive copy of record ' . $entity->id); - + } + + // This is an edit, so copy on write + + LogBehavior::strace($alias, 'Changelog creating archive copy of record ' . $entity->id); + // XXX start a transaction that gets finished in afterSave // we're normally already in a transaction (if atomic), maybe we don't need to manage another one? - $class = get_class($entity); - - // We disable setters because we want an exact copy of the original record - $archive = new $class($entity->getOriginalValues(), ['useSetters' => false]); - - // Update the appropriate attributes - unset($archive->id); - $archive->$parentfk = $entity->id; - - // We actually want to update the *current* record with $actor, not the - // archived copy (which was presumably updated by the previous $actor) - $entity->actor_identifier = $actor; - - // We also increment the revision on the entity, not the archive - $entity->revision++; - - // Cake 3+ doesn't have callbacks=false, so we use the archive flag so we - // don't recurse indefinitely. We also skip validation in case (eg) validation - // rules changed since the original record was created. - $subject->saveOrFail($archive, [ - 'checkRules' => false, - 'archive' => false, - // We don't want to save associated models by default since - // it will rekey them to the new archive copy. - 'associated' => false - ]); - - return; - } + $class = get_class($entity); + + // We disable setters because we want an exact copy of the original record + $archive = new $class($entity->getOriginalValues(), ['useSetters' => false]); + + // Update the appropriate attributes + unset($archive->id); + $archive->$parentfk = $entity->id; + + // We actually want to update the *current* record with $actor, not the + // archived copy (which was presumably updated by the previous $actor) + $entity->actor_identifier = $actor; + + // We also increment the revision on the entity, not the archive + $entity->revision++; + + // Cake 3+ doesn't have callbacks=false, so we use the archive flag so we + // don't recurse indefinitely. We also skip validation in case (eg) validation + // rules changed since the original record was created. + $subject->saveOrFail($archive, [ + 'checkRules' => false, + 'archive' => false, + // We don't want to save associated models by default since + // it will rekey them to the new archive copy. + 'associated' => false + ]); } } \ No newline at end of file diff --git a/app/src/Model/Behavior/LogBehavior.php b/app/src/Model/Behavior/LogBehavior.php index 99df65398..8da8b9005 100644 --- a/app/src/Model/Behavior/LogBehavior.php +++ b/app/src/Model/Behavior/LogBehavior.php @@ -29,24 +29,28 @@ namespace App\Model\Behavior; +use Cake\Core\Exception\CakeException; +use Cake\Event\Event; use Cake\Log\Log; use Cake\ORM\Behavior; +use Cake\ORM\Query; class LogBehavior extends Behavior { /** * Automatic logging before a find. * + * @param Event $event The beforeFind event that was fired. + * @param Query $query Query + * @param \ArrayObject $options The options for the query + * @param boolean $primary Whether or not this is the root query (vs an associated query) + * @throws CakeException * @since COmanage Registry v5.0.0 - * @param Event $event The beforeFind event that was fired. - * @param Query $query Query - * @param ArrayObject $options The options for the query - * @param boolean $primary Whether or not this is the root query (vs an associated query) */ // XXX most likely we can't use Cake's log level to suppress trace logging, since it // isn't a log level. Maybe define a global config option like "trace=true"? - public function beforeFind(\Cake\Event\Event $event, \Cake\ORM\Query $query, \ArrayObject $options, bool $primary) { + public function beforeFind(Event $event, Query $query, \ArrayObject $options, bool $primary) { $subject = $event->getSubject(); $label = getmypid() . "/" . $subject->getAlias() . ": "; // XXX can we inject IP address of requester (where available)? diff --git a/app/src/Model/Entity/AdHocAttribute.php b/app/src/Model/Entity/AdHocAttribute.php index 66c1922b2..0b0bf6dd7 100644 --- a/app/src/Model/Entity/AdHocAttribute.php +++ b/app/src/Model/Entity/AdHocAttribute.php @@ -36,7 +36,7 @@ class AdHocAttribute extends Entity { use \App\Lib\Traits\ReadOnlyEntityTrait; use \App\Lib\Traits\MVETrait; - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/src/Model/Entity/Address.php b/app/src/Model/Entity/Address.php index 387ffca52..7ede4ce63 100644 --- a/app/src/Model/Entity/Address.php +++ b/app/src/Model/Entity/Address.php @@ -36,7 +36,7 @@ class Address extends Entity { use \App\Lib\Traits\ReadOnlyEntityTrait; use \App\Lib\Traits\MVETrait; - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/src/Model/Entity/Api.php b/app/src/Model/Entity/Api.php index f453372b2..a91a1f4e0 100644 --- a/app/src/Model/Entity/Api.php +++ b/app/src/Model/Entity/Api.php @@ -34,7 +34,7 @@ class Api extends Entity { use \App\Lib\Traits\ReadOnlyEntityTrait; - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/src/Model/Entity/ApiUser.php b/app/src/Model/Entity/ApiUser.php index 63abe5ab3..0057b8e5f 100644 --- a/app/src/Model/Entity/ApiUser.php +++ b/app/src/Model/Entity/ApiUser.php @@ -35,7 +35,7 @@ class ApiUser extends Entity { use \App\Lib\Traits\ReadOnlyEntityTrait; - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/src/Model/Entity/ApplicationState.php b/app/src/Model/Entity/ApplicationState.php index db92f2ad9..396224c5a 100644 --- a/app/src/Model/Entity/ApplicationState.php +++ b/app/src/Model/Entity/ApplicationState.php @@ -32,7 +32,7 @@ use Cake\ORM\Entity; class ApplicationState extends Entity { - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/src/Model/Entity/AuthenticationEvent.php b/app/src/Model/Entity/AuthenticationEvent.php index ea77fe225..c13ed3d53 100644 --- a/app/src/Model/Entity/AuthenticationEvent.php +++ b/app/src/Model/Entity/AuthenticationEvent.php @@ -32,7 +32,7 @@ use Cake\ORM\Entity; class AuthenticationEvent extends Entity { - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false diff --git a/app/src/Model/Entity/Co.php b/app/src/Model/Entity/Co.php index 20ffd8933..3514df96c 100644 --- a/app/src/Model/Entity/Co.php +++ b/app/src/Model/Entity/Co.php @@ -34,7 +34,7 @@ use \App\Lib\Enum\SuspendableStatusEnum; class Co extends Entity { - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/src/Model/Entity/CoSetting.php b/app/src/Model/Entity/CoSetting.php index 7b1d08a05..f15cddbb1 100644 --- a/app/src/Model/Entity/CoSetting.php +++ b/app/src/Model/Entity/CoSetting.php @@ -37,7 +37,7 @@ class CoSetting extends Entity { use \App\Lib\Traits\ReadOnlyEntityTrait; - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/src/Model/Entity/Cou.php b/app/src/Model/Entity/Cou.php index a494edb1f..cc6b0e535 100644 --- a/app/src/Model/Entity/Cou.php +++ b/app/src/Model/Entity/Cou.php @@ -34,7 +34,7 @@ class Cou extends Entity { use \App\Lib\Traits\ReadOnlyEntityTrait; - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/src/Model/Entity/Dashboard.php b/app/src/Model/Entity/Dashboard.php index 1f68e2dc7..1365b3fb3 100644 --- a/app/src/Model/Entity/Dashboard.php +++ b/app/src/Model/Entity/Dashboard.php @@ -34,7 +34,7 @@ class Dashboard extends Entity { use \App\Lib\Traits\ReadOnlyEntityTrait; - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/src/Model/Entity/EmailAddress.php b/app/src/Model/Entity/EmailAddress.php index a5aff5855..0185cc179 100644 --- a/app/src/Model/Entity/EmailAddress.php +++ b/app/src/Model/Entity/EmailAddress.php @@ -36,7 +36,7 @@ class EmailAddress extends Entity { use \App\Lib\Traits\ReadOnlyEntityTrait; use \App\Lib\Traits\MVETrait; - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/src/Model/Entity/EnrollmentFlow.php b/app/src/Model/Entity/EnrollmentFlow.php index ab8f26bd4..148310b3e 100644 --- a/app/src/Model/Entity/EnrollmentFlow.php +++ b/app/src/Model/Entity/EnrollmentFlow.php @@ -35,7 +35,7 @@ class EnrollmentFlow extends Entity { use \App\Lib\Traits\EntityMetaTrait; use \App\Lib\Traits\ReadOnlyEntityTrait; - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/src/Model/Entity/EnrollmentFlowStep.php b/app/src/Model/Entity/EnrollmentFlowStep.php index a92278e1a..a87570a98 100644 --- a/app/src/Model/Entity/EnrollmentFlowStep.php +++ b/app/src/Model/Entity/EnrollmentFlowStep.php @@ -35,7 +35,7 @@ class EnrollmentFlowStep extends Entity { use \App\Lib\Traits\EntityMetaTrait; use \App\Lib\Traits\ReadOnlyEntityTrait; - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/src/Model/Entity/ExtIdentitySourceRecord.php b/app/src/Model/Entity/ExtIdentitySourceRecord.php index 46edcb702..098a10efa 100644 --- a/app/src/Model/Entity/ExtIdentitySourceRecord.php +++ b/app/src/Model/Entity/ExtIdentitySourceRecord.php @@ -37,7 +37,7 @@ class ExtIdentitySourceRecord extends Entity { use \App\Lib\Traits\EntityMetaTrait; use \App\Lib\Traits\ReadOnlyEntityTrait; - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/src/Model/Entity/ExternalIdentity.php b/app/src/Model/Entity/ExternalIdentity.php index b9b01a340..bd2cd4baf 100644 --- a/app/src/Model/Entity/ExternalIdentity.php +++ b/app/src/Model/Entity/ExternalIdentity.php @@ -34,7 +34,7 @@ class ExternalIdentity extends Entity { use \App\Lib\Traits\ReadOnlyEntityTrait; - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/src/Model/Entity/ExternalIdentityRole.php b/app/src/Model/Entity/ExternalIdentityRole.php index aeafe661e..63193ea20 100644 --- a/app/src/Model/Entity/ExternalIdentityRole.php +++ b/app/src/Model/Entity/ExternalIdentityRole.php @@ -34,7 +34,7 @@ class ExternalIdentityRole extends Entity { use \App\Lib\Traits\ReadOnlyEntityTrait; - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/src/Model/Entity/ExternalIdentitySource.php b/app/src/Model/Entity/ExternalIdentitySource.php index 13ac3780a..7bc510b72 100644 --- a/app/src/Model/Entity/ExternalIdentitySource.php +++ b/app/src/Model/Entity/ExternalIdentitySource.php @@ -34,7 +34,7 @@ class ExternalIdentitySource extends Entity { use \App\Lib\Traits\ReadOnlyEntityTrait; - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/src/Model/Entity/Flange.php b/app/src/Model/Entity/Flange.php index dd84b611d..4e480bc75 100644 --- a/app/src/Model/Entity/Flange.php +++ b/app/src/Model/Entity/Flange.php @@ -34,7 +34,7 @@ class Flange extends Entity { use \App\Lib\Traits\ReadOnlyEntityTrait; - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/src/Model/Entity/Group.php b/app/src/Model/Entity/Group.php index d60936101..b1950b0b6 100644 --- a/app/src/Model/Entity/Group.php +++ b/app/src/Model/Entity/Group.php @@ -33,7 +33,7 @@ use \App\Lib\Enum\GroupTypeEnum; class Group extends Entity { - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/src/Model/Entity/GroupMember.php b/app/src/Model/Entity/GroupMember.php index 21471edf6..eb9a4bf92 100644 --- a/app/src/Model/Entity/GroupMember.php +++ b/app/src/Model/Entity/GroupMember.php @@ -32,7 +32,7 @@ use Cake\ORM\Entity; class GroupMember extends Entity { - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/src/Model/Entity/GroupNesting.php b/app/src/Model/Entity/GroupNesting.php index 5034d0c10..1f78b9a8a 100644 --- a/app/src/Model/Entity/GroupNesting.php +++ b/app/src/Model/Entity/GroupNesting.php @@ -32,7 +32,7 @@ use Cake\ORM\Entity; class GroupNesting extends Entity { - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/src/Model/Entity/HistoryRecord.php b/app/src/Model/Entity/HistoryRecord.php index 71df1779f..e91d7ed76 100644 --- a/app/src/Model/Entity/HistoryRecord.php +++ b/app/src/Model/Entity/HistoryRecord.php @@ -34,7 +34,7 @@ class HistoryRecord extends Entity { use \App\Lib\Traits\ReadOnlyEntityTrait; - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/src/Model/Entity/Identifier.php b/app/src/Model/Entity/Identifier.php index bc231646d..4a8128563 100644 --- a/app/src/Model/Entity/Identifier.php +++ b/app/src/Model/Entity/Identifier.php @@ -36,7 +36,7 @@ class Identifier extends Entity { use \App\Lib\Traits\ReadOnlyEntityTrait; use \App\Lib\Traits\MVETrait; - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/src/Model/Entity/IdentifierAssignment.php b/app/src/Model/Entity/IdentifierAssignment.php index aef39cae9..35db54f9e 100644 --- a/app/src/Model/Entity/IdentifierAssignment.php +++ b/app/src/Model/Entity/IdentifierAssignment.php @@ -34,7 +34,7 @@ class IdentifierAssignment extends Entity { use \App\Lib\Traits\ReadOnlyEntityTrait; - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/src/Model/Entity/Job.php b/app/src/Model/Entity/Job.php index 7441775ca..1b74fed24 100644 --- a/app/src/Model/Entity/Job.php +++ b/app/src/Model/Entity/Job.php @@ -33,7 +33,7 @@ use \App\Lib\Enum\JobStatusEnum; class Job extends Entity { - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/src/Model/Entity/JobHistoryRecord.php b/app/src/Model/Entity/JobHistoryRecord.php index 5df5e3f2f..9fab7a9f8 100644 --- a/app/src/Model/Entity/JobHistoryRecord.php +++ b/app/src/Model/Entity/JobHistoryRecord.php @@ -32,7 +32,7 @@ use Cake\ORM\Entity; class JobHistoryRecord extends Entity { - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/src/Model/Entity/MessageTemplate.php b/app/src/Model/Entity/MessageTemplate.php index a87a62a13..e2eb1825b 100644 --- a/app/src/Model/Entity/MessageTemplate.php +++ b/app/src/Model/Entity/MessageTemplate.php @@ -36,7 +36,7 @@ class MessageTemplate extends Entity { use \App\Lib\Traits\ReadOnlyEntityTrait; - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/src/Model/Entity/Meta.php b/app/src/Model/Entity/Meta.php index 9564e4c50..a015028c2 100644 --- a/app/src/Model/Entity/Meta.php +++ b/app/src/Model/Entity/Meta.php @@ -32,7 +32,7 @@ use Cake\ORM\Entity; class Meta extends Entity { - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/src/Model/Entity/MostlyStaticPage.php b/app/src/Model/Entity/MostlyStaticPage.php index 055e90451..353846c0f 100644 --- a/app/src/Model/Entity/MostlyStaticPage.php +++ b/app/src/Model/Entity/MostlyStaticPage.php @@ -34,7 +34,7 @@ class MostlyStaticPage extends Entity { use \App\Lib\Traits\ReadOnlyEntityTrait; - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/src/Model/Entity/Name.php b/app/src/Model/Entity/Name.php index 97b51a67f..70fe676a5 100644 --- a/app/src/Model/Entity/Name.php +++ b/app/src/Model/Entity/Name.php @@ -36,14 +36,14 @@ class Name extends Entity { use \App\Lib\Traits\ReadOnlyEntityTrait; use \App\Lib\Traits\MVETrait; - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, ]; // Make full name available to the API v2 JSON response - protected $_virtual = [ + protected array $_virtual = [ 'full_name' ]; diff --git a/app/src/Model/Entity/Notification.php b/app/src/Model/Entity/Notification.php index 901b801c6..d2e42dc9c 100644 --- a/app/src/Model/Entity/Notification.php +++ b/app/src/Model/Entity/Notification.php @@ -33,7 +33,7 @@ use \App\Lib\Enum\NotificationStatusEnum; class Notification extends Entity { - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/src/Model/Entity/Person.php b/app/src/Model/Entity/Person.php index b81d904a0..46b1f42c1 100644 --- a/app/src/Model/Entity/Person.php +++ b/app/src/Model/Entity/Person.php @@ -35,7 +35,7 @@ class Person extends Entity { use \App\Lib\Traits\ReadOnlyEntityTrait; - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/src/Model/Entity/PersonRole.php b/app/src/Model/Entity/PersonRole.php index 6662d037e..bae395c4d 100644 --- a/app/src/Model/Entity/PersonRole.php +++ b/app/src/Model/Entity/PersonRole.php @@ -35,7 +35,7 @@ class PersonRole extends Entity { use \App\Lib\Traits\ReadOnlyEntityTrait; - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/src/Model/Entity/Petition.php b/app/src/Model/Entity/Petition.php index 5090acd77..7395ada80 100644 --- a/app/src/Model/Entity/Petition.php +++ b/app/src/Model/Entity/Petition.php @@ -38,7 +38,7 @@ class Petition extends Entity { isReadOnly as traitIsReadOnly; } - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/src/Model/Entity/PetitionHistoryRecord.php b/app/src/Model/Entity/PetitionHistoryRecord.php index eaa5a5f42..7e30ea221 100644 --- a/app/src/Model/Entity/PetitionHistoryRecord.php +++ b/app/src/Model/Entity/PetitionHistoryRecord.php @@ -32,7 +32,7 @@ use Cake\ORM\Entity; class PetitionHistoryRecord extends Entity { - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/src/Model/Entity/PetitionStepResult.php b/app/src/Model/Entity/PetitionStepResult.php index 9b64c15ca..55ce62b9b 100644 --- a/app/src/Model/Entity/PetitionStepResult.php +++ b/app/src/Model/Entity/PetitionStepResult.php @@ -32,7 +32,7 @@ use Cake\ORM\Entity; class PetitionStepResult extends Entity { - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/src/Model/Entity/Pipeline.php b/app/src/Model/Entity/Pipeline.php index 8df0fb96b..f1c73d267 100644 --- a/app/src/Model/Entity/Pipeline.php +++ b/app/src/Model/Entity/Pipeline.php @@ -34,7 +34,7 @@ class Pipeline extends Entity { use \App\Lib\Traits\ReadOnlyEntityTrait; - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/src/Model/Entity/Plugin.php b/app/src/Model/Entity/Plugin.php index f91bc25e3..fd0deac65 100644 --- a/app/src/Model/Entity/Plugin.php +++ b/app/src/Model/Entity/Plugin.php @@ -33,7 +33,7 @@ use \App\Lib\Enum\SuspendableStatusEnum; class Plugin extends Entity { - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/src/Model/Entity/Pronoun.php b/app/src/Model/Entity/Pronoun.php index 872be9521..866f4e4ef 100644 --- a/app/src/Model/Entity/Pronoun.php +++ b/app/src/Model/Entity/Pronoun.php @@ -37,7 +37,7 @@ class Pronoun extends Entity { use \App\Lib\Traits\ReadOnlyEntityTrait; use \App\Lib\Traits\MVETrait; - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/src/Model/Entity/ProvisioningHistoryRecord.php b/app/src/Model/Entity/ProvisioningHistoryRecord.php index 946439233..fec2cae16 100644 --- a/app/src/Model/Entity/ProvisioningHistoryRecord.php +++ b/app/src/Model/Entity/ProvisioningHistoryRecord.php @@ -32,7 +32,7 @@ use Cake\ORM\Entity; class ProvisioningHistoryRecord extends Entity { - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/src/Model/Entity/ProvisioningTarget.php b/app/src/Model/Entity/ProvisioningTarget.php index 1efceee83..dc654a002 100644 --- a/app/src/Model/Entity/ProvisioningTarget.php +++ b/app/src/Model/Entity/ProvisioningTarget.php @@ -35,7 +35,7 @@ class ProvisioningTarget extends Entity { use \App\Lib\Traits\ReadOnlyEntityTrait; - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/src/Model/Entity/Server.php b/app/src/Model/Entity/Server.php index 30a1b3fa4..d7d1867a9 100644 --- a/app/src/Model/Entity/Server.php +++ b/app/src/Model/Entity/Server.php @@ -34,7 +34,7 @@ class Server extends Entity { use \App\Lib\Traits\ReadOnlyEntityTrait; - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/src/Model/Entity/TelephoneNumber.php b/app/src/Model/Entity/TelephoneNumber.php index 0b068c11b..61c82429c 100644 --- a/app/src/Model/Entity/TelephoneNumber.php +++ b/app/src/Model/Entity/TelephoneNumber.php @@ -36,7 +36,7 @@ class TelephoneNumber extends Entity { use \App\Lib\Traits\ReadOnlyEntityTrait; use \App\Lib\Traits\MVETrait; - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/src/Model/Entity/TrafficDetour.php b/app/src/Model/Entity/TrafficDetour.php index 05d551543..068434d4f 100644 --- a/app/src/Model/Entity/TrafficDetour.php +++ b/app/src/Model/Entity/TrafficDetour.php @@ -34,7 +34,7 @@ class TrafficDetour extends Entity { use \App\Lib\Traits\ReadOnlyEntityTrait; - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/src/Model/Entity/Type.php b/app/src/Model/Entity/Type.php index 1cee64e8b..4c14825a2 100644 --- a/app/src/Model/Entity/Type.php +++ b/app/src/Model/Entity/Type.php @@ -34,7 +34,7 @@ class Type extends Entity { use \App\Lib\Traits\ReadOnlyEntityTrait; - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/src/Model/Entity/Url.php b/app/src/Model/Entity/Url.php index d92eda483..f990ba436 100644 --- a/app/src/Model/Entity/Url.php +++ b/app/src/Model/Entity/Url.php @@ -36,7 +36,7 @@ class Url extends Entity { use \App\Lib\Traits\ReadOnlyEntityTrait; use \App\Lib\Traits\MVETrait; - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/src/Model/Entity/Verification.php b/app/src/Model/Entity/Verification.php index b714c716c..252369ceb 100644 --- a/app/src/Model/Entity/Verification.php +++ b/app/src/Model/Entity/Verification.php @@ -33,7 +33,7 @@ use App\Lib\Enum\VerificationMethodEnum; class Verification extends Entity { - protected $_accessible = [ + protected array $_accessible = [ '*' => true, 'id' => false, 'slug' => false, diff --git a/app/src/Model/Table/AddressesTable.php b/app/src/Model/Table/AddressesTable.php index d9341a2cf..d67879698 100644 --- a/app/src/Model/Table/AddressesTable.php +++ b/app/src/Model/Table/AddressesTable.php @@ -181,7 +181,7 @@ public function search(int $coId, string $q, int $limit) { return $this->find() ->where($whereClause) ->andWhere(['People.co_id' => $coId]) - ->order(['Addresses.street']) + ->orderBy(['Addresses.street']) ->limit($limit) ->contain([ 'People' => 'PrimaryName', diff --git a/app/src/Model/Table/CousTable.php b/app/src/Model/Table/CousTable.php index 0afbe7534..b77cece05 100644 --- a/app/src/Model/Table/CousTable.php +++ b/app/src/Model/Table/CousTable.php @@ -227,7 +227,7 @@ public function localAfterSave(\Cake\Event\EventInterface $event, \Cake\Datasour public function marshalProvisioningData(int $id): array { $ret = []; // We need the archived record on delete to properly deprovision - $ret['data'] = $this->get($id, ['archived' => true]); + $ret['data'] = $this->get($id, archived: true); // Provisioning Eligibility is // - Deleted if the changelog deleted flag is true @@ -260,7 +260,7 @@ public function potentialParents(int $coId, int $id=null, bool $hierarchy=false) $query = null; if($hierarchy) { - $query = $this->find('treeList', ['spacer' => '-']); + $query = $this->find('treeList', spacer: '-'); } else { $query = $this->find('list'); } @@ -268,7 +268,7 @@ public function potentialParents(int $coId, int $id=null, bool $hierarchy=false) $query = $query->where(['co_id' => $coId]) // true overrides the default Cake order for treeList so we get // our items sorted alphabetically instead of by tree ID - ->order(['name' => 'ASC'], true); + ->orderBy(['name' => 'ASC'], true); if($id) { $query = $query->where(['id <>' => $id]); @@ -333,21 +333,21 @@ public function validationDefault(Validator $validator): Validator { 'content' => ['rule' => 'isInteger'] ]); $validator->notEmptyString('co_id'); - + $this->registerStringValidation($validator, $schema, 'name', true); - + $this->registerStringValidation($validator, $schema, 'description', false); - + $validator->add('parent_id', [ 'content' => ['rule' => 'isInteger'] ]); $validator->allowEmptyString('parent_id'); - + $validator->add('lft', [ 'content' => ['rule' => 'isInteger'] ]); $validator->allowEmptyString('lft'); - + $validator->add('rght', [ 'content' => ['rule' => 'isInteger'] ]); diff --git a/app/src/Model/Table/EnrollmentFlowsTable.php b/app/src/Model/Table/EnrollmentFlowsTable.php index 7a3e20fee..2a9546e88 100644 --- a/app/src/Model/Table/EnrollmentFlowsTable.php +++ b/app/src/Model/Table/EnrollmentFlowsTable.php @@ -201,7 +201,7 @@ public function calculateNextStep(int $petitionId): array { 'valueField' => 'status' ]) ->where(['petition_id' => $petition->id]) - ->order(['enrollment_flow_step_id' => 'ASC']) + ->orderBy(['enrollment_flow_step_id' => 'ASC']) ->toArray(); // Pull the Enrollment Flow Steps for this Enrollment Flow, in order, @@ -212,7 +212,7 @@ public function calculateNextStep(int $petitionId): array { 'enrollment_flow_id' => $petition->enrollment_flow_id, 'status' => SuspendableStatusEnum::Active ]) - ->order(['EnrollmentFlowSteps.ordr' => 'ASC']) + ->orderBy(['EnrollmentFlowSteps.ordr' => 'ASC']) ->contain($this->EnrollmentFlowSteps->getPluginRelations()) ->all(); diff --git a/app/src/Model/Table/ExtIdentitySourceRecordsTable.php b/app/src/Model/Table/ExtIdentitySourceRecordsTable.php index 4d2a7601c..41ec0f60d 100644 --- a/app/src/Model/Table/ExtIdentitySourceRecordsTable.php +++ b/app/src/Model/Table/ExtIdentitySourceRecordsTable.php @@ -186,6 +186,7 @@ public function validationDefault(Validator $validator): Validator { // Note that adopting an EIS Record briefly creates a second EIS Record for the same // source key, so we shouldn't try to enforce uniqueness of the source key here. // (See ExternalIdentitiesTable::adopt.) + // XXX Resyncing breaks things for OrcidSource $this->registerStringValidation($validator, $schema, 'source_key', true); // Since source_record comes from upstream, it's not clear that we should diff --git a/app/src/Model/Table/GroupMembersTable.php b/app/src/Model/Table/GroupMembersTable.php index 98c0a2a2d..b6e6cce2f 100644 --- a/app/src/Model/Table/GroupMembersTable.php +++ b/app/src/Model/Table/GroupMembersTable.php @@ -244,7 +244,7 @@ public function isMember(int $groupId, public function localAfterSave(\Cake\Event\EventInterface $event, \Cake\Datasource\EntityInterface $entity, \ArrayObject $options): bool { // Pull the related entities for HistoryRecord comment creation. - $person = $this->People->get($entity->person_id, ['contain' => ['PrimaryName']]); + $person = $this->People->get($entity->person_id, contain: ['PrimaryName']); $group = $this->Groups->get($entity->group_id); $action = null; @@ -258,9 +258,7 @@ public function localAfterSave(\Cake\Event\EventInterface $event, \Cake\Datasour if(!empty($entity->group_nesting_id)) { // We need to allow retrieval of archived records since we might be called // after the GroupNesting was deleted - $nesting = $this->GroupNestings->get($entity->group_nesting_id, - ['contain' => ['Groups'], - 'archived' => true]); + $nesting = $this->GroupNestings->get($entity->group_nesting_id, contain: ['Groups'], archived: true); $langKeySuffix = '.nesting'; $commentParams[] = $nesting->group->name; diff --git a/app/src/Model/Table/GroupNestingsTable.php b/app/src/Model/Table/GroupNestingsTable.php index 158e515c9..eca7373d5 100644 --- a/app/src/Model/Table/GroupNestingsTable.php +++ b/app/src/Model/Table/GroupNestingsTable.php @@ -177,7 +177,7 @@ public function availableGroups(int $groupId): array { 'Groups.group_type IS' => null ] ]) - ->order(['Groups.name' => 'ASC']) + ->orderBy(['Groups.name' => 'ASC']) ->toArray(); } diff --git a/app/src/Model/Table/GroupsTable.php b/app/src/Model/Table/GroupsTable.php index d66ff20a4..90339a4f3 100644 --- a/app/src/Model/Table/GroupsTable.php +++ b/app/src/Model/Table/GroupsTable.php @@ -29,7 +29,10 @@ namespace App\Model\Table; +use Cake\Datasource\Exception\InvalidPrimaryKeyException; use Cake\Event\EventInterface; +use Cake\ORM\Exception\PersistenceFailedException; +use Cake\ORM\Exception\RolledbackTransactionException; use Cake\ORM\Query; use Cake\ORM\RulesChecker; use Cake\ORM\Table; @@ -240,18 +243,17 @@ public function initialize(array $config): void { ] ]); } - + /** * Add the system groups for a CO or COU. (AR-CO-6, AR-COU-4) * - * @since COmanage Registry v5.0.0 - * @param int $coId CO ID - * @param int $couId COU ID - * @param bool $rename If true, rename any existing groups + * @param int $coId CO ID + * @param int|null $couId COU ID + * @param bool $rename If true, rename any existing groups * @return bool True on success - * @throws InvalidArgumentException - * @throws RuntimeException - * @throws PersistenceFailedException + * @throws InvalidPrimaryKeyException + * @throws RolledbackTransactionException + * @since COmanage Registry v5.0.0 */ public function addDefaults(int $coId, int $couId=null, bool $rename=false): bool { @@ -462,11 +464,11 @@ public function buildRules(RulesChecker $rules): RulesChecker { /** * Create an Owners Group for the requested Group. - * - * @since COmanage Registry v5.0.0 - * @param Group $group Group Entity to create an Owners Group for + * + * @param Group $group Group Entity to create an Owners Group for * @return int Owners Group ID * @throws PersistenceFailedException + * @since COmanage Registry v5.0.0 */ public function createOwnersGroup($group): int { @@ -492,7 +494,8 @@ public function createOwnersGroup($group): int { // Update the original Group with a pointer to this one $group->owners_group_id = $ownerGroup->id; - $this->saveOrFail($group); + // We do not want to create an archive on the new group we just created. + $this->saveOrFail($group, ['archive' => false]); return $ownerGroup->id; } @@ -755,15 +758,9 @@ public function localAfterSave(\Cake\Event\EventInterface $event, \Cake\Datasour public function marshalProvisioningData(int $id): array { $ret = []; - $ret['data'] = $this->get($id, [ - // We need archives for handling deleted records - 'archived' => 'true', - 'contain' => [ - 'GroupMembers', - 'Identifiers' - ] - ]); - + // We need archives for handling deleted records + $ret['data'] = $this->get($id, archived: true, contain: ['GroupMembers', 'Identifiers']); + // Provisioning Eligibility is // - Deleted if the changelog deleted flag is true // - Eligible if the status is Active and the group type is not Owners @@ -1115,7 +1112,7 @@ public function search(int $coId, string $q, int $limit) { return $this->find() ->where($whereClause) ->andWhere(['Groups.co_id' => $coId]) - ->order(['Groups.name']) + ->orderBy(['Groups.name']) ->limit($limit) ->all(); } diff --git a/app/src/Model/Table/IdentifierAssignmentsTable.php b/app/src/Model/Table/IdentifierAssignmentsTable.php index d34d19f8d..3bbc58110 100644 --- a/app/src/Model/Table/IdentifierAssignmentsTable.php +++ b/app/src/Model/Table/IdentifierAssignmentsTable.php @@ -198,7 +198,7 @@ public function assign( 'IdentifierAssignments.status' => SuspendableStatusEnum::Active, 'IdentifierAssignments.context' => $context ]) - ->order(['IdentifierAssignments.ordr' => 'ASC']) + ->orderBy(['IdentifierAssignments.ordr' => 'ASC']) ->contain($this->getPluginRelations()) ->all(); diff --git a/app/src/Model/Table/JobsTable.php b/app/src/Model/Table/JobsTable.php index 7e23e3cb2..675cc0c8b 100644 --- a/app/src/Model/Table/JobsTable.php +++ b/app/src/Model/Table/JobsTable.php @@ -182,7 +182,7 @@ public function assignNext(int $coId): ?Job { ] ]) // We sort by id to pull the oldest job first - ->order(['id' => 'ASC']) + ->orderBy(['id' => 'ASC']) ->epilog('FOR UPDATE') ->first(); diff --git a/app/src/Model/Table/NamesTable.php b/app/src/Model/Table/NamesTable.php index c61010e01..1d07a8e09 100644 --- a/app/src/Model/Table/NamesTable.php +++ b/app/src/Model/Table/NamesTable.php @@ -347,7 +347,7 @@ public function search(int $coId, string $q, int $limit) { $results = $this->find() ->where($whereClause[1]) ->andWhere(['People.co_id' => $coId]) - ->order(['Names.family', 'Names.given', 'Names.middle']) + ->orderBy(['Names.family', 'Names.given', 'Names.middle']) ->limit($limit) ->contain(['People' => 'PrimaryName']) ->all(); @@ -356,7 +356,7 @@ public function search(int $coId, string $q, int $limit) { $results2 = $this->find() ->where($whereClause[2]) ->andWhere(['People.co_id' => $coId]) - ->order(['Names.family', 'Names.given', 'Names.middle']) + ->orderBy(['Names.family', 'Names.given', 'Names.middle']) ->limit($limit - $results->count()) ->contain(['People' => 'PrimaryName']) ->all(); diff --git a/app/src/Model/Table/PeopleTable.php b/app/src/Model/Table/PeopleTable.php index 92e8ec0fc..537ba420e 100644 --- a/app/src/Model/Table/PeopleTable.php +++ b/app/src/Model/Table/PeopleTable.php @@ -469,10 +469,10 @@ public function localAfterSave(\Cake\Event\EventInterface $event, \Cake\Datasour public function marshalProvisioningData(int $id): array { $ret = []; - $ret['data'] = $this->get($id, [ + $ret['data'] = $this->get($id, // We need archives for handling deleted records - 'archived' => 'true', - 'contain' => [ + archived: 'true', + contain: [ 'PrimaryName' => [ 'Types' ], 'Addresses' => [ 'Types' ], 'AdHocAttributes', @@ -510,7 +510,7 @@ public function marshalProvisioningData(int $id): array { 'TelephoneNumbers' => [ 'Types' ], 'Urls' => [ 'Types' ] ] - ]); + ); // Provisioning Eligibility is // - Deleted if the changelog deleted flag is true OR status is Archived diff --git a/app/src/Model/Table/PetitionsTable.php b/app/src/Model/Table/PetitionsTable.php index b67199cc0..424e68e4f 100644 --- a/app/src/Model/Table/PetitionsTable.php +++ b/app/src/Model/Table/PetitionsTable.php @@ -380,7 +380,7 @@ public function derive(int $id) { */ public function finalize(int $id) { - $petition = $this->get($id, ['contain' => 'EnrollmentFlows']); + $petition = $this->get($id, contain: ['EnrollmentFlows']); if($petition->isComplete()) { throw new \InvalidArgumentException(__d('error', 'Petitions.completed', [$id])); @@ -528,12 +528,12 @@ public function getEnrolleeName(int $id): ?string { // First see if there is an Enrollee Person associated with the Petition, which would // be the case for (eg) account linking. If so, use their Primary Name. - $petition = $this->get($id, ['contain' => [ + $petition = $this->get($id, contain: [ 'EnrolleePeople' => 'PrimaryName', 'PetitionStepResults' => [ 'EnrollmentFlowSteps' => $this->PetitionStepResults->EnrollmentFlowSteps->getPluginRelations() ] - ]]); + ]); if(!empty($petition->enrollee_person->primary_name)) { return $petition->enrollee_person->primary_name->full_name; @@ -574,13 +574,13 @@ public function hydrate(int $id) { // This is intended to be the first part of finalization, so we set the Petition status // to Finalizing. - $petition = $this->get($id, ['contain' => [ + $petition = $this->get($id, contain: [ 'EnrollmentFlows' => [ 'EnrollmentFlowSteps' => array_merge( $this->EnrollmentFlows->EnrollmentFlowSteps->getPluginRelations(), ['sort' => ['EnrollmentFlowSteps.ordr' => 'ASC']] ) - ]]]); + ]]); if($petition->isComplete()) { throw new \InvalidArgumentException(__d('error', 'Petitions.completed', [$id])); @@ -766,7 +766,7 @@ public function isApproverForFlow(int $id, int $personId): bool { 'enrollment_flow_id' => $petition->enrollment_flow_id, 'status' => SuspendableStatusEnum::Active ]) - ->order(['EnrollmentFlowSteps.ordr' => 'ASC']) + ->orderBy(['EnrollmentFlowSteps.ordr' => 'ASC']) ->all(); foreach($steps as $step) { diff --git a/app/src/Model/Table/PipelinesTable.php b/app/src/Model/Table/PipelinesTable.php index 0ff4f0550..abba13f1d 100644 --- a/app/src/Model/Table/PipelinesTable.php +++ b/app/src/Model/Table/PipelinesTable.php @@ -565,7 +565,7 @@ public function execute( // pass around. $pipeline->flanges = $this->Flanges->find() ->where(['pipeline_id' => $id]) - ->order(['Flanges.ordr' => 'ASC']) + ->orderBy(['Flanges.ordr' => 'ASC']) ->contain($this->Flanges->getPluginRelations()) ->all(); @@ -2513,9 +2513,9 @@ public function validationDefault(Validator $validator): Validator { ]); $validator->notEmptyString( field: 'match_email_address_type_id', - when: function ($context) { - return (!empty($context['data']['match_strategy']) - && ($context['data']['match_strategy'] == MatchStrategyEnum::EmailAddress)); + when: function ($ctx) { + return (!empty($ctx['data']['match_strategy']) + && ($ctx['data']['match_strategy'] == MatchStrategyEnum::EmailAddress)); } ); @@ -2524,9 +2524,9 @@ public function validationDefault(Validator $validator): Validator { ]); $validator->notEmptyString( field: 'match_identifier_type_id', - when: function ($context) { - return (!empty($context['data']['match_strategy']) - && ($context['data']['match_strategy'] == MatchStrategyEnum::Identifier)); + when: function ($ctx) { + return (!empty($ctx['data']['match_strategy']) + && ($ctx['data']['match_strategy'] == MatchStrategyEnum::Identifier)); } ); diff --git a/app/src/Model/Table/PluginsTable.php b/app/src/Model/Table/PluginsTable.php index f0f271b0c..6bc7824c3 100644 --- a/app/src/Model/Table/PluginsTable.php +++ b/app/src/Model/Table/PluginsTable.php @@ -212,7 +212,7 @@ public function deactivate(int $id): bool { public function findActive(Query $query): Query { return $query->where(['Plugins.status' => SuspendableStatusEnum::Active]) - ->order(['plugin' => 'ASC']); + ->orderBy(['plugin' => 'ASC']); } /** diff --git a/app/src/Model/Table/ProvisioningTargetsTable.php b/app/src/Model/Table/ProvisioningTargetsTable.php index 75f723ba4..c118d9e83 100644 --- a/app/src/Model/Table/ProvisioningTargetsTable.php +++ b/app/src/Model/Table/ProvisioningTargetsTable.php @@ -153,7 +153,7 @@ public function provision( $query = $query->where(['ProvisioningTargets.id' => $id]); } - $targets = $query->order(['ProvisioningTargets.ordr' => 'ASC']) + $targets = $query->orderBy(['ProvisioningTargets.ordr' => 'ASC']) ->contain($this->getPluginRelations()) ->all(); @@ -277,7 +277,7 @@ public function status(int $coId, int $groupId=null, int $personId=null): array 'provisioning_target_id' => $t->id, $subjectFK => $subjectID ]) - ->order(['id' => 'DESC']) + ->orderBy(['id' => 'DESC']) ->first(); if(!empty($rec)) { diff --git a/app/src/Model/Table/TrafficDetoursTable.php b/app/src/Model/Table/TrafficDetoursTable.php index 7a36f36c5..536d29c2a 100644 --- a/app/src/Model/Table/TrafficDetoursTable.php +++ b/app/src/Model/Table/TrafficDetoursTable.php @@ -93,13 +93,14 @@ public function initialize(array $config): void { ] ]); } - + /** * Determine the next Traffic Detour to process, given the last Traffic Detour to run. - * + * + * @param string $context + * @param int $lastDetourId ID of last Traffic Detour to run, or 0 if none + * @return TrafficDetour|null The next Traffic Detour to process, or null if none left * @since COmanage Registry v5.1.0 - * @param int $lastDetourId ID of last Traffic Detour to run, or 0 if none - * @return TrafficDetour The next Traffic Detour to process, or null if none left */ public function calculateNextDetour(string $context, int $lastDetourId=0): ?TrafficDetour { @@ -107,7 +108,7 @@ public function calculateNextDetour(string $context, int $lastDetourId=0): ?Traf $detours = $this->find() ->where(['status' => SuspendableStatusEnum::Active]) - ->order(['TrafficDetours.ordr' => 'ASC']) + ->orderBy(['TrafficDetours.ordr' => 'ASC']) ->all(); // Should we return the next Traffic Detour? If we haven't seen any yet then we diff --git a/app/src/Model/Table/TypesTable.php b/app/src/Model/Table/TypesTable.php index a47f7532f..9443acdc9 100644 --- a/app/src/Model/Table/TypesTable.php +++ b/app/src/Model/Table/TypesTable.php @@ -285,7 +285,7 @@ public function getTypeLabel(int $id): string { public function marshalProvisioningData(int $id): array { $ret = []; // We need the archived record on delete to properly deprovision - $ret['data'] = $this->get($id, ['archived' => true]); + $ret['data'] = $this->get($id, archived: true); // Provisioning Eligibility is // - Deleted if the changelog deleted flag is true diff --git a/app/src/View/Helper/BadgeHelper.php b/app/src/View/Helper/BadgeHelper.php index 9cf7c7d95..f41eb1266 100644 --- a/app/src/View/Helper/BadgeHelper.php +++ b/app/src/View/Helper/BadgeHelper.php @@ -32,7 +32,7 @@ class BadgeHelper extends Helper { - public $helpers = ['Html']; + public array $helpers = ['Html']; /** * Helper which will produce Bootstrap based Badge diff --git a/app/src/View/Helper/FieldHelper.php b/app/src/View/Helper/FieldHelper.php index 5e674a06c..7bdb5a254 100644 --- a/app/src/View/Helper/FieldHelper.php +++ b/app/src/View/Helper/FieldHelper.php @@ -37,7 +37,7 @@ use DOMDocument; class FieldHelper extends Helper { - public $helpers = ['Form', 'Html']; + public array $helpers = ['Form', 'Html']; /** * List of predefined editable form actions diff --git a/app/src/View/Helper/MenuHelper.php b/app/src/View/Helper/MenuHelper.php index 0752e1052..0d96cfa8f 100644 --- a/app/src/View/Helper/MenuHelper.php +++ b/app/src/View/Helper/MenuHelper.php @@ -33,7 +33,7 @@ class MenuHelper extends Helper { - public $helpers = ['Html']; + public array $helpers = ['Html']; /** * Get the Menu Order per action diff --git a/app/templates/Cos/select.php b/app/templates/Cos/select.php index 96df06e13..5f1b11e18 100644 --- a/app/templates/Cos/select.php +++ b/app/templates/Cos/select.php @@ -36,7 +36,7 @@ element('flash') // Flash messages ?> - + element('notify/alert', ['message' => __d('information','cos.none')]) ?>

diff --git a/app/templates/Standard/index.php b/app/templates/Standard/index.php index b586292a6..e9bc3bdeb 100644 --- a/app/templates/Standard/index.php +++ b/app/templates/Standard/index.php @@ -517,8 +517,10 @@ case 'echo': default: // By default our label is the column value, but it might be overridden - $label = $prefix . $entity->$col . $suffix; - + if(!is_object($entity->$col)) { + $label = $prefix . $entity->$col . $suffix; + } + // If there is no calculated default value but a default is configured, // use that instead if(empty($label) && !empty($cfg['default'])) { diff --git a/app/templates/element/menuTop.php b/app/templates/element/menuTop.php index ca9696e37..9508f4f24 100644 --- a/app/templates/element/menuTop.php +++ b/app/templates/element/menuTop.php @@ -174,7 +174,7 @@ class="form-check-input" - 1): // More than one CO is available, so present the switch button ?> + 1): // More than one CO is available, so present the switch button ?>
Html->link(' ' . __d('menu','co.switch'), '/cos/select', diff --git a/app/templates/element/subnavigation/supertitle.php b/app/templates/element/subnavigation/supertitle.php index c7354fa47..71d6a4205 100644 --- a/app/templates/element/subnavigation/supertitle.php +++ b/app/templates/element/subnavigation/supertitle.php @@ -64,7 +64,7 @@ && !empty($this->getPlugin()) && $vv_subnavigation_tabs[0] !== StringUtilities::entityToClassName($vv_bc_parent_obj) ) { - $object = $vv_obj ?? $$objectName?->first(); + $object = $vv_obj ?? $$objectName?->items()?->first(); if ($object === null) { // This is a deep nested association that has not been initialized yet. The controller name // will become the supertitle diff --git a/app/vendor/autoload.php b/app/vendor/autoload.php index 3b4acb469..8f79a47f2 100644 --- a/app/vendor/autoload.php +++ b/app/vendor/autoload.php @@ -14,10 +14,7 @@ echo $err; } } - trigger_error( - $err, - E_USER_ERROR - ); + throw new RuntimeException($err); } require_once __DIR__ . '/composer/autoload_real.php'; diff --git a/app/vendor/bin/composer b/app/vendor/bin/composer index b8ca913e6..fd55d73eb 100755 --- a/app/vendor/bin/composer +++ b/app/vendor/bin/composer @@ -112,9 +112,8 @@ if (PHP_VERSION_ID < 80000) { (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true)) || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) ) { - include("phpvfscomposer://" . __DIR__ . '/..'.'/composer/composer/bin/composer'); - exit(0); + return include("phpvfscomposer://" . __DIR__ . '/..'.'/composer/composer/bin/composer'); } } -include __DIR__ . '/..'.'/composer/composer/bin/composer'; +return include __DIR__ . '/..'.'/composer/composer/bin/composer'; diff --git a/app/vendor/bin/doctrine-dbal b/app/vendor/bin/doctrine-dbal index e86bf8dcc..4ed6f70b4 100755 --- a/app/vendor/bin/doctrine-dbal +++ b/app/vendor/bin/doctrine-dbal @@ -112,9 +112,8 @@ if (PHP_VERSION_ID < 80000) { (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true)) || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) ) { - include("phpvfscomposer://" . __DIR__ . '/..'.'/doctrine/dbal/bin/doctrine-dbal'); - exit(0); + return include("phpvfscomposer://" . __DIR__ . '/..'.'/doctrine/dbal/bin/doctrine-dbal'); } } -include __DIR__ . '/..'.'/doctrine/dbal/bin/doctrine-dbal'; +return include __DIR__ . '/..'.'/doctrine/dbal/bin/doctrine-dbal'; diff --git a/app/vendor/bin/phinx b/app/vendor/bin/phinx index 9942549c0..20ed10c9a 100755 --- a/app/vendor/bin/phinx +++ b/app/vendor/bin/phinx @@ -112,9 +112,8 @@ if (PHP_VERSION_ID < 80000) { (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true)) || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) ) { - include("phpvfscomposer://" . __DIR__ . '/..'.'/robmorgan/phinx/bin/phinx'); - exit(0); + return include("phpvfscomposer://" . __DIR__ . '/..'.'/robmorgan/phinx/bin/phinx'); } } -include __DIR__ . '/..'.'/robmorgan/phinx/bin/phinx'; +return include __DIR__ . '/..'.'/robmorgan/phinx/bin/phinx'; diff --git a/app/vendor/bin/php-parse b/app/vendor/bin/php-parse index 1bd2c838c..61566e60c 100755 --- a/app/vendor/bin/php-parse +++ b/app/vendor/bin/php-parse @@ -112,9 +112,8 @@ if (PHP_VERSION_ID < 80000) { (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true)) || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) ) { - include("phpvfscomposer://" . __DIR__ . '/..'.'/nikic/php-parser/bin/php-parse'); - exit(0); + return include("phpvfscomposer://" . __DIR__ . '/..'.'/nikic/php-parser/bin/php-parse'); } } -include __DIR__ . '/..'.'/nikic/php-parser/bin/php-parse'; +return include __DIR__ . '/..'.'/nikic/php-parser/bin/php-parse'; diff --git a/app/vendor/bin/phpunit b/app/vendor/bin/phpunit index e92cddc50..b5b530a8f 100755 --- a/app/vendor/bin/phpunit +++ b/app/vendor/bin/phpunit @@ -115,9 +115,8 @@ if (PHP_VERSION_ID < 80000) { (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true)) || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) ) { - include("phpvfscomposer://" . __DIR__ . '/..'.'/phpunit/phpunit/phpunit'); - exit(0); + return include("phpvfscomposer://" . __DIR__ . '/..'.'/phpunit/phpunit/phpunit'); } } -include __DIR__ . '/..'.'/phpunit/phpunit/phpunit'; +return include __DIR__ . '/..'.'/phpunit/phpunit/phpunit'; diff --git a/app/vendor/bin/psysh b/app/vendor/bin/psysh index ea7f565c1..7b983930e 100755 --- a/app/vendor/bin/psysh +++ b/app/vendor/bin/psysh @@ -112,9 +112,8 @@ if (PHP_VERSION_ID < 80000) { (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true)) || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) ) { - include("phpvfscomposer://" . __DIR__ . '/..'.'/psy/psysh/bin/psysh'); - exit(0); + return include("phpvfscomposer://" . __DIR__ . '/..'.'/psy/psysh/bin/psysh'); } } -include __DIR__ . '/..'.'/psy/psysh/bin/psysh'; +return include __DIR__ . '/..'.'/psy/psysh/bin/psysh'; diff --git a/app/vendor/bin/validate-json b/app/vendor/bin/validate-json index d077db58b..8be90f428 100755 --- a/app/vendor/bin/validate-json +++ b/app/vendor/bin/validate-json @@ -112,9 +112,8 @@ if (PHP_VERSION_ID < 80000) { (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true)) || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) ) { - include("phpvfscomposer://" . __DIR__ . '/..'.'/justinrainbow/json-schema/bin/validate-json'); - exit(0); + return include("phpvfscomposer://" . __DIR__ . '/..'.'/justinrainbow/json-schema/bin/validate-json'); } } -include __DIR__ . '/..'.'/justinrainbow/json-schema/bin/validate-json'; +return include __DIR__ . '/..'.'/justinrainbow/json-schema/bin/validate-json'; diff --git a/app/vendor/bin/var-dump-server b/app/vendor/bin/var-dump-server index c52c77272..18db1c1eb 100755 --- a/app/vendor/bin/var-dump-server +++ b/app/vendor/bin/var-dump-server @@ -112,9 +112,8 @@ if (PHP_VERSION_ID < 80000) { (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true)) || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) ) { - include("phpvfscomposer://" . __DIR__ . '/..'.'/symfony/var-dumper/Resources/bin/var-dump-server'); - exit(0); + return include("phpvfscomposer://" . __DIR__ . '/..'.'/symfony/var-dumper/Resources/bin/var-dump-server'); } } -include __DIR__ . '/..'.'/symfony/var-dumper/Resources/bin/var-dump-server'; +return include __DIR__ . '/..'.'/symfony/var-dumper/Resources/bin/var-dump-server'; diff --git a/app/vendor/brick/varexporter/CHANGELOG.md b/app/vendor/brick/varexporter/CHANGELOG.md index f636ce3fa..762b4fbe2 100644 --- a/app/vendor/brick/varexporter/CHANGELOG.md +++ b/app/vendor/brick/varexporter/CHANGELOG.md @@ -1,5 +1,32 @@ # Changelog +## [0.6.0](https://github.com/brick/varexporter/releases/tag/0.6.0) - 2025-02-20 + +💥 **BC breaks** + +- Minimum PHP version is now `8.1` (#39) + +✨ **New features** + +- Support for exporting `match` constructs in closures (#38) + +Thanks to @reinfi! + +## [0.5.0](https://github.com/brick/varexporter/releases/tag/0.5.0) - 2024-05-10 + +✨ **Compatibility** + +- Added compatibility with `nikic/php-parser` `5.x` +- Removed compatibility with `nikic/php-parser` `4.x` + +💥 **BC breaks** + +- deprecated constant `VarExporter::INLINE_NUMERIC_SCALAR_ARRAY` has been removed, please use `INLINE_SCALAR_LIST` instead + +## [0.4.0](https://github.com/brick/varexporter/releases/tag/0.4.0) - 2023-09-01 + +Minimum PHP version is now `7.4`. No breaking changes. + ## [0.3.8](https://github.com/brick/varexporter/releases/tag/0.3.8) - 2023-01-22 ✨ **New feature** diff --git a/app/vendor/brick/varexporter/composer.json b/app/vendor/brick/varexporter/composer.json index 916985d8c..53554754b 100644 --- a/app/vendor/brick/varexporter/composer.json +++ b/app/vendor/brick/varexporter/composer.json @@ -7,13 +7,13 @@ ], "license": "MIT", "require": { - "php": "^7.2 || ^8.0", - "nikic/php-parser": "^4.0" + "php": "^8.1", + "nikic/php-parser": "^5.0" }, "require-dev": { - "phpunit/phpunit": "^8.5 || ^9.0", + "phpunit/phpunit": "^10.5", "php-coveralls/php-coveralls": "^2.2", - "vimeo/psalm": "4.23.0" + "vimeo/psalm": "6.8.4" }, "autoload": { "psr-4": { diff --git a/app/vendor/brick/varexporter/src/ExportException.php b/app/vendor/brick/varexporter/src/ExportException.php index 888cf9acd..662c32c2e 100644 --- a/app/vendor/brick/varexporter/src/ExportException.php +++ b/app/vendor/brick/varexporter/src/ExportException.php @@ -9,9 +9,7 @@ final class ExportException extends \Exception { /** - * @param string $message - * @param string[] $path - * @param Throwable|null $previous + * @param string[] $path */ public function __construct(string $message, array $path, ?Throwable $previous = null) { @@ -26,8 +24,6 @@ public function __construct(string $message, array $path, ?Throwable $previous = * Returns a string representation of the given path. * * @param string[] $path - * - * @return string */ public static function pathToString(array $path) : string { diff --git a/app/vendor/brick/varexporter/src/Internal/GenericExporter.php b/app/vendor/brick/varexporter/src/Internal/GenericExporter.php index 6b520a231..7757ccdbe 100644 --- a/app/vendor/brick/varexporter/src/Internal/GenericExporter.php +++ b/app/vendor/brick/varexporter/src/Internal/GenericExporter.php @@ -21,7 +21,7 @@ final class GenericExporter /** * @var ObjectExporter[] */ - private $objectExporters = []; + private array $objectExporters = []; /** * The visited objects, to detect circular references. @@ -30,80 +30,66 @@ final class GenericExporter * * @var array> */ - private $visitedObjects = []; + private array $visitedObjects = []; /** * @psalm-readonly - * - * @var bool */ - public $addTypeHints; + public bool $addTypeHints; /** * @psalm-readonly - * - * @var bool */ - public $skipDynamicProperties; + public bool $skipDynamicProperties; /** * @psalm-readonly - * - * @var bool */ - public $inlineArray; + public bool $inlineArray; /** * @psalm-readonly - * - * @var bool */ - public $inlineScalarList; + public bool $inlineScalarList; /** * @psalm-readonly - * - * @var bool */ - public $closureSnapshotUses; + public bool $closureSnapshotUses; /** * @psalm-readonly - * - * @var bool */ - public $trailingCommaInArray; + public bool $trailingCommaInArray; /** * @psalm-readonly - * - * @var int */ - public $indentLevel; + public int $indentLevel; public function __construct(int $options, int $indentLevel = 0) { $this->objectExporters[] = new ObjectExporter\StdClassExporter($this); - if (! ($options & VarExporter::NO_CLOSURES)) { + if (($options & VarExporter::NO_CLOSURES) === 0) { $this->objectExporters[] = new ObjectExporter\ClosureExporter($this); } - if (! ($options & VarExporter::NO_SET_STATE)) { + if (($options & VarExporter::NO_SET_STATE) === 0) { $this->objectExporters[] = new ObjectExporter\SetStateExporter($this); } $this->objectExporters[] = new ObjectExporter\InternalClassExporter($this); - if (! ($options & VarExporter::NO_SERIALIZE)) { + if (($options & VarExporter::NO_SERIALIZE) === 0) { $this->objectExporters[] = new ObjectExporter\SerializeExporter($this); } - if (! ($options & VarExporter::NO_ENUMS)) { + if (($options & VarExporter::NO_ENUMS) === 0) { $this->objectExporters[] = new ObjectExporter\EnumExporter($this); } - if (! ($options & VarExporter::NOT_ANY_OBJECT)) { + if (($options & VarExporter::NOT_ANY_OBJECT) === 0) { $this->objectExporters[] = new ObjectExporter\AnyObjectExporter($this); } @@ -126,31 +112,27 @@ public function __construct(int $options, int $indentLevel = 0) * * @throws ExportException */ - public function export($var, array $path, array $parentIds) : array + public function export(mixed $var, array $path, array $parentIds) : array { - switch ($type = gettype($var)) { - case 'boolean': - case 'integer': - case 'double': - case 'string': - return [var_export($var, true)]; - - case 'NULL': - // lowercase null - return ['null']; - - case 'array': - /** @var array $var */ - return $this->exportArray($var, $path, $parentIds); - - case 'object': - /** @var object $var */ - return $this->exportObject($var, $path, $parentIds); - - default: - // resources - throw new ExportException(sprintf('Type "%s" is not supported.', $type), $path); + if ($var === null) { + return ['null']; + } + + // bool, int, float, string + if (is_scalar($var)) { + return [var_export($var, true)]; + } + + if (is_array($var)) { + return $this->exportArray($var, $path, $parentIds); + } + + if (is_object($var)) { + return $this->exportObject($var, $path, $parentIds); } + + // resources + throw new ExportException(sprintf('Type "%s" is not supported.', gettype($var)), $path); } /** @@ -188,11 +170,7 @@ public function exportArray(array $array, array $path, array $parentIds) : array $exported = $this->export($value, $newPath, $parentIds); if ($inline) { - if ($isList) { - $result[] = $exported[0]; - } else { - $result[] = var_export($key, true) . ' => ' . $exported[0]; - } + $result[] = $isList ? $exported[0] : var_export($key, true) . ' => ' . $exported[0]; } else { $prepend = ''; $append = ''; @@ -228,9 +206,6 @@ public function exportArray(array $array, array $path, array $parentIds) : array * Types considered scalar here are int, bool, float, string and null. * If the array is empty, this method returns true. * - * @param array $array - * - * @return bool */ private function isScalarList(array $array) : bool { @@ -261,7 +236,7 @@ public function exportObject(object $object, array $path, array $parentIds) : ar throw new ExportException(sprintf( 'Object of class "%s" has a circular reference at %s. ' . 'Circular references are currently not supported.', - get_class($object), + $object::class, ExportException::pathToString($this->visitedObjects[$parentId][$id]) ), $path); } diff --git a/app/vendor/brick/varexporter/src/Internal/ObjectExporter.php b/app/vendor/brick/varexporter/src/Internal/ObjectExporter.php index 9b34614ee..d3e7eee8a 100644 --- a/app/vendor/brick/varexporter/src/Internal/ObjectExporter.php +++ b/app/vendor/brick/varexporter/src/Internal/ObjectExporter.php @@ -13,14 +13,8 @@ */ abstract class ObjectExporter { - /** - * @var GenericExporter - */ - protected $exporter; + protected GenericExporter $exporter; - /** - * @param GenericExporter $exporter - */ public function __construct(GenericExporter $exporter) { $this->exporter = $exporter; @@ -30,8 +24,6 @@ public function __construct(GenericExporter $exporter) * Returns whether this exporter supports the given object. * * @param \ReflectionObject $reflectionObject A reflection of the object. - * - * @return bool */ abstract public function supports(\ReflectionObject $reflectionObject) : bool; @@ -54,8 +46,6 @@ abstract public function export(object $object, \ReflectionObject $reflectionObj * * If the class has a constructor, reflection will be used to bypass it. * - * @param \ReflectionClass $class - * * @return string[] The lines of code. */ final protected function getCreateObjectCode(\ReflectionClass $class) : array diff --git a/app/vendor/brick/varexporter/src/Internal/ObjectExporter/AnyObjectExporter.php b/app/vendor/brick/varexporter/src/Internal/ObjectExporter/AnyObjectExporter.php index 7cb3b6292..573d6c357 100644 --- a/app/vendor/brick/varexporter/src/Internal/ObjectExporter/AnyObjectExporter.php +++ b/app/vendor/brick/varexporter/src/Internal/ObjectExporter/AnyObjectExporter.php @@ -5,6 +5,7 @@ namespace Brick\VarExporter\Internal\ObjectExporter; use Brick\VarExporter\Internal\ObjectExporter; +use Override; /** * Handles any class through direct property access and bound closures. @@ -14,21 +15,18 @@ * * @internal This class is for internal use, and not part of the public API. It may change at any time without warning. */ -class AnyObjectExporter extends ObjectExporter +final class AnyObjectExporter extends ObjectExporter { - /** - * {@inheritDoc} - */ + #[Override] public function supports(\ReflectionObject $reflectionObject) : bool { return true; } /** - * {@inheritDoc} - * * @psalm-suppress MixedAssignment */ + #[Override] public function export(object $object, \ReflectionObject $reflectionObject, array $path, array $parentIds) : array { $lines = $this->getCreateObjectCode($reflectionObject); @@ -156,9 +154,6 @@ public function export(object $object, \ReflectionObject $reflectionObject, arra /** * Returns the key of the given property in the object-to-array cast. * - * @param \ReflectionProperty $property - * - * @return string */ private function getPropertyKey(\ReflectionProperty $property) : string { @@ -175,11 +170,6 @@ private function getPropertyKey(\ReflectionProperty $property) : string return $name; } - /** - * @param string $var - * - * @return string - */ private function escapePropName(string $var) : string { if (preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $var) === 1) { diff --git a/app/vendor/brick/varexporter/src/Internal/ObjectExporter/ClosureExporter.php b/app/vendor/brick/varexporter/src/Internal/ObjectExporter/ClosureExporter.php index ea98891cc..1e6120e71 100644 --- a/app/vendor/brick/varexporter/src/Internal/ObjectExporter/ClosureExporter.php +++ b/app/vendor/brick/varexporter/src/Internal/ObjectExporter/ClosureExporter.php @@ -7,6 +7,7 @@ use Brick\VarExporter\ExportException; use Brick\VarExporter\Internal\ObjectExporter; use Closure; +use Override; use PhpParser\Error; use PhpParser\Node; use PhpParser\NodeTraverser; @@ -14,6 +15,7 @@ use PhpParser\NodeVisitor\NameResolver; use PhpParser\Parser; use PhpParser\ParserFactory; +use PhpParser\PhpVersion; use ReflectionFunction; /** @@ -21,24 +23,17 @@ * * @internal This class is for internal use, and not part of the public API. It may change at any time without warning. */ -class ClosureExporter extends ObjectExporter +final class ClosureExporter extends ObjectExporter { - /** - * @var Parser|null - */ - private $parser; + private ?Parser $parser = null; - /** - * {@inheritDoc} - */ + #[Override] public function supports(\ReflectionObject $reflectionObject) : bool { return $reflectionObject->getName() === \Closure::class; } - /** - * {@inheritDoc} - */ + #[Override] public function export(object $object, \ReflectionObject $reflectionObject, array $path, array $parentIds) : array { assert($object instanceof Closure); @@ -64,13 +59,10 @@ public function export(object $object, \ReflectionObject $reflectionObject, arra return [$code]; } - /** - * @return Parser - */ - private function getParser() + private function getParser(): Parser { if ($this->parser === null) { - $this->parser = (new ParserFactory)->create(ParserFactory::ONLY_PHP7); + $this->parser = (new ParserFactory)->createForHostVersion(); } return $this->parser; @@ -88,7 +80,7 @@ private function getParser() */ private function parseFile(string $filename, array $path) : array { - if (substr($filename, -16) === " : eval()'d code") { + if (str_ends_with($filename, " : eval()'d code")) { throw new ExportException("Closure defined in eval()'d code cannot be exported.", $path); } @@ -139,8 +131,6 @@ private function resolveNames(array $ast) : array * @param int $line The line number where the closure is located in the source file. * @param string[] $path The path to the closure in the array/object graph. * - * @return Node\Expr\Closure - * * @throws ExportException */ private function getClosure( @@ -150,10 +140,10 @@ private function getClosure( int $line, array $path ) : Node\Expr\Closure { - $finder = new FindingVisitor(function(Node $node) use ($line) : bool { - return ($node instanceof Node\Expr\Closure || $node instanceof Node\Expr\ArrowFunction) - && $node->getStartLine() === $line; - }); + $finder = new FindingVisitor( + fn(Node $node): bool => ($node instanceof Node\Expr\Closure || $node instanceof Node\Expr\ArrowFunction) + && $node->getStartLine() === $line + ); $traverser = new NodeTraverser(); $traverser->addVisitor($finder); @@ -190,8 +180,6 @@ private function getClosure( * * @param ReflectionFunction $reflectionFunction Reflection of the closure. * @param Node\Expr\ArrowFunction $arrowFunction Parsed arrow function. - * - * @return Node\Expr\Closure */ private function convertArrowFunction( ReflectionFunction $reflectionFunction, diff --git a/app/vendor/brick/varexporter/src/Internal/ObjectExporter/ClosureExporter/PrettyPrinter.php b/app/vendor/brick/varexporter/src/Internal/ObjectExporter/ClosureExporter/PrettyPrinter.php index 4010810a4..97fd44245 100644 --- a/app/vendor/brick/varexporter/src/Internal/ObjectExporter/ClosureExporter/PrettyPrinter.php +++ b/app/vendor/brick/varexporter/src/Internal/ObjectExporter/ClosureExporter/PrettyPrinter.php @@ -4,6 +4,7 @@ namespace Brick\VarExporter\Internal\ObjectExporter\ClosureExporter; +use Override; use PhpParser\PrettyPrinter\Standard; /** @@ -13,24 +14,14 @@ */ final class PrettyPrinter extends Standard { - /** - * @var int - */ - private $varExporterNestingLevel = 0; + private int $varExporterNestingLevel = 0; - /** - * @param int $level - * - * @return void - */ public function setVarExporterNestingLevel(int $level) : void { $this->varExporterNestingLevel = $level; } - /** - * {@inheritDoc} - */ + #[Override] protected function resetState() : void { parent::resetState(); diff --git a/app/vendor/brick/varexporter/src/Internal/ObjectExporter/EnumExporter.php b/app/vendor/brick/varexporter/src/Internal/ObjectExporter/EnumExporter.php index dfec28f42..86fc1cbae 100644 --- a/app/vendor/brick/varexporter/src/Internal/ObjectExporter/EnumExporter.php +++ b/app/vendor/brick/varexporter/src/Internal/ObjectExporter/EnumExporter.php @@ -5,6 +5,7 @@ namespace Brick\VarExporter\Internal\ObjectExporter; use Brick\VarExporter\Internal\ObjectExporter; +use Override; use UnitEnum; /** @@ -12,28 +13,21 @@ * * @internal This class is for internal use, and not part of the public API. It may change at any time without warning. */ -class EnumExporter extends ObjectExporter +final class EnumExporter extends ObjectExporter { - /** - * {@inheritDoc} - * - * See: https://github.com/vimeo/psalm/pull/8117 - * @psalm-suppress RedundantCondition - */ + #[Override] public function supports(\ReflectionObject $reflectionObject) : bool { - return method_exists($reflectionObject, 'isEnum') && $reflectionObject->isEnum(); + return $reflectionObject->isEnum(); } - /** - * {@inheritDoc} - */ + #[Override] public function export(object $object, \ReflectionObject $reflectionObject, array $path, array $parentIds) : array { assert($object instanceof UnitEnum); return [ - get_class($object) . '::' . $object->name + $object::class . '::' . $object->name ]; } } diff --git a/app/vendor/brick/varexporter/src/Internal/ObjectExporter/InternalClassExporter.php b/app/vendor/brick/varexporter/src/Internal/ObjectExporter/InternalClassExporter.php index e0adbd111..79ce7cf05 100644 --- a/app/vendor/brick/varexporter/src/Internal/ObjectExporter/InternalClassExporter.php +++ b/app/vendor/brick/varexporter/src/Internal/ObjectExporter/InternalClassExporter.php @@ -6,25 +6,22 @@ use Brick\VarExporter\ExportException; use Brick\VarExporter\Internal\ObjectExporter; +use Override; /** * Throws on internal classes. * * @internal This class is for internal use, and not part of the public API. It may change at any time without warning. */ -class InternalClassExporter extends ObjectExporter +final class InternalClassExporter extends ObjectExporter { - /** - * {@inheritDoc} - */ + #[Override] public function supports(\ReflectionObject $reflectionObject) : bool { return $reflectionObject->isInternal(); } - /** - * {@inheritDoc} - */ + #[Override] public function export(object $object, \ReflectionObject $reflectionObject, array $path, array $parentIds) : array { $className = $reflectionObject->getName(); diff --git a/app/vendor/brick/varexporter/src/Internal/ObjectExporter/SerializeExporter.php b/app/vendor/brick/varexporter/src/Internal/ObjectExporter/SerializeExporter.php index 604a104ea..6953ae8b4 100644 --- a/app/vendor/brick/varexporter/src/Internal/ObjectExporter/SerializeExporter.php +++ b/app/vendor/brick/varexporter/src/Internal/ObjectExporter/SerializeExporter.php @@ -5,26 +5,23 @@ namespace Brick\VarExporter\Internal\ObjectExporter; use Brick\VarExporter\Internal\ObjectExporter; +use Override; /** * Handles instances of classes with __serialize() and __unserialize() methods. * * @internal This class is for internal use, and not part of the public API. It may change at any time without warning. */ -class SerializeExporter extends ObjectExporter +final class SerializeExporter extends ObjectExporter { - /** - * {@inheritDoc} - */ + #[Override] public function supports(\ReflectionObject $reflectionObject) : bool { return $reflectionObject->hasMethod('__serialize') && $reflectionObject->hasMethod('__unserialize'); } - /** - * {@inheritDoc} - */ + #[Override] public function export(object $object, \ReflectionObject $reflectionObject, array $path, array $parentIds) : array { $lines = $this->getCreateObjectCode($reflectionObject); diff --git a/app/vendor/brick/varexporter/src/Internal/ObjectExporter/SetStateExporter.php b/app/vendor/brick/varexporter/src/Internal/ObjectExporter/SetStateExporter.php index a464b992a..a33c79e38 100644 --- a/app/vendor/brick/varexporter/src/Internal/ObjectExporter/SetStateExporter.php +++ b/app/vendor/brick/varexporter/src/Internal/ObjectExporter/SetStateExporter.php @@ -6,17 +6,16 @@ use Brick\VarExporter\ExportException; use Brick\VarExporter\Internal\ObjectExporter; +use Override; /** * Handles instances of classes with a __set_state() method. * * @internal This class is for internal use, and not part of the public API. It may change at any time without warning. */ -class SetStateExporter extends ObjectExporter +final class SetStateExporter extends ObjectExporter { - /** - * {@inheritDoc} - */ + #[Override] public function supports(\ReflectionObject $reflectionObject) : bool { if ($reflectionObject->hasMethod('__set_state')) { @@ -28,9 +27,7 @@ public function supports(\ReflectionObject $reflectionObject) : bool return false; } - /** - * {@inheritDoc} - */ + #[Override] public function export(object $object, \ReflectionObject $reflectionObject, array $path, array $parentIds) : array { $className = $reflectionObject->getName(); @@ -38,9 +35,8 @@ public function export(object $object, \ReflectionObject $reflectionObject, arra $vars = $this->getObjectVars($object, $path); $exportedVars = $this->exporter->exportArray($vars, $path, $parentIds); - $exportedVars = $this->exporter->wrap($exportedVars, '\\' . $className . '::__set_state(', ')'); - return $exportedVars; + return $this->exporter->wrap($exportedVars, '\\' . $className . '::__set_state(', ')'); } /** @@ -76,10 +72,8 @@ private function getObjectVars(object $object, array $path) : array $name = substr($name, $pos + 1); } - assert($name !== false); - if (array_key_exists($name, $result)) { - $className = get_class($object); + $className = $object::class; throw new ExportException( 'Class "' . $className . '" has overridden private property "' . $name . '". ' . @@ -98,12 +92,6 @@ private function getObjectVars(object $object, array $path) : array return $result; } - /** - * @param object $object - * @param string $name - * - * @return bool - */ private function isDynamicProperty(object $object, string $name) : bool { $reflectionClass = new \ReflectionClass($object); diff --git a/app/vendor/brick/varexporter/src/Internal/ObjectExporter/StdClassExporter.php b/app/vendor/brick/varexporter/src/Internal/ObjectExporter/StdClassExporter.php index ac958f18f..5c7e869d0 100644 --- a/app/vendor/brick/varexporter/src/Internal/ObjectExporter/StdClassExporter.php +++ b/app/vendor/brick/varexporter/src/Internal/ObjectExporter/StdClassExporter.php @@ -5,25 +5,22 @@ namespace Brick\VarExporter\Internal\ObjectExporter; use Brick\VarExporter\Internal\ObjectExporter; +use Override; /** * Handles stdClass objects. * * @internal This class is for internal use, and not part of the public API. It may change at any time without warning. */ -class StdClassExporter extends ObjectExporter +final class StdClassExporter extends ObjectExporter { - /** - * {@inheritDoc} - */ + #[Override] public function supports(\ReflectionObject $reflectionObject) : bool { return $reflectionObject->getName() === \stdClass::class; } - /** - * {@inheritDoc} - */ + #[Override] public function export(object $object, \ReflectionObject $reflectionObject, array $path, array $parentIds) : array { $exported = $this->exporter->exportArray((array) $object, $path, $parentIds); diff --git a/app/vendor/brick/varexporter/src/VarExporter.php b/app/vendor/brick/varexporter/src/VarExporter.php index 21d6e4fcd..7258f337a 100644 --- a/app/vendor/brick/varexporter/src/VarExporter.php +++ b/app/vendor/brick/varexporter/src/VarExporter.php @@ -54,11 +54,6 @@ final class VarExporter */ public const INLINE_SCALAR_LIST = 1 << 7; - /** - * @deprecated Please use INLINE_SCALAR_LIST instead. - */ - public const INLINE_NUMERIC_SCALAR_ARRAY = self::INLINE_SCALAR_LIST; - /** * Export static vars defined via `use` as variables. */ @@ -85,11 +80,9 @@ final class VarExporter * Combine multiple options with a bitwise OR `|` operator. * @param int $indentLevel The base output indentation level. * - * @return string - * * @throws ExportException */ - public static function export($var, int $options = 0, int $indentLevel = 0) : string + public static function export(mixed $var, int $options = 0, int $indentLevel = 0) : string { $exporter = new GenericExporter($options, $indentLevel); $lines = $exporter->export($var, [], []); @@ -98,14 +91,15 @@ public static function export($var, int $options = 0, int $indentLevel = 0) : st $export = implode(PHP_EOL, $lines); } else { $firstLine = array_shift($lines); - $lines = array_map(function ($line) use ($indentLevel) { - return str_repeat(' ', $indentLevel) . $line; - }, $lines); + $lines = array_map( + fn($line) => str_repeat(' ', $indentLevel) . $line, + $lines, + ); $export = $firstLine . PHP_EOL . implode(PHP_EOL, $lines); } - if ($options & self::ADD_RETURN) { + if (($options & self::ADD_RETURN) !== 0) { return 'return ' . $export . ';' . PHP_EOL; } diff --git a/app/vendor/cakephp-plugins.php b/app/vendor/cakephp-plugins.php index 7520a9c95..b81cefcb2 100644 --- a/app/vendor/cakephp-plugins.php +++ b/app/vendor/cakephp-plugins.php @@ -15,6 +15,5 @@ 'Migrations' => $baseDir . '/vendor/cakephp/migrations/', 'OrcidSource' => $baseDir . '/plugins/OrcidSource/', 'SshKeyAuthenticator' => $baseDir . '/plugins/SshKeyAuthenticator/', - 'TestWidget' => $baseDir . '/plugins/TestWidget/', ], ]; diff --git a/app/vendor/cakephp/bake/.phive/phars.xml b/app/vendor/cakephp/bake/.phive/phars.xml new file mode 100644 index 000000000..f5aa33004 --- /dev/null +++ b/app/vendor/cakephp/bake/.phive/phars.xml @@ -0,0 +1,4 @@ + + + + diff --git a/app/vendor/cakephp/bake/Dockerfile b/app/vendor/cakephp/bake/Dockerfile index 296613920..7acfb27ee 100644 --- a/app/vendor/cakephp/bake/Dockerfile +++ b/app/vendor/cakephp/bake/Dockerfile @@ -1,7 +1,7 @@ # Basic docker based environment # Necessary to trick dokku into building the documentation # using dockerfile instead of herokuish -FROM ubuntu:17.04 +FROM ubuntu:22.04 # Add basic tools RUN apt-get update && \ @@ -13,9 +13,11 @@ RUN apt-get update && \ libffi-dev \ libssl-dev +# Prevent interactive timezone input +ENV DEBIAN_FRONTEND=noninteractive RUN LC_ALL=C.UTF-8 add-apt-repository ppa:ondrej/php && \ apt-get update && \ - apt-get install -y php7.2-cli php7.2-mbstring php7.2-xml php7.2-zip php7.2-intl php7.2-opcache php7.2-sqlite + apt-get install -y php8.1-cli php8.1-mbstring php8.1-xml php8.1-zip php8.1-intl php8.1-opcache php8.1-sqlite WORKDIR /code diff --git a/app/vendor/cakephp/bake/README.md b/app/vendor/cakephp/bake/README.md index 3cc152bc7..a403eae02 100644 --- a/app/vendor/cakephp/bake/README.md +++ b/app/vendor/cakephp/bake/README.md @@ -20,7 +20,7 @@ composer require --dev cakephp/bake ## Documentation -You can find the documentation for bake [on its own cookbook](https://book.cakephp.org/bake/2). +You can find the documentation for bake [on its own cookbook](https://book.cakephp.org/bake/3). ## Testing diff --git a/app/vendor/cakephp/bake/composer.json b/app/vendor/cakephp/bake/composer.json index 679346281..fedab7a39 100644 --- a/app/vendor/cakephp/bake/composer.json +++ b/app/vendor/cakephp/bake/composer.json @@ -1,34 +1,37 @@ { "name": "cakephp/bake", "description": "Bake plugin for CakePHP", - "type": "cakephp-plugin", - "keywords": ["cakephp", "bake"], - "homepage": "https://github.com/cakephp/bake", "license": "MIT", + "type": "cakephp-plugin", + "keywords": [ + "cakephp", + "bake", + "dev", + "cli" + ], "authors": [ { "name": "CakePHP Community", "homepage": "https://github.com/cakephp/bake/graphs/contributors" } ], + "homepage": "https://github.com/cakephp/bake", "support": { "issues": "https://github.com/cakephp/bake/issues", "forum": "https://stackoverflow.com/tags/cakephp", - "irc": "irc://irc.freenode.org/cakephp", "source": "https://github.com/cakephp/bake" }, "require": { - "php": ">=7.2", - "cakephp/cakephp": "^4.3.0", - "cakephp/twig-view": "^1.0.2", - "brick/varexporter": "^0.3.5", - "nikic/php-parser": "^4.13.2" + "php": ">=8.1", + "brick/varexporter": "^0.6.0", + "cakephp/cakephp": "^5.1", + "cakephp/twig-view": "^2.0.0", + "nikic/php-parser": "^5.0.0" }, "require-dev": { - "cakephp/cakephp-codesniffer": "^4.0", - "phpunit/phpunit": "^8.5 || ^9.3", - "cakephp/debug_kit": "^4.1", - "cakephp/plugin-installer": "^1.3" + "cakephp/cakephp-codesniffer": "^5.0.0", + "cakephp/debug_kit": "^5.0.0", + "phpunit/phpunit": "^10.5.5 || ^11.1.3" }, "autoload": { "psr-4": { @@ -38,11 +41,16 @@ "autoload-dev": { "psr-4": { "BakeTest\\": "tests/test_app/Plugin/BakeTest/src/", - "Company\\Pastry\\": "tests/test_app/Plugin/Company/Pastry/src/", - "Pastry\\PastryTest\\": "tests/test_app/Plugin/PastryTest/src/", - "WithBakeSubFolder\\": "tests/test_app/Plugin/WithBakeSubFolder/src/", "Bake\\Test\\": "tests/", - "Bake\\Test\\App\\": "tests/test_app/App/" + "Bake\\Test\\App\\": "tests/test_app/App/", + "Company\\Pastry\\": "tests/test_app/Plugin/Company/Pastry/src/", + "WithBakeSubFolder\\": "tests/test_app/Plugin/WithBakeSubFolder/src/" + } + }, + "config": { + "allow-plugins": { + "cakephp/plugin-installer": true, + "dealerdirect/phpcodesniffer-composer-installer": true } }, "scripts": { @@ -50,17 +58,13 @@ "@test", "@cs-check" ], - "cs-check": "phpcs --parallel=16", - "cs-fix": "phpcbf --parallel=16", - "stan": "phpstan analyse && psalm.phar", - "stan-setup": "cp composer.json composer.backup && composer require --dev phpstan/phpstan:^1.7 psalm/phar:~4.27.0 && mv composer.backup composer.json", + "cs-check": "phpcs --parallel=16 -p src/ tests/", + "cs-fix": "phpcbf --parallel=16 -p src/ tests/", + "phpstan": "tools/phpstan analyse", + "stan": "@phpstan", + "stan-baseline": "tools/phpstan --generate-baseline", + "stan-setup": "phive install", "test": "phpunit", "test-coverage": "phpunit --coverage-clover=clover.xml" - }, - "config": { - "allow-plugins": { - "cakephp/plugin-installer": true, - "dealerdirect/phpcodesniffer-composer-installer": true - } } } diff --git a/app/vendor/cakephp/bake/docs.Dockerfile b/app/vendor/cakephp/bake/docs.Dockerfile index 4f3ca473c..eb134b0ee 100644 --- a/app/vendor/cakephp/bake/docs.Dockerfile +++ b/app/vendor/cakephp/bake/docs.Dockerfile @@ -13,7 +13,7 @@ FROM ghcr.io/cakephp/docs-builder:runtime as runtime # Configure search index script ENV LANGS="en es fr ja pt ru" ENV SEARCH_SOURCE="/usr/share/nginx/html" -ENV SEARCH_URL_PREFIX="/bake/2" +ENV SEARCH_URL_PREFIX="/bake/3" COPY --from=builder /data/docs /data/docs COPY --from=builder /data/website /data/website diff --git a/app/vendor/cakephp/bake/docs/config/all.py b/app/vendor/cakephp/bake/docs/config/all.py index 260522630..cfeecdc20 100644 --- a/app/vendor/cakephp/bake/docs/config/all.py +++ b/app/vendor/cakephp/bake/docs/config/all.py @@ -10,10 +10,10 @@ # # The full version, including alpha/beta/rc tags. -release = '2.x' +release = '3.x' # The search index version. -search_version = 'bake-2' +search_version = 'bake-3' # The marketing display name for the book. version_name = '' @@ -24,7 +24,8 @@ # Other versions that display in the version picker menu. version_list = [ {'name': '1.x', 'number': '/bake/1.x', 'title': '1.x'}, - {'name': '2.x', 'number': '/bake/2.x', 'title': '2.x', 'current': True}, + {'name': '2.x', 'number': '/bake/2.x', 'title': '2.x'}, + {'name': '3.x', 'number': '/bake/3.x', 'title': '3.x', 'current': True}, ] # Languages available. diff --git a/app/vendor/cakephp/bake/docs/en/development.rst b/app/vendor/cakephp/bake/docs/en/development.rst index 735c4183f..c7c7aa252 100644 --- a/app/vendor/cakephp/bake/docs/en/development.rst +++ b/app/vendor/cakephp/bake/docs/en/development.rst @@ -88,7 +88,7 @@ you can use the following event:: 'view', 'add', 'edit', - 'delete' + 'delete', ]); } } @@ -129,7 +129,7 @@ looks like this:: /** * Hook method for defining this command's option parser. * - * @see https://book.cakephp.org/4/en/console-commands/commands.html#defining-arguments-and-options + * @see https://book.cakephp.org/5/en/console-commands/commands.html#defining-arguments-and-options * @param \Cake\Console\ConsoleOptionParser $parser The parser to be defined * @return \Cake\Console\ConsoleOptionParser The built parser. */ @@ -145,7 +145,7 @@ looks like this:: * * @param \Cake\Console\Arguments $args The command arguments. * @param \Cake\Console\ConsoleIo $io The console io - * @return null|void|int The exit code or null for success + * @return int|null|void The exit code or null for success */ public function execute(Arguments $args, ConsoleIo $io) { @@ -172,7 +172,7 @@ And the resultant baked class (**src/Command/FooCommand.php**) looks like this:: /** * Hook method for defining this command's option parser. * - * @see https://book.cakephp.org/4/en/console-commands/commands.html#defining-arguments-and-options + * @see https://book.cakephp.org/5/en/console-commands/commands.html#defining-arguments-and-options * @param \Cake\Console\ConsoleOptionParser $parser The parser to be defined * @return \Cake\Console\ConsoleOptionParser The built parser. */ @@ -188,7 +188,7 @@ And the resultant baked class (**src/Command/FooCommand.php**) looks like this:: * * @param \Cake\Console\Arguments $args The command arguments. * @param \Cake\Console\ConsoleIo $io The console io - * @return null|void|int The exit code or null for success + * @return int|null|void The exit code or null for success */ public function execute(Arguments $args, ConsoleIo $io) { diff --git a/app/vendor/cakephp/bake/docs/en/index.rst b/app/vendor/cakephp/bake/docs/en/index.rst index 28899b7b0..38c7d2f89 100644 --- a/app/vendor/cakephp/bake/docs/en/index.rst +++ b/app/vendor/cakephp/bake/docs/en/index.rst @@ -14,7 +14,7 @@ Installation Before trying to use or extend bake, make sure it is installed in your application. Bake is provided as a plugin that you can install with Composer:: - composer require --dev cakephp/bake:"^2.0" + composer require --dev cakephp/bake:"^3.0" The above will install bake as a development dependency. This means that it will not be installed when you do production deployments. diff --git a/app/vendor/cakephp/bake/docs/en/usage.rst b/app/vendor/cakephp/bake/docs/en/usage.rst index d68a745f0..8cf9da928 100644 --- a/app/vendor/cakephp/bake/docs/en/usage.rst +++ b/app/vendor/cakephp/bake/docs/en/usage.rst @@ -31,9 +31,11 @@ You can get the list of available bake command by running ``bin/cake bake --help - bake behavior - bake cell - bake command + - bake command_helper - bake component - bake controller - bake controller all + - bake enum - bake fixture - bake fixture all - bake form @@ -50,6 +52,18 @@ You can get the list of available bake command by running ``bin/cake bake --help To run a command, type `cake command_name [args|options]` To get help on a specific command, type `cake command_name --help` +Bake Models +=========== + +Models are generically baked from the existing DB tables. +The conventions here apply, so it will detect relations based on ``thing_id`` foreign keys to ``things`` tables with their ``id`` primary keys. + +For non-conventional relations, you can use references in the constraints / foreign key definitions for Bake to detect the relations, e.g.:: + + ->addForeignKey('billing_country_id', 'countries') // defaults to `id` + ->addForeignKey('shipping_country_id', 'countries', 'cid') + + Bake Themes =========== @@ -59,4 +73,4 @@ template files used when baking. To create your own templates, see the .. meta:: :title lang=en: Code Generation with Bake - :keywords lang=en: command line interface,functional application,database,database configuration,bash script,basic ingredients,project,model,path path,code generation,scaffolding,windows users,configuration file,few minutes,config,iew,shell,models,running,mysql + :keywords lang=en: command line interface,functional application,database,database configuration,bash script,basic ingredients,project,model,path path,code generation,scaffolding,windows users,configuration file,few minutes,config,view,models,running,mysql diff --git a/app/vendor/cakephp/bake/docs/es/usage.rst b/app/vendor/cakephp/bake/docs/es/usage.rst index dad6ffd95..40a6e43a3 100644 --- a/app/vendor/cakephp/bake/docs/es/usage.rst +++ b/app/vendor/cakephp/bake/docs/es/usage.rst @@ -14,7 +14,7 @@ Si tiene problemas para ejecutar el script, asegurese de: lanzar ``bin/cake bake``. Antes de comenzar la ejecución, asegúrese de disponer al menos de una conexión -a una base de datos configurada. +a una base de datos configurada. Para comenzar con la ejecución del comando debe abrir la consola de windows y ejecutar "Cake Bake" @@ -53,8 +53,6 @@ El resultado debería ser algo similar a lo siguiente:: - migration_snapshot - model - plugin - - shell - - shell-helper - template - test @@ -112,4 +110,4 @@ propios templates, ver :ref:`bake theme creation documentation .. meta:: :title lang=es: Crear código con Bake - :keywords lang=es: interfaz de línea de comando, aplicación funcional, base de datos, configuración de base de datos, bash script, ingredientes básicos, proyecto, modelo, path, crear código, generación de código, scaffolding, usuarios windows, archivo de configuración, pocos minutos, configurar, iew, shell, modelos, running, mysql + :keywords lang=es: interfaz de línea de comando, aplicación funcional, base de datos, configuración de base de datos, bash script, ingredientes básicos, proyecto, modelo, path, crear código, generación de código, scaffolding, usuarios windows, archivo de configuración, pocos minutos, configurar, view, modelos, running, mysql diff --git a/app/vendor/cakephp/bake/docs/fr/development.rst b/app/vendor/cakephp/bake/docs/fr/development.rst index b4f72c38d..697a6301d 100644 --- a/app/vendor/cakephp/bake/docs/fr/development.rst +++ b/app/vendor/cakephp/bake/docs/fr/development.rst @@ -137,7 +137,7 @@ ressemble à ceci:: /** * Méthode hook pour définir le parseur d'option de cette commande. * - * @see https://book.cakephp.org/4/fr/console-commands/commands.html#defining-arguments-and-options + * @see https://book.cakephp.org/5/fr/console-commands/commands.html#defining-arguments-and-options * @param \Cake\Console\ConsoleOptionParser $parser Le parseur à définir * @return \Cake\Console\ConsoleOptionParser Le parseur construit. */ @@ -153,7 +153,7 @@ ressemble à ceci:: * * @param \Cake\Console\Arguments $args Les arguments de la commande. * @param \Cake\Console\ConsoleIo $io La console il - * @return null|void|int Le code de sortie ou null pour un succès + * @return int|null|void Le code de sortie ou null pour un succès */ public function execute(Arguments $args, ConsoleIo $io) { @@ -181,7 +181,7 @@ ressemble à ceci:: /** * Méthode hook pour définir le parseur d'option de cette commande. * - * @see https://book.cakephp.org/4/fr/console-commands/commands.html#defining-arguments-and-options + * @see https://book.cakephp.org/5/fr/console-commands/commands.html#defining-arguments-and-options * @param \Cake\Console\ConsoleOptionParser $parser Le parseur à définir * @return \Cake\Console\ConsoleOptionParser Le parseur construit. */ @@ -197,7 +197,7 @@ ressemble à ceci:: * * @param \Cake\Console\Arguments $args Les arguments de la commande. * @param \Cake\Console\ConsoleIo $io La console io - * @return null|void|int Le code de sortie ou null pour un succès + * @return int|null|void Le code de sortie ou null pour un succès */ public function execute(Arguments $args, ConsoleIo $io) { diff --git a/app/vendor/cakephp/bake/docs/fr/usage.rst b/app/vendor/cakephp/bake/docs/fr/usage.rst index 79f9f4957..2a6e3d482 100644 --- a/app/vendor/cakephp/bake/docs/fr/usage.rst +++ b/app/vendor/cakephp/bake/docs/fr/usage.rst @@ -62,4 +62,4 @@ theme bake `. .. meta:: :title lang=fr: Génération de Code avec Bake - :keywords lang=fr: interface ligne de commande,application fonctionnelle,base de données,configuration base de données,bash script,ingredients basiques,project,model,path path,génération de code,scaffolding,windows users,configuration file,few minutes,config,iew,shell,models,running,mysql + :keywords lang=fr: interface ligne de commande,application fonctionnelle,base de données,configuration base de données,bash script,ingredients basiques,project,model,path path,génération de code,scaffolding,windows users,configuration file,few minutes,config,view,models,running,mysql diff --git a/app/vendor/cakephp/bake/docs/ja/usage.rst b/app/vendor/cakephp/bake/docs/ja/usage.rst index d9470e6c3..9912102ef 100644 --- a/app/vendor/cakephp/bake/docs/ja/usage.rst +++ b/app/vendor/cakephp/bake/docs/ja/usage.rst @@ -43,8 +43,6 @@ Windows システムの場合、 ``bin\cake bake`` を試してみてくださ - migration_snapshot - model - plugin - - shell - - shell-helper - template - test @@ -99,4 +97,4 @@ Bake テーマオプション .. meta:: :title lang=ja: Code Generation with Bake - :keywords lang=ja: command line interface,functional application,database,database configuration,bash script,basic ingredients,project,model,path path,code generation,scaffolding,windows users,configuration file,few minutes,config,iew,shell,models,running,mysql + :keywords lang=ja: command line interface,functional application,database,database configuration,bash script,basic ingredients,project,model,path path,code generation,scaffolding,windows users,configuration file,few minutes,config,view,models,running,mysql diff --git a/app/vendor/cakephp/bake/docs/pt/usage.rst b/app/vendor/cakephp/bake/docs/pt/usage.rst index 7d513a0f6..f8cf8b4aa 100644 --- a/app/vendor/cakephp/bake/docs/pt/usage.rst +++ b/app/vendor/cakephp/bake/docs/pt/usage.rst @@ -44,8 +44,6 @@ Para ver as opções disponíveis no Bake digite:: - bake model - bake model all - bake plugin - - bake shell - - bake shell_helper - bake task - bake template - bake template all @@ -104,7 +102,7 @@ disponíveis usando a opção ``--help``:: Omitting all arguments and options will list the table names you can generate models for. - + Temas para o Bake ================= diff --git a/app/vendor/cakephp/bake/docs/ru/usage.rst b/app/vendor/cakephp/bake/docs/ru/usage.rst index 8cfd72b85..128936141 100644 --- a/app/vendor/cakephp/bake/docs/ru/usage.rst +++ b/app/vendor/cakephp/bake/docs/ru/usage.rst @@ -46,8 +46,6 @@ - model - plugin - seed - - shell - - shell_helper - task - template - test @@ -94,8 +92,6 @@ and testing setup for a new plugin. Can create plugins in any of your bootstrapped plugin paths. seed Bake seed class. - shell Bake a shell class file. - shell_helper Bake a shell_helper class file. task Bake a task class file. template Bake views for a controller, using built-in or custom templates. @@ -128,4 +124,4 @@ .. meta:: :title lang=ru: Генерация кода с помощью Bake - :keywords lang=en: command line interface,functional application,database,database configuration,bash script,basic ingredients,project,model,path path,code generation,scaffolding,windows users,configuration file,few minutes,config,iew,shell,models,running,mysql + :keywords lang=en: command line interface,functional application,database,database configuration,bash script,basic ingredients,project,model,path path,code generation,scaffolding,windows users,configuration file,few minutes,config,view,models,running,mysql diff --git a/app/vendor/cakephp/bake/phpcs.xml b/app/vendor/cakephp/bake/phpcs.xml index f52106709..d17484779 100644 --- a/app/vendor/cakephp/bake/phpcs.xml +++ b/app/vendor/cakephp/bake/phpcs.xml @@ -1,7 +1,7 @@ - + src/ tests/ @@ -9,6 +9,5 @@ */comparisons/* - /tests/test_app/tests/ - /tests/test_app/Plugin/TestBake/ + tests/test_app/* diff --git a/app/vendor/cakephp/bake/phpstan-baseline.neon b/app/vendor/cakephp/bake/phpstan-baseline.neon new file mode 100644 index 000000000..e45840cfa --- /dev/null +++ b/app/vendor/cakephp/bake/phpstan-baseline.neon @@ -0,0 +1,19 @@ +parameters: + ignoreErrors: + - + message: '#^Method Bake\\BakePlugin\:\:bootstrap\(\) has parameter \$app with generic interface Cake\\Core\\PluginApplicationInterface but does not specify its types\: TSubject$#' + identifier: missingType.generics + count: 1 + path: src/BakePlugin.php + + - + message: '#^Instanceof between mixed and Cake\\Chronos\\Chronos will always evaluate to false\.$#' + identifier: instanceof.alwaysFalse + count: 1 + path: src/Command/FixtureCommand.php + + - + message: '#^Dead catch \- UnexpectedValueException is never thrown in the try block\.$#' + identifier: catch.neverThrown + count: 1 + path: src/Command/TestCommand.php diff --git a/app/vendor/cakephp/bake/phpstan.neon b/app/vendor/cakephp/bake/phpstan.neon index ac6727fb6..32434594a 100644 --- a/app/vendor/cakephp/bake/phpstan.neon +++ b/app/vendor/cakephp/bake/phpstan.neon @@ -1,7 +1,11 @@ +includes: + - phpstan-baseline.neon + parameters: level: 6 - checkMissingIterableValueType: false paths: - src/ bootstrapFiles: - tests/bootstrap.php + ignoreErrors: + - identifier: missingType.iterableValue diff --git a/app/vendor/cakephp/bake/psalm-baseline.xml b/app/vendor/cakephp/bake/psalm-baseline.xml deleted file mode 100644 index a49882bc1..000000000 --- a/app/vendor/cakephp/bake/psalm-baseline.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - new Filesystem() - - - findRecursive - - - - - new Filesystem() - - - find - - - diff --git a/app/vendor/cakephp/bake/psalm.xml b/app/vendor/cakephp/bake/psalm.xml deleted file mode 100644 index 8f0d13b5c..000000000 --- a/app/vendor/cakephp/bake/psalm.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - diff --git a/app/vendor/cakephp/bake/src/BakePlugin.php b/app/vendor/cakephp/bake/src/BakePlugin.php new file mode 100644 index 000000000..0b02eaad3 --- /dev/null +++ b/app/vendor/cakephp/bake/src/BakePlugin.php @@ -0,0 +1,159 @@ +getPlugins()->has('Cake/TwigView')) { + $app->addPlugin('Cake/TwigView'); + } + } + + /** + * Define the console commands for an application. + * + * @param \Cake\Console\CommandCollection $commands The CommandCollection to add commands into. + * @return \Cake\Console\CommandCollection The updated collection. + */ + public function console(CommandCollection $commands): CommandCollection + { + // Add commands in plugins and app. + $commands = $this->discoverCommands($commands); + + // Add entry command to handle entry point and backwards compat. + $commands->add(EntryCommand::defaultName(), EntryCommand::class); + + return $commands; + } + + /** + * Scan plugins and application to find commands that are intended + * to be used with bake. + * + * Non-Abstract commands extending `Bake\Command\BakeCommand` are included. + * Plugins are scanned in the order they are listed in `Plugin::loaded()` + * + * @param \Cake\Console\CommandCollection $commands The CommandCollection to add commands into. + * @return \Cake\Console\CommandCollection The updated collection. + */ + protected function discoverCommands(CommandCollection $commands): CommandCollection + { + foreach (Plugin::getCollection()->with('console') as $plugin) { + $namespace = str_replace('/', '\\', $plugin->getName()); + $pluginPath = $plugin->getClassPath(); + + $found = $this->findInPath($namespace, $pluginPath); + if (count($found)) { + $commands->addMany($found); + } + } + + $found = $this->findInPath(Configure::read('App.namespace'), APP); + if (count($found)) { + $commands->addMany($found); + } + + return $commands; + } + + /** + * Search a path for commands. + * + * @param string $namespace The namespace classes are expected to be in. + * @param string $path The path to look in. + * @return array + * @phpstan-return array> + */ + protected function findInPath(string $namespace, string $path): array + { + $hasSubfolder = false; + $path .= 'Command/'; + $namespace .= '\Command\\'; + + if (file_exists($path . 'Bake/')) { + $hasSubfolder = true; + $path .= 'Bake/'; + $namespace .= 'Bake\\'; + } elseif (!file_exists($path)) { + return []; + } + + $iterator = new DirectoryIterator($path); + $candidates = []; + foreach ($iterator as $item) { + if ($item->isDot() || $item->isDir()) { + continue; + } + $class = $namespace . $item->getBasename('.php'); + + if (!$hasSubfolder) { + try { + $reflection = new ReflectionClass($class); + } catch (ReflectionException) { + continue; + } + if (!$reflection->isInstantiable() || !$reflection->isSubclassOf(BakeCommand::class)) { + continue; + } + } + + /** @var class-string<\Bake\Command\BakeCommand> $class */ + $candidates[$class::defaultName()] = $class; + } + + return $candidates; + } +} diff --git a/app/vendor/cakephp/bake/src/CodeGen/ClassBuilder.php b/app/vendor/cakephp/bake/src/CodeGen/ClassBuilder.php index b543127c0..1cf858b1c 100644 --- a/app/vendor/cakephp/bake/src/CodeGen/ClassBuilder.php +++ b/app/vendor/cakephp/bake/src/CodeGen/ClassBuilder.php @@ -21,7 +21,7 @@ class ClassBuilder /** * @var \Bake\CodeGen\ParsedClass|null */ - protected $parsedClass; + protected ?ParsedClass $parsedClass; /** * @param \Bake\CodeGen\ParsedClass $parsedClass Parsed class it already exists diff --git a/app/vendor/cakephp/bake/src/CodeGen/CodeParser.php b/app/vendor/cakephp/bake/src/CodeGen/CodeParser.php index 2293fb021..c48c8eecb 100644 --- a/app/vendor/cakephp/bake/src/CodeGen/CodeParser.php +++ b/app/vendor/cakephp/bake/src/CodeGen/CodeParser.php @@ -17,17 +17,19 @@ namespace Bake\CodeGen; use PhpParser\Error; -use PhpParser\Lexer\Emulative; use PhpParser\Node; use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\GroupUse; use PhpParser\Node\Stmt\Namespace_; use PhpParser\Node\Stmt\Use_; -use PhpParser\Node\Stmt\UseUse; +use PhpParser\Node\UseItem; use PhpParser\NodeAbstract; use PhpParser\NodeTraverser; +use PhpParser\NodeVisitor; use PhpParser\NodeVisitorAbstract; +use PhpParser\Parser; use PhpParser\ParserFactory; +use PhpParser\PhpVersion; /** * @internal @@ -42,34 +44,30 @@ class CodeParser extends NodeVisitorAbstract /** * @var \PhpParser\Parser */ - protected $parser; + protected Parser $parser; /** * @var \PhpParser\NodeTraverser */ - protected $traverser; + protected NodeTraverser $traverser; /** * @var string */ - protected $fileText = ''; + protected string $fileText = ''; /** * @var array */ - protected $parsed = []; + protected array $parsed = []; /** * Constructor */ public function __construct() { - $this->parser = (new ParserFactory())->create( - ParserFactory::PREFER_PHP7, - new Emulative([ - 'usedAttributes' => ['comments', 'startLine', 'endLine', 'startFilePos', 'endFilePos'], - ]) - ); + $version = PhpVersion::fromComponents(8, 1); + $this->parser = (new ParserFactory())->createForVersion($version); $this->traverser = new NodeTraverser(); $this->traverser->addVisitor($this); } @@ -97,7 +95,7 @@ public function parseFile(string $code): ?ParsedFile $this->parsed['imports']['class'], $this->parsed['imports']['function'], $this->parsed['imports']['const'], - $this->parsed['class'] + $this->parsed['class'], ); } @@ -136,7 +134,11 @@ public function enterNode(Node $node) throw new ParseException('Multiple use statements per line are not supported, update your file'); } - [$alias, $target] = $this->normalizeUse(current($node->uses)); + if ($node->uses === []) { + throw new ParseException('Use statement without uses!'); + } + + [$alias, $target] = $this->normalizeUse($node->uses[0]); switch ($node->type) { case Use_::TYPE_NORMAL: $this->parsed['imports']['class'][$alias] = $target; @@ -149,7 +151,7 @@ public function enterNode(Node $node) break; } - return NodeTraverser::DONT_TRAVERSE_CHILDREN; + return NodeVisitor::DONT_TRAVERSE_CHILDREN; } if ($node instanceof GroupUse) { @@ -199,10 +201,10 @@ public function enterNode(Node $node) $implements, $constants, $properties, - $methods + $methods, ); - return NodeTraverser::DONT_TRAVERSE_CHILDREN; + return NodeVisitor::DONT_TRAVERSE_CHILDREN; } return null; @@ -229,11 +231,11 @@ protected function getNodeCode(NodeAbstract $node): string } /** - * @param \PhpParser\Node\Stmt\UseUse $use Use node + * @param \PhpParser\Node\UseItem $use Use item * @param string|null $prefix Group use prefix * @return array{string, string} */ - protected function normalizeUse(UseUse $use, ?string $prefix = null): array + protected function normalizeUse(UseItem $use, ?string $prefix = null): array { $name = (string)$use->name; if ($prefix) { diff --git a/app/vendor/cakephp/bake/src/CodeGen/FileBuilder.php b/app/vendor/cakephp/bake/src/CodeGen/FileBuilder.php index 74c77a7aa..1aee4e15d 100644 --- a/app/vendor/cakephp/bake/src/CodeGen/FileBuilder.php +++ b/app/vendor/cakephp/bake/src/CodeGen/FileBuilder.php @@ -23,22 +23,22 @@ class FileBuilder /** * @var \Cake\Console\ConsoleIo */ - protected $io; + protected ConsoleIo $io; /** * @var string */ - protected $namespace; + protected string $namespace; /** * @var \Bake\CodeGen\ParsedFile|null */ - protected $parsedFile; + protected ?ParsedFile $parsedFile; /** * @var \Bake\CodeGen\ClassBuilder */ - protected $classBuilder; + protected ClassBuilder $classBuilder; /** * @param \Cake\Console\ConsoleIo $io Console io @@ -51,7 +51,7 @@ public function __construct(ConsoleIo $io, string $namespace, ?ParsedFile $parse throw new ParseException(sprintf( 'Existing namespace `%s` does not match expected namespace `%s`, cannot update existing file', $parsedFile->namespace, - $namespace + $namespace, )); } diff --git a/app/vendor/cakephp/bake/src/CodeGen/ImportHelper.php b/app/vendor/cakephp/bake/src/CodeGen/ImportHelper.php index 576b42c7b..3b786ffd8 100644 --- a/app/vendor/cakephp/bake/src/CodeGen/ImportHelper.php +++ b/app/vendor/cakephp/bake/src/CodeGen/ImportHelper.php @@ -61,7 +61,7 @@ public static function merge(array $existing, array $imports, ?ConsoleIo $io = n if ($io) { $io->warning(sprintf( 'Import `%s` conflicts with existing import, discarding.', - $class + $class, )); } continue; @@ -72,7 +72,7 @@ public static function merge(array $existing, array $imports, ?ConsoleIo $io = n if ($io) { $io->warning(sprintf( 'Import `%s` conflicts with existing import, discarding.', - $class + $class, )); } continue; diff --git a/app/vendor/cakephp/bake/src/CodeGen/ParsedClass.php b/app/vendor/cakephp/bake/src/CodeGen/ParsedClass.php index b7bda0737..dcb9ba9d3 100644 --- a/app/vendor/cakephp/bake/src/CodeGen/ParsedClass.php +++ b/app/vendor/cakephp/bake/src/CodeGen/ParsedClass.php @@ -24,27 +24,27 @@ class ParsedClass /** * @var string */ - public $name; + public string $name; /** * @var array */ - public $implements; + public array $implements; /** * @var array */ - public $constants; + public array $constants; /** * @var array */ - public $properties; + public array $properties; /** * @var array */ - public $methods; + public array $methods; /** * @param string $name Class name diff --git a/app/vendor/cakephp/bake/src/CodeGen/ParsedFile.php b/app/vendor/cakephp/bake/src/CodeGen/ParsedFile.php index f00521710..2abed81fd 100644 --- a/app/vendor/cakephp/bake/src/CodeGen/ParsedFile.php +++ b/app/vendor/cakephp/bake/src/CodeGen/ParsedFile.php @@ -24,27 +24,27 @@ class ParsedFile /** * @var string */ - public $namespace; + public string $namespace; /** * @var array */ - public $classImports; + public array $classImports; /** * @var array */ - public $functionImports; + public array $functionImports; /** * @var array */ - public $constImports; + public array $constImports; /** * @var \Bake\CodeGen\ParsedClass */ - public $class; + public ParsedClass $class; /** * @param string $namespace Namespace @@ -58,7 +58,7 @@ public function __construct( array $classImports, array $functionImports, array $constImports, - ParsedClass $class + ParsedClass $class, ) { $this->namespace = $namespace; $this->classImports = $classImports; diff --git a/app/vendor/cakephp/bake/src/Command/AllCommand.php b/app/vendor/cakephp/bake/src/Command/AllCommand.php index 785f46534..0c0465021 100644 --- a/app/vendor/cakephp/bake/src/Command/AllCommand.php +++ b/app/vendor/cakephp/bake/src/Command/AllCommand.php @@ -21,6 +21,7 @@ use Cake\Console\ConsoleIo; use Cake\Console\ConsoleOptionParser; use Cake\Datasource\ConnectionManager; +use Throwable; /** * Command for `bake all` @@ -30,9 +31,9 @@ class AllCommand extends BakeCommand /** * All commands to call. * - * @var string[] + * @var array */ - protected $commands = [ + protected array $commands = [ ModelCommand::class, ControllerCommand::class, TemplateCommand::class, @@ -49,7 +50,7 @@ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionPar $parser = $this->_setCommonOptions($parser); $parser = $parser->setDescription( - 'Generate the model, controller, template, tests and fixture for a table.' + 'Generate the model, controller, template, tests and fixture for a table.', )->addArgument('name', [ 'help' => 'Name of the table to generate code for.', ])->addOption('everything', [ @@ -83,20 +84,21 @@ public function execute(Arguments $args, ConsoleIo $io): ?int /** @var \Cake\Database\Connection $connection */ $connection = ConnectionManager::get($this->connection); $scanner = new TableScanner($connection); - if (empty($name) && !$args->getOption('everything')) { + $tables = $scanner->removeShadowTranslationTables($scanner->listUnskipped()); + + if (!$name && !$args->getOption('everything')) { $io->out('Choose a table to generate from the following:'); - foreach ($scanner->listUnskipped() as $table) { + foreach ($tables as $table) { $io->out('- ' . $this->_camelize($table)); } return static::CODE_SUCCESS; } - if ($args->getOption('everything')) { - $tables = $scanner->listUnskipped(); - } else { + if (!$args->getOption('everything')) { $tables = [$name]; } + $errors = 0; foreach ($this->commands as $commandName) { /** @var \Cake\Command\Command $command */ $command = new $commandName(); @@ -111,13 +113,29 @@ public function execute(Arguments $args, ConsoleIo $io): ?int } foreach ($tables as $table) { - $subArgs = new Arguments([$table], $options, ['name']); - $command->execute($subArgs, $io); + $parser = $command->getOptionParser(); + $subArgs = new Arguments([$table], $options, $parser->argumentNames()); + + try { + $command->execute($subArgs, $io); + } catch (Throwable $e) { + if (!$args->getOption('everything') || !$args->getOption('force')) { + throw $e; + } + + $message = sprintf('Error generating %s for %s: %s', $commandName, $table, $e->getMessage()); + $io->err('' . $message . ''); + $errors++; + } } } - $io->out('Bake All complete.', 1, ConsoleIo::NORMAL); + if ($errors) { + $io->out(sprintf('Bake All completed, but with %s errors.', $errors), 1, ConsoleIo::NORMAL); + } else { + $io->out('Bake All complete.', 1, ConsoleIo::NORMAL); + } - return static::CODE_SUCCESS; + return $errors ? static::CODE_ERROR : static::CODE_SUCCESS; } } diff --git a/app/vendor/cakephp/bake/src/Command/BakeCommand.php b/app/vendor/cakephp/bake/src/Command/BakeCommand.php index 06e84064a..930ac6b95 100644 --- a/app/vendor/cakephp/bake/src/Command/BakeCommand.php +++ b/app/vendor/cakephp/bake/src/Command/BakeCommand.php @@ -28,6 +28,7 @@ use Cake\Event\Event; use Cake\Event\EventManager; use InvalidArgumentException; +use function Cake\Core\pluginSplit; /** * Base class for commands that bake can use. @@ -45,7 +46,7 @@ abstract class BakeCommand extends Command * * @var string */ - protected $pathFragment; + protected string $pathFragment; /** * Get the command name. @@ -137,7 +138,7 @@ public function getTemplatePath(Arguments $args, ?string $container = null): str if (empty($paths)) { throw new InvalidArgumentException( 'Could not read template paths. ' . - 'Ensure `App.paths.templates` is defined in your application configuration.' + 'Ensure `App.paths.templates` is defined in your application configuration.', ); } $path = $paths[0]; @@ -220,7 +221,7 @@ protected function parseFile(string $path): ?ParsedFile * @param string $path The path to create the file at * @param string $contents The contents to put into the file * @param bool $forceOverwrite Whether the file should be overwritten without prompting the user - * @param bool $skipIfUnchnged Skip writing output if the contents match existing file + * @param bool $skipIfUnchanged Skip writing output if the contents match existing file * @return bool True if successful, false otherwise * @throws \Cake\Console\Exception\StopException When `q` is given as an answer * to whether a file should be overwritten. @@ -230,9 +231,9 @@ protected function writeFile( string $path, string $contents, bool $forceOverwrite = false, - bool $skipIfUnchnged = true + bool $skipIfUnchanged = true, ): bool { - if ($skipIfUnchnged && file_exists($path) && file_get_contents($path) === $contents) { + if ($skipIfUnchanged && file_exists($path) && file_get_contents($path) === $contents) { $io->info("Skipping update to `{$path}`. It already exists and would not change."); return true; diff --git a/app/vendor/cakephp/bake/src/Command/BehaviorCommand.php b/app/vendor/cakephp/bake/src/Command/BehaviorCommand.php index c7af462f9..40968e9ea 100644 --- a/app/vendor/cakephp/bake/src/Command/BehaviorCommand.php +++ b/app/vendor/cakephp/bake/src/Command/BehaviorCommand.php @@ -26,7 +26,7 @@ class BehaviorCommand extends SimpleBakeCommand * * @var string */ - public $pathFragment = 'Model/Behavior/'; + public string $pathFragment = 'Model/Behavior/'; /** * @inheritDoc diff --git a/app/vendor/cakephp/bake/src/Command/CellCommand.php b/app/vendor/cakephp/bake/src/Command/CellCommand.php index b77be780f..657e738b6 100644 --- a/app/vendor/cakephp/bake/src/Command/CellCommand.php +++ b/app/vendor/cakephp/bake/src/Command/CellCommand.php @@ -31,7 +31,7 @@ class CellCommand extends SimpleBakeCommand * * @var string */ - public $pathFragment = 'View/Cell/'; + public string $pathFragment = 'View/Cell/'; /** * @inheritDoc diff --git a/app/vendor/cakephp/bake/src/Command/CommandCommand.php b/app/vendor/cakephp/bake/src/Command/CommandCommand.php index ea5cabd8e..580508e19 100644 --- a/app/vendor/cakephp/bake/src/Command/CommandCommand.php +++ b/app/vendor/cakephp/bake/src/Command/CommandCommand.php @@ -16,6 +16,9 @@ */ namespace Bake\Command; +use Cake\Console\Arguments; +use Cake\Utility\Inflector; + /** * Console Command generator. */ @@ -26,7 +29,7 @@ class CommandCommand extends SimpleBakeCommand * * @var string */ - public $pathFragment = 'Command/'; + public string $pathFragment = 'Command/'; /** * @inheritDoc @@ -51,4 +54,24 @@ public function template(): string { return 'Bake.Command/command'; } + + /** + * Get template data. + * + * @param \Cake\Console\Arguments $arguments Arguments object. + * @return array + * @phpstan-return array + */ + public function templateData(Arguments $arguments): array + { + $data = parent::templateData($arguments); + + $data['command_name'] = Inflector::underscore(str_replace( + '.', + ' ', + $arguments->getArgument('name'), + )); + + return $data; + } } diff --git a/app/vendor/cakephp/bake/src/Command/CommandHelperCommand.php b/app/vendor/cakephp/bake/src/Command/CommandHelperCommand.php index 84f8d63bd..542ac1809 100644 --- a/app/vendor/cakephp/bake/src/Command/CommandHelperCommand.php +++ b/app/vendor/cakephp/bake/src/Command/CommandHelperCommand.php @@ -26,7 +26,7 @@ class CommandHelperCommand extends SimpleBakeCommand * * @var string */ - public $pathFragment = 'Command/Helper/'; + public string $pathFragment = 'Command/Helper/'; /** * @inheritDoc diff --git a/app/vendor/cakephp/bake/src/Command/ComponentCommand.php b/app/vendor/cakephp/bake/src/Command/ComponentCommand.php index 452800fde..3adace9b7 100644 --- a/app/vendor/cakephp/bake/src/Command/ComponentCommand.php +++ b/app/vendor/cakephp/bake/src/Command/ComponentCommand.php @@ -26,7 +26,7 @@ class ComponentCommand extends SimpleBakeCommand * * @var string */ - public $pathFragment = 'Controller/Component/'; + public string $pathFragment = 'Controller/Component/'; /** * @inheritDoc diff --git a/app/vendor/cakephp/bake/src/Command/ControllerAllCommand.php b/app/vendor/cakephp/bake/src/Command/ControllerAllCommand.php index b482fcce6..113e90061 100644 --- a/app/vendor/cakephp/bake/src/Command/ControllerAllCommand.php +++ b/app/vendor/cakephp/bake/src/Command/ControllerAllCommand.php @@ -33,7 +33,7 @@ class ControllerAllCommand extends BakeCommand /** * @var \Bake\Command\ControllerCommand */ - protected $controllerCommand; + protected ControllerCommand $controllerCommand; /** * @inheritDoc diff --git a/app/vendor/cakephp/bake/src/Command/ControllerCommand.php b/app/vendor/cakephp/bake/src/Command/ControllerCommand.php index 2b4047f70..c78bc5df4 100644 --- a/app/vendor/cakephp/bake/src/Command/ControllerCommand.php +++ b/app/vendor/cakephp/bake/src/Command/ControllerCommand.php @@ -21,6 +21,7 @@ use Cake\Console\ConsoleIo; use Cake\Console\ConsoleOptionParser; use Cake\Core\Configure; +use Cake\Core\Plugin; use Cake\Datasource\ConnectionManager; /** @@ -33,7 +34,7 @@ class ControllerCommand extends BakeCommand * * @var string */ - public $pathFragment = 'Controller/'; + public string $pathFragment = 'Controller/'; /** * Execute the command. @@ -86,6 +87,9 @@ public function bake(string $controllerName, Arguments $args, ConsoleIo $io): vo $actions = array_map('trim', explode(',', $args->getOption('actions'))); $actions = array_filter($actions); } + if (!$args->getOption('actions') && Plugin::isLoaded('Authentication') && $controllerName === 'Users') { + $actions[] = 'login'; + } $helpers = $this->getHelpers($args); $components = $this->getComponents($args); @@ -146,7 +150,7 @@ public function bake(string $controllerName, Arguments $args, ConsoleIo $io): vo 'pluralName', 'prefix', 'singularHumanName', - 'singularName' + 'singularName', ); $data['name'] = $controllerName; @@ -202,7 +206,7 @@ public function bakeTest(string $className, Arguments $args, ConsoleIo $io): voi $testArgs = new Arguments( ['controller', $className], $args->getOptions(), - ['type', 'name'] + ['type', 'name'], ); $test->execute($testArgs, $io); } @@ -211,7 +215,7 @@ public function bakeTest(string $className, Arguments $args, ConsoleIo $io): voi * Get the list of components for the controller. * * @param \Cake\Console\Arguments $args The console arguments - * @return string[] + * @return array */ public function getComponents(Arguments $args): array { @@ -219,6 +223,10 @@ public function getComponents(Arguments $args): array if ($args->getOption('components')) { $components = explode(',', $args->getOption('components')); $components = array_values(array_filter(array_map('trim', $components))); + } else { + if (Plugin::isLoaded('Authorization')) { + $components[] = 'Authorization.Authorization'; + } } return $components; @@ -228,7 +236,7 @@ public function getComponents(Arguments $args): array * Get the list of helpers for the controller. * * @param \Cake\Console\Arguments $args The console arguments - * @return string[] + * @return array */ public function getHelpers(Arguments $args): array { @@ -251,7 +259,7 @@ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionPar { $parser = $this->_setCommonOptions($parser); $parser->setDescription( - 'Bake a controller skeleton.' + 'Bake a controller skeleton.', )->addArgument('name', [ 'help' => 'Name of the controller to bake (without the `Controller` suffix). ' . 'You can use Plugin.name to bake controllers into plugins.', diff --git a/app/vendor/cakephp/bake/src/Command/EntryCommand.php b/app/vendor/cakephp/bake/src/Command/EntryCommand.php index c549880e9..87b9dd759 100644 --- a/app/vendor/cakephp/bake/src/Command/EntryCommand.php +++ b/app/vendor/cakephp/bake/src/Command/EntryCommand.php @@ -16,7 +16,6 @@ */ namespace Bake\Command; -use Bake\Shell\Task\BakeTask; use Cake\Command\Command; use Cake\Console\Arguments; use Cake\Console\Command\HelpCommand; @@ -25,10 +24,6 @@ use Cake\Console\ConsoleIo; use Cake\Console\ConsoleOptionParser; use Cake\Console\Exception\ConsoleException; -use Cake\Console\Shell; -use Cake\Core\Configure; -use Cake\Core\Plugin as CorePlugin; -use Cake\Utility\Inflector; /** * Command that provides help and an entry point to bake tools. @@ -40,14 +35,14 @@ class EntryCommand extends Command implements CommandCollectionAwareInterface * * @var \Cake\Console\CommandCollection */ - protected $commands; + protected CommandCollection $commands; /** * The HelpCommand to get help. * * @var \Cake\Console\Command\HelpCommand */ - protected $help; + protected HelpCommand $help; /** * @inheritDoc @@ -68,8 +63,7 @@ public function setCommandCollection(CommandCollection $commands): void /** * Run the command. * - * Override the run() method so that we can splice in dynamic - * subcommand handling for legacy tasks. + * Override the run() method for special handling of the `--help` option. * * @param array $argv Arguments from the CLI environment. * @param \Cake\Console\ConsoleIo $io The console io @@ -85,7 +79,7 @@ public function run(array $argv, ConsoleIo $io): ?int $args = new Arguments( $arguments, $options, - $parser->argumentNames() + $parser->argumentNames(), ); } catch (ConsoleException $e) { $io->err('Error: ' . $e->getMessage()); @@ -107,9 +101,6 @@ public function run(array $argv, ConsoleIo $io): ?int /** * Execute the command. * - * This command acts as a catch-all for legacy tasks that may - * be defined in the application or plugins. - * * @param \Cake\Console\Arguments $args The command arguments. * @param \Cake\Console\ConsoleIo $io The console io * @return int|null The exit code or null for success @@ -118,34 +109,10 @@ public function execute(Arguments $args, ConsoleIo $io): ?int { if ($args->hasArgumentAt(0)) { $name = $args->getArgumentAt(0); - $task = $this->createTask($name, $io); - if ($task) { - $argList = $args->getArguments(); - - // Remove command name. - array_shift($argList); - foreach ($args->getOptions() as $key => $value) { - if ($value === false) { - continue; - } elseif ($value === true) { - $argList[] = '--' . $key; - } else { - $argList[] = '--' . $key; - $argList[] = $value; - } - } - - $result = $task->runCommand($argList); - if ($result === false) { - return static::CODE_ERROR; - } - if ($result === true) { - return static::CODE_SUCCESS; - } - - return $result; - } - $io->err("Could not find a task named `{$name}`."); + $io->err( + "Could not find bake command named `$name`." + . ' Run `bake --help` to get a list of commands.', + ); return static::CODE_ERROR; } @@ -154,43 +121,6 @@ public function execute(Arguments $args, ConsoleIo $io): ?int return static::CODE_ERROR; } - /** - * Find and create a Shell based BakeTask - * - * @param string $name The task name. - * @param \Cake\Console\ConsoleIo $io The console io. - * @return \Cake\Console\Shell|null - */ - protected function createTask(string $name, ConsoleIo $io): ?Shell - { - $found = false; - $name = Inflector::camelize($name); - $factory = function ($className, $io) { - $task = new $className($io); - $task->setRootName('cake bake'); - - return $task; - }; - - // Look in each plugin for the requested task - foreach (CorePlugin::loaded() as $plugin) { - $namespace = str_replace('/', '\\', $plugin); - $candidate = $namespace . '\Shell\Task\\' . $name . 'Task'; - if (class_exists($candidate) && is_subclass_of($candidate, BakeTask::class)) { - return $factory($candidate, $io); - } - } - - // Try the app as well - $namespace = Configure::read('App.namespace'); - $candidate = $namespace . '\Shell\Task\\' . $name . 'Task'; - if (class_exists($candidate) && is_subclass_of($candidate, BakeTask::class)) { - return $factory($candidate, $io); - } - - return null; - } - /** * Gets the option parser instance and configures it. * @@ -200,15 +130,13 @@ protected function createTask(string $name, ConsoleIo $io): ?Shell public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser { $this->help = new HelpCommand(); - /** @psalm-suppress InaccessibleMethod Protected methods as class based */ $parser = $this->help->buildOptionParser($parser); $parser ->setDescription( 'Bake generates code for your application. Different types of classes can be generated' . ' with the subcommands listed below. For example run bake controller --help' . - ' to learn more about generating a controller.' - ) - ->setEpilog('Older Shell based tasks will not be listed here, but can still be run.'); + ' to learn more about generating a controller.', + ); $commands = []; foreach ($this->commands as $command => $class) { if (substr($command, 0, 4) === 'bake') { diff --git a/app/vendor/cakephp/bake/src/Command/EnumCommand.php b/app/vendor/cakephp/bake/src/Command/EnumCommand.php new file mode 100644 index 000000000..b1f02f039 --- /dev/null +++ b/app/vendor/cakephp/bake/src/Command/EnumCommand.php @@ -0,0 +1,173 @@ + + */ + public function templateData(Arguments $arguments): array + { + $cases = EnumParser::parseCases($arguments->getArgument('cases'), (bool)$arguments->getOption('int')); + $isOfTypeInt = $this->isOfTypeInt($cases); + $backingType = $isOfTypeInt ? 'int' : 'string'; + if ($arguments->getOption('int')) { + if ($cases && !$isOfTypeInt) { + throw new InvalidArgumentException('Cases do not match requested `int` backing type.'); + } + + $backingType = 'int'; + } + + $data = parent::templateData($arguments); + $data['backingType'] = $backingType; + $data['cases'] = $this->formatCases($cases); + + return $data; + } + + /** + * Gets the option parser instance and configures it. + * + * @param \Cake\Console\ConsoleOptionParser $parser The option parser to update. + * @return \Cake\Console\ConsoleOptionParser + */ + public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser + { + $parser = $this->_setCommonOptions($parser); + + $parser->setDescription( + 'Bake backed enums for use in models.', + )->addArgument('name', [ + 'help' => 'Name of the enum to bake. You can use Plugin.name to bake plugin enums.', + 'required' => true, + ])->addArgument('cases', [ + 'help' => 'List of either `one,two` for string or `foo:0,bar:1` for int type.', + ])->addOption('int', [ + 'help' => 'Using backed enums with int instead of string as return type.', + 'boolean' => true, + 'short' => 'i', + ]); + + return $parser; + } + + /** + * @param array $definition + * @return bool + */ + protected function isOfTypeInt(array $definition): bool + { + if (!$definition) { + return false; + } + + foreach ($definition as $value) { + if (!is_int($value)) { + return false; + } + } + + return true; + } + + /** + * @param array $cases + * @return array + */ + protected function formatCases(array $cases): array + { + $formatted = []; + foreach ($cases as $case => $value) { + $case = Inflector::camelize(Inflector::underscore($case)); + if (is_string($value)) { + $value = '\'' . $value . '\''; + } + $formatted[] = 'case ' . $case . ' = ' . $value . ';'; + } + + return $formatted; + } + + /** + * Generate a class stub + * + * @param string $name The class name + * @param \Cake\Console\Arguments $args The console arguments + * @param \Cake\Console\ConsoleIo $io The console io + * @return void + */ + protected function bake(string $name, Arguments $args, ConsoleIo $io): void + { + parent::bake($name, $args, $io); + + $path = $this->getPath($args); + $filename = $path . $name . '.php'; + + // Work around composer caching that classes/files do not exist. + // Check for the file as it might not exist in tests. + if (file_exists($filename)) { + require_once $filename; + } + } +} diff --git a/app/vendor/cakephp/bake/src/Command/FixtureAllCommand.php b/app/vendor/cakephp/bake/src/Command/FixtureAllCommand.php index 9dff89cd0..83de359c3 100644 --- a/app/vendor/cakephp/bake/src/Command/FixtureAllCommand.php +++ b/app/vendor/cakephp/bake/src/Command/FixtureAllCommand.php @@ -49,11 +49,11 @@ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionPar $parser = $this->_setCommonOptions($parser); $parser = $parser->setDescription( - 'Generate all fixtures for use with the test suite.' + 'Generate all fixtures for use with the test suite.', )->addOption('count', [ 'help' => 'When using generated data, the number of records to include in the fixture(s).', 'short' => 'n', - 'default' => 1, + 'default' => '1', ])->addOption('schema', [ 'help' => 'Create a fixture that imports schema, instead of dumping a schema snapshot into the fixture.', 'short' => 's', @@ -86,7 +86,9 @@ public function execute(Arguments $args, ConsoleIo $io): ?int $connection = ConnectionManager::get($args->getOption('connection') ?? 'default'); $scanner = new TableScanner($connection); $fixture = new FixtureCommand(); - foreach ($scanner->listUnskipped() as $table) { + + $tables = $scanner->removeShadowTranslationTables($scanner->listUnskipped()); + foreach ($tables as $table) { $fixtureArgs = new Arguments([$table], $args->getOptions(), ['name']); $fixture->execute($fixtureArgs, $io); } diff --git a/app/vendor/cakephp/bake/src/Command/FixtureCommand.php b/app/vendor/cakephp/bake/src/Command/FixtureCommand.php index 402b092e8..b461cef26 100644 --- a/app/vendor/cakephp/bake/src/Command/FixtureCommand.php +++ b/app/vendor/cakephp/bake/src/Command/FixtureCommand.php @@ -18,16 +18,21 @@ use Bake\Utility\TableScanner; use Brick\VarExporter\VarExporter; +use Cake\Chronos\Chronos; +use Cake\Chronos\ChronosDate; use Cake\Console\Arguments; use Cake\Console\ConsoleIo; use Cake\Console\ConsoleOptionParser; use Cake\Core\Configure; -use Cake\Database\Exception\DatabaseException; +use Cake\Core\Exception\CakeException; use Cake\Database\Schema\TableSchemaInterface; +use Cake\Database\Type\EnumType; +use Cake\Database\TypeFactory; use Cake\Datasource\ConnectionManager; use Cake\Utility\Inflector; use Cake\Utility\Text; use DateTimeInterface; +use ReflectionEnum; /** * Task class for creating and updating fixtures files. @@ -62,7 +67,7 @@ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionPar $parser = $this->_setCommonOptions($parser); $parser = $parser->setDescription( - 'Generate fixtures for use with the test suite. You can use `bake fixture all` to bake all fixtures.' + 'Generate fixtures for use with the test suite. You can use `bake fixture all` to bake all fixtures.', )->addArgument('name', [ 'help' => 'Name of the fixture to bake (without the `Fixture` suffix). ' . 'You can use Plugin.name to bake plugin fixtures.', @@ -71,10 +76,9 @@ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionPar ])->addOption('count', [ 'help' => 'When using generated data, the number of records to include in the fixture(s).', 'short' => 'n', - 'default' => 1, + 'default' => '1', ])->addOption('fields', [ 'help' => 'Create a fixture that includes the deprecated $fields property.', - 'short' => 'f', 'boolean' => true, ])->addOption('schema', [ 'help' => 'Create a fixture that imports schema, instead of dumping a schema snapshot into the fixture.', @@ -159,7 +163,7 @@ protected function bake(string $model, string $useTable, Arguments $args, Consol try { $data = $this->readSchema($model, $useTable); - } catch (DatabaseException $e) { + } catch (CakeException $e) { $this->getTableLocator()->remove($model); $useTable = Inflector::underscore($model); $table = $useTable; @@ -223,7 +227,7 @@ public function validateNames(TableSchemaInterface $schema, ConsoleIo $io): void $io->abort(sprintf( 'Unable to bake model. Table column name must start with a letter or underscore and cannot contain special characters. Found `%s`.', - $column + $column, )); } } @@ -318,7 +322,7 @@ protected function _generateSchema(TableSchemaInterface $table): string * Formats Schema columns from Model Object * * @param array $values options keys(type, null, default, key, length, extra) - * @return string[] Formatted values + * @return array Formatted values */ protected function _values(array $values): array { @@ -382,7 +386,7 @@ protected function _generateRecords(TableSchemaInterface $table, int $recordCoun 0, (int)$fieldInfo['length'] > 2 ? (int)$fieldInfo['length'] - 2 - : (int)$fieldInfo['length'] + : (int)$fieldInfo['length'], ); } } @@ -417,6 +421,33 @@ protected function _generateRecords(TableSchemaInterface $table, int $recordCoun $insert = Text::uuid(); break; } + if (str_starts_with($fieldInfo['type'], 'enum-')) { + $insert = null; + if ($fieldInfo['default'] || $fieldInfo['null'] === false) { + $dbType = TypeFactory::build($fieldInfo['type']); + if ($dbType instanceof EnumType) { + $class = $dbType->getEnumClassName(); + $reflectionEnum = new ReflectionEnum($class); + $backingType = (string)$reflectionEnum->getBackingType(); + + if ($fieldInfo['default'] !== null) { + $insert = $fieldInfo['default']; + if ($backingType === 'int') { + $insert = (int)$insert; + } + } else { + $cases = $reflectionEnum->getCases(); + if ($cases) { + $firstCase = array_shift($cases); + /** @var \BackedEnum $firstValue */ + $firstValue = $firstCase->getValue(); + $insert = $firstValue->value; + } + } + } + } + } + $record[$field] = $insert; } $records[] = $record; @@ -435,9 +466,11 @@ protected function _generateRecords(TableSchemaInterface $table, int $recordCoun protected function _makeRecordString(array $records): string { foreach ($records as &$record) { - array_walk($record, function (&$value) { - if ($value instanceof DateTimeInterface) { + array_walk($record, function (&$value): void { + if ($value instanceof DateTimeInterface || $value instanceof Chronos) { $value = $value->format('Y-m-d H:i:s'); + } elseif ($value instanceof ChronosDate) { + $value = $value->format('Y-m-d'); } }); } diff --git a/app/vendor/cakephp/bake/src/Command/FormCommand.php b/app/vendor/cakephp/bake/src/Command/FormCommand.php index f36229891..af9382591 100644 --- a/app/vendor/cakephp/bake/src/Command/FormCommand.php +++ b/app/vendor/cakephp/bake/src/Command/FormCommand.php @@ -26,7 +26,7 @@ class FormCommand extends SimpleBakeCommand * * @var string */ - public $pathFragment = 'Form/'; + public string $pathFragment = 'Form/'; /** * @inheritDoc diff --git a/app/vendor/cakephp/bake/src/Command/HelperCommand.php b/app/vendor/cakephp/bake/src/Command/HelperCommand.php index 82205f5ab..41a749558 100644 --- a/app/vendor/cakephp/bake/src/Command/HelperCommand.php +++ b/app/vendor/cakephp/bake/src/Command/HelperCommand.php @@ -26,7 +26,7 @@ class HelperCommand extends SimpleBakeCommand * * @var string */ - public $pathFragment = 'View/Helper/'; + public string $pathFragment = 'View/Helper/'; /** * @inheritDoc diff --git a/app/vendor/cakephp/bake/src/Command/MailerCommand.php b/app/vendor/cakephp/bake/src/Command/MailerCommand.php index b96d7337e..beba98758 100644 --- a/app/vendor/cakephp/bake/src/Command/MailerCommand.php +++ b/app/vendor/cakephp/bake/src/Command/MailerCommand.php @@ -29,7 +29,7 @@ class MailerCommand extends SimpleBakeCommand * * @var string */ - public $pathFragment = 'Mailer/'; + public string $pathFragment = 'Mailer/'; /** * @inheritDoc diff --git a/app/vendor/cakephp/bake/src/Command/MiddlewareCommand.php b/app/vendor/cakephp/bake/src/Command/MiddlewareCommand.php index 61b3a0c41..9d376cd45 100644 --- a/app/vendor/cakephp/bake/src/Command/MiddlewareCommand.php +++ b/app/vendor/cakephp/bake/src/Command/MiddlewareCommand.php @@ -26,7 +26,7 @@ class MiddlewareCommand extends SimpleBakeCommand * * @var string */ - public $pathFragment = 'Middleware/'; + public string $pathFragment = 'Middleware/'; /** * @inheritDoc diff --git a/app/vendor/cakephp/bake/src/Command/ModelAllCommand.php b/app/vendor/cakephp/bake/src/Command/ModelAllCommand.php index 8b2eaf27f..a65f696cd 100644 --- a/app/vendor/cakephp/bake/src/Command/ModelAllCommand.php +++ b/app/vendor/cakephp/bake/src/Command/ModelAllCommand.php @@ -33,7 +33,7 @@ class ModelAllCommand extends BakeCommand /** * @var \Bake\Command\ModelCommand */ - protected $modelCommand; + protected ModelCommand $modelCommand; /** * @inheritDoc @@ -83,7 +83,8 @@ public function execute(Arguments $args, ConsoleIo $io): ?int /** @var \Cake\Database\Connection $connection */ $connection = ConnectionManager::get($this->connection); $scanner = new TableScanner($connection); - foreach ($scanner->listUnskipped() as $table) { + $tables = $scanner->removeShadowTranslationTables($scanner->listUnskipped()); + foreach ($tables as $table) { $this->getTableLocator()->clear(); $modelArgs = new Arguments([$table], $args->getOptions(), ['name']); $this->modelCommand->execute($modelArgs, $io); diff --git a/app/vendor/cakephp/bake/src/Command/ModelCommand.php b/app/vendor/cakephp/bake/src/Command/ModelCommand.php index fc9b95ec7..48c45204a 100644 --- a/app/vendor/cakephp/bake/src/Command/ModelCommand.php +++ b/app/vendor/cakephp/bake/src/Command/ModelCommand.php @@ -17,6 +17,7 @@ namespace Bake\Command; use Bake\CodeGen\FileBuilder; +use Bake\Utility\Model\EnumParser; use Bake\Utility\TableScanner; use Cake\Console\Arguments; use Cake\Console\ConsoleIo; @@ -28,9 +29,13 @@ use Cake\Database\Schema\CachedCollection; use Cake\Database\Schema\TableSchema; use Cake\Database\Schema\TableSchemaInterface; +use Cake\Database\Type\EnumType; +use Cake\Database\TypeFactory; use Cake\Datasource\ConnectionManager; use Cake\ORM\Table; use Cake\Utility\Inflector; +use ReflectionEnum; +use function Cake\Core\pluginSplit; /** * Command for generating model files. @@ -42,7 +47,7 @@ class ModelCommand extends BakeCommand * * @var string */ - public $pathFragment = 'Model/'; + public string $pathFragment = 'Model/'; /** * Table prefix @@ -51,14 +56,14 @@ class ModelCommand extends BakeCommand * * @var string */ - public $tablePrefix = ''; + public string $tablePrefix = ''; /** * Holds tables found on connection. * - * @var string[] + * @var array */ - protected $_tables = []; + protected array $_tables = []; /** * Execute the command. @@ -110,6 +115,8 @@ public function bake(string $name, Arguments $args, ConsoleIo $io): void $tableObject = $this->getTableObject($name, $table); $this->validateNames($tableObject->getSchema(), $io); $data = $this->getTableContext($tableObject, $table, $name, $args, $io); + + $this->bakeEnums($tableObject, $data, $args, $io); $this->bakeTable($tableObject, $data, $args, $io); $this->bakeEntity($tableObject, $data, $args, $io); $this->bakeFixture($tableObject->getAlias(), $tableObject->getTable(), $args, $io); @@ -131,7 +138,7 @@ public function validateNames(TableSchemaInterface $schema, ConsoleIo $io): void $io->abort(sprintf( 'Unable to bake model. Table column name must start with a letter or underscore and cannot contain special characters. Found `%s`.', - $column + $column, )); } } @@ -152,7 +159,7 @@ public function getTableContext( string $table, string $name, Arguments $args, - ConsoleIo $io + ConsoleIo $io, ): array { $associations = $this->getAssociations($tableObject, $args, $io); $this->applyAssociations($tableObject, $associations); @@ -167,6 +174,7 @@ public function getTableContext( $behaviors = $this->getBehaviors($tableObject); $connection = $this->connection; $hidden = $this->getHiddenFields($tableObject, $args); + $enumSchema = $this->getEnumDefinitions($tableObject->getSchema()); return compact( 'associations', @@ -180,7 +188,8 @@ public function getTableContext( 'rulesChecker', 'behaviors', 'connection', - 'hidden' + 'hidden', + 'enumSchema', ); } @@ -237,7 +246,7 @@ public function getAssociations(Table $table, Arguments $args, ConsoleIo $io): a if (is_array($primary) && count($primary) > 1) { $io->warning( - 'Bake cannot generate associations for composite primary keys at this time.' + 'Bake cannot generate associations for composite primary keys at this time.', ); return $associations; @@ -247,6 +256,8 @@ public function getAssociations(Table $table, Arguments $args, ConsoleIo $io): a $associations = $this->findHasMany($table, $associations); $associations = $this->findBelongsToMany($table, $associations); + $associations = $this->ensureAliasUniqueness($associations); + return $associations; } @@ -266,6 +277,7 @@ public function applyAssociations(Table $model, array $associations): void if (get_class($model) !== Table::class) { return; } + foreach ($associations as $type => $assocs) { foreach ($assocs as $assoc) { $alias = $assoc['alias']; @@ -340,6 +352,7 @@ public function findBelongsTo(Table $model, array $associations, ?Arguments $arg continue; } + $className = null; if ($fieldName === 'parent_id') { $className = $this->plugin ? $this->plugin . '.' . $model->getAlias() : $model->getAlias(); $assoc = [ @@ -352,7 +365,7 @@ public function findBelongsTo(Table $model, array $associations, ?Arguments $arg if (!$this->getTableLocator()->exists($tmpModelName)) { $this->getTableLocator()->get( $tmpModelName, - ['connection' => ConnectionManager::get($this->connection)] + ['connection' => ConnectionManager::get($this->connection)], ); } $associationTable = $this->getTableLocator()->get($tmpModelName); @@ -366,7 +379,7 @@ public function findBelongsTo(Table $model, array $associations, ?Arguments $arg $allowAliasRelations = $args && $args->getOption('skip-relation-check'); $found = $this->findTableReferencedBy($schema, $fieldName); if ($found) { - $tmpModelName = Inflector::camelize($found); + $className = ($this->plugin ? $this->plugin . '.' : '') . Inflector::camelize($found); } elseif (!$allowAliasRelations) { continue; } @@ -375,6 +388,9 @@ public function findBelongsTo(Table $model, array $associations, ?Arguments $arg 'alias' => $tmpModelName, 'foreignKey' => $fieldName, ]; + if ($className && $className !== $tmpModelName) { + $assoc['className'] = $className; + } if ($schema->getColumn($fieldName)['null'] === false) { $assoc['joinType'] = 'INNER'; } @@ -383,6 +399,7 @@ public function findBelongsTo(Table $model, array $associations, ?Arguments $arg if ($this->plugin && empty($assoc['className'])) { $assoc['className'] = $this->plugin . '.' . $assoc['alias']; } + $associations['belongsTo'][] = $assoc; } @@ -626,7 +643,7 @@ public function findBelongsToMany(Table $model, array $associations): array * @param \Cake\Console\Arguments $args CLI Arguments * @return array|string|null */ - public function getDisplayField(Table $model, Arguments $args) + public function getDisplayField(Table $model, Arguments $args): array|string|null { if ($args->getOption('display-field')) { return (string)$args->getOption('display-field'); @@ -640,7 +657,7 @@ public function getDisplayField(Table $model, Arguments $args) * * @param \Cake\ORM\Table $model The model to introspect. * @param \Cake\Console\Arguments $args CLI Arguments - * @return string[] The columns in the primary key + * @return array The columns in the primary key */ public function getPrimaryKey(Table $model, Arguments $args): array { @@ -729,11 +746,11 @@ public function getEntityPropertySchema(Table $model): array * * @param \Cake\ORM\Table $table The table instance to get fields for. * @param \Cake\Console\Arguments $args CLI Arguments - * @return string[]|false|null Either an array of fields, `false` in + * @return array|false|null Either an array of fields, `false` in * case the no-fields option is used, or `null` if none of the * field options is used. */ - public function getFields(Table $table, Arguments $args) + public function getFields(Table $table, Arguments $args): array|false|null { if ($args->getOption('no-fields')) { return false; @@ -760,7 +777,7 @@ public function getFields(Table $table, Arguments $args) * * @param \Cake\ORM\Table $model The model to introspect. * @param \Cake\Console\Arguments $args CLI Arguments - * @return string[] The columns to make accessible + * @return array The columns to make accessible */ public function getHiddenFields(Table $model, Arguments $args): array { @@ -787,7 +804,7 @@ public function getHiddenFields(Table $model, Arguments $args): array * @param \Cake\Console\Arguments $args CLI Arguments * @return array|false The validation rules. */ - public function getValidation(Table $model, array $associations, Arguments $args) + public function getValidation(Table $model, array $associations, Arguments $args): array|false { if ($args->getOption('no-validation')) { return []; @@ -835,7 +852,7 @@ public function fieldValidation( TableSchemaInterface $schema, string $fieldName, array $metaData, - array $primaryKey + array $primaryKey, ): array { $ignoreFields = ['lft', 'rght', 'created', 'modified', 'updated']; if (in_array($fieldName, $ignoreFields, true)) { @@ -962,7 +979,7 @@ protected function getEmptyMethod(string $fieldName, array $metaData, string $pr return $prefix . 'EmptyDateTime'; } - if (preg_match('/file|image/', $fieldName)) { + if (preg_match('/(^|\s|_|-)(attachment|file|image)$/i', $fieldName)) { return $prefix . 'EmptyFile'; } @@ -983,22 +1000,14 @@ public function getRules(Table $model, array $associations, Arguments $args): ar return []; } $schema = $model->getSchema(); - $fields = $schema->columns(); - if (empty($fields)) { + $schemaFields = $schema->columns(); + if (empty($schemaFields)) { return []; } - $uniqueColumns = ['username', 'login']; - if (in_array($model->getAlias(), ['Users', 'Accounts'])) { - $uniqueColumns[] = 'email'; - } + $uniqueRules = []; + $uniqueConstraintsColumns = []; - $rules = []; - foreach ($fields as $fieldName) { - if (in_array($fieldName, $uniqueColumns, true)) { - $rules[$fieldName] = ['name' => 'isUnique', 'fields' => [$fieldName], 'options' => []]; - } - } foreach ($schema->constraints() as $name) { $constraint = $schema->getConstraint($name); if ($constraint['type'] !== TableSchema::CONSTRAINT_UNIQUE) { @@ -1006,8 +1015,11 @@ public function getRules(Table $model, array $associations, Arguments $args): ar } $options = []; - $fields = $constraint['columns']; - foreach ($fields as $field) { + /** @var array $constraintFields */ + $constraintFields = $constraint['columns']; + $uniqueConstraintsColumns = [...$uniqueConstraintsColumns, ...$constraintFields]; + + foreach ($constraintFields as $field) { if ($schema->isNullable($field)) { $allowMultiple = !ConnectionManager::get($this->connection)->getDriver() instanceof Sqlserver; $options['allowMultipleNulls'] = $allowMultiple; @@ -1015,15 +1027,37 @@ public function getRules(Table $model, array $associations, Arguments $args): ar } } - $rules[$constraint['columns'][0]] = ['name' => 'isUnique', 'fields' => $fields, 'options' => $options]; + $uniqueRules[] = ['name' => 'isUnique', 'fields' => $constraintFields, 'options' => $options]; } + $possiblyUniqueColumns = ['username', 'login']; + if (in_array($model->getAlias(), ['Users', 'Accounts'])) { + $possiblyUniqueColumns[] = 'email'; + } + + $possiblyUniqueRules = []; + foreach ($schemaFields as $field) { + if ( + !in_array($field, $uniqueConstraintsColumns, true) && + in_array($field, $possiblyUniqueColumns, true) + ) { + $possiblyUniqueRules[] = ['name' => 'isUnique', 'fields' => [$field], 'options' => []]; + } + } + + $rules = [...$possiblyUniqueRules, ...$uniqueRules]; + if (empty($associations['belongsTo'])) { return $rules; } foreach ($associations['belongsTo'] as $assoc) { - $rules[$assoc['foreignKey']] = ['name' => 'existsIn', 'extra' => $assoc['alias'], 'options' => []]; + $rules[] = [ + 'name' => 'existsIn', + 'fields' => (array)$assoc['foreignKey'], + 'extra' => $assoc['alias'], + 'options' => [], + ]; } return $rules; @@ -1100,7 +1134,7 @@ public function getCounterCache(Table $model): array * Bake an entity class. * * @param \Cake\ORM\Table $model Model name or object - * @param array $data An array to use to generate the Table + * @param array $data An array to use to generate the Table * @param \Cake\Console\Arguments $args CLI Arguments * @param \Cake\Console\ConsoleIo $io CLI io * @return void @@ -1152,7 +1186,7 @@ public function bakeEntity(Table $model, array $data, Arguments $args, ConsoleIo * Bake a table class. * * @param \Cake\ORM\Table $model Model name or object - * @param array $data An array to use to generate the Table + * @param array $data An array to use to generate the Table * @param \Cake\Console\Arguments $args CLI Arguments * @param \Cake\Console\ConsoleIo $io CLI Arguments * @return void @@ -1194,6 +1228,7 @@ public function bakeTable(Table $model, array $data, Arguments $args, ConsoleIo 'validation' => [], 'rulesChecker' => [], 'behaviors' => [], + 'enums' => $this->enums($model, $entity, $namespace), 'connection' => $this->connection, 'fileBuilder' => new FileBuilder($io, "{$namespace}\Model\Table", $parsedFile), ]; @@ -1216,9 +1251,9 @@ public function bakeTable(Table $model, array $data, Arguments $args, ConsoleIo } /** - * Outputs the a list of possible models or controllers from database + * Outputs the list of possible models or controllers from database * - * @return string[] + * @return array */ public function listAll(): array { @@ -1235,9 +1270,9 @@ public function listAll(): array } /** - * Outputs the a list of unskipped models or controllers from database + * Outputs the list of unskipped models or controllers from database * - * @return string[] + * @return array */ public function listUnskipped(): array { @@ -1277,7 +1312,7 @@ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionPar $parser = $this->_setCommonOptions($parser); $parser->setDescription( - 'Bake table and entity classes.' + 'Bake table and entity classes.', )->addArgument('name', [ 'help' => 'Name of the model to bake (without the Table suffix). ' . 'You can use Plugin.name to bake plugin models.', @@ -1327,7 +1362,7 @@ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionPar 'help' => 'Generate relations for all "example_id" fields' . ' without checking the database if a table "examples" exists.', ])->setEpilog( - 'Omitting all arguments and options will list the table names you can generate models for.' + 'Omitting all arguments and options will list the table names you can generate models for.', ); return $parser; @@ -1346,7 +1381,7 @@ public function bakeFixture( string $className, string $useTable, Arguments $args, - ConsoleIo $io + ConsoleIo $io, ): void { if ($args->getOption('no-fixture')) { return; @@ -1355,7 +1390,7 @@ public function bakeFixture( $fixtureArgs = new Arguments( [$className], ['table' => $useTable] + $args->getOptions(), - ['name'] + ['name'], ); $fixture->execute($fixtureArgs, $io); } @@ -1377,8 +1412,185 @@ public function bakeTest(string $className, Arguments $args, ConsoleIo $io): voi $testArgs = new Arguments( ['table', $className], $args->getOptions(), - ['type', 'name'] + ['type', 'name'], ); $test->execute($testArgs, $io); } + + /** + * @param \Cake\ORM\Table $table + * @param string $entity + * @param string $namespace + * @return array + */ + protected function enums(Table $table, string $entity, string $namespace): array + { + $fields = $this->possibleEnumFields($table->getSchema()); + $enumClassNamespace = $namespace . '\Model\Enum\\'; + + $enums = []; + foreach ($fields as $field) { + $enumClassName = $enumClassNamespace . $entity . Inflector::camelize($field); + if (!class_exists($enumClassName)) { + continue; + } + + $enums[$field] = $enumClassName; + } + + return $enums; + } + + /** + * @param \Cake\Database\Schema\TableSchemaInterface $schema + * @return array + */ + protected function possibleEnumFields(TableSchemaInterface $schema): array + { + $fields = []; + + foreach ($schema->columns() as $column) { + $columnSchema = $schema->getColumn($column); + if (str_starts_with($columnSchema['type'], 'enum-')) { + $fields[] = $column; + + continue; + } + + if (!in_array($columnSchema['type'], ['string', 'integer', 'tinyinteger', 'smallinteger'], true)) { + continue; + } + + $fields[] = $column; + } + + return $fields; + } + + /** + * @param \Cake\Database\Schema\TableSchemaInterface $schema + * @return array + */ + protected function getEnumDefinitions(TableSchemaInterface $schema): array + { + $enums = []; + + foreach ($schema->columns() as $column) { + $columnSchema = $schema->getColumn($column); + if ( + !in_array($columnSchema['type'], ['string', 'integer', 'tinyinteger', 'smallinteger'], true) + && !str_starts_with($columnSchema['type'], 'enum-') + ) { + continue; + } + + if (empty($columnSchema['comment']) || !str_contains($columnSchema['comment'], '[enum]')) { + continue; + } + + $enumsDefinitionString = EnumParser::parseDefinitionString($columnSchema['comment']); + $isInt = in_array($columnSchema['type'], ['integer', 'tinyinteger', 'smallinteger'], true); + if (str_starts_with($columnSchema['type'], 'enum-')) { + $dbType = TypeFactory::build($columnSchema['type']); + if ($dbType instanceof EnumType) { + $class = $dbType->getEnumClassName(); + $reflectionEnum = new ReflectionEnum($class); + $backingType = (string)$reflectionEnum->getBackingType(); + if ($backingType === 'int') { + $isInt = true; + } + } + } + $enumsDefinition = EnumParser::parseCases($enumsDefinitionString, $isInt); + if (!$enumsDefinition) { + continue; + } + + $enums[$column] = [ + 'type' => $isInt ? 'int' : 'string', + 'cases' => $enumsDefinition, + ]; + } + + return $enums; + } + + /** + * @param \Cake\ORM\Table $model + * @param array $data + * @param \Cake\Console\Arguments $args + * @param \Cake\Console\ConsoleIo $io + * @return void + */ + protected function bakeEnums(Table $model, array $data, Arguments $args, ConsoleIo $io): void + { + $enums = $data['enumSchema']; + if (!$enums) { + return; + } + + $entity = $this->_entityName($model->getAlias()); + + foreach ($enums as $column => $data) { + $enumCommand = new EnumCommand(); + + $name = $entity . Inflector::camelize($column); + if ($this->plugin) { + $name = $this->plugin . '.' . $name; + } + + $enumCases = $data['cases']; + + $cases = []; + foreach ($enumCases as $k => $v) { + $cases[] = $k . ':' . $v; + } + + $args = new Arguments( + [$name, implode(',', $cases)], + ['int' => $data['type'] === 'int'] + $args->getOptions(), + ['name', 'cases'], + ); + $enumCommand->execute($args, $io); + } + } + + /** + * @param array> $associations + * @return array> + */ + protected function ensureAliasUniqueness(array $associations): array + { + $existing = []; + foreach ($associations as $type => $associationsPerType) { + foreach ($associationsPerType as $k => $association) { + $alias = $association['alias']; + if (in_array($alias, $existing, true)) { + $alias = $this->createAssociationAlias($association); + } + $existing[] = $alias; + if (empty($association['className'])) { + $className = $this->plugin ? $this->plugin . '.' . $association['alias'] : $association['alias']; + if ($className !== $alias) { + $association['className'] = $className; + } + } + $association['alias'] = $alias; + $associations[$type][$k] = $association; + } + } + + return $associations; + } + + /** + * @param array $association + * @return string + */ + protected function createAssociationAlias(array $association): string + { + $foreignKey = $association['foreignKey']; + + return $this->_modelNameFromKey($foreignKey); + } } diff --git a/app/vendor/cakephp/bake/src/Command/PluginCommand.php b/app/vendor/cakephp/bake/src/Command/PluginCommand.php index 992ba2ee9..341f73e3d 100644 --- a/app/vendor/cakephp/bake/src/Command/PluginCommand.php +++ b/app/vendor/cakephp/bake/src/Command/PluginCommand.php @@ -26,8 +26,10 @@ use Cake\Core\App; use Cake\Core\Configure; use Cake\Core\Plugin; -use Cake\Filesystem\Filesystem; +use Cake\Utility\Filesystem; use Cake\Utility\Inflector; +use RuntimeException; +use function Cake\Core\env; /** * The Plugin Command handles creating an empty plugin, ready to be used @@ -39,18 +41,9 @@ class PluginCommand extends BakeCommand * * @var string */ - public $path; + public string $path; - /** - * initialize - * - * @return void - */ - public function initialize(): void - { - parent::initialize(); - $this->path = current(App::path('plugins')); - } + protected bool $isVendor = false; /** * Execute the command. @@ -71,6 +64,18 @@ public function execute(Arguments $args, ConsoleIo $io): ?int $parts = explode('/', $name); $plugin = implode('/', array_map([Inflector::class, 'camelize'], $parts)); + if ($args->getOption('standalone-path')) { + $this->path = $args->getOption('standalone-path'); + $this->path = rtrim($this->path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; + $this->isVendor = true; + + if (!is_dir($this->path)) { + $io->err(sprintf('Path `%s` does not exist.', $this->path)); + + return static::CODE_ERROR; + } + } + $pluginPath = $this->_pluginPath($plugin); if (is_dir($pluginPath)) { $io->out(sprintf('Plugin: %s already exists, no action taken', $plugin)); @@ -98,10 +103,15 @@ public function execute(Arguments $args, ConsoleIo $io): ?int */ public function bake(string $plugin, Arguments $args, ConsoleIo $io): ?bool { - $pathOptions = App::path('plugins'); - if (count($pathOptions) > 1) { - $this->findPath($pathOptions, $io); + if (!$this->isVendor) { + $pathOptions = App::path('plugins'); + $this->path = current($pathOptions); + + if (count($pathOptions) > 1) { + $this->findPath($pathOptions, $io); + } } + $io->out(sprintf('Plugin Name: %s', $plugin)); $io->out(sprintf('Plugin Directory: %s', $this->path . $plugin)); $io->hr(); @@ -114,8 +124,27 @@ public function bake(string $plugin, Arguments $args, ConsoleIo $io): ?bool $this->_generateFiles($plugin, $this->path, $args, $io); - $this->_modifyAutoloader($plugin, $this->path, $args, $io); - $this->_modifyApplication($plugin, $io); + if (!$this->isVendor) { + $this->_modifyApplication($plugin, $io); + + $composer = $this->findComposer($args, $io); + + try { + $cwd = getcwd(); + + // Windows makes running multiple commands at once hard. + chdir(dirname($this->_rootComposerFilePath())); + $command = 'php ' . escapeshellarg($composer) . ' dump-autoload'; + $process = new Process($io); + $io->out($process->call($command)); + + chdir($cwd); + } catch (RuntimeException $e) { + $error = $e->getMessage(); + $io->error(sprintf('Could not run `composer dump-autoload`: %s', $error)); + $this->abort(); + } + } $io->hr(); $io->out(sprintf('Created: %s in %s', $plugin, $this->path . $plugin), 2); @@ -156,22 +185,21 @@ protected function _generateFiles( string $pluginName, string $path, Arguments $args, - ConsoleIo $io + ConsoleIo $io, ): void { $namespace = str_replace('/', '\\', $pluginName); $baseNamespace = Configure::read('App.namespace'); $name = $pluginName; $vendor = 'your-name-here'; - if (strpos($pluginName, '/') !== false) { + if (str_contains($pluginName, '/')) { [$vendor, $name] = explode('/', $pluginName); } $package = Inflector::dasherize($vendor) . '/' . Inflector::dasherize($name); - /** @psalm-suppress UndefinedConstant */ $composerConfig = json_decode( file_get_contents(ROOT . DS . 'composer.json'), - true + true, ); $renderer = $this->createTemplateRenderer() @@ -202,9 +230,24 @@ protected function _generateFiles( do { $templatesPath = array_shift($paths) . BakeView::BAKE_TEMPLATE_FOLDER . '/Plugin'; if (is_dir($templatesPath)) { - $templates = array_keys(iterator_to_array( - $fs->findRecursive($templatesPath, '/\.twig$/') - )); + $files = iterator_to_array( + $fs->findRecursive($templatesPath, '/\.twig$/'), + ); + + if (!$this->isVendor) { + $vendorFiles = [ + '.gitignore.twig', 'README.md.twig', 'composer.json.twig', 'phpunit.xml.dist.twig', + 'bootstrap.php.twig', 'schema.sql.twig', + ]; + + foreach ($files as $key => $file) { + if (in_array($file->getFilename(), $vendorFiles, true)) { + unset($files[$key]); + } + } + } + + $templates = array_keys($files); } } while (!$templates); @@ -235,76 +278,13 @@ protected function _generateFile( string $template, string $root, string $filename, - ConsoleIo $io + ConsoleIo $io, ): void { $io->out(sprintf('Generating %s file...', $template)); $out = $renderer->generate('Bake.Plugin/' . $template); $io->createFile($root . $filename, $out); } - /** - * Modifies App's composer.json to include the plugin and tries to call - * composer dump-autoload to refresh the autoloader cache - * - * @param string $plugin Name of plugin - * @param string $path The path to save the phpunit.xml file to. - * @param \Cake\Console\Arguments $args The Arguments instance. - * @param \Cake\Console\ConsoleIo $io The io instance. - * @return bool True if composer could be modified correctly - */ - protected function _modifyAutoloader( - string $plugin, - string $path, - Arguments $args, - ConsoleIo $io - ): bool { - $file = $this->_rootComposerFilePath(); - - if (!file_exists($file)) { - $io->out(sprintf('Main composer file %s not found', $file)); - - return false; - } - - $autoloadPath = str_replace(ROOT . DS, '', $this->path); - $autoloadPath = str_replace('\\', '/', $autoloadPath); - $namespace = str_replace('/', '\\', $plugin); - - $config = json_decode(file_get_contents($file), true); - $config['autoload']['psr-4'][$namespace . '\\'] = $autoloadPath . $plugin . '/src/'; - $config['autoload-dev']['psr-4'][$namespace . '\\Test\\'] = $autoloadPath . $plugin . '/tests/'; - - $io->out('Modifying composer autoloader'); - - $out = json_encode($config, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . "\n"; - $io->createFile($file, $out, $this->force); - - $composer = $this->findComposer($args, $io); - - if (!$composer) { - $io->error('Could not locate composer. Add composer to your PATH, or use the --composer option.'); - $this->abort(); - } - - try { - $cwd = getcwd(); - - // Windows makes running multiple commands at once hard. - chdir(dirname($this->_rootComposerFilePath())); - $command = 'php ' . escapeshellarg($composer) . ' dump-autoload'; - $process = new Process($io); - $io->out($process->call($command)); - - chdir($cwd); - } catch (\RuntimeException $e) { - $error = $e->getMessage(); - $io->error(sprintf('Could not run `composer dump-autoload`: %s', $error)); - $this->abort(); - } - - return true; - } - /** * The path to the main application's composer file * @@ -370,9 +350,10 @@ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionPar { $parser->setDescription( 'Create the directory structure, AppController class and testing setup for a new plugin. ' . - 'Can create plugins in any of your bootstrapped plugin paths.' + 'Can create plugins in any of your bootstrapped plugin paths.', )->addArgument('name', [ - 'help' => 'CamelCased name of the plugin to create.', + 'help' => 'CamelCased name of the plugin to create.' + . ' For standalone plugins you can use vendor prefixed names like MyVendor/MyPlugin.', ])->addOption('composer', [ 'default' => ROOT . DS . 'composer.phar', 'help' => 'The path to the composer executable.', @@ -385,6 +366,10 @@ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionPar 'help' => 'The theme to use when baking code.', 'default' => Configure::read('Bake.theme') ?: null, 'choices' => $this->_getBakeThemes(), + ]) + ->addOption('standalone-path', [ + 'short' => 'p', + 'help' => 'Generate a standalone plugin in the provided path.', ]); return $parser; @@ -397,7 +382,7 @@ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionPar * @param \Cake\Console\ConsoleIo $io The console io * @return string|bool Either the path to composer or false if it cannot be found. */ - public function findComposer(Arguments $args, ConsoleIo $io) + public function findComposer(Arguments $args, ConsoleIo $io): string|bool { if ($args->hasOption('composer')) { /** @var string $path */ @@ -423,7 +408,7 @@ public function findComposer(Arguments $args, ConsoleIo $io) * @param \Cake\Console\ConsoleIo $io The console io * @return string|bool */ - protected function _searchPath(array $path, ConsoleIo $io) + protected function _searchPath(array $path, ConsoleIo $io): string|bool { $composer = ['composer.phar', 'composer']; foreach ($path as $dir) { diff --git a/app/vendor/cakephp/bake/src/Command/ShellHelperCommand.php b/app/vendor/cakephp/bake/src/Command/ShellHelperCommand.php deleted file mode 100644 index 55643663d..000000000 --- a/app/vendor/cakephp/bake/src/Command/ShellHelperCommand.php +++ /dev/null @@ -1,54 +0,0 @@ -_setCommonOptions($parser); $name = $this->name(); $parser->setDescription( - sprintf('Bake a %s class file.', $name) + sprintf('Bake a %s class file.', $name), )->addArgument('name', [ 'help' => sprintf( 'Name of the %s to bake. Can use Plugin.name to bake %s files into plugins.', $name, - $name + $name, ), ])->addOption('no-test', [ 'boolean' => true, diff --git a/app/vendor/cakephp/bake/src/Command/TemplateAllCommand.php b/app/vendor/cakephp/bake/src/Command/TemplateAllCommand.php index dd16f13ad..45e14ef10 100644 --- a/app/vendor/cakephp/bake/src/Command/TemplateAllCommand.php +++ b/app/vendor/cakephp/bake/src/Command/TemplateAllCommand.php @@ -30,7 +30,7 @@ class TemplateAllCommand extends BakeCommand /** * @var \Bake\Command\TemplateCommand */ - protected $templateCommand; + protected TemplateCommand $templateCommand; /** * @inheritDoc @@ -65,8 +65,15 @@ public function execute(Arguments $args, ConsoleIo $io): int $connection = ConnectionManager::get($this->connection); $scanner = new TableScanner($connection); - foreach ($scanner->listUnskipped() as $table) { - $templateArgs = new Arguments([$table], $args->getOptions(), ['name']); + $tables = $scanner->removeShadowTranslationTables($scanner->listUnskipped()); + foreach ($tables as $table) { + $parser = $this->templateCommand->getOptionParser(); + $templateArgs = new Arguments( + [$table], + $args->getOptions(), + $parser->argumentNames(), + ); + $this->templateCommand->execute($templateArgs, $io); } @@ -88,7 +95,7 @@ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionPar 'help' => 'The routing prefix to generate views for.', ])->addOption('index-columns', [ 'help' => 'Limit for the number of index columns', - 'default' => 0, + 'default' => '0', ]); return $parser; diff --git a/app/vendor/cakephp/bake/src/Command/TemplateCommand.php b/app/vendor/cakephp/bake/src/Command/TemplateCommand.php index 57d9372da..558187ab9 100644 --- a/app/vendor/cakephp/bake/src/Command/TemplateCommand.php +++ b/app/vendor/cakephp/bake/src/Command/TemplateCommand.php @@ -28,7 +28,9 @@ use Cake\ORM\Table; use Cake\Utility\Inflector; use Cake\View\Exception\MissingTemplateException; +use Exception; use RuntimeException; +use function Cake\Core\namespaceSplit; /** * Task class for creating view template files. @@ -40,56 +42,56 @@ class TemplateCommand extends BakeCommand * * @var string */ - public $controllerName; + public string $controllerName; /** * Classname of the controller being used * * @var string */ - public $controllerClass; + public string $controllerClass; /** * Name with plugin of the model being used * * @var string */ - public $modelName = null; + public string $modelName; /** * Actions to use for scaffolding * - * @var string[] + * @var array */ - public $scaffoldActions = ['index', 'view', 'add', 'edit']; + public array $scaffoldActions = ['index', 'view', 'add', 'edit']; /** * Actions that exclude hidden fields * - * @var string[] + * @var array */ - public $excludeHiddenActions = ['index', 'view']; + public array $excludeHiddenActions = ['index', 'view']; /** * AssociationFilter utility * * @var \Bake\Utility\Model\AssociationFilter|null */ - protected $_associationFilter; + protected ?AssociationFilter $_associationFilter = null; /** * Template path. * * @var string */ - public $path; + public string $path; /** * Output extension * * @var string */ - public $ext = 'php'; + public string $ext = 'php'; /** * Override initialize @@ -219,7 +221,7 @@ public function getTemplatePath(Arguments $args, ?string $container = null): str /** * Get a list of actions that can / should have view templates baked for them. * - * @return string[] Array of action names that should be baked + * @return array Array of action names that should be baked */ protected function _methodsToBake(): array { @@ -230,12 +232,12 @@ protected function _methodsToBake(): array $methods = array_diff( array_map( 'Cake\Utility\Inflector::underscore', - get_class_methods($this->controllerClass) + get_class_methods($this->controllerClass), ), array_map( 'Cake\Utility\Inflector::underscore', - get_class_methods($base . '\Controller\AppController') - ) + get_class_methods($base . '\Controller\AppController'), + ), ); } if (empty($methods)) { @@ -293,7 +295,7 @@ protected function _loadController(ConsoleIo $io): array $fields = $schema->columns(); $hidden = $modelObject->newEmptyEntity()->getHidden() ?: ['token', 'password', 'passwd']; $modelClass = $this->modelName; - } catch (\Exception $exception) { + } catch (Exception $exception) { $io->error($exception->getMessage()); $this->abort(); } @@ -329,7 +331,7 @@ protected function _loadController(ConsoleIo $io): array 'hidden', 'associations', 'keyFields', - 'namespace' + 'namespace', ); } @@ -347,8 +349,8 @@ public function bake( Arguments $args, ConsoleIo $io, string $template, - $content = '', - ?string $outputFile = null + string|bool $content = '', + ?string $outputFile = null, ): void { if ($outputFile === null) { $outputFile = $template; @@ -418,7 +420,7 @@ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionPar $parser = $this->_setCommonOptions($parser); $parser->setDescription( - 'Bake views for a controller, using built-in or custom templates. ' + 'Bake views for a controller, using built-in or custom templates. ', )->addArgument('name', [ 'help' => 'Name of the controller views to bake. You can use Plugin.name as a shortcut for plugin baking.', ])->addArgument('template', [ @@ -431,7 +433,7 @@ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionPar 'help' => 'The routing prefix to generate views for.', ])->addOption('index-columns', [ 'help' => 'Limit for the number of index columns', - 'default' => 0, + 'default' => '0', ]); return $parser; diff --git a/app/vendor/cakephp/bake/src/Command/TestCommand.php b/app/vendor/cakephp/bake/src/Command/TestCommand.php index 9fe9cd91f..dd5e6c301 100644 --- a/app/vendor/cakephp/bake/src/Command/TestCommand.php +++ b/app/vendor/cakephp/bake/src/Command/TestCommand.php @@ -19,18 +19,18 @@ use Cake\Console\Arguments; use Cake\Console\ConsoleIo; use Cake\Console\ConsoleOptionParser; -use Cake\Console\Shell; use Cake\Controller\Controller; use Cake\Core\Configure; use Cake\Core\Exception\CakeException; use Cake\Core\Plugin; -use Cake\Filesystem\Filesystem; -use Cake\Http\Response; use Cake\Http\ServerRequest as Request; use Cake\ORM\Table; +use Cake\Utility\Filesystem; use Cake\Utility\Inflector; use ReflectionClass; use UnexpectedValueException; +use function Cake\Core\namespaceSplit; +use function Cake\Core\pluginSplit; /** * Command class for generating test files. @@ -40,18 +40,15 @@ class TestCommand extends BakeCommand /** * class types that methods can be generated for * - * @var string[] + * @var array */ - public $classTypes = [ + public array $classTypes = [ 'Entity' => 'Model\Entity', 'Table' => 'Model\Table', 'Controller' => 'Controller', 'Component' => 'Controller\Component', 'Behavior' => 'Model\Behavior', 'Helper' => 'View\Helper', - 'Shell' => 'Shell', - 'Task' => 'Shell\Task', - 'ShellHelper' => 'Shell\Helper', 'Cell' => 'View\Cell', 'Form' => 'Form', 'Mailer' => 'Mailer', @@ -63,18 +60,15 @@ class TestCommand extends BakeCommand /** * class types that methods can be generated for * - * @var string[] + * @var array */ - public $classSuffixes = [ + public array $classSuffixes = [ 'Entity' => '', 'Table' => 'Table', 'Controller' => 'Controller', 'Component' => 'Component', 'Behavior' => 'Behavior', 'Helper' => 'Helper', - 'Shell' => 'Shell', - 'Task' => 'Task', - 'ShellHelper' => 'Helper', 'Cell' => 'Cell', 'Form' => 'Form', 'Mailer' => 'Mailer', @@ -86,18 +80,18 @@ class TestCommand extends BakeCommand /** * Blacklisted methods for controller test cases. * - * @var string[] + * @var array */ - protected $blacklistedMethods = [ + protected array $blacklistedMethods = [ 'initialize', ]; /** * Internal list of fixtures that have been added so far. * - * @var string[] + * @var array */ - protected $_fixtures = []; + protected array $_fixtures = []; /** * Execute test generation @@ -146,7 +140,7 @@ protected function outputTypeChoices(ConsoleIo $io): void { $io->out( 'You must provide a class type to bake a test for. The valid types are:', - 2 + 2, ); $i = 0; foreach ($this->classTypes as $option => $package) { @@ -168,7 +162,7 @@ protected function outputClassChoices(string $typeName, ConsoleIo $io): void $type = $this->mapType($typeName); $io->out( 'You must provide a class to bake a test for. Some possible options are:', - 2 + 2, ); $options = $this->_getClassOptions($type); $i = 0; @@ -207,7 +201,7 @@ protected function _bakeAll(string $type, Arguments $args, ConsoleIo $io): void * Get the possible classes for a given type. * * @param string $namespace The namespace fragment to look for classes in. - * @return string[] + * @return array */ protected function _getClassOptions(string $namespace): array { @@ -238,7 +232,7 @@ protected function _getClassOptions(string $namespace): array * @param \Cake\Console\ConsoleIo $io ConsoleIo instance * @return string|bool */ - public function bake(string $type, string $className, Arguments $args, ConsoleIo $io) + public function bake(string $type, string $className, Arguments $args, ConsoleIo $io): string|bool { $type = $this->normalize($type); if (!isset($this->classSuffixes[$type]) || !isset($this->classTypes[$type])) { @@ -278,7 +272,7 @@ public function bake(string $type, string $className, Arguments $args, ConsoleIo $properties = $this->generateProperties($type, $subject, $fullClassName); - $io->out("\n" . sprintf('Baking test case for %s ...', $fullClassName), 1, Shell::QUIET); + $io->out("\n" . sprintf('Baking test case for %s ...', $fullClassName), 1, ConsoleIo::QUIET); $contents = $this->createTemplateRenderer() ->set('fixtures', $this->_fixtures) @@ -297,7 +291,7 @@ public function bake(string $type, string $className, Arguments $args, ConsoleIo 'uses', 'baseNamespace', 'subNamespace', - 'namespace' + 'namespace', )) ->generate('Bake.tests/test_case'); @@ -331,7 +325,7 @@ public function typeCanDetectFixtures(string $type): bool * @param string $class The classname of the class the test is being generated for. * @return object And instance of the class that is going to be tested. */ - public function buildTestSubject(string $type, string $class) + public function buildTestSubject(string $type, string $class): object { if ($type === 'Table') { [, $name] = namespaceSplit($class); @@ -347,7 +341,7 @@ public function buildTestSubject(string $type, string $class) ]); } } elseif ($type === 'Controller') { - $instance = new $class(new Request(), new Response()); + $instance = new $class(new Request()); } else { $instance = new $class(); } @@ -416,7 +410,7 @@ public function mapType(string $type): string * No parent methods will be returned * * @param string $className Name of class to look at. - * @return string[] Array of method names. + * @return array Array of method names. * @throws \ReflectionException */ public function getTestableMethods(string $className): array @@ -441,18 +435,17 @@ public function getTestableMethods(string $className): array * loaded models. * * @param \Cake\ORM\Table|\Cake\Controller\Controller $subject The object you want to generate fixtures for. - * @return string[] Array of fixtures to be included in the test. + * @return array Array of fixtures to be included in the test. */ - public function generateFixtureList($subject): array + public function generateFixtureList(Table|Controller $subject): array { $this->_fixtures = []; if ($subject instanceof Table) { $this->_processModel($subject); - } elseif ($subject instanceof Controller) { + } else { $this->_processController($subject); } - /** @psalm-suppress RedundantFunctionCall */ return array_values($this->_fixtures); } @@ -490,7 +483,7 @@ protected function _processModel(Table $subject): void protected function _processController(Controller $subject): void { try { - $model = $subject->loadModel(); + $model = $subject->fetchTable(); } catch (UnexpectedValueException $exception) { // No fixtures needed or possible return; @@ -538,7 +531,7 @@ public function hasMockClass(string $type): bool * * @param string $type The Type of object you are generating tests for eg. controller * @param string $fullClassName The full classname of the class the test is being generated for. - * @return string[] Constructor snippets for the thing you are building. + * @return array Constructor snippets for the thing you are building. */ public function generateConstructor(string $type, string $fullClassName): array { @@ -561,27 +554,16 @@ public function generateConstructor(string $type, string $fullClassName): array $pre = '$view = new View();'; $construct = "new {$className}(\$view);"; } - if ($type === 'Command') { - $construct = '$this->useCommandRunner();'; - } if ($type === 'Component') { $pre = '$registry = new ComponentRegistry();'; $construct = "new {$className}(\$registry);"; } - if ($type === 'Shell') { - $pre = "\$this->io = \$this->getMockBuilder('Cake\Console\ConsoleIo')->getMock();"; - $construct = "new {$className}(\$this->io);"; - } - if ($type === 'Task') { - $pre = "\$this->io = \$this->getMockBuilder('Cake\Console\ConsoleIo')->getMock();"; - $construct = "new {$className}(\$this->io);"; - } if ($type === 'Cell') { $pre = "\$this->request = \$this->getMockBuilder('Cake\Http\ServerRequest')->getMock();\n"; $pre .= " \$this->response = \$this->getMockBuilder('Cake\Http\Response')->getMock();"; $construct = "new {$className}(\$this->request, \$this->response);"; } - if ($type === 'ShellHelper' || $type === 'CommandHelper') { + if ($type === 'CommandHelper') { $pre = "\$this->stub = new ConsoleOutput();\n"; $pre .= ' $this->io = new ConsoleIo($this->stub);'; $construct = "new {$className}(\$this->io);"; @@ -622,17 +604,7 @@ public function generateProperties(string $type, string $subject, string $fullCl ]; break; - case 'Shell': - case 'Task': - $properties[] = [ - 'description' => 'ConsoleIo mock', - 'type' => '\Cake\Console\ConsoleIo|\PHPUnit\Framework\MockObject\MockObject', - 'name' => 'io', - ]; - break; - case 'CommandHelper': - case 'ShellHelper': $properties[] = [ 'description' => 'ConsoleOutput stub', 'type' => '\Cake\TestSuite\Stub\ConsoleOutput', @@ -662,7 +634,7 @@ public function generateProperties(string $type, string $subject, string $fullCl * * @param string $type The Type of object you are generating tests for eg. controller * @param string $fullClassName The Classname of the class the test is being generated for. - * @return string[] An array containing used classes + * @return array An array containing used classes */ public function generateUses(string $type, string $fullClassName): array { @@ -673,7 +645,7 @@ public function generateUses(string $type, string $fullClassName): array if ($type === 'Helper') { $uses[] = 'Cake\View\View'; } - if ($type === 'ShellHelper' || $type === 'CommandHelper') { + if ($type === 'CommandHelper') { $uses[] = 'Cake\TestSuite\Stub\ConsoleOutput'; $uses[] = 'Cake\Console\ConsoleIo'; } @@ -737,7 +709,7 @@ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionPar $types = array_merge($types, array_map([$this, 'underscore'], $types)); $parser->setDescription( - 'Bake test case skeletons for classes.' + 'Bake test case skeletons for classes.', )->addArgument('type', [ 'help' => 'Type of class to bake, can be any of the following:' . ' controller, model, helper, component or behavior.', diff --git a/app/vendor/cakephp/bake/src/Plugin.php b/app/vendor/cakephp/bake/src/Plugin.php deleted file mode 100644 index ec8703b03..000000000 --- a/app/vendor/cakephp/bake/src/Plugin.php +++ /dev/null @@ -1,159 +0,0 @@ -addPlugin('Cake/TwigView'); - } - - /** - * Define the console commands for an application. - * - * @param \Cake\Console\CommandCollection $commands The CommandCollection to add commands into. - * @return \Cake\Console\CommandCollection The updated collection. - */ - public function console(CommandCollection $commands): CommandCollection - { - // Add commands in plugins and app. - $commands = $this->discoverCommands($commands); - - // Add entry command to handle entry point and backwards compat. - $commands->add(EntryCommand::defaultName(), EntryCommand::class); - - return $commands; - } - - /** - * Scan plugins and application to find commands that are intended - * to be used with bake. - * - * Non-Abstract commands extending `Bake\Command\BakeCommand` are included. - * Plugins are scanned in the order they are listed in `Plugin::loaded()` - * - * @param \Cake\Console\CommandCollection $commands The CommandCollection to add commands into. - * @return \Cake\Console\CommandCollection The updated collection. - */ - protected function discoverCommands(CommandCollection $commands): CommandCollection - { - foreach (CorePlugin::getCollection()->with('console') as $plugin) { - $namespace = str_replace('/', '\\', $plugin->getName()); - $pluginPath = $plugin->getClassPath(); - - $found = $this->findInPath($namespace, $pluginPath); - if (count($found)) { - $commands->addMany($found); - } - } - - $found = $this->findInPath(Configure::read('App.namespace'), APP); - if (count($found)) { - $commands->addMany($found); - } - - return $commands; - } - - /** - * Search a path for commands. - * - * @param string $namespace The namespace classes are expected to be in. - * @param string $path The path to look in. - * @return string[] - * @psalm-return array> - */ - protected function findInPath(string $namespace, string $path): array - { - $hasSubfolder = false; - $path .= 'Command/'; - $namespace .= '\Command\\'; - - if (file_exists($path . 'Bake/')) { - $hasSubfolder = true; - $path .= 'Bake/'; - $namespace .= 'Bake\\'; - } elseif (!file_exists($path)) { - return []; - } - - $iterator = new DirectoryIterator($path); - $candidates = []; - foreach ($iterator as $item) { - if ($item->isDot() || $item->isDir()) { - continue; - } - /** @psalm-var class-string<\Bake\Command\BakeCommand> $class */ - $class = $namespace . $item->getBasename('.php'); - - if (!$hasSubfolder) { - try { - $reflection = new ReflectionClass($class); - /** @phpstan-ignore-next-line */ - } catch (ReflectionException $e) { - continue; - } - if (!$reflection->isInstantiable() || !$reflection->isSubclassOf(BakeCommand::class)) { - continue; - } - } - - $candidates[$class::defaultName()] = $class; - } - - return $candidates; - } -} diff --git a/app/vendor/cakephp/bake/src/Shell/Task/BakeTask.php b/app/vendor/cakephp/bake/src/Shell/Task/BakeTask.php deleted file mode 100644 index 8d2a4b909..000000000 --- a/app/vendor/cakephp/bake/src/Shell/Task/BakeTask.php +++ /dev/null @@ -1,198 +0,0 @@ -connection) && !empty($this->params['connection'])) { - $this->connection = $this->params['connection']; - } - } - - /** - * Get the prefix name. - * - * Handles camelcasing each namespace in the prefix path. - * - * @return string The inflected prefix path. - */ - protected function _getPrefix(): string - { - $prefix = $this->param('prefix'); - if (!$prefix) { - return ''; - } - $parts = explode('/', $prefix); - - return implode('/', array_map([$this, '_camelize'], $parts)); - } - - /** - * Gets the path for output. Checks the plugin property - * and returns the correct path. - * - * @return string Path to output. - */ - public function getPath(): string - { - $path = APP . $this->pathFragment; - if ($this->plugin) { - $path = $this->_pluginPath($this->plugin) . 'src/' . $this->pathFragment; - } - $prefix = $this->_getPrefix(); - if ($prefix) { - $path .= $prefix . DS; - } - - return str_replace('/', DS, $path); - } - - /** - * Base execute method parses some parameters and sets some properties on the bake tasks. - * call when overriding execute() - * - * @return int|null - */ - public function main() - { - if (isset($this->params['plugin'])) { - $parts = explode('/', $this->params['plugin']); - $this->plugin = implode('/', array_map([$this, '_camelize'], $parts)); - if (strpos($this->plugin, '\\')) { - $this->abort('Invalid plugin namespace separator, please use / instead of \ for plugins.'); - } - } - if (isset($this->params['connection'])) { - $this->connection = $this->params['connection']; - } - - return static::CODE_SUCCESS; - } - - /** - * Executes an external shell command and pipes its output to the stdout - * - * @param string $command the command to execute - * @return void - * @throws \RuntimeException if any errors occurred during the execution - */ - public function callProcess(string $command): void - { - $process = new Process($this->_io); - $out = $process->call($command); - $this->out($out); - } - - /** - * Handles splitting up the plugin prefix and classname. - * - * Sets the plugin parameter and plugin property. - * - * @param string $name The name to possibly split. - * @return string The name without the plugin prefix. - */ - protected function _getName(string $name): string - { - if (empty($name)) { - return $name; - } - - if (strpos($name, '.')) { - [$plugin, $name] = pluginSplit($name); - $this->plugin = $this->params['plugin'] = $plugin; - } - - return $name; - } - - /** - * Delete empty file in a given path - * - * @param string $path Path to folder which contains 'empty' file. - * @return void - */ - protected function _deleteEmptyFile(string $path): void - { - if (file_exists($path)) { - unlink($path); - $this->out(sprintf('Deleted `%s`', $path), 1, Shell::QUIET); - } - } - - /** - * Get the option parser for this task. - * - * This base class method sets up some commonly used options. - * - * @return \Cake\Console\ConsoleOptionParser - */ - public function getOptionParser(): ConsoleOptionParser - { - return $this->_setCommonOptions(parent::getOptionParser()); - } -} diff --git a/app/vendor/cakephp/bake/src/Shell/Task/SimpleBakeTask.php b/app/vendor/cakephp/bake/src/Shell/Task/SimpleBakeTask.php deleted file mode 100644 index efadac3cb..000000000 --- a/app/vendor/cakephp/bake/src/Shell/Task/SimpleBakeTask.php +++ /dev/null @@ -1,128 +0,0 @@ - - */ - public function templateData(): array - { - $namespace = Configure::read('App.namespace'); - if ($this->plugin) { - $namespace = $this->_pluginNamespace($this->plugin); - } - - return ['namespace' => $namespace]; - } - - /** - * Execute method - * - * @param string|null $name The name of the object to bake. - * @return int|null - */ - public function main(?string $name = null): ?int - { - parent::main(); - if (empty($name)) { - $this->abort('You must provide a name to bake a ' . $this->name()); - } - $name = $this->_getName($name); - $name = Inflector::camelize($name); - $this->bake($name); - - return static::CODE_SUCCESS; - } - - /** - * Generate a class stub - * - * @param string $name The classname to generate. - * @return string - */ - public function bake(string $name): string - { - $renderer = new TemplateRenderer($this->param('theme')); - $renderer->set('name', $name); - $renderer->set($this->templateData()); - $contents = $renderer->generate($this->template()); - - $filename = $this->getPath() . $this->fileName($name); - $this->createFile($filename, $contents); - $emptyFile = $this->getPath() . '.gitkeep'; - $this->_deleteEmptyFile($emptyFile); - - return $contents; - } - - /** - * Gets the option parser instance and configures it. - * - * @return \Cake\Console\ConsoleOptionParser - */ - public function getOptionParser(): ConsoleOptionParser - { - $parser = parent::getOptionParser(); - $name = $this->name(); - $parser->setDescription( - sprintf('Bake a %s class file.', $name) - )->addArgument('name', [ - 'help' => sprintf( - 'Name of the %s to bake. Can use Plugin.name to bake %s files into plugins.', - $name, - $name - ), - ]); - - return $parser; - } -} diff --git a/app/vendor/cakephp/bake/src/Utility/CommonOptionsTrait.php b/app/vendor/cakephp/bake/src/Utility/CommonOptionsTrait.php index 07647b4e6..fde67f954 100644 --- a/app/vendor/cakephp/bake/src/Utility/CommonOptionsTrait.php +++ b/app/vendor/cakephp/bake/src/Utility/CommonOptionsTrait.php @@ -33,22 +33,22 @@ trait CommonOptionsTrait /** * @var string */ - public $plugin; + public ?string $plugin = null; /** * @var string|null */ - public $theme; + public ?string $theme = null; /** * @var string */ - public $connection; + public string $connection; /** * @var bool */ - public $force = false; + public bool $force = false; /** * Pull common/frequently used arguments & options into properties @@ -68,7 +68,7 @@ protected function extractCommonProperties(Arguments $args): void if (strpos($this->plugin, '\\')) { throw new InvalidArgumentException( - 'Invalid plugin namespace separator, please use / instead of \ for plugins.' + 'Invalid plugin namespace separator, please use / instead of \ for plugins.', ); } } diff --git a/app/vendor/cakephp/bake/src/Utility/Model/AssociationFilter.php b/app/vendor/cakephp/bake/src/Utility/Model/AssociationFilter.php index 643d68b62..c6f6a47c5 100644 --- a/app/vendor/cakephp/bake/src/Utility/Model/AssociationFilter.php +++ b/app/vendor/cakephp/bake/src/Utility/Model/AssociationFilter.php @@ -19,6 +19,7 @@ use Cake\ORM\Table; use Cake\Utility\Inflector; use Exception; +use function Cake\Core\namespaceSplit; /** * Utility class to filter Model Table associations @@ -30,8 +31,8 @@ class AssociationFilter * belongsToMany associations provided * * @param \Cake\ORM\Table $table Table - * @param string[] $aliases array of aliases - * @return string[] $aliases + * @param array $aliases array of aliases + * @return array $aliases */ public function filterHasManyAssociationsAliases(Table $table, array $aliases): array { @@ -44,7 +45,7 @@ public function filterHasManyAssociationsAliases(Table $table, array $aliases): * Get the array of junction aliases for all the BelongsToMany associations * * @param \Cake\ORM\Table $table Table - * @return string[] Junction aliases of all the BelongsToMany associations + * @return array Junction aliases of all the BelongsToMany associations */ public function belongsToManyJunctionAliases(Table $table): array { diff --git a/app/vendor/cakephp/bake/src/Utility/Model/EnumParser.php b/app/vendor/cakephp/bake/src/Utility/Model/EnumParser.php new file mode 100644 index 000000000..e559fe03e --- /dev/null +++ b/app/vendor/cakephp/bake/src/Utility/Model/EnumParser.php @@ -0,0 +1,61 @@ + + */ + public static function parseCases(?string $casesString, bool $int): array + { + if ($casesString === null || $casesString === '') { + return []; + } + + $enumCases = explode(',', $casesString); + + $definition = []; + foreach ($enumCases as $k => $enumCase) { + $case = $value = trim($enumCase); + if (str_contains($case, ':')) { + $value = trim(mb_substr($case, strpos($case, ':') + 1)); + $case = mb_substr($case, 0, strpos($case, ':')); + } elseif ($int) { + $value = $k; + } + + if (!preg_match('/^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*$/', $case)) { + throw new InvalidArgumentException(sprintf('`%s` is not a valid enum case', $case)); + } + if (is_string($value) && str_contains($value, '\'')) { + throw new InvalidArgumentException(sprintf('`%s` value cannot contain `\'` character', $case)); + } + + $definition[$case] = $int ? (int)$value : $value; + } + + return $definition; + } + + /** + * Parses an enum definition from a DB column comment. + * + * @param string $comment + * @return string + */ + public static function parseDefinitionString(string $comment): string + { + $string = trim(mb_substr($comment, strpos($comment, '[enum]') + 6)); + if (str_contains($string, ';')) { + $string = trim(mb_substr($string, 0, strpos($string, ';'))); + } + + return $string; + } +} diff --git a/app/vendor/cakephp/bake/src/Utility/Process.php b/app/vendor/cakephp/bake/src/Utility/Process.php index 974e0ea4a..78686c334 100644 --- a/app/vendor/cakephp/bake/src/Utility/Process.php +++ b/app/vendor/cakephp/bake/src/Utility/Process.php @@ -29,7 +29,7 @@ class Process /** * @var \Cake\Console\ConsoleIo */ - protected $io; + protected ConsoleIo $io; /** * Constructor @@ -59,7 +59,7 @@ public function call(string $command): string $process = proc_open( $command, $descriptorSpec, - $pipes + $pipes, ); if (!is_resource($process)) { throw new RuntimeException("Could not start subprocess for `$command`"); @@ -74,7 +74,7 @@ public function call(string $command): string $exit = proc_close($process); if ($exit !== 0) { - throw new \RuntimeException($error); + throw new RuntimeException($error); } return $output; diff --git a/app/vendor/cakephp/bake/src/Utility/SubsetSchemaCollection.php b/app/vendor/cakephp/bake/src/Utility/SubsetSchemaCollection.php index f05c13a10..290332159 100644 --- a/app/vendor/cakephp/bake/src/Utility/SubsetSchemaCollection.php +++ b/app/vendor/cakephp/bake/src/Utility/SubsetSchemaCollection.php @@ -30,16 +30,16 @@ class SubsetSchemaCollection implements CollectionInterface /** * @var \Cake\Database\Schema\CollectionInterface */ - protected $collection; + protected CollectionInterface $collection; /** - * @var string[] + * @var list */ - protected $tables = []; + protected array $tables = []; /** * @param \Cake\Database\Schema\CollectionInterface $collection The wrapped collection - * @param string[] $tables The subset of tables. + * @param list $tables The subset of tables. */ public function __construct(CollectionInterface $collection, array $tables) { @@ -60,7 +60,7 @@ public function getInnerCollection(): CollectionInterface /** * Get the list of tables in this schema collection. * - * @return string[] + * @return list */ public function listTables(): array { diff --git a/app/vendor/cakephp/bake/src/Utility/TableScanner.php b/app/vendor/cakephp/bake/src/Utility/TableScanner.php index 2b327c3f6..b10be2c0e 100644 --- a/app/vendor/cakephp/bake/src/Utility/TableScanner.php +++ b/app/vendor/cakephp/bake/src/Utility/TableScanner.php @@ -32,18 +32,18 @@ class TableScanner /** * @var \Cake\Database\Connection */ - protected $connection; + protected Connection $connection; /** - * @var string[] + * @var array */ - protected $ignore; + protected array $ignore; /** * Constructor * * @param \Cake\Database\Connection $connection The connection name in ConnectionManager - * @param string[]|null $ignore List of tables or regex pattern to ignore. If null, the default ignore + * @param array|null $ignore List of tables or regex pattern to ignore. If null, the default ignore * list will be used. */ public function __construct(Connection $connection, ?array $ignore = null) @@ -58,13 +58,13 @@ public function __construct(Connection $connection, ?array $ignore = null) /** * Get all tables in the connection without applying ignores. * - * @return string[] + * @return array */ public function listAll(): array { $schema = $this->connection->getSchemaCollection(); $tables = $schema->listTables(); - if (empty($tables)) { + if (!$tables) { throw new RuntimeException('Your database does not have any tables.'); } sort($tables); @@ -75,7 +75,7 @@ public function listAll(): array /** * Get all tables in the connection that aren't ignored. * - * @return string[] + * @return array */ public function listUnskipped(): array { @@ -90,6 +90,29 @@ public function listUnskipped(): array return $tables; } + /** + * Call from any All command that needs the shadow translation tables to be skipped. + * + * @param array $tables + * @return array + */ + public function removeShadowTranslationTables(array $tables): array + { + foreach ($tables as $key => $table) { + if (!preg_match('/^(.+)_translations$/', $table, $matches)) { + continue; + } + + if (empty($tables[$matches[1]])) { + continue; + } + + unset($tables[$key]); + } + + return $tables; + } + /** * @param string $table Table name. * @return bool @@ -97,7 +120,7 @@ public function listUnskipped(): array protected function shouldSkip(string $table): bool { foreach ($this->ignore as $ignore) { - if (strpos($ignore, '/') === 0) { + if (str_starts_with($ignore, '/')) { if ((bool)preg_match($ignore, $table)) { return true; } diff --git a/app/vendor/cakephp/bake/src/Utility/TemplateRenderer.php b/app/vendor/cakephp/bake/src/Utility/TemplateRenderer.php index 027934b89..d3f8eb633 100644 --- a/app/vendor/cakephp/bake/src/Utility/TemplateRenderer.php +++ b/app/vendor/cakephp/bake/src/Utility/TemplateRenderer.php @@ -37,14 +37,14 @@ class TemplateRenderer * * @var \Bake\View\BakeView|null */ - protected $view; + protected ?BakeView $view = null; /** * Template theme * * @var string|null */ - protected $theme; + protected ?string $theme; /** * Constructor diff --git a/app/vendor/cakephp/bake/src/View/BakeView.php b/app/vendor/cakephp/bake/src/View/BakeView.php index 3a3b6df78..841865434 100644 --- a/app/vendor/cakephp/bake/src/View/BakeView.php +++ b/app/vendor/cakephp/bake/src/View/BakeView.php @@ -18,11 +18,17 @@ use Cake\Core\Configure; use Cake\Core\ConventionsTrait; +use Cake\Event\EventDispatcherTrait; use Cake\Event\EventInterface; use Cake\TwigView\View\TwigView; +use function Cake\Core\pluginSplit; class BakeView extends TwigView { + /** + * @use \Cake\Event\EventDispatcherTrait<\Cake\View\View> + */ + use EventDispatcherTrait; use ConventionsTrait; /** @@ -35,7 +41,7 @@ class BakeView extends TwigView /** * @inheritDoc */ - protected $layout = 'Bake.default'; + protected string $layout = 'Bake.default'; /** * Initialize view @@ -73,7 +79,7 @@ public function initialize(): void * @throws \Cake\Core\Exception\CakeException If there is an error in the view. * @return string Rendered content. */ - public function render(?string $template = null, $layout = null): string + public function render(?string $template = null, string|false|null $layout = null): string { $viewFileName = $this->_getTemplateFileName($template); [, $templateEventName] = pluginSplit($template); @@ -101,15 +107,16 @@ public function render(?string $template = null, $layout = null): string * * Use the Bake prefix for bake related view events * + * @template TSubject of \Cake\View\View * @param string $name Name of the event. - * @param mixed $data Any value you wish to be transported with this event to + * @param array $data Any value you wish to be transported with this event to * it can be read by listeners. * - * @param mixed $subject The object that this event applies to + * @param TSubject|null $subject The object that this event applies to * ($this by default). - * @return \Cake\Event\EventInterface + * @return \Cake\Event\EventInterface<\Cake\View\View> */ - public function dispatchEvent(string $name, $data = null, $subject = null): EventInterface + public function dispatchEvent(string $name, array $data = [], ?object $subject = null): EventInterface { $name = preg_replace('/^View\./', 'Bake.', $name); @@ -121,7 +128,7 @@ public function dispatchEvent(string $name, $data = null, $subject = null): Even * * @param ?string $plugin Optional plugin name to scan for view files. * @param bool $cached Set to false to force a refresh of view paths. Default true. - * @return string[] paths + * @return list paths */ protected function _paths(?string $plugin = null, bool $cached = true): array { diff --git a/app/vendor/cakephp/bake/src/View/Helper/BakeHelper.php b/app/vendor/cakephp/bake/src/View/Helper/BakeHelper.php index 8dc0d899f..41b48a9e7 100644 --- a/app/vendor/cakephp/bake/src/View/Helper/BakeHelper.php +++ b/app/vendor/cakephp/bake/src/View/Helper/BakeHelper.php @@ -8,11 +8,16 @@ use Brick\VarExporter\VarExporter; use Cake\Core\Configure; use Cake\Core\ConventionsTrait; +use Cake\Core\Plugin; use Cake\Database\Schema\TableSchema; +use Cake\Database\Type\EnumType; +use Cake\Database\TypeFactory; use Cake\Datasource\SchemaInterface; use Cake\ORM\Table; use Cake\Utility\Inflector; use Cake\View\Helper; +use function Cake\Collection\collection; +use function Cake\Core\pluginSplit; /** * Bake helper @@ -26,14 +31,14 @@ class BakeHelper extends Helper * * @var array */ - protected $_defaultConfig = []; + protected array $_defaultConfig = []; /** * AssociationFilter utility * * @var \Bake\Utility\Model\AssociationFilter|null */ - protected $_associationFilter = null; + protected ?AssociationFilter $_associationFilter = null; /** * Used for generating formatted properties such as component and helper arrays @@ -60,74 +65,6 @@ public function arrayProperty(string $name, array $value = [], array $options = return $this->_View->element('array_property', $options); } - /** - * Returns an array converted into a formatted multiline string - * - * @param array $list array of items to be stringified - * @param array $options options to use - * @return string - * @deprecated 2.5.0 Use BakeHelper::exportVar() instead. - */ - public function stringifyList(array $list, array $options = []): string - { - $defaults = [ - 'indent' => 2, - 'tab' => ' ', - 'trailingComma' => !isset($options['indent']) || $options['indent'] ? true : false, - 'quotes' => true, - ]; - $options += $defaults; - - if (!$list) { - return ''; - } - - foreach ($list as $k => &$v) { - if ($options['quotes']) { - $v = "'$v'"; - } - if (!is_numeric($k)) { - $nestedOptions = $options; - if ($nestedOptions['indent']) { - $nestedOptions['indent'] += 1; - } - if (is_array($v)) { - $v = sprintf( - "'%s' => [%s]", - $k, - $this->stringifyList($v, $nestedOptions) - ); - } else { - $v = "'$k' => $v"; - } - } elseif (is_array($v)) { - $nestedOptions = $options; - if ($nestedOptions['indent']) { - $nestedOptions['indent'] += 1; - } - $v = sprintf( - '[%s]', - $this->stringifyList($v, $nestedOptions) - ); - } - } - - $start = $end = ''; - $join = ', '; - if ($options['indent']) { - $join = ','; - $start = "\n" . str_repeat($options['tab'], $options['indent']); - $join .= $start; - $end = "\n" . str_repeat($options['tab'], $options['indent'] - 1); - } - - if ($options['trailingComma'] && $options['indent'] > 0) { - $end = ',' . $end; - } - - return $start . implode($join, $list) . $end; - } - /** * Export variable to string representation. * @@ -140,7 +77,7 @@ public function stringifyList(array $list, array $options = []): string * @throws \Brick\VarExporter\ExportException * @see https://github.com/brick/varexporter#options */ - public function exportVar($var, int $indentLevel = 0, int $options = 0): string + public function exportVar(mixed $var, int $indentLevel = 0, int $options = 0): string { $options |= VarExporter::TRAILING_COMMA_IN_ARRAY; @@ -161,7 +98,7 @@ public function exportArray(array $var, int $indentLevel = 0, bool $inline = tru { $options = 0; if ($inline) { - $options = VarExporter::INLINE_NUMERIC_SCALAR_ARRAY; + $options = VarExporter::INLINE_SCALAR_LIST; } return $this->exportVar($var, $indentLevel, $options); @@ -173,7 +110,7 @@ public function exportArray(array $var, int $indentLevel = 0, bool $inline = tru * * @param \Cake\ORM\Table $table object to find associations on * @param string $assoc association to extract - * @return string[] + * @return array */ public function aliasExtractor(Table $table, string $assoc): array { @@ -207,7 +144,7 @@ public function aliasExtractor(Table $table, string $assoc): array */ public function classInfo(string $class, string $type, string $suffix): array { - [$plugin, $name] = \pluginSplit($class); + [$plugin, $name] = pluginSplit($class); $base = Configure::read('App.namespace'); if ($plugin !== null) { @@ -231,6 +168,16 @@ public function classInfo(string $class, string $type, string $suffix): array ]; } + /** + * Check if the current application has a plugin installed + * + * @param string $plugin The plugin name to check for. + */ + public function hasPlugin(string $plugin): bool + { + return Plugin::isLoaded($plugin); + } + /** * Return list of fields to generate controls for. * @@ -245,8 +192,8 @@ public function filterFields( array $fields, SchemaInterface $schema, ?Table $modelObject = null, - $takeFields = 0, - array $filterTypes = ['binary'] + string|int $takeFields = 0, + array $filterTypes = ['binary'], ): array { $fields = collection($fields) ->filter(function ($field) use ($schema, $filterTypes) { @@ -279,7 +226,7 @@ public function getViewFieldsData(array $fields, SchemaInterface $schema, array $immediateAssociations = $associations['BelongsTo']; $associationFields = collection($fields) ->map(function ($field) use ($immediateAssociations) { - foreach ($immediateAssociations as $alias => $details) { + foreach ($immediateAssociations as $details) { if ($field === $details['foreignKey']) { return [$field => $details]; } @@ -299,6 +246,9 @@ public function getViewFieldsData(array $fields, SchemaInterface $schema, array if (isset($associationFields[$field])) { return 'string'; } + if ($type && str_starts_with($type, 'enum-')) { + return 'enum'; + } $numberTypes = ['decimal', 'biginteger', 'integer', 'float', 'smallinteger', 'tinyinteger']; if (in_array($type, $numberTypes, true)) { return 'number'; @@ -324,6 +274,7 @@ public function getViewFieldsData(array $fields, SchemaInterface $schema, array 'number' => [], 'string' => [], 'boolean' => [], + 'enum' => [], 'date' => [], 'text' => [], ]; @@ -343,6 +294,26 @@ public function columnData(string $field, TableSchema $schema): ?array return $schema->getColumn($field); } + /** + * Check if a column is both an enum, and the mapped enum implements `label()` as a method. + * + * @param string $field the field to check + * @param \Cake\Database\Schema\TableSchema $schema The table schema to read from. + * @return bool + */ + public function enumSupportsLabel(string $field, TableSchema $schema): bool + { + $typeName = $schema->getColumnType($field); + if (!str_starts_with($typeName, 'enum-')) { + return false; + } + $type = TypeFactory::build($typeName); + assert($type instanceof EnumType); + $enumClass = $type->getEnumClassName(); + + return method_exists($enumClass, 'label'); + } + /** * Get alias of associated table. * @@ -362,7 +333,7 @@ public function getAssociatedTableAlias(Table $modelObj, string $assoc): string * * @param string $field Field name. * @param array $rules Validation rules list. - * @return string[] + * @return array */ public function getValidationMethods(string $field, array $rules): array { @@ -380,7 +351,7 @@ public function getValidationMethods(string $field, array $rules): array $field, $ruleName, $rule['rule'], - $rule['provider'] + $rule['provider'], ); continue; } @@ -389,7 +360,7 @@ public function getValidationMethods(string $field, array $rules): array $validationMethods[] = sprintf( "->%s('%s')", $rule['rule'], - $field + $field, ); continue; } @@ -398,14 +369,14 @@ public function getValidationMethods(string $field, array $rules): array return $this->exportVar( $item, is_array($item) ? 3 : 0, - VarExporter::INLINE_NUMERIC_SCALAR_ARRAY + VarExporter::INLINE_SCALAR_LIST, ); }, $rule['args']); $validationMethods[] = sprintf( "->%s('%s', %s)", $rule['rule'], $field, - implode(', ', $rule['args']) + implode(', ', $rule['args']), ); } @@ -415,11 +386,11 @@ public function getValidationMethods(string $field, array $rules): array /** * Get field accessibility data. * - * @param string[]|false|null $fields Fields list. - * @param string[]|null $primaryKey Primary key. + * @param array|false|null $fields Fields list. + * @param array|null $primaryKey Primary key. * @return array */ - public function getFieldAccessibility($fields = null, $primaryKey = null): array + public function getFieldAccessibility(array|false|null $fields = null, ?array $primaryKey = null): array { $accessible = []; @@ -543,7 +514,7 @@ public function concat( string $delimiter, array $strings, string $prefix = '', - string $suffix = '' + string $suffix = '', ): string { $output = implode( $delimiter, @@ -553,7 +524,7 @@ public function concat( } return implode($delimiter, array_filter($string)); - }, array_filter($strings)) + }, array_filter($strings)), ); if ($prefix && !empty($output)) { @@ -570,8 +541,8 @@ public function concat( * To be mocked elsewhere... * * @param \Cake\ORM\Table $table Table - * @param string[] $aliases array of aliases - * @return string[] + * @param array $aliases array of aliases + * @return array */ protected function _filterHasManyAssociationsAliases(Table $table, array $aliases): array { diff --git a/app/vendor/cakephp/bake/src/View/Helper/DocBlockHelper.php b/app/vendor/cakephp/bake/src/View/Helper/DocBlockHelper.php index 456396bc5..05b988f1a 100644 --- a/app/vendor/cakephp/bake/src/View/Helper/DocBlockHelper.php +++ b/app/vendor/cakephp/bake/src/View/Helper/DocBlockHelper.php @@ -5,6 +5,7 @@ use Cake\Collection\Collection; use Cake\Core\App; +use Cake\Database\Type\EnumType; use Cake\Database\TypeFactory; use Cake\ORM\Association; use Cake\Utility\Inflector; @@ -18,7 +19,7 @@ class DocBlockHelper extends Helper /** * @var bool Whether to add a blank line between different class annotations */ - protected $_annotationSpacing = true; + protected bool $_annotationSpacing = true; /** * Writes the DocBlock header for a class which includes the property and method declarations. Annotations are @@ -88,7 +89,7 @@ public function associatedEntityTypeToHintType(string $type, Association $associ * in generating `@property` hints. * * This method expects a property schema as generated by - * `\Bake\Shell\Task\ModelTask::getEntityPropertySchema()`. + * `\Bake\Command\ModelCommand::getEntityPropertySchema()`. * * The generated map has the format of * @@ -99,7 +100,7 @@ public function associatedEntityTypeToHintType(string $type, Association $associ * ] * ``` * - * @see \Bake\Shell\Task\ModelTask::getEntityPropertySchema + * @see \Bake\Command\ModelCommand::getEntityPropertySchema * @param array $propertySchema The property schema to use for generating the type map. * @return array The property DocType map. */ @@ -125,7 +126,7 @@ public function buildEntityPropertyHintTypeMap(array $propertySchema): array * in generating `@property` hints. * * This method expects a property schema as generated by - * `\Bake\Shell\Task\ModelTask::getEntityPropertySchema()`. + * `\Bake\Command\ModelCommand::getEntityPropertySchema()`. * * The generated map has the format of * @@ -136,7 +137,7 @@ public function buildEntityPropertyHintTypeMap(array $propertySchema): array * ] * ``` * - * @see \Bake\Shell\Task\ModelTask::getEntityPropertySchema + * @see \Bake\Command\ModelCommand::getEntityPropertySchema * @param array $propertySchema The property schema to use for generating the type map. * @return array The property DocType map. */ @@ -150,7 +151,7 @@ public function buildEntityAssociationHintTypeMap(array $propertySchema): array $properties = $this->_insertAfter( $properties, $info['association']->getForeignKey(), - [$property => $type] + [$property => $type], ); } else { $properties[$property] = $type; @@ -201,18 +202,43 @@ public function columnTypeToHintType(string $type): ?string return 'string|resource'; case 'date': + $dbType = TypeFactory::build($type); + if (method_exists($dbType, 'getDateClassName')) { + // allow custom Date class which should extend \Cake\I18n\Date + return '\\' . $dbType->getDateClassName(); + } + + return '\Cake\I18n\Date'; + case 'datetime': case 'datetimefractional': - case 'time': case 'timestamp': case 'timestampfractional': case 'timestamptimezone': $dbType = TypeFactory::build($type); if (method_exists($dbType, 'getDateTimeClassName')) { + // allow custom DateTime class which should extend \Cake\I18n\DateTime return '\\' . $dbType->getDateTimeClassName(); } + return '\Cake\I18n\DateTime'; + + case 'time': + $dbType = TypeFactory::build($type); + if (method_exists($dbType, 'getTimeClassName')) { + // allow custom Time class which should extend \Cake\I18n\Time + return '\\' . $dbType->getTimeClassName(); + } + return '\Cake\I18n\Time'; + + default: + if (str_starts_with($type, 'enum-')) { + $dbType = TypeFactory::build($type); + if ($dbType instanceof EnumType) { + return '\\' . $dbType->getEnumClassName(); + } + } } // Any unique or custom types will have a `string` type hint @@ -223,8 +249,8 @@ public function columnTypeToHintType(string $type): ?string * Renders a map of DocBlock property types as an array of * `@property` hints. * - * @param string[] $properties A key value pair where key is the name of a property and the value is the type. - * @return string[] + * @param array $properties A key value pair where key is the name of a property and the value is the type. + * @return array */ public function propertyHints(array $properties): array { @@ -245,14 +271,14 @@ public function propertyHints(array $properties): array * @param array $behaviors Behaviors list. * @param string $entity Entity name. * @param string $namespace Namespace. - * @return string[] + * @return array */ public function buildTableAnnotations( array $associations, array $associationInfo, array $behaviors, string $entity, - string $namespace + string $namespace, ): array { $annotations = []; foreach ($associations as $type => $assocs) { @@ -267,17 +293,17 @@ public function buildTableAnnotations( // phpcs:disable $annotations[] = "@method \\{$namespace}\\Model\\Entity\\{$entity} newEmptyEntity()"; $annotations[] = "@method \\{$namespace}\\Model\\Entity\\{$entity} newEntity(array \$data, array \$options = [])"; - $annotations[] = "@method \\{$namespace}\\Model\\Entity\\{$entity}[] newEntities(array \$data, array \$options = [])"; - $annotations[] = "@method \\{$namespace}\\Model\\Entity\\{$entity} get(\$primaryKey, \$options = [])"; - $annotations[] = "@method \\{$namespace}\\Model\\Entity\\{$entity} findOrCreate(\$search, ?callable \$callback = null, \$options = [])"; + $annotations[] = "@method array<\\{$namespace}\\Model\\Entity\\{$entity}> newEntities(array \$data, array \$options = [])"; + $annotations[] = "@method \\{$namespace}\\Model\\Entity\\{$entity} get(mixed \$primaryKey, array|string \$finder = 'all', \\Psr\\SimpleCache\\CacheInterface|string|null \$cache = null, \Closure|string|null \$cacheKey = null, mixed ...\$args)"; + $annotations[] = "@method \\{$namespace}\\Model\\Entity\\{$entity} findOrCreate(\$search, ?callable \$callback = null, array \$options = [])"; $annotations[] = "@method \\{$namespace}\\Model\\Entity\\{$entity} patchEntity(\\Cake\\Datasource\\EntityInterface \$entity, array \$data, array \$options = [])"; - $annotations[] = "@method \\{$namespace}\\Model\\Entity\\{$entity}[] patchEntities(iterable \$entities, array \$data, array \$options = [])"; - $annotations[] = "@method \\{$namespace}\\Model\\Entity\\{$entity}|false save(\\Cake\\Datasource\\EntityInterface \$entity, \$options = [])"; - $annotations[] = "@method \\{$namespace}\\Model\\Entity\\{$entity} saveOrFail(\\Cake\\Datasource\\EntityInterface \$entity, \$options = [])"; - $annotations[] = "@method \\{$namespace}\\Model\\Entity\\{$entity}[]|\Cake\Datasource\ResultSetInterface|false saveMany(iterable \$entities, \$options = [])"; - $annotations[] = "@method \\{$namespace}\\Model\\Entity\\{$entity}[]|\Cake\Datasource\ResultSetInterface saveManyOrFail(iterable \$entities, \$options = [])"; - $annotations[] = "@method \\{$namespace}\\Model\\Entity\\{$entity}[]|\Cake\Datasource\ResultSetInterface|false deleteMany(iterable \$entities, \$options = [])"; - $annotations[] = "@method \\{$namespace}\\Model\\Entity\\{$entity}[]|\Cake\Datasource\ResultSetInterface deleteManyOrFail(iterable \$entities, \$options = [])"; + $annotations[] = "@method array<\\{$namespace}\\Model\\Entity\\{$entity}> patchEntities(iterable \$entities, array \$data, array \$options = [])"; + $annotations[] = "@method \\{$namespace}\\Model\\Entity\\{$entity}|false save(\\Cake\\Datasource\\EntityInterface \$entity, array \$options = [])"; + $annotations[] = "@method \\{$namespace}\\Model\\Entity\\{$entity} saveOrFail(\\Cake\\Datasource\\EntityInterface \$entity, array \$options = [])"; + $annotations[] = "@method iterable<\\{$namespace}\\Model\\Entity\\{$entity}>|\Cake\Datasource\ResultSetInterface<\\{$namespace}\\Model\\Entity\\{$entity}>|false saveMany(iterable \$entities, array \$options = [])"; + $annotations[] = "@method iterable<\\{$namespace}\\Model\\Entity\\{$entity}>|\Cake\Datasource\ResultSetInterface<\\{$namespace}\\Model\\Entity\\{$entity}> saveManyOrFail(iterable \$entities, array \$options = [])"; + $annotations[] = "@method iterable<\\{$namespace}\\Model\\Entity\\{$entity}>|\Cake\Datasource\ResultSetInterface<\\{$namespace}\\Model\\Entity\\{$entity}>|false deleteMany(iterable \$entities, array \$options = [])"; + $annotations[] = "@method iterable<\\{$namespace}\\Model\\Entity\\{$entity}>|\Cake\Datasource\ResultSetInterface<\\{$namespace}\\Model\\Entity\\{$entity}> deleteManyOrFail(iterable \$entities, array \$options = [])"; // phpcs:enable foreach ($behaviors as $behavior => $behaviorData) { $className = App::className($behavior, 'Model/Behavior', 'Behavior'); @@ -301,14 +327,14 @@ public function buildTableAnnotations( * @param mixed $value The entry to insert. * @return array The array with the new value inserted. */ - protected function _insertAfter(array $target, string $key, $value): array + protected function _insertAfter(array $target, string $key, mixed $value): array { $index = array_search($key, array_keys($target)); if ($index !== false) { $target = array_merge( array_slice($target, 0, $index + 1), $value, - array_slice($target, $index + 1, null) + array_slice($target, $index + 1, null), ); } else { $target += (array)$value; diff --git a/app/vendor/cakephp/bake/templates/bake/Command/command.twig b/app/vendor/cakephp/bake/templates/bake/Command/command.twig index ab52d71d6..9580b3fa4 100644 --- a/app/vendor/cakephp/bake/templates/bake/Command/command.twig +++ b/app/vendor/cakephp/bake/templates/bake/Command/command.twig @@ -28,18 +28,44 @@ */ class {{ name }}Command extends Command { + /** + * The name of this command. + * + * @var string + */ + protected string $name = '{{ command_name }}'; + + /** + * Get the default command name. + * + * @return string + */ + public static function defaultName(): string + { + return '{{ command_name }}'; + } + + /** + * Get the command description. + * + * @return string + */ + public static function getDescription(): string + { + return 'Command description here.'; + } + /** * Hook method for defining this command's option parser. * - * @see https://book.cakephp.org/4/en/console-commands/commands.html#defining-arguments-and-options + * @see https://book.cakephp.org/5/en/console-commands/commands.html#defining-arguments-and-options * @param \Cake\Console\ConsoleOptionParser $parser The parser to be defined * @return \Cake\Console\ConsoleOptionParser The built parser. */ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser { - $parser = parent::buildOptionParser($parser); - - return $parser; + return parent::buildOptionParser($parser) + ->setDescription(static::getDescription()); } /** @@ -47,7 +73,7 @@ class {{ name }}Command extends Command * * @param \Cake\Console\Arguments $args The command arguments. * @param \Cake\Console\ConsoleIo $io The console io - * @return null|void|int The exit code or null for success + * @return int|null|void The exit code or null for success */ public function execute(Arguments $args, ConsoleIo $io) { diff --git a/app/vendor/cakephp/bake/templates/bake/Controller/component.twig b/app/vendor/cakephp/bake/templates/bake/Controller/component.twig index 8f4b42bbe..f7861b8da 100644 --- a/app/vendor/cakephp/bake/templates/bake/Controller/component.twig +++ b/app/vendor/cakephp/bake/templates/bake/Controller/component.twig @@ -31,5 +31,5 @@ class {{ name }}Component extends Component * * @var array */ - protected $_defaultConfig = []; + protected array $_defaultConfig = []; } diff --git a/app/vendor/cakephp/bake/templates/bake/Controller/controller.twig b/app/vendor/cakephp/bake/templates/bake/Controller/controller.twig index 894c2cda9..83a843e1c 100644 --- a/app/vendor/cakephp/bake/templates/bake/Controller/controller.twig +++ b/app/vendor/cakephp/bake/templates/bake/Controller/controller.twig @@ -21,6 +21,8 @@ namespace: "#{namespace}\\Controller#{prefix}", classImports: (plugin or prefix) ? ["#{baseNamespace}\\Controller\\AppController"] : [], }) }} +{% set has_login = 'login' in actions and Bake.hasPlugin('Authentication') %} +{% set include_initialize = components or helpers or has_login %} /** * {{ name }} Controller @@ -33,14 +35,10 @@ {% set classInfo = Bake.classInfo(component, 'Controller/Component', 'Component') %} * @property {{ classInfo.fqn }} ${{ classInfo.name }} {% endfor %} - -{%- if 'index' in actions %} - * @method \{{ namespace }}\Model\Entity\{{ entityClassName }}[]|\Cake\Datasource\ResultSetInterface paginate($object = null, array $settings = []) -{% endif %} */ class {{ name }}Controller extends AppController { -{% if components or helpers %} +{% if include_initialize %} /** * Initialize controller * @@ -55,6 +53,9 @@ class {{ name }}Controller extends AppController {% endfor %} {% if helpers %} $this->viewBuilder()->setHelpers({{ Bake.exportArray(helpers)|raw }}); +{% endif %} +{% if has_login %} + $this->Authentication->allowUnauthenticated(['login']); {% endif %} } {% if actions|length %}{{ "\n" }}{% endif %} diff --git a/app/vendor/cakephp/bake/templates/bake/Mailer/mailer.twig b/app/vendor/cakephp/bake/templates/bake/Mailer/mailer.twig index 55c01a532..88a442d05 100644 --- a/app/vendor/cakephp/bake/templates/bake/Mailer/mailer.twig +++ b/app/vendor/cakephp/bake/templates/bake/Mailer/mailer.twig @@ -30,5 +30,5 @@ class {{ name }}Mailer extends Mailer * * @var string */ - public static $name = '{{ name }}'; + public static string $name = '{{ name }}'; } diff --git a/app/vendor/cakephp/bake/templates/bake/Model/behavior.twig b/app/vendor/cakephp/bake/templates/bake/Model/behavior.twig index 5a40c4a69..c314e7f96 100644 --- a/app/vendor/cakephp/bake/templates/bake/Model/behavior.twig +++ b/app/vendor/cakephp/bake/templates/bake/Model/behavior.twig @@ -31,5 +31,5 @@ class {{ name }}Behavior extends Behavior * * @var array */ - protected $_defaultConfig = []; + protected array $_defaultConfig = []; } diff --git a/app/vendor/cakephp/bake/templates/bake/Model/entity.twig b/app/vendor/cakephp/bake/templates/bake/Model/entity.twig index 68d599731..2524788ee 100644 --- a/app/vendor/cakephp/bake/templates/bake/Model/entity.twig +++ b/app/vendor/cakephp/bake/templates/bake/Model/entity.twig @@ -49,7 +49,7 @@ class {{ name }} extends Entity{{ fileBuilder.classBuilder.implements ? ' implem * * @var array */ - protected $_accessible = {{ Bake.exportVar(accessible, 1)|raw }}; + protected array $_accessible = {{ Bake.exportVar(accessible, 1)|raw }}; {% endif %} {% if accessible and hidden %} @@ -59,9 +59,9 @@ class {{ name }} extends Entity{{ fileBuilder.classBuilder.implements ? ' implem /** * Fields that are excluded from JSON versions of the entity. * - * @var array + * @var list */ - protected $_hidden = {{ Bake.exportVar(hidden, 1)|raw }}; + protected array $_hidden = {{ Bake.exportVar(hidden, 1)|raw }}; {% endif %} {% set userProperties = fileBuilder.classBuilder.userProperties(generatedProperties) %} {% if userProperties %} diff --git a/app/vendor/cakephp/bake/templates/bake/Model/enum.twig b/app/vendor/cakephp/bake/templates/bake/Model/enum.twig new file mode 100644 index 000000000..83f48243a --- /dev/null +++ b/app/vendor/cakephp/bake/templates/bake/Model/enum.twig @@ -0,0 +1,38 @@ +{# +/** + * CakePHP(tm) : Rapid Development Framework (https://cakephp.org) + * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) + * + * Licensed under The MIT License + * For full copyright and license information, please see the LICENSE.txt + * Redistributions of files must retain the above copyright notice. + * + * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) + * @link https://cakephp.org CakePHP(tm) Project + * @since 3.1.0 + * @license https://www.opensource.org/licenses/mit-license.php MIT License + */ +#} +{{ element('Bake.file_header', { + namespace: "#{namespace}\\Model\\Enum", + classImports: [ + 'Cake\\Database\\Type\\EnumLabelInterface', + 'Cake\\Utility\\Inflector', + ], +}) }} + +{{ DocBlock.classDescription(name, 'Enum', [])|raw }} +enum {{ name }}: {{ backingType }} implements EnumLabelInterface +{ +{% if cases %} + {{ Bake.concat('\n ', cases) }} + +{% endif %} + /** + * @return string + */ + public function label(): string + { + return Inflector::humanize(Inflector::underscore($this->name)); + } +} diff --git a/app/vendor/cakephp/bake/templates/bake/Model/table.twig b/app/vendor/cakephp/bake/templates/bake/Model/table.twig index 7c213e0dc..acd786fae 100644 --- a/app/vendor/cakephp/bake/templates/bake/Model/table.twig +++ b/app/vendor/cakephp/bake/templates/bake/Model/table.twig @@ -17,7 +17,7 @@ {% set generatedFunctions = ['initialize'] %} {{ element('Bake.file_header', { namespace: fileBuilder.namespace, - classImports: fileBuilder.classImports(['Cake\\ORM\\Query', 'Cake\\ORM\\RulesChecker', 'Cake\\ORM\\Table', 'Cake\\Validation\\Validator']), + classImports: fileBuilder.classImports(['Cake\\ORM\\Query\\SelectQuery', 'Cake\\ORM\\RulesChecker', 'Cake\\ORM\\Table', 'Cake\\Validation\\Validator']), }) }} {{ DocBlock.classDescription(name, 'Model', annotations)|raw }} @@ -36,7 +36,7 @@ class {{ name }}Table extends Table{{ fileBuilder.classBuilder.implements ? ' im /** * Initialize method * - * @param array $config The configuration for the Table. + * @param array $config The configuration for the Table. * @return void */ public function initialize(array $config): void @@ -61,6 +61,17 @@ class {{ name }}Table extends Table{{ fileBuilder.classBuilder.implements ? ' im {%- endif %} {% endif %} +{%- if enums %} + +{% endif %} + +{%- if enums %} + +{%- for name, className in enums %} + $this->getSchema()->setColumnType('{{ name }}', \Cake\Database\Type\EnumType::from(\{{ className }}::class)); +{% endfor %} +{% endif %} + {%- if behaviors %} {% endif %} @@ -128,13 +139,13 @@ class {{ name }}Table extends Table{{ fileBuilder.classBuilder.implements ? ' im */ public function buildRules(RulesChecker $rules): RulesChecker { -{% for field, rule in rulesChecker %} -{% set fields = rule.fields is defined ? Bake.exportArray(rule.fields) : Bake.exportVar(field) %} +{% for rule in rulesChecker %} +{% set fields = Bake.exportArray(rule.fields) %} {% set options = '' %} {% for optionName, optionValue in rule.options %} {%~ set options = (loop.first ? '[' : options) ~ "'#{optionName}' => " ~ Bake.exportVar(optionValue) ~ (loop.last ? ']' : ', ') %} {% endfor %} - $rules->add($rules->{{ rule.name }}({{ fields|raw }}{{ (rule.extra|default ? ", '#{rule.extra}'" : '')|raw }}{{ (options ? ', ' ~ options : '')|raw }}), ['errorField' => '{{ field }}']); + $rules->add($rules->{{ rule.name }}({{ fields|raw }}{{ (rule.extra|default ? ", '#{rule.extra}'" : '')|raw }}{{ (options ? ', ' ~ options : '')|raw }}), ['errorField' => '{{ rule.fields[0] }}']); {% endfor %} return $rules; diff --git a/app/vendor/cakephp/bake/templates/bake/Plugin/composer.json.twig b/app/vendor/cakephp/bake/templates/bake/Plugin/composer.json.twig index 75e0699f3..a4f95ba7f 100644 --- a/app/vendor/cakephp/bake/templates/bake/Plugin/composer.json.twig +++ b/app/vendor/cakephp/bake/templates/bake/Plugin/composer.json.twig @@ -20,11 +20,11 @@ "type": "cakephp-plugin", "license": "MIT", "require": { - "php": ">=7.2", + "php": ">=8.1", "cakephp/cakephp": "{{ cakeVersion|raw }}" }, "require-dev": { - "phpunit/phpunit": "^8.5 || ^9.3" + "phpunit/phpunit": "^10.1" }, "autoload": { "psr-4": { diff --git a/app/vendor/cakephp/bake/templates/bake/Plugin/phpunit.xml.dist.twig b/app/vendor/cakephp/bake/templates/bake/Plugin/phpunit.xml.dist.twig index 7a6f6e717..ae6b5c53f 100644 --- a/app/vendor/cakephp/bake/templates/bake/Plugin/phpunit.xml.dist.twig +++ b/app/vendor/cakephp/bake/templates/bake/Plugin/phpunit.xml.dist.twig @@ -34,12 +34,12 @@ - + - - + + src/ - - + + diff --git a/app/vendor/cakephp/bake/templates/bake/Plugin/src/Plugin.php.twig b/app/vendor/cakephp/bake/templates/bake/Plugin/src/Plugin.php.twig index a7d1575ac..e1982d0f2 100644 --- a/app/vendor/cakephp/bake/templates/bake/Plugin/src/Plugin.php.twig +++ b/app/vendor/cakephp/bake/templates/bake/Plugin/src/Plugin.php.twig @@ -41,6 +41,7 @@ class {{ name }}Plugin extends BasePlugin */ public function bootstrap(PluginApplicationInterface $app): void { + // remove this method hook if you don't need it } /** @@ -54,6 +55,7 @@ class {{ name }}Plugin extends BasePlugin */ public function routes(RouteBuilder $routes): void { + // remove this method hook if you don't need it $routes->plugin( '{{ plugin }}', ['path' => '/{{ routePath }}'], @@ -75,6 +77,7 @@ class {{ name }}Plugin extends BasePlugin public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue { // Add your middlewares here + // remove this method hook if you don't need it return $middlewareQueue; } @@ -88,6 +91,7 @@ class {{ name }}Plugin extends BasePlugin public function console(CommandCollection $commands): CommandCollection { // Add your commands here + // remove this method hook if you don't need it $commands = parent::console($commands); @@ -99,10 +103,11 @@ class {{ name }}Plugin extends BasePlugin * * @param \Cake\Core\ContainerInterface $container The Container to update. * @return void - * @link https://book.cakephp.org/4/en/development/dependency-injection.html#dependency-injection + * @link https://book.cakephp.org/5/en/development/dependency-injection.html#dependency-injection */ public function services(ContainerInterface $container): void { // Add your services here + // remove this method hook if you don't need it } } diff --git a/app/vendor/cakephp/bake/templates/bake/Shell/helper.twig b/app/vendor/cakephp/bake/templates/bake/Shell/helper.twig deleted file mode 100644 index 799299feb..000000000 --- a/app/vendor/cakephp/bake/templates/bake/Shell/helper.twig +++ /dev/null @@ -1,39 +0,0 @@ -{# -/** - * CakePHP(tm) : Rapid Development Framework (https://cakephp.org) - * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) - * - * Licensed under The MIT License - * For full copyright and license information, please see the LICENSE.txt - * Redistributions of files must retain the above copyright notice. - * - * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) - * @link https://cakephp.org CakePHP(tm) Project - * @since 2.0.0 - * @license https://www.opensource.org/licenses/mit-license.php MIT License - */ -#} -{{ element('Bake.file_header', { - namespace: "#{namespace}\\Shell\\Helper", - classImports: [ - 'Cake\Console\Helper', - ], -}) }} - -/** - * {{ name }} shell helper. - */ -class {{ name }}Helper extends Helper -{ - /** - * Output method. - * - * Generate the output for this shell helper. - * - * @param array $args Arguments. - * @return void - */ - public function output(array $args): void - { - } -} diff --git a/app/vendor/cakephp/bake/templates/bake/Template/index.twig b/app/vendor/cakephp/bake/templates/bake/Template/index.twig index 5748cbb83..7cab8aa11 100644 --- a/app/vendor/cakephp/bake/templates/bake/Template/index.twig +++ b/app/vendor/cakephp/bake/templates/bake/Template/index.twig @@ -43,13 +43,16 @@ {% for alias, details in associations.BelongsTo %} {% if field == details.foreignKey %} {% set isKey = true %} - has('{{ details.property }}') ? $this->Html->link(${{ singularVar }}->{{ details.property }}->{{ details.displayField }}, ['controller' => '{{ details.controller }}', 'action' => 'view', ${{ singularVar }}->{{ details.property }}->{{ details.primaryKey[0] }}]) : '' ?> + hasValue('{{ details.property }}') ? $this->Html->link(${{ singularVar }}->{{ details.property }}->{{ details.displayField }}, ['controller' => '{{ details.controller }}', 'action' => 'view', ${{ singularVar }}->{{ details.property }}->{{ details.primaryKey[0] }}]) : '' ?> {% endif %} {% endfor %} {% endif %} {% if isKey is not same as(true) %} {% set columnData = Bake.columnData(field, schema) %} -{% if columnData.type not in ['integer', 'float', 'decimal', 'biginteger', 'smallinteger', 'tinyinteger'] %} +{% set supportsLabel = Bake.enumSupportsLabel(field, schema) %} +{% if columnData.type starts with 'enum-' %} + {{ field }} === null ? '' : h(${{ singularVar }}->{{ field }}->{% if supportsLabel %}label(){% else %}value{% endif %}) ?> +{% elseif columnData.type not in ['integer', 'float', 'decimal', 'biginteger', 'smallinteger', 'tinyinteger'] %} {{ field }}) ?> {% elseif columnData.null %} {{ field }} === null ? '' : $this->Number->format(${{ singularVar }}->{{ field }}) ?> @@ -62,7 +65,14 @@ Html->link(__('View'), ['action' => 'view', {{ pk|raw }}]) ?> Html->link(__('Edit'), ['action' => 'edit', {{ pk|raw }}]) ?> - Form->postLink(__('Delete'), ['action' => 'delete', {{ pk|raw }}], ['confirm' => __('Are you sure you want to delete # {0}?', {{ pk|raw }})]) ?> + Form->postLink( + __('Delete'), + ['action' => 'delete', {{ pk|raw }}], + [ + 'method' => 'delete', + 'confirm' => __('Are you sure you want to delete # {0}?', {{ pk|raw }}), + ] + ) ?> @@ -79,4 +89,4 @@

Paginator->counter(__('Page {{ '{{' }}page{{ '}}' }} of {{ '{{' }}pages{{ '}}' }}, showing {{ '{{' }}current{{ '}}' }} record(s) out of {{ '{{' }}count{{ '}}' }} total')) ?>

- + \ No newline at end of file diff --git a/app/vendor/cakephp/bake/templates/bake/Template/view.twig b/app/vendor/cakephp/bake/templates/bake/Template/view.twig index 0e75b8a41..de4141355 100644 --- a/app/vendor/cakephp/bake/templates/bake/Template/view.twig +++ b/app/vendor/cakephp/bake/templates/bake/Template/view.twig @@ -35,7 +35,7 @@ {% set done = [] %} -
+

{{ displayField }}) ?>

@@ -45,7 +45,7 @@ {% set details = associationFields[field] %} - + {% else %} @@ -59,7 +59,7 @@ {% for alias, details in associations.HasOne %} - + {% endfor %} {% endif %} @@ -76,6 +76,20 @@ {% endfor %} {% endif %} +{% if groupedFields.enum %} +{% for field in groupedFields.enum %} + + +{% set columnData = Bake.columnData(field, schema) %} +{% set supportsLabel = Bake.enumSupportsLabel(field, schema) %} +{% if columnData.null %} + +{% else %} + +{% endif %} + +{% endfor %} +{% endif %} {% if groupedFields.date %} {% for field in groupedFields.date %} @@ -105,7 +119,7 @@ {% endif %} {% set relations = associations.BelongsToMany|merge(associations.HasMany) %} {% for alias, details in relations %} -{% set otherSingularVar = alias|variable %} +{% set otherSingularVar = alias|singularize|variable %} {% set otherPluralHumanName = details.controller|underscore|humanize %} @@ -138,4 +159,4 @@ {% endfor %} - + \ No newline at end of file diff --git a/app/vendor/cakephp/bake/templates/bake/View/cell.twig b/app/vendor/cakephp/bake/templates/bake/View/cell.twig index fa73562e3..922ba54e7 100644 --- a/app/vendor/cakephp/bake/templates/bake/View/cell.twig +++ b/app/vendor/cakephp/bake/templates/bake/View/cell.twig @@ -31,7 +31,7 @@ class {{ name }}Cell extends Cell * * @var array */ - protected $_validCellOptions = []; + protected array $_validCellOptions = []; /** * Initialization logic run at the end of object construction. diff --git a/app/vendor/cakephp/bake/templates/bake/View/helper.twig b/app/vendor/cakephp/bake/templates/bake/View/helper.twig index f32145f34..e47adae6e 100644 --- a/app/vendor/cakephp/bake/templates/bake/View/helper.twig +++ b/app/vendor/cakephp/bake/templates/bake/View/helper.twig @@ -31,6 +31,5 @@ class {{ name }}Helper extends Helper * * @var array */ - protected $_defaultConfig = []; - + protected array $_defaultConfig = []; } diff --git a/app/vendor/cakephp/bake/templates/bake/element/Controller/add.twig b/app/vendor/cakephp/bake/templates/bake/element/Controller/add.twig index 72f26cb05..081d1c79f 100644 --- a/app/vendor/cakephp/bake/templates/bake/element/Controller/add.twig +++ b/app/vendor/cakephp/bake/templates/bake/element/Controller/add.twig @@ -22,6 +22,9 @@ public function add() { ${{ singularName }} = $this->{{ currentModelName }}->newEmptyEntity(); +{% if Bake.hasPlugin('Authorization') %} + $this->Authorization->authorize(${{ singularName }}); +{% endif %} if ($this->request->is('post')) { ${{ singularName }} = $this->{{ currentModelName }}->patchEntity(${{ singularName }}, $this->request->getData()); if ($this->{{ currentModelName }}->save(${{ singularName }})) { @@ -37,7 +40,7 @@ {%- for assoc in associations %} {%- set otherName = Bake.getAssociatedTableAlias(modelObj, assoc) %} {%- set otherPlural = otherName|variable %} - ${{ otherPlural }} = $this->{{ currentModelName }}->{{ otherName }}->find('list', ['limit' => 200])->all(); + ${{ otherPlural }} = $this->{{ currentModelName }}->{{ otherName }}->find('list', limit: 200)->all(); {{- "\n" }} {%- set compact = compact|merge(["'#{otherPlural}'"]) %} {% endfor %} diff --git a/app/vendor/cakephp/bake/templates/bake/element/Controller/delete.twig b/app/vendor/cakephp/bake/templates/bake/element/Controller/delete.twig index 67bd8fc71..bab93c72a 100644 --- a/app/vendor/cakephp/bake/templates/bake/element/Controller/delete.twig +++ b/app/vendor/cakephp/bake/templates/bake/element/Controller/delete.twig @@ -17,13 +17,16 @@ * Delete method * * @param string|null $id {{ singularHumanName }} id. - * @return \Cake\Http\Response|null|void Redirects to index. + * @return \Cake\Http\Response|null Redirects to index. * @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found. */ public function delete($id = null) { $this->request->allowMethod(['post', 'delete']); ${{ singularName }} = $this->{{ currentModelName }}->get($id); +{% if Bake.hasPlugin('Authorization') %} + $this->Authorization->authorize(${{ singularName }}); +{% endif %} if ($this->{{ currentModelName }}->delete(${{ singularName }})) { $this->Flash->success(__('The {{ singularHumanName|lower }} has been deleted.')); } else { diff --git a/app/vendor/cakephp/bake/templates/bake/element/Controller/edit.twig b/app/vendor/cakephp/bake/templates/bake/element/Controller/edit.twig index 50d7531e2..793f8515f 100644 --- a/app/vendor/cakephp/bake/templates/bake/element/Controller/edit.twig +++ b/app/vendor/cakephp/bake/templates/bake/element/Controller/edit.twig @@ -25,9 +25,10 @@ */ public function edit($id = null) { - ${{ singularName }} = $this->{{ currentModelName }}->get($id, [ - 'contain' => {{ Bake.exportArray(belongsToMany)|raw }}, - ]); + ${{ singularName }} = $this->{{ currentModelName }}->get($id, contain: {{ Bake.exportArray(belongsToMany)|raw }}); +{% if Bake.hasPlugin('Authorization') %} + $this->Authorization->authorize(${{ singularName }}); +{% endif %} if ($this->request->is(['patch', 'post', 'put'])) { ${{ singularName }} = $this->{{ currentModelName }}->patchEntity(${{ singularName }}, $this->request->getData()); if ($this->{{ currentModelName }}->save(${{ singularName }})) { @@ -40,7 +41,7 @@ {% for assoc in belongsTo|merge(belongsToMany) %} {%- set otherName = Bake.getAssociatedTableAlias(modelObj, assoc) %} {%- set otherPlural = otherName|variable %} - ${{ otherPlural }} = $this->{{ currentModelName }}->{{ otherName }}->find('list', ['limit' => 200])->all(); + ${{ otherPlural }} = $this->{{ currentModelName }}->{{ otherName }}->find('list', limit: 200)->all(); {{- "\n" }} {%- set compact = compact|merge(["'#{otherPlural}'"]) %} {% endfor %} diff --git a/app/vendor/cakephp/bake/templates/bake/element/Controller/index.twig b/app/vendor/cakephp/bake/templates/bake/element/Controller/index.twig index a03e9ff42..0394d9663 100644 --- a/app/vendor/cakephp/bake/templates/bake/element/Controller/index.twig +++ b/app/vendor/cakephp/bake/templates/bake/element/Controller/index.twig @@ -22,11 +22,15 @@ { {% set belongsTo = Bake.aliasExtractor(modelObj, 'BelongsTo') %} {% if belongsTo %} - $this->paginate = [ - 'contain' => {{ Bake.exportArray(belongsTo)|raw }}, - ]; + $query = $this->{{ currentModelName }}->find() + ->contain({{ Bake.exportArray(belongsTo)|raw }}); +{% else %} + $query = $this->{{ currentModelName }}->find(); {% endif %} - ${{ pluralName }} = $this->paginate($this->{{ currentModelName }}); +{% if Bake.hasPlugin('Authorization') %} + $query = $this->Authorization->applyScope($query); +{% endif %} + ${{ pluralName }} = $this->paginate($query); $this->set(compact('{{ pluralName }}')); } diff --git a/app/vendor/cakephp/bake/templates/bake/element/Controller/login.twig b/app/vendor/cakephp/bake/templates/bake/element/Controller/login.twig index d094789da..409f02433 100644 --- a/app/vendor/cakephp/bake/templates/bake/element/Controller/login.twig +++ b/app/vendor/cakephp/bake/templates/bake/element/Controller/login.twig @@ -9,24 +9,33 @@ * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://cakephp.org CakePHP(tm) Project - * @since 2.0.0 + * @since 3.2.0 * @license https://www.opensource.org/licenses/mit-license.php MIT License */ #} /** * Login method * - * @return \Cake\Http\Response|null|void Renders view + * @return \Cake\Http\Response|null|void Redirects on successful login, renders view otherwise. */ public function login() { - if ($this->request->is('post')) { - $user = $this->Auth->identify(); - if ($user) { - $this->Auth->setUser($user); +{% if Bake.hasPlugin('Authorization') %} + $this->Authorization->skipAuthorization(); - return $this->redirect($this->Auth->redirectUrl()); +{% endif %} + $this->request->allowMethod(['get', 'post']); + $result = $this->Authentication->getResult(); + if ($result->isValid()) { + $this->Flash->success(__('Login successful')); + $redirect = $this->Authentication->getLoginRedirect(); + if ($redirect) { + return $this->redirect($redirect); } - $this->Flash->error(__('Invalid credentials, try again')); + } + + // Display error if user submitted and authentication failed + if ($this->request->is('post')) { + $this->Flash->error(__('Invalid username or password')); } } diff --git a/app/vendor/cakephp/bake/templates/bake/element/Controller/logout.twig b/app/vendor/cakephp/bake/templates/bake/element/Controller/logout.twig deleted file mode 100644 index c0d6eecb7..000000000 --- a/app/vendor/cakephp/bake/templates/bake/element/Controller/logout.twig +++ /dev/null @@ -1,24 +0,0 @@ -{# -/** - * CakePHP(tm) : Rapid Development Framework (https://cakephp.org) - * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) - * - * Licensed under The MIT License - * For full copyright and license information, please see the LICENSE.txt - * Redistributions of files must retain the above copyright notice. - * - * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) - * @link https://cakephp.org CakePHP(tm) Project - * @since 2.0.0 - * @license https://www.opensource.org/licenses/mit-license.php MIT License - */ -#} - /** - * Logout method - * - * @return \Cake\Http\Response|null|void Redirects to logout URL - */ - public function logout() - { - return $this->redirect($this->Auth->logout()); - } diff --git a/app/vendor/cakephp/bake/templates/bake/element/Controller/view.twig b/app/vendor/cakephp/bake/templates/bake/element/Controller/view.twig index e2badd891..d85bd17a0 100644 --- a/app/vendor/cakephp/bake/templates/bake/element/Controller/view.twig +++ b/app/vendor/cakephp/bake/templates/bake/element/Controller/view.twig @@ -26,9 +26,9 @@ */ public function view($id = null) { - ${{ singularName }} = $this->{{ currentModelName }}->get($id, [ - 'contain' => {{ Bake.exportArray(allAssociations)|raw }}, - ]); - + ${{ singularName }} = $this->{{ currentModelName }}->get($id, contain: {{ Bake.exportArray(allAssociations)|raw }}); +{% if Bake.hasPlugin('Authorization') %} + $this->Authorization->authorize(${{ singularName }}); +{% endif %} $this->set(compact('{{ singularName }}')); } diff --git a/app/vendor/cakephp/bake/templates/bake/element/array_property.twig b/app/vendor/cakephp/bake/templates/bake/element/array_property.twig index a96011e31..c105903fe 100644 --- a/app/vendor/cakephp/bake/templates/bake/element/array_property.twig +++ b/app/vendor/cakephp/bake/templates/bake/element/array_property.twig @@ -3,4 +3,4 @@ * * @var array */ - public ${{ name }} = {{ Bake.exportArray(value)|raw }}; \ No newline at end of file + public array ${{ name }} = {{ Bake.exportArray(value)|raw }}; \ No newline at end of file diff --git a/app/vendor/cakephp/bake/templates/bake/element/form.twig b/app/vendor/cakephp/bake/templates/bake/element/form.twig index 981fcf7db..f442cbd00 100644 --- a/app/vendor/cakephp/bake/templates/bake/element/form.twig +++ b/app/vendor/cakephp/bake/templates/bake/element/form.twig @@ -30,7 +30,7 @@ {%- set done = [] %} -
+
Form->create(${{ singularVar }}) ?>
diff --git a/app/vendor/cakephp/bake/templates/bake/tests/fixture.twig b/app/vendor/cakephp/bake/templates/bake/tests/fixture.twig index c71b6bd3d..7bb23775f 100644 --- a/app/vendor/cakephp/bake/templates/bake/tests/fixture.twig +++ b/app/vendor/cakephp/bake/templates/bake/tests/fixture.twig @@ -35,7 +35,7 @@ class {{ name }}Fixture extends TestFixture * * @var string */ - public $table = '{{ table|raw }}'; + public string $table = '{{ table|raw }}'; {% endif %} {%- if import %} @@ -44,7 +44,7 @@ class {{ name }}Fixture extends TestFixture * * @var array */ - public $import = {{ import|raw }}; + public array $import = {{ import|raw }}; {% endif %} @@ -55,7 +55,7 @@ class {{ name }}Fixture extends TestFixture * @var array */ // phpcs:disable - public $fields = {{ schema|raw }}; + public array $fields = {{ schema|raw }}; // phpcs:enable {% endif %} diff --git a/app/vendor/cakephp/bake/templates/bake/tests/test_case.twig b/app/vendor/cakephp/bake/templates/bake/tests/test_case.twig index 23c488c19..464f308b7 100644 --- a/app/vendor/cakephp/bake/templates/bake/tests/test_case.twig +++ b/app/vendor/cakephp/bake/templates/bake/tests/test_case.twig @@ -16,17 +16,15 @@ */ #} {% set isController = type|lower == 'controller' %} -{% set isShell = type|lower == 'shell' %} {% set isCommand = type|lower == 'command' %} {% if isController %} {%- set traitName = 'IntegrationTestTrait' %} -{% elseif isShell or isCommand %} + {%- set uses = uses|merge(['Cake\\TestSuite\\IntegrationTestTrait']) %} +{% elseif isCommand %} {%- set traitName = 'ConsoleIntegrationTestTrait' %} + {%- set uses = uses|merge(['Cake\\Console\\TestSuite\\ConsoleIntegrationTestTrait']) %} {% endif %} {%- set uses = uses|merge(["Cake\\TestSuite\\TestCase"]) %} -{% if traitName is defined %} - {%- set uses = uses|merge(["Cake\\TestSuite\\#{traitName}"]) %} -{% endif %} {{- element('Bake.file_header', { namespace: "#{baseNamespace}\\Test\\TestCase\\#{subNamespace}", @@ -37,7 +35,7 @@ * {{ fullClassName }} Test Case {% if isController or isCommand %} * - * @uses \{{ fullClassName }} + * @link \{{ fullClassName }} {% endif %} */ class {{ className }}Test extends TestCase @@ -69,9 +67,9 @@ class {{ className }}Test extends TestCase /** * Fixtures * - * @var array + * @var list */ - protected $fixtures = {{ Bake.exportVar(fixtures|values, 1)|raw }}; + protected array $fixtures = {{ Bake.exportVar(fixtures|values, 1)|raw }}; {% if construction or methods %} {% endif %} @@ -125,7 +123,7 @@ class {{ className }}Test extends TestCase * Test {{ method }} method * * @return void - * @uses \{{ fullClassName }}::{{ method }}() + * @link \{{ fullClassName }}::{{ method }}() */ public function test{{ method|camelize }}(): void { diff --git a/app/vendor/cakephp/bake/tests/Fixture/ArticlesFixture.php b/app/vendor/cakephp/bake/tests/Fixture/ArticlesFixture.php index be7ee54db..9f9a87682 100644 --- a/app/vendor/cakephp/bake/tests/Fixture/ArticlesFixture.php +++ b/app/vendor/cakephp/bake/tests/Fixture/ArticlesFixture.php @@ -18,7 +18,7 @@ class ArticlesFixture extends TestFixture { - public $records = [ + public array $records = [ ['author_id' => 1, 'title' => 'First Article', 'body' => 'First Article Body', 'published' => 'Y'], ['author_id' => 3, 'title' => 'Second Article', 'body' => 'Second Article Body', 'published' => 'Y'], ['author_id' => 1, 'title' => 'Third Article', 'body' => 'Third Article Body', 'published' => 'Y'], diff --git a/app/vendor/cakephp/bake/tests/Fixture/AuthorsFixture.php b/app/vendor/cakephp/bake/tests/Fixture/AuthorsFixture.php index 0f3207c80..4b828dc83 100644 --- a/app/vendor/cakephp/bake/tests/Fixture/AuthorsFixture.php +++ b/app/vendor/cakephp/bake/tests/Fixture/AuthorsFixture.php @@ -18,7 +18,7 @@ class AuthorsFixture extends TestFixture { - public $records = [ + public array $records = [ ['name' => 'mariano'], ['name' => 'nate'], ['name' => 'larry'], diff --git a/app/vendor/cakephp/bake/tests/Fixture/BakeArticlesBakeTagsFixture.php b/app/vendor/cakephp/bake/tests/Fixture/BakeArticlesBakeTagsFixture.php index 7171bbdd1..72a9499cf 100644 --- a/app/vendor/cakephp/bake/tests/Fixture/BakeArticlesBakeTagsFixture.php +++ b/app/vendor/cakephp/bake/tests/Fixture/BakeArticlesBakeTagsFixture.php @@ -21,21 +21,4 @@ */ class BakeArticlesBakeTagsFixture extends TestFixture { - /** - * fields property - * - * @var array - */ - public $fields = [ - 'bake_article_id' => ['type' => 'integer', 'null' => false], - 'bake_tag_id' => ['type' => 'integer', 'null' => false], - '_constraints' => ['UNIQUE_TAG' => ['type' => 'unique', 'columns' => ['bake_article_id', 'bake_tag_id']]], - ]; - - /** - * records property - * - * @var array - */ - public $records = []; } diff --git a/app/vendor/cakephp/bake/tests/Fixture/BakeArticlesFixture.php b/app/vendor/cakephp/bake/tests/Fixture/BakeArticlesFixture.php index ebf02ef5c..ae80b1410 100644 --- a/app/vendor/cakephp/bake/tests/Fixture/BakeArticlesFixture.php +++ b/app/vendor/cakephp/bake/tests/Fixture/BakeArticlesFixture.php @@ -21,28 +21,4 @@ */ class BakeArticlesFixture extends TestFixture { - /** - * fields property - * - * @var array - */ - public $fields = [ - 'id' => ['type' => 'integer'], - 'bake_user_id' => ['type' => 'integer', 'null' => false], - 'title' => ['type' => 'string', 'length' => 50, 'null' => false], - 'body' => 'text', - 'rating' => ['type' => 'float', 'unsigned' => true, 'default' => 0.0, 'null' => false], - 'score' => ['type' => 'decimal', 'unsigned' => true, 'default' => 0.0, 'null' => false], - 'published' => ['type' => 'boolean', 'length' => 1, 'default' => false, 'null' => false], - 'created' => 'datetime', - 'updated' => 'datetime', - '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]], - ]; - - /** - * records property - * - * @var array - */ - public $records = []; } diff --git a/app/vendor/cakephp/bake/tests/Fixture/BakeCarFixture.php b/app/vendor/cakephp/bake/tests/Fixture/BakeCarFixture.php index 29127751a..5fa12baa3 100644 --- a/app/vendor/cakephp/bake/tests/Fixture/BakeCarFixture.php +++ b/app/vendor/cakephp/bake/tests/Fixture/BakeCarFixture.php @@ -24,28 +24,5 @@ class BakeCarFixture extends TestFixture /** * @var string */ - public $table = 'car'; - - /** - * fields property - * - * @var array - */ - public $fields = [ - 'id' => ['type' => 'integer'], - 'bake_user_id' => ['type' => 'integer', 'null' => false], - 'title' => ['type' => 'string', 'null' => false], - 'body' => 'text', - 'published' => ['type' => 'boolean', 'length' => 1, 'default' => false], - 'created' => 'datetime', - 'updated' => 'datetime', - '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]], - ]; - - /** - * records property - * - * @var array - */ - public $records = []; + public string $table = 'car'; } diff --git a/app/vendor/cakephp/bake/tests/Fixture/BakeCommentsFixture.php b/app/vendor/cakephp/bake/tests/Fixture/BakeCommentsFixture.php index 82fd0c3b9..fea3cadb2 100644 --- a/app/vendor/cakephp/bake/tests/Fixture/BakeCommentsFixture.php +++ b/app/vendor/cakephp/bake/tests/Fixture/BakeCommentsFixture.php @@ -21,26 +21,4 @@ */ class BakeCommentsFixture extends TestFixture { - /** - * fields property - * - * @var array - */ - public $fields = [ - 'otherid' => ['type' => 'integer'], - 'bake_article_id' => ['type' => 'integer', 'null' => false], - 'bake_user_id' => ['type' => 'integer', 'null' => false], - 'comment' => 'text', - 'published' => ['type' => 'string', 'length' => 1, 'default' => 'N'], - 'created' => ['type' => 'datetime', 'length' => null, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null], - 'updated' => ['type' => 'datetime', 'length' => null, 'null' => true, 'default' => null, 'comment' => '', 'precision' => null], - '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['otherid']]], - ]; - - /** - * records property - * - * @var array - */ - public $records = []; } diff --git a/app/vendor/cakephp/bake/tests/Fixture/BakeTagsFixture.php b/app/vendor/cakephp/bake/tests/Fixture/BakeTagsFixture.php index 2d48fcb07..a02152318 100644 --- a/app/vendor/cakephp/bake/tests/Fixture/BakeTagsFixture.php +++ b/app/vendor/cakephp/bake/tests/Fixture/BakeTagsFixture.php @@ -21,23 +21,4 @@ */ class BakeTagsFixture extends TestFixture { - /** - * fields property - * - * @var array - */ - public $fields = [ - 'id' => ['type' => 'integer'], - 'tag' => ['type' => 'string', 'null' => false], - 'created' => 'datetime', - 'updated' => 'datetime', - '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]], - ]; - - /** - * records property - * - * @var array - */ - public $records = []; } diff --git a/app/vendor/cakephp/bake/tests/Fixture/BakeTemplateAuthorsFixture.php b/app/vendor/cakephp/bake/tests/Fixture/BakeTemplateAuthorsFixture.php index cf366e069..eaa86a684 100644 --- a/app/vendor/cakephp/bake/tests/Fixture/BakeTemplateAuthorsFixture.php +++ b/app/vendor/cakephp/bake/tests/Fixture/BakeTemplateAuthorsFixture.php @@ -27,32 +27,14 @@ class BakeTemplateAuthorsFixture extends TestFixture * * @var string */ - public $table = 'bake_authors'; - - /** - * fields property - * - * @var array - */ - public $fields = [ - 'id' => ['type' => 'integer'], - 'role_id' => ['type' => 'integer', 'null' => false], - 'name' => ['type' => 'string', 'default' => null], - 'description' => ['type' => 'text', 'default' => null], - 'member' => ['type' => 'boolean'], - 'member_number' => ['type' => 'integer', 'null' => true], - 'account_balance' => ['type' => 'decimal', 'null' => true, 'precision' => 2, 'length' => 12], - 'created' => 'datetime', - 'modified' => 'datetime', - '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]], - ]; + public string $table = 'bake_authors'; /** * records property * * @var array */ - public $records = [ + public array $records = [ ['name' => 'mariano', 'role_id' => 1], ['name' => 'nate', 'role_id' => 2], ['name' => 'larry', 'role_id' => 2], diff --git a/app/vendor/cakephp/bake/tests/Fixture/BakeTemplateProfilesFixture.php b/app/vendor/cakephp/bake/tests/Fixture/BakeTemplateProfilesFixture.php index 5f280b683..50fba6d80 100644 --- a/app/vendor/cakephp/bake/tests/Fixture/BakeTemplateProfilesFixture.php +++ b/app/vendor/cakephp/bake/tests/Fixture/BakeTemplateProfilesFixture.php @@ -27,27 +27,14 @@ class BakeTemplateProfilesFixture extends TestFixture /** * @var string */ - public $table = 'profiles'; - - /** - * fields property - * - * @var array - */ - public $fields = [ - 'id' => ['type' => 'integer'], - 'author_id' => ['type' => 'integer', 'null' => false], - 'nick' => ['type' => 'string', 'null' => false], - 'avatar' => ['type' => 'string', 'default' => null], - '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]], - ]; + public string $table = 'profiles'; /** * records property * * @var array */ - public $records = [ + public array $records = [ ['author_id' => 1, 'nick' => 'The Comedian', 'avatar' => 'smiley.png'], ['author_id' => 2, 'nick' => 'Rorschach', 'avatar' => 'stains.png'], ['author_id' => 3, 'nick' => 'Ozymandias', 'avatar' => null], diff --git a/app/vendor/cakephp/bake/tests/Fixture/BakeTemplateRolesFixture.php b/app/vendor/cakephp/bake/tests/Fixture/BakeTemplateRolesFixture.php index 19914b7e5..ed1cd3fc0 100644 --- a/app/vendor/cakephp/bake/tests/Fixture/BakeTemplateRolesFixture.php +++ b/app/vendor/cakephp/bake/tests/Fixture/BakeTemplateRolesFixture.php @@ -24,25 +24,14 @@ class BakeTemplateRolesFixture extends TestFixture /** * @var string */ - public $table = 'roles'; - - /** - * fields property - * - * @var array - */ - public $fields = [ - 'id' => ['type' => 'integer'], - 'name' => ['type' => 'string', 'null' => false], - '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]], - ]; + public string $table = 'roles'; /** * records property * * @var array */ - public $records = [ + public array $records = [ ['name' => 'admin'], ['name' => 'user'], ]; diff --git a/app/vendor/cakephp/bake/tests/Fixture/BinaryTestsFixture.php b/app/vendor/cakephp/bake/tests/Fixture/BinaryTestsFixture.php index 032b1dbf8..88ce771cf 100644 --- a/app/vendor/cakephp/bake/tests/Fixture/BinaryTestsFixture.php +++ b/app/vendor/cakephp/bake/tests/Fixture/BinaryTestsFixture.php @@ -21,22 +21,4 @@ */ class BinaryTestsFixture extends TestFixture { - /** - * fields property - * - * @var array - */ - public $fields = [ - 'id' => ['type' => 'integer'], - 'byte' => ['type' => 'binary', 'length' => 1], - 'data' => ['type' => 'binary', 'length' => 300], - '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]], - ]; - - /** - * records property - * - * @var array - */ - public $records = []; } diff --git a/app/vendor/cakephp/bake/tests/Fixture/CategoriesFixture.php b/app/vendor/cakephp/bake/tests/Fixture/CategoriesFixture.php index cb4f8be92..0ab0d4e16 100644 --- a/app/vendor/cakephp/bake/tests/Fixture/CategoriesFixture.php +++ b/app/vendor/cakephp/bake/tests/Fixture/CategoriesFixture.php @@ -21,33 +21,12 @@ */ class CategoriesFixture extends TestFixture { - /** - * Fields - * - * @var array - */ - // phpcs:disable - public $fields = [ - 'id' => ['type' => 'integer', 'length' => 11, 'unsigned' => true, 'null' => false, 'default' => null, 'comment' => '', 'autoIncrement' => true, 'precision' => null], - 'created' => ['type' => 'datetime', 'length' => null, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null], - 'modified' => ['type' => 'datetime', 'length' => null, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null], - 'name' => ['type' => 'string', 'length' => 100, 'null' => false, 'default' => '', 'comment' => '', 'precision' => null, 'fixed' => null], - '_constraints' => [ - 'primary' => ['type' => 'primary', 'columns' => ['id'], 'length' => []], - ], - '_options' => [ - 'engine' => 'InnoDB', - 'collation' => 'utf8_general_ci' - ], - ]; - // phpcs:enable - /** * Records * * @var array */ - public $records = [ + public array $records = [ [ 'created' => '2015-12-30 18:11:36', 'modified' => '2015-12-30 18:11:36', diff --git a/app/vendor/cakephp/bake/tests/Fixture/CategoriesProductsFixture.php b/app/vendor/cakephp/bake/tests/Fixture/CategoriesProductsFixture.php index 46e7ac22f..c66ac82ab 100644 --- a/app/vendor/cakephp/bake/tests/Fixture/CategoriesProductsFixture.php +++ b/app/vendor/cakephp/bake/tests/Fixture/CategoriesProductsFixture.php @@ -21,31 +21,12 @@ */ class CategoriesProductsFixture extends TestFixture { - /** - * Fields - * - * @var array - */ - // phpcs:disable - public $fields = [ - 'category_id' => ['type' => 'integer', 'length' => 11, 'unsigned' => true, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null, 'autoIncrement' => null], - 'product_id' => ['type' => 'integer', 'length' => 11, 'unsigned' => true, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null, 'autoIncrement' => null], - '_constraints' => [ - 'primary' => ['type' => 'primary', 'columns' => ['category_id', 'product_id'], 'length' => []], - ], - '_options' => [ - 'engine' => 'InnoDB', - 'collation' => 'utf8_general_ci' - ], - ]; - // phpcs:enable - /** * Records * * @var array */ - public $records = [ + public array $records = [ [ 'category_id' => 1, 'product_id' => 1, diff --git a/app/vendor/cakephp/bake/tests/Fixture/CategoryThreadsFixture.php b/app/vendor/cakephp/bake/tests/Fixture/CategoryThreadsFixture.php index 7cddd1093..3142e3766 100644 --- a/app/vendor/cakephp/bake/tests/Fixture/CategoryThreadsFixture.php +++ b/app/vendor/cakephp/bake/tests/Fixture/CategoryThreadsFixture.php @@ -21,28 +21,12 @@ */ class CategoryThreadsFixture extends TestFixture { - /** - * fields property - * - * @var array - */ - public $fields = [ - 'id' => ['type' => 'integer'], - 'parent_id' => ['type' => 'integer'], - 'name' => ['type' => 'string', 'null' => false], - 'lft' => ['type' => 'integer', 'unsigned' => true], - 'rght' => ['type' => 'integer', 'unsigned' => true], - 'created' => 'datetime', - 'updated' => 'datetime', - '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]], - ]; - /** * records property * * @var array */ - public $records = [ + public array $records = [ ['parent_id' => 0, 'name' => 'Category 1', 'lft' => 1, 'rght' => 14, 'created' => '2007-03-18 15:30:23', 'updated' => '2007-03-18 15:32:31'], ['parent_id' => 1, 'name' => 'Category 1.1', 'lft' => 2, 'rght' => 9, 'created' => '2007-03-18 15:30:23', 'updated' => '2007-03-18 15:32:31'], ['parent_id' => 2, 'name' => 'Category 1.1.1', 'lft' => 3, 'rght' => 8, 'created' => '2007-03-18 15:30:23', 'updated' => '2007-03-18 15:32:31'], diff --git a/app/vendor/cakephp/bake/tests/Fixture/CommentsFixture.php b/app/vendor/cakephp/bake/tests/Fixture/CommentsFixture.php index b19154990..b93066586 100644 --- a/app/vendor/cakephp/bake/tests/Fixture/CommentsFixture.php +++ b/app/vendor/cakephp/bake/tests/Fixture/CommentsFixture.php @@ -18,7 +18,7 @@ class CommentsFixture extends TestFixture { - public $records = [ + public array $records = [ ['article_id' => 1, 'user_id' => 2, 'comment' => 'First Comment for First Article', 'published' => 'Y', 'created' => '2007-03-18 10:45:23', 'updated' => '2007-03-18 10:47:31'], ['article_id' => 1, 'user_id' => 4, 'comment' => 'Second Comment for First Article', 'published' => 'Y', 'created' => '2007-03-18 10:47:23', 'updated' => '2007-03-18 10:49:31'], ['article_id' => 1, 'user_id' => 1, 'comment' => 'Third Comment for First Article', 'published' => 'Y', 'created' => '2007-03-18 10:49:23', 'updated' => '2007-03-18 10:51:31'], diff --git a/app/vendor/cakephp/bake/tests/Fixture/DatatypesFixture.php b/app/vendor/cakephp/bake/tests/Fixture/DatatypesFixture.php index bc1502721..addc7cee2 100644 --- a/app/vendor/cakephp/bake/tests/Fixture/DatatypesFixture.php +++ b/app/vendor/cakephp/bake/tests/Fixture/DatatypesFixture.php @@ -21,30 +21,12 @@ */ class DatatypesFixture extends TestFixture { - /** - * Fields property - * - * @var array - */ - public $fields = [ - 'id' => ['type' => 'integer', 'null' => false], - 'decimal_field' => ['type' => 'decimal', 'length' => '6', 'precision' => 3, 'default' => '0.000'], - 'float_field' => ['type' => 'float', 'length' => '5,2', 'null' => false, 'default' => null], - 'huge_int' => ['type' => 'biginteger'], - 'small_int' => ['type' => 'smallinteger'], - 'tiny_int' => ['type' => 'tinyinteger'], - 'bool' => ['type' => 'boolean', 'null' => false, 'default' => false], - 'uuid' => ['type' => 'uuid'], - 'timestamp_field' => ['type' => 'timestamp'], - '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]], - ]; - /** * Records property * * @var array */ - public $records = [ + public array $records = [ ['decimal_field' => '30.123', 'float_field' => 42.23, 'huge_int' => '1234567891234567891', 'small_int' => '1234', 'tiny_int' => '12', 'bool' => 0, 'timestamp_field' => '2007-03-17 01:16:23'], ]; } diff --git a/app/vendor/cakephp/bake/tests/Fixture/HiddenFieldsFixture.php b/app/vendor/cakephp/bake/tests/Fixture/HiddenFieldsFixture.php index f1db98741..7fd63501d 100644 --- a/app/vendor/cakephp/bake/tests/Fixture/HiddenFieldsFixture.php +++ b/app/vendor/cakephp/bake/tests/Fixture/HiddenFieldsFixture.php @@ -21,26 +21,12 @@ */ class HiddenFieldsFixture extends TestFixture { - /** - * fields property - * - * @var array - */ - public $fields = [ - 'id' => ['type' => 'integer'], - 'password' => ['type' => 'string', 'null' => true, 'length' => 255], - 'auth_token' => ['type' => 'string', 'null' => true, 'length' => 255], - '_constraints' => [ - 'primary' => ['type' => 'primary', 'columns' => ['id']], - ], - ]; - /** * records property * * @var array */ - public $records = [ + public array $records = [ ['password' => '$2a$10$u05j8FjsvLBNdfhBhc21LOuVMpzpabVXQ9OpC2wO3pSO0q6t7HHMO', 'auth_token' => '12345'], ['password' => '$2a$10$u05j8FjsvLBNdfhBhc21LOuVMpzpabVXQ9OpC2wO3pSO0q6t7HHMO', 'auth_token' => '23456'], ]; diff --git a/app/vendor/cakephp/bake/tests/Fixture/InvitationsFixture.php b/app/vendor/cakephp/bake/tests/Fixture/InvitationsFixture.php index b7b8028d5..e6606917b 100644 --- a/app/vendor/cakephp/bake/tests/Fixture/InvitationsFixture.php +++ b/app/vendor/cakephp/bake/tests/Fixture/InvitationsFixture.php @@ -21,43 +21,12 @@ */ class InvitationsFixture extends TestFixture { - /** - * fields property - * - * @var array - */ - public $fields = [ - 'id' => ['type' => 'integer'], - 'sender_id' => ['type' => 'integer', 'null' => false], - 'receiver_id' => ['type' => 'integer', 'null' => false], - 'body' => 'text', - 'created' => 'datetime', - 'updated' => 'datetime', - '_constraints' => [ - 'primary' => ['type' => 'primary', 'columns' => ['id']], - 'sender_idx' => [ - 'type' => 'foreign', - 'columns' => ['sender_id'], - 'references' => ['users', 'id'], - 'update' => 'noAction', - 'delete' => 'noAction', - ], - 'receiver_idx' => [ - 'type' => 'foreign', - 'columns' => ['receiver_id'], - 'references' => ['users', 'id'], - 'update' => 'noAction', - 'delete' => 'noAction', - ], - ], - ]; - /** * records property * * @var array */ - public $records = [ + public array $records = [ [ 'sender_id' => 1, 'receiver_id' => 1, diff --git a/app/vendor/cakephp/bake/tests/Fixture/NumberTreesFixture.php b/app/vendor/cakephp/bake/tests/Fixture/NumberTreesFixture.php index 5081adc93..5fdf419a0 100644 --- a/app/vendor/cakephp/bake/tests/Fixture/NumberTreesFixture.php +++ b/app/vendor/cakephp/bake/tests/Fixture/NumberTreesFixture.php @@ -23,25 +23,4 @@ */ class NumberTreesFixture extends TestFixture { - /** - * fields property - * - * @var array - */ - public $fields = [ - 'id' => ['type' => 'integer'], - 'name' => ['type' => 'string', 'length' => 50, 'null' => false], - 'parent_id' => 'integer', - 'lft' => ['type' => 'integer', 'unsigned' => true], - 'rght' => ['type' => 'integer', 'unsigned' => true], - 'depth' => ['type' => 'integer', 'unsigned' => true], - '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]], - ]; - - /** - * Records - * - * @var array - */ - public $records = []; } diff --git a/app/vendor/cakephp/bake/tests/Fixture/OldProductsFixture.php b/app/vendor/cakephp/bake/tests/Fixture/OldProductsFixture.php index ba0e9defd..8e6d3ac86 100644 --- a/app/vendor/cakephp/bake/tests/Fixture/OldProductsFixture.php +++ b/app/vendor/cakephp/bake/tests/Fixture/OldProductsFixture.php @@ -21,33 +21,12 @@ */ class OldProductsFixture extends TestFixture { - /** - * Fields - * - * @var array - */ - // phpcs:disable - public $fields = [ - 'id' => ['type' => 'integer', 'length' => 11, 'unsigned' => true, 'null' => false, 'default' => null, 'comment' => '', 'autoIncrement' => true, 'precision' => null], - 'created' => ['type' => 'datetime', 'length' => null, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null], - 'modified' => ['type' => 'datetime', 'length' => null, 'null' => true, 'default' => null, 'comment' => '', 'precision' => null], - 'name' => ['type' => 'string', 'length' => 100, 'null' => false, 'default' => '', 'comment' => '', 'precision' => null, 'fixed' => null], - '_constraints' => [ - 'primary' => ['type' => 'primary', 'columns' => ['id'], 'length' => []], - ], - '_options' => [ - 'engine' => 'InnoDB', - 'collation' => 'utf8_general_ci' - ], - ]; - // phpcs:enable - /** * Records * * @var array */ - public $records = [ + public array $records = [ [ 'created' => '2015-12-30 18:11:36', 'modified' => '2015-12-30 18:11:36', diff --git a/app/vendor/cakephp/bake/tests/Fixture/PostsFixture.php b/app/vendor/cakephp/bake/tests/Fixture/PostsFixture.php index db8ef12c3..a7b808f5d 100644 --- a/app/vendor/cakephp/bake/tests/Fixture/PostsFixture.php +++ b/app/vendor/cakephp/bake/tests/Fixture/PostsFixture.php @@ -18,7 +18,7 @@ class PostsFixture extends TestFixture { - public $records = [ + public array $records = [ ['author_id' => 1, 'title' => 'First Post', 'body' => 'First Post Body', 'published' => 'Y'], ['author_id' => 3, 'title' => 'Second Post', 'body' => 'Second Post Body', 'published' => 'Y'], ['author_id' => 1, 'title' => 'Third Post', 'body' => 'Third Post Body', 'published' => 'Y'], diff --git a/app/vendor/cakephp/bake/tests/Fixture/ProductVersionsFixture.php b/app/vendor/cakephp/bake/tests/Fixture/ProductVersionsFixture.php index ee77356d8..bd04c6cf9 100644 --- a/app/vendor/cakephp/bake/tests/Fixture/ProductVersionsFixture.php +++ b/app/vendor/cakephp/bake/tests/Fixture/ProductVersionsFixture.php @@ -21,32 +21,12 @@ */ class ProductVersionsFixture extends TestFixture { - /** - * Fields - * - * @var array - */ - // phpcs:disable - public $fields = [ - 'id' => ['type' => 'integer', 'length' => 11, 'unsigned' => true, 'null' => false, 'default' => null, 'comment' => '', 'autoIncrement' => true, 'precision' => null], - 'product_id' => ['type' => 'integer', 'length' => 11, 'unsigned' => true, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null, 'autoIncrement' => null], - 'version' => ['type' => 'datetime', 'length' => null, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null], - '_constraints' => [ - 'primary' => ['type' => 'primary', 'columns' => ['id'], 'length' => []], - ], - '_options' => [ - 'engine' => 'InnoDB', - 'collation' => 'utf8_general_ci' - ], - ]; - // phpcs:enable - /** * Records * * @var array */ - public $records = [ + public array $records = [ [ 'product_id' => 1, 'version' => '2015-12-30 18:11:37', diff --git a/app/vendor/cakephp/bake/tests/Fixture/ProductsFixture.php b/app/vendor/cakephp/bake/tests/Fixture/ProductsFixture.php index 9958a57d6..381c45d19 100644 --- a/app/vendor/cakephp/bake/tests/Fixture/ProductsFixture.php +++ b/app/vendor/cakephp/bake/tests/Fixture/ProductsFixture.php @@ -21,33 +21,12 @@ */ class ProductsFixture extends TestFixture { - /** - * Fields - * - * @var array - */ - // phpcs:disable - public $fields = [ - 'id' => ['type' => 'integer', 'length' => 11, 'unsigned' => true, 'null' => false, 'default' => null, 'comment' => '', 'autoIncrement' => true, 'precision' => null], - 'created' => ['type' => 'datetime', 'length' => null, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null], - 'modified' => ['type' => 'datetime', 'length' => null, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null], - 'name' => ['type' => 'string', 'length' => 100, 'null' => false, 'default' => '', 'comment' => '', 'precision' => null, 'fixed' => null], - '_constraints' => [ - 'primary' => ['type' => 'primary', 'columns' => ['id'], 'length' => []], - ], - '_options' => [ - 'engine' => 'InnoDB', - 'collation' => 'utf8_general_ci' - ], - ]; - // phpcs:enable - /** * Records * * @var array */ - public $records = [ + public array $records = [ [ 'created' => '2015-12-30 18:11:37', 'modified' => '2015-12-30 18:11:37', diff --git a/app/vendor/cakephp/bake/tests/Fixture/RelationsFixture.php b/app/vendor/cakephp/bake/tests/Fixture/RelationsFixture.php new file mode 100644 index 000000000..d7b09a1f3 --- /dev/null +++ b/app/vendor/cakephp/bake/tests/Fixture/RelationsFixture.php @@ -0,0 +1,36 @@ + 1, + 'other_id' => 1, + 'body' => 'Try it out!', + ], + ]; +} diff --git a/app/vendor/cakephp/bake/tests/Fixture/TagsFixture.php b/app/vendor/cakephp/bake/tests/Fixture/TagsFixture.php index 459788235..67bef54e2 100644 --- a/app/vendor/cakephp/bake/tests/Fixture/TagsFixture.php +++ b/app/vendor/cakephp/bake/tests/Fixture/TagsFixture.php @@ -18,7 +18,7 @@ class TagsFixture extends TestFixture { - public $records = [ + public array $records = [ ['name' => 'tag1', 'description' => 'A big description', 'created' => '2016-01-01 00:00'], ['name' => 'tag2', 'description' => 'Another big description', 'created' => '2016-01-01 00:00'], ['name' => 'tag3', 'description' => 'Yet another one', 'created' => '2016-01-01 00:00'], diff --git a/app/vendor/cakephp/bake/tests/Fixture/TodoItemsFixture.php b/app/vendor/cakephp/bake/tests/Fixture/TodoItemsFixture.php index 2bfe8c0de..6a6ab191c 100644 --- a/app/vendor/cakephp/bake/tests/Fixture/TodoItemsFixture.php +++ b/app/vendor/cakephp/bake/tests/Fixture/TodoItemsFixture.php @@ -24,30 +24,4 @@ */ class TodoItemsFixture extends TestFixture { - /** - * fields property - * - * @var array - */ - public $fields = [ - 'id' => ['type' => 'integer', 'null' => false], - 'user_id' => ['type' => 'integer', 'null' => false], - 'title' => ['type' => 'string', 'length' => 50, 'null' => false], - 'body' => ['type' => 'text'], - 'effort' => ['type' => 'decimal', 'default' => 0, 'null' => false], - 'completed' => ['type' => 'boolean', 'default' => false, 'null' => false], - 'todo_task_count' => ['type' => 'integer', 'default' => 0, 'null' => false], - 'created' => ['type' => 'datetime'], - 'updated' => ['type' => 'datetime'], - '_constraints' => [ - 'primary' => ['type' => 'primary', 'columns' => ['id']], - ], - ]; - - /** - * records property - * - * @var array - */ - public $records = []; } diff --git a/app/vendor/cakephp/bake/tests/Fixture/TodoItemsTodoLabelsFixture.php b/app/vendor/cakephp/bake/tests/Fixture/TodoItemsTodoLabelsFixture.php index 405b162e3..2aae85c78 100644 --- a/app/vendor/cakephp/bake/tests/Fixture/TodoItemsTodoLabelsFixture.php +++ b/app/vendor/cakephp/bake/tests/Fixture/TodoItemsTodoLabelsFixture.php @@ -24,37 +24,4 @@ */ class TodoItemsTodoLabelsFixture extends TestFixture { - /** - * fields property - * - * @var array - */ - public $fields = [ - 'todo_item_id' => ['type' => 'integer', 'null' => false], - 'todo_label_id' => ['type' => 'integer', 'null' => false], - '_constraints' => [ - 'primary' => ['type' => 'primary', 'columns' => ['todo_item_id', 'todo_label_id']], - 'item_fk' => [ - 'type' => 'foreign', - 'columns' => ['todo_item_id'], - 'references' => ['todo_items', 'id'], - 'update' => 'cascade', - 'delete' => 'cascade', - ], - 'label_fk' => [ - 'type' => 'foreign', - 'columns' => ['todo_label_id'], - 'references' => ['todo_labels', 'id'], - 'update' => 'cascade', - 'delete' => 'cascade', - ], - ], - ]; - - /** - * records property - * - * @var array - */ - public $records = []; } diff --git a/app/vendor/cakephp/bake/tests/Fixture/TodoLabelsFixture.php b/app/vendor/cakephp/bake/tests/Fixture/TodoLabelsFixture.php index c2b59fc3c..d13a5ec65 100644 --- a/app/vendor/cakephp/bake/tests/Fixture/TodoLabelsFixture.php +++ b/app/vendor/cakephp/bake/tests/Fixture/TodoLabelsFixture.php @@ -24,23 +24,4 @@ */ class TodoLabelsFixture extends TestFixture { - /** - * fields property - * - * @var array - */ - public $fields = [ - 'id' => ['type' => 'integer'], - 'label' => ['type' => 'string', 'null' => false], - '_constraints' => [ - 'primary' => ['type' => 'primary', 'columns' => ['id']], - ], - ]; - - /** - * records property - * - * @var array - */ - public $records = []; } diff --git a/app/vendor/cakephp/bake/tests/Fixture/TodoTasksFixture.php b/app/vendor/cakephp/bake/tests/Fixture/TodoTasksFixture.php index 7c34c8588..5593836fa 100644 --- a/app/vendor/cakephp/bake/tests/Fixture/TodoTasksFixture.php +++ b/app/vendor/cakephp/bake/tests/Fixture/TodoTasksFixture.php @@ -24,27 +24,4 @@ */ class TodoTasksFixture extends TestFixture { - /** - * fields property - * - * @var array - */ - public $fields = [ - 'uid' => ['type' => 'integer'], - 'todo_item_id' => ['type' => 'integer', 'null' => false], - 'title' => ['type' => 'string', 'length' => 50, 'null' => false], - 'body' => 'text', - 'completed' => ['type' => 'boolean', 'default' => false, 'null' => false], - 'effort' => ['type' => 'decimal', 'default' => 0.0, 'null' => false, 'unsigned' => true], - 'created' => 'datetime', - 'updated' => 'datetime', - '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['uid']]], - ]; - - /** - * records property - * - * @var array - */ - public $records = []; } diff --git a/app/vendor/cakephp/bake/tests/Fixture/UniqueFieldsFixture.php b/app/vendor/cakephp/bake/tests/Fixture/UniqueFieldsFixture.php index 2d64967de..34e0a37de 100644 --- a/app/vendor/cakephp/bake/tests/Fixture/UniqueFieldsFixture.php +++ b/app/vendor/cakephp/bake/tests/Fixture/UniqueFieldsFixture.php @@ -21,38 +21,12 @@ */ class UniqueFieldsFixture extends TestFixture { - /** - * fields property - * - * @var array - */ - public $fields = [ - 'id' => ['type' => 'integer'], - 'username' => ['type' => 'string', 'null' => true, 'length' => 255], - 'email' => ['type' => 'string', 'null' => true, 'length' => 255], - 'field_1' => ['type' => 'string', 'null' => true, 'length' => 255], - 'field_2' => ['type' => 'string', 'null' => true,'length' => 255], - '_constraints' => [ - 'primary' => [ - 'type' => 'primary', - 'columns' => ['id'], - ], - 'multiple_fields_unique' => [ - 'type' => 'unique', - 'columns' => [ - 'field_1', - 'field_2', - ], - ], - ], - ]; - /** * records property * * @var array */ - public $records = [ + public array $records = [ ['field_1' => 'unique_value_1', 'field_2' => 'unique_value_2'], ['field_1' => 'unique_value_2', 'field_2' => 'unique_value_3'], ]; diff --git a/app/vendor/cakephp/bake/tests/Fixture/UsersFixture.php b/app/vendor/cakephp/bake/tests/Fixture/UsersFixture.php index 259ef06c7..a98fb40bd 100644 --- a/app/vendor/cakephp/bake/tests/Fixture/UsersFixture.php +++ b/app/vendor/cakephp/bake/tests/Fixture/UsersFixture.php @@ -21,28 +21,12 @@ */ class UsersFixture extends TestFixture { - /** - * fields property - * - * @var array - */ - public $fields = [ - 'id' => ['type' => 'integer'], - 'username' => ['type' => 'string', 'null' => true, 'length' => 255], - 'password' => ['type' => 'string', 'null' => true, 'length' => 255], - 'created' => ['type' => 'timestamp', 'null' => true], - 'updated' => ['type' => 'timestamp', 'null' => true], - '_constraints' => [ - 'primary' => ['type' => 'primary', 'columns' => ['id']], - ], - ]; - /** * records property * * @var array */ - public $records = [ + public array $records = [ ['username' => 'mariano', 'password' => '$2a$10$u05j8FjsvLBNdfhBhc21LOuVMpzpabVXQ9OpC2wO3pSO0q6t7HHMO', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31'], ['username' => 'nate', 'password' => '$2a$10$u05j8FjsvLBNdfhBhc21LOuVMpzpabVXQ9OpC2wO3pSO0q6t7HHMO', 'created' => '2008-03-17 01:18:23', 'updated' => '2008-03-17 01:20:31'], ['username' => 'larry', 'password' => '$2a$10$u05j8FjsvLBNdfhBhc21LOuVMpzpabVXQ9OpC2wO3pSO0q6t7HHMO', 'created' => '2010-05-10 01:20:23', 'updated' => '2010-05-10 01:22:31'], diff --git a/app/vendor/cakephp/bake/tests/bootstrap.php b/app/vendor/cakephp/bake/tests/bootstrap.php index 246ac80c9..a031b40e6 100644 --- a/app/vendor/cakephp/bake/tests/bootstrap.php +++ b/app/vendor/cakephp/bake/tests/bootstrap.php @@ -16,6 +16,8 @@ // phpcs:ignoreFile +use function Cake\Core\env; +use Bake\BakePlugin; use Cake\Cache\Cache; use Cake\Core\Configure; use Cake\Core\Plugin; @@ -37,7 +39,6 @@ unset($findRoot); chdir($root); -require_once 'vendor/cakephp/cakephp/src/basics.php'; require_once 'vendor/autoload.php'; define('ROOT', $root . DS . 'tests' . DS . 'test_app' . DS); @@ -85,4 +86,4 @@ Configure::write('Debugger.exportFormatter', TextFormatter::class); -Plugin::getCollection()->add(new \Bake\Plugin()); +Plugin::getCollection()->add(new BakePlugin()); diff --git a/app/vendor/cakephp/bake/tests/schema.php b/app/vendor/cakephp/bake/tests/schema.php index 6b3c6a045..b26ac345e 100644 --- a/app/vendor/cakephp/bake/tests/schema.php +++ b/app/vendor/cakephp/bake/tests/schema.php @@ -208,7 +208,7 @@ 'body' => 'text', 'rating' => ['type' => 'float', 'unsigned' => true, 'default' => 0.0, 'null' => false], 'score' => ['type' => 'decimal', 'unsigned' => true, 'default' => 0.0, 'null' => false], - 'published' => ['type' => 'boolean', 'length' => 1, 'default' => false, 'null' => false], + 'published' => ['type' => 'boolean', 'length' => 1, 'default' => false], 'created' => 'datetime', 'updated' => 'datetime', ], @@ -227,6 +227,19 @@ ], 'constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]], ], + [ + 'table' => 'cars', + 'columns' => [ + 'id' => ['type' => 'integer'], + 'bake_user_id' => ['type' => 'integer', 'null' => false], + 'title' => ['type' => 'string', 'null' => false], + 'body' => 'text', + 'published' => ['type' => 'boolean', 'length' => 1, 'default' => false], + 'created' => 'datetime', + 'updated' => 'datetime', + ], + 'constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]], + ], [ 'table' => 'bake_comments', 'columns' => [ @@ -358,6 +371,46 @@ ], 'constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]], ], + [ + 'table' => 'bake_users', + 'columns' => [ + 'id' => ['type' => 'integer'], + 'username' => ['type' => 'string', 'null' => true, 'length' => 255], + 'password' => ['type' => 'string', 'null' => true, 'length' => 255], + 'status' => ['type' => 'tinyinteger', 'length' => 2, 'default' => null, 'null' => true], + 'created' => ['type' => 'timestamp', 'null' => true], + 'updated' => ['type' => 'timestamp', 'null' => true], + ], + 'constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]], + ], + [ + 'table' => 'relations', + 'columns' => [ + 'id' => ['type' => 'integer'], + 'user_id' => ['type' => 'integer', 'null' => false], + 'other_id' => ['type' => 'integer', 'null' => false], + 'body' => 'text', + 'created' => 'datetime', + 'updated' => 'datetime', + ], + 'constraints' => [ + 'primary' => ['type' => 'primary', 'columns' => ['id']], + 'user_idx' => [ + 'type' => 'foreign', + 'columns' => ['user_id'], + 'references' => ['users', 'id'], + 'update' => 'noAction', + 'delete' => 'noAction', + ], + 'other_idx' => [ + 'type' => 'foreign', + 'columns' => ['other_id'], + 'references' => ['users', 'id'], + 'update' => 'noAction', + 'delete' => 'noAction', + ], + ], + ], [ 'table' => 'invitations', 'columns' => [ diff --git a/app/vendor/cakephp/cakephp-codesniffer/.github/codecov.yml b/app/vendor/cakephp/cakephp-codesniffer/.github/codecov.yml deleted file mode 100644 index 0d79235e3..000000000 --- a/app/vendor/cakephp/cakephp-codesniffer/.github/codecov.yml +++ /dev/null @@ -1,7 +0,0 @@ -codecov: - require_ci_to_pass: yes - -coverage: - range: "90...100" - -comment: false diff --git a/app/vendor/cakephp/cakephp-codesniffer/.github/workflows/ci.yml b/app/vendor/cakephp/cakephp-codesniffer/.github/workflows/ci.yml deleted file mode 100644 index 29190d14d..000000000 --- a/app/vendor/cakephp/cakephp-codesniffer/.github/workflows/ci.yml +++ /dev/null @@ -1,73 +0,0 @@ -name: CI - -on: - push: - branches: - - master - pull_request: - branches: - - '*' - -jobs: - testsuite: - runs-on: ubuntu-22.04 - strategy: - fail-fast: false - matrix: - php-version: ['7.2', '7.4'] - dependencies: [highest] - include: - - php-version: '7.2' - dependencies: 'lowest' - - php-version: '8.0' - composer-options: "--ignore-platform-reqs" - - steps: - - uses: actions/checkout@v2 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php-version }} - extensions: mbstring, intl - coverage: pcov - - - name: Composer install - uses: ramsey/composer-install@v3 - with: - dependency-versions: ${{ matrix.dependencies }} - composer-options: "${{ matrix.composer-options }}" - - - name: Setup problem matchers for PHPUnit - if: matrix.php-version == '7.4' - run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" - - - name: Run PHPUnit - run: | - vendor/bin/phpcs --config-set installed_paths $(pwd) - vendor/bin/phpunit --filter CakePHP - - - name: Submit code coverage - if: matrix.php-version == '7.4' - uses: codecov/codecov-action@v1 - - cs-stan: - name: Coding Standard & Static Analysis - runs-on: ubuntu-22.04 - - steps: - - uses: actions/checkout@v2 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '7.2' - extensions: mbstring, intl - tools: cs2pr - coverage: none - - - name: Composer install - uses: ramsey/composer-install@v3 - - - name: Run PHP CodeSniffer - run: vendor/bin/phpcs --report=checkstyle CakePHP/ | cs2pr diff --git a/app/vendor/cakephp/cakephp-codesniffer/CakePHP/Sniffs/Classes/ReturnTypeHintSniff.php b/app/vendor/cakephp/cakephp-codesniffer/CakePHP/Sniffs/Classes/ReturnTypeHintSniff.php index 699115c5a..3fd3fb0b6 100644 --- a/app/vendor/cakephp/cakephp-codesniffer/CakePHP/Sniffs/Classes/ReturnTypeHintSniff.php +++ b/app/vendor/cakephp/cakephp-codesniffer/CakePHP/Sniffs/Classes/ReturnTypeHintSniff.php @@ -58,7 +58,7 @@ public function process(File $phpcsFile, $stackPtr) // We skip for interface methods if (empty($tokens[$stackPtr]['scope_opener']) || empty($tokens[$stackPtr]['scope_closer'])) { - return []; + return; } $returnTokenCode = $tokens[$startIndex]['code']; @@ -67,7 +67,7 @@ public function process(File $phpcsFile, $stackPtr) $phpcsFile->addError( 'Chaining methods (@return $this) should not have any return-type-hint.', $startIndex, - 'InvalidSelf' + 'InvalidSelf', ); return; @@ -76,7 +76,7 @@ public function process(File $phpcsFile, $stackPtr) $fix = $phpcsFile->addFixableError( 'Chaining methods (@return $this) should not have any return-type-hint (Remove "self").', $startIndex, - 'InvalidSelf' + 'InvalidSelf', ); if (!$fix) { return; @@ -175,7 +175,7 @@ protected function assertNotThisOrStatic(File $phpCsFile, int $stackPointer): vo $phpCsFile->addError( 'Class name repeated, expected `self` or `$this`.', $classNameIndex, - 'InvalidClass' + 'InvalidClass', ); } } @@ -228,9 +228,17 @@ protected function getClassNameWithNamespace(File $phpCsFile): ?string return null; } + $classPointer = $phpCsFile->findPrevious( + [T_CLASS, T_TRAIT, T_INTERFACE, T_ENUM], + $lastToken, + ); + if (!$classPointer) { + return null; + } + return ClassHelper::getFullyQualifiedName( $phpCsFile, - $phpCsFile->findPrevious([T_CLASS, T_TRAIT, T_INTERFACE, T_ENUM], $lastToken) + $classPointer, ); } } diff --git a/app/vendor/cakephp/cakephp-codesniffer/CakePHP/Sniffs/Commenting/DocBlockAlignmentSniff.php b/app/vendor/cakephp/cakephp-codesniffer/CakePHP/Sniffs/Commenting/DocBlockAlignmentSniff.php index 41e90c1f3..c04c15437 100644 --- a/app/vendor/cakephp/cakephp-codesniffer/CakePHP/Sniffs/Commenting/DocBlockAlignmentSniff.php +++ b/app/vendor/cakephp/cakephp-codesniffer/CakePHP/Sniffs/Commenting/DocBlockAlignmentSniff.php @@ -61,7 +61,7 @@ public function process(File $phpcsFile, $stackPtr) $commentBorder = $phpcsFile->findNext( [T_DOC_COMMENT_STAR, T_DOC_COMMENT_CLOSE_TAG], $searchToken, - $commentClose + 1 + $commentClose + 1, ); if ($commentBorder !== false) { $tokensToIndent[$commentBorder] = $codeIndentation + 1; diff --git a/app/vendor/cakephp/cakephp-codesniffer/CakePHP/Sniffs/Commenting/FunctionCommentSniff.php b/app/vendor/cakephp/cakephp-codesniffer/CakePHP/Sniffs/Commenting/FunctionCommentSniff.php index fa8674a7b..c6a3fa2c9 100644 --- a/app/vendor/cakephp/cakephp-codesniffer/CakePHP/Sniffs/Commenting/FunctionCommentSniff.php +++ b/app/vendor/cakephp/cakephp-codesniffer/CakePHP/Sniffs/Commenting/FunctionCommentSniff.php @@ -16,8 +16,6 @@ use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Sniffs\Sniff; -use PHP_CodeSniffer\Util\Common; -use PHP_CodeSniffer\Util\Tokens; /** * Parses and verifies the doc comments for functions. @@ -25,18 +23,8 @@ * Verifies that : *
    *
  • A comment exists
  • - *
  • There is a blank newline after the short description
  • - *
  • There is a blank newline between the long and short description
  • - *
  • There is a blank newline between the long description and tags
  • - *
  • Parameter names represent those in the method
  • - *
  • Parameter comments are in the correct order
  • - *
  • Parameter comments are complete
  • - *
  • A type hint is provided for array and custom class
  • - *
  • Type hint matches the actual variable/class type
  • - *
  • A blank line is present before the first and after the last parameter
  • - *
  • A return type exists
  • + *
  • No spacing between doc comment and function
  • *
  • Any throw tag must have a comment
  • - *
  • The tag order and indentation are correct
  • *
* * @category PHP @@ -50,29 +38,11 @@ */ class FunctionCommentSniff implements Sniff { - /** - * Disable the check for functions with a lower visibility than the value given. - * - * Allowed values are public, protected, and private. - * - * @var string - */ - public $minimumVisibility = 'private'; - - /** - * Array of methods which do not require a return type. - * - * @var array - */ - public $specialMethods = [ - '__construct', - '__destruct', - ]; - /** * Returns an array of tokens this test wants to listen for. * * @return array + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingNativeTypeHint */ public function register() { @@ -80,280 +50,87 @@ public function register() } /** - * Processes this test, when one of its tokens is encountered. - * - * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. - * @param int $stackPtr The position of the current token in the stack passed in $tokens. - * @return void + * @inheritDoc */ public function process(File $phpcsFile, $stackPtr) { - $scopeModifier = $phpcsFile->getMethodProperties($stackPtr)['scope']; - if ( - $scopeModifier === 'protected' - && $this->minimumVisibility === 'public' - || $scopeModifier === 'private' - && ($this->minimumVisibility === 'public' || $this->minimumVisibility === 'protected') - ) { - return; - } - $tokens = $phpcsFile->getTokens(); - $ignore = Tokens::$methodPrefixes; - $ignore[] = T_WHITESPACE; - - $commentEnd = $phpcsFile->findPrevious($ignore, $stackPtr - 1, null, true); - if ($tokens[$commentEnd]['code'] === T_COMMENT) { - // Inline comments might just be closing comments for - // control structures or functions instead of function comments - // using the wrong comment type. If there is other code on the line, - // assume they relate to that code. - $prev = $phpcsFile->findPrevious($ignore, $commentEnd - 1, null, true); - if ($prev !== false && $tokens[$prev]['line'] === $tokens[$commentEnd]['line']) { - $commentEnd = $prev; - } - } - - if ( - $tokens[$commentEnd]['code'] !== T_DOC_COMMENT_CLOSE_TAG - && $tokens[$commentEnd]['code'] !== T_COMMENT - ) { - $previous = $commentEnd; - if ( - $tokens[$commentEnd]['code'] === T_ATTRIBUTE_END - || $tokens[$commentEnd]['code'] === T_ATTRIBUTE - ) { - while ($tokens[$previous]['code'] !== T_ATTRIBUTE) { - $previous--; - } - $previous--; - - $commentEnd = $phpcsFile->findPrevious($ignore, $previous, null, true); - if ($tokens[$commentEnd]['code'] === T_DOC_COMMENT_CLOSE_TAG) { - if ($tokens[$commentEnd]['line'] !== $tokens[$previous]['line'] - 1) { - $error = 'There must be no blank lines after the function comment'; - $phpcsFile->addError($error, $commentEnd, 'SpacingAfter'); - } - - return; - } - } - $function = $phpcsFile->getDeclarationName($stackPtr); + $docCommentEnd = $phpcsFile->findPrevious( + [T_DOC_COMMENT_CLOSE_TAG, T_SEMICOLON, T_CLOSE_CURLY_BRACKET, T_OPEN_CURLY_BRACKET], + $stackPtr - 1, + null, + ); + if ($docCommentEnd === false || $tokens[$docCommentEnd]['code'] !== T_DOC_COMMENT_CLOSE_TAG) { $phpcsFile->addError( 'Missing doc comment for function %s()', $stackPtr, 'Missing', - [$function] + [$phpcsFile->getDeclarationName($stackPtr)], ); - $phpcsFile->recordMetric($stackPtr, 'Function has doc comment', 'no'); return; - } else { - $phpcsFile->recordMetric($stackPtr, 'Function has doc comment', 'yes'); } - if ($tokens[$commentEnd]['code'] === T_COMMENT) { - $phpcsFile->addError('You must use "/**" style comments for a function comment', $stackPtr, 'WrongStyle'); - - return; - } - - if ($tokens[$commentEnd]['line'] !== $tokens[$stackPtr]['line'] - 1) { - $error = 'There must be no blank lines after the function comment'; - $phpcsFile->addError($error, $commentEnd, 'SpacingAfter'); - } + $lastEndToken = $docCommentEnd; + do { + $attribute = $phpcsFile->findNext( + [T_ATTRIBUTE], + $lastEndToken + 1, + $stackPtr, + ); + if ($attribute !== false) { + if ($tokens[$lastEndToken]['line'] !== $tokens[$attribute]['line'] - 1) { + $phpcsFile->addError( + 'There must be no blank lines after the function comment or attribute', + $lastEndToken, + 'SpacingAfter', + ); - $commentStart = $tokens[$commentEnd]['comment_opener']; - foreach ($tokens[$commentStart]['comment_tags'] as $tag) { - if ($tokens[$tag]['content'] === '@see') { - // Make sure the tag isn't empty. - $string = $phpcsFile->findNext(T_DOC_COMMENT_STRING, $tag, $commentEnd); - if ($string === false || $tokens[$string]['line'] !== $tokens[$tag]['line']) { - $error = 'Content missing for @see tag in function comment'; - $phpcsFile->addError($error, $tag, 'EmptySees'); + return; } - } - } - $this->processReturn($phpcsFile, $stackPtr, $commentStart); - $this->processThrows($phpcsFile, $stackPtr, $commentStart); - $this->processParams($phpcsFile, $stackPtr, $commentStart); - } - - /** - * Checks if the doc comment is an inheritDoc comment. - * - * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. - * @param int $commentStart The position in the stack where the comment started. - * @return bool True if the comment is an inheritdoc - */ - protected function isInheritDoc(File $phpcsFile, $commentStart) - { - $tokens = $phpcsFile->getTokens(); - - $empty = [ - T_DOC_COMMENT_WHITESPACE, - T_DOC_COMMENT_STAR, - ]; - - $commentEnd = $tokens[$commentStart]['comment_closer']; - $inheritDoc = $phpcsFile->findNext($empty, $commentStart + 1, $commentEnd, true); - if ($inheritDoc === false) { - return false; - } - - if (preg_match('/^@inheritDoc$/i', $tokens[$inheritDoc]['content']) === 1) { - return true; - } - - if (preg_match('/^{@inheritDoc}$/i', $tokens[$inheritDoc]['content']) !== 1) { - return false; - } - - $notAllowed = ['@param', '@return']; - foreach ($tokens[$commentStart]['comment_tags'] as $tag) { - if (in_array($tokens[$tag]['content'], $notAllowed, true)) { - return false; + $lastEndToken = $tokens[$attribute]['attribute_closer']; } + } while ($attribute !== false); + + if ($tokens[$lastEndToken]['line'] !== $tokens[$stackPtr]['line'] - 1) { + $phpcsFile->addError( + 'There must be no blank lines after the function comment or attribute', + $lastEndToken, + 'SpacingAfter', + ); } - return true; + $commentStart = $tokens[$docCommentEnd]['comment_opener']; + $this->processTagSpacing($phpcsFile, $stackPtr, $commentStart); + $this->processThrows($phpcsFile, $stackPtr, $commentStart); } /** - * Process the return comment of this function comment. - * * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. * @param int $stackPtr The position of the current token in the stack passed in $tokens. - * @param int $commentStart The position in the stack where the comment started. * @return void */ - protected function processReturn(File $phpcsFile, $stackPtr, $commentStart) + protected function processTagSpacing(File $phpcsFile, int $stackPtr, int $commentStart): void { - if ($this->isInheritDoc($phpcsFile, $commentStart)) { - return; - } - $tokens = $phpcsFile->getTokens(); - - // Skip constructor and destructor. - $className = ''; - foreach ($tokens[$stackPtr]['conditions'] as $condPtr => $condition) { - if ($condition === T_CLASS || $condition === T_INTERFACE) { - $className = $phpcsFile->getDeclarationName($condPtr); - $className = strtolower(ltrim($className, '_')); - } - } - - $methodName = $phpcsFile->getDeclarationName($stackPtr); - $isSpecialMethod = ($methodName === '__construct' || $methodName === '__destruct'); - if ($methodName !== '_') { - $methodName = strtolower(ltrim($methodName, '_')); - } - - $return = null; - foreach ($tokens[$commentStart]['comment_tags'] as $tag) { - if ($tokens[$tag]['content'] === '@return') { - if ($return !== null) { - $error = 'Only 1 @return tag is allowed in a function comment'; - $phpcsFile->addError($error, $tag, 'DuplicateReturn'); - - return; - } - - $return = $tag; - } - } - - if ($isSpecialMethod === true) { - return; - } - - if ($return === null) { - $error = 'Missing @return tag in function comment'; - $phpcsFile->addWarning($error, $tokens[$commentStart]['comment_closer'], 'MissingReturn'); - - return; - } - - $content = $tokens[$return + 2]['content']; - if (empty($content) === true || $tokens[$return + 2]['code'] !== T_DOC_COMMENT_STRING) { - $error = 'Return type missing for @return tag in function comment'; - $phpcsFile->addError($error, $return, 'MissingReturnType'); - - return; - } - - $endToken = $tokens[$stackPtr]['scope_closer'] ?? false; - if (!$endToken) { - return; - } - - [$types, ] = explode(' ', $content); - $typeNames = explode('|', $types); - - // If the return type is void, make sure there is - // no non-void return statements in the function. - if ($typeNames === ['void']) { - for ($returnToken = $stackPtr; $returnToken < $endToken; $returnToken++) { - if ($tokens[$returnToken]['code'] === T_CLOSURE) { - $returnToken = $tokens[$returnToken]['scope_closer']; - continue; - } - - if ( - $tokens[$returnToken]['code'] === T_RETURN - || $tokens[$returnToken]['code'] === T_YIELD - || $tokens[$returnToken]['code'] === T_YIELD_FROM - ) { - break; - } - } - - if ($returnToken !== $endToken) { - // If the function is not returning anything, just - // exiting, then there is no problem. - $semicolon = $phpcsFile->findNext(T_WHITESPACE, $returnToken + 1, null, true); - if ($tokens[$semicolon]['code'] !== T_SEMICOLON) { - $error = 'Function return type is void, but function contains return statement'; - $phpcsFile->addWarning($error, $return, 'InvalidReturnVoid'); - } - } - - return; - } - - // If return type is not void, there needs to be a return statement - // somewhere in the function that returns something. - if (!in_array('mixed', $typeNames, true) && !in_array('void', $typeNames, true)) { - $returnToken = $phpcsFile->findNext([T_RETURN, T_YIELD, T_YIELD_FROM], $stackPtr, $endToken); - if ($returnToken === false && !$this->hasException($phpcsFile, $stackPtr, $endToken)) { - $error = 'Function return type is not void, but function has no return statement'; - $phpcsFile->addWarning($error, $return, 'InvalidNoReturn'); - } else { - $semicolon = $phpcsFile->findNext(T_WHITESPACE, $returnToken + 1, null, true); - if ($tokens[$semicolon]['code'] === T_SEMICOLON) { - $error = 'Function return type is not void, but function is returning void here'; - $phpcsFile->addWarning($error, $returnToken, 'InvalidReturnNotVoid'); + $tags = $tokens[$commentStart]['comment_tags']; + foreach ($tags as $tag) { + if ( + $tokens[$tag + 2]['code'] === T_DOC_COMMENT_STRING && + $phpcsFile->fixer->getTokenContent($tag + 1) !== ' ' + ) { + $fix = $phpcsFile->addFixableWarning('Should be only one space after tag', $tag, 'TagAlignment'); + if ($fix) { + $phpcsFile->fixer->beginChangeset(); + $phpcsFile->fixer->replaceToken($tag + 1, ' '); + $phpcsFile->fixer->endChangeset(); } } } } - /** - * @param \PHP_CodeSniffer\Files\File $phpcsFile File - * @param int $startIndex Start index - * @param int $endIndex End index - * @return bool - */ - protected function hasException(File $phpcsFile, $startIndex, $endIndex) - { - $throwIndex = $phpcsFile->findNext([T_THROW], $startIndex, $endIndex); - - return $throwIndex !== false; - } - /** * Process any throw tags that this function comment has. * @@ -362,23 +139,20 @@ protected function hasException(File $phpcsFile, $startIndex, $endIndex) * @param int $commentStart The position in the stack where the comment started. * @return void */ - protected function processThrows(File $phpcsFile, $stackPtr, $commentStart) + protected function processThrows(File $phpcsFile, int $stackPtr, int $commentStart): void { $tokens = $phpcsFile->getTokens(); - foreach ($tokens[$commentStart]['comment_tags'] as $pos => $tag) { + foreach ($tokens[$commentStart]['comment_tags'] as $tag) { if ($tokens[$tag]['content'] !== '@throws') { continue; } - $exception = $comment = null; + $exception = null; if ($tokens[$tag + 2]['code'] === T_DOC_COMMENT_STRING) { $matches = []; preg_match('/([^\s]+)(?:\s+(.*))?/', $tokens[$tag + 2]['content'], $matches); - $exception = $matches[1]; - if (isset($matches[2]) === true) { - $comment = $matches[2]; - } + $exception = $matches[1] ?? null; } if ($exception === null) { @@ -387,190 +161,4 @@ protected function processThrows(File $phpcsFile, $stackPtr, $commentStart) } } } - - /** - * Process the function parameter comments. - * - * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. - * @param int $stackPtr The position of the current token in the stack passed in $tokens. - * @param int $commentStart The position in the stack where the comment started. - * @return void - */ - protected function processParams(File $phpcsFile, $stackPtr, $commentStart) - { - if ($this->isInheritDoc($phpcsFile, $commentStart)) { - return; - } - - $tokens = $phpcsFile->getTokens(); - - $params = []; - $maxType = $maxVar = 0; - foreach ($tokens[$commentStart]['comment_tags'] as $pos => $tag) { - if ($tokens[$tag]['content'] !== '@param') { - continue; - } - - $type = $var = $comment = ''; - $typeSpace = $varSpace = 0; - $commentLines = []; - if ($tokens[$tag + 2]['code'] === T_DOC_COMMENT_STRING) { - $matches = []; - preg_match('/([^$]+)(?:((?:\$|&)[^\s]+)(?:(\s+)(.*))?)?/', $tokens[$tag + 2]['content'], $matches); - - $typeLen = strlen($matches[1]); - $type = trim($matches[1]); - $typeSpace = $typeLen - strlen($type); - $typeLen = strlen($type); - if ($typeLen > $maxType) { - $maxType = $typeLen; - } - - if (isset($matches[2]) === true) { - $var = $matches[2]; - $varLen = strlen($var); - if ($varLen > $maxVar) { - $maxVar = $varLen; - } - - if (isset($matches[4]) === true) { - $varSpace = strlen($matches[3]); - $comment = $matches[4]; - $commentLines[] = [ - 'comment' => $comment, - 'token' => $tag + 2, - 'indent' => $varSpace, - ]; - - // Any strings until the next tag belong to this comment. - if (isset($tokens[$commentStart]['comment_tags'][$pos + 1]) === true) { - $end = $tokens[$commentStart]['comment_tags'][$pos + 1]; - } else { - $end = $tokens[$commentStart]['comment_closer']; - } - - for ($i = $tag + 3; $i < $end; $i++) { - if ($tokens[$i]['code'] === T_DOC_COMMENT_STRING) { - $indent = 0; - if ($tokens[$i - 1]['code'] === T_DOC_COMMENT_WHITESPACE) { - $indent = strlen($tokens[$i - 1]['content']); - } - - $comment .= ' ' . $tokens[$i]['content']; - $commentLines[] = [ - 'comment' => $tokens[$i]['content'], - 'token' => $i, - 'indent' => $indent, - ]; - } - } - } else { - $error = 'Missing parameter comment'; - $phpcsFile->addError($error, $tag, 'MissingParamComment'); - $commentLines[] = ['comment' => '']; - } - } else { - $error = 'Missing parameter name'; - $phpcsFile->addError($error, $tag, 'MissingParamName'); - } - } else { - $error = 'Missing parameter type'; - $phpcsFile->addError($error, $tag, 'MissingParamType'); - } - - $params[] = compact('tag', 'type', 'var', 'comment', 'commentLines', 'typeSpace', 'varSpace'); - } - - $realParams = $phpcsFile->getMethodParameters($stackPtr); - $foundParams = []; - - foreach ($params as $pos => $param) { - // If the type is empty, the whole line is empty. - if ($param['type'] === '') { - continue; - } - - // Check the param type value. - $typeNames = explode('|', $param['type']); - foreach ($typeNames as $typeName) { - if ($typeName === 'integer') { - $suggestedName = 'int'; - } elseif ($typeName === 'boolean') { - $suggestedName = 'bool'; - } elseif (in_array($typeName, ['int', 'bool'])) { - $suggestedName = $typeName; - } else { - $suggestedName = Common::suggestType($typeName); - } - - if ($typeName !== $suggestedName) { - $error = 'Expected "%s" but found "%s" for parameter type'; - $data = [$suggestedName, $typeName]; - - $fix = $phpcsFile->addFixableError($error, $param['tag'], 'IncorrectParamVarName', $data); - if ($fix === true) { - $content = $suggestedName; - $content .= str_repeat(' ', $param['typeSpace']); - $content .= $param['var']; - $content .= str_repeat(' ', $param['varSpace']); - if (isset($param['commentLines'][0])) { - $content .= $param['commentLines'][0]['comment']; - } - $phpcsFile->fixer->replaceToken($param['tag'] + 2, $content); - } - } - } - - if ($param['var'] === '') { - continue; - } - - $foundParams[] = $param['var']; - - // Make sure the param name is correct. - if (isset($realParams[$pos]) === true) { - $realName = $realParams[$pos]['name']; - if ($realName !== $param['var']) { - $code = 'ParamNameNoMatch'; - $data = [$param['var'], $realName]; - - $error = 'Doc comment for parameter %s does not match '; - if (strtolower($param['var']) === strtolower($realName)) { - $error .= 'case of '; - $code = 'ParamNameNoCaseMatch'; - } - - $error .= 'actual variable name %s'; - - $fix = $phpcsFile->addFixableWarning($error, $param['tag'], $code, $data); - - if ($fix === true) { - $content = $suggestedName; - $content .= str_repeat(' ', $param['typeSpace']); - $content .= $realName; - $content .= str_repeat(' ', $param['varSpace']); - $content .= $param['commentLines'][0]['comment']; - $phpcsFile->fixer->replaceToken($param['tag'] + 2, $content); - } - } - } elseif (substr($param['var'], -4) !== ',...') { - // We must have an extra parameter comment. - $error = 'Superfluous parameter comment'; - $phpcsFile->addError($error, $param['tag'], 'ExtraParamComment'); - } - } - - $realNames = []; - foreach ($realParams as $realParam) { - $realNames[] = $realParam['name']; - } - - // Report missing comments. - $diff = array_diff($realNames, $foundParams); - foreach ($diff as $neededParam) { - $error = 'Doc comment for parameter "%s" missing'; - $data = [$neededParam]; - $phpcsFile->addWarning($error, $commentStart, 'MissingParamTag', $data); - } - } } diff --git a/app/vendor/cakephp/cakephp-codesniffer/CakePHP/Sniffs/Commenting/InheritDocSniff.php b/app/vendor/cakephp/cakephp-codesniffer/CakePHP/Sniffs/Commenting/InheritDocSniff.php index d36475c2f..b03243377 100644 --- a/app/vendor/cakephp/cakephp-codesniffer/CakePHP/Sniffs/Commenting/InheritDocSniff.php +++ b/app/vendor/cakephp/cakephp-codesniffer/CakePHP/Sniffs/Commenting/InheritDocSniff.php @@ -38,7 +38,6 @@ public function process(File $phpcsFile, $stackPtr) { $tokens = $phpcsFile->getTokens(); - $commentStart = $stackPtr; $commentEnd = $tokens[$stackPtr]['comment_closer']; $empty = [ diff --git a/app/vendor/cakephp/cakephp-codesniffer/CakePHP/Sniffs/Commenting/TypeHintSniff.php b/app/vendor/cakephp/cakephp-codesniffer/CakePHP/Sniffs/Commenting/TypeHintSniff.php new file mode 100644 index 000000000..da82ecfcb --- /dev/null +++ b/app/vendor/cakephp/cakephp-codesniffer/CakePHP/Sniffs/Commenting/TypeHintSniff.php @@ -0,0 +1,312 @@ + + */ + protected static array $typeHintTags = [ + '@var', + '@psalm-var', + '@phpstan-var', + '@param', + '@psalm-param', + '@phpstan-param', + '@return', + '@psalm-return', + '@phpstan-return', + ]; + + /** + * Returns an array of tokens this test wants to listen for. + * + * @return array + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingNativeTypeHint + */ + public function register() + { + return [T_DOC_COMMENT_OPEN_TAG]; + } + + /** + * @inheritDoc + */ + public function process(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + if (!isset($tokens[$stackPtr]['comment_closer'])) { + return; + } + + foreach ($tokens[$stackPtr]['comment_tags'] as $tag) { + if ( + $tokens[$tag + 2]['code'] !== T_DOC_COMMENT_STRING || + !in_array($tokens[$tag]['content'], self::$typeHintTags, true) + ) { + continue; + } + + $tagComment = $phpcsFile->fixer->getTokenContent($tag + 2); + $valueNode = self::getValueNode($tokens[$tag]['content'], $tagComment); + if ($valueNode instanceof InvalidTagValueNode) { + continue; + } + + if (isset($valueNode->type)) { + if ($valueNode->type instanceof UnionTypeNode) { + $types = $valueNode->type->types; + } elseif ($valueNode->type instanceof ArrayTypeNode) { + $types = [$valueNode->type]; + } else { + continue; + } + } else { + $phpcsFile->addWarning('@param type hint is missing', $tag, 'MissingParamType'); + continue; + } + + if ($this->ignorePhpStormGenerics && $this->isPhpStormGenericType($types)) { + continue; + } + + $originalTypeHint = $this->renderUnionTypes($types); + $sortedTypeHint = $this->getSortedTypeHint($types); + if ($sortedTypeHint === $originalTypeHint) { + continue; + } + + $fix = $phpcsFile->addFixableWarning( + '%s type hint is not formatted properly, expected "%s"', + $tag, + 'IncorrectFormat', + [$tokens[$tag]['content'], $sortedTypeHint], + ); + if (!$fix) { + continue; + } + + $newComment = $tagComment; + if ($valueNode instanceof VarTagValueNode) { + $newComment = trim(sprintf( + '%s %s %s', + $sortedTypeHint, + $valueNode->variableName, + $valueNode->description, + )); + if ($tagComment[-1] === ' ') { + // tags above variables in code have a trailing space + $newComment .= ' '; + } + } elseif ($valueNode instanceof ParamTagValueNode) { + $newComment = trim(sprintf( + '%s %s%s %s', + $sortedTypeHint, + $valueNode->isVariadic ? '...' : '', + $valueNode->parameterName, + $valueNode->description, + )); + } elseif ($valueNode instanceof ReturnTagValueNode) { + $newComment = trim(sprintf( + '%s %s', + $sortedTypeHint, + $valueNode->description, + )); + } + + $phpcsFile->fixer->beginChangeset(); + $phpcsFile->fixer->replaceToken($tag + 2, $newComment); + $phpcsFile->fixer->endChangeset(); + } + } + + /** + * @param array $types node types + * @return bool + */ + protected function isPhpStormGenericType(array $types): bool + { + if (count($types) != 2) { + return false; + } + + return $types[0] instanceof IdentifierTypeNode && + $types[1] instanceof ArrayTypeNode && + $types[0]->name[0] === '\\'; + } + + /** + * @param array $types node types + * @return string + */ + protected function getSortedTypeHint(array $types): string + { + static $shouldSort = [ + '\\Closure', + '\\Generator', + '\\ArrayObject', + '\\ArrayAccess', + '\\Traversable', + '\\Stringable', + '\\Countable', + '$this', + 'self', + 'mixed', + 'callable', + 'resource', + 'object', + 'iterable', + 'list', + 'array', + 'callable-string', + 'class-string', + 'interface-string', + 'scalar', + 'string', + 'float', + 'int', + 'bool', + 'true', + 'false', + 'null', + 'void', + ]; + + $sortable = array_fill_keys($shouldSort, []); + $unsortable = []; + foreach ($types as $type) { + $sortName = null; + if ($type instanceof IdentifierTypeNode) { + $sortName = $type->name; + } elseif ($type instanceof NullableTypeNode) { + if ($type->type instanceof IdentifierTypeNode) { + $sortName = $type->type->name; + } + } elseif ($type instanceof ArrayTypeNode) { + if ($this->convertArraysToGenerics) { + $type = new GenericTypeNode(new IdentifierTypeNode('array'), [$type->type]); + $sortName = 'array'; + } elseif ($type->type instanceof IdentifierTypeNode) { + $sortName = $type->type->name; + } else { + $sortName = 'array'; + } + } elseif ($type instanceof ArrayShapeNode) { + $sortName = 'array'; + } elseif ($type instanceof GenericTypeNode) { + if (in_array($type->type->name, $shouldSort)) { + $sortName = $type->type->name; + } + } + + if (!$sortName) { + $unsortable[] = $type; + continue; + } + + if (in_array($sortName, $shouldSort, true)) { + if ($type instanceof ArrayTypeNode) { + array_unshift($sortable[$sortName], $type); + } else { + $sortable[$sortName][] = $type; + } + } else { + $unsortable[] = $type; + } + } + + $sorted = []; + array_walk($sortable, function ($types) use (&$sorted): void { + $sorted = array_merge($sorted, $types); + }); + + return $this->renderUnionTypes(array_merge($unsortable, $sorted)); + } + + /** + * @param array<\PHPStan\PhpDocParser\Ast\Type\TypeNode> $typeNodes type nodes + * @return string + */ + protected function renderUnionTypes(array $typeNodes): string + { + // Remove parenthesis added by phpstan around union and intersection types + return (string)preg_replace( + ['/ ([\|&]) /', '/<\(/', '/\)>/', '/\), /', '/, \(/'], + ['${1}', '<', '>', ', ', ', '], + implode('|', $typeNodes), + ); + } + + /** + * @param string $tagName tag name + * @param string $tagComment tag comment + * @return \PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode + */ + protected static function getValueNode(string $tagName, string $tagComment): PhpDocTagValueNode + { + static $phpDocParser; + if (!$phpDocParser) { + $config = new ParserConfig(usedAttributes: ['lines' => true, 'indexes' => true]); + + $constExprParser = new ConstExprParser($config); + $phpDocParser = new PhpDocParser($config, new TypeParser($config, $constExprParser), $constExprParser); + } + + static $phpDocLexer; + if (!$phpDocLexer) { + $config = new ParserConfig(usedAttributes: ['lines' => true, 'indexes' => true]); + $phpDocLexer = new Lexer($config); + } + + return $phpDocParser->parseTagValue(new TokenIterator($phpDocLexer->tokenize($tagComment)), $tagName); + } +} diff --git a/app/vendor/cakephp/cakephp-codesniffer/CakePHP/Sniffs/Formatting/BlankLineBeforeReturnSniff.php b/app/vendor/cakephp/cakephp-codesniffer/CakePHP/Sniffs/Formatting/BlankLineBeforeReturnSniff.php index f5da30288..916da5588 100644 --- a/app/vendor/cakephp/cakephp-codesniffer/CakePHP/Sniffs/Formatting/BlankLineBeforeReturnSniff.php +++ b/app/vendor/cakephp/cakephp-codesniffer/CakePHP/Sniffs/Formatting/BlankLineBeforeReturnSniff.php @@ -27,16 +27,6 @@ */ class BlankLineBeforeReturnSniff implements Sniff { - /** - * A list of tokenizers this sniff supports. - * - * @var array - */ - public $supportedTokenizers = [ - 'PHP', - 'JS', - ]; - /** * @inheritDoc */ @@ -56,18 +46,17 @@ public function process(File $phpcsFile, $stackPtr) $prevLineTokens = []; while ($current >= 0 && $tokens[$current]['line'] >= $previousLine) { - $currentTokenCode = $tokens[$current]['code']; if ( $tokens[$current]['line'] == $previousLine - && $currentTokenCode !== T_WHITESPACE - && $currentTokenCode !== T_COMMENT - && $currentTokenCode !== T_DOC_COMMENT_OPEN_TAG - && $currentTokenCode !== T_DOC_COMMENT_TAG - && $currentTokenCode !== T_DOC_COMMENT_STRING - && $currentTokenCode !== T_DOC_COMMENT_CLOSE_TAG - && $currentTokenCode !== T_DOC_COMMENT_WHITESPACE + && $tokens[$current]['code'] !== T_WHITESPACE + && $tokens[$current]['code'] !== T_COMMENT + && $tokens[$current]['code'] !== T_DOC_COMMENT_OPEN_TAG + && $tokens[$current]['code'] !== T_DOC_COMMENT_TAG + && $tokens[$current]['code'] !== T_DOC_COMMENT_STRING + && $tokens[$current]['code'] !== T_DOC_COMMENT_CLOSE_TAG + && $tokens[$current]['code'] !== T_DOC_COMMENT_WHITESPACE ) { - $prevLineTokens[] = $currentTokenCode; + $prevLineTokens[] = $tokens[$current]['code']; } $current--; } @@ -83,7 +72,7 @@ public function process(File $phpcsFile, $stackPtr) $fix = $phpcsFile->addFixableError( 'Missing blank line before return statement', $stackPtr, - 'BlankLineBeforeReturn' + 'BlankLineBeforeReturn', ); if ($fix === true) { $phpcsFile->fixer->beginChangeset(); diff --git a/app/vendor/cakephp/cakephp-codesniffer/CakePHP/Sniffs/Functions/ClosureDeclarationSniff.php b/app/vendor/cakephp/cakephp-codesniffer/CakePHP/Sniffs/Functions/ClosureDeclarationSniff.php deleted file mode 100644 index 3756a8712..000000000 --- a/app/vendor/cakephp/cakephp-codesniffer/CakePHP/Sniffs/Functions/ClosureDeclarationSniff.php +++ /dev/null @@ -1,51 +0,0 @@ -getTokens(); - $spaces = 0; - - if ($tokens[$stackPtr + 1]['code'] === T_WHITESPACE) { - $spaces = strlen($tokens[$stackPtr + 1]['content']); - } - - if ($spaces !== 1) { - $error = 'Expected 1 space after closure\'s function keyword; %s found'; - $data = [$spaces]; - $phpcsFile->addError($error, $stackPtr, 'SpaceAfterFunction', $data); - } - } -} diff --git a/app/vendor/cakephp/cakephp-codesniffer/CakePHP/Sniffs/NamingConventions/ValidFunctionNameSniff.php b/app/vendor/cakephp/cakephp-codesniffer/CakePHP/Sniffs/NamingConventions/ValidFunctionNameSniff.php index 777a888bc..c7dd3c033 100644 --- a/app/vendor/cakephp/cakephp-codesniffer/CakePHP/Sniffs/NamingConventions/ValidFunctionNameSniff.php +++ b/app/vendor/cakephp/cakephp-codesniffer/CakePHP/Sniffs/NamingConventions/ValidFunctionNameSniff.php @@ -29,7 +29,7 @@ class ValidFunctionNameSniff extends AbstractScopeSniff * * @var array */ - protected $_magicMethods = [ + protected array $_magicMethods = [ 'construct', 'destruct', 'call', @@ -63,6 +63,10 @@ public function __construct() protected function processTokenWithinScope(File $phpcsFile, $stackPtr, $currScope) { $methodName = $phpcsFile->getDeclarationName($stackPtr); + if ($methodName === null) { + return; + } + $className = $phpcsFile->getDeclarationName($currScope); $errorData = [$className . '::' . $methodName]; diff --git a/app/vendor/cakephp/cakephp-codesniffer/CakePHP/Sniffs/PHP/DisallowShortOpenTagSniff.php b/app/vendor/cakephp/cakephp-codesniffer/CakePHP/Sniffs/PHP/DisallowShortOpenTagSniff.php index be835fe74..61ff05067 100644 --- a/app/vendor/cakephp/cakephp-codesniffer/CakePHP/Sniffs/PHP/DisallowShortOpenTagSniff.php +++ b/app/vendor/cakephp/cakephp-codesniffer/CakePHP/Sniffs/PHP/DisallowShortOpenTagSniff.php @@ -32,6 +32,7 @@ class DisallowShortOpenTagSniff implements Sniff * So include T_INLINE_HTML which is what "addFixableError( 'Use single instead of double quotes for simple strings.', $stackPtr, - 'UseSingleQuote' + 'UseSingleQuote', ); if ($fix) { $content = substr($content, 1, -1); diff --git a/app/vendor/cakephp/cakephp-codesniffer/CakePHP/Sniffs/WhiteSpace/FunctionSpacingSniff.php b/app/vendor/cakephp/cakephp-codesniffer/CakePHP/Sniffs/WhiteSpace/FunctionSpacingSniff.php index f5b500856..064f04bb1 100644 --- a/app/vendor/cakephp/cakephp-codesniffer/CakePHP/Sniffs/WhiteSpace/FunctionSpacingSniff.php +++ b/app/vendor/cakephp/cakephp-codesniffer/CakePHP/Sniffs/WhiteSpace/FunctionSpacingSniff.php @@ -53,6 +53,9 @@ public function process(File $phpCsFile, $stackPointer) $closingParenthesisIndex = $tokens[$openingParenthesisIndex]['parenthesis_closer']; $semicolonIndex = $phpCsFile->findNext(T_SEMICOLON, $closingParenthesisIndex + 1); + if (!$semicolonIndex) { + return; + } $nextContentIndex = $phpCsFile->findNext(T_WHITESPACE, $semicolonIndex + 1, null, true); @@ -65,7 +68,7 @@ public function process(File $phpCsFile, $stackPointer) $fix = $phpCsFile->addFixableError( 'Every function/method needs a newline afterwards', $closingParenthesisIndex, - 'Abstract' + 'Abstract', ); if ($fix) { $phpCsFile->fixer->addNewline($semicolonIndex); @@ -84,6 +87,9 @@ public function process(File $phpCsFile, $stackPointer) } $nextContentIndex = $phpCsFile->findNext(T_WHITESPACE, $closingBraceIndex + 1, null, true); + if (!$nextContentIndex) { + return; + } // Do not mess with the end of the class if ($tokens[$nextContentIndex]['code'] === T_CLOSE_CURLY_BRACKET) { @@ -100,7 +106,7 @@ public function process(File $phpCsFile, $stackPointer) * @param int|null $nextContentIndex Index * @return void */ - protected function assertNewLineAtTheEnd(File $phpCsFile, $closingBraceIndex, $nextContentIndex) + protected function assertNewLineAtTheEnd(File $phpCsFile, int $closingBraceIndex, ?int $nextContentIndex): void { $tokens = $phpCsFile->getTokens(); @@ -108,7 +114,7 @@ protected function assertNewLineAtTheEnd(File $phpCsFile, $closingBraceIndex, $n $fix = $phpCsFile->addFixableError( 'Every function/method needs a newline afterwards', $closingBraceIndex, - 'Concrete' + 'Concrete', ); if ($fix) { $phpCsFile->fixer->addNewline($closingBraceIndex); @@ -123,7 +129,7 @@ protected function assertNewLineAtTheEnd(File $phpCsFile, $closingBraceIndex, $n * @param int $stackPointer Stack pointer * @return void */ - protected function assertNewLineAtTheBeginning(File $phpCsFile, $stackPointer) + protected function assertNewLineAtTheBeginning(File $phpCsFile, int $stackPointer): void { $tokens = $phpCsFile->getTokens(); @@ -147,6 +153,9 @@ protected function assertNewLineAtTheBeginning(File $phpCsFile, $stackPointer) } $prevContentIndex = $phpCsFile->findPrevious(T_WHITESPACE, $firstTokenInLineIndex - 1, null, true); + if (!$prevContentIndex) { + return; + } // Do not mess with the start of the class if ($tokens[$prevContentIndex]['code'] === T_OPEN_CURLY_BRACKET) { @@ -160,7 +169,7 @@ protected function assertNewLineAtTheBeginning(File $phpCsFile, $stackPointer) $fix = $phpCsFile->addFixableError( 'Every function/method needs a newline before', $firstTokenInLineIndex, - 'Concrete' + 'Concrete', ); if ($fix) { $phpCsFile->fixer->addNewline($prevContentIndex); diff --git a/app/vendor/cakephp/cakephp-codesniffer/CakePHP/Sniffs/WhiteSpace/TabAndSpaceSniff.php b/app/vendor/cakephp/cakephp-codesniffer/CakePHP/Sniffs/WhiteSpace/TabAndSpaceSniff.php index c39c9f251..5b179ed19 100644 --- a/app/vendor/cakephp/cakephp-codesniffer/CakePHP/Sniffs/WhiteSpace/TabAndSpaceSniff.php +++ b/app/vendor/cakephp/cakephp-codesniffer/CakePHP/Sniffs/WhiteSpace/TabAndSpaceSniff.php @@ -24,17 +24,6 @@ class TabAndSpaceSniff implements Sniff { - /** - * A list of tokenizers this sniff supports. - * - * @var array - */ - public $supportedTokenizers = [ - 'PHP', - 'JS', - 'CSS', - ]; - /** * @inheritDoc */ diff --git a/app/vendor/cakephp/cakephp-codesniffer/CakePHP/ruleset.xml b/app/vendor/cakephp/cakephp-codesniffer/CakePHP/ruleset.xml index 5ad91117b..b20fa1a0c 100644 --- a/app/vendor/cakephp/cakephp-codesniffer/CakePHP/ruleset.xml +++ b/app/vendor/cakephp/cakephp-codesniffer/CakePHP/ruleset.xml @@ -65,6 +65,9 @@ */tests/* + + */tests/* + @@ -97,6 +100,24 @@ + + + + + + + + + + + + + + + + + + @@ -116,17 +137,17 @@ - - - - - + + + + + - + @@ -142,7 +163,15 @@ */tests/* + + + + + + + + @@ -164,10 +193,64 @@ */tests/Fixture/* - + + + + + + + + */tests/* + + + + + + + + + + */tests/* + + + + + + + + + */tests/* + + + + + + + + + + */tests/* + + + + + + + + + + + + + + + + + + diff --git a/app/vendor/cakephp/cakephp-codesniffer/README.md b/app/vendor/cakephp/cakephp-codesniffer/README.md index 1d7e3943b..ee0b0d9d7 100644 --- a/app/vendor/cakephp/cakephp-codesniffer/README.md +++ b/app/vendor/cakephp/cakephp-codesniffer/README.md @@ -1,11 +1,11 @@ # CakePHP Code Sniffer -![Build Status](https://github.com/cakephp/cakephp-codesniffer/actions/workflows/ci.yml/badge.svg?branch=master) +![Build Status](https://github.com/cakephp/cakephp-codesniffer/actions/workflows/ci.yml/badge.svg?branch=5.next) [![Total Downloads](https://img.shields.io/packagist/dt/cakephp/cakephp-codesniffer.svg?style=flat-square)](https://packagist.org/packages/cakephp/cakephp-codesniffer) [![Latest Stable Version](https://img.shields.io/packagist/v/cakephp/cakephp-codesniffer.svg?style=flat-square)](https://packagist.org/packages/cakephp/cakephp-codesniffer) [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) -This code works with [squizlabs/php_codesniffer](https://github.com/squizlabs/PHP_CodeSniffer) +This code works with [squizlabs/php_codesniffer](https://github.com/PHPCSStandards/PHP_CodeSniffer) and checks code against the coding standards used in CakePHP. This sniffer package follows [PSR-12](https://www.php-fig.org/psr/psr-12/) completely and ships with a lot of additional fixers on top. @@ -62,14 +62,19 @@ additional configuration state in `phpcs` that is required. composer test ``` -Once this has been done once, you can use `phpunit --filter CakePHP` to run the +Once this has been done once, you can use `composer phpunit` to run the tests for the rules in this repository. +The tests are present inside the `CakePHP/Tests/` folder. + ## Contributing If you'd like to contribute to the Code Sniffer, you can fork the project add features and send pull requests. +> [!NOTE] +> Please make sure to run `composer docs` if you change the ruleset.xml file. + ## Releasing CakePHP Code Sniffer * Create a signed tag diff --git a/app/vendor/cakephp/cakephp-codesniffer/composer.json b/app/vendor/cakephp/cakephp-codesniffer/composer.json index 4e593b5ee..e7f83ff4c 100644 --- a/app/vendor/cakephp/cakephp-codesniffer/composer.json +++ b/app/vendor/cakephp/cakephp-codesniffer/composer.json @@ -18,12 +18,13 @@ "source": "https://github.com/cakephp/cakephp-codesniffer" }, "require": { - "php": ">=7.2.0", - "slevomat/coding-standard": "^7.0 || ^8.0", - "squizlabs/php_codesniffer": "^3.6" + "php": ">=8.1.0", + "phpstan/phpdoc-parser": "^2.1.0", + "slevomat/coding-standard": "^8.16", + "squizlabs/php_codesniffer": "^3.9" }, "require-dev": { - "phpunit/phpunit": "^7.1" + "phpunit/phpunit": "^9.3.4" }, "autoload": { "psr-4": { @@ -37,12 +38,14 @@ }, "scripts": { "add-standard" : "phpcs --config-set installed_paths $(pwd)", + "phpunit": "phpunit vendor/squizlabs/php_codesniffer/tests/AllTests.php --filter CakePHP --no-configuration --bootstrap=vendor/squizlabs/php_codesniffer/tests/bootstrap.php --dont-report-useless-tests", "test": [ "@add-standard", - "phpunit --filter CakePHP" + "@phpunit" ], "cs-check": "phpcs --colors --parallel=16 -p -s CakePHP/", "cs-fix": "phpcbf --colors --parallel=16 -p CakePHP/", + "stan": "tools/phpstan analyse", "docs": "php docs/generate.php", "explain": "phpcs -e --standard=CakePHP" } diff --git a/app/vendor/cakephp/cakephp-codesniffer/docs/README.md b/app/vendor/cakephp/cakephp-codesniffer/docs/README.md index 225fe6f50..f88e5ed80 100644 --- a/app/vendor/cakephp/cakephp-codesniffer/docs/README.md +++ b/app/vendor/cakephp/cakephp-codesniffer/docs/README.md @@ -1,6 +1,6 @@ # CakePHP ruleset -The CakePHP standard contains 135 sniffs +The CakePHP standard contains 155 sniffs CakePHP (19 sniffs) ------------------- @@ -8,11 +8,11 @@ CakePHP (19 sniffs) - CakePHP.Commenting.DocBlockAlignment - CakePHP.Commenting.FunctionComment - CakePHP.Commenting.InheritDoc +- CakePHP.Commenting.TypeHint - CakePHP.ControlStructures.ControlStructures - CakePHP.ControlStructures.ElseIfDeclaration - CakePHP.ControlStructures.WhileStructures - CakePHP.Formatting.BlankLineBeforeReturn -- CakePHP.Functions.ClosureDeclaration - CakePHP.NamingConventions.ValidFunctionName - CakePHP.NamingConventions.ValidTraitName - CakePHP.PHP.DisallowShortOpenTag @@ -24,7 +24,7 @@ CakePHP (19 sniffs) - CakePHP.WhiteSpace.FunctionSpacing - CakePHP.WhiteSpace.TabAndSpace -Generic (25 sniffs) +Generic (26 sniffs) ------------------- - Generic.Arrays.DisallowLongArraySyntax - Generic.CodeAnalysis.ForLoopShouldBeWhileLoop @@ -37,7 +37,7 @@ Generic (25 sniffs) - Generic.Files.LineEndings - Generic.Files.LineLength - Generic.Formatting.DisallowMultipleStatements -- Generic.Formatting.NoSpaceAfterCast +- Generic.Formatting.SpaceAfterCast - Generic.Functions.FunctionCallArgumentSpacing - Generic.NamingConventions.UpperCaseConstantName - Generic.PHP.DeprecatedFunctions @@ -50,10 +50,11 @@ Generic (25 sniffs) - Generic.PHP.NoSilencedErrors - Generic.WhiteSpace.DisallowTabIndent - Generic.WhiteSpace.IncrementDecrementSpacing +- Generic.WhiteSpace.LanguageConstructSpacing - Generic.WhiteSpace.ScopeIndent PEAR (1 sniff) ---------------- +-------------- - PEAR.Functions.ValidDefaultValue PSR1 (3 sniffs) @@ -62,6 +63,18 @@ PSR1 (3 sniffs) - PSR1.Files.SideEffects - PSR1.Methods.CamelCapsMethodName +PSR2 (9 sniffs) +--------------- +- PSR2.Classes.ClassDeclaration +- PSR2.Classes.PropertyDeclaration +- PSR2.ControlStructures.ElseIfDeclaration +- PSR2.ControlStructures.SwitchDeclaration +- PSR2.Files.ClosingTag +- PSR2.Files.EndFileNewline +- PSR2.Methods.FunctionCallSignature +- PSR2.Methods.FunctionClosingBrace +- PSR2.Methods.MethodDeclaration + PSR12 (17 sniffs) ----------------- - PSR12.Classes.AnonClassDeclaration @@ -82,24 +95,16 @@ PSR12 (17 sniffs) - PSR12.Properties.ConstantVisibility - PSR12.Traits.UseDeclaration -PSR2 (9 sniffs) ---------------- -- PSR2.Classes.ClassDeclaration -- PSR2.Classes.PropertyDeclaration -- PSR2.ControlStructures.ElseIfDeclaration -- PSR2.ControlStructures.SwitchDeclaration -- PSR2.Files.ClosingTag -- PSR2.Files.EndFileNewline -- PSR2.Methods.FunctionCallSignature -- PSR2.Methods.FunctionClosingBrace -- PSR2.Methods.MethodDeclaration - -SlevomatCodingStandard (32 sniffs) +SlevomatCodingStandard (52 sniffs) ---------------------------------- - SlevomatCodingStandard.Arrays.TrailingArrayComma +- SlevomatCodingStandard.Attributes.AttributeAndTargetSpacing +- SlevomatCodingStandard.Attributes.RequireAttributeAfterDocComment +- SlevomatCodingStandard.Classes.BackedEnumTypeSpacing - SlevomatCodingStandard.Classes.ClassConstantVisibility - SlevomatCodingStandard.Classes.EmptyLinesAroundClassBraces - SlevomatCodingStandard.Classes.ModernClassNameReference +- SlevomatCodingStandard.Classes.PropertyDeclaration - SlevomatCodingStandard.Commenting.DisallowOneLinePropertyDocComment - SlevomatCodingStandard.Commenting.DocCommentSpacing - SlevomatCodingStandard.Commenting.EmptyComment @@ -110,10 +115,21 @@ SlevomatCodingStandard (32 sniffs) - SlevomatCodingStandard.ControlStructures.LanguageConstructWithParentheses - SlevomatCodingStandard.ControlStructures.NewWithParentheses - SlevomatCodingStandard.ControlStructures.RequireNullCoalesceOperator +- SlevomatCodingStandard.ControlStructures.RequireShortTernaryOperator - SlevomatCodingStandard.Exceptions.DeadCatch +- SlevomatCodingStandard.Functions.ArrowFunctionDeclaration +- SlevomatCodingStandard.Functions.DisallowTrailingCommaInCall +- SlevomatCodingStandard.Functions.DisallowTrailingCommaInClosureUse +- SlevomatCodingStandard.Functions.DisallowTrailingCommaInDeclaration +- SlevomatCodingStandard.Functions.NamedArgumentSpacing +- SlevomatCodingStandard.Functions.RequireTrailingCommaInCall +- SlevomatCodingStandard.Functions.RequireTrailingCommaInClosureUse +- SlevomatCodingStandard.Functions.RequireTrailingCommaInDeclaration - SlevomatCodingStandard.Namespaces.AlphabeticallySortedUses +- SlevomatCodingStandard.Namespaces.DisallowGroupUse - SlevomatCodingStandard.Namespaces.FullyQualifiedClassNameInAnnotation - SlevomatCodingStandard.Namespaces.NamespaceDeclaration +- SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly - SlevomatCodingStandard.Namespaces.UnusedUses - SlevomatCodingStandard.Namespaces.UseDoesNotStartWithBackslash - SlevomatCodingStandard.Namespaces.UseFromSameNamespace @@ -123,13 +139,18 @@ SlevomatCodingStandard (32 sniffs) - SlevomatCodingStandard.PHP.UselessParentheses - SlevomatCodingStandard.PHP.UselessSemicolon - SlevomatCodingStandard.TypeHints.DeclareStrictTypes +- SlevomatCodingStandard.TypeHints.DNFTypeHintFormat - SlevomatCodingStandard.TypeHints.LongTypeHints - SlevomatCodingStandard.TypeHints.NullableTypeForNullDefaultValue +- SlevomatCodingStandard.TypeHints.ParameterTypeHint - SlevomatCodingStandard.TypeHints.ParameterTypeHintSpacing +- SlevomatCodingStandard.TypeHints.PropertyTypeHint +- SlevomatCodingStandard.TypeHints.ReturnTypeHint - SlevomatCodingStandard.TypeHints.ReturnTypeHintSpacing - SlevomatCodingStandard.Variables.DuplicateAssignmentToVariable +- SlevomatCodingStandard.Variables.UnusedVariable -Squiz (28 sniffs) +Squiz (27 sniffs) ----------------- - Squiz.Arrays.ArrayBracketSpacing - Squiz.Classes.ClassFileName @@ -153,7 +174,6 @@ Squiz (28 sniffs) - Squiz.Scope.StaticThisUsage - Squiz.WhiteSpace.CastSpacing - Squiz.WhiteSpace.ControlStructureSpacing -- Squiz.WhiteSpace.LanguageConstructSpacing - Squiz.WhiteSpace.LogicalOperatorSpacing - Squiz.WhiteSpace.ScopeClosingBrace - Squiz.WhiteSpace.ScopeKeywordSpacing @@ -161,5 +181,5 @@ Squiz (28 sniffs) - Squiz.WhiteSpace.SuperfluousWhitespace Zend (1 sniff) ---------------- +-------------- - Zend.NamingConventions.ValidVariableName \ No newline at end of file diff --git a/app/vendor/cakephp/cakephp-codesniffer/tests/phpstan_bootstrap.php b/app/vendor/cakephp/cakephp-codesniffer/tests/phpstan_bootstrap.php new file mode 100644 index 000000000..3cc86c575 --- /dev/null +++ b/app/vendor/cakephp/cakephp-codesniffer/tests/phpstan_bootstrap.php @@ -0,0 +1,8 @@ + Coverage Status + + PHPStan + Code Consistency @@ -35,7 +38,7 @@ recommend using the [app skeleton](https://github.com/cakephp/app) as a starting point. For existing applications you can run the following: ``` bash -$ composer require cakephp/cakephp +composer require cakephp/cakephp ``` For details on the (minimum/maximum) PHP version see [version map](https://github.com/cakephp/cakephp/wiki#version-map). @@ -51,13 +54,12 @@ tests for CakePHP by doing the following: a non-SQLite datasource. 3. Run `phpunit`. -## Some Handy Links +## Learn More -* [CakePHP](https://cakephp.org) - The rapid development PHP framework. -* [CookBook](https://book.cakephp.org) - The CakePHP user documentation; start learning here! -* [API](https://api.cakephp.org) - A reference to CakePHP's classes. -* [Awesome CakePHP](https://github.com/FriendsOfCake/awesome-cakephp) - A list of featured resources around the framework. -* [Plugins](https://plugins.cakephp.org) - A repository of extensions to the framework. +* [CakePHP](https://cakephp.org) - The home of the CakePHP project. +* [Book](https://book.cakephp.org) - The CakePHP documentation; start learning here! +* [API](https://api.cakephp.org) - A reference to CakePHP's classes and API documentation. +* [Awesome CakePHP](https://github.com/FriendsOfCake/awesome-cakephp) - A curated list of featured resources around the framework. * [The Bakery](https://bakery.cakephp.org) - Tips, tutorials and articles. * [Community Center](https://community.cakephp.org) - A source for everything community related. * [Training](https://training.cakephp.org) - Join a live session and get skilled with the framework. @@ -76,7 +78,7 @@ tests for CakePHP by doing the following: ## Contributing * [CONTRIBUTING.md](.github/CONTRIBUTING.md) - Quick pointers for contributing to the CakePHP project. -* [CookBook "Contributing" Section](https://book.cakephp.org/4/en/contributing.html) - Details about contributing to the project. +* [CookBook "Contributing" Section](https://book.cakephp.org/5/en/contributing.html) - Details about contributing to the project. # Security diff --git a/app/vendor/cakephp/cakephp/VERSION.txt b/app/vendor/cakephp/cakephp/VERSION.txt index 7b31d540c..e75a351c6 100644 --- a/app/vendor/cakephp/cakephp/VERSION.txt +++ b/app/vendor/cakephp/cakephp/VERSION.txt @@ -16,4 +16,4 @@ // @license https://opensource.org/licenses/mit-license.php MIT License // +--------------------------------------------------------------------------------------------+ // //////////////////////////////////////////////////////////////////////////////////////////////////// -4.6.2 +5.2.7 diff --git a/app/vendor/cakephp/cakephp/composer.json b/app/vendor/cakephp/cakephp/composer.json index 7f9c27d9a..80824b2a5 100644 --- a/app/vendor/cakephp/cakephp/composer.json +++ b/app/vendor/cakephp/cakephp/composer.json @@ -22,21 +22,23 @@ } ], "require": { - "php": ">=7.4.0,<9", + "php": ">=8.1", "ext-intl": "*", "ext-json": "*", "ext-mbstring": "*", - "cakephp/chronos": "^2.4.0", - "composer/ca-bundle": "^1.2", - "laminas/laminas-diactoros": "^2.2.2", - "laminas/laminas-httphandlerrunner": "^1.1 || ^2.0", - "league/container": "^4.2.0", + "cakephp/chronos": "^3.1", + "composer/ca-bundle": "^1.5", + "laminas/laminas-diactoros": "^3.3", + "laminas/laminas-httphandlerrunner": "^2.6", + "league/container": "^4.2", "psr/container": "^1.1 || ^2.0", - "psr/http-client": "^1.0", - "psr/http-server-handler": "^1.0", - "psr/http-server-middleware": "^1.0", - "psr/log": "^1.0 || ^2.0", - "psr/simple-cache": "^1.0 || ^2.0" + "psr/http-client": "^1.0.2", + "psr/http-factory": "^1.1", + "psr/http-message": "^1.1 || ^2.0", + "psr/http-server-handler": "^1.0.2", + "psr/http-server-middleware": "^1.0.2", + "psr/log": "^3.0", + "psr/simple-cache": "^2.0 || ^3.0" }, "replace": { "cakephp/cache": "self.version", @@ -46,7 +48,6 @@ "cakephp/database": "self.version", "cakephp/datasource": "self.version", "cakephp/event": "self.version", - "cakephp/filesystem": "self.version", "cakephp/form": "self.version", "cakephp/http": "self.version", "cakephp/i18n": "self.version", @@ -56,30 +57,34 @@ "cakephp/validation": "self.version" }, "require-dev": { - "cakephp/cakephp-codesniffer": "^4.5", + "cakephp/cakephp-codesniffer": "^5.2", + "http-interop/http-factory-tests": "dev-main", "mikey179/vfsstream": "^1.6.10", + "mockery/mockery": "^1.6", "paragonie/csp-builder": "^2.3 || ^3.0", - "phpunit/phpunit": "^8.5 || ^9.3" + "phpunit/phpunit": "^10.5.5 || ^11.1.3 || ^12.0.9" }, "suggest": { "ext-curl": "To enable more efficient network calls in Http\\Client.", "ext-openssl": "To use Security::encrypt() or have secure CSRF token generation.", - "lib-ICU": "To use locale-aware features in the I18n and Database packages", "paragonie/csp-builder": "CSP builder, to use the CSP Middleware" }, "provide": { - "psr/container-implementation": "^1.0 || ^2.0", + "psr/container-implementation": "^2.0", "psr/http-client-implementation": "^1.0", + "psr/http-factory-implementation": "^1.0", "psr/http-server-handler-implementation": "^1.0", "psr/http-server-middleware-implementation": "^1.0", - "psr/log-implementation": "^1.0 || ^2.0", - "psr/simple-cache-implementation": "^1.0 || ^2.0" + "psr/log-implementation": "^3.0", + "psr/simple-cache-implementation": "^3.0" }, "config": { + "lock": false, "process-timeout": 900, "sort-packages": true, "allow-plugins": { - "dealerdirect/phpcodesniffer-composer-installer": true + "dealerdirect/phpcodesniffer-composer-installer": true, + "phpstan/extension-installer": true } }, "autoload": { @@ -91,6 +96,7 @@ "src/Error/functions.php", "src/Collection/functions.php", "src/I18n/functions.php", + "src/ORM/bootstrap.php", "src/Routing/functions.php", "src/Utility/bootstrap.php" ] @@ -114,16 +120,17 @@ "@cs-check", "@test" ], - "cs-check": "phpcs --colors --parallel=16 -p src/ tests/", - "cs-fix": "phpcbf --colors --parallel=16 -p src/ tests/", + "cs-check": "phpcs --colors --parallel=16 -p", + "cs-fix": "phpcbf --colors --parallel=16 -p", "phpstan": "tools/phpstan analyse", "psalm": "tools/psalm --show-info=false", "stan": [ "@phpstan", "@psalm" ], - "stan-tests": "phpstan.phar analyze -c tests/phpstan.neon", - "stan-baseline": "phpstan.phar --generate-baseline", + "phpstan-tests": "tools/phpstan analyze -c tests/phpstan.neon", + "phpstan-baseline": "tools/phpstan --generate-baseline", + "psalm-baseline": "tools/psalm --set-baseline=psalm-baseline.xml", "stan-setup": "phive install", "lowest": "validate-prefer-lowest", "lowest-setup": "composer update --prefer-lowest --prefer-stable --prefer-dist --no-interaction && cp composer.json composer.backup && composer require --dev dereuromark/composer-prefer-lowest && mv composer.backup composer.json", @@ -132,8 +139,12 @@ }, "support": { "issues": "https://github.com/cakephp/cakephp/issues", - "forum": "https://stackoverflow.com/tags/cakephp", - "irc": "irc://irc.freenode.org/cakephp", + "forum": "https://discourse.cakephp.org/", "source": "https://github.com/cakephp/cakephp" + }, + "extra": { + "branch-alias": { + "dev-5.next": "5.2.x-dev" + } } } diff --git a/app/vendor/cakephp/cakephp/config/bootstrap.php b/app/vendor/cakephp/cakephp/config/bootstrap.php index da48e6bff..4e95849bc 100644 --- a/app/vendor/cakephp/cakephp/config/bootstrap.php +++ b/app/vendor/cakephp/cakephp/config/bootstrap.php @@ -15,12 +15,7 @@ use Cake\Routing\Router; -/** - * @var float - */ define('TIME_START', microtime(true)); -require CAKE . 'basics.php'; - // Sets the initial router state so future reloads work. Router::reload(); diff --git a/app/vendor/cakephp/cakephp/src/Auth/AbstractPasswordHasher.php b/app/vendor/cakephp/cakephp/src/Auth/AbstractPasswordHasher.php deleted file mode 100644 index c9f3b4084..000000000 --- a/app/vendor/cakephp/cakephp/src/Auth/AbstractPasswordHasher.php +++ /dev/null @@ -1,79 +0,0 @@ - - */ - protected $_defaultConfig = []; - - /** - * Constructor - * - * @param array $config Array of config. - */ - public function __construct(array $config = []) - { - $this->setConfig($config); - } - - /** - * Generates password hash. - * - * @param string $password Plain text password to hash. - * @return string|false Either the password hash string or false - */ - abstract public function hash(string $password); - - /** - * Check hash. Generate hash from user provided password string or data array - * and check against existing hash. - * - * @param string $password Plain text password to hash. - * @param string $hashedPassword Existing hashed password. - * @return bool True if hashes match else false. - */ - abstract public function check(string $password, string $hashedPassword): bool; - - /** - * Returns true if the password need to be rehashed, due to the password being - * created with anything else than the passwords generated by this class. - * - * Returns true by default since the only implementation users should rely - * on is the one provided by default in php 5.5+ or any compatible library - * - * @param string $password The password to verify - * @return bool - */ - public function needsRehash(string $password): bool - { - return password_needs_rehash($password, PASSWORD_DEFAULT); - } -} diff --git a/app/vendor/cakephp/cakephp/src/Auth/BaseAuthenticate.php b/app/vendor/cakephp/cakephp/src/Auth/BaseAuthenticate.php deleted file mode 100644 index 61dc2b14c..000000000 --- a/app/vendor/cakephp/cakephp/src/Auth/BaseAuthenticate.php +++ /dev/null @@ -1,265 +0,0 @@ - ['some_finder_option' => 'some_value']] - * - `passwordHasher` Password hasher class. Can be a string specifying class name - * or an array containing `className` key, any other keys will be passed as - * config to the class. Defaults to 'Default'. - * - * @var array - */ - protected $_defaultConfig = [ - 'fields' => [ - 'username' => 'username', - 'password' => 'password', - ], - 'userModel' => 'Users', - 'finder' => 'all', - 'passwordHasher' => 'Default', - ]; - - /** - * A Component registry, used to get more components. - * - * @var \Cake\Controller\ComponentRegistry - */ - protected $_registry; - - /** - * Password hasher instance. - * - * @var \Cake\Auth\AbstractPasswordHasher|null - */ - protected $_passwordHasher; - - /** - * Whether the user authenticated by this class - * requires their password to be rehashed with another algorithm. - * - * @var bool - */ - protected $_needsPasswordRehash = false; - - /** - * Constructor - * - * @param \Cake\Controller\ComponentRegistry $registry The Component registry used on this request. - * @param array $config Array of config to use. - */ - public function __construct(ComponentRegistry $registry, array $config = []) - { - $this->_registry = $registry; - $this->setConfig($config); - } - - /** - * Find a user record using the username and password provided. - * - * Input passwords will be hashed even when a user doesn't exist. This - * helps mitigate timing attacks that are attempting to find valid usernames. - * - * @param string $username The username/identifier. - * @param string|null $password The password, if not provided password checking is skipped - * and result of find is returned. - * @return array|false Either false on failure, or an array of user data. - */ - protected function _findUser(string $username, ?string $password = null) - { - $result = $this->_query($username)->first(); - - if ($result === null) { - // Waste time hashing the password, to prevent - // timing side-channels. However, don't hash - // null passwords as authentication systems - // like digest auth don't use passwords - // and hashing *could* create a timing side-channel. - if ($password !== null) { - $hasher = $this->passwordHasher(); - $hasher->hash($password); - } - - return false; - } - - $passwordField = $this->_config['fields']['password']; - if ($password !== null) { - $hasher = $this->passwordHasher(); - $hashedPassword = $result->get($passwordField); - - if ($hashedPassword === null || $hashedPassword === '') { - // Waste time hashing the password, to prevent - // timing side-channels to distinguish whether - // user has password or not. - $hasher->hash($password); - - return false; - } - - if (!$hasher->check($password, $hashedPassword)) { - return false; - } - - $this->_needsPasswordRehash = $hasher->needsRehash($hashedPassword); - $result->unset($passwordField); - } - $hidden = $result->getHidden(); - if ($password === null && in_array($passwordField, $hidden, true)) { - $key = array_search($passwordField, $hidden, true); - unset($hidden[$key]); - $result->setHidden($hidden); - } - - return $result->toArray(); - } - - /** - * Get query object for fetching user from database. - * - * @param string $username The username/identifier. - * @return \Cake\ORM\Query - */ - protected function _query(string $username): Query - { - $config = $this->_config; - $table = $this->getTableLocator()->get($config['userModel']); - - $options = [ - 'conditions' => [$table->aliasField($config['fields']['username']) => $username], - ]; - - $finder = $config['finder']; - if (is_array($finder)) { - $options += current($finder); - $finder = key($finder); - } - - $options['username'] = $options['username'] ?? $username; - - return $table->find($finder, $options); - } - - /** - * Return password hasher object - * - * @return \Cake\Auth\AbstractPasswordHasher Password hasher instance - * @throws \RuntimeException If password hasher class not found or - * it does not extend AbstractPasswordHasher - */ - public function passwordHasher(): AbstractPasswordHasher - { - if ($this->_passwordHasher !== null) { - return $this->_passwordHasher; - } - - $passwordHasher = $this->_config['passwordHasher']; - - return $this->_passwordHasher = PasswordHasherFactory::build($passwordHasher); - } - - /** - * Returns whether the password stored in the repository for the logged in user - * requires to be rehashed with another algorithm - * - * @return bool - */ - public function needsPasswordRehash(): bool - { - return $this->_needsPasswordRehash; - } - - /** - * Authenticate a user based on the request information. - * - * @param \Cake\Http\ServerRequest $request Request to get authentication information from. - * @param \Cake\Http\Response $response A response object that can have headers added. - * @return array|false Either false on failure, or an array of user data on success. - */ - abstract public function authenticate(ServerRequest $request, Response $response); - - /** - * Get a user based on information in the request. Primarily used by stateless authentication - * systems like basic and digest auth. - * - * @param \Cake\Http\ServerRequest $request Request object. - * @return array|false Either false or an array of user information - */ - public function getUser(ServerRequest $request) - { - return false; - } - - /** - * Handle unauthenticated access attempt. In implementation valid return values - * can be: - * - * - Null - No action taken, AuthComponent should return appropriate response. - * - \Cake\Http\Response - A response object, which will cause AuthComponent to - * simply return that response. - * - * @param \Cake\Http\ServerRequest $request A request object. - * @param \Cake\Http\Response $response A response object. - * @return \Cake\Http\Response|null|void - */ - public function unauthenticated(ServerRequest $request, Response $response) - { - } - - /** - * Returns a list of all events that this authenticate class will listen to. - * - * An authenticate class can listen to following events fired by AuthComponent: - * - * - `Auth.afterIdentify` - Fired after a user has been identified using one of - * configured authenticate class. The callback function should have signature - * like `afterIdentify(EventInterface $event, array $user)` when `$user` is the - * identified user record. - * - * - `Auth.logout` - Fired when AuthComponent::logout() is called. The callback - * function should have signature like `logout(EventInterface $event, array $user)` - * where `$user` is the user about to be logged out. - * - * @return array List of events this class listens to. Defaults to `[]`. - */ - public function implementedEvents(): array - { - return []; - } -} diff --git a/app/vendor/cakephp/cakephp/src/Auth/BaseAuthorize.php b/app/vendor/cakephp/cakephp/src/Auth/BaseAuthorize.php deleted file mode 100644 index 49c50ce75..000000000 --- a/app/vendor/cakephp/cakephp/src/Auth/BaseAuthorize.php +++ /dev/null @@ -1,66 +0,0 @@ - - */ - protected $_defaultConfig = []; - - /** - * Constructor - * - * @param \Cake\Controller\ComponentRegistry $registry The controller for this request. - * @param array $config An array of config. This class does not use any config. - */ - public function __construct(ComponentRegistry $registry, array $config = []) - { - $this->_registry = $registry; - $this->setConfig($config); - } - - /** - * Checks user authorization. - * - * @param \ArrayAccess|array $user Active user data - * @param \Cake\Http\ServerRequest $request Request instance. - * @return bool - */ - abstract public function authorize($user, ServerRequest $request): bool; -} diff --git a/app/vendor/cakephp/cakephp/src/Auth/BasicAuthenticate.php b/app/vendor/cakephp/cakephp/src/Auth/BasicAuthenticate.php deleted file mode 100644 index 4e55bf1c0..000000000 --- a/app/vendor/cakephp/cakephp/src/Auth/BasicAuthenticate.php +++ /dev/null @@ -1,117 +0,0 @@ -loadComponent('Auth', [ - * 'authenticate' => ['Basic'] - * 'storage' => 'Memory', - * 'unauthorizedRedirect' => false, - * ]); - * ``` - * - * You should set `storage` to `Memory` to prevent CakePHP from sending a - * session cookie to the client. - * - * You should set `unauthorizedRedirect` to `false`. This causes `AuthComponent` to - * throw a `ForbiddenException` exception instead of redirecting to another page. - * - * Since HTTP Basic Authentication is stateless you don't need call `setUser()` - * in your controller. The user credentials will be checked on each request. If - * valid credentials are not provided, required authentication headers will be sent - * by this authentication provider which triggers the login dialog in the browser/client. - * - * @see https://book.cakephp.org/4/en/controllers/components/authentication.html - */ -class BasicAuthenticate extends BaseAuthenticate -{ - /** - * Authenticate a user using HTTP auth. Will use the configured User model and attempt a - * login using HTTP auth. - * - * @param \Cake\Http\ServerRequest $request The request to authenticate with. - * @param \Cake\Http\Response $response The response to add headers to. - * @return array|false Either false on failure, or an array of user data on success. - */ - public function authenticate(ServerRequest $request, Response $response) - { - return $this->getUser($request); - } - - /** - * Get a user based on information in the request. Used by cookie-less auth for stateless clients. - * - * @param \Cake\Http\ServerRequest $request Request object. - * @return array|false Either false or an array of user information - */ - public function getUser(ServerRequest $request) - { - $username = $request->getEnv('PHP_AUTH_USER'); - $pass = $request->getEnv('PHP_AUTH_PW'); - - if (!is_string($username) || $username === '' || !is_string($pass) || $pass === '') { - return false; - } - - return $this->_findUser($username, $pass); - } - - /** - * Handles an unauthenticated access attempt by sending appropriate login headers - * - * @param \Cake\Http\ServerRequest $request A request object. - * @param \Cake\Http\Response $response A response object. - * @return \Cake\Http\Response|null|void - * @throws \Cake\Http\Exception\UnauthorizedException - */ - public function unauthenticated(ServerRequest $request, Response $response) - { - $unauthorizedException = new UnauthorizedException(); - $unauthorizedException->setHeaders($this->loginHeaders($request)); - - throw $unauthorizedException; - } - - /** - * Generate the login headers - * - * @param \Cake\Http\ServerRequest $request Request object. - * @return array Headers for logging in. - */ - public function loginHeaders(ServerRequest $request): array - { - $realm = $this->getConfig('realm') ?: $request->getEnv('SERVER_NAME'); - - return [ - 'WWW-Authenticate' => sprintf('Basic realm="%s"', $realm), - ]; - } -} diff --git a/app/vendor/cakephp/cakephp/src/Auth/ControllerAuthorize.php b/app/vendor/cakephp/cakephp/src/Auth/ControllerAuthorize.php deleted file mode 100644 index 8cd8b8717..000000000 --- a/app/vendor/cakephp/cakephp/src/Auth/ControllerAuthorize.php +++ /dev/null @@ -1,97 +0,0 @@ -request->getParam('admin')) { - * return $user['role'] === 'admin'; - * } - * return !empty($user); - * } - * ``` - * - * The above is simple implementation that would only authorize users of the - * 'admin' role to access admin routing. - * - * @see \Cake\Controller\Component\AuthComponent::$authenticate - */ -class ControllerAuthorize extends BaseAuthorize -{ - /** - * Controller for the request. - * - * @var \Cake\Controller\Controller - */ - protected $_Controller; - - /** - * @inheritDoc - */ - public function __construct(ComponentRegistry $registry, array $config = []) - { - parent::__construct($registry, $config); - $this->controller($registry->getController()); - } - - /** - * Get/set the controller this authorize object will be working with. Also - * checks that isAuthorized is implemented. - * - * @param \Cake\Controller\Controller|null $controller null to get, a controller to set. - * @return \Cake\Controller\Controller - */ - public function controller(?Controller $controller = null): Controller - { - if ($controller) { - $this->_Controller = $controller; - } - - return $this->_Controller; - } - - /** - * Checks user authorization using a controller callback. - * - * @param \ArrayAccess|array $user Active user data - * @param \Cake\Http\ServerRequest $request Request instance. - * @throws \Cake\Core\Exception\CakeException If controller does not have method `isAuthorized()`. - * @return bool - */ - public function authorize($user, ServerRequest $request): bool - { - if (!method_exists($this->_Controller, 'isAuthorized')) { - throw new CakeException(sprintf( - '%s does not implement an isAuthorized() method.', - get_class($this->_Controller) - )); - } - - return (bool)$this->_Controller->isAuthorized($user); - } -} diff --git a/app/vendor/cakephp/cakephp/src/Auth/DefaultPasswordHasher.php b/app/vendor/cakephp/cakephp/src/Auth/DefaultPasswordHasher.php deleted file mode 100644 index 35a8246ce..000000000 --- a/app/vendor/cakephp/cakephp/src/Auth/DefaultPasswordHasher.php +++ /dev/null @@ -1,82 +0,0 @@ - - */ - protected $_defaultConfig = [ - 'hashType' => PASSWORD_DEFAULT, - 'hashOptions' => [], - ]; - - /** - * Generates password hash. - * - * @param string $password Plain text password to hash. - * @return string|false Password hash or false on failure - * @psalm-suppress InvalidNullableReturnType - * @link https://book.cakephp.org/4/en/controllers/components/authentication.html#hashing-passwords - */ - public function hash(string $password) - { - /** @psalm-suppress NullableReturnStatement */ - return password_hash( - $password, - $this->_config['hashType'], - $this->_config['hashOptions'] - ); - } - - /** - * Check hash. Generate hash for user provided password and check against existing hash. - * - * @param string $password Plain text password to hash. - * @param string $hashedPassword Existing hashed password. - * @return bool True if hashes match else false. - */ - public function check(string $password, string $hashedPassword): bool - { - return password_verify($password, $hashedPassword); - } - - /** - * Returns true if the password need to be rehashed, due to the password being - * created with anything else than the passwords generated by this class. - * - * @param string $password The password to verify - * @return bool - */ - public function needsRehash(string $password): bool - { - return password_needs_rehash($password, $this->_config['hashType'], $this->_config['hashOptions']); - } -} diff --git a/app/vendor/cakephp/cakephp/src/Auth/DigestAuthenticate.php b/app/vendor/cakephp/cakephp/src/Auth/DigestAuthenticate.php deleted file mode 100644 index 65921063c..000000000 --- a/app/vendor/cakephp/cakephp/src/Auth/DigestAuthenticate.php +++ /dev/null @@ -1,295 +0,0 @@ -loadComponent('Auth', [ - * 'authenticate' => ['Digest'], - * 'storage' => 'Memory', - * 'unauthorizedRedirect' => false, - * ]); - * ``` - * - * You should set `storage` to `Memory` to prevent CakePHP from sending a - * session cookie to the client. - * - * You should set `unauthorizedRedirect` to `false`. This causes `AuthComponent` to - * throw a `ForbiddenException` exception instead of redirecting to another page. - * - * Since HTTP Digest Authentication is stateless you don't need call `setUser()` - * in your controller. The user credentials will be checked on each request. If - * valid credentials are not provided, required authentication headers will be sent - * by this authentication provider which triggers the login dialog in the browser/client. - * - * ### Generating passwords compatible with Digest authentication. - * - * DigestAuthenticate requires a special password hash that conforms to RFC2617. - * You can generate this password using `DigestAuthenticate::password()` - * - * ``` - * $digestPass = DigestAuthenticate::password($username, $password, env('SERVER_NAME')); - * ``` - * - * If you wish to use digest authentication alongside other authentication methods, - * it's recommended that you store the digest authentication separately. For - * example `User.digest_pass` could be used for a digest password, while - * `User.password` would store the password hash for use with other methods like - * Basic or Form. - * - * @see https://book.cakephp.org/4/en/controllers/components/authentication.html - */ -class DigestAuthenticate extends BasicAuthenticate -{ - /** - * Constructor - * - * Besides the keys specified in BaseAuthenticate::$_defaultConfig, - * DigestAuthenticate uses the following extra keys: - * - * - `secret` The secret to use for nonce validation. Defaults to Security::getSalt(). - * - `realm` The realm authentication is for, Defaults to the servername. - * - `qop` Defaults to 'auth', no other values are supported at this time. - * - `opaque` A string that must be returned unchanged by clients. - * Defaults to `md5($config['realm'])` - * - `nonceLifetime` The number of seconds that nonces are valid for. Defaults to 300. - * - * @param \Cake\Controller\ComponentRegistry $registry The Component registry - * used on this request. - * @param array $config Array of config to use. - */ - public function __construct(ComponentRegistry $registry, array $config = []) - { - $this->setConfig([ - 'nonceLifetime' => 300, - 'secret' => Security::getSalt(), - 'realm' => null, - 'qop' => 'auth', - 'opaque' => null, - ]); - - parent::__construct($registry, $config); - } - - /** - * Get a user based on information in the request. Used by cookie-less auth for stateless clients. - * - * @param \Cake\Http\ServerRequest $request Request object. - * @return array|false Either false or an array of user information - */ - public function getUser(ServerRequest $request) - { - $digest = $this->_getDigest($request); - if (empty($digest)) { - return false; - } - - $user = $this->_findUser($digest['username']); - if (empty($user)) { - return false; - } - - if (!$this->validNonce($digest['nonce'])) { - return false; - } - - $field = $this->_config['fields']['password']; - $password = $user[$field]; - unset($user[$field]); - - $requestMethod = $request->getEnv('ORIGINAL_REQUEST_METHOD') ?: $request->getMethod(); - $hash = $this->generateResponseHash( - $digest, - $password, - (string)$requestMethod - ); - if (hash_equals($hash, $digest['response'])) { - return $user; - } - - return false; - } - - /** - * Gets the digest headers from the request/environment. - * - * @param \Cake\Http\ServerRequest $request Request object. - * @return array|null Array of digest information. - */ - protected function _getDigest(ServerRequest $request): ?array - { - $digest = $request->getEnv('PHP_AUTH_DIGEST'); - if (empty($digest) && function_exists('apache_request_headers')) { - $headers = apache_request_headers(); - if (!empty($headers['Authorization']) && substr($headers['Authorization'], 0, 7) === 'Digest ') { - $digest = substr($headers['Authorization'], 7); - } - } - if (empty($digest)) { - return null; - } - - return $this->parseAuthData($digest); - } - - /** - * Parse the digest authentication headers and split them up. - * - * @param string $digest The raw digest authentication headers. - * @return array|null An array of digest authentication headers - */ - public function parseAuthData(string $digest): ?array - { - if (substr($digest, 0, 7) === 'Digest ') { - $digest = substr($digest, 7); - } - $keys = $match = []; - $req = ['nonce' => 1, 'nc' => 1, 'cnonce' => 1, 'qop' => 1, 'username' => 1, 'uri' => 1, 'response' => 1]; - preg_match_all('/(\w+)=([\'"]?)([a-zA-Z0-9\:\#\%\?\&@=\.\/_-]+)\2/', $digest, $match, PREG_SET_ORDER); - - foreach ($match as $i) { - $keys[$i[1]] = $i[3]; - unset($req[$i[1]]); - } - - if (empty($req)) { - return $keys; - } - - return null; - } - - /** - * Generate the response hash for a given digest array. - * - * @param array $digest Digest information containing data from DigestAuthenticate::parseAuthData(). - * @param string $password The digest hash password generated with DigestAuthenticate::password() - * @param string $method Request method - * @return string Response hash - */ - public function generateResponseHash(array $digest, string $password, string $method): string - { - return md5( - $password . - ':' . $digest['nonce'] . ':' . $digest['nc'] . ':' . $digest['cnonce'] . ':' . $digest['qop'] . ':' . - md5($method . ':' . $digest['uri']) - ); - } - - /** - * Creates an auth digest password hash to store - * - * @param string $username The username to use in the digest hash. - * @param string $password The unhashed password to make a digest hash for. - * @param string $realm The realm the password is for. - * @return string the hashed password that can later be used with Digest authentication. - */ - public static function password(string $username, string $password, string $realm): string - { - return md5($username . ':' . $realm . ':' . $password); - } - - /** - * Generate the login headers - * - * @param \Cake\Http\ServerRequest $request Request object. - * @return array Headers for logging in. - */ - public function loginHeaders(ServerRequest $request): array - { - $realm = $this->_config['realm'] ?: $request->getEnv('SERVER_NAME'); - - $options = [ - 'realm' => $realm, - 'qop' => $this->_config['qop'], - 'nonce' => $this->generateNonce(), - 'opaque' => $this->_config['opaque'] ?: md5($realm), - ]; - - $digest = $this->_getDigest($request); - if ($digest && isset($digest['nonce']) && !$this->validNonce($digest['nonce'])) { - $options['stale'] = true; - } - - $opts = []; - foreach ($options as $k => $v) { - if (is_bool($v)) { - $v = $v ? 'true' : 'false'; - $opts[] = sprintf('%s=%s', $k, $v); - } else { - $opts[] = sprintf('%s="%s"', $k, $v); - } - } - - return [ - 'WWW-Authenticate' => 'Digest ' . implode(',', $opts), - ]; - } - - /** - * Generate a nonce value that is validated in future requests. - * - * @return string - */ - protected function generateNonce(): string - { - $expiryTime = microtime(true) + $this->getConfig('nonceLifetime'); - $secret = $this->getConfig('secret'); - $signatureValue = hash_hmac('sha256', $expiryTime . ':' . $secret, $secret); - $nonceValue = $expiryTime . ':' . $signatureValue; - - return base64_encode($nonceValue); - } - - /** - * Check the nonce to ensure it is valid and not expired. - * - * @param string $nonce The nonce value to check. - * @return bool - */ - protected function validNonce(string $nonce): bool - { - $value = base64_decode($nonce); - if ($value === false) { - return false; - } - $parts = explode(':', $value); - if (count($parts) !== 2) { - return false; - } - [$expires, $checksum] = $parts; - if ($expires < microtime(true)) { - return false; - } - $secret = $this->getConfig('secret'); - $check = hash_hmac('sha256', $expires . ':' . $secret, $secret); - - return hash_equals($check, $checksum); - } -} diff --git a/app/vendor/cakephp/cakephp/src/Auth/FallbackPasswordHasher.php b/app/vendor/cakephp/cakephp/src/Auth/FallbackPasswordHasher.php deleted file mode 100644 index 7c4de0518..000000000 --- a/app/vendor/cakephp/cakephp/src/Auth/FallbackPasswordHasher.php +++ /dev/null @@ -1,105 +0,0 @@ - - */ - protected $_defaultConfig = [ - 'hashers' => [], - ]; - - /** - * Holds the list of password hasher objects that will be used - * - * @var array<\Cake\Auth\AbstractPasswordHasher> - */ - protected $_hashers = []; - - /** - * Constructor - * - * @param array $config configuration options for this object. Requires the - * `hashers` key to be present in the array with a list of other hashers to be - * used. - */ - public function __construct(array $config = []) - { - parent::__construct($config); - foreach ($this->_config['hashers'] as $key => $hasher) { - if (is_array($hasher) && !isset($hasher['className'])) { - $hasher['className'] = $key; - } - $this->_hashers[] = PasswordHasherFactory::build($hasher); - } - } - - /** - * Generates password hash. - * - * Uses the first password hasher in the list to generate the hash - * - * @param string $password Plain text password to hash. - * @return string|false Password hash or false - */ - public function hash(string $password) - { - return $this->_hashers[0]->hash($password); - } - - /** - * Verifies that the provided password corresponds to its hashed version - * - * This will iterate over all configured hashers until one of them returns - * true. - * - * @param string $password Plain text password to hash. - * @param string $hashedPassword Existing hashed password. - * @return bool True if hashes match else false. - */ - public function check(string $password, string $hashedPassword): bool - { - foreach ($this->_hashers as $hasher) { - if ($hasher->check($password, $hashedPassword)) { - return true; - } - } - - return false; - } - - /** - * Returns true if the password need to be rehashed, with the first hasher present - * in the list of hashers - * - * @param string $password The password to verify - * @return bool - */ - public function needsRehash(string $password): bool - { - return $this->_hashers[0]->needsRehash($password); - } -} diff --git a/app/vendor/cakephp/cakephp/src/Auth/FormAuthenticate.php b/app/vendor/cakephp/cakephp/src/Auth/FormAuthenticate.php deleted file mode 100644 index 953bbcaab..000000000 --- a/app/vendor/cakephp/cakephp/src/Auth/FormAuthenticate.php +++ /dev/null @@ -1,90 +0,0 @@ -loadComponent('Auth', [ - * 'authenticate' => [ - * 'Form' => [ - * 'fields' => ['username' => 'email', 'password' => 'passwd'], - * 'finder' => 'auth', - * ] - * ] - * ]); - * ``` - * - * When configuring FormAuthenticate you can pass in config to which fields, model and finder - * are used. See `BaseAuthenticate::$_defaultConfig` for more information. - * - * @see https://book.cakephp.org/4/en/controllers/components/authentication.html - */ -class FormAuthenticate extends BaseAuthenticate -{ - /** - * Checks the fields to ensure they are supplied. - * - * @param \Cake\Http\ServerRequest $request The request that contains login information. - * @param array $fields The fields to be checked. - * @return bool False if the fields have not been supplied. True if they exist. - */ - protected function _checkFields(ServerRequest $request, array $fields): bool - { - foreach ([$fields['username'], $fields['password']] as $field) { - $value = $request->getData($field); - if (empty($value) || !is_string($value)) { - return false; - } - } - - return true; - } - - /** - * Authenticates the identity contained in a request. Will use the `config.userModel`, and `config.fields` - * to find POST data that is used to find a matching record in the `config.userModel`. Will return false if - * there is no post data, either username or password is missing, or if the scope conditions have not been met. - * - * @param \Cake\Http\ServerRequest $request The request that contains login information. - * @param \Cake\Http\Response $response Unused response object. - * @return array|false False on login failure. An array of User data on success. - */ - public function authenticate(ServerRequest $request, Response $response) - { - $fields = $this->_config['fields']; - if (!$this->_checkFields($request, $fields)) { - return false; - } - - return $this->_findUser( - $request->getData($fields['username']), - $request->getData($fields['password']) - ); - } -} diff --git a/app/vendor/cakephp/cakephp/src/Auth/PasswordHasherFactory.php b/app/vendor/cakephp/cakephp/src/Auth/PasswordHasherFactory.php deleted file mode 100644 index 06bebc0e1..000000000 --- a/app/vendor/cakephp/cakephp/src/Auth/PasswordHasherFactory.php +++ /dev/null @@ -1,59 +0,0 @@ -|string $passwordHasher Name of the password hasher or an array with - * at least the key `className` set to the name of the class to use - * @return \Cake\Auth\AbstractPasswordHasher Password hasher instance - * @throws \RuntimeException If password hasher class not found or - * it does not extend {@link \Cake\Auth\AbstractPasswordHasher} - */ - public static function build($passwordHasher): AbstractPasswordHasher - { - $config = []; - if (is_string($passwordHasher)) { - $class = $passwordHasher; - } else { - $class = $passwordHasher['className']; - $config = $passwordHasher; - unset($config['className']); - } - - $className = App::className($class, 'Auth', 'PasswordHasher'); - if ($className === null) { - throw new RuntimeException(sprintf('Password hasher class "%s" was not found.', $class)); - } - - $hasher = new $className($config); - if (!($hasher instanceof AbstractPasswordHasher)) { - throw new RuntimeException('Password hasher must extend AbstractPasswordHasher class.'); - } - - return $hasher; - } -} diff --git a/app/vendor/cakephp/cakephp/src/Auth/Storage/MemoryStorage.php b/app/vendor/cakephp/cakephp/src/Auth/Storage/MemoryStorage.php deleted file mode 100644 index 0be0d1449..000000000 --- a/app/vendor/cakephp/cakephp/src/Auth/Storage/MemoryStorage.php +++ /dev/null @@ -1,81 +0,0 @@ -_user; - } - - /** - * @inheritDoc - */ - public function write($user): void - { - $this->_user = $user; - } - - /** - * @inheritDoc - */ - public function delete(): void - { - $this->_user = null; - } - - /** - * @inheritDoc - */ - public function redirectUrl($url = null) - { - if ($url === null) { - return $this->_redirectUrl; - } - - if ($url === false) { - $this->_redirectUrl = null; - - return null; - } - - $this->_redirectUrl = $url; - - return null; - } -} diff --git a/app/vendor/cakephp/cakephp/src/Auth/Storage/SessionStorage.php b/app/vendor/cakephp/cakephp/src/Auth/Storage/SessionStorage.php deleted file mode 100644 index bfb6463ff..000000000 --- a/app/vendor/cakephp/cakephp/src/Auth/Storage/SessionStorage.php +++ /dev/null @@ -1,144 +0,0 @@ - - */ - protected $_defaultConfig = [ - 'key' => 'Auth.User', - 'redirect' => 'Auth.redirect', - ]; - - /** - * Constructor. - * - * @param \Cake\Http\ServerRequest $request Request instance. - * @param \Cake\Http\Response $response Response instance. - * @param array $config Configuration list. - */ - public function __construct(ServerRequest $request, Response $response, array $config = []) - { - $this->_session = $request->getSession(); - $this->setConfig($config); - } - - /** - * Read user record from session. - * - * @return \ArrayAccess|array|null User record if available else null. - * @psalm-suppress InvalidReturnType - */ - public function read() - { - if ($this->_user !== null) { - return $this->_user ?: null; - } - - /** @psalm-suppress PossiblyInvalidPropertyAssignmentValue */ - $this->_user = $this->_session->read($this->_config['key']) ?: false; - - /** @psalm-suppress InvalidReturnStatement */ - return $this->_user ?: null; - } - - /** - * Write user record to session. - * - * The session id is also renewed to help mitigate issues with session replays. - * - * @param \ArrayAccess|array $user User record. - * @return void - */ - public function write($user): void - { - $this->_user = $user; - - $this->_session->renew(); - $this->_session->write($this->_config['key'], $user); - } - - /** - * Delete user record from session. - * - * The session id is also renewed to help mitigate issues with session replays. - * - * @return void - */ - public function delete(): void - { - $this->_user = false; - - $this->_session->delete($this->_config['key']); - $this->_session->renew(); - } - - /** - * @inheritDoc - */ - public function redirectUrl($url = null) - { - if ($url === null) { - return $this->_session->read($this->_config['redirect']); - } - - if ($url === false) { - $this->_session->delete($this->_config['redirect']); - - return null; - } - - $this->_session->write($this->_config['redirect'], $url); - - return null; - } -} diff --git a/app/vendor/cakephp/cakephp/src/Auth/Storage/StorageInterface.php b/app/vendor/cakephp/cakephp/src/Auth/Storage/StorageInterface.php deleted file mode 100644 index 6e8aadd4a..000000000 --- a/app/vendor/cakephp/cakephp/src/Auth/Storage/StorageInterface.php +++ /dev/null @@ -1,57 +0,0 @@ - - */ - protected $_defaultConfig = [ - 'hashType' => null, - ]; - - /** - * @inheritDoc - */ - public function __construct(array $config = []) - { - if (Configure::read('debug')) { - Debugger::checkSecurityKeys(); - } - - parent::__construct($config); - } - - /** - * @inheritDoc - */ - public function hash(string $password) - { - return Security::hash($password, $this->_config['hashType'], true); - } - - /** - * Check hash. Generate hash for user provided password and check against existing hash. - * - * @param string $password Plain text password to hash. - * @param string $hashedPassword Existing hashed password. - * @return bool True if hashes match else false. - */ - public function check(string $password, string $hashedPassword): bool - { - return $hashedPassword === $this->hash($password); - } -} diff --git a/app/vendor/cakephp/cakephp/src/Cache/Cache.php b/app/vendor/cakephp/cakephp/src/Cache/Cache.php index b91032291..dab7c9be2 100644 --- a/app/vendor/cakephp/cakephp/src/Cache/Cache.php +++ b/app/vendor/cakephp/cakephp/src/Cache/Cache.php @@ -20,8 +20,9 @@ use Cake\Cache\Exception\CacheWriteException; use Cake\Cache\Exception\InvalidArgumentException; use Cake\Core\StaticConfigTrait; +use Closure; +use Psr\SimpleCache\CacheInterface; use RuntimeException; -use function Cake\Core\deprecationWarning; /** * Cache provides a consistent interface to Caching in your application. It allows you @@ -58,8 +59,6 @@ * - `MemcacheEngine` - Uses the PECL::Memcache extension and Memcached for storage. * Fast reads/writes, and benefits from memcache being distributed. * - `RedisEngine` - Uses redis and php-redis extension to store cache data. - * - `WincacheEngine` - Uses Windows Cache Extension for PHP. Supports wincache 1.1.0 and higher. - * This engine is recommended to people deploying on windows with IIS. * - `XcacheEngine` - Uses the Xcache extension, an alternative to APCu. * * See Cache engine documentation for expected configuration keys. @@ -75,16 +74,15 @@ class Cache * class names. * * @var array - * @psalm-var array + * @phpstan-var array */ - protected static $_dsnClassMap = [ + protected static array $_dsnClassMap = [ 'array' => Engine\ArrayEngine::class, 'apcu' => Engine\ApcuEngine::class, 'file' => Engine\FileEngine::class, 'memcached' => Engine\MemcachedEngine::class, 'null' => Engine\NullEngine::class, 'redis' => Engine\RedisEngine::class, - 'wincache' => Engine\WincacheEngine::class, ]; /** @@ -92,21 +90,21 @@ class Cache * * @var bool */ - protected static $_enabled = true; + protected static bool $_enabled = true; /** * Group to Config mapping * * @var array */ - protected static $_groups = []; + protected static array $_groups = []; /** * Cache Registry used for creating and using cache adapters. * - * @var \Cake\Cache\CacheRegistry|null + * @var \Cake\Cache\CacheRegistry */ - protected static $_registry; + protected static CacheRegistry $_registry; /** * Returns the Cache Registry instance used for creating and using cache adapters. @@ -115,11 +113,7 @@ class Cache */ public static function getRegistry(): CacheRegistry { - if (static::$_registry === null) { - static::$_registry = new CacheRegistry(); - } - - return static::$_registry; + return static::$_registry ??= new CacheRegistry(); } /** @@ -149,11 +143,10 @@ protected static function _buildEngine(string $name): void if (empty(static::$_config[$name]['className'])) { throw new InvalidArgumentException( - sprintf('The "%s" cache configuration does not exist.', $name) + sprintf('The `%s` cache configuration does not exist.', $name), ); } - /** @var array $config */ $config = static::$_config[$name]; try { @@ -172,13 +165,14 @@ protected static function _buildEngine(string $name): void if ($config['fallback'] === $name) { throw new InvalidArgumentException(sprintf( - '"%s" cache configuration cannot fallback to itself.', - $name + '`%s` cache configuration cannot fallback to itself.', + $name, ), 0, $e); } - /** @var \Cake\Cache\CacheEngine $fallbackEngine */ $fallbackEngine = clone static::pool($config['fallback']); + assert($fallbackEngine instanceof CacheEngine); + $newConfig = $config + ['groups' => [], 'prefix' => null]; $fallbackEngine->setConfig('groups', $newConfig['groups'], false); if ($newConfig['prefix']) { @@ -192,6 +186,7 @@ protected static function _buildEngine(string $name): void } if (!empty($config['groups'])) { + /** @var string $group */ foreach ($config['groups'] as $group) { static::$_groups[$group][] = $name; static::$_groups[$group] = array_unique(static::$_groups[$group]); @@ -200,27 +195,13 @@ protected static function _buildEngine(string $name): void } } - /** - * Get a cache engine object for the named cache config. - * - * @param string $config The name of the configured cache backend. - * @return \Psr\SimpleCache\CacheInterface&\Cake\Cache\CacheEngineInterface - * @deprecated 3.7.0 Use {@link pool()} instead. This method will be removed in 5.0. - */ - public static function engine(string $config) - { - deprecationWarning('Cache::engine() is deprecated. Use Cache::pool() instead.'); - - return static::pool($config); - } - /** * Get a SimpleCacheEngine object for the named cache pool. * * @param string $config The name of the configured cache backend. * @return \Psr\SimpleCache\CacheInterface&\Cake\Cache\CacheEngineInterface */ - public static function pool(string $config) + public static function pool(string $config): CacheInterface&CacheEngineInterface { if (!static::$_enabled) { return new NullEngine(); @@ -259,7 +240,7 @@ public static function pool(string $config) * @param string $config Optional string configuration name to write to. Defaults to 'default' * @return bool True if the data was successfully cached, false on failure */ - public static function write(string $key, $value, string $config = 'default'): bool + public static function write(string $key, mixed $value, string $config = 'default'): bool { if (is_resource($value)) { return false; @@ -272,7 +253,7 @@ public static function write(string $key, $value, string $config = 'default'): b "%s cache was unable to write '%s' to %s cache", $config, $key, - get_class($backend) + $backend::class, )); } @@ -328,7 +309,7 @@ public static function writeMany(iterable $data, string $config = 'default'): bo * @return mixed The cached data, or null if the data doesn't exist, has expired, * or if there was an error fetching it. */ - public static function read(string $key, string $config = 'default') + public static function read(string $key, string $config = 'default'): mixed { return static::pool($config)->get($key); } @@ -371,10 +352,10 @@ public static function readMany(iterable $keys, string $config = 'default'): ite * or if there was an error fetching it. * @throws \Cake\Cache\Exception\InvalidArgumentException When offset < 0 */ - public static function increment(string $key, int $offset = 1, string $config = 'default') + public static function increment(string $key, int $offset = 1, string $config = 'default'): int|false { if ($offset < 0) { - throw new InvalidArgumentException('Offset cannot be less than 0.'); + throw new InvalidArgumentException('Offset cannot be less than `0`.'); } return static::pool($config)->increment($key, $offset); @@ -390,10 +371,10 @@ public static function increment(string $key, int $offset = 1, string $config = * or if there was an error fetching it * @throws \Cake\Cache\Exception\InvalidArgumentException when offset < 0 */ - public static function decrement(string $key, int $offset = 1, string $config = 'default') + public static function decrement(string $key, int $offset = 1, string $config = 'default'): int|false { if ($offset < 0) { - throw new InvalidArgumentException('Offset cannot be less than 0.'); + throw new InvalidArgumentException('Offset cannot be less than `0`.'); } return static::pool($config)->decrement($key, $offset); @@ -520,7 +501,7 @@ public static function groupConfigs(?string $group = null): array return [$group => self::$_groups[$group]]; } - throw new InvalidArgumentException(sprintf('Invalid cache group %s', $group)); + throw new InvalidArgumentException(sprintf('Invalid cache group `%s`.', $group)); } /** @@ -560,8 +541,8 @@ public static function enabled(): bool /** * Provides the ability to easily do read-through caching. * - * When called if the $key is not set in $config, the $callable function - * will be invoked. The results will then be stored into the cache config + * If the key is not set, the default callback is run to get the default value. + * The results will then be stored into the cache config * at key. * * Examples: @@ -575,20 +556,20 @@ public static function enabled(): bool * ``` * * @param string $key The cache key to read/store data at. - * @param callable $callable The callable that provides data in the case when - * the cache key is empty. Can be any callable type supported by your PHP. + * @param \Closure $default The callback that provides data in the case when + * the cache key is empty. * @param string $config The cache configuration to use for this operation. * Defaults to default. * @return mixed If the key is found: the cached data. - * If the key is not found the value returned by the callable. + * If the key is not found the value returned by the the default callback. */ - public static function remember(string $key, callable $callable, string $config = 'default') + public static function remember(string $key, Closure $default, string $config = 'default'): mixed { $existing = self::read($key, $config); if ($existing !== null) { return $existing; } - $results = $callable(); + $results = $default(); self::write($key, $results, $config); return $results; @@ -617,7 +598,7 @@ public static function remember(string $key, callable $callable, string $config * @return bool True if the data was successfully cached, false on failure. * Or if the key existed already. */ - public static function add(string $key, $value, string $config = 'default'): bool + public static function add(string $key, mixed $value, string $config = 'default'): bool { if (is_resource($value)) { return false; diff --git a/app/vendor/cakephp/cakephp/src/Cache/CacheEngine.php b/app/vendor/cakephp/cakephp/src/Cache/CacheEngine.php index b37fc672f..22a180fc0 100644 --- a/app/vendor/cakephp/cakephp/src/Cache/CacheEngine.php +++ b/app/vendor/cakephp/cakephp/src/Cache/CacheEngine.php @@ -54,7 +54,7 @@ abstract class CacheEngine implements CacheInterface, CacheEngineInterface * * @var array */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'duration' => 3600, 'groups' => [], 'prefix' => 'cake_', @@ -67,7 +67,7 @@ abstract class CacheEngine implements CacheInterface, CacheEngineInterface * * @var string */ - protected $_groupPrefix = ''; + protected string $_groupPrefix = ''; /** * Initialize the cache engine @@ -96,13 +96,13 @@ public function init(array $config = []): bool /** * Ensure the validity of the given cache key. * - * @param string $key Key to check. + * @param mixed $key Key to check. * @return void * @throws \Cake\Cache\Exception\InvalidArgumentException When the key is not valid. */ - protected function ensureValidKey($key): void + protected function ensureValidKey(mixed $key): void { - if (!is_string($key) || strlen($key) === 0) { + if (!is_string($key) || $key === '') { throw new InvalidArgumentException('A cache key must be a non-empty string.'); } } @@ -115,15 +115,8 @@ protected function ensureValidKey($key): void * @return void * @throws \Cake\Cache\Exception\InvalidArgumentException */ - protected function ensureValidType($iterable, string $check = self::CHECK_VALUE): void + protected function ensureValidType(iterable $iterable, string $check = self::CHECK_VALUE): void { - if (!is_iterable($iterable)) { - throw new InvalidArgumentException(sprintf( - 'A cache %s must be either an array or a Traversable.', - $check === self::CHECK_VALUE ? 'key set' : 'set' - )); - } - foreach ($iterable as $key => $value) { if ($check === self::CHECK_VALUE) { $this->ensureValidKey($value); @@ -136,13 +129,13 @@ protected function ensureValidType($iterable, string $check = self::CHECK_VALUE) /** * Obtains multiple cache items by their unique keys. * - * @param iterable $keys A list of keys that can obtained in a single operation. + * @param iterable $keys A list of keys that can obtained in a single operation. * @param mixed $default Default value to return for keys that do not exist. - * @return iterable A list of key value pairs. Cache keys that do not exist or are stale will have $default as value. + * @return iterable A list of key value pairs. Cache keys that do not exist or are stale will have $default as value. * @throws \Cake\Cache\Exception\InvalidArgumentException If $keys is neither an array nor a Traversable, * or if any of the $keys are not a legal value. */ - public function getMultiple($keys, $default = null): iterable + public function getMultiple(iterable $keys, mixed $default = null): iterable { $this->ensureValidType($keys); @@ -165,10 +158,11 @@ public function getMultiple($keys, $default = null): iterable * @throws \Cake\Cache\Exception\InvalidArgumentException If $values is neither an array nor a Traversable, * or if any of the $values are not a legal value. */ - public function setMultiple($values, $ttl = null): bool + public function setMultiple(iterable $values, DateInterval|int|null $ttl = null): bool { $this->ensureValidType($values, self::CHECK_KEY); + $restore = null; if ($ttl !== null) { $restore = $this->getConfig('duration'); $this->setConfig('duration', $ttl); @@ -183,7 +177,7 @@ public function setMultiple($values, $ttl = null): bool return true; } finally { - if (isset($restore)) { + if ($restore !== null) { $this->setConfig('duration', $restore); } } @@ -201,7 +195,7 @@ public function setMultiple($values, $ttl = null): bool * @throws \Cake\Cache\Exception\InvalidArgumentException If $keys is neither an array nor a Traversable, * or if any of the $keys are not a legal value. */ - public function deleteMultiple($keys): bool + public function deleteMultiple(iterable $keys): bool { $this->ensureValidType($keys); @@ -227,7 +221,7 @@ public function deleteMultiple($keys): bool * @return bool * @throws \Cake\Cache\Exception\InvalidArgumentException If the $key string is not a legal value. */ - public function has($key): bool + public function has(string $key): bool { return $this->get($key) !== null; } @@ -240,7 +234,7 @@ public function has($key): bool * @return mixed The value of the item from the cache, or $default in case of cache miss. * @throws \Cake\Cache\Exception\InvalidArgumentException If the $key string is not a legal value. */ - abstract public function get($key, $default = null); + abstract public function get(string $key, mixed $default = null): mixed; /** * Persists data in the cache, uniquely referenced by the given key with an optional expiration TTL time. @@ -254,7 +248,7 @@ abstract public function get($key, $default = null); * @throws \Cake\Cache\Exception\InvalidArgumentException * MUST be thrown if the $key string is not a legal value. */ - abstract public function set($key, $value, $ttl = null): bool; + abstract public function set(string $key, mixed $value, DateInterval|int|null $ttl = null): bool; /** * Increment a number under the key and return incremented value @@ -263,7 +257,7 @@ abstract public function set($key, $value, $ttl = null): bool; * @param int $offset How much to add * @return int|false New incremented value, false otherwise */ - abstract public function increment(string $key, int $offset = 1); + abstract public function increment(string $key, int $offset = 1): int|false; /** * Decrement a number under the key and return decremented value @@ -272,7 +266,7 @@ abstract public function increment(string $key, int $offset = 1); * @param int $offset How much to subtract * @return int|false New incremented value, false otherwise */ - abstract public function decrement(string $key, int $offset = 1); + abstract public function decrement(string $key, int $offset = 1): int|false; /** * Delete a key from the cache @@ -280,7 +274,7 @@ abstract public function decrement(string $key, int $offset = 1); * @param string $key Identifier for the data * @return bool True if the value was successfully deleted, false if it didn't exist or couldn't be removed */ - abstract public function delete($key): bool; + abstract public function delete(string $key): bool; /** * Delete all keys from the cache @@ -299,7 +293,7 @@ abstract public function clear(): bool; * @param mixed $value Data to be cached. * @return bool True if the data was successfully cached, false on failure. */ - public function add(string $key, $value): bool + public function add(string $key, mixed $value): bool { $cachedValue = $this->get($key); if ($cachedValue === null) { @@ -341,13 +335,13 @@ public function groups(): array * @return string Prefixed key with potentially unsafe characters replaced. * @throws \Cake\Cache\Exception\InvalidArgumentException If key's value is invalid. */ - protected function _key($key): string + protected function _key(string $key): string { $this->ensureValidKey($key); $prefix = ''; if ($this->_groupPrefix) { - $prefix = md5(implode('_', $this->groups())); + $prefix = hash('xxh128', implode('_', $this->groups())); } $key = preg_replace('/[\s]+/', '_', $key); @@ -377,7 +371,7 @@ protected function warning(string $message): void * driver's default duration will be used. * @return int */ - protected function duration($ttl): int + protected function duration(DateInterval|int|null $ttl): int { if ($ttl === null) { return $this->_config['duration']; @@ -385,12 +379,12 @@ protected function duration($ttl): int if (is_int($ttl)) { return $ttl; } - if ($ttl instanceof DateInterval) { - return (int)DateTime::createFromFormat('U', '0') - ->add($ttl) - ->format('U'); - } - throw new InvalidArgumentException('TTL values must be one of null, int, \DateInterval'); + /** @var \DateTime $datetime */ + $datetime = DateTime::createFromFormat('U', '0'); + + return (int)$datetime + ->add($ttl) + ->format('U'); } } diff --git a/app/vendor/cakephp/cakephp/src/Cache/CacheEngineInterface.php b/app/vendor/cakephp/cakephp/src/Cache/CacheEngineInterface.php index d7eefe39f..d6e190646 100644 --- a/app/vendor/cakephp/cakephp/src/Cache/CacheEngineInterface.php +++ b/app/vendor/cakephp/cakephp/src/Cache/CacheEngineInterface.php @@ -35,7 +35,7 @@ interface CacheEngineInterface * @return bool True if the data was successfully cached, false on failure. * Or if the key existed already. */ - public function add(string $key, $value): bool; + public function add(string $key, mixed $value): bool; /** * Increment a number under the key and return incremented value @@ -44,7 +44,7 @@ public function add(string $key, $value): bool; * @param int $offset How much to add * @return int|false New incremented value, false otherwise */ - public function increment(string $key, int $offset = 1); + public function increment(string $key, int $offset = 1): int|false; /** * Decrement a number under the key and return decremented value @@ -53,7 +53,7 @@ public function increment(string $key, int $offset = 1); * @param int $offset How much to subtract * @return int|false New incremented value, false otherwise */ - public function decrement(string $key, int $offset = 1); + public function decrement(string $key, int $offset = 1): int|false; /** * Clear all values belonging to the named group. diff --git a/app/vendor/cakephp/cakephp/src/Cache/CacheRegistry.php b/app/vendor/cakephp/cakephp/src/Cache/CacheRegistry.php index 1c5aa4660..5f7f78492 100644 --- a/app/vendor/cakephp/cakephp/src/Cache/CacheRegistry.php +++ b/app/vendor/cakephp/cakephp/src/Cache/CacheRegistry.php @@ -18,8 +18,8 @@ use BadMethodCallException; use Cake\Core\App; +use Cake\Core\Exception\CakeException; use Cake\Core\ObjectRegistry; -use RuntimeException; /** * An object registry for cache engines. @@ -36,11 +36,11 @@ class CacheRegistry extends ObjectRegistry * Part of the template method for Cake\Core\ObjectRegistry::load() * * @param string $class Partial classname to resolve. - * @return string|null Either the correct classname or null. - * @psalm-return class-string|null + * @return class-string<\Cake\Cache\CacheEngine>|null Either the correct classname or null. */ protected function _resolveClassName(string $class): ?string { + /** @var class-string<\Cake\Cache\CacheEngine>|null */ return App::className($class, 'Cache/Engine', 'Engine'); } @@ -56,7 +56,7 @@ protected function _resolveClassName(string $class): ?string */ protected function _throwMissingClassError(string $class, ?string $plugin): void { - throw new BadMethodCallException(sprintf('Cache engine %s is not available.', $class)); + throw new BadMethodCallException(sprintf('Cache engine `%s` is not available.', $class)); } /** @@ -64,13 +64,13 @@ protected function _throwMissingClassError(string $class, ?string $plugin): void * * Part of the template method for Cake\Core\ObjectRegistry::load() * - * @param \Cake\Cache\CacheEngine|string $class The classname or object to make. + * @param \Cake\Cache\CacheEngine|class-string<\Cake\Cache\CacheEngine> $class The classname or object to make. * @param string $alias The alias of the object. * @param array $config An array of settings to use for the cache engine. * @return \Cake\Cache\CacheEngine The constructed CacheEngine class. - * @throws \RuntimeException when an object doesn't implement the correct interface. + * @throws \Cake\Core\Exception\CakeException When the cache engine cannot be initialized. */ - protected function _create($class, string $alias, array $config): CacheEngine + protected function _create(object|string $class, string $alias, array $config): CacheEngine { if (is_object($class)) { $instance = $class; @@ -79,18 +79,14 @@ protected function _create($class, string $alias, array $config): CacheEngine } unset($config['className']); - if (!($instance instanceof CacheEngine)) { - throw new RuntimeException( - 'Cache engines must use Cake\Cache\CacheEngine as a base class.' - ); - } + assert($instance instanceof CacheEngine, 'Cache engines must extend `' . CacheEngine::class . '`.'); if (!$instance->init($config)) { - throw new RuntimeException( + throw new CakeException( sprintf( - 'Cache engine %s is not properly configured. Check error log for additional information.', - get_class($instance) - ) + 'Cache engine `%s` is not properly configured. Check error log for additional information.', + $instance::class, + ), ); } diff --git a/app/vendor/cakephp/cakephp/src/Cache/Engine/ApcuEngine.php b/app/vendor/cakephp/cakephp/src/Cache/Engine/ApcuEngine.php index bc90e8f0f..43d580c68 100644 --- a/app/vendor/cakephp/cakephp/src/Cache/Engine/ApcuEngine.php +++ b/app/vendor/cakephp/cakephp/src/Cache/Engine/ApcuEngine.php @@ -18,7 +18,8 @@ use APCUIterator; use Cake\Cache\CacheEngine; -use RuntimeException; +use Cake\Core\Exception\CakeException; +use DateInterval; /** * APCu storage engine for cache @@ -31,7 +32,7 @@ class ApcuEngine extends CacheEngine * * @var array */ - protected $_compiledGroupNames = []; + protected array $_compiledGroupNames = []; /** * Initialize the Cache Engine @@ -44,7 +45,7 @@ class ApcuEngine extends CacheEngine public function init(array $config = []): bool { if (!extension_loaded('apcu')) { - throw new RuntimeException('The `apcu` extension must be enabled to use ApcuEngine.'); + throw new CakeException('The `apcu` extension must be enabled to use ApcuEngine.'); } return parent::init($config); @@ -61,7 +62,7 @@ public function init(array $config = []): bool * @return bool True on success and false on failure. * @link https://secure.php.net/manual/en/function.apcu-store.php */ - public function set($key, $value, $ttl = null): bool + public function set(string $key, mixed $value, DateInterval|int|null $ttl = null): bool { $key = $this->_key($key); $duration = $this->duration($ttl); @@ -78,7 +79,7 @@ public function set($key, $value, $ttl = null): bool * has expired, or if there was an error fetching it * @link https://secure.php.net/manual/en/function.apcu-fetch.php */ - public function get($key, $default = null) + public function get(string $key, mixed $default = null): mixed { $value = apcu_fetch($this->_key($key), $success); if ($success === false) { @@ -96,7 +97,7 @@ public function get($key, $default = null) * @return int|false New incremented value, false otherwise * @link https://secure.php.net/manual/en/function.apcu-inc.php */ - public function increment(string $key, int $offset = 1) + public function increment(string $key, int $offset = 1): int|false { $key = $this->_key($key); @@ -111,7 +112,7 @@ public function increment(string $key, int $offset = 1) * @return int|false New decremented value, false otherwise * @link https://secure.php.net/manual/en/function.apcu-dec.php */ - public function decrement(string $key, int $offset = 1) + public function decrement(string $key, int $offset = 1): int|false { $key = $this->_key($key); @@ -125,7 +126,7 @@ public function decrement(string $key, int $offset = 1) * @return bool True if the value was successfully deleted, false if it didn't exist or couldn't be removed * @link https://secure.php.net/manual/en/function.apcu-delete.php */ - public function delete($key): bool + public function delete(string $key): bool { $key = $this->_key($key); @@ -144,7 +145,7 @@ public function clear(): bool if (class_exists(APCUIterator::class, false)) { $iterator = new APCUIterator( '/^' . preg_quote($this->_config['prefix'], '/') . '/', - APC_ITER_NONE + APC_ITER_NONE, ); apcu_delete($iterator); @@ -153,7 +154,7 @@ public function clear(): bool $cache = apcu_cache_info(); // Raises warning by itself already foreach ($cache['cache_list'] as $key) { - if (strpos($key['info'], $this->_config['prefix']) === 0) { + if (str_starts_with($key['info'], $this->_config['prefix'])) { apcu_delete($key['info']); } } @@ -170,7 +171,7 @@ public function clear(): bool * @return bool True if the data was successfully cached, false on failure. * @link https://secure.php.net/manual/en/function.apcu-add.php */ - public function add(string $key, $value): bool + public function add(string $key, mixed $value): bool { $key = $this->_key($key); $duration = $this->_config['duration']; @@ -189,7 +190,7 @@ public function add(string $key, $value): bool */ public function groups(): array { - if (empty($this->_compiledGroupNames)) { + if (!$this->_compiledGroupNames) { foreach ($this->_config['groups'] as $group) { $this->_compiledGroupNames[] = $this->_config['prefix'] . $group; } @@ -203,7 +204,7 @@ public function groups(): array $value = 1; if (apcu_store($group, $value) === false) { $this->warning( - sprintf('Failed to store key "%s" with value "%s" into APCu cache.', $group, $value) + sprintf('Failed to store key `%s` with value `%s` into APCu cache.', $group, $value), ); } $groups[$group] = $value; diff --git a/app/vendor/cakephp/cakephp/src/Cache/Engine/ArrayEngine.php b/app/vendor/cakephp/cakephp/src/Cache/Engine/ArrayEngine.php index 4693c0242..6b1f2500c 100644 --- a/app/vendor/cakephp/cakephp/src/Cache/Engine/ArrayEngine.php +++ b/app/vendor/cakephp/cakephp/src/Cache/Engine/ArrayEngine.php @@ -17,6 +17,7 @@ namespace Cake\Cache\Engine; use Cake\Cache\CacheEngine; +use DateInterval; /** * Array storage engine for cache. @@ -37,7 +38,7 @@ class ArrayEngine extends CacheEngine * * @var array */ - protected $data = []; + protected array $data = []; /** * Write data for key into cache @@ -49,7 +50,7 @@ class ArrayEngine extends CacheEngine * for it or let the driver take care of that. * @return bool True on success and false on failure. */ - public function set($key, $value, $ttl = null): bool + public function set(string $key, mixed $value, DateInterval|int|null $ttl = null): bool { $key = $this->_key($key); $expires = time() + $this->duration($ttl); @@ -66,7 +67,7 @@ public function set($key, $value, $ttl = null): bool * @return mixed The cached data, or default value if the data doesn't exist, has * expired, or if there was an error fetching it. */ - public function get($key, $default = null) + public function get(string $key, mixed $default = null): mixed { $key = $this->_key($key); if (!isset($this->data[$key])) { @@ -92,7 +93,7 @@ public function get($key, $default = null) * @param int $offset How much to increment * @return int|false New incremented value, false otherwise */ - public function increment(string $key, int $offset = 1) + public function increment(string $key, int $offset = 1): int|false { if ($this->get($key) === null) { $this->set($key, 0); @@ -110,7 +111,7 @@ public function increment(string $key, int $offset = 1) * @param int $offset How much to subtract * @return int|false New decremented value, false otherwise */ - public function decrement(string $key, int $offset = 1) + public function decrement(string $key, int $offset = 1): int|false { if ($this->get($key) === null) { $this->set($key, 0); @@ -127,7 +128,7 @@ public function decrement(string $key, int $offset = 1) * @param string $key Identifier for the data * @return bool True if the value was successfully deleted, false if it didn't exist or couldn't be removed */ - public function delete($key): bool + public function delete(string $key): bool { $key = $this->_key($key); unset($this->data[$key]); diff --git a/app/vendor/cakephp/cakephp/src/Cache/Engine/FileEngine.php b/app/vendor/cakephp/cakephp/src/Cache/Engine/FileEngine.php index 9e786787a..2e20d7952 100644 --- a/app/vendor/cakephp/cakephp/src/Cache/Engine/FileEngine.php +++ b/app/vendor/cakephp/cakephp/src/Cache/Engine/FileEngine.php @@ -18,6 +18,7 @@ use Cake\Cache\CacheEngine; use CallbackFilterIterator; +use DateInterval; use Exception; use FilesystemIterator; use LogicException; @@ -38,9 +39,9 @@ class FileEngine extends CacheEngine /** * Instance of SplFileObject class * - * @var \SplFileObject|null + * @var \SplFileObject */ - protected $_File; + protected SplFileObject $_File; /** * The default config used unless overridden by runtime configuration @@ -51,15 +52,14 @@ class FileEngine extends CacheEngine * - `lock` Used by FileCache. Should files be locked before writing to them? * - `mask` The mask used for created files * - `dirMask` The mask used for created folders - * - `path` Path to where cachefiles should be saved. Defaults to system's temp dir. + * - `path` Path to where cache files should be saved. Defaults to system's temp dir. * - `prefix` Prepended to all entries. Good for when you need to share a keyspace * with either another cache config or another application. - * cache::gc from ever being called automatically. * - `serialize` Should cache objects be serialized first. * * @var array */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'duration' => 3600, 'groups' => [], 'lock' => true, @@ -75,7 +75,7 @@ class FileEngine extends CacheEngine * * @var bool */ - protected $_init = true; + protected bool $_init = true; /** * Initialize File Cache Engine @@ -89,9 +89,7 @@ public function init(array $config = []): bool { parent::init($config); - if ($this->_config['path'] === null) { - $this->_config['path'] = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'cake_cache' . DIRECTORY_SEPARATOR; - } + $this->_config['path'] ??= sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'cake_cache' . DIRECTORY_SEPARATOR; if (substr($this->_config['path'], -1) !== DIRECTORY_SEPARATOR) { $this->_config['path'] .= DIRECTORY_SEPARATOR; } @@ -112,7 +110,7 @@ public function init(array $config = []): bool * for it or let the driver take care of that. * @return bool True on success and false on failure. */ - public function set($key, $value, $ttl = null): bool + public function set(string $key, mixed $value, DateInterval|int|null $ttl = null): bool { if ($value === '' || !$this->_init) { return false; @@ -129,14 +127,12 @@ public function set($key, $value, $ttl = null): bool } $expires = time() + $this->duration($ttl); - $contents = implode([$expires, PHP_EOL, $value, PHP_EOL]); + $contents = implode('', [$expires, PHP_EOL, $value, PHP_EOL]); if ($this->_config['lock']) { - /** @psalm-suppress PossiblyNullReference */ $this->_File->flock(LOCK_EX); } - /** @psalm-suppress PossiblyNullReference */ $this->_File->rewind(); $success = $this->_File->ftruncate(0) && $this->_File->fwrite($contents) && @@ -145,7 +141,7 @@ public function set($key, $value, $ttl = null): bool if ($this->_config['lock']) { $this->_File->flock(LOCK_UN); } - $this->_File = null; + unset($this->_File); return $success; } @@ -158,7 +154,7 @@ public function set($key, $value, $ttl = null): bool * @return mixed The cached data, or default value if the data doesn't exist, has * expired, or if there was an error fetching it */ - public function get($key, $default = null) + public function get(string $key, mixed $default = null): mixed { $key = $this->_key($key); @@ -167,14 +163,11 @@ public function get($key, $default = null) } if ($this->_config['lock']) { - /** @psalm-suppress PossiblyNullReference */ $this->_File->flock(LOCK_SH); } - /** @psalm-suppress PossiblyNullReference */ $this->_File->rewind(); $time = time(); - /** @psalm-suppress RiskyCast */ $cachetime = (int)$this->_File->current(); if ($cachetime < $time) { @@ -188,7 +181,6 @@ public function get($key, $default = null) $data = ''; $this->_File->next(); while ($this->_File->valid()) { - /** @psalm-suppress PossiblyInvalidOperand */ $data .= $this->_File->current(); $this->_File->next(); } @@ -200,7 +192,7 @@ public function get($key, $default = null) $data = trim($data); if ($data !== '' && !empty($this->_config['serialize'])) { - $data = unserialize($data); + return unserialize($data); } return $data; @@ -213,7 +205,7 @@ public function get($key, $default = null) * @return bool True if the value was successfully deleted, false if it didn't * exist or couldn't be removed */ - public function delete($key): bool + public function delete(string $key): bool { $key = $this->_key($key); @@ -221,9 +213,8 @@ public function delete($key): bool return false; } - /** @psalm-suppress PossiblyNullReference */ $path = $this->_File->getRealPath(); - $this->_File = null; + unset($this->_File); if ($path === false) { return false; @@ -244,21 +235,21 @@ public function clear(): bool if (!$this->_init) { return false; } - $this->_File = null; + unset($this->_File); $this->_clearDirectory($this->_config['path']); $directory = new RecursiveDirectoryIterator( $this->_config['path'], - FilesystemIterator::SKIP_DOTS + FilesystemIterator::SKIP_DOTS, ); - $contents = new RecursiveIteratorIterator( + $iterator = new RecursiveIteratorIterator( $directory, - RecursiveIteratorIterator::SELF_FIRST + RecursiveIteratorIterator::SELF_FIRST, ); $cleared = []; /** @var \SplFileInfo $fileInfo */ - foreach ($contents as $fileInfo) { + foreach ($iterator as $fileInfo) { if ($fileInfo->isFile()) { unset($fileInfo); continue; @@ -282,7 +273,7 @@ public function clear(): bool // unsetting iterators helps releasing possible locks in certain environments, // which could otherwise make `rmdir()` fail - unset($directory, $contents); + unset($directory, $iterator); return true; } @@ -313,7 +304,7 @@ protected function _clearDirectory(string $path): void try { $file = new SplFileObject($path . $entry, 'r'); - } catch (Exception $e) { + } catch (Exception) { continue; } @@ -338,7 +329,7 @@ protected function _clearDirectory(string $path): void * @return int|false * @throws \LogicException */ - public function decrement(string $key, int $offset = 1) + public function decrement(string $key, int $offset = 1): int|false { throw new LogicException('Files cannot be atomically decremented.'); } @@ -351,7 +342,7 @@ public function decrement(string $key, int $offset = 1) * @return int|false * @throws \LogicException */ - public function increment(string $key, int $offset = 1) + public function increment(string $key, int $offset = 1): int|false { throw new LogicException('Files cannot be atomically incremented.'); } @@ -382,7 +373,7 @@ protected function _setKey(string $key, bool $createKey = false): bool return false; } if ( - empty($this->_File) || + !isset($this->_File) || $this->_File->getBasename() !== $key || $this->_File->valid() === false ) { @@ -398,9 +389,9 @@ protected function _setKey(string $key, bool $createKey = false): bool if (!$exists && !chmod($this->_File->getPathname(), (int)$this->_config['mask'])) { trigger_error(sprintf( - 'Could not apply permission mask "%s" on cache file "%s"', + 'Could not apply permission mask `%s` on cache file `%s`', $this->_File->getPathname(), - $this->_config['mask'] + $this->_config['mask'], ), E_USER_WARNING); } } @@ -429,7 +420,7 @@ protected function _active(): bool $this->_init = false; trigger_error(sprintf( '%s is not writable', - $this->_config['path'] + $this->_config['path'], ), E_USER_WARNING); } @@ -439,7 +430,7 @@ protected function _active(): bool /** * @inheritDoc */ - protected function _key($key): string + protected function _key(string $key): string { $key = parent::_key($key); @@ -454,14 +445,14 @@ protected function _key($key): string */ public function clearGroup(string $group): bool { - $this->_File = null; + unset($this->_File); $prefix = (string)$this->_config['prefix']; $directoryIterator = new RecursiveDirectoryIterator($this->_config['path']); $contents = new RecursiveIteratorIterator( $directoryIterator, - RecursiveIteratorIterator::CHILD_FIRST + RecursiveIteratorIterator::CHILD_FIRST, ); $filtered = new CallbackFilterIterator( $contents, @@ -470,20 +461,18 @@ function (SplFileInfo $current) use ($group, $prefix) { return false; } - $hasPrefix = $prefix === '' - || strpos($current->getBasename(), $prefix) === 0; + $hasPrefix = $prefix === '' || str_starts_with($current->getBasename(), $prefix); if ($hasPrefix === false) { return false; } - $pos = strpos( + return str_contains( $current->getPathname(), - DIRECTORY_SEPARATOR . $group . DIRECTORY_SEPARATOR + DIRECTORY_SEPARATOR . $group . DIRECTORY_SEPARATOR, ); - - return $pos !== false; - } + }, ); + /** @var \SplFileInfo $object */ foreach ($filtered as $object) { $path = $object->getPathname(); unset($object); diff --git a/app/vendor/cakephp/cakephp/src/Cache/Engine/MemcachedEngine.php b/app/vendor/cakephp/cakephp/src/Cache/Engine/MemcachedEngine.php index 687987603..6a2d19ec1 100644 --- a/app/vendor/cakephp/cakephp/src/Cache/Engine/MemcachedEngine.php +++ b/app/vendor/cakephp/cakephp/src/Cache/Engine/MemcachedEngine.php @@ -18,8 +18,9 @@ use Cake\Cache\CacheEngine; use Cake\Cache\Exception\InvalidArgumentException; +use Cake\Core\Exception\CakeException; +use DateInterval; use Memcached; -use RuntimeException; /** * Memcached storage engine for cache. Memcached has some limitations in the amount of @@ -37,7 +38,7 @@ class MemcachedEngine extends CacheEngine * * @var \Memcached */ - protected $_Memcached; + protected Memcached $_Memcached; /** * The default config used unless overridden by runtime configuration @@ -53,7 +54,7 @@ class MemcachedEngine extends CacheEngine * - `prefix` Prepended to all entries. Good for when you need to share a keyspace * with either another cache config or another application. * - `serialize` The serializer engine used to serialize data. Available engines are 'php', - * 'igbinary' and 'json'. Beside 'php', the memcached extension must be compiled with the + * 'igbinary' and 'json'. Besides 'php', the memcached extension must be compiled with the * appropriate serializer support. * - `servers` String or array of memcached servers. If an array MemcacheEngine will use * them as a pool. @@ -62,7 +63,7 @@ class MemcachedEngine extends CacheEngine * * @var array */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'compress' => false, 'duration' => 3600, 'groups' => [], @@ -84,12 +85,12 @@ class MemcachedEngine extends CacheEngine * * @var array */ - protected $_serializers = []; + protected array $_serializers = []; /** * @var array */ - protected $_compiledGroupNames = []; + protected array $_compiledGroupNames = []; /** * Initialize the Cache Engine @@ -104,7 +105,7 @@ class MemcachedEngine extends CacheEngine public function init(array $config = []): bool { if (!extension_loaded('memcached')) { - throw new RuntimeException('The `memcached` extension must be enabled to use MemcachedEngine.'); + throw new CakeException('The `memcached` extension must be enabled to use MemcachedEngine.'); } $this->_serializers = [ @@ -134,6 +135,10 @@ public function init(array $config = []): bool $this->_config['servers'] = [$this->_config['servers']]; } + if (isset($this->_Memcached)) { + return true; + } + if ($this->_config['persistent']) { $this->_Memcached = new Memcached($this->_config['persistent']); } else { @@ -149,7 +154,7 @@ public function init(array $config = []): bool throw new InvalidArgumentException( 'Invalid cache configuration. Multiple persistent cache configurations are detected' . ' with different `servers` values. `servers` values for persistent cache configurations' . - ' must be the same when using the same persistence id.' + ' must be the same when using the same persistence id.', ); } } @@ -175,20 +180,20 @@ public function init(array $config = []): bool if (empty($this->_config['username']) && !empty($this->_config['login'])) { throw new InvalidArgumentException( - 'Please pass "username" instead of "login" for connecting to Memcached' + 'Please pass "username" instead of "login" for connecting to Memcached', ); } if ($this->_config['username'] !== null && $this->_config['password'] !== null) { if (!method_exists($this->_Memcached, 'setSaslAuthData')) { throw new InvalidArgumentException( - 'Memcached extension is not built with SASL support' + 'Memcached extension is not built with SASL support', ); } $this->_Memcached->setOption(Memcached::OPT_BINARY_PROTOCOL, true); $this->_Memcached->setSaslAuthData( $this->_config['username'], - $this->_config['password'] + $this->_config['password'], ); } @@ -209,7 +214,7 @@ protected function _setOptions(): void $serializer = strtolower($this->_config['serialize']); if (!isset($this->_serializers[$serializer])) { throw new InvalidArgumentException( - sprintf('%s is not a valid serializer engine for Memcached', $serializer) + sprintf('`%s` is not a valid serializer engine for Memcached.', $serializer), ); } @@ -218,13 +223,13 @@ protected function _setOptions(): void !constant('Memcached::HAVE_' . strtoupper($serializer)) ) { throw new InvalidArgumentException( - sprintf('Memcached extension is not compiled with %s support', $serializer) + sprintf('Memcached extension is not compiled with `%s` support.', $serializer), ); } $this->_Memcached->setOption( Memcached::OPT_SERIALIZER, - $this->_serializers[$serializer] + $this->_serializers[$serializer], ); // Check for Amazon ElastiCache instance @@ -232,15 +237,12 @@ protected function _setOptions(): void defined('Memcached::OPT_CLIENT_MODE') && defined('Memcached::DYNAMIC_CLIENT_MODE') ) { - $this->_Memcached->setOption( - Memcached::OPT_CLIENT_MODE, - Memcached::DYNAMIC_CLIENT_MODE - ); + $this->_Memcached->setOption(Memcached::OPT_CLIENT_MODE, Memcached::DYNAMIC_CLIENT_MODE); } $this->_Memcached->setOption( Memcached::OPT_COMPRESSION, - (bool)$this->_config['compress'] + (bool)$this->_config['compress'], ); } @@ -254,10 +256,10 @@ protected function _setOptions(): void public function parseServerString(string $server): array { $socketTransport = 'unix://'; - if (strpos($server, $socketTransport) === 0) { + if (str_starts_with($server, $socketTransport)) { return [substr($server, strlen($socketTransport)), 0]; } - if (substr($server, 0, 1) === '[') { + if (str_starts_with($server, '[')) { $position = strpos($server, ']:'); if ($position !== false) { $position++; @@ -282,7 +284,7 @@ public function parseServerString(string $server): array * @return string|int|bool|null * @see https://secure.php.net/manual/en/memcached.getoption.php */ - public function getOption(int $name) + public function getOption(int $name): string|int|bool|null { return $this->_Memcached->getOption($name); } @@ -301,7 +303,7 @@ public function getOption(int $name) * @return bool True if the data was successfully cached, false on failure * @see https://www.php.net/manual/en/memcached.set.php */ - public function set($key, $value, $ttl = null): bool + public function set(string $key, mixed $value, DateInterval|int|null $ttl = null): bool { $duration = $this->duration($ttl); @@ -317,7 +319,7 @@ public function set($key, $value, $ttl = null): bool * for it or let the driver take care of that. * @return bool Whether the write was successful or not. */ - public function setMultiple($values, $ttl = null): bool + public function setMultiple(iterable $values, DateInterval|int|null $ttl = null): bool { $cacheData = []; foreach ($values as $key => $value) { @@ -336,7 +338,7 @@ public function setMultiple($values, $ttl = null): bool * @return mixed The cached data, or default value if the data doesn't exist, has * expired, or if there was an error fetching it. */ - public function get($key, $default = null) + public function get(string $key, mixed $default = null): mixed { $key = $this->_key($key); $value = $this->_Memcached->get($key); @@ -350,12 +352,12 @@ public function get($key, $default = null) /** * Read many keys from the cache at once * - * @param iterable $keys An array of identifiers for the data + * @param iterable $keys An array of identifiers for the data * @param mixed $default Default value to return for keys that do not exist. - * @return array An array containing, for each of the given $keys, the cached data or - * false if cached data could not be retrieved. + * @return iterable An array containing, for each of the given $keys, the cached data or + * `$default` if cached data could not be retrieved. */ - public function getMultiple($keys, $default = null): array + public function getMultiple(iterable $keys, mixed $default = null): iterable { $cacheKeys = []; foreach ($keys as $key) { @@ -363,6 +365,10 @@ public function getMultiple($keys, $default = null): array } $values = $this->_Memcached->getMulti($cacheKeys); + if ($values === false) { + return array_fill_keys(array_keys($cacheKeys), $default); + } + $return = []; foreach ($cacheKeys as $original => $prefixed) { $return[$original] = array_key_exists($prefixed, $values) ? $values[$prefixed] : $default; @@ -378,7 +384,7 @@ public function getMultiple($keys, $default = null): array * @param int $offset How much to increment * @return int|false New incremented value, false otherwise */ - public function increment(string $key, int $offset = 1) + public function increment(string $key, int $offset = 1): int|false { return $this->_Memcached->increment($this->_key($key), $offset); } @@ -390,7 +396,7 @@ public function increment(string $key, int $offset = 1) * @param int $offset How much to subtract * @return int|false New decremented value, false otherwise */ - public function decrement(string $key, int $offset = 1) + public function decrement(string $key, int $offset = 1): int|false { return $this->_Memcached->decrement($this->_key($key), $offset); } @@ -402,7 +408,7 @@ public function decrement(string $key, int $offset = 1) * @return bool True if the value was successfully deleted, false if it didn't * exist or couldn't be removed. */ - public function delete($key): bool + public function delete(string $key): bool { return $this->_Memcached->delete($this->_key($key)); } @@ -414,7 +420,7 @@ public function delete($key): bool * @return bool of boolean values that are true if the key was successfully * deleted, false if it didn't exist or couldn't be removed. */ - public function deleteMultiple($keys): bool + public function deleteMultiple(iterable $keys): bool { $cacheKeys = []; foreach ($keys as $key) { @@ -437,7 +443,7 @@ public function clear(): bool } foreach ($keys as $key) { - if ($this->_config['prefix'] === '' || strpos($key, $this->_config['prefix']) === 0) { + if (str_starts_with($key, $this->_config['prefix'])) { $this->_Memcached->delete($key); } } @@ -452,7 +458,7 @@ public function clear(): bool * @param mixed $value Data to be cached. * @return bool True if the data was successfully cached, false on failure. */ - public function add(string $key, $value): bool + public function add(string $key, mixed $value): bool { $duration = $this->_config['duration']; $key = $this->_key($key); @@ -469,7 +475,7 @@ public function add(string $key, $value): bool */ public function groups(): array { - if (empty($this->_compiledGroupNames)) { + if (!$this->_compiledGroupNames) { foreach ($this->_config['groups'] as $group) { $this->_compiledGroupNames[] = $this->_config['prefix'] . $group; } diff --git a/app/vendor/cakephp/cakephp/src/Cache/Engine/NullEngine.php b/app/vendor/cakephp/cakephp/src/Cache/Engine/NullEngine.php index 89defe467..4fc45ee6a 100644 --- a/app/vendor/cakephp/cakephp/src/Cache/Engine/NullEngine.php +++ b/app/vendor/cakephp/cakephp/src/Cache/Engine/NullEngine.php @@ -17,6 +17,7 @@ namespace Cake\Cache\Engine; use Cake\Cache\CacheEngine; +use DateInterval; /** * Null cache engine, all operations appear to work, but do nothing. @@ -36,7 +37,7 @@ public function init(array $config = []): bool /** * @inheritDoc */ - public function set($key, $value, $ttl = null): bool + public function set(string $key, mixed $value, DateInterval|int|null $ttl = null): bool { return true; } @@ -44,7 +45,7 @@ public function set($key, $value, $ttl = null): bool /** * @inheritDoc */ - public function setMultiple($values, $ttl = null): bool + public function setMultiple(iterable $values, DateInterval|int|null $ttl = null): bool { return true; } @@ -52,7 +53,7 @@ public function setMultiple($values, $ttl = null): bool /** * @inheritDoc */ - public function get($key, $default = null) + public function get(string $key, mixed $default = null): mixed { return $default; } @@ -60,7 +61,7 @@ public function get($key, $default = null) /** * @inheritDoc */ - public function getMultiple($keys, $default = null): iterable + public function getMultiple(iterable $keys, mixed $default = null): iterable { $result = []; @@ -74,7 +75,7 @@ public function getMultiple($keys, $default = null): iterable /** * @inheritDoc */ - public function increment(string $key, int $offset = 1) + public function increment(string $key, int $offset = 1): int|false { return 1; } @@ -82,7 +83,7 @@ public function increment(string $key, int $offset = 1) /** * @inheritDoc */ - public function decrement(string $key, int $offset = 1) + public function decrement(string $key, int $offset = 1): int|false { return 0; } @@ -90,7 +91,7 @@ public function decrement(string $key, int $offset = 1) /** * @inheritDoc */ - public function delete($key): bool + public function delete(string $key): bool { return true; } @@ -98,7 +99,7 @@ public function delete($key): bool /** * @inheritDoc */ - public function deleteMultiple($keys): bool + public function deleteMultiple(iterable $keys): bool { return true; } diff --git a/app/vendor/cakephp/cakephp/src/Cache/Engine/RedisEngine.php b/app/vendor/cakephp/cakephp/src/Cache/Engine/RedisEngine.php index cdd479fb2..8e4fb4af1 100644 --- a/app/vendor/cakephp/cakephp/src/Cache/Engine/RedisEngine.php +++ b/app/vendor/cakephp/cakephp/src/Cache/Engine/RedisEngine.php @@ -18,10 +18,11 @@ namespace Cake\Cache\Engine; use Cake\Cache\CacheEngine; +use Cake\Core\Exception\CakeException; use Cake\Log\Log; +use DateInterval; use Redis; use RedisException; -use RuntimeException; /** * Redis storage engine for cache. @@ -33,7 +34,7 @@ class RedisEngine extends CacheEngine * * @var \Redis */ - protected $_Redis; + protected Redis $_Redis; /** * The default config used unless overridden by runtime configuration @@ -52,10 +53,13 @@ class RedisEngine extends CacheEngine * - `server` URL or IP to the Redis server host. * - `timeout` timeout in seconds (float). * - `unix_socket` Path to the unix socket file (default: false) + * - `clearUsesFlushDb` Enable clear() and clearBlocking() to use FLUSHDB. This will be + * faster than standard clear()/clearBlocking() but will ignore prefixes and will + * cause dataloss if other applications are sharing a redis database. * * @var array */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'database' => 0, 'duration' => 3600, 'groups' => [], @@ -69,6 +73,7 @@ class RedisEngine extends CacheEngine 'timeout' => 0, 'unix_socket' => false, 'scanCount' => 10, + 'clearUsesFlushDb' => false, ]; /** @@ -82,7 +87,7 @@ class RedisEngine extends CacheEngine public function init(array $config = []): bool { if (!extension_loaded('redis')) { - throw new RuntimeException('The `redis` extension must be enabled to use RedisEngine.'); + throw new CakeException('The `redis` extension must be enabled to use RedisEngine.'); } if (!empty($config['host'])) { @@ -136,7 +141,7 @@ protected function _connect(): bool $return = $this->_Redis->auth($this->_config['password']); } if ($return) { - $return = $this->_Redis->select((int)$this->_config['database']); + return $this->_Redis->select((int)$this->_config['database']); } return $return; @@ -150,13 +155,13 @@ protected function _connect(): bool * @throws \RedisException * @return bool True if Redis server was connected */ - protected function _connectTransient($server, array $ssl): bool + protected function _connectTransient(string $server, array $ssl): bool { - if (empty($ssl)) { + if ($ssl === []) { return $this->_Redis->connect( $server, (int)$this->_config['port'], - (int)$this->_config['timeout'] + (int)$this->_config['timeout'], ); } @@ -167,7 +172,7 @@ protected function _connectTransient($server, array $ssl): bool null, 0, 0.0, - ['ssl' => $ssl] + ['ssl' => $ssl], ); } @@ -179,16 +184,16 @@ protected function _connectTransient($server, array $ssl): bool * @throws \RedisException * @return bool True if Redis server was connected */ - protected function _connectPersistent($server, array $ssl): bool + protected function _connectPersistent(string $server, array $ssl): bool { $persistentId = $this->_config['port'] . $this->_config['timeout'] . $this->_config['database']; - if (empty($ssl)) { + if ($ssl === []) { return $this->_Redis->pconnect( $server, (int)$this->_config['port'], (int)$this->_config['timeout'], - $persistentId + $persistentId, ); } @@ -199,7 +204,7 @@ protected function _connectPersistent($server, array $ssl): bool $persistentId, 0, 0.0, - ['ssl' => $ssl] + ['ssl' => $ssl], ); } @@ -213,7 +218,7 @@ protected function _connectPersistent($server, array $ssl): bool * for it or let the driver take care of that. * @return bool True if the data was successfully cached, false on failure */ - public function set($key, $value, $ttl = null): bool + public function set(string $key, mixed $value, DateInterval|int|null $ttl = null): bool { $key = $this->_key($key); $value = $this->serialize($value); @@ -234,7 +239,7 @@ public function set($key, $value, $ttl = null): bool * @return mixed The cached data, or the default if the data doesn't exist, has * expired, or if there was an error fetching it */ - public function get($key, $default = null) + public function get(string $key, mixed $default = null): mixed { $value = $this->_Redis->get($this->_key($key)); if ($value === false) { @@ -251,7 +256,7 @@ public function get($key, $default = null) * @param int $offset How much to increment * @return int|false New incremented value, false otherwise */ - public function increment(string $key, int $offset = 1) + public function increment(string $key, int $offset = 1): int|false { $duration = $this->_config['duration']; $key = $this->_key($key); @@ -271,7 +276,7 @@ public function increment(string $key, int $offset = 1) * @param int $offset How much to subtract * @return int|false New decremented value, false otherwise */ - public function decrement(string $key, int $offset = 1) + public function decrement(string $key, int $offset = 1): int|false { $duration = $this->_config['duration']; $key = $this->_key($key); @@ -290,11 +295,11 @@ public function decrement(string $key, int $offset = 1) * @param string $key Identifier for the data * @return bool True if the value was successfully deleted, false if it didn't exist or couldn't be removed */ - public function delete($key): bool + public function delete(string $key): bool { $key = $this->_key($key); - return $this->_Redis->del($key) > 0; + return (int)$this->_Redis->del($key) > 0; } /** @@ -309,7 +314,7 @@ public function deleteAsync(string $key): bool { $key = $this->_key($key); - return $this->_Redis->unlink($key) > 0; + return (int)$this->_Redis->unlink($key) > 0; } /** @@ -319,6 +324,12 @@ public function deleteAsync(string $key): bool */ public function clear(): bool { + if ($this->getConfig('clearUsesFlushDb')) { + $this->_Redis->flushDB(false); + + return true; + } + $this->_Redis->setOption(Redis::OPT_SCAN, (string)Redis::SCAN_RETRY); $isAllDeleted = true; @@ -333,7 +344,7 @@ public function clear(): bool } foreach ($keys as $key) { - $isDeleted = ($this->_Redis->del($key) > 0); + $isDeleted = ((int)$this->_Redis->unlink($key) > 0); $isAllDeleted = $isAllDeleted && $isDeleted; } } @@ -344,12 +355,16 @@ public function clear(): bool /** * Delete all keys from the cache by a blocking operation * - * Faster than clear() using unlink method. - * * @return bool True if the cache was successfully cleared, false otherwise */ public function clearBlocking(): bool { + if ($this->getConfig('clearUsesFlushDb')) { + $this->_Redis->flushDB(true); + + return true; + } + $this->_Redis->setOption(Redis::OPT_SCAN, (string)Redis::SCAN_RETRY); $isAllDeleted = true; @@ -364,7 +379,7 @@ public function clearBlocking(): bool } foreach ($keys as $key) { - $isDeleted = ($this->_Redis->unlink($key) > 0); + $isDeleted = ((int)$this->_Redis->del($key) > 0); $isAllDeleted = $isAllDeleted && $isDeleted; } } @@ -381,7 +396,7 @@ public function clearBlocking(): bool * @return bool True if the data was successfully cached, false on failure. * @link https://github.com/phpredis/phpredis#set */ - public function add(string $key, $value): bool + public function add(string $key, mixed $value): bool { $duration = $this->_config['duration']; $key = $this->_key($key); @@ -432,13 +447,13 @@ public function clearGroup(string $group): bool * Serialize value for saving to Redis. * * This is needed instead of using Redis' in built serialization feature - * as it creates problems incrementing/decrementing intially set integer value. + * as it creates problems incrementing/decrementing initially set integer value. * * @param mixed $value Value to serialize. * @return string * @link https://github.com/phpredis/phpredis/issues/81 */ - protected function serialize($value): string + protected function serialize(mixed $value): string { if (is_int($value)) { return (string)$value; @@ -453,7 +468,7 @@ protected function serialize($value): string * @param string $value Value to unserialize. * @return mixed */ - protected function unserialize(string $value) + protected function unserialize(string $value): mixed { if (preg_match('/^[-]?\d+$/', $value)) { return (int)$value; @@ -477,7 +492,7 @@ protected function _createRedisInstance(): Redis */ public function __destruct() { - if (empty($this->_config['persistent']) && $this->_Redis instanceof Redis) { + if (empty($this->_config['persistent'])) { $this->_Redis->close(); } } diff --git a/app/vendor/cakephp/cakephp/src/Cache/Engine/WincacheEngine.php b/app/vendor/cakephp/cakephp/src/Cache/Engine/WincacheEngine.php deleted file mode 100644 index ca0388e4b..000000000 --- a/app/vendor/cakephp/cakephp/src/Cache/Engine/WincacheEngine.php +++ /dev/null @@ -1,202 +0,0 @@ - - */ - protected $_compiledGroupNames = []; - - /** - * Initialize the Cache Engine - * - * Called automatically by the cache frontend - * - * @param array $config array of setting for the engine - * @return bool True if the engine has been successfully initialized, false if not - */ - public function init(array $config = []): bool - { - if (!extension_loaded('wincache')) { - throw new RuntimeException('The `wincache` extension must be enabled to use WincacheEngine.'); - } - - parent::init($config); - - return true; - } - - /** - * Write data for key into cache - * - * @param string $key Identifier for the data - * @param mixed $value Data to be cached - * @param \DateInterval|int|null $ttl Optional. The TTL value of this item. If no value is sent and - * the driver supports TTL then the library may set a default value - * for it or let the driver take care of that. - * @return bool True if the data was successfully cached, false on failure - */ - public function set($key, $value, $ttl = null): bool - { - $key = $this->_key($key); - $duration = $this->duration($ttl); - - return wincache_ucache_set($key, $value, $duration); - } - - /** - * Read a key from the cache - * - * @param string $key Identifier for the data - * @param mixed $default Default value to return if the key does not exist. - * @return mixed The cached data, or default value if the data doesn't exist, - * has expired, or if there was an error fetching it - */ - public function get($key, $default = null) - { - $value = wincache_ucache_get($this->_key($key), $success); - if ($success === false) { - return $default; - } - - return $value; - } - - /** - * Increments the value of an integer cached key - * - * @param string $key Identifier for the data - * @param int $offset How much to increment - * @return int|false New incremented value, false otherwise - */ - public function increment(string $key, int $offset = 1) - { - $key = $this->_key($key); - - return wincache_ucache_inc($key, $offset); - } - - /** - * Decrements the value of an integer cached key - * - * @param string $key Identifier for the data - * @param int $offset How much to subtract - * @return int|false New decremented value, false otherwise - */ - public function decrement(string $key, int $offset = 1) - { - $key = $this->_key($key); - - return wincache_ucache_dec($key, $offset); - } - - /** - * Delete a key from the cache - * - * @param string $key Identifier for the data - * @return bool True if the value was successfully deleted, false if it didn't exist or couldn't be removed - */ - public function delete($key): bool - { - $key = $this->_key($key); - - return wincache_ucache_delete($key); - } - - /** - * Delete all keys from the cache. This will clear every - * item in the cache matching the cache config prefix. - * - * @return bool True Returns true. - */ - public function clear(): bool - { - $info = wincache_ucache_info(); - $cacheKeys = $info['ucache_entries']; - unset($info); - foreach ($cacheKeys as $key) { - if (strpos($key['key_name'], $this->_config['prefix']) === 0) { - wincache_ucache_delete($key['key_name']); - } - } - - return true; - } - - /** - * Returns the `group value` for each of the configured groups - * If the group initial value was not found, then it initializes - * the group accordingly. - * - * @return array - */ - public function groups(): array - { - if (empty($this->_compiledGroupNames)) { - foreach ($this->_config['groups'] as $group) { - $this->_compiledGroupNames[] = $this->_config['prefix'] . $group; - } - } - - $groups = wincache_ucache_get($this->_compiledGroupNames); - if (count($groups) !== count($this->_config['groups'])) { - foreach ($this->_compiledGroupNames as $group) { - if (!isset($groups[$group])) { - wincache_ucache_set($group, 1); - $groups[$group] = 1; - } - } - ksort($groups); - } - - $result = []; - $groups = array_values($groups); - foreach ($this->_config['groups'] as $i => $group) { - $result[] = $group . $groups[$i]; - } - - return $result; - } - - /** - * Increments the group value to simulate deletion of all keys under a group - * old values will remain in storage until they expire. - * - * @param string $group The group to clear. - * @return bool success - */ - public function clearGroup(string $group): bool - { - $success = false; - wincache_ucache_inc($this->_config['prefix'] . $group, 1, $success); - - return $success; - } -} diff --git a/app/vendor/cakephp/cakephp/src/Cache/Exception/InvalidArgumentException.php b/app/vendor/cakephp/cakephp/src/Cache/Exception/InvalidArgumentException.php index 1931e65ac..660f5aab5 100644 --- a/app/vendor/cakephp/cakephp/src/Cache/Exception/InvalidArgumentException.php +++ b/app/vendor/cakephp/cakephp/src/Cache/Exception/InvalidArgumentException.php @@ -25,10 +25,3 @@ class InvalidArgumentException extends CakeException implements InvalidArgumentInterface { } -// phpcs:disable -// @deprecated Backwards compatibility alias. Will be removed in 5.0 -class_alias( - 'Cake\Cache\Exception\InvalidArgumentException', - 'Cake\Cache\InvalidArgumentException' -); -// phpcs:enable diff --git a/app/vendor/cakephp/cakephp/src/Cache/InvalidArgumentException.php b/app/vendor/cakephp/cakephp/src/Cache/InvalidArgumentException.php deleted file mode 100644 index 8d0e9e6a5..000000000 --- a/app/vendor/cakephp/cakephp/src/Cache/InvalidArgumentException.php +++ /dev/null @@ -1,13 +0,0 @@ -=7.4.0", - "cakephp/core": "^4.0", - "psr/simple-cache": "^1.0 || ^2.0" + "php": ">=8.1", + "cakephp/core": "5.2.*@dev", + "psr/simple-cache": "^2.0 || ^3.0" }, "provide": { - "psr/simple-cache-implementation": "^1.0 || ^2.0" + "psr/simple-cache-implementation": "^3.0" }, "autoload": { "psr-4": { "Cake\\Cache\\": "." } + }, + "prefer-stable": true, + "extra": { + "branch-alias": { + "dev-5.x": "5.2.x-dev" + } } } diff --git a/app/vendor/cakephp/cakephp/src/Collection/Collection.php b/app/vendor/cakephp/cakephp/src/Collection/Collection.php index f1280f3a0..eaceff476 100644 --- a/app/vendor/cakephp/cakephp/src/Collection/Collection.php +++ b/app/vendor/cakephp/cakephp/src/Collection/Collection.php @@ -19,7 +19,6 @@ use ArrayIterator; use Exception; use IteratorIterator; -use Serializable; /** * A collection is an immutable list of elements with a handful of functions to @@ -27,7 +26,7 @@ * * @template-extends \IteratorIterator> */ -class Collection extends IteratorIterator implements CollectionInterface, Serializable +class Collection extends IteratorIterator implements CollectionInterface { use CollectionTrait; @@ -46,17 +45,6 @@ public function __construct(iterable $items) parent::__construct($items); } - /** - * Returns a string representation of this object that can be used - * to reconstruct it - * - * @return string - */ - public function serialize(): string - { - return serialize($this->buffered()); - } - /** * Returns an array for serializing this of this object. * @@ -67,17 +55,6 @@ public function __serialize(): array return $this->buffered()->toArray(); } - /** - * Unserializes the passed string and rebuilds the Collection instance - * - * @param string $collection The serialized collection - * @return void - */ - public function unserialize($collection): void - { - $this->__construct(unserialize($collection)); - } - /** * Rebuilds the Collection instance. * @@ -89,32 +66,6 @@ public function __unserialize(array $data): void $this->__construct($data); } - /** - * {@inheritDoc} - * - * @return int - */ - public function count(): int - { - $traversable = $this->optimizeUnwrap(); - - if (is_array($traversable)) { - return count($traversable); - } - - return iterator_count($traversable); - } - - /** - * {@inheritDoc} - * - * @return int - */ - public function countKeys(): int - { - return count($this->toArray()); - } - /** * Returns an array that can be used to describe the internal state of this * object. @@ -125,7 +76,7 @@ public function __debugInfo(): array { try { $count = $this->count(); - } catch (Exception $e) { + } catch (Exception) { $count = 'An exception occurred while getting count'; } diff --git a/app/vendor/cakephp/cakephp/src/Collection/CollectionInterface.php b/app/vendor/cakephp/cakephp/src/Collection/CollectionInterface.php index 1fcd3a1ab..2c1767632 100644 --- a/app/vendor/cakephp/cakephp/src/Collection/CollectionInterface.php +++ b/app/vendor/cakephp/cakephp/src/Collection/CollectionInterface.php @@ -16,9 +16,10 @@ */ namespace Cake\Collection; +use Countable; use Iterator; use JsonSerializable; -use Traversable; +use const SORT_NUMERIC; /** * Describes the methods a Collection should implement. A collection is an immutable @@ -27,7 +28,7 @@ * * @template-extends \Iterator */ -interface CollectionInterface extends Iterator, JsonSerializable +interface CollectionInterface extends Iterator, JsonSerializable, Countable { /** * Applies a callback to the elements in this collection. @@ -91,11 +92,25 @@ public function filter(?callable $callback = null): CollectionInterface; * }); * ``` * - * @param callable $callback the method that will receive each of the elements and - * returns true whether they should be out of the resulting collection. + * @param callable|null $callback the method that will receive each of the elements and + * returns true whether they should be out of the resulting collection. + * If left null, a callback that filters out truthy values will be used. + * @return self + */ + public function reject(?callable $callback = null): CollectionInterface; + + /** + * Loops through each value in the collection and returns a new collection + * with only unique values based on the value returned by the callback. + * + * The callback is passed the value as the first argument and the key as the + * second argument. + * + * @param callable|null $callback the method that will receive each of the elements and + * returns the value used to determine uniqueness. * @return self */ - public function reject(callable $callback): CollectionInterface; + public function unique(?callable $callback = null): CollectionInterface; /** * Returns true if all values in this collection pass the truth test provided @@ -148,7 +163,7 @@ public function some(callable $callback): bool; * @param mixed $value The value to check for * @return bool true if $value is present in this collection */ - public function contains($value): bool; + public function contains(mixed $value): bool; /** * Returns another collection after modifying each of the values in this one using @@ -176,17 +191,17 @@ public function map(callable $callback): CollectionInterface; /** * Folds the values in this collection to a single value, as the result of - * applying the callback function to all elements. $zero is the initial state + * applying the callback function to all elements. $initial is the initial state * of the reduction, and each successive step of it should be returned * by the callback function. - * If $zero is omitted the first value of the collection will be used in its place + * If $initial is omitted the first value of the collection will be used in its place * and reduction will start from the second item. * * @param callable $callback The callback function to be called * @param mixed $initial The state of reduction * @return mixed */ - public function reduce(callable $callback, $initial = null); + public function reduce(callable $callback, mixed $initial = null): mixed; /** * Returns a new collection containing the column or property value found in each @@ -204,8 +219,8 @@ public function reduce(callable $callback, $initial = null); * * ``` * $items = [ - * ['comment' => ['body' => 'cool', 'user' => ['name' => 'Mark']], - * ['comment' => ['body' => 'very cool', 'user' => ['name' => 'Renan']] + * ['comment' => ['body' => 'cool', 'user' => ['name' => 'Mark']]], + * ['comment' => ['body' => 'very cool', 'user' => ['name' => 'Renan']]], * ]; * $extracted = (new Collection($items))->extract('comment.user.name'); * @@ -218,7 +233,7 @@ public function reduce(callable $callback, $initial = null); * ``` * $items = [ * ['comment' => ['votes' => [['value' => 1], ['value' => 2], ['value' => 3]]], - * ['comment' => ['votes' => [['value' => 4]] + * ['comment' => ['votes' => [['value' => 4]], * ]; * $extracted = (new Collection($items))->extract('comment.votes.{*}.value'); * @@ -231,7 +246,7 @@ public function reduce(callable $callback, $initial = null); * of doing that. * @return self */ - public function extract($path): CollectionInterface; + public function extract(callable|string $path): CollectionInterface; /** * Returns the top element in this collection after being sorted by a property. @@ -256,7 +271,7 @@ public function extract($path): CollectionInterface; * @see \Cake\Collection\CollectionInterface::sortBy() * @return mixed The value of the top element in the collection */ - public function max($path, int $sort = \SORT_NUMERIC); + public function max(callable|string $path, int $sort = SORT_NUMERIC): mixed; /** * Returns the bottom element in this collection after being sorted by a property. @@ -281,7 +296,7 @@ public function max($path, int $sort = \SORT_NUMERIC); * @see \Cake\Collection\CollectionInterface::sortBy() * @return mixed The value of the bottom element in the collection */ - public function min($path, int $sort = \SORT_NUMERIC); + public function min(callable|string $path, int $sort = SORT_NUMERIC): mixed; /** * Returns the average of all the values extracted with $path @@ -311,7 +326,7 @@ public function min($path, int $sort = \SORT_NUMERIC); * that will return the value of the property to compute the average. * @return float|int|null */ - public function avg($path = null); + public function avg(callable|string|null $path = null): float|int|null; /** * Returns the median of all the values extracted with $path @@ -344,7 +359,7 @@ public function avg($path = null); * that will return the value of the property to compute the median. * @return float|int|null */ - public function median($path = null); + public function median(callable|string|null $path = null): float|int|null; /** * Returns a sorted iterator out of the elements in this collection, @@ -380,7 +395,11 @@ public function median($path = null); * @param int $sort The sort type, one of SORT_STRING, SORT_NUMERIC or SORT_NATURAL * @return self */ - public function sortBy($path, int $order = SORT_DESC, int $sort = \SORT_NUMERIC): CollectionInterface; + public function sortBy( + callable|string $path, + int $order = SORT_DESC, + int $sort = SORT_NUMERIC, + ): CollectionInterface; /** * Splits a collection into sets, grouped by the result of running each value @@ -423,7 +442,7 @@ public function sortBy($path, int $order = SORT_DESC, int $sort = \SORT_NUMERIC) * or a function returning the grouping key out of the provided element * @return self */ - public function groupBy($path): CollectionInterface; + public function groupBy(callable|string $path): CollectionInterface; /** * Given a list and a callback function that returns a key for each element @@ -462,7 +481,7 @@ public function groupBy($path): CollectionInterface; * or a function returning the indexing key out of the provided element * @return self */ - public function indexBy($path): CollectionInterface; + public function indexBy(callable|string $path): CollectionInterface; /** * Sorts a list into groups and returns a count for the number of elements @@ -500,7 +519,7 @@ public function indexBy($path): CollectionInterface; * or a function returning the indexing key out of the provided element * @return self */ - public function countBy($path): CollectionInterface; + public function countBy(callable|string $path): CollectionInterface; /** * Returns the total sum of all the values extracted with $matcher @@ -527,7 +546,7 @@ public function countBy($path): CollectionInterface; * that will return the value of the property to sum. * @return float|int */ - public function sumOf($path = null); + public function sumOf(callable|string|null $path = null): float|int; /** * Returns a new collection with the elements placed in a random order, @@ -595,15 +614,15 @@ public function skip(int $length): CollectionInterface; * * ``` * $items = [ - * ['comment' => ['body' => 'cool', 'user' => ['name' => 'Mark']], - * ['comment' => ['body' => 'very cool', 'user' => ['name' => 'Renan']], + * ['comment' => ['body' => 'cool', 'user' => ['name' => 'Mark']]], + * ['comment' => ['body' => 'very cool', 'user' => ['name' => 'Renan']]], * ]; * * $extracted = (new Collection($items))->match(['user.name' => 'Renan']); * * // Result will look like this when converted to array * [ - * ['comment' => ['body' => 'very cool', 'user' => ['name' => 'Renan']]] + * ['comment' => ['body' => 'very cool', 'user' => ['name' => 'Renan']]], * ] * ``` * @@ -624,21 +643,21 @@ public function match(array $conditions): CollectionInterface; * @see \Cake\Collection\CollectionInterface::match() * @return mixed */ - public function firstMatch(array $conditions); + public function firstMatch(array $conditions): mixed; /** * Returns the first result in this collection * * @return mixed The first value in the collection will be returned. */ - public function first(); + public function first(): mixed; /** * Returns the last result in this collection * * @return mixed The last value in the collection will be returned. */ - public function last(); + public function last(): mixed; /** * Returns a new collection as the result of concatenating the list of elements @@ -647,7 +666,7 @@ public function last(); * @param iterable $items Items list. * @return self */ - public function append($items): CollectionInterface; + public function append(iterable $items): CollectionInterface; /** * Append a single item creating a new collection. @@ -656,15 +675,15 @@ public function append($items): CollectionInterface; * @param mixed $key The key to append the item with. If null a key will be generated. * @return self */ - public function appendItem($item, $key = null): CollectionInterface; + public function appendItem(mixed $item, mixed $key = null): CollectionInterface; /** * Prepend a set of items to a collection creating a new collection * - * @param mixed $items The items to prepend. + * @param iterable $items The items to prepend. * @return self */ - public function prepend($items): CollectionInterface; + public function prepend(iterable $items): CollectionInterface; /** * Prepend a single item creating a new collection. @@ -673,7 +692,7 @@ public function prepend($items): CollectionInterface; * @param mixed $key The key to prepend the item with. If null a key will be generated. * @return self */ - public function prependItem($item, $key = null): CollectionInterface; + public function prependItem(mixed $item, mixed $key = null): CollectionInterface; /** * Returns a new collection where the values extracted based on a value path @@ -715,7 +734,11 @@ public function prependItem($item, $key = null): CollectionInterface; * grouping key or a function returning the key out of the provided element * @return self */ - public function combine($keyPath, $valuePath, $groupPath = null): CollectionInterface; + public function combine( + callable|string $keyPath, + callable|string $valuePath, + callable|string|null $groupPath = null, + ): CollectionInterface; /** * Returns a new collection where the values are nested in a tree-like structure @@ -728,7 +751,11 @@ public function combine($keyPath, $valuePath, $groupPath = null): CollectionInte * @param string $nestingKey The key name under which children are nested * @return self */ - public function nest($idPath, $parentPath, string $nestingKey = 'children'): CollectionInterface; + public function nest( + callable|string $idPath, + callable|string $parentPath, + string $nestingKey = 'children', + ): CollectionInterface; /** * Returns a new collection containing each of the elements found in `$values` as @@ -747,16 +774,16 @@ public function nest($idPath, $parentPath, string $nestingKey = 'children'): Col * * ``` * $items = [ - * ['comment' => ['body' => 'cool', 'user' => ['name' => 'Mark']], - * ['comment' => ['body' => 'awesome', 'user' => ['name' => 'Renan']] + * ['comment' => ['body' => 'cool', 'user' => ['name' => 'Mark']]], + * ['comment' => ['body' => 'awesome', 'user' => ['name' => 'Renan']]], * ]; * $ages = [25, 28]; * $inserted = (new Collection($items))->insert('comment.user.age', $ages); * * // Result will look like this when converted to array * [ - * ['comment' => ['body' => 'cool', 'user' => ['name' => 'Mark', 'age' => 25]], - * ['comment' => ['body' => 'awesome', 'user' => ['name' => 'Renan', 'age' => 28]] + * ['comment' => ['body' => 'cool', 'user' => ['name' => 'Mark', 'age' => 25]]], + * ['comment' => ['body' => 'awesome', 'user' => ['name' => 'Renan', 'age' => 28]]] * ]; * ``` * @@ -766,7 +793,7 @@ public function nest($idPath, $parentPath, string $nestingKey = 'children'): Col * values are matched with the elements in this collection by its positional index. * @return self */ - public function insert(string $path, $values): CollectionInterface; + public function insert(string $path, mixed $values): CollectionInterface; /** * Returns an array representation of the results @@ -891,7 +918,10 @@ public function buffered(): CollectionInterface; * or a callable function that will return the children list * @return self */ - public function listNested($order = 'desc', $nestingKey = 'children'): CollectionInterface; + public function listNested( + string|int $order = 'desc', + callable|string $nestingKey = 'children', + ): CollectionInterface; /** * Creates a new collection that when iterated will stop yielding results if @@ -926,7 +956,7 @@ public function listNested($order = 'desc', $nestingKey = 'children'): Collectio * and the value the condition against with each element will be matched. * @return self */ - public function stopWhen($condition): CollectionInterface; + public function stopWhen(callable|array $condition): CollectionInterface; /** * Creates a new collection where the items are the @@ -996,8 +1026,9 @@ public function through(callable $callback): CollectionInterface; * @param iterable ...$items The collections to zip. * @return self */ - public function zip(iterable $items): CollectionInterface; + public function zip(iterable ...$items): CollectionInterface; + // phpcs:disable /** * Combines the elements of this collection with each of the elements of the * passed iterables, using their positional index as a reference. @@ -1019,6 +1050,7 @@ public function zip(iterable $items): CollectionInterface; * @return self */ public function zipWith(iterable $items, $callback): CollectionInterface; + // phpcs:enable /** * Breaks the collection into smaller arrays of the given size. @@ -1076,9 +1108,9 @@ public function isEmpty(): bool; * losing any possible transformations. This is used mainly to remove empty * IteratorIterator wrappers that can only slowdown the iteration process. * - * @return \Traversable + * @return \Iterator */ - public function unwrap(): Traversable; + public function unwrap(): Iterator; /** * Transpose rows and columns into columns and rows diff --git a/app/vendor/cakephp/cakephp/src/Collection/CollectionTrait.php b/app/vendor/cakephp/cakephp/src/Collection/CollectionTrait.php index 7cee96969..07b0c7e7e 100644 --- a/app/vendor/cakephp/cakephp/src/Collection/CollectionTrait.php +++ b/app/vendor/cakephp/cakephp/src/Collection/CollectionTrait.php @@ -18,6 +18,7 @@ use AppendIterator; use ArrayIterator; +use BackedEnum; use Cake\Collection\Iterator\BufferedIterator; use Cake\Collection\Iterator\ExtractIterator; use Cake\Collection\Iterator\FilterIterator; @@ -29,15 +30,19 @@ use Cake\Collection\Iterator\StoppableIterator; use Cake\Collection\Iterator\TreeIterator; use Cake\Collection\Iterator\UnfoldIterator; +use Cake\Collection\Iterator\UniqueIterator; use Cake\Collection\Iterator\ZipIterator; use Countable; +use Generator; use InvalidArgumentException; +use Iterator; use LimitIterator; use LogicException; -use OuterIterator; use RecursiveIteratorIterator; -use RuntimeException; -use Traversable; +use UnitEnum; +use const SORT_ASC; +use const SORT_DESC; +use const SORT_NUMERIC; /** * Offers a handful of methods to manipulate iterators @@ -55,7 +60,7 @@ trait CollectionTrait * @param mixed ...$args Constructor arguments. * @return \Cake\Collection\CollectionInterface */ - protected function newCollection(...$args): CollectionInterface + protected function newCollection(mixed ...$args): CollectionInterface { return new Collection(...$args); } @@ -77,11 +82,7 @@ public function each(callable $callback) */ public function filter(?callable $callback = null): CollectionInterface { - if ($callback === null) { - $callback = function ($v) { - return (bool)$v; - }; - } + $callback ??= fn($v) => (bool)$v; return new FilterIterator($this->unwrap(), $callback); } @@ -89,11 +90,21 @@ public function filter(?callable $callback = null): CollectionInterface /** * @inheritDoc */ - public function reject(callable $callback): CollectionInterface + public function reject(?callable $callback = null): CollectionInterface { - return new FilterIterator($this->unwrap(), function ($key, $value, $items) use ($callback) { - return !$callback($key, $value, $items); - }); + $callback ??= fn($v) => (bool)$v; + + return new FilterIterator($this->unwrap(), fn($value, $key, $items) => !$callback($value, $key, $items)); + } + + /** + * @inheritDoc + */ + public function unique(?callable $callback = null): CollectionInterface + { + $callback ??= fn($v) => $v; + + return new UniqueIterator($this->unwrap(), $callback); } /** @@ -127,7 +138,7 @@ public function some(callable $callback): bool /** * @inheritDoc */ - public function contains($value): bool + public function contains(mixed $value): bool { foreach ($this->optimizeUnwrap() as $v) { if ($value === $v) { @@ -149,12 +160,9 @@ public function map(callable $callback): CollectionInterface /** * @inheritDoc */ - public function reduce(callable $callback, $initial = null) + public function reduce(callable $callback, mixed $initial = null): mixed { - $isFirst = false; - if (func_num_args() < 2) { - $isFirst = true; - } + $isFirst = func_num_args() < 2; $result = $initial; foreach ($this->optimizeUnwrap() as $k => $value) { @@ -172,13 +180,13 @@ public function reduce(callable $callback, $initial = null) /** * @inheritDoc */ - public function extract($path): CollectionInterface + public function extract(callable|string $path): CollectionInterface { $extractor = new ExtractIterator($this->unwrap(), $path); - if (is_string($path) && strpos($path, '{*}') !== false) { - $extractor = $extractor + if (is_string($path) && str_contains($path, '{*}')) { + return $extractor ->filter(function ($data) { - return $data !== null && ($data instanceof Traversable || is_array($data)); + return is_iterable($data); }) ->unfold(); } @@ -189,23 +197,23 @@ public function extract($path): CollectionInterface /** * @inheritDoc */ - public function max($path, int $sort = \SORT_NUMERIC) + public function max(callable|string $path, int $sort = SORT_NUMERIC): mixed { - return (new SortIterator($this->unwrap(), $path, \SORT_DESC, $sort))->first(); + return (new SortIterator($this->unwrap(), $path, SORT_DESC, $sort))->first(); } /** * @inheritDoc */ - public function min($path, int $sort = \SORT_NUMERIC) + public function min(callable|string $path, int $sort = SORT_NUMERIC): mixed { - return (new SortIterator($this->unwrap(), $path, \SORT_ASC, $sort))->first(); + return (new SortIterator($this->unwrap(), $path, SORT_ASC, $sort))->first(); } /** * @inheritDoc */ - public function avg($path = null) + public function avg(callable|string|null $path = null): float|int|null { $result = $this; if ($path !== null) { @@ -228,7 +236,7 @@ public function avg($path = null) /** * @inheritDoc */ - public function median($path = null) + public function median(callable|string|null $path = null): float|int|null { $items = $this; if ($path !== null) { @@ -254,26 +262,77 @@ public function median($path = null) /** * @inheritDoc */ - public function sortBy($path, int $order = \SORT_DESC, int $sort = \SORT_NUMERIC): CollectionInterface + public function sortBy(callable|string $path, int $order = SORT_DESC, int $sort = SORT_NUMERIC): CollectionInterface { return new SortIterator($this->unwrap(), $path, $order, $sort); } /** - * @inheritDoc + * Splits a collection into sets, grouped by the result of running each value + * through the callback. If $callback is a string instead of a callable, + * groups by the property named by $callback on each of the values. + * + * When $callback is a string it should be a property name to extract or + * a dot separated path of properties that should be followed to get the last + * one in the path. + * + * ### Example: + * + * ``` + * $items = [ + * ['id' => 1, 'name' => 'foo', 'parent_id' => 10], + * ['id' => 2, 'name' => 'bar', 'parent_id' => 11], + * ['id' => 3, 'name' => 'baz', 'parent_id' => 10], + * ]; + * + * $group = (new Collection($items))->groupBy('parent_id'); + * + * // Or + * $group = (new Collection($items))->groupBy(function ($e) { + * return $e['parent_id']; + * }); + * + * // Result will look like this when converted to array + * [ + * 10 => [ + * ['id' => 1, 'name' => 'foo', 'parent_id' => 10], + * ['id' => 3, 'name' => 'baz', 'parent_id' => 10], + * ], + * 11 => [ + * ['id' => 2, 'name' => 'bar', 'parent_id' => 11], + * ] + * ]; + * ``` + * + * @param callable|string $path The column name to use for grouping or callback that returns the value. + * or a function returning the grouping key out of the provided element + * @param bool $preserveKeys Whether to preserve the keys of the existing + * collection when the values are grouped. Defaults to false. + * @return \Cake\Collection\CollectionInterface */ - public function groupBy($path): CollectionInterface + public function groupBy(callable|string $path, bool $preserveKeys = false): CollectionInterface { $callback = $this->_propertyExtractor($path); $group = []; - foreach ($this->optimizeUnwrap() as $value) { + foreach ($this->optimizeUnwrap() as $key => $value) { $pathValue = $callback($value); if ($pathValue === null) { throw new InvalidArgumentException( 'Cannot group by path that does not exist or contains a null value. ' . - 'Use a callback to return a default value for that path.' + 'Use a callback to return a default value for that path.', ); } + if ($pathValue instanceof BackedEnum) { + $pathValue = $pathValue->value; + } elseif ($pathValue instanceof UnitEnum) { + $pathValue = $pathValue->name; + } + + if ($preserveKeys) { + $group[$pathValue][$key] = $value; + continue; + } + $group[$pathValue][] = $value; } @@ -283,7 +342,7 @@ public function groupBy($path): CollectionInterface /** * @inheritDoc */ - public function indexBy($path): CollectionInterface + public function indexBy(callable|string $path): CollectionInterface { $callback = $this->_propertyExtractor($path); $group = []; @@ -292,9 +351,15 @@ public function indexBy($path): CollectionInterface if ($pathValue === null) { throw new InvalidArgumentException( 'Cannot index by path that does not exist or contains a null value. ' . - 'Use a callback to return a default value for that path.' + 'Use a callback to return a default value for that path.', ); } + if ($pathValue instanceof BackedEnum) { + $pathValue = $pathValue->value; + } elseif ($pathValue instanceof UnitEnum) { + $pathValue = $pathValue->name; + } + $group[$pathValue] = $value; } @@ -304,19 +369,12 @@ public function indexBy($path): CollectionInterface /** * @inheritDoc */ - public function countBy($path): CollectionInterface + public function countBy(callable|string $path): CollectionInterface { $callback = $this->_propertyExtractor($path); - $mapper = function ($value, $key, $mr) use ($callback): void { - /** @var \Cake\Collection\Iterator\MapReduce $mr */ - $mr->emitIntermediate($value, $callback($value)); - }; - - $reducer = function ($values, $key, $mr): void { - /** @var \Cake\Collection\Iterator\MapReduce $mr */ - $mr->emit(count($values), $key); - }; + $mapper = fn($value, $key, MapReduce $mr) => $mr->emitIntermediate($value, $callback($value)); + $reducer = fn($values, $key, MapReduce $mr) => $mr->emit(count($values), $key); return $this->newCollection(new MapReduce($this->unwrap(), $mapper, $reducer)); } @@ -324,7 +382,7 @@ public function countBy($path): CollectionInterface /** * @inheritDoc */ - public function sumOf($path = null) + public function sumOf(callable|string|null $path = null): float|int { if ($path === null) { return array_sum($this->toList()); @@ -385,7 +443,7 @@ public function match(array $conditions): CollectionInterface /** * @inheritDoc */ - public function firstMatch(array $conditions) + public function firstMatch(array $conditions): mixed { return $this->match($conditions)->first(); } @@ -393,18 +451,20 @@ public function firstMatch(array $conditions) /** * @inheritDoc */ - public function first() + public function first(): mixed { $iterator = new LimitIterator($this, 0, 1); foreach ($iterator as $result) { return $result; } + + return null; } /** * @inheritDoc */ - public function last() + public function last(): mixed { $iterator = $this->optimizeUnwrap(); if (is_array($iterator)) { @@ -416,7 +476,6 @@ public function last() if ($count === 0) { return null; } - /** @var iterable $iterator */ $iterator = new LimitIterator($iterator, $count - 1, 1); } @@ -454,7 +513,7 @@ public function takeLast(int $length): CollectionInterface return $this->newCollection($iterator); } - $generator = function ($iterator, $length) { + $generator = function ($iterator, $length): Generator { $result = []; $bucket = 0; $offset = 0; @@ -511,7 +570,7 @@ public function takeLast(int $length): CollectionInterface $offset++; } - $offset = $offset % $length; + $offset %= $length; $head = array_slice($result, $offset); $tail = array_slice($result, 0, $offset); @@ -530,7 +589,7 @@ public function takeLast(int $length): CollectionInterface /** * @inheritDoc */ - public function append($items): CollectionInterface + public function append(iterable $items): CollectionInterface { $list = new AppendIterator(); $list->append($this->unwrap()); @@ -542,7 +601,7 @@ public function append($items): CollectionInterface /** * @inheritDoc */ - public function appendItem($item, $key = null): CollectionInterface + public function appendItem(mixed $item, mixed $key = null): CollectionInterface { if ($key !== null) { $data = [$key => $item]; @@ -556,7 +615,7 @@ public function appendItem($item, $key = null): CollectionInterface /** * @inheritDoc */ - public function prepend($items): CollectionInterface + public function prepend(mixed $items): CollectionInterface { return $this->newCollection($items)->append($this); } @@ -564,7 +623,7 @@ public function prepend($items): CollectionInterface /** * @inheritDoc */ - public function prependItem($item, $key = null): CollectionInterface + public function prependItem(mixed $item, mixed $key = null): CollectionInterface { if ($key !== null) { $data = [$key => $item]; @@ -578,8 +637,11 @@ public function prependItem($item, $key = null): CollectionInterface /** * @inheritDoc */ - public function combine($keyPath, $valuePath, $groupPath = null): CollectionInterface - { + public function combine( + callable|string $keyPath, + callable|string $valuePath, + callable|string|null $groupPath = null, + ): CollectionInterface { $options = [ 'keyPath' => $this->_propertyExtractor($keyPath), 'valuePath' => $this->_propertyExtractor($valuePath), @@ -595,10 +657,16 @@ public function combine($keyPath, $valuePath, $groupPath = null): CollectionInte if ($mapKey === null) { throw new InvalidArgumentException( 'Cannot index by path that does not exist or contains a null value. ' . - 'Use a callback to return a default value for that path.' + 'Use a callback to return a default value for that path.', ); } + if ($mapKey instanceof BackedEnum) { + $mapKey = $mapKey->value; + } elseif ($mapKey instanceof UnitEnum) { + $mapKey = $mapKey->name; + } + $mapReduce->emit($rowVal($value, $key), $mapKey); return null; @@ -608,7 +676,7 @@ public function combine($keyPath, $valuePath, $groupPath = null): CollectionInte if ($key === null) { throw new InvalidArgumentException( 'Cannot group by path that does not exist or contains a null value. ' . - 'Use a callback to return a default value for that path.' + 'Use a callback to return a default value for that path.', ); } @@ -616,13 +684,13 @@ public function combine($keyPath, $valuePath, $groupPath = null): CollectionInte if ($mapKey === null) { throw new InvalidArgumentException( 'Cannot index by path that does not exist or contains a null value. ' . - 'Use a callback to return a default value for that path.' + 'Use a callback to return a default value for that path.', ); } $mapReduce->emitIntermediate( [$mapKey => $rowVal($value, $key)], - $key + $key, ); }; @@ -640,8 +708,11 @@ public function combine($keyPath, $valuePath, $groupPath = null): CollectionInte /** * @inheritDoc */ - public function nest($idPath, $parentPath, string $nestingKey = 'children'): CollectionInterface - { + public function nest( + callable|string $idPath, + callable|string $parentPath, + string $nestingKey = 'children', + ): CollectionInterface { $parents = []; $idPath = $this->_propertyExtractor($idPath); $parentPath = $this->_propertyExtractor($parentPath); @@ -661,9 +732,8 @@ public function nest($idPath, $parentPath, string $nestingKey = 'children'): Col $isObject = is_object(current($parents)); $foundOutType = true; } - if (empty($key) || !isset($parents[$key])) { + if (!$key || !isset($parents[$key])) { foreach ($values as $id) { - /** @psalm-suppress PossiblyInvalidArgument */ $parents[$id] = $isObject ? $parents[$id] : new ArrayIterator($parents[$id], 1); $mapReduce->emit($parents[$id]); } @@ -679,8 +749,8 @@ public function nest($idPath, $parentPath, string $nestingKey = 'children'): Col }; return $this->newCollection(new MapReduce($this->unwrap(), $mapper, $reducer)) - ->map(function ($value) use (&$isObject) { - /** @var \ArrayIterator $value */ + ->map(function ($value) use ($isObject) { + /** @var \ArrayIterator|\ArrayObject $value */ return $isObject ? $value : $value->getArrayCopy(); }); } @@ -688,7 +758,7 @@ public function nest($idPath, $parentPath, string $nestingKey = 'children'): Col /** * @inheritDoc */ - public function insert(string $path, $values): CollectionInterface + public function insert(string $path, mixed $values): CollectionInterface { return new InsertIterator($this->unwrap(), $path, $values); } @@ -706,7 +776,7 @@ public function toArray(bool $keepKeys = true): array } // RecursiveIteratorIterator can return duplicate key values causing // data loss when converted into an array - if ($keepKeys && get_class($iterator) === RecursiveIteratorIterator::class) { + if ($keepKeys && $iterator::class === RecursiveIteratorIterator::class) { $keepKeys = false; } @@ -742,7 +812,7 @@ public function compile(bool $keepKeys = true): CollectionInterface */ public function lazy(): CollectionInterface { - $generator = function () { + $generator = function (): Generator { foreach ($this->unwrap() as $k => $v) { yield $k => $v; } @@ -762,8 +832,10 @@ public function buffered(): CollectionInterface /** * @inheritDoc */ - public function listNested($order = 'desc', $nestingKey = 'children'): CollectionInterface - { + public function listNested( + string|int $order = 'desc', + callable|string $nestingKey = 'children', + ): CollectionInterface { if (is_string($order)) { $order = strtolower($order); $modes = [ @@ -773,24 +845,30 @@ public function listNested($order = 'desc', $nestingKey = 'children'): Collectio ]; if (!isset($modes[$order])) { - throw new RuntimeException(sprintf( - "Invalid direction `%s` provided. Must be one of: 'desc', 'asc', 'leaves'", - $order + throw new InvalidArgumentException(sprintf( + "Invalid direction `%s` provided. Must be one of: 'desc', 'asc', 'leaves'.", + $order, )); } $order = $modes[$order]; } + assert( + $order === RecursiveIteratorIterator::LEAVES_ONLY || + $order === RecursiveIteratorIterator::SELF_FIRST || + $order === RecursiveIteratorIterator::CHILD_FIRST, + ); + return new TreeIterator( new NestIterator($this, $nestingKey), - $order + $order, ); } /** * @inheritDoc */ - public function stopWhen($condition): CollectionInterface + public function stopWhen(callable|array $condition): CollectionInterface { if (!is_callable($condition)) { $condition = $this->_createMatcherFilter($condition); @@ -804,17 +882,13 @@ public function stopWhen($condition): CollectionInterface */ public function unfold(?callable $callback = null): CollectionInterface { - if ($callback === null) { - $callback = function ($item) { - return $item; - }; - } + $callback ??= fn($v) => $v; return $this->newCollection( new RecursiveIteratorIterator( new UnfoldIterator($this->unwrap(), $callback), - RecursiveIteratorIterator::LEAVES_ONLY - ) + RecursiveIteratorIterator::LEAVES_ONLY, + ), ); } @@ -831,9 +905,9 @@ public function through(callable $callback): CollectionInterface /** * @inheritDoc */ - public function zip(iterable $items): CollectionInterface + public function zip(iterable ...$items): CollectionInterface { - return new ZipIterator(array_merge([$this->unwrap()], func_get_args())); + return new ZipIterator(array_merge([$this->unwrap()], $items)); } /** @@ -848,6 +922,7 @@ public function zipWith(iterable $items, $callback): CollectionInterface $items = [$items]; } + /** @var callable $callback */ return new ZipIterator(array_merge([$this->unwrap()], $items), $callback); } @@ -856,7 +931,7 @@ public function zipWith(iterable $items, $callback): CollectionInterface */ public function chunk(int $chunkSize): CollectionInterface { - return $this->map(function ($v, $k, $iterator) use ($chunkSize) { + return $this->map(function ($v, $k, Iterator $iterator) use ($chunkSize) { $values = [$v]; for ($i = 1; $i < $chunkSize; $i++) { $iterator->next(); @@ -875,7 +950,7 @@ public function chunk(int $chunkSize): CollectionInterface */ public function chunkWithKeys(int $chunkSize, bool $keepKeys = true): CollectionInterface { - return $this->map(function ($v, $k, $iterator) use ($chunkSize, $keepKeys) { + return $this->map(function ($v, $k, Iterator $iterator) use ($chunkSize, $keepKeys) { $key = 0; if ($keepKeys) { $key = $k; @@ -902,6 +977,7 @@ public function chunkWithKeys(int $chunkSize, bool $keepKeys = true): Collection */ public function isEmpty(): bool { + // phpcs:ignore SlevomatCodingStandard.Variables.UnusedVariable.UnusedVariable foreach ($this as $el) { return false; } @@ -912,20 +988,18 @@ public function isEmpty(): bool /** * @inheritDoc */ - public function unwrap(): Traversable + public function unwrap(): Iterator { $iterator = $this; - while ( - get_class($iterator) === Collection::class - && $iterator instanceof OuterIterator - ) { + while ($iterator::class === Collection::class) { $iterator = $iterator->getInnerIterator(); } if ($iterator !== $this && $iterator instanceof CollectionInterface) { - $iterator = $iterator->unwrap(); + return $iterator->unwrap(); } + /** @var \Iterator */ return $iterator; } @@ -1040,15 +1114,14 @@ public function countKeys(): int * Unwraps this iterator and returns the simplest * traversable that can be used for getting the data out * - * @return iterable + * @return \Iterator|array */ - protected function optimizeUnwrap(): iterable + protected function optimizeUnwrap(): Iterator|array { - /** @var \ArrayObject $iterator */ $iterator = $this->unwrap(); - if (get_class($iterator) === ArrayIterator::class) { - $iterator = $iterator->getArrayCopy(); + if ($iterator::class === ArrayIterator::class) { + return $iterator->getArrayCopy(); } return $iterator; diff --git a/app/vendor/cakephp/cakephp/src/Collection/ExtractTrait.php b/app/vendor/cakephp/cakephp/src/Collection/ExtractTrait.php index c9b69a58e..dadff648a 100644 --- a/app/vendor/cakephp/cakephp/src/Collection/ExtractTrait.php +++ b/app/vendor/cakephp/cakephp/src/Collection/ExtractTrait.php @@ -16,8 +16,8 @@ */ namespace Cake\Collection; +use ArrayAccess; use Closure; -use Traversable; /** * Provides utility protected methods for extracting a property or column @@ -32,23 +32,25 @@ trait ExtractTrait * @param callable|string $path A dot separated path of column to follow * so that the final one can be returned or a callable that will take care * of doing that. - * @return callable + * @return \Closure */ - protected function _propertyExtractor($path): callable + protected function _propertyExtractor(callable|string $path): Closure { if (!is_string($path)) { - return $path; + return $path(...); } $parts = explode('.', $path); - if (strpos($path, '{*}') !== false) { - return function ($element) use ($parts) { - return $this->_extract($element, $parts); - }; + if (str_contains($path, '{*}')) { + return fn($element) => $this->_extract($element, $parts); } return function ($element) use ($parts) { + if (!is_array($element) && !$element instanceof ArrayAccess) { + return null; + } + return $this->_simpleExtract($element, $parts); }; } @@ -58,11 +60,11 @@ protected function _propertyExtractor($path): callable * by iterating over the column names contained in $path. * It will return arrays for elements in represented with `{*}` * - * @param \ArrayAccess|array $data Data. + * @param \ArrayAccess|array $data Data. * @param array $parts Path to extract from. * @return mixed */ - protected function _extract($data, array $parts) + protected function _extract(ArrayAccess|array $data, array $parts): mixed { $value = null; $collectionTransform = false; @@ -75,10 +77,7 @@ protected function _extract($data, array $parts) if ( $collectionTransform && - !( - $data instanceof Traversable || - is_array($data) - ) + !is_iterable($data) ) { return null; } @@ -104,11 +103,11 @@ protected function _extract($data, array $parts) * Returns a column from $data that can be extracted * by iterating over the column names contained in $path * - * @param \ArrayAccess|array $data Data. + * @param \ArrayAccess|array $data Data. * @param array $parts Path to extract from. * @return mixed */ - protected function _simpleExtract($data, array $parts) + protected function _simpleExtract(ArrayAccess|array $data, array $parts): mixed { $value = null; foreach ($parts as $column) { @@ -136,7 +135,7 @@ protected function _createMatcherFilter(array $conditions): Closure $matchers = []; foreach ($conditions as $property => $value) { $extractor = $this->_propertyExtractor($property); - $matchers[] = function ($v) use ($extractor, $value) { + $matchers[] = function ($v) use ($extractor, $value): bool { return $extractor($v) == $value; }; } diff --git a/app/vendor/cakephp/cakephp/src/Collection/Iterator/BufferedIterator.php b/app/vendor/cakephp/cakephp/src/Collection/Iterator/BufferedIterator.php index cdf1c4ef6..079a4a320 100644 --- a/app/vendor/cakephp/cakephp/src/Collection/Iterator/BufferedIterator.php +++ b/app/vendor/cakephp/cakephp/src/Collection/Iterator/BufferedIterator.php @@ -17,43 +17,41 @@ namespace Cake\Collection\Iterator; use Cake\Collection\Collection; -use Countable; -use Serializable; use SplDoublyLinkedList; /** * Creates an iterator from another iterator that will keep the results of the inner * iterator in memory, so that results don't have to be re-calculated. */ -class BufferedIterator extends Collection implements Countable, Serializable +class BufferedIterator extends Collection { /** * The in-memory cache containing results from previous iterators * - * @var \SplDoublyLinkedList + * @var \SplDoublyLinkedList */ - protected $_buffer; + protected SplDoublyLinkedList $_buffer; /** * Points to the next record number that should be fetched * * @var int */ - protected $_index = 0; + protected int $_index = 0; /** * Last record fetched from the inner iterator * * @var mixed */ - protected $_current; + protected mixed $_current; /** * Last key obtained from the inner iterator * * @var mixed */ - protected $_key; + protected mixed $_key; /** * Whether the internal iterator's rewind method was already @@ -61,14 +59,14 @@ class BufferedIterator extends Collection implements Countable, Serializable * * @var bool */ - protected $_started = false; + protected bool $_started = false; /** * Whether the internal iterator has reached its end. * * @var bool */ - protected $_finished = false; + protected bool $_finished = false; /** * Maintains an in-memory cache of the results yielded by the internal @@ -87,8 +85,7 @@ public function __construct(iterable $items) * * @return mixed */ - #[\ReturnTypeWillChange] - public function key() + public function key(): mixed { return $this->_key; } @@ -98,8 +95,7 @@ public function key() * * @return mixed */ - #[\ReturnTypeWillChange] - public function current() + public function current(): mixed { return $this->_current; } @@ -171,7 +167,7 @@ public function next(): void } /** - * Returns the number or items in this collection + * Returns the number of items in this collection. * * @return int */ @@ -188,21 +184,6 @@ public function count(): int return $this->_buffer->count(); } - /** - * Returns a string representation of this object that can be used - * to reconstruct it - * - * @return string - */ - public function serialize(): string - { - if (!$this->_finished) { - $this->count(); - } - - return serialize($this->_buffer); - } - /** * Magic method used for serializing the iterator instance. * @@ -217,20 +198,6 @@ public function __serialize(): array return iterator_to_array($this->_buffer); } - /** - * Unserializes the passed string and rebuilds the BufferedIterator instance - * - * @param string $collection The serialized buffer iterator - * @return void - */ - public function unserialize($collection): void - { - $this->__construct([]); - $this->_buffer = unserialize($collection); - $this->_started = true; - $this->_finished = true; - } - /** * Magic method used to rebuild the iterator instance. * diff --git a/app/vendor/cakephp/cakephp/src/Collection/Iterator/ExtractIterator.php b/app/vendor/cakephp/cakephp/src/Collection/Iterator/ExtractIterator.php index f5ba7b97b..4c5f161c0 100644 --- a/app/vendor/cakephp/cakephp/src/Collection/Iterator/ExtractIterator.php +++ b/app/vendor/cakephp/cakephp/src/Collection/Iterator/ExtractIterator.php @@ -19,7 +19,7 @@ use ArrayIterator; use Cake\Collection\Collection; use Cake\Collection\CollectionInterface; -use Traversable; +use Iterator; /** * Creates an iterator from another iterator that extract the requested column @@ -45,10 +45,10 @@ class ExtractIterator extends Collection * * ``` * $items = [ - * ['comment' => ['body' => 'cool', 'user' => ['name' => 'Mark']], - * ['comment' => ['body' => 'very cool', 'user' => ['name' => 'Renan']] + * ['comment' => ['body' => 'cool', 'user' => ['name' => 'Mark']]], + * ['comment' => ['body' => 'very cool', 'user' => ['name' => 'Renan']]], * ]; - * $extractor = new ExtractIterator($items, 'comment.user.name''); + * $extractor = new ExtractIterator($items, 'comment.user.name'); * ``` * * @param iterable $items The list of values to iterate @@ -56,7 +56,7 @@ class ExtractIterator extends Collection * so that the final one can be returned or a callable that will take care * of doing that. */ - public function __construct(iterable $items, $path) + public function __construct(iterable $items, callable|string $path) { $this->_extractor = $this->_propertyExtractor($path); parent::__construct($items); @@ -68,8 +68,7 @@ public function __construct(iterable $items, $path) * * @return mixed */ - #[\ReturnTypeWillChange] - public function current() + public function current(): mixed { $extractor = $this->_extractor; @@ -79,7 +78,7 @@ public function current() /** * @inheritDoc */ - public function unwrap(): Traversable + public function unwrap(): Iterator { $iterator = $this->getInnerIterator(); @@ -87,7 +86,7 @@ public function unwrap(): Traversable $iterator = $iterator->unwrap(); } - if (get_class($iterator) !== ArrayIterator::class) { + if ($iterator::class !== ArrayIterator::class) { return $this; } diff --git a/app/vendor/cakephp/cakephp/src/Collection/Iterator/FilterIterator.php b/app/vendor/cakephp/cakephp/src/Collection/Iterator/FilterIterator.php index 461dffebe..b72721b88 100644 --- a/app/vendor/cakephp/cakephp/src/Collection/Iterator/FilterIterator.php +++ b/app/vendor/cakephp/cakephp/src/Collection/Iterator/FilterIterator.php @@ -21,7 +21,6 @@ use Cake\Collection\CollectionInterface; use CallbackFilterIterator; use Iterator; -use Traversable; /** * Creates a filtered iterator from another iterator. The filtering is done by @@ -45,10 +44,10 @@ class FilterIterator extends Collection * in the current iteration, the key of the element and the passed $items iterator * as arguments, in that order. * - * @param \Traversable|array $items The items to be filtered. + * @param iterable $items The items to be filtered. * @param callable $callback Callback. */ - public function __construct($items, callable $callback) + public function __construct(iterable $items, callable $callback) { if (!$items instanceof Iterator) { $items = new Collection($items); @@ -62,7 +61,7 @@ public function __construct($items, callable $callback) /** * @inheritDoc */ - public function unwrap(): Traversable + public function unwrap(): Iterator { /** @var \IteratorIterator $filter */ $filter = $this->getInnerIterator(); @@ -72,7 +71,7 @@ public function unwrap(): Traversable $iterator = $iterator->unwrap(); } - if (get_class($iterator) !== ArrayIterator::class) { + if ($iterator::class !== ArrayIterator::class) { return $filter; } diff --git a/app/vendor/cakephp/cakephp/src/Collection/Iterator/InsertIterator.php b/app/vendor/cakephp/cakephp/src/Collection/Iterator/InsertIterator.php index 8bcffc449..190128f82 100644 --- a/app/vendor/cakephp/cakephp/src/Collection/Iterator/InsertIterator.php +++ b/app/vendor/cakephp/cakephp/src/Collection/Iterator/InsertIterator.php @@ -31,14 +31,14 @@ class InsertIterator extends Collection * * @var \Cake\Collection\Collection */ - protected $_values; + protected Collection $_values; /** * Holds whether the values collection is still valid. (has more records) * * @var bool */ - protected $_validValues = true; + protected bool $_validValues = true; /** * An array containing each of the properties to be traversed to reach the @@ -46,14 +46,14 @@ class InsertIterator extends Collection * * @var array */ - protected $_path; + protected array $_path; /** * The property name to which values will be assigned * * @var string */ - protected $_target; + protected string $_target; /** * Constructs a new collection that will dynamically add properties to it out of @@ -101,8 +101,7 @@ public function next(): void * * @return mixed */ - #[\ReturnTypeWillChange] - public function current() + public function current(): mixed { $row = parent::current(); diff --git a/app/vendor/cakephp/cakephp/src/Collection/Iterator/MapReduce.php b/app/vendor/cakephp/cakephp/src/Collection/Iterator/MapReduce.php index 0ea3368fe..feb56e076 100644 --- a/app/vendor/cakephp/cakephp/src/Collection/Iterator/MapReduce.php +++ b/app/vendor/cakephp/cakephp/src/Collection/Iterator/MapReduce.php @@ -31,33 +31,32 @@ class MapReduce implements IteratorAggregate { /** - * Holds the shuffled results that were emitted from the map - * phase + * Holds the shuffled results emitted from the map phase * * @var array */ - protected $_intermediate = []; + protected array $_intermediate = []; /** * Holds the results as emitted during the reduce phase * * @var array */ - protected $_result = []; + protected array $_result = []; /** * Whether the Map-Reduce routine has been executed already on the data * * @var bool */ - protected $_executed = false; + protected bool $_executed = false; /** * Holds the original data that needs to be processed * - * @var \Traversable + * @var iterable */ - protected $_data; + protected iterable $_data; /** * A callable that will be executed for each record in the original data @@ -79,7 +78,7 @@ class MapReduce implements IteratorAggregate * * @var int */ - protected $_counter = 0; + protected int $_counter = 0; /** * Constructor @@ -107,7 +106,7 @@ class MapReduce implements IteratorAggregate * ['odd' => [1, 3, 5], 'even' => [2, 4]] * ``` * - * @param \Traversable $data the original data to be processed + * @param iterable $data The original data to be processed. * @param callable $mapper the mapper callback. This function will receive 3 arguments. * The first one is the current value, second the current results key and third is * this class instance so you can call the result emitters. @@ -116,7 +115,7 @@ class MapReduce implements IteratorAggregate * of the bucket that was created during the mapping phase and third one is an * instance of this class. */ - public function __construct(Traversable $data, callable $mapper, ?callable $reducer = null) + public function __construct(iterable $data, callable $mapper, ?callable $reducer = null) { $this->_data = $data; $this->_mapper = $mapper; @@ -139,16 +138,23 @@ public function getIterator(): Traversable } /** - * Appends a new record to the bucket labelled with $key, usually as a result + * Appends a new record to the bucket labeled with $key, usually as a result * of mapping a single record from the original data. * * @param mixed $val The record itself to store in the bucket * @param mixed $bucket the name of the bucket where to put the record + * @param mixed $key An optional key to assign to the value * @return void */ - public function emitIntermediate($val, $bucket): void + public function emitIntermediate(mixed $val, mixed $bucket, mixed $key = null): void { - $this->_intermediate[$bucket][] = $val; + if ($key === null) { + $this->_intermediate[$bucket][] = $val; + + return; + } + + $this->_intermediate[$bucket][$key] = $val; } /** @@ -159,7 +165,7 @@ public function emitIntermediate($val, $bucket): void * @param mixed $key and optional key to assign to the value * @return void */ - public function emit($val, $key = null): void + public function emit(mixed $val, mixed $key = null): void { $this->_result[$key ?? $this->_counter] = $val; $this->_counter++; @@ -181,14 +187,15 @@ protected function _execute(): void $mapper($val, $key, $this); } - if (!empty($this->_intermediate) && empty($this->_reducer)) { + if ($this->_intermediate && $this->_reducer === null) { throw new LogicException('No reducer function was provided'); } - /** @var callable $reducer */ $reducer = $this->_reducer; - foreach ($this->_intermediate as $key => $list) { - $reducer($list, $key, $this); + if ($reducer !== null) { + foreach ($this->_intermediate as $key => $list) { + $reducer($list, $key, $this); + } } $this->_intermediate = []; $this->_executed = true; diff --git a/app/vendor/cakephp/cakephp/src/Collection/Iterator/NestIterator.php b/app/vendor/cakephp/cakephp/src/Collection/Iterator/NestIterator.php index 831bc46fa..8e5b8abd1 100644 --- a/app/vendor/cakephp/cakephp/src/Collection/Iterator/NestIterator.php +++ b/app/vendor/cakephp/cakephp/src/Collection/Iterator/NestIterator.php @@ -40,9 +40,9 @@ class NestIterator extends Collection implements RecursiveIterator * * @param iterable $items Collection items. * @param callable|string $nestKey the property that contains the nested items - * If a callable is passed, it should return the childrens for the passed item + * If a callable is passed, it should return the children for the passed item */ - public function __construct(iterable $items, $nestKey) + public function __construct(iterable $items, callable|string $nestKey) { parent::__construct($items); $this->_nestKey = $nestKey; @@ -51,7 +51,7 @@ public function __construct(iterable $items, $nestKey) /** * Returns a traversable containing the children for the current item * - * @return \RecursiveIterator + * @return \RecursiveIterator */ public function getChildren(): RecursiveIterator { @@ -72,7 +72,7 @@ public function hasChildren(): bool $children = $property($this->current()); if (is_array($children)) { - return !empty($children); + return $children !== []; } return $children instanceof Traversable; diff --git a/app/vendor/cakephp/cakephp/src/Collection/Iterator/NoChildrenIterator.php b/app/vendor/cakephp/cakephp/src/Collection/Iterator/NoChildrenIterator.php index 53f5ceb79..23c7ce68c 100644 --- a/app/vendor/cakephp/cakephp/src/Collection/Iterator/NoChildrenIterator.php +++ b/app/vendor/cakephp/cakephp/src/Collection/Iterator/NoChildrenIterator.php @@ -41,7 +41,7 @@ public function hasChildren(): bool /** * Returns a self instance without any elements. * - * @return \RecursiveIterator + * @return \RecursiveIterator */ public function getChildren(): RecursiveIterator { diff --git a/app/vendor/cakephp/cakephp/src/Collection/Iterator/ReplaceIterator.php b/app/vendor/cakephp/cakephp/src/Collection/Iterator/ReplaceIterator.php index 285757a68..15d39c953 100644 --- a/app/vendor/cakephp/cakephp/src/Collection/Iterator/ReplaceIterator.php +++ b/app/vendor/cakephp/cakephp/src/Collection/Iterator/ReplaceIterator.php @@ -19,6 +19,7 @@ use ArrayIterator; use Cake\Collection\Collection; use Cake\Collection\CollectionInterface; +use Iterator; use Traversable; /** @@ -39,7 +40,7 @@ class ReplaceIterator extends Collection * * @var \Traversable */ - protected $_innerIterator; + protected Traversable $_innerIterator; /** * Creates an iterator from another iterator that will modify each of the values @@ -65,18 +66,15 @@ public function __construct(iterable $items, callable $callback) * * @return mixed */ - #[\ReturnTypeWillChange] - public function current() + public function current(): mixed { - $callback = $this->_callback; - - return $callback(parent::current(), $this->key(), $this->_innerIterator); + return ($this->_callback)(parent::current(), $this->key(), $this->_innerIterator); } /** * @inheritDoc */ - public function unwrap(): Traversable + public function unwrap(): Iterator { $iterator = $this->_innerIterator; @@ -84,7 +82,7 @@ public function unwrap(): Traversable $iterator = $iterator->unwrap(); } - if (get_class($iterator) !== ArrayIterator::class) { + if ($iterator::class !== ArrayIterator::class) { return $this; } diff --git a/app/vendor/cakephp/cakephp/src/Collection/Iterator/SortIterator.php b/app/vendor/cakephp/cakephp/src/Collection/Iterator/SortIterator.php index a9dacec54..4160a4bc4 100644 --- a/app/vendor/cakephp/cakephp/src/Collection/Iterator/SortIterator.php +++ b/app/vendor/cakephp/cakephp/src/Collection/Iterator/SortIterator.php @@ -16,9 +16,13 @@ */ namespace Cake\Collection\Iterator; +use Cake\Chronos\ChronosDate; +use Cake\Chronos\ChronosTime; use Cake\Collection\Collection; use DateTimeInterface; -use Traversable; +use Iterator; +use const SORT_DESC; +use const SORT_NUMERIC; /** * An iterator that will return the passed items in order. The order is given by @@ -59,8 +63,12 @@ class SortIterator extends Collection * @param int $type the type of comparison to perform, either SORT_STRING * SORT_NUMERIC or SORT_NATURAL */ - public function __construct(iterable $items, $callback, int $dir = \SORT_DESC, int $type = \SORT_NUMERIC) - { + public function __construct( + iterable $items, + callable|string $callback, + int $dir = SORT_DESC, + int $type = SORT_NUMERIC, + ) { if (!is_array($items)) { $items = iterator_to_array((new Collection($items))->unwrap(), false); } @@ -69,7 +77,11 @@ public function __construct(iterable $items, $callback, int $dir = \SORT_DESC, i $results = []; foreach ($items as $key => $val) { $val = $callback($val); - if ($val instanceof DateTimeInterface && $type === \SORT_NUMERIC) { + $isDateTime = + $val instanceof ChronosDate || + $val instanceof ChronosTime || + $val instanceof DateTimeInterface; + if ($isDateTime && $type === SORT_NUMERIC) { $val = $val->format('U'); } $results[$key] = $val; @@ -86,10 +98,11 @@ public function __construct(iterable $items, $callback, int $dir = \SORT_DESC, i /** * {@inheritDoc} * - * @return \Traversable + * @return \Iterator */ - public function unwrap(): Traversable + public function unwrap(): Iterator { + /** @var \Iterator */ return $this->getInnerIterator(); } } diff --git a/app/vendor/cakephp/cakephp/src/Collection/Iterator/StoppableIterator.php b/app/vendor/cakephp/cakephp/src/Collection/Iterator/StoppableIterator.php index d79de6788..44225c6bf 100644 --- a/app/vendor/cakephp/cakephp/src/Collection/Iterator/StoppableIterator.php +++ b/app/vendor/cakephp/cakephp/src/Collection/Iterator/StoppableIterator.php @@ -19,6 +19,7 @@ use ArrayIterator; use Cake\Collection\Collection; use Cake\Collection\CollectionInterface; +use Iterator; use Traversable; /** @@ -43,7 +44,7 @@ class StoppableIterator extends Collection * * @var \Traversable */ - protected $_innerIterator; + protected Traversable $_innerIterator; /** * Creates an iterator that can be stopped based on a condition provided by a callback. @@ -86,7 +87,7 @@ public function valid(): bool /** * @inheritDoc */ - public function unwrap(): Traversable + public function unwrap(): Iterator { $iterator = $this->_innerIterator; @@ -94,7 +95,7 @@ public function unwrap(): Traversable $iterator = $iterator->unwrap(); } - if (get_class($iterator) !== ArrayIterator::class) { + if ($iterator::class !== ArrayIterator::class) { return $this; } diff --git a/app/vendor/cakephp/cakephp/src/Collection/Iterator/TreeIterator.php b/app/vendor/cakephp/cakephp/src/Collection/Iterator/TreeIterator.php index e8072db31..ad8a9fb72 100644 --- a/app/vendor/cakephp/cakephp/src/Collection/Iterator/TreeIterator.php +++ b/app/vendor/cakephp/cakephp/src/Collection/Iterator/TreeIterator.php @@ -35,20 +35,23 @@ class TreeIterator extends RecursiveIteratorIterator implements CollectionInterf * The iteration mode * * @var int + * @phpstan-var \RecursiveIteratorIterator::LEAVES_ONLY|\RecursiveIteratorIterator::SELF_FIRST|\RecursiveIteratorIterator::CHILD_FIRST */ - protected $_mode; + protected int $_mode; /** * Constructor * - * @param \RecursiveIterator $items The iterator to flatten. + * @param \RecursiveIterator $items The iterator to flatten. * @param int $mode Iterator mode. * @param int $flags Iterator flags. + * @phpstan-param \RecursiveIteratorIterator::LEAVES_ONLY|\RecursiveIteratorIterator::SELF_FIRST|\RecursiveIteratorIterator::CHILD_FIRST $mode + * @phpstan-param \RecursiveIteratorIterator::LEAVES_ONLY|\RecursiveIteratorIterator::CATCH_GET_CHILD $flags */ public function __construct( RecursiveIterator $items, int $mode = RecursiveIteratorIterator::SELF_FIRST, - int $flags = 0 + int $flags = 0, ) { parent::__construct($items, $mode, $flags); $this->_mode = $mode; @@ -92,21 +95,27 @@ public function __construct( * their depth in the tree * @return \Cake\Collection\Iterator\TreePrinter */ - public function printer($valuePath, $keyPath = null, $spacer = '__') - { + public function printer( + callable|string $valuePath, + callable|string|null $keyPath = null, + string $spacer = '__', + ): TreePrinter { if (!$keyPath) { $counter = 0; - $keyPath = function () use (&$counter) { + $keyPath = function () use (&$counter): int { return $counter++; }; } + /** @var \RecursiveIterator $iterator */ + $iterator = $this->getInnerIterator(); + return new TreePrinter( - $this->getInnerIterator(), + $iterator, $valuePath, $keyPath, $spacer, - $this->_mode + $this->_mode, ); } } diff --git a/app/vendor/cakephp/cakephp/src/Collection/Iterator/TreePrinter.php b/app/vendor/cakephp/cakephp/src/Collection/Iterator/TreePrinter.php index 0942b0f18..8a4c4bc2c 100644 --- a/app/vendor/cakephp/cakephp/src/Collection/Iterator/TreePrinter.php +++ b/app/vendor/cakephp/cakephp/src/Collection/Iterator/TreePrinter.php @@ -50,19 +50,19 @@ class TreePrinter extends RecursiveIteratorIterator implements CollectionInterfa * * @var mixed */ - protected $_current; + protected mixed $_current = null; /** * The string to use for prefixing the values according to their depth in the tree. * * @var string */ - protected $_spacer; + protected string $_spacer; /** * Constructor * - * @param \RecursiveIterator $items The iterator to flatten. + * @param \RecursiveIterator $items The iterator to flatten. * @param callable|string $valuePath The property to extract or a callable to return * the display value. * @param callable|string $keyPath The property to use as iteration key or a @@ -70,13 +70,14 @@ class TreePrinter extends RecursiveIteratorIterator implements CollectionInterfa * @param string $spacer The string to use for prefixing the values according to * their depth in the tree. * @param int $mode Iterator mode. + * @phpstan-param \RecursiveIteratorIterator::LEAVES_ONLY|\RecursiveIteratorIterator::SELF_FIRST|\RecursiveIteratorIterator::CHILD_FIRST $mode */ public function __construct( RecursiveIterator $items, - $valuePath, - $keyPath, + callable|string $valuePath, + callable|string $keyPath, string $spacer, - int $mode = RecursiveIteratorIterator::SELF_FIRST + int $mode = RecursiveIteratorIterator::SELF_FIRST, ) { parent::__construct($items, $mode); $this->_value = $this->_propertyExtractor($valuePath); @@ -89,8 +90,7 @@ public function __construct( * * @return mixed */ - #[\ReturnTypeWillChange] - public function key() + public function key(): mixed { $extractor = $this->_key; @@ -127,7 +127,7 @@ public function next(): void * * @return mixed */ - protected function _fetchCurrent() + protected function _fetchCurrent(): mixed { if ($this->_current !== null) { return $this->_current; diff --git a/app/vendor/cakephp/cakephp/src/Collection/Iterator/UnfoldIterator.php b/app/vendor/cakephp/cakephp/src/Collection/Iterator/UnfoldIterator.php index db284c1aa..8827460ff 100644 --- a/app/vendor/cakephp/cakephp/src/Collection/Iterator/UnfoldIterator.php +++ b/app/vendor/cakephp/cakephp/src/Collection/Iterator/UnfoldIterator.php @@ -44,7 +44,7 @@ class UnfoldIterator extends IteratorIterator implements RecursiveIterator * * @var \Traversable */ - protected $_innerIterator; + protected Traversable $_innerIterator; /** * Creates the iterator that will generate child iterators from each of the @@ -77,7 +77,7 @@ public function hasChildren(): bool * Returns an iterator containing the items generated by transforming * the current value with the callable function. * - * @return \RecursiveIterator + * @return \RecursiveIterator */ public function getChildren(): RecursiveIterator { diff --git a/app/vendor/cakephp/cakephp/src/Collection/Iterator/UniqueIterator.php b/app/vendor/cakephp/cakephp/src/Collection/Iterator/UniqueIterator.php new file mode 100644 index 000000000..49e7e4449 --- /dev/null +++ b/app/vendor/cakephp/cakephp/src/Collection/Iterator/UniqueIterator.php @@ -0,0 +1,52 @@ + $v) { + $compareValue = $callback($v, $k); + if (!in_array($compareValue, $uniqueValues, true)) { + $unique[$k] = $v; + $uniqueValues[] = $compareValue; + } + } + + parent::__construct($unique); + } +} diff --git a/app/vendor/cakephp/cakephp/src/Collection/Iterator/ZipIterator.php b/app/vendor/cakephp/cakephp/src/Collection/Iterator/ZipIterator.php index e032faf74..0288455c0 100644 --- a/app/vendor/cakephp/cakephp/src/Collection/Iterator/ZipIterator.php +++ b/app/vendor/cakephp/cakephp/src/Collection/Iterator/ZipIterator.php @@ -20,7 +20,6 @@ use Cake\Collection\CollectionInterface; use Cake\Collection\CollectionTrait; use MultipleIterator; -use Serializable; /** * Creates an iterator that returns elements grouped in pairs @@ -44,10 +43,15 @@ * $iterator->toList(); // Returns [4, 6] * ``` */ -class ZipIterator extends MultipleIterator implements CollectionInterface, Serializable +class ZipIterator implements CollectionInterface { use CollectionTrait; + /** + * @var \MultipleIterator + */ + protected MultipleIterator $multipleIterator; + /** * The function to use for zipping items together * @@ -60,7 +64,7 @@ class ZipIterator extends MultipleIterator implements CollectionInterface, Seria * * @var array */ - protected $_iterators = []; + protected array $_iterators = []; /** * Creates the iterator to merge together the values by for all the passed @@ -71,16 +75,16 @@ class ZipIterator extends MultipleIterator implements CollectionInterface, Seria */ public function __construct(array $sets, ?callable $callable = null) { - $sets = array_map(function ($items) { - return (new Collection($items))->unwrap(); - }, $sets); + $this->multipleIterator = new MultipleIterator( + MultipleIterator::MIT_NEED_ALL | MultipleIterator::MIT_KEYS_NUMERIC, + ); $this->_callback = $callable; - parent::__construct(MultipleIterator::MIT_NEED_ALL | MultipleIterator::MIT_KEYS_NUMERIC); foreach ($sets as $set) { - $this->_iterators[] = $set; - $this->attachIterator($set); + $iterator = (new Collection($set))->unwrap(); + $this->_iterators[] = $iterator; + $this->multipleIterator->attachIterator($iterator); } } @@ -88,52 +92,66 @@ public function __construct(array $sets, ?callable $callable = null) * Returns the value resulting out of zipping all the elements for all the * iterators with the same positional index. * - * @return array + * @return mixed */ - #[\ReturnTypeWillChange] - public function current() + public function current(): mixed { - if ($this->_callback === null) { - return parent::current(); + $current = $this->multipleIterator->current(); + if ($this->_callback) { + return call_user_func_array($this->_callback, $current); } - return call_user_func_array($this->_callback, parent::current()); + return $current; } /** - * Returns a string representation of this object that can be used - * to reconstruct it + * Implements Iterator::key(). * - * @return string + * @return mixed */ - public function serialize(): string + public function key(): mixed { - return serialize($this->_iterators); + return $this->multipleIterator->key(); } /** - * Magic method used for serializing the iterator instance. + * Implements Iterator::next(). * - * @return array + * @return void */ - public function __serialize(): array + public function next(): void { - return $this->_iterators; + $this->multipleIterator->next(); } /** - * Unserializes the passed string and rebuilds the ZipIterator instance + * Implements Iterator::rewind(). * - * @param string $iterators The serialized iterators * @return void */ - public function unserialize($iterators): void + public function rewind(): void { - parent::__construct(MultipleIterator::MIT_NEED_ALL | MultipleIterator::MIT_KEYS_NUMERIC); - $this->_iterators = unserialize($iterators); - foreach ($this->_iterators as $it) { - $this->attachIterator($it); - } + $this->multipleIterator->rewind(); + } + + /** + * Implements Iterator::valid(). + * + * @return bool + */ + public function valid(): bool + { + return $this->multipleIterator->valid(); + } + + /** + * Magic method used for serializing the iterator instance. + * + * @return array + */ + public function __serialize(): array + { + return $this->_iterators; } /** @@ -144,11 +162,13 @@ public function unserialize($iterators): void */ public function __unserialize(array $data): void { - parent::__construct(MultipleIterator::MIT_NEED_ALL | MultipleIterator::MIT_KEYS_NUMERIC); + $this->multipleIterator = new MultipleIterator( + MultipleIterator::MIT_NEED_ALL | MultipleIterator::MIT_KEYS_NUMERIC, + ); $this->_iterators = $data; foreach ($this->_iterators as $it) { - $this->attachIterator($it); + $this->multipleIterator->attachIterator($it); } } } diff --git a/app/vendor/cakephp/cakephp/src/Collection/README.md b/app/vendor/cakephp/cakephp/src/Collection/README.md index d0de5df9a..7b87ad3fc 100644 --- a/app/vendor/cakephp/cakephp/src/Collection/README.md +++ b/app/vendor/cakephp/cakephp/src/Collection/README.md @@ -28,4 +28,4 @@ you have in your application as well. ## Documentation -Please make sure you check the [official documentation](https://book.cakephp.org/4/en/core-libraries/collections.html) +Please make sure you check the [official documentation](https://book.cakephp.org/5/en/core-libraries/collections.html) diff --git a/app/vendor/cakephp/cakephp/src/Collection/composer.json b/app/vendor/cakephp/cakephp/src/Collection/composer.json index 16ec80218..aa4fe5cae 100644 --- a/app/vendor/cakephp/cakephp/src/Collection/composer.json +++ b/app/vendor/cakephp/cakephp/src/Collection/composer.json @@ -23,7 +23,7 @@ "source": "https://github.com/cakephp/collection" }, "require": { - "php": ">=7.4.0" + "php": ">=8.1" }, "autoload": { "psr-4": { @@ -32,5 +32,10 @@ "files": [ "functions.php" ] + }, + "extra": { + "branch-alias": { + "dev-5.x": "5.2.x-dev" + } } } diff --git a/app/vendor/cakephp/cakephp/src/Collection/functions.php b/app/vendor/cakephp/cakephp/src/Collection/functions.php index b3bbfb9eb..e84369b1c 100644 --- a/app/vendor/cakephp/cakephp/src/Collection/functions.php +++ b/app/vendor/cakephp/cakephp/src/Collection/functions.php @@ -1,7 +1,7 @@ setDescription('Clear all data in a single cache engine') + ->setDescription(static::getDescription()) ->addArgument('engine', [ 'help' => 'The cache engine to clear.' . 'For example, `cake cache clear _cake_model_` will clear the model cache.' . @@ -77,9 +84,6 @@ public function execute(Arguments $args, ConsoleIo $io): ?int if ($engine instanceof ApcuEngine) { $io->warning("ApcuEngine detected: Cleared {$name} CLI cache successfully " . "but {$name} web cache must be cleared separately."); - } elseif ($engine instanceof WincacheEngine) { - $io->warning("WincacheEngine detected: Cleared {$name} CLI cache successfully " . - "but {$name} web cache must be cleared separately."); } else { $io->out("Cleared {$name} cache"); } diff --git a/app/vendor/cakephp/cakephp/src/Command/CacheClearGroupCommand.php b/app/vendor/cakephp/cakephp/src/Command/CacheClearGroupCommand.php index 96b97247c..2ac77f774 100644 --- a/app/vendor/cakephp/cakephp/src/Command/CacheClearGroupCommand.php +++ b/app/vendor/cakephp/cakephp/src/Command/CacheClearGroupCommand.php @@ -37,17 +37,25 @@ public static function defaultName(): string return 'cache clear_group'; } + /** + * @inheritDoc + */ + public static function getDescription(): string + { + return 'Clear all data in a single cache group.'; + } + /** * Hook method for defining this command's option parser. * - * @see https://book.cakephp.org/4/en/console-commands/option-parsers.html + * @link https://book.cakephp.org/5/en/console-commands/option-parsers.html * @param \Cake\Console\ConsoleOptionParser $parser The parser to be defined * @return \Cake\Console\ConsoleOptionParser The built parser. */ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser { $parser = parent::buildOptionParser($parser); - $parser->setDescription('Clear all data in a single cache group.'); + $parser->setDescription(static::getDescription()); $parser->addArgument('group', [ 'help' => 'The cache group to clear. For example, `cake cache clear_group mygroup` will clear ' . 'all cache items belonging to group "mygroup".', @@ -72,7 +80,7 @@ public function execute(Arguments $args, ConsoleIo $io): ?int $group = (string)$args->getArgument('group'); try { $groupConfigs = Cache::groupConfigs($group); - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { $io->error(sprintf('Cache group "%s" not found', $group)); return static::CODE_ERROR; @@ -94,7 +102,7 @@ public function execute(Arguments $args, ConsoleIo $io): ?int $io->error(sprintf( 'Error encountered clearing group "%s". Was unable to clear entries for "%s".', $group, - $groupConfig + $groupConfig, )); $this->abort(); } else { diff --git a/app/vendor/cakephp/cakephp/src/Command/CacheClearallCommand.php b/app/vendor/cakephp/cakephp/src/Command/CacheClearallCommand.php index 2eb290b50..79e897921 100644 --- a/app/vendor/cakephp/cakephp/src/Command/CacheClearallCommand.php +++ b/app/vendor/cakephp/cakephp/src/Command/CacheClearallCommand.php @@ -36,17 +36,25 @@ public static function defaultName(): string return 'cache clear_all'; } + /** + * @inheritDoc + */ + public static function getDescription(): string + { + return 'Clear all data in all configured cache engines.'; + } + /** * Hook method for defining this command's option parser. * - * @see https://book.cakephp.org/4/en/console-commands/option-parsers.html + * @link https://book.cakephp.org/5/en/console-commands/option-parsers.html * @param \Cake\Console\ConsoleOptionParser $parser The parser to be defined * @return \Cake\Console\ConsoleOptionParser The built parser. */ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser { $parser = parent::buildOptionParser($parser); - $parser->setDescription('Clear all data in all configured cache engines.'); + $parser->setDescription(static::getDescription()); return $parser; } diff --git a/app/vendor/cakephp/cakephp/src/Command/CacheListCommand.php b/app/vendor/cakephp/cakephp/src/Command/CacheListCommand.php index 979278b92..df885217c 100644 --- a/app/vendor/cakephp/cakephp/src/Command/CacheListCommand.php +++ b/app/vendor/cakephp/cakephp/src/Command/CacheListCommand.php @@ -34,17 +34,25 @@ public static function defaultName(): string return 'cache list'; } + /** + * @inheritDoc + */ + public static function getDescription(): string + { + return 'Show a list of configured caches.'; + } + /** * Hook method for defining this command's option parser. * - * @see https://book.cakephp.org/4/en/console-commands/option-parsers.html + * @link https://book.cakephp.org/5/en/console-commands/option-parsers.html * @param \Cake\Console\ConsoleOptionParser $parser The parser to be defined * @return \Cake\Console\ConsoleOptionParser The built parser. */ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser { $parser = parent::buildOptionParser($parser); - $parser->setDescription('Show a list of configured caches.'); + $parser->setDescription(static::getDescription()); return $parser; } @@ -60,7 +68,7 @@ public function execute(Arguments $args, ConsoleIo $io): ?int { $engines = Cache::configured(); foreach ($engines as $engine) { - $io->out("- $engine"); + $io->out("- {$engine}"); } return static::CODE_SUCCESS; diff --git a/app/vendor/cakephp/cakephp/src/Command/Command.php b/app/vendor/cakephp/cakephp/src/Command/Command.php index 1ab0c3c49..3ccff8713 100644 --- a/app/vendor/cakephp/cakephp/src/Command/Command.php +++ b/app/vendor/cakephp/cakephp/src/Command/Command.php @@ -19,7 +19,6 @@ use Cake\Console\Arguments; use Cake\Console\BaseCommand; use Cake\Console\ConsoleIo; -use Cake\Datasource\ModelAwareTrait; use Cake\Log\LogTrait; use Cake\ORM\Locator\LocatorAwareTrait; @@ -30,32 +29,10 @@ * Includes traits that integrate logging * and ORM models to console commands. */ -#[\AllowDynamicProperties] class Command extends BaseCommand { use LocatorAwareTrait; use LogTrait; - use ModelAwareTrait; - - /** - * Constructor - * - * By default CakePHP will construct command objects when - * building the CommandCollection for your application. - */ - public function __construct() - { - $this->modelFactory('Table', function ($alias) { - return $this->getTableLocator()->get($alias); - }); - - if ($this->defaultTable !== null) { - $this->modelClass = $this->defaultTable; - } - if (isset($this->modelClass)) { - $this->loadModel(); - } - } /** * Implement this method with your command's logic. @@ -63,15 +40,9 @@ public function __construct() * @param \Cake\Console\Arguments $args The command arguments. * @param \Cake\Console\ConsoleIo $io The console io * @return int|null|void The exit code or null for success + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingNativeTypeHint */ public function execute(Arguments $args, ConsoleIo $io) { } } - -// phpcs:disable -class_alias( - 'Cake\Command\Command', - 'Cake\Console\Command' -); -// phpcs:enable diff --git a/app/vendor/cakephp/cakephp/src/Command/CompletionCommand.php b/app/vendor/cakephp/cakephp/src/Command/CompletionCommand.php index 77a5f9011..54524abfb 100644 --- a/app/vendor/cakephp/cakephp/src/Command/CompletionCommand.php +++ b/app/vendor/cakephp/cakephp/src/Command/CompletionCommand.php @@ -17,14 +17,12 @@ namespace Cake\Command; use Cake\Console\Arguments; +use Cake\Console\BaseCommand; use Cake\Console\CommandCollection; use Cake\Console\CommandCollectionAwareInterface; use Cake\Console\ConsoleIo; use Cake\Console\ConsoleOptionParser; -use Cake\Console\Shell; -use Cake\Utility\Inflector; use ReflectionClass; -use ReflectionMethod; /** * Provide command completion shells such as bash. @@ -34,7 +32,15 @@ class CompletionCommand extends Command implements CommandCollectionAwareInterfa /** * @var \Cake\Console\CommandCollection */ - protected $commands; + protected CommandCollection $commands; + + /** + * @inheritDoc + */ + public static function getDescription(): string + { + return 'Used by shells like bash to autocomplete command name, options and arguments'; + } /** * Set the command collection used to get completion data on. @@ -59,7 +65,6 @@ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionPar 'commands' => 'Output a list of available commands', 'subcommands' => 'Output a list of available sub-commands for a command', 'options' => 'Output a list of available options for a command and possible subcommand.', - 'fuzzy' => 'Does nothing. Only for backwards compatibility', ]; $modeHelp = ''; foreach ($modes as $key => $help) { @@ -67,7 +72,7 @@ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionPar } $parser->setDescription( - 'Used by shells like bash to autocomplete command name, options and arguments' + static::getDescription(), )->addArgument('mode', [ 'help' => 'The type of thing to get completion on.', 'required' => true, @@ -96,25 +101,16 @@ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionPar * * @param \Cake\Console\Arguments $args The command arguments. * @param \Cake\Console\ConsoleIo $io The console io - * @return int + * @return int|null */ public function execute(Arguments $args, ConsoleIo $io): ?int { - $mode = $args->getArgument('mode'); - switch ($mode) { - case 'commands': - return $this->getCommands($args, $io); - case 'subcommands': - return $this->getSubcommands($args, $io); - case 'options': - return $this->getOptions($args, $io); - case 'fuzzy': - return static::CODE_SUCCESS; - default: - $io->err('Invalid mode chosen.'); - } - - return static::CODE_SUCCESS; + return match ($args->getArgument('mode')) { + 'commands' => $this->getCommands($args, $io), + 'subcommands' => $this->getSubcommands($args, $io), + 'options' => $this->getOptions($args, $io), + default => static::CODE_ERROR, + }; } /** @@ -162,17 +158,6 @@ protected function getSubcommands(Arguments $args, ConsoleIo $io): int // hits as subcommands if (count($parts) > 1) { $options[] = implode(' ', array_slice($parts, 1)); - continue; - } - - // Handle class strings - if (is_string($value)) { - $reflection = new ReflectionClass($value); - $value = $reflection->newInstance(); - } - if ($value instanceof Shell) { - $shellCommands = $this->shellSubcommands($value); - $options = array_merge($options, $shellCommands); } } $options = array_unique($options); @@ -181,49 +166,12 @@ protected function getSubcommands(Arguments $args, ConsoleIo $io): int return static::CODE_SUCCESS; } - /** - * Reflect the subcommands names out of a shell. - * - * @param \Cake\Console\Shell $shell The shell to get commands for - * @return array A list of commands - */ - protected function shellSubcommands(Shell $shell): array - { - $shell->initialize(); - $shell->loadTasks(); - - $optionParser = $shell->getOptionParser(); - $subcommands = $optionParser->subcommands(); - - $output = array_keys($subcommands); - - // If there are no formal subcommands all methods - // on a shell are 'subcommands' - if (count($subcommands) === 0) { - /** @psalm-suppress DeprecatedClass */ - $coreShellReflection = new ReflectionClass(Shell::class); - $reflection = new ReflectionClass($shell); - foreach ($reflection->getMethods(ReflectionMethod::IS_PUBLIC) as $method) { - if ( - $shell->hasMethod($method->getName()) - && !$coreShellReflection->hasMethod($method->getName()) - ) { - $output[] = $method->getName(); - } - } - } - $taskNames = array_map('Cake\Utility\Inflector::underscore', $shell->taskNames); - $output = array_merge($output, $taskNames); - - return array_unique($output); - } - /** * Get the options for a command or subcommand * * @param \Cake\Console\Arguments $args The command arguments. * @param \Cake\Console\ConsoleIo $io The console io - * @return int + * @return int|null */ protected function getOptions(Arguments $args, ConsoleIo $io): ?int { @@ -247,28 +195,18 @@ protected function getOptions(Arguments $args, ConsoleIo $io): ?int if (is_string($value)) { $reflection = new ReflectionClass($value); $value = $reflection->newInstance(); + assert($value instanceof BaseCommand); } - $parser = null; - if ($value instanceof Command) { - $parser = $value->getOptionParser(); - } - if ($value instanceof Shell) { - $value->initialize(); - $value->loadTasks(); + if (method_exists($value, 'getOptionParser')) { + /** @var \Cake\Console\ConsoleOptionParser $parser */ $parser = $value->getOptionParser(); - $subcommand = Inflector::camelize((string)$subcommand); - if ($subcommand && $value->hasTask($subcommand)) { - $parser = $value->{$subcommand}->getOptionParser(); - } - } - if ($parser) { foreach ($parser->options() as $name => $option) { - $options[] = "--$name"; + $options[] = "--{$name}"; $short = $option->short(); if ($short) { - $options[] = "-$short"; + $options[] = "-{$short}"; } } } diff --git a/app/vendor/cakephp/cakephp/src/Command/CounterCacheCommand.php b/app/vendor/cakephp/cakephp/src/Command/CounterCacheCommand.php new file mode 100644 index 000000000..6f2663344 --- /dev/null +++ b/app/vendor/cakephp/cakephp/src/Command/CounterCacheCommand.php @@ -0,0 +1,110 @@ +fetchTable($args->getArgument('model')); + + if (!$table->hasBehavior('CounterCache')) { + $io->error('The specified model does not have the CounterCache behavior attached.'); + + return static::CODE_ERROR; + } + + $methodArgs = []; + if ($args->hasOption('assoc')) { + $methodArgs['assocName'] = $args->getOption('assoc'); + } + if ($args->hasOption('limit')) { + $methodArgs['limit'] = (int)$args->getOption('limit'); + } + if ($args->hasOption('page')) { + $methodArgs['page'] = (int)$args->getOption('page'); + } + + /** @var \Cake\ORM\Table $table */ + $table->getBehavior('CounterCache')->updateCounterCache(...$methodArgs); + + $io->success('Counter cache updated successfully.'); + + return static::CODE_SUCCESS; + } + + /** + * @inheritDoc + */ + public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser + { + $parser->setDescription(static::getDescription()) + ->addArgument('model', [ + 'help' => 'The model to update the counter cache for.', + 'required' => true, + ])->addOption('assoc', [ + 'help' => 'The association to update the counter cache for. By default all associations are updated.', + 'short' => 'a', + 'default' => null, + ]) + ->addOption('limit', [ + 'help' => 'The number of records to update per page/iteration', + 'short' => 'l', + 'default' => null, + ]) + ->addOption('page', [ + 'help' => 'The page/iteration number. By default all records will be updated one page at a time.', + 'short' => 'p', + 'default' => null, + ]); + + return $parser; + } +} diff --git a/app/vendor/cakephp/cakephp/src/Command/Helper/BannerHelper.php b/app/vendor/cakephp/cakephp/src/Command/Helper/BannerHelper.php new file mode 100644 index 000000000..4d404371c --- /dev/null +++ b/app/vendor/cakephp/cakephp/src/Command/Helper/BannerHelper.php @@ -0,0 +1,107 @@ +padding = $padding; + + return $this; + } + + /** + * Modify the padding of the helper + * + * @param string $style The style value to use. + * @return $this + */ + public function withStyle(string $style) + { + $this->style = $style; + + return $this; + } + + /** + * Output a banner + * + * @param array $args The messages to output + * @return void + */ + public function output(array $args): void + { + if ($args === []) { + throw new InvalidArgumentException('At least one argument is required'); + } + + $lengths = array_map(fn($i) => mb_strlen($i), $args); + $maxLength = max($lengths); + $bannerLength = $maxLength + $this->padding * 2; + $start = "<{$this->style}>"; + $end = "style}>"; + + $lines = [ + '', + $start . str_repeat(' ', $bannerLength) . $end, + ]; + foreach ($args as $line) { + $lineLength = mb_strlen($line); + $linePadding = (int)max($this->padding, $bannerLength - $lineLength - $this->padding); + + $lines[] = $start . + str_repeat(' ', $this->padding) . + $line . + str_repeat(' ', $linePadding) . + $end; + } + + $lines[] = $start . str_repeat(' ', $bannerLength) . $end; + $lines[] = ''; + + $this->_io->out($lines); + } +} diff --git a/app/vendor/cakephp/cakephp/src/Shell/Helper/ProgressHelper.php b/app/vendor/cakephp/cakephp/src/Command/Helper/ProgressHelper.php similarity index 90% rename from app/vendor/cakephp/cakephp/src/Shell/Helper/ProgressHelper.php rename to app/vendor/cakephp/cakephp/src/Command/Helper/ProgressHelper.php index f3eb29ff1..f14d54c31 100644 --- a/app/vendor/cakephp/cakephp/src/Shell/Helper/ProgressHelper.php +++ b/app/vendor/cakephp/cakephp/src/Command/Helper/ProgressHelper.php @@ -14,10 +14,10 @@ * @since 3.1.0 * @license https://opensource.org/licenses/mit-license.php MIT License */ -namespace Cake\Shell\Helper; +namespace Cake\Command\Helper; use Cake\Console\Helper; -use RuntimeException; +use InvalidArgumentException; /** * Create a progress bar using a supplied callback. @@ -30,7 +30,7 @@ * $this->helper('Progress')->output(['callback' => function ($progress) { * // Do work * $progress->increment(); - * }); + * }]); * ``` */ class ProgressHelper extends Helper @@ -51,21 +51,21 @@ class ProgressHelper extends Helper * * @var float|int */ - protected $_progress = 0; + protected float|int $_progress = 0; /** * The total number of 'items' to progress through. * * @var int */ - protected $_total = self::DEFAULT_TOTAL; + protected int $_total = self::DEFAULT_TOTAL; /** * The width of the bar. * * @var int */ - protected $_width = self::DEFAULT_WIDTH; + protected int $_width = self::DEFAULT_WIDTH; /** * Output a progress bar. @@ -77,7 +77,7 @@ class ProgressHelper extends Helper * - `width` The width of the progress bar. Defaults to 80. * - `callback` The callback that will be called in a loop to advance the progress bar. * - * @param array $args The arguments/options to use when outputing the progress bar. + * @param array $args The arguments/options to use when outputting the progress bar. * @return void */ public function output(array $args): void @@ -87,7 +87,7 @@ public function output(array $args): void $args['callback'] = $args[0]; } if (!$args['callback'] || !is_callable($args['callback'])) { - throw new RuntimeException('Callback option must be a callable.'); + throw new InvalidArgumentException('Callback option must be a callable.'); } $this->init($args); @@ -108,7 +108,7 @@ public function output(array $args): void * to 100. * - `width` The width of the progress bar. Defaults to 80. * - * @param array $args The initialization data. + * @param array $args The initialization data. * @return $this */ public function init(array $args = []) @@ -127,7 +127,7 @@ public function init(array $args = []) * @param float|int $num The amount of progress to advance by. * @return $this */ - public function increment($num = 1) + public function increment(float|int $num = 1) { $this->_progress = min(max(0, $this->_progress + $num), $this->_total); diff --git a/app/vendor/cakephp/cakephp/src/Shell/Helper/TableHelper.php b/app/vendor/cakephp/cakephp/src/Command/Helper/TableHelper.php similarity index 92% rename from app/vendor/cakephp/cakephp/src/Shell/Helper/TableHelper.php rename to app/vendor/cakephp/cakephp/src/Command/Helper/TableHelper.php index ff22acfc8..741fa43fa 100644 --- a/app/vendor/cakephp/cakephp/src/Shell/Helper/TableHelper.php +++ b/app/vendor/cakephp/cakephp/src/Command/Helper/TableHelper.php @@ -13,14 +13,14 @@ * @since 3.1.0 * @license https://opensource.org/licenses/mit-license.php MIT License */ -namespace Cake\Shell\Helper; +namespace Cake\Command\Helper; use Cake\Console\Helper; use UnexpectedValueException; /** * Create a visually pleasing ASCII art table - * from 2 dimensional array data. + * from 2-dimensional array data. */ class TableHelper extends Helper { @@ -29,7 +29,7 @@ class TableHelper extends Helper * * @var array */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'headers' => true, 'rowSeparator' => false, 'headerStyle' => 'info', @@ -38,7 +38,7 @@ class TableHelper extends Helper /** * Calculate the column widths * - * @param array $rows The rows on which the columns width will be calculated on. + * @param array $rows The rows on which the column's width will be calculated on. * @return array */ protected function _calculateWidths(array $rows): array @@ -68,13 +68,13 @@ protected function _cellWidth(string $text): int return 0; } - if (strpos($text, '<') === false && strpos($text, '>') === false) { + if (!str_contains($text, '<') && !str_contains($text, '>')) { return mb_strwidth($text); } $styles = $this->_io->styles(); $tags = implode('|', array_keys($styles)); - $text = preg_replace('##', '', $text); + $text = (string)preg_replace('##', '', $text); return mb_strwidth($text); } @@ -105,7 +105,7 @@ protected function _rowSeparator(array $widths): void */ protected function _render(array $row, array $widths, array $options = []): void { - if (count($row) === 0) { + if ($row === []) { return; } @@ -141,7 +141,7 @@ protected function _render(array $row, array $widths, array $options = []): void */ public function output(array $args): void { - if (empty($args)) { + if (!$args) { return; } @@ -156,7 +156,7 @@ public function output(array $args): void $this->_rowSeparator($widths); } - if (empty($args)) { + if (!$args) { return; } diff --git a/app/vendor/cakephp/cakephp/src/Command/I18nCommand.php b/app/vendor/cakephp/cakephp/src/Command/I18nCommand.php index 5fc84b598..594ba739b 100644 --- a/app/vendor/cakephp/cakephp/src/Command/I18nCommand.php +++ b/app/vendor/cakephp/cakephp/src/Command/I18nCommand.php @@ -25,6 +25,14 @@ */ class I18nCommand extends Command { + /** + * @inheritDoc + */ + public static function getDescription(): string + { + return 'I18n commands let you generate .pot files to power translations in your application.'; + } + /** * Execute interactive mode * @@ -34,7 +42,7 @@ class I18nCommand extends Command */ public function execute(Arguments $args, ConsoleIo $io): ?int { - $io->out('I18n Shell'); + $io->out('I18n Command'); $io->hr(); $io->out('[E]xtract POT file from sources'); $io->out('[I]nitialize a language from POT file'); @@ -60,7 +68,7 @@ public function execute(Arguments $args, ConsoleIo $io): ?int default: $io->err( 'You have made an invalid selection. ' . - 'Please choose a command to execute by entering E, I, H, or Q.' + 'Please choose a command to execute by entering E, I, H, or Q.', ); } if ($code === static::CODE_ERROR) { @@ -79,9 +87,7 @@ public function execute(Arguments $args, ConsoleIo $io): ?int */ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser { - $parser->setDescription( - 'I18n commands let you generate .pot files to power translations in your application.' - ); + $parser->setDescription(static::getDescription()); return $parser; } diff --git a/app/vendor/cakephp/cakephp/src/Command/I18nExtractCommand.php b/app/vendor/cakephp/cakephp/src/Command/I18nExtractCommand.php index 1b6a04252..73d78cf2c 100644 --- a/app/vendor/cakephp/cakephp/src/Command/I18nExtractCommand.php +++ b/app/vendor/cakephp/cakephp/src/Command/I18nExtractCommand.php @@ -16,13 +16,15 @@ */ namespace Cake\Command; +use Cake\Command\Helper\ProgressHelper; use Cake\Console\Arguments; use Cake\Console\ConsoleIo; use Cake\Console\ConsoleOptionParser; use Cake\Core\App; use Cake\Core\Configure; +use Cake\Core\Exception\CakeException; use Cake\Core\Plugin; -use Cake\Filesystem\Filesystem; +use Cake\Utility\Filesystem; use Cake\Utility\Inflector; /** @@ -30,97 +32,105 @@ */ class I18nExtractCommand extends Command { - /** - * @inheritDoc - */ - public static function defaultName(): string - { - return 'i18n extract'; - } - /** * Paths to use when looking for strings * * @var array */ - protected $_paths = []; + protected array $_paths = []; /** * Files from where to extract * * @var array */ - protected $_files = []; + protected array $_files = []; /** * Merge all domain strings into the default.pot file * * @var bool */ - protected $_merge = false; + protected bool $_merge = false; /** * Current file being processed * * @var string */ - protected $_file = ''; + protected string $_file = ''; /** * Contains all content waiting to be written * * @var array */ - protected $_storage = []; + protected array $_storage = []; /** * Extracted tokens * * @var array */ - protected $_tokens = []; + protected array $_tokens = []; /** * Extracted strings indexed by domain. * * @var array */ - protected $_translations = []; + protected array $_translations = []; /** * Destination path * * @var string */ - protected $_output = ''; + protected string $_output = ''; /** * An array of directories to exclude. * * @var array */ - protected $_exclude = []; + protected array $_exclude = []; /** * Holds whether this call should extract the CakePHP Lib messages * * @var bool */ - protected $_extractCore = false; + protected bool $_extractCore = false; /** * Displays marker error(s) if true * * @var bool */ - protected $_markerError = false; + protected bool $_markerError = false; /** * Count number of marker errors found * * @var int */ - protected $_countMarkerError = 0; + protected int $_countMarkerError = 0; + + /** + * @inheritDoc + */ + public static function defaultName(): string + { + return 'i18n extract'; + } + + /** + * @inheritDoc + */ + public static function getDescription(): string + { + return 'Extract i18n POT files from application source files.'; + } /** * Method to interact with the user and get path selections. @@ -130,18 +140,17 @@ public static function defaultName(): string */ protected function _getPaths(ConsoleIo $io): void { - /** @psalm-suppress UndefinedConstant */ $defaultPaths = array_merge( [APP], array_values(App::path('templates')), - ['D'] // This is required to break the loop below + ['D'], // This is required to break the loop below ); $defaultPathIndex = 0; while (true) { - $currentPaths = count($this->_paths) > 0 ? $this->_paths : ['None']; + $currentPaths = $this->_paths !== [] ? $this->_paths : ['None']; $message = sprintf( "Current paths: %s\nWhat is the path you would like to extract?\n[Q]uit [D]one", - implode(', ', $currentPaths) + implode(', ', $currentPaths), ); $response = $io->ask($message, $defaultPaths[$defaultPathIndex] ?? 'D'); if (strtoupper($response) === 'Q') { @@ -183,26 +192,29 @@ public function execute(Arguments $args, ConsoleIo $io): ?int } if ($args->getOption('paths')) { $this->_paths = explode(',', (string)$args->getOption('paths')); - } elseif ($args->getOption('plugin')) { + } + if ($args->getOption('plugin')) { $plugin = Inflector::camelize((string)$args->getOption('plugin')); - $this->_paths = [Plugin::classPath($plugin), Plugin::templatePath($plugin)]; - } else { + if ($this->_paths === []) { + $this->_paths = [Plugin::classPath($plugin), Plugin::templatePath($plugin)]; + } + } elseif (!$args->getOption('paths')) { $this->_getPaths($io); } if ($args->hasOption('extract-core')) { - $this->_extractCore = !(strtolower((string)$args->getOption('extract-core')) === 'no'); + $this->_extractCore = strtolower((string)$args->getOption('extract-core')) !== 'no'; } else { $response = $io->askChoice( 'Would you like to extract the messages from the CakePHP core?', ['y', 'n'], - 'n' + 'n', ); $this->_extractCore = strtolower($response) === 'y'; } if ($args->hasOption('exclude-plugins') && $this->_isExtractingApp()) { - $this->_exclude = array_merge($this->_exclude, App::path('plugins')); + $this->_exclude = array_merge($this->_exclude, array_values(App::path('plugins'))); } if ($this->_extractCore) { @@ -219,12 +231,14 @@ public function execute(Arguments $args, ConsoleIo $io): ?int $message = "What is the path you would like to output?\n[Q]uit"; $localePaths = array_values(App::path('locales')); if (!$localePaths) { - $localePaths[] = ROOT . 'resources' . DIRECTORY_SEPARATOR . 'locales'; + $localePaths[] = ROOT . DIRECTORY_SEPARATOR + . 'resources' . DIRECTORY_SEPARATOR + . 'locales' . DIRECTORY_SEPARATOR; } while (true) { $response = $io->ask( $message, - $localePaths[0] + $localePaths[0], ); if (strtoupper($response) === 'Q') { $io->err('Extract Aborted'); @@ -239,33 +253,33 @@ public function execute(Arguments $args, ConsoleIo $io): ?int $io->err(''); $io->err( 'The directory path you supplied was ' . - 'not found. Please try again.' + 'not found. Please try again.', ); $io->err(''); } } if ($args->hasOption('merge')) { - $this->_merge = !(strtolower((string)$args->getOption('merge')) === 'no'); + $this->_merge = strtolower((string)$args->getOption('merge')) !== 'no'; } else { $io->out(); $response = $io->askChoice( 'Would you like to merge all domain strings into the default.pot file?', ['y', 'n'], - 'n' + 'n', ); $this->_merge = strtolower($response) === 'y'; } $this->_markerError = (bool)$args->getOption('marker-error'); - if (empty($this->_files)) { + if (!$this->_files) { $this->_searchFiles(); } $this->_output = rtrim($this->_output, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; if (!$this->_isPathUsable($this->_output)) { - $io->err(sprintf('The output directory %s was not found or writable.', $this->_output)); + $io->err(sprintf('The output directory `%s` was not found or writable.', $this->_output)); return static::CODE_ERROR; } @@ -282,7 +296,7 @@ public function execute(Arguments $args, ConsoleIo $io): ?int * * @param string $domain The domain * @param string $msgid The message string - * @param array $details Context and plural form if any, file and line references + * @param array $details Context and plural form if any, file and line references * @return void */ protected function _addTranslation(string $domain, string $msgid, array $details = []): void @@ -327,8 +341,11 @@ protected function _extract(Arguments $args, ConsoleIo $io): void $this->_extractTokens($args, $io); $this->_buildFiles($args); $this->_writeFiles($args, $io); - $this->_paths = $this->_files = $this->_storage = []; - $this->_translations = $this->_tokens = []; + $this->_paths = []; + $this->_files = []; + $this->_storage = []; + $this->_translations = []; + $this->_tokens = []; $io->out(); if ($this->_countMarkerError) { $io->err("{$this->_countMarkerError} marker error(s) detected."); @@ -346,11 +363,11 @@ protected function _extract(Arguments $args, ConsoleIo $io): void */ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser { - $parser->setDescription( - 'Extract i18n POT files from application source files. ' . + $parser->setDescription([ + static::getDescription(), 'Source files are parsed and string literal format strings ' . - 'provided to the __ family of functions are extracted.' - )->addOption('app', [ + 'provided to the __ family of functions are extracted.', + ])->addOption('app', [ 'help' => 'Directory where your application is located.', ])->addOption('paths', [ 'help' => 'Comma separated list of paths that are searched for source files.', @@ -368,7 +385,7 @@ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionPar 'help' => 'Ignores all files in plugins if this command is run inside from the same app directory.', ])->addOption('plugin', [ 'help' => 'Extracts tokens only from the plugin specified and ' - . 'puts the result in the plugin\'s `locales` directory.', + . "puts the result in the plugin's `locales` directory.", 'short' => 'p', ])->addOption('exclude', [ 'help' => 'Comma separated list of directories to exclude.' . @@ -402,8 +419,8 @@ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionPar */ protected function _extractTokens(Arguments $args, ConsoleIo $io): void { - /** @var \Cake\Shell\Helper\ProgressHelper $progress */ $progress = $io->helper('progress'); + assert($progress instanceof ProgressHelper); $progress->init(['total' => count($this->_files)]); $isVerbose = $args->getOption('verbose'); @@ -425,7 +442,7 @@ protected function _extractTokens(Arguments $args, ConsoleIo $io): void $io->verbose(sprintf('Processing %s...', $file)); } - $code = file_get_contents($file); + $code = (string)file_get_contents($file); if (preg_match($pattern, $code) === 1) { $allTokens = token_get_all($code); @@ -490,19 +507,18 @@ protected function _parse(ConsoleIo $io, string $functionName, array $map): void if ($mapCount === count($strings)) { $singular = ''; - $plural = $context = null; $vars = array_combine($map, $strings); extract($vars); - $domain = $domain ?? 'default'; + $domain ??= 'default'; $details = [ 'file' => $this->_file, 'line' => $line, ]; $details['file'] = '.' . str_replace(ROOT, '', $details['file']); - if ($plural !== null) { + if (isset($plural)) { $details['msgid_plural'] = $plural; } - if ($context !== null) { + if (isset($context)) { $details['msgctxt'] = $context; } $this->_addTranslation($domain, $singular, $details); @@ -523,7 +539,6 @@ protected function _parse(ConsoleIo $io, string $functionName, array $map): void protected function _buildFiles(Arguments $args): void { $paths = $this->_paths; - /** @psalm-suppress UndefinedConstant */ $paths[] = realpath(APP) . DIRECTORY_SEPARATOR; usort($paths, function (string $a, string $b) { @@ -586,7 +601,7 @@ protected function _buildFiles(Arguments $args): void */ protected function _store(string $domain, string $header, string $sentence): void { - $this->_storage[$domain] = $this->_storage[$domain] ?? []; + $this->_storage[$domain] ??= []; if (!isset($this->_storage[$domain][$sentence])) { $this->_storage[$domain][$sentence] = $header; @@ -616,16 +631,10 @@ protected function _writeFiles(Arguments $args, ConsoleIo $io): void $output .= $header . $sentence; } - // Remove vendor prefix if present. - $slashPosition = strpos($domain, '/'); - if ($slashPosition !== false) { - $domain = substr($domain, $slashPosition + 1); - } - $filename = str_replace('/', '_', $domain) . '.pot'; $outputPath = $this->_output . $filename; - if ($this->checkUnchanged($outputPath, $headerLength, $output) === true) { + if ($this->checkUnchanged($outputPath, $headerLength, $output)) { $io->out($filename . ' is unchanged. Skipping.'); continue; } @@ -636,7 +645,7 @@ protected function _writeFiles(Arguments $args, ConsoleIo $io): void $response = $io->askChoice( sprintf('Error: %s already exists in this location. Overwrite? [Y]es, [N]o, [A]ll', $filename), ['y', 'n', 'a'], - 'y' + 'y', ); if (strtoupper($response) === 'N') { $response = ''; @@ -698,9 +707,12 @@ protected function checkUnchanged(string $oldFile, int $headerLength, string $ne return false; } $oldFileContent = file_get_contents($oldFile); + if ($oldFileContent === false) { + throw new CakeException(sprintf('Cannot read file content of `%s`', $oldFile)); + } - $oldChecksum = sha1((string)substr($oldFileContent, $headerLength)); - $newChecksum = sha1((string)substr($newFileContent, $headerLength)); + $oldChecksum = sha1(substr($oldFileContent, $headerLength)); + $newChecksum = sha1(substr($newFileContent, $headerLength)); return $oldChecksum === $newChecksum; } @@ -777,9 +789,9 @@ protected function _formatString(string $string): string * @param int $count Count * @return void */ - protected function _markerError($io, string $file, int $line, string $marker, int $count): void + protected function _markerError(ConsoleIo $io, string $file, int $line, string $marker, int $count): void { - if (strpos($this->_file, CAKE_CORE_INCLUDE_PATH) === false) { + if (!str_contains($this->_file, CAKE_CORE_INCLUDE_PATH)) { $this->_countMarkerError++; } @@ -818,10 +830,10 @@ protected function _markerError($io, string $file, int $line, string $marker, in protected function _searchFiles(): void { $pattern = false; - if (!empty($this->_exclude)) { + if ($this->_exclude) { $exclude = []; foreach ($this->_exclude as $e) { - if (DIRECTORY_SEPARATOR !== '\\' && $e[0] !== DIRECTORY_SEPARATOR) { + if (DIRECTORY_SEPARATOR !== '\\' && !str_starts_with($e, DIRECTORY_SEPARATOR)) { $e = DIRECTORY_SEPARATOR . $e; } $exclude[] = preg_quote($e, '/'); @@ -839,8 +851,8 @@ protected function _searchFiles(): void $files = $fs->findRecursive($path, '/\.php$/'); $files = array_keys(iterator_to_array($files)); sort($files); - if (!empty($pattern)) { - $files = preg_grep($pattern, $files, PREG_GREP_INVERT); + if ($pattern) { + $files = preg_grep($pattern, $files, PREG_GREP_INVERT) ?: []; $files = array_values($files); } $this->_files = array_merge($this->_files, $files); @@ -856,7 +868,6 @@ protected function _searchFiles(): void */ protected function _isExtractingApp(): bool { - /** @psalm-suppress UndefinedConstant */ return $this->_paths === [APP]; } @@ -866,10 +877,10 @@ protected function _isExtractingApp(): bool * @param string $path Path to folder * @return bool true if it exists and is writable, false otherwise */ - protected function _isPathUsable($path): bool + protected function _isPathUsable(string $path): bool { if (!is_dir($path)) { - mkdir($path, 0770, true); + mkdir($path, 0755, true); } return is_dir($path) && is_writable($path); diff --git a/app/vendor/cakephp/cakephp/src/Command/I18nInitCommand.php b/app/vendor/cakephp/cakephp/src/Command/I18nInitCommand.php index 39bbf5b83..b9dd59798 100644 --- a/app/vendor/cakephp/cakephp/src/Command/I18nInitCommand.php +++ b/app/vendor/cakephp/cakephp/src/Command/I18nInitCommand.php @@ -20,6 +20,7 @@ use Cake\Console\ConsoleIo; use Cake\Console\ConsoleOptionParser; use Cake\Core\App; +use Cake\Core\Exception\CakeException; use Cake\Core\Plugin; use Cake\Utility\Inflector; use DirectoryIterator; @@ -37,6 +38,14 @@ public static function defaultName(): string return 'i18n init'; } + /** + * @inheritDoc + */ + public static function getDescription(): string + { + return 'Initialize a language PO file from the POT file.'; + } + /** * Execute the command * @@ -66,20 +75,24 @@ public function execute(Arguments $args, ConsoleIo $io): ?int $sourceFolder = rtrim($response, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; $targetFolder = $sourceFolder . $language . DIRECTORY_SEPARATOR; if (!is_dir($targetFolder)) { - mkdir($targetFolder, 0770, true); + mkdir($targetFolder, 0755, true); } $count = 0; $iterator = new DirectoryIterator($sourceFolder); - foreach ($iterator as $fileinfo) { - if (!$fileinfo->isFile()) { + foreach ($iterator as $fileInfo) { + if (!$fileInfo->isFile()) { continue; } - $filename = $fileinfo->getFilename(); - $newFilename = $fileinfo->getBasename('.pot'); + $filename = $fileInfo->getFilename(); + $newFilename = $fileInfo->getBasename('.pot'); $newFilename .= '.po'; - $io->createFile($targetFolder . $newFilename, file_get_contents($sourceFolder . $filename)); + $content = file_get_contents($sourceFolder . $filename); + if ($content === false) { + throw new CakeException(sprintf('Cannot read file content of `%s`', $sourceFolder . $filename)); + } + $io->createFile($targetFolder . $newFilename, $content); $count++; } @@ -96,7 +109,7 @@ public function execute(Arguments $args, ConsoleIo $io): ?int */ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser { - $parser->setDescription('Initialize a language PO file from the POT file') + $parser->setDescription(static::getDescription()) ->addOption('plugin', [ 'help' => 'The plugin to create a PO file in.', 'short' => 'p', diff --git a/app/vendor/cakephp/cakephp/src/Command/PluginAssetsCopyCommand.php b/app/vendor/cakephp/cakephp/src/Command/PluginAssetsCopyCommand.php index 8f5074233..2221e374e 100644 --- a/app/vendor/cakephp/cakephp/src/Command/PluginAssetsCopyCommand.php +++ b/app/vendor/cakephp/cakephp/src/Command/PluginAssetsCopyCommand.php @@ -22,8 +22,6 @@ /** * Command for copying plugin assets to app's webroot. - * - * @psalm-suppress PropertyNotSetInConstructor */ class PluginAssetsCopyCommand extends Command { @@ -37,6 +35,14 @@ public static function defaultName(): string return 'plugin assets copy'; } + /** + * @inheritDoc + */ + public static function getDescription(): string + { + return "Copy plugin assets to app's webroot."; + } + /** * Execute the command * @@ -67,9 +73,9 @@ public function execute(Arguments $args, ConsoleIo $io): ?int */ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser { - $parser->setDescription([ - 'Copy plugin assets to app\'s webroot.', - ])->addArgument('name', [ + $parser->setDescription( + static::getDescription(), + )->addArgument('name', [ 'help' => 'A specific plugin you want to copy assets for.', 'required' => false, ])->addOption('overwrite', [ diff --git a/app/vendor/cakephp/cakephp/src/Command/PluginAssetsRemoveCommand.php b/app/vendor/cakephp/cakephp/src/Command/PluginAssetsRemoveCommand.php index f4cf1ed75..b0d613b2e 100644 --- a/app/vendor/cakephp/cakephp/src/Command/PluginAssetsRemoveCommand.php +++ b/app/vendor/cakephp/cakephp/src/Command/PluginAssetsRemoveCommand.php @@ -22,8 +22,6 @@ /** * Command for removing plugin assets from app's webroot. - * - * @psalm-suppress PropertyNotSetInConstructor */ class PluginAssetsRemoveCommand extends Command { @@ -37,6 +35,14 @@ public static function defaultName(): string return 'plugin assets remove'; } + /** + * @inheritDoc + */ + public static function getDescription(): string + { + return "Remove plugin assets from app's webroot."; + } + /** * Execute the command * @@ -76,9 +82,9 @@ public function execute(Arguments $args, ConsoleIo $io): ?int */ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser { - $parser->setDescription([ - 'Remove plugin assets from app\'s webroot.', - ])->addArgument('name', [ + $parser->setDescription( + static::getDescription(), + )->addArgument('name', [ 'help' => 'A specific plugin you want to remove.', 'required' => false, ]); diff --git a/app/vendor/cakephp/cakephp/src/Command/PluginAssetsSymlinkCommand.php b/app/vendor/cakephp/cakephp/src/Command/PluginAssetsSymlinkCommand.php index 103c3e266..ba49750f5 100644 --- a/app/vendor/cakephp/cakephp/src/Command/PluginAssetsSymlinkCommand.php +++ b/app/vendor/cakephp/cakephp/src/Command/PluginAssetsSymlinkCommand.php @@ -22,8 +22,6 @@ /** * Command for symlinking / copying plugin assets to app's webroot. - * - * @psalm-suppress PropertyNotSetInConstructor */ class PluginAssetsSymlinkCommand extends Command { @@ -37,6 +35,14 @@ public static function defaultName(): string return 'plugin assets symlink'; } + /** + * @inheritDoc + */ + public static function getDescription(): string + { + return "Symlink (copy as fallback) plugin assets to app's webroot."; + } + /** * Execute the command * @@ -68,9 +74,9 @@ public function execute(Arguments $args, ConsoleIo $io): ?int */ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser { - $parser->setDescription([ - 'Symlink (copy as fallback) plugin assets to app\'s webroot.', - ])->addArgument('name', [ + $parser->setDescription( + static::getDescription(), + )->addArgument('name', [ 'help' => 'A specific plugin you want to symlink assets for.', 'required' => false, ])->addOption('overwrite', [ diff --git a/app/vendor/cakephp/cakephp/src/Command/PluginAssetsTrait.php b/app/vendor/cakephp/cakephp/src/Command/PluginAssetsTrait.php index 8e0ec7ff5..03feceb26 100644 --- a/app/vendor/cakephp/cakephp/src/Command/PluginAssetsTrait.php +++ b/app/vendor/cakephp/cakephp/src/Command/PluginAssetsTrait.php @@ -16,9 +16,11 @@ */ namespace Cake\Command; +use Cake\Console\Arguments; +use Cake\Console\ConsoleIo; use Cake\Core\Configure; use Cake\Core\Plugin; -use Cake\Filesystem\Filesystem; +use Cake\Utility\Filesystem; use Cake\Utility\Inflector; /** @@ -33,14 +35,14 @@ trait PluginAssetsTrait * * @var \Cake\Console\Arguments */ - protected $args; + protected Arguments $args; /** * Console IO * * @var \Cake\Console\ConsoleIo */ - protected $io; + protected ConsoleIo $io; /** * Get list of plugins to process. Plugins without a webroot directory are skipped. @@ -65,7 +67,7 @@ protected function _list(?string $name = null): array $this->io->verbose('', 1); $this->io->verbose( sprintf('Skipping plugin %s. It does not have webroot folder.', $plugin), - 2 + 2, ); continue; } @@ -74,7 +76,7 @@ protected function _list(?string $name = null): array $wwwRoot = Configure::read('App.wwwRoot'); $dir = $wwwRoot; $namespaced = false; - if (strpos($link, '/') !== false) { + if (str_contains($link, '/')) { $namespaced = true; $parts = explode('/', $link); $link = array_pop($parts); @@ -120,12 +122,12 @@ protected function _process(array $plugins, bool $copy = false, bool $overwrite if (file_exists($dest)) { if ($overwrite && !$this->_remove($config)) { continue; - } elseif (!$overwrite) { + } + if (!$overwrite) { $this->io->verbose( $dest . ' already exists', - 1 + 1, ); - continue; } } @@ -133,7 +135,7 @@ protected function _process(array $plugins, bool $copy = false, bool $overwrite if (!$copy) { $result = $this->_createSymlink( $config['srcPath'], - $dest + $dest, ); if ($result) { continue; @@ -142,7 +144,7 @@ protected function _process(array $plugins, bool $copy = false, bool $overwrite $this->_copyDirectory( $config['srcPath'], - $dest + $dest, ); } @@ -161,7 +163,7 @@ protected function _remove(array $config): bool if ($config['namespaced'] && !is_dir($config['destDir'])) { $this->io->verbose( $config['destDir'] . $config['link'] . ' does not exist', - 1 + 1, ); return false; @@ -172,7 +174,7 @@ protected function _remove(array $config): bool if (!file_exists($dest)) { $this->io->verbose( $dest . ' does not exist', - 1 + 1, ); return false; @@ -180,16 +182,15 @@ protected function _remove(array $config): bool if (is_link($dest)) { // phpcs:ignore - $success = DS === '\\' ? @rmdir($dest) : @unlink($dest); + $success = DIRECTORY_SEPARATOR === '\\' ? @rmdir($dest) : @unlink($dest); if ($success) { $this->io->out('Unlinked ' . $dest); return true; - } else { - $this->io->err('Failed to unlink ' . $dest); - - return false; } + $this->io->err('Failed to unlink ' . $dest); + + return false; } $fs = new Filesystem(); @@ -197,11 +198,10 @@ protected function _remove(array $config): bool $this->io->out('Deleted ' . $dest); return true; - } else { - $this->io->err('Failed to delete ' . $dest); - - return false; } + $this->io->err('Failed to delete ' . $dest); + + return false; } /** diff --git a/app/vendor/cakephp/cakephp/src/Command/PluginListCommand.php b/app/vendor/cakephp/cakephp/src/Command/PluginListCommand.php new file mode 100644 index 000000000..455e92e63 --- /dev/null +++ b/app/vendor/cakephp/cakephp/src/Command/PluginListCommand.php @@ -0,0 +1,105 @@ +getOption('composer-path'); + $config = PluginConfig::getAppConfig($path ?: null); + + $table = [ + ['Plugin', 'Is Loaded', 'Only Debug', 'Only CLI', 'Optional', 'Version'], + ]; + + if ($config === []) { + $io->warning(__d('cake', 'No plugins have been found.')); + + return static::CODE_ERROR; + } + + foreach ($config as $pluginName => $options) { + $isLoaded = $loadedPluginsCollection->has($pluginName); + $onlyDebug = $options['onlyDebug'] ?? false; + $onlyCli = $options['onlyCli'] ?? false; + $optional = $options['optional'] ?? false; + $version = $options['version'] ?? ''; + $table[] = [ + $pluginName, + $isLoaded ? 'X' : '', + $onlyDebug ? 'X' : '', + $onlyCli ? 'X' : '', + $optional ? 'X' : '', + $version, + ]; + } + $io->helper('Table')->output($table); + + return static::CODE_SUCCESS; + } + + /** + * Get the option parser. + * + * @param \Cake\Console\ConsoleOptionParser $parser The option parser to update + * @return \Cake\Console\ConsoleOptionParser + */ + public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser + { + $parser->setDescription(static::getDescription()); + $parser->addOption('composer-path', [ + 'help' => 'The absolute path to the composer.lock file to retrieve the versions from', + ]); + + return $parser; + } +} diff --git a/app/vendor/cakephp/cakephp/src/Command/PluginLoadCommand.php b/app/vendor/cakephp/cakephp/src/Command/PluginLoadCommand.php index bfd61b91b..469b79fd4 100644 --- a/app/vendor/cakephp/cakephp/src/Command/PluginLoadCommand.php +++ b/app/vendor/cakephp/cakephp/src/Command/PluginLoadCommand.php @@ -16,19 +16,27 @@ */ namespace Cake\Command; +use Brick\VarExporter\VarExporter; use Cake\Console\Arguments; use Cake\Console\ConsoleIo; use Cake\Console\ConsoleOptionParser; use Cake\Core\Exception\MissingPluginException; use Cake\Core\Plugin; +use Cake\Core\PluginInterface; +use Cake\Utility\Hash; /** * Command for loading plugins. - * - * @psalm-suppress PropertyNotSetInConstructor */ class PluginLoadCommand extends Command { + /** + * Config file + * + * @var string + */ + protected string $configFile = CONFIG . 'plugins.php'; + /** * @inheritDoc */ @@ -38,18 +46,12 @@ public static function defaultName(): string } /** - * Arguments - * - * @var \Cake\Console\Arguments - */ - protected $args; - - /** - * Console IO - * - * @var \Cake\Console\ConsoleIo + * @inheritDoc */ - protected $io; + public static function getDescription(): string + { + return 'Command for loading plugins.'; + } /** * Execute the command @@ -60,73 +62,76 @@ public static function defaultName(): string */ public function execute(Arguments $args, ConsoleIo $io): ?int { - $this->io = $io; - $this->args = $args; + $plugin = (string)$args->getArgument('plugin'); + $options = []; + if ($args->getOption('only-debug')) { + $options['onlyDebug'] = true; + } + if ($args->getOption('only-cli')) { + $options['onlyCli'] = true; + } + if ($args->getOption('optional')) { + $options['optional'] = true; + } + + foreach (PluginInterface::VALID_HOOKS as $hook) { + if ($args->getOption('no-' . $hook)) { + $options[$hook] = false; + } + } - $plugin = $args->getArgument('plugin') ?? ''; try { Plugin::getCollection()->findPath($plugin); } catch (MissingPluginException $e) { - $this->io->err($e->getMessage()); - $this->io->err('Ensure you have the correct spelling and casing.'); + if (empty($options['optional'])) { + $io->err($e->getMessage()); + $io->err('Ensure you have the correct spelling and casing.'); - return static::CODE_ERROR; + return static::CODE_ERROR; + } } - $app = APP . 'Application.php'; - if (file_exists($app)) { - $this->modifyApplication($app, $plugin); - - return static::CODE_SUCCESS; + $result = $this->modifyConfigFile($plugin, $options); + if ($result === static::CODE_ERROR) { + $io->err('Failed to update `CONFIG/plugins.php`'); } - return static::CODE_ERROR; + $io->success('Plugin added successfully to `CONFIG/plugins.php`'); + + return $result; } /** - * Modify the application class + * Modify the plugins config file. * - * @param string $app The Application file to modify. - * @param string $plugin The plugin name to add. - * @return void + * @param string $plugin Plugin name. + * @param array $options Plugin options. + * @return int */ - protected function modifyApplication(string $app, string $plugin): void + protected function modifyConfigFile(string $plugin, array $options): int { - $contents = file_get_contents($app); - - // Find start of bootstrap - if (!preg_match('/^(\s+)public function bootstrap(?:\s*)\(\)/mu', $contents, $matches, PREG_OFFSET_CAPTURE)) { - $this->io->err('Your Application class does not have a bootstrap() method. Please add one.'); - $this->abort(); + // phpcs:ignore + $config = @include $this->configFile; + if (!is_array($config)) { + $config = []; + } else { + $config = Hash::normalize($config); } - $offset = $matches[0][1]; - $indent = $matches[1][0]; + $config[$plugin] = $options; - // Find closing function bracket - if (!preg_match("/^$indent\}\n$/mu", $contents, $matches, PREG_OFFSET_CAPTURE, $offset)) { - $this->io->err('Your Application class does not have a bootstrap() method. Please add one.'); - $this->abort(); + if (class_exists(VarExporter::class)) { + $array = VarExporter::export($config, VarExporter::TRAILING_COMMA_IN_ARRAY); + } else { + $array = var_export($config, true); } + $contents = 'addPlugin\(\'' . $plugin . '\'#mu'; - if (preg_match($regex, $contents, $otherMatches, PREG_OFFSET_CAPTURE)) { - $this->io->info('The specified plugin is already loaded!'); - - return; + if (file_put_contents($this->configFile, $contents)) { + return static::CODE_SUCCESS; } - $append = "$indent \$this->addPlugin('%s');\n"; - $insert = str_replace(', []', '', sprintf($append, $plugin)); - - $offset = $matches[0][1]; - $contents = substr_replace($contents, $insert, $offset, 0); - - file_put_contents($app, $contents); - - $this->io->out(''); - $this->io->out(sprintf('%s modified', $app)); + return static::CODE_ERROR; } /** @@ -137,14 +142,43 @@ protected function modifyApplication(string $app, string $plugin): void */ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser { - $parser->setDescription([ - 'Command for loading plugins.', - ]) - ->addArgument('plugin', [ - 'help' => 'Name of the plugin to load. Must be in CamelCase format. Example: cake plugin load Example', - 'required' => true, - ]); - - return $parser; + return $parser + ->setDescription(static::getDescription()) + ->addArgument('plugin', [ + 'help' => 'Name of the plugin to load. Must be in CamelCase format. Example: cake plugin load Example', + 'required' => true, + ]) + ->addOption('only-debug', [ + 'boolean' => true, + 'help' => 'Load the plugin only when `debug` is enabled.', + ]) + ->addOption('only-cli', [ + 'boolean' => true, + 'help' => 'Load the plugin only for CLI.', + ]) + ->addOption('optional', [ + 'boolean' => true, + 'help' => 'Do not throw an error if the plugin is not available.', + ]) + ->addOption('no-bootstrap', [ + 'boolean' => true, + 'help' => 'Do not run the `bootstrap()` hook.', + ]) + ->addOption('no-console', [ + 'boolean' => true, + 'help' => 'Do not run the `console()` hook.', + ]) + ->addOption('no-middleware', [ + 'boolean' => true, + 'help' => 'Do not run the `middleware()` hook..', + ]) + ->addOption('no-routes', [ + 'boolean' => true, + 'help' => 'Do not run the `routes()` hook.', + ]) + ->addOption('no-services', [ + 'boolean' => true, + 'help' => 'Do not run the `services()` hook.', + ]); } } diff --git a/app/vendor/cakephp/cakephp/src/Command/PluginLoadedCommand.php b/app/vendor/cakephp/cakephp/src/Command/PluginLoadedCommand.php index ccb05c1ff..1e44d83c0 100644 --- a/app/vendor/cakephp/cakephp/src/Command/PluginLoadedCommand.php +++ b/app/vendor/cakephp/cakephp/src/Command/PluginLoadedCommand.php @@ -34,6 +34,14 @@ public static function defaultName(): string return 'plugin loaded'; } + /** + * @inheritDoc + */ + public static function getDescription(): string + { + return 'Displays all currently loaded plugins.'; + } + /** * Displays all currently loaded plugins. * @@ -57,7 +65,7 @@ public function execute(Arguments $args, ConsoleIo $io): ?int */ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser { - $parser->setDescription('Displays all currently loaded plugins.'); + $parser->setDescription(static::getDescription()); return $parser; } diff --git a/app/vendor/cakephp/cakephp/src/Command/PluginUnloadCommand.php b/app/vendor/cakephp/cakephp/src/Command/PluginUnloadCommand.php index 9841eca3c..a9db3c4be 100644 --- a/app/vendor/cakephp/cakephp/src/Command/PluginUnloadCommand.php +++ b/app/vendor/cakephp/cakephp/src/Command/PluginUnloadCommand.php @@ -16,15 +16,24 @@ */ namespace Cake\Command; +use Brick\VarExporter\VarExporter; use Cake\Console\Arguments; use Cake\Console\ConsoleIo; use Cake\Console\ConsoleOptionParser; +use Cake\Utility\Hash; /** * Command for unloading plugins. */ class PluginUnloadCommand extends Command { + /** + * Config file + * + * @var string + */ + protected string $configFile = CONFIG . 'plugins.php'; + /** * @inheritDoc */ @@ -33,6 +42,14 @@ public static function defaultName(): string return 'plugin unload'; } + /** + * @inheritDoc + */ + public static function getDescription(): string + { + return 'Command for unloading plugins.'; + } + /** * Execute the command * @@ -42,55 +59,53 @@ public static function defaultName(): string */ public function execute(Arguments $args, ConsoleIo $io): ?int { - $plugin = $args->getArgument('plugin'); - if (!$plugin) { - $io->err('You must provide a plugin name in CamelCase format.'); - $io->err('To unload an "Example" plugin, run `cake plugin unload Example`.'); + $plugin = (string)$args->getArgument('plugin'); - return static::CODE_ERROR; - } - - $app = APP . 'Application.php'; - if (file_exists($app) && $this->modifyApplication($app, $plugin)) { - $io->out(''); - $io->out(sprintf('%s modified', $app)); + $result = $this->modifyConfigFile($plugin); + if ($result === null) { + $io->success('Plugin removed from `CONFIG/plugins.php`'); return static::CODE_SUCCESS; } + $io->err($result); + return static::CODE_ERROR; } /** - * Modify the application class. + * Modify the plugins config file. * - * @param string $app Path to the application to update. - * @param string $plugin Name of plugin. - * @return bool If modify passed. + * @param string $plugin Plugin name. + * @return string|null */ - protected function modifyApplication(string $app, string $plugin): bool + protected function modifyConfigFile(string $plugin): ?string { - $plugin = preg_quote($plugin, '/'); - $finder = "/ - # whitespace and addPlugin call - \s*\\\$this\-\>addPlugin\( - # plugin name in quotes of any kind - \s*['\"]{$plugin}['\"] - # method arguments assuming a literal array with multiline args - (\s*,[\s\\n]*\[(\\n.*|.*){0,5}\][\\n\s]*)? - # closing paren of method - \);/mx"; - - $content = file_get_contents($app); - $newContent = preg_replace($finder, '', $content); - - if ($newContent === $content) { - return false; + // phpcs:ignore + $config = @include $this->configFile; + if (!is_array($config)) { + return '`CONFIG/plugins.php` not found or does not return an array'; + } + + $config = Hash::normalize($config); + if (!array_key_exists($plugin, $config)) { + return sprintf('Plugin `%s` could not be found', $plugin); } - file_put_contents($app, $newContent); + unset($config[$plugin]); + + if (class_exists(VarExporter::class)) { + $array = VarExporter::export($config); + } else { + $array = var_export($config, true); + } + $contents = 'configFile, $contents)) { + return null; + } - return true; + return 'Failed to update `CONFIG/plugins.php`'; } /** @@ -101,11 +116,12 @@ protected function modifyApplication(string $app, string $plugin): bool */ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser { - $parser->setDescription([ - 'Command for unloading plugins.', - ]) + $parser->setDescription( + static::getDescription(), + ) ->addArgument('plugin', [ 'help' => 'Name of the plugin to unload.', + 'required' => true, ]); return $parser; diff --git a/app/vendor/cakephp/cakephp/src/Command/RoutesCheckCommand.php b/app/vendor/cakephp/cakephp/src/Command/RoutesCheckCommand.php index 86245a5c7..feaa310f3 100644 --- a/app/vendor/cakephp/cakephp/src/Command/RoutesCheckCommand.php +++ b/app/vendor/cakephp/cakephp/src/Command/RoutesCheckCommand.php @@ -37,33 +37,35 @@ public static function defaultName(): string return 'routes check'; } + /** + * @inheritDoc + */ + public static function getDescription(): string + { + return 'Check a URL string against the routes.'; + } + /** * Display all routes in an application * * @param \Cake\Console\Arguments $args The command arguments. * @param \Cake\Console\ConsoleIo $io The console io * @return int|null The exit code or null for success + * @throws \JsonException */ public function execute(Arguments $args, ConsoleIo $io): ?int { $url = $args->getArgument('url'); try { - $request = new ServerRequest(['url' => $url]); - $route = Router::parseRequest($request); - $name = null; - foreach (Router::routes() as $r) { - if ($r->match($route)) { - $name = $r->options['_name'] ?? $r->getName(); - break; - } - } + $parsed = Router::parseRequest(new ServerRequest(['url' => $url])); + $name = $parsed['_name'] ?? $parsed['_route']->getName(); - unset($route['_route'], $route['_matchedRoute']); - ksort($route); + unset($parsed['_route'], $parsed['_matchedRoute']); + ksort($parsed); $output = [ ['Route name', 'URI template', 'Defaults'], - [$name, $url, json_encode($route)], + [$name, $url, json_encode($parsed, JSON_THROW_ON_ERROR)], ]; $io->helper('table')->output($output); $io->out(); @@ -74,8 +76,8 @@ public function execute(Arguments $args, ConsoleIo $io): ?int ]; $io->helper('table')->output($output); $io->out(); - } catch (MissingRouteException $e) { - $io->warning("'$url' did not match any routes."); + } catch (MissingRouteException) { + $io->warning("'{$url}' did not match any routes."); $io->out(); return static::CODE_ERROR; @@ -92,10 +94,10 @@ public function execute(Arguments $args, ConsoleIo $io): ?int */ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser { - $parser->setDescription( - 'Check a URL string against the routes. ' . - 'Will output the routing parameters the route resolves to.' - ) + $parser->setDescription([ + static::getDescription(), + 'Will output the routing parameters the route resolves to.', + ]) ->addArgument('url', [ 'help' => 'The URL to check.', 'required' => true, diff --git a/app/vendor/cakephp/cakephp/src/Command/RoutesCommand.php b/app/vendor/cakephp/cakephp/src/Command/RoutesCommand.php index 66d9814de..12e3c4b60 100644 --- a/app/vendor/cakephp/cakephp/src/Command/RoutesCommand.php +++ b/app/vendor/cakephp/cakephp/src/Command/RoutesCommand.php @@ -26,22 +26,35 @@ */ class RoutesCommand extends Command { + /** + * @inheritDoc + */ + public static function getDescription(): string + { + return 'Get the list of routes connected in this application.'; + } + /** * Display all routes in an application * * @param \Cake\Console\Arguments $args The command arguments. * @param \Cake\Console\ConsoleIo $io The console io * @return int|null The exit code or null for success + * @throws \JsonException */ public function execute(Arguments $args, ConsoleIo $io): ?int { $header = ['Route name', 'URI template', 'Plugin', 'Prefix', 'Controller', 'Action', 'Method(s)']; + if ($args->getOption('with-middlewares') || $args->getOption('verbose')) { + $header[] = 'Middlewares'; + } if ($args->getOption('verbose')) { $header[] = 'Defaults'; } $availableRoutes = Router::routes(); - $output = $duplicateRoutesCounter = []; + $output = []; + $duplicateRoutesCounter = []; foreach ($availableRoutes as $route) { $methods = isset($route->defaults['_method']) ? (array)$route->defaults['_method'] : ['']; @@ -56,9 +69,12 @@ public function execute(Arguments $args, ConsoleIo $io): ?int implode(', ', $methods), ]; + if ($args->getOption('with-middlewares') || $args->getOption('verbose')) { + $item[] = implode(', ', $route->getMiddleware()); + } if ($args->getOption('verbose')) { ksort($route->defaults); - $item[] = json_encode($route->defaults); + $item[] = json_encode($route->defaults, JSON_THROW_ON_ERROR); } $output[] = $item; @@ -128,11 +144,16 @@ public function execute(Arguments $args, ConsoleIo $io): ?int public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser { $parser - ->setDescription('Get the list of routes connected in this application.') + ->setDescription(static::getDescription()) ->addOption('sort', [ 'help' => 'Sorts alphabetically by route name A-Z', 'short' => 's', 'boolean' => true, + ]) + ->addOption('with-middlewares', [ + 'help' => 'Show route specific middlewares', + 'short' => 'm', + 'boolean' => true, ]); return $parser; diff --git a/app/vendor/cakephp/cakephp/src/Command/RoutesGenerateCommand.php b/app/vendor/cakephp/cakephp/src/Command/RoutesGenerateCommand.php index 35eae5cc2..483683174 100644 --- a/app/vendor/cakephp/cakephp/src/Command/RoutesGenerateCommand.php +++ b/app/vendor/cakephp/cakephp/src/Command/RoutesGenerateCommand.php @@ -35,6 +35,14 @@ public static function defaultName(): string return 'routes generate'; } + /** + * @inheritDoc + */ + public static function getDescription(): string + { + return 'Check a routing array against the routes.'; + } + /** * Display all routes in an application * @@ -47,9 +55,9 @@ public function execute(Arguments $args, ConsoleIo $io): ?int try { $args = $this->_splitArgs($args->getArguments()); $url = Router::url($args); - $io->out("> $url"); + $io->out("> {$url}"); $io->out(); - } catch (MissingRouteException $e) { + } catch (MissingRouteException) { $io->err('The provided parameters do not match any routes.'); $io->out(); @@ -69,8 +77,8 @@ protected function _splitArgs(array $args): array { $out = []; foreach ($args as $arg) { - if (strpos($arg, ':') !== false) { - [$key, $value] = explode(':', $arg); + if (str_contains($arg, ':')) { + [$key, $value] = explode(':', $arg, 2); if (in_array($value, ['true', 'false'], true)) { $value = $value === 'true'; } @@ -91,13 +99,13 @@ protected function _splitArgs(array $args): array */ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser { - $parser->setDescription( - 'Check a routing array against the routes. ' . + $parser->setDescription([ + static::getDescription(), 'Will output the URL if there is a match.' . "\n\n" . 'Routing parameters should be supplied in a key:value format. ' . - 'For example `controller:Articles action:view 2`' - ); + 'For example `controller:Articles action:view 2`', + ]); return $parser; } diff --git a/app/vendor/cakephp/cakephp/src/Command/SchemacacheBuildCommand.php b/app/vendor/cakephp/cakephp/src/Command/SchemacacheBuildCommand.php index cd556a8f7..0fc323b57 100644 --- a/app/vendor/cakephp/cakephp/src/Command/SchemacacheBuildCommand.php +++ b/app/vendor/cakephp/cakephp/src/Command/SchemacacheBuildCommand.php @@ -19,6 +19,7 @@ use Cake\Console\Arguments; use Cake\Console\ConsoleIo; use Cake\Console\ConsoleOptionParser; +use Cake\Database\Connection; use Cake\Database\SchemaCache; use Cake\Datasource\ConnectionManager; use RuntimeException; @@ -38,6 +39,14 @@ public static function defaultName(): string return 'schema_cache build'; } + /** + * @inheritDoc + */ + public static function getDescription(): string + { + return 'Build all metadata caches for the connection.'; + } + /** * Display all routes in an application * @@ -48,8 +57,8 @@ public static function defaultName(): string public function execute(Arguments $args, ConsoleIo $io): ?int { try { - /** @var \Cake\Database\Connection $connection */ $connection = ConnectionManager::get((string)$args->getOption('connection')); + assert($connection instanceof Connection); $cache = new SchemaCache($connection); } catch (RuntimeException $e) { @@ -60,7 +69,7 @@ public function execute(Arguments $args, ConsoleIo $io): ?int $tables = $cache->build($args->getArgument('name')); foreach ($tables as $table) { - $io->verbose(sprintf('Cached "%s"', $table)); + $io->verbose(sprintf('Cached `%s`', $table)); } $io->out('Cache build complete'); @@ -76,10 +85,10 @@ public function execute(Arguments $args, ConsoleIo $io): ?int */ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser { - $parser->setDescription( - 'Build all metadata caches for the connection. If a ' . - 'table name is provided, only that table will be cached.' - )->addOption('connection', [ + $parser->setDescription([ + static::getDescription(), + ' If a table name is provided, only that table will be cached.', + ])->addOption('connection', [ 'help' => 'The connection to build/clear metadata cache data for.', 'short' => 'c', 'default' => 'default', diff --git a/app/vendor/cakephp/cakephp/src/Command/SchemacacheClearCommand.php b/app/vendor/cakephp/cakephp/src/Command/SchemacacheClearCommand.php index fedf74e28..bd5cc3305 100644 --- a/app/vendor/cakephp/cakephp/src/Command/SchemacacheClearCommand.php +++ b/app/vendor/cakephp/cakephp/src/Command/SchemacacheClearCommand.php @@ -19,6 +19,7 @@ use Cake\Console\Arguments; use Cake\Console\ConsoleIo; use Cake\Console\ConsoleOptionParser; +use Cake\Database\Connection; use Cake\Database\SchemaCache; use Cake\Datasource\ConnectionManager; use RuntimeException; @@ -38,6 +39,14 @@ public static function defaultName(): string return 'schema_cache clear'; } + /** + * @inheritDoc + */ + public static function getDescription(): string + { + return 'Clear all metadata caches for the connection.'; + } + /** * Display all routes in an application * @@ -48,8 +57,8 @@ public static function defaultName(): string public function execute(Arguments $args, ConsoleIo $io): ?int { try { - /** @var \Cake\Database\Connection $connection */ $connection = ConnectionManager::get((string)$args->getOption('connection')); + assert($connection instanceof Connection); $cache = new SchemaCache($connection); } catch (RuntimeException $e) { @@ -60,7 +69,7 @@ public function execute(Arguments $args, ConsoleIo $io): ?int $tables = $cache->clear($args->getArgument('name')); foreach ($tables as $table) { - $io->verbose(sprintf('Cleared "%s"', $table)); + $io->verbose(sprintf('Cleared `%s`', $table)); } $io->out('Cache clear complete'); @@ -76,10 +85,10 @@ public function execute(Arguments $args, ConsoleIo $io): ?int */ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser { - $parser->setDescription( - 'Clear all metadata caches for the connection. If a ' . - 'table name is provided, only that table will be removed.' - )->addOption('connection', [ + $parser->setDescription([ + static::getDescription(), + 'If a table name is provided, only that table will be removed.', + ])->addOption('connection', [ 'help' => 'The connection to build/clear metadata cache data for.', 'short' => 'c', 'default' => 'default', diff --git a/app/vendor/cakephp/cakephp/src/Command/ServerCommand.php b/app/vendor/cakephp/cakephp/src/Command/ServerCommand.php index 8019638f9..831261718 100644 --- a/app/vendor/cakephp/cakephp/src/Command/ServerCommand.php +++ b/app/vendor/cakephp/cakephp/src/Command/ServerCommand.php @@ -47,28 +47,36 @@ class ServerCommand extends Command * * @var string */ - protected $_host = self::DEFAULT_HOST; + protected string $_host = self::DEFAULT_HOST; /** * listen port * * @var int */ - protected $_port = self::DEFAULT_PORT; + protected int $_port = self::DEFAULT_PORT; /** * document root * * @var string */ - protected $_documentRoot = WWW_ROOT; + protected string $_documentRoot = WWW_ROOT; /** * ini path * * @var string */ - protected $_iniPath = ''; + protected string $_iniPath = ''; + + /** + * @inheritDoc + */ + public static function getDescription(): string + { + return 'PHP Built-in Server for CakePHP'; + } /** * Starts up the Command and displays the welcome message. @@ -77,7 +85,7 @@ class ServerCommand extends Command * @param \Cake\Console\Arguments $args The command arguments. * @param \Cake\Console\ConsoleIo $io The console io * @return void - * @link https://book.cakephp.org/4/en/console-and-shells.html#hook-methods + * @link https://book.cakephp.org/5/en/console-and-shells.html#hook-methods */ protected function startup(Arguments $args, ConsoleIo $io): void { @@ -133,10 +141,10 @@ public function execute(Arguments $args, ConsoleIo $io): ?int $phpBinary, $this->_host, $this->_port, - escapeshellarg($this->_documentRoot) + escapeshellarg($this->_documentRoot), ); - if (!empty($this->_iniPath)) { + if ($this->_iniPath) { $command = sprintf('%s -c %s', $command, $this->_iniPath); } @@ -159,8 +167,8 @@ public function execute(Arguments $args, ConsoleIo $io): ?int public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser { $parser->setDescription([ - 'PHP Built-in Server for CakePHP', - '[WARN] Don\'t use this in a production environment', + static::getDescription(), + "[WARN] Don't use this in a production environment", ])->addOption('host', [ 'short' => 'H', 'help' => 'ServerHost', diff --git a/app/vendor/cakephp/cakephp/src/Command/VersionCommand.php b/app/vendor/cakephp/cakephp/src/Command/VersionCommand.php index 8f8a13ee0..d56891a0f 100644 --- a/app/vendor/cakephp/cakephp/src/Command/VersionCommand.php +++ b/app/vendor/cakephp/cakephp/src/Command/VersionCommand.php @@ -25,12 +25,20 @@ */ class VersionCommand extends Command { + /** + * @inheritDoc + */ + public static function getDescription(): string + { + return 'Show the CakePHP version.'; + } + /** * Print out the version of CakePHP in use. * * @param \Cake\Console\Arguments $args The command arguments. * @param \Cake\Console\ConsoleIo $io The console io - * @return int + * @return int|null */ public function execute(Arguments $args, ConsoleIo $io): ?int { diff --git a/app/vendor/cakephp/cakephp/src/Console/Arguments.php b/app/vendor/cakephp/cakephp/src/Console/Arguments.php index 5de44d9dd..fb126afe0 100644 --- a/app/vendor/cakephp/cakephp/src/Console/Arguments.php +++ b/app/vendor/cakephp/cakephp/src/Console/Arguments.php @@ -16,6 +16,9 @@ */ namespace Cake\Console; +use Cake\Console\Exception\ConsoleException; +use function Cake\Core\deprecationWarning; + /** * Provides an interface for interacting with * a command's options and arguments. @@ -27,27 +30,27 @@ class Arguments * * @var array */ - protected $argNames; + protected array $argNames; /** * Positional arguments. * - * @var array + * @var array|string> */ - protected $args; + protected array $args; /** * Named options * - * @var array + * @var array|string|bool|null> */ - protected $options; + protected array $options; /** * Constructor * - * @param array $args Positional arguments - * @param array $options Named arguments + * @param array|string> $args Positional arguments + * @param array|string|bool|null> $options Named arguments * @param array $argNames List of argument names. Order is expected to be * the same as $args. */ @@ -61,7 +64,7 @@ public function __construct(array $args, array $options, array $argNames) /** * Get all positional arguments. * - * @return array + * @return array|string> */ public function getArguments(): array { @@ -76,15 +79,48 @@ public function getArguments(): array */ public function getArgumentAt(int $index): ?string { - if ($this->hasArgumentAt($index)) { - return $this->args[$index]; + if (!$this->hasArgumentAt($index)) { + return null; + } + + $value = $this->args[$index]; + + if ($value !== null && !is_string($value)) { + throw new ConsoleException(sprintf( + 'Argument at index `%d` is not of type `string`, use `getArrayArgument()` instead.', + $index, + )); + } + + return $value; + } + + /** + * Get positional arguments (multiple) by index. + * + * @param int $index The argument index to access. + * @return array|null The argument value or null + */ + public function getArrayArgumentAt(int $index): ?array + { + if (!$this->hasArgumentAt($index)) { + return null; + } + + $value = $this->args[$index]; + + if ($value !== null && !is_array($value)) { + throw new ConsoleException(sprintf( + 'Argument at index `%d` is not of type `array`, use `getArgument()` instead.', + $index, + )); } - return null; + return $value; } /** - * Check if a positional argument exists + * Check if a positional argument exists by index * * @param int $index The argument index to check. * @return bool @@ -111,25 +147,55 @@ public function hasArgument(string $name): bool } /** - * Check if a positional argument exists by name + * Returns positional argument value by name or null if doesn't exist * * @param string $name The argument name to check. * @return string|null */ public function getArgument(string $name): ?string { + $this->assertArgumentExists($name); + $offset = array_search($name, $this->argNames, true); - if ($offset === false || !isset($this->args[$offset])) { - return null; + $value = $this->args[$offset] ?? null; + + if ($value !== null && !is_string($value)) { + throw new ConsoleException(sprintf( + 'Argument `%s` is not of type `string`, use `getArrayArgument()` instead.', + $name, + )); + } + + return $value; + } + + /** + * Gets a multiple (array) argument's value or null if not set. + * + * @param string $name Argument name. + * @return array|null + */ + public function getArrayArgument(string $name): ?array + { + $this->assertArgumentExists($name); + + $offset = array_search($name, $this->argNames, true); + $value = $this->args[$offset] ?? null; + + if ($value !== null && !is_array($value)) { + throw new ConsoleException(sprintf( + 'Argument `%s` is not of type `array`, use `getArgument()` instead.', + $name, + )); } - return $this->args[$offset]; + return $value; } /** * Get an array of all the options * - * @return array + * @return array|string|bool|null> */ public function getOptions(): array { @@ -137,14 +203,77 @@ public function getOptions(): array } /** - * Get an option's value or null + * Get a non-multiple option's value or null if not set. * * @param string $name The name of the option to check. - * @return string|int|bool|null The option value or null. + * @return string|bool|null */ - public function getOption(string $name) + public function getOption(string $name): string|bool|null { - return $this->options[$name] ?? null; + $value = $this->options[$name] ?? null; + if (is_array($value)) { + throw new ConsoleException(sprintf( + 'Cannot get multiple values for option `%s`, use `getArrayOption()` instead.', + $name, + )); + } + + assert($value === null || is_string($value) || is_bool($value)); + + return $value; + } + + /** + * Get a boolean option's value or null if not set. + * + * @param string $name Option name. + * @return bool|null + */ + public function getBooleanOption(string $name): ?bool + { + $value = $this->options[$name] ?? null; + if ($value !== null && !is_bool($value)) { + throw new ConsoleException(sprintf( + 'Option `%s` is not of type `bool`, use `getOption()` instead.', + $name, + )); + } + + return $value; + } + + /** + * Gets a multiple option's value or null if not set. + * + * @return array|null + * @deprecated 5.2.0 Use getArrayOption instead. + */ + public function getMultipleOption(string $name): ?array + { + deprecationWarning( + '5.2.0', + 'getMultipleOption() is deprecated. Use `getArrayOption()` instead.', + ); + + return $this->getArrayOption($name); + } + + /** + * Gets a multiple (array) option's value or null if not set. + * + * @return array|null + */ + public function getArrayOption(string $name): ?array + { + $value = $this->options[$name] ?? null; + if ($value !== null && !is_array($value)) { + throw new ConsoleException(sprintf( + 'Option `%s` is not of type `array`, use `getOption()` instead.', + $name, + )); + } + + return $value; } /** @@ -157,4 +286,20 @@ public function hasOption(string $name): bool { return isset($this->options[$name]); } + + /** + * @param string $name + * @return void + */ + protected function assertArgumentExists(string $name): void + { + if (in_array($name, $this->argNames, true)) { + return; + } + + throw new ConsoleException(sprintf( + 'Argument `%s` is not defined on this Command. Could this be an option maybe?', + $name, + )); + } } diff --git a/app/vendor/cakephp/cakephp/src/Console/BaseCommand.php b/app/vendor/cakephp/cakephp/src/Console/BaseCommand.php index b9b13e47a..646db2984 100644 --- a/app/vendor/cakephp/cakephp/src/Console/BaseCommand.php +++ b/app/vendor/cakephp/cakephp/src/Console/BaseCommand.php @@ -18,10 +18,9 @@ use Cake\Console\Exception\ConsoleException; use Cake\Console\Exception\StopException; +use Cake\Event\EventDispatcherInterface; +use Cake\Event\EventDispatcherTrait; use Cake\Utility\Inflector; -use InvalidArgumentException; -use RuntimeException; -use function Cake\Core\getTypeName; /** * Base class for console commands. @@ -31,26 +30,44 @@ * - `initialize` Acts as a post-construct hook. * - `buildOptionParser` Build/Configure the option parser for your command. * - `execute` Execute your command with parsed Arguments and ConsoleIo + * + * @implements \Cake\Event\EventDispatcherInterface<\Cake\Command\Command> */ -abstract class BaseCommand implements CommandInterface +abstract class BaseCommand implements CommandInterface, EventDispatcherInterface { + /** + * @use \Cake\Event\EventDispatcherTrait<\Cake\Command\Command> + */ + use EventDispatcherTrait; + /** * The name of this command. * * @var string */ - protected $name = 'cake unknown'; + protected string $name = 'cake unknown'; + + protected ?CommandFactoryInterface $factory = null; + + /** + * Constructor + * + * @param \Cake\Console\CommandFactoryInterface|null $factory Command factory instance. + */ + public function __construct(?CommandFactoryInterface $factory = null) + { + $this->factory = $factory; + } /** * @inheritDoc */ public function setName(string $name) { - if (strpos($name, ' ') < 1) { - throw new InvalidArgumentException( - "The name '{$name}' is missing a space. Names should look like `cake routes`" - ); - } + assert( + str_contains($name, ' ') && !str_starts_with($name, ' '), + "The name '{$name}' is missing a space. Names should look like `cake routes`", + ); $this->name = $name; return $this; @@ -100,7 +117,6 @@ public function getRootName(): string public static function defaultName(): string { $pos = strrpos(static::class, '\\'); - /** @psalm-suppress PossiblyFalseOperand */ $name = substr(static::class, $pos + 1, -7); return Inflector::underscore($name); @@ -112,7 +128,7 @@ public static function defaultName(): string * You can override buildOptionParser() to define your options & arguments. * * @return \Cake\Console\ConsoleOptionParser - * @throws \RuntimeException When the parser is invalid + * @throws \Cake\Core\Exception\CakeException When the parser is invalid */ public function getOptionParser(): ConsoleOptionParser { @@ -121,14 +137,7 @@ public function getOptionParser(): ConsoleOptionParser $parser->setRootName($root); $parser->setDescription(static::getDescription()); - $parser = $this->buildOptionParser($parser); - if ($parser->subcommands()) { - throw new RuntimeException( - 'You cannot add sub-commands to `Command` sub-classes. Instead make a separate command.' - ); - } - - return $parser; + return $this->buildOptionParser($parser); } /** @@ -168,7 +177,7 @@ public function run(array $argv, ConsoleIo $io): ?int $args = new Arguments( $arguments, $options, - $parser->argumentNames() + $parser->argumentNames(), ); } catch (ConsoleException $e) { $io->err('Error: ' . $e->getMessage()); @@ -187,7 +196,12 @@ public function run(array $argv, ConsoleIo $io): ?int $io->setInteractive(false); } - return $this->execute($args, $io); + $this->dispatchEvent('Command.beforeExecute', ['args' => $args]); + /** @var int|null $result */ + $result = $this->execute($args, $io); + $this->dispatchEvent('Command.afterExecute', ['args' => $args, 'result' => $result]); + + return $result; } /** @@ -206,7 +220,7 @@ protected function displayHelp(ConsoleOptionParser $parser, Arguments $args, Con $io->setOutputAs(ConsoleOutput::RAW); } - $io->out($parser->help(null, $format)); + $io->out($parser->help($format)); } /** @@ -235,6 +249,7 @@ protected function setOutputLevel(Arguments $args, ConsoleIo $io): void * @param \Cake\Console\Arguments $args The command arguments. * @param \Cake\Console\ConsoleIo $io The console io * @return int|null|void The exit code or null for success + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingNativeTypeHint */ abstract public function execute(Arguments $args, ConsoleIo $io); @@ -243,10 +258,9 @@ abstract public function execute(Arguments $args, ConsoleIo $io); * * @param int $code The exit code to use. * @throws \Cake\Console\Exception\StopException - * @return void - * @psalm-return never-return + * @return never */ - public function abort(int $code = self::CODE_ERROR): void + public function abort(int $code = self::CODE_ERROR): never { throw new StopException('Command aborted', $code); } @@ -263,19 +277,15 @@ public function abort(int $code = self::CODE_ERROR): void * @param \Cake\Console\ConsoleIo|null $io The ConsoleIo instance to use for the executed command. * @return int|null The exit code or null for success of the command. */ - public function executeCommand($command, array $args = [], ?ConsoleIo $io = null): ?int + public function executeCommand(CommandInterface|string $command, array $args = [], ?ConsoleIo $io = null): ?int { if (is_string($command)) { - if (!class_exists($command)) { - throw new InvalidArgumentException("Command class '{$command}' does not exist."); - } - $command = new $command(); - } - if (!$command instanceof CommandInterface) { - $commandType = getTypeName($command); - throw new InvalidArgumentException( - "Command '{$commandType}' is not a subclass of Cake\Console\CommandInterface." + assert( + is_subclass_of($command, CommandInterface::class), + sprintf('Command `%s` is not a subclass of `%s`.', $command, CommandInterface::class), ); + + $command = $this->factory?->create($command) ?? new $command(); } $io = $io ?: new ConsoleIo(); diff --git a/app/vendor/cakephp/cakephp/src/Console/Command.php b/app/vendor/cakephp/cakephp/src/Console/Command.php deleted file mode 100644 index 5062c8ddc..000000000 --- a/app/vendor/cakephp/cakephp/src/Console/Command.php +++ /dev/null @@ -1,9 +0,0 @@ - $commands The command collection to output. * @return void */ protected function asText(ConsoleIo $io, iterable $commands): void @@ -85,7 +86,7 @@ protected function asText(ConsoleIo $io, iterable $commands): void $invert = []; foreach ($commands as $name => $class) { if (is_object($class)) { - $class = get_class($class); + $class = $class::class; } if (!isset($invert[$class])) { $invert[$class] = []; @@ -95,20 +96,20 @@ protected function asText(ConsoleIo $io, iterable $commands): void $grouped = []; $plugins = Plugin::loaded(); foreach ($invert as $class => $names) { - preg_match('/^(.+)\\\\(Command|Shell)\\\\/', $class, $matches); + preg_match('/^(.+)\\\\Command\\\\/', $class, $matches); // Probably not a useful class - if (empty($matches)) { + if (!$matches) { continue; } $namespace = str_replace('\\', '/', $matches[1]); - $prefix = 'App'; + $prefix = 'app'; if ($namespace === 'Cake') { - $prefix = 'CakePHP'; + $prefix = 'cakephp'; } elseif (in_array($namespace, $plugins, true)) { - $prefix = $namespace; + $prefix = Inflector::underscore($namespace); } $shortestName = $this->getShortestName($names); - if (strpos($shortestName, '.') !== false) { + if (str_contains($shortestName, '.')) { [, $shortestName] = explode('.', $shortestName, 2); } @@ -119,6 +120,16 @@ protected function asText(ConsoleIo $io, iterable $commands): void } ksort($grouped); + if (isset($grouped['CakePHP'])) { + $cakephp = $grouped['CakePHP']; + $grouped = ['CakePHP' => $cakephp] + $grouped; + } + + if (isset($grouped['App'])) { + $app = $grouped['App']; + $grouped = ['App' => $app] + $grouped; + } + $this->outputPaths($io); $io->out('Available Commands:', 2); @@ -159,7 +170,7 @@ protected function outputPaths(ConsoleIo $io): void if (defined('CORE_PATH')) { $paths['core'] = rtrim(CORE_PATH, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; } - if (!count($paths)) { + if ($paths === []) { return; } $io->out('Current Paths:', 2); @@ -172,13 +183,10 @@ protected function outputPaths(ConsoleIo $io): void /** * @param array $names Names * @return string + * @phpstan-param non-empty-array $names */ protected function getShortestName(array $names): string { - if (count($names) <= 1) { - return array_shift($names); - } - usort($names, function ($a, $b) { return strlen($a) - strlen($b); }); @@ -190,7 +198,7 @@ protected function getShortestName(array $names): string * Output as XML * * @param \Cake\Console\ConsoleIo $io The console io - * @param iterable $commands The command collection to output + * @param iterable $commands The command collection to output * @return void */ protected function asXml(ConsoleIo $io, iterable $commands): void @@ -198,7 +206,7 @@ protected function asXml(ConsoleIo $io, iterable $commands): void $shells = new SimpleXMLElement(''); foreach ($commands as $name => $class) { if (is_object($class)) { - $class = get_class($class); + $class = $class::class; } $shell = $shells->addChild('shell'); $shell->addAttribute('name', $name); @@ -207,7 +215,7 @@ protected function asXml(ConsoleIo $io, iterable $commands): void $shell->addAttribute('help', $name . ' -h'); } $io->setOutputAs(ConsoleOutput::RAW); - $io->out($shells->saveXML()); + $io->out((string)$shells->saveXML()); } /** @@ -219,7 +227,7 @@ protected function asXml(ConsoleIo $io, iterable $commands): void protected function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser { $parser->setDescription( - 'Get the list of available commands for this application.' + 'Get the list of available commands for this application.', )->addOption('xml', [ 'help' => 'Get the listing as XML.', 'boolean' => true, diff --git a/app/vendor/cakephp/cakephp/src/Console/CommandCollection.php b/app/vendor/cakephp/cakephp/src/Console/CommandCollection.php index 16934f3a9..d656f29e8 100644 --- a/app/vendor/cakephp/cakephp/src/Console/CommandCollection.php +++ b/app/vendor/cakephp/cakephp/src/Console/CommandCollection.php @@ -29,23 +29,21 @@ * CakePHP will use the mapped commands to construct and dispatch * shell commands. * - * @template-implements \IteratorAggregate> + * @template-implements \IteratorAggregate> */ class CommandCollection implements IteratorAggregate, Countable { /** * Command list * - * @var array - * @psalm-var array - * @psalm-suppress DeprecatedClass + * @var array> */ - protected $commands = []; + protected array $commands = []; /** * Constructor * - * @param array $commands The map of commands to add to the collection. + * @param array> $commands The map of commands to add to the collection. */ public function __construct(array $commands = []) { @@ -58,25 +56,28 @@ public function __construct(array $commands = []) * Add a command to the collection * * @param string $name The name of the command you want to map. - * @param \Cake\Console\CommandInterface|\Cake\Console\Shell|string $command The command to map. - * Can be a FQCN, Shell instance or CommandInterface instance. + * @param \Cake\Console\CommandInterface|class-string<\Cake\Console\CommandInterface> $command The command to map. + * Can be a FQCN or CommandInterface instance. * @return $this * @throws \InvalidArgumentException */ - public function add(string $name, $command) + public function add(string $name, CommandInterface|string $command) { - if (!is_subclass_of($command, Shell::class) && !is_subclass_of($command, CommandInterface::class)) { - $class = is_string($command) ? $command : get_class($command); - throw new InvalidArgumentException(sprintf( - "Cannot use '%s' for command '%s'. " . - "It is not a subclass of Cake\Console\Shell or Cake\Command\Command.", - $class, - $name - )); + if (is_string($command)) { + assert( + is_subclass_of($command, CommandInterface::class), + sprintf( + 'Cannot use `%s` for command `%s`. ' . + 'It is not a subclass of `%s`.', + $command, + $name, + CommandInterface::class, + ), + ); } if (!preg_match('/^[^\s]+(?:(?: [^\s]+){1,2})?$/ui', $name)) { throw new InvalidArgumentException( - "The command name `{$name}` is invalid. Names can only be a maximum of three words." + "The command name `{$name}` is invalid. Names can only be a maximum of three words.", ); } @@ -88,7 +89,7 @@ public function add(string $name, $command) /** * Add multiple commands at once. * - * @param array $commands A map of command names => command classes/instances. + * @param array> $commands A map of command names => command classes/instances. * @return $this * @see \Cake\Console\CommandCollection::add() */ @@ -129,14 +130,13 @@ public function has(string $name): bool * Get the target for a command. * * @param string $name The named shell. - * @return \Cake\Console\CommandInterface|\Cake\Console\Shell|class-string<\Cake\Console\CommandInterface> Either the command class or an instance. + * @return \Cake\Console\CommandInterface|class-string<\Cake\Console\CommandInterface> Either the command class or an instance. * @throws \InvalidArgumentException when unknown commands are fetched. - * @psalm-return \Cake\Console\CommandInterface|\Cake\Console\Shell|class-string */ - public function get(string $name) + public function get(string $name): CommandInterface|string { if (!$this->has($name)) { - throw new InvalidArgumentException("The $name is not a known command name."); + throw new InvalidArgumentException(sprintf('The `%s` is not a known command name.', $name)); } return $this->commands[$name]; @@ -146,7 +146,7 @@ public function get(string $name) * Implementation of IteratorAggregate. * * @return \Traversable - * @psalm-return \Traversable)> + * @phpstan-return \Traversable> */ public function getIterator(): Traversable { @@ -166,7 +166,7 @@ public function count(): int } /** - * Auto-discover shell & commands from the named plugin. + * Auto-discover commands from the named plugin. * * Discovered commands will have their names de-duplicated with * existing commands in the collection. If a command is already @@ -174,7 +174,7 @@ public function count(): int * the long name (`plugin.command`) will be returned. * * @param string $plugin The plugin to scan. - * @return array Discovered plugin commands. + * @return array> Discovered plugin commands. */ public function discoverPlugin(string $plugin): array { @@ -187,8 +187,8 @@ public function discoverPlugin(string $plugin): array /** * Resolve names based on existing commands * - * @param array $input The results of a CommandScanner operation. - * @return array A flat map of command names => class names. + * @param array> $input The results of a CommandScanner operation. + * @return array> A flat map of command names => class names. */ protected function resolveNames(array $input): array { @@ -204,9 +204,11 @@ protected function resolveNames(array $input): array $name = $info['fullName']; } - $out[$name] = $info['class']; + /** @var class-string<\Cake\Console\CommandInterface> $class */ + $class = $info['class']; + $out[$name] = $class; if ($addLong) { - $out[$info['fullName']] = $info['class']; + $out[$info['fullName']] = $class; } } @@ -214,7 +216,7 @@ protected function resolveNames(array $input): array } /** - * Automatically discover shell commands in CakePHP, the application and all plugins. + * Automatically discover commands in CakePHP, the application and all plugins. * * Commands will be located using filesystem conventions. Commands are * discovered in the following order: @@ -225,7 +227,7 @@ protected function resolveNames(array $input): array * Commands defined in the application will overwrite commands with * the same name provided by CakePHP. * - * @return array An array of command names and their classes. + * @return array> An array of command names and their classes. */ public function autoDiscover(): array { diff --git a/app/vendor/cakephp/cakephp/src/Console/CommandFactory.php b/app/vendor/cakephp/cakephp/src/Console/CommandFactory.php index 364bb4b16..43a4a4e44 100644 --- a/app/vendor/cakephp/cakephp/src/Console/CommandFactory.php +++ b/app/vendor/cakephp/cakephp/src/Console/CommandFactory.php @@ -15,20 +15,19 @@ namespace Cake\Console; use Cake\Core\ContainerInterface; -use InvalidArgumentException; /** - * This is a factory for creating Command and Shell instances. + * This is a factory for creating Command instances. * * This factory can be replaced or extended if you need to customize building - * your command and shell objects. + * your command objects. */ class CommandFactory implements CommandFactoryInterface { /** * @var \Cake\Core\ContainerInterface|null */ - protected $container; + protected ?ContainerInterface $container = null; /** * Constructor @@ -43,21 +42,13 @@ public function __construct(?ContainerInterface $container = null) /** * @inheritDoc */ - public function create(string $className) + public function create(string $className): CommandInterface { - if ($this->container && $this->container->has($className)) { - $command = $this->container->get($className); - } else { - $command = new $className(); + if ($this->container?->has($className)) { + return $this->container->get($className); } - if (!($command instanceof CommandInterface) && !($command instanceof Shell)) { - /** @psalm-suppress DeprecatedClass */ - $valid = implode('` or `', [Shell::class, CommandInterface::class]); - $message = sprintf('Class `%s` must be an instance of `%s`.', $className, $valid); - throw new InvalidArgumentException($message); - } - - return $command; + /** @var \Cake\Console\CommandInterface */ + return new $className($this); } } diff --git a/app/vendor/cakephp/cakephp/src/Console/CommandFactoryInterface.php b/app/vendor/cakephp/cakephp/src/Console/CommandFactoryInterface.php index 67284a416..1a8bb995b 100644 --- a/app/vendor/cakephp/cakephp/src/Console/CommandFactoryInterface.php +++ b/app/vendor/cakephp/cakephp/src/Console/CommandFactoryInterface.php @@ -20,10 +20,10 @@ interface CommandFactoryInterface { /** - * The factory method for creating Command and Shell instances. + * The factory method for creating Command instances. * - * @param string $className Command/Shell class name. - * @return \Cake\Console\Shell|\Cake\Console\CommandInterface + * @param string $className Command class name. + * @return \Cake\Console\CommandInterface */ - public function create(string $className); + public function create(string $className): CommandInterface; } diff --git a/app/vendor/cakephp/cakephp/src/Console/CommandInterface.php b/app/vendor/cakephp/cakephp/src/Console/CommandInterface.php index fa5822afe..d0e8205e0 100644 --- a/app/vendor/cakephp/cakephp/src/Console/CommandInterface.php +++ b/app/vendor/cakephp/cakephp/src/Console/CommandInterface.php @@ -45,7 +45,6 @@ interface CommandInterface * * @param string $name The name the command uses in the collection. * @return $this - * @throws \InvalidArgumentException */ public function setName(string $name); diff --git a/app/vendor/cakephp/cakephp/src/Console/CommandRunner.php b/app/vendor/cakephp/cakephp/src/Console/CommandRunner.php index 8e84e7ea0..774b8ed42 100644 --- a/app/vendor/cakephp/cakephp/src/Console/CommandRunner.php +++ b/app/vendor/cakephp/cakephp/src/Console/CommandRunner.php @@ -22,6 +22,7 @@ use Cake\Console\Exception\StopException; use Cake\Core\ConsoleApplicationInterface; use Cake\Core\ContainerApplicationInterface; +use Cake\Core\EventAwareApplicationInterface; use Cake\Core\PluginApplicationInterface; use Cake\Event\EventDispatcherInterface; use Cake\Event\EventDispatcherTrait; @@ -30,14 +31,17 @@ use Cake\Routing\Router; use Cake\Routing\RoutingApplicationInterface; use Cake\Utility\Inflector; -use InvalidArgumentException; -use RuntimeException; /** * Run CLI commands for the provided application. + * + * @implements \Cake\Event\EventDispatcherInterface<\Cake\Core\ConsoleApplicationInterface> */ class CommandRunner implements EventDispatcherInterface { + /** + * @use \Cake\Event\EventDispatcherTrait<\Cake\Core\ConsoleApplicationInterface> + */ use EventDispatcherTrait; /** @@ -45,28 +49,32 @@ class CommandRunner implements EventDispatcherInterface * * @var \Cake\Core\ConsoleApplicationInterface */ - protected $app; + protected ConsoleApplicationInterface $app; /** * The application console commands are being run for. * * @var \Cake\Console\CommandFactoryInterface|null */ - protected $factory; + protected ?CommandFactoryInterface $factory = null; /** * The root command name. Defaults to `cake`. * * @var string */ - protected $root; + protected string $root; /** * Alias mappings. * - * @var array + * @var array */ - protected $aliases = []; + protected array $aliases = [ + '--version' => 'version', + '--help' => 'help', + '-h' => 'help', + ]; /** * Constructor @@ -78,16 +86,11 @@ class CommandRunner implements EventDispatcherInterface public function __construct( ConsoleApplicationInterface $app, string $root = 'cake', - ?CommandFactoryInterface $factory = null + ?CommandFactoryInterface $factory = null, ) { $this->app = $app; $this->root = $root; $this->factory = $factory; - $this->aliases = [ - '--version' => 'version', - '--help' => 'help', - '-h' => 'help', - ]; } /** @@ -103,7 +106,7 @@ public function __construct( * $runner->setAliases(['--version' => 'version']); * ``` * - * @param array $aliases The map of aliases to replace. + * @param array $aliases The map of aliases to replace. * @return $this */ public function setAliases(array $aliases) @@ -126,10 +129,11 @@ public function setAliases(array $aliases) * @param array $argv The arguments from the CLI environment. * @param \Cake\Console\ConsoleIo|null $io The ConsoleIo instance. Used primarily for testing. * @return int The exit code of the command. - * @throws \RuntimeException */ public function run(array $argv, ?ConsoleIo $io = null): int { + assert($argv !== [], 'Cannot run any commands. No arguments received.'); + $this->bootstrap(); $commands = new CommandCollection([ @@ -146,9 +150,6 @@ public function run(array $argv, ?ConsoleIo $io = null): int $this->dispatchEvent('Console.buildCommands', ['commands' => $commands]); $this->loadRoutes(); - if (empty($argv)) { - throw new RuntimeException('Cannot run any commands. No arguments received.'); - } // Remove the root executable segment array_shift($argv); @@ -163,19 +164,13 @@ public function run(array $argv, ?ConsoleIo $io = null): int return CommandInterface::CODE_ERROR; } - $result = CommandInterface::CODE_ERROR; - $shell = $this->getCommand($io, $commands, $name); - if ($shell instanceof Shell) { - $result = $this->runShell($shell, $argv); - } - if ($shell instanceof CommandInterface) { - $result = $this->runCommand($shell, $argv, $io); - } + $command = $this->getCommand($io, $commands, $name); + $result = $this->runCommand($command, $argv, $io); - if ($result === null || $result === true) { + if ($result === null) { return CommandInterface::CODE_SUCCESS; } - if (is_int($result) && $result >= 0 && $result <= 255) { + if ($result >= 0 && $result <= 255) { return $result; } @@ -215,22 +210,16 @@ public function getEventManager(): EventManagerInterface /** * Get/set the application's event manager. * - * If the application does not support events and this method is used as - * a setter, an exception will be raised. - * * @param \Cake\Event\EventManagerInterface $eventManager The event manager to set. * @return $this - * @throws \InvalidArgumentException */ public function setEventManager(EventManagerInterface $eventManager) { - if ($this->app instanceof PluginApplicationInterface) { + if ($this->app instanceof EventDispatcherInterface) { $this->app->setEventManager($eventManager); - - return $this; } - throw new InvalidArgumentException('Cannot set the event manager, the application does not support events.'); + return $this; } /** @@ -239,20 +228,17 @@ public function setEventManager(EventManagerInterface $eventManager) * @param \Cake\Console\ConsoleIo $io The IO wrapper for the created shell class. * @param \Cake\Console\CommandCollection $commands The command collection to find the shell in. * @param string $name The command name to find - * @return \Cake\Console\CommandInterface|\Cake\Console\Shell + * @return \Cake\Console\CommandInterface */ - protected function getCommand(ConsoleIo $io, CommandCollection $commands, string $name) + protected function getCommand(ConsoleIo $io, CommandCollection $commands, string $name): CommandInterface { $instance = $commands->get($name); if (is_string($instance)) { - $instance = $this->createCommand($instance, $io); - } - if ($instance instanceof Shell) { - $instance->setRootName($this->root); - } - if ($instance instanceof CommandInterface) { - $instance->setName("{$this->root} {$name}"); + $instance = $this->createCommand($instance); } + + $instance->setName("{$this->root} {$name}"); + if ($instance instanceof CommandCollectionAwareInterface) { $instance->setCommandCollection($commands); } @@ -278,6 +264,14 @@ protected function longestCommandName(CommandCollection $commands, array $argv): if ($commands->has($name)) { return [$name, array_slice($argv, $i)]; } + + $firstChar = $name[0] ?? ''; + if ($firstChar == strtoupper($firstChar) && str_contains($name, '.')) { + $underName = Inflector::underscore($name); + if ($commands->has($underName)) { + return [$underName, array_slice($argv, $i)]; + } + } } $name = array_shift($argv); @@ -313,7 +307,7 @@ protected function resolveName(CommandCollection $commands, ConsoleIo $io, ?stri "Unknown command `{$this->root} {$name}`. " . "Run `{$this->root} --help` to get the list of commands.", $name, - $commands->keys() + $commands->keys(), ); } @@ -331,53 +325,41 @@ protected function resolveName(CommandCollection $commands, ConsoleIo $io, ?stri protected function runCommand(CommandInterface $command, array $argv, ConsoleIo $io): ?int { try { - return $command->run($argv, $io); - } catch (StopException $e) { - return $e->getCode(); - } - } - - /** - * Execute a Shell class. - * - * @param \Cake\Console\Shell $shell The shell to run. - * @param array $argv The CLI arguments to invoke. - * @return int|bool|null Exit code - */ - protected function runShell(Shell $shell, array $argv) - { - try { - $shell->initialize(); + $eventManager = $this->getEventManager(); + if ($this->app instanceof EventAwareApplicationInterface) { + $eventManager = $this->app->events($eventManager); + $eventManager = $this->app->pluginEvents($eventManager); + } + $this->setEventManager($eventManager); + if ($command instanceof EventDispatcherInterface) { + $command->setEventManager($this->getEventManager()); + } - return $shell->runCommand($argv, true); + return $command->run($argv, $io); } catch (StopException $e) { return $e->getCode(); } } /** - * The wrapper for creating shell instances. + * The wrapper for creating command instances. * - * @param string $className Shell class name. - * @param \Cake\Console\ConsoleIo $io The IO wrapper for the created shell class. - * @return \Cake\Console\CommandInterface|\Cake\Console\Shell + * @param string $className Command class name. + * @return \Cake\Console\CommandInterface */ - protected function createCommand(string $className, ConsoleIo $io) + protected function createCommand(string $className): CommandInterface { if (!$this->factory) { $container = null; if ($this->app instanceof ContainerApplicationInterface) { $container = $this->app->getContainer(); } - $this->factory = new CommandFactory($container); - } - $shell = $this->factory->create($className); - if ($shell instanceof Shell) { - $shell->setIo($io); + $this->factory = new CommandFactory($container); + $container?->add(CommandFactoryInterface::class, $this->factory); } - return $shell; + return $this->factory->create($className); } /** diff --git a/app/vendor/cakephp/cakephp/src/Console/CommandScanner.php b/app/vendor/cakephp/cakephp/src/Console/CommandScanner.php index d086679af..3dd778838 100644 --- a/app/vendor/cakephp/cakephp/src/Console/CommandScanner.php +++ b/app/vendor/cakephp/cakephp/src/Console/CommandScanner.php @@ -19,8 +19,9 @@ use Cake\Core\App; use Cake\Core\Configure; use Cake\Core\Plugin; -use Cake\Filesystem\Filesystem; +use Cake\Utility\Filesystem; use Cake\Utility\Inflector; +use ReflectionClass; /** * Used by CommandCollection and CommandTask to scan the filesystem @@ -37,20 +38,12 @@ class CommandScanner */ public function scanCore(): array { - $coreShells = $this->scanDir( - dirname(__DIR__) . DIRECTORY_SEPARATOR . 'Shell' . DIRECTORY_SEPARATOR, - 'Cake\Shell\\', - '', - ['command_list'] - ); - $coreCommands = $this->scanDir( + return $this->scanDir( dirname(__DIR__) . DIRECTORY_SEPARATOR . 'Command' . DIRECTORY_SEPARATOR, 'Cake\Command\\', '', - ['command_list'] + ['command_list'], ); - - return array_merge($coreShells, $coreCommands); } /** @@ -61,20 +54,13 @@ public function scanCore(): array public function scanApp(): array { $appNamespace = Configure::read('App.namespace'); - $appShells = $this->scanDir( - App::classPath('Shell')[0], - $appNamespace . '\Shell\\', - '', - [] - ); - $appCommands = $this->scanDir( + + return $this->scanDir( App::classPath('Command')[0], $appNamespace . '\Command\\', '', - [] + [], ); - - return array_merge($appShells, $appCommands); } /** @@ -92,10 +78,7 @@ public function scanPlugin(string $plugin): array $namespace = str_replace('/', '\\', $plugin); $prefix = Inflector::underscore($plugin) . '.'; - $commands = $this->scanDir($path . 'Command', $namespace . '\Command\\', $prefix, []); - $shells = $this->scanDir($path . 'Shell', $namespace . '\Shell\\', $prefix, []); - - return array_merge($shells, $commands); + return $this->scanDir($path . 'Command', $namespace . '\Command\\', $prefix, []); } /** @@ -117,32 +100,32 @@ protected function scanDir(string $path, string $namespace, string $prefix, arra // This ensures `Command` class is not added to the list. $hide[] = ''; - $classPattern = '/(Shell|Command)\.php$/'; + $classPattern = '/Command\.php$/'; $fs = new Filesystem(); - /** @var array<\SplFileInfo> $files */ + /** @var \Iterator<\SplFileInfo> $files */ $files = $fs->find($path, $classPattern); - $shells = []; + $commands = []; foreach ($files as $fileInfo) { $file = $fileInfo->getFilename(); - $name = Inflector::underscore(preg_replace($classPattern, '', $file)); + $name = Inflector::underscore((string)preg_replace($classPattern, '', $file)); if (in_array($name, $hide, true)) { continue; } $class = $namespace . $fileInfo->getBasename('.php'); - /** @psalm-suppress DeprecatedClass */ - if ( - !is_subclass_of($class, Shell::class) - && !is_subclass_of($class, CommandInterface::class) - ) { + if (!is_subclass_of($class, CommandInterface::class)) { + continue; + } + $reflection = new ReflectionClass($class); + if ($reflection->isAbstract()) { continue; } if (is_subclass_of($class, BaseCommand::class)) { $name = $class::defaultName(); } - $shells[$path . $file] = [ + $commands[$path . $file] = [ 'file' => $path . $file, 'fullName' => $prefix . $name, 'name' => $name, @@ -150,8 +133,8 @@ protected function scanDir(string $path, string $namespace, string $prefix, arra ]; } - ksort($shells); + ksort($commands); - return array_values($shells); + return array_values($commands); } } diff --git a/app/vendor/cakephp/cakephp/src/Console/ConsoleErrorHandler.php b/app/vendor/cakephp/cakephp/src/Console/ConsoleErrorHandler.php deleted file mode 100644 index 27fcf22f8..000000000 --- a/app/vendor/cakephp/cakephp/src/Console/ConsoleErrorHandler.php +++ /dev/null @@ -1,10 +0,0 @@ -_canReadline = (extension_loaded('readline') && $handle === 'php://stdin'); - $this->_input = fopen($handle, 'rb'); + $input = fopen($handle, 'rb'); + if ($input === false) { + throw new CakeException(sprintf('Cannot open handle `%s`', $handle)); + } + + $this->_input = $input; + } + + /** + * Destruct and free resources + * + * @return void + */ + public function __destruct() + { + if (isset($this->_input) && is_resource($this->_input)) { + fclose($this->_input); + } + unset($this->_input); } /** diff --git a/app/vendor/cakephp/cakephp/src/Console/ConsoleInputArgument.php b/app/vendor/cakephp/cakephp/src/Console/ConsoleInputArgument.php index 329189657..eb489698c 100644 --- a/app/vendor/cakephp/cakephp/src/Console/ConsoleInputArgument.php +++ b/app/vendor/cakephp/cakephp/src/Console/ConsoleInputArgument.php @@ -32,35 +32,42 @@ class ConsoleInputArgument * * @var string */ - protected $_name; + protected string $_name; /** * Help string * * @var string */ - protected $_help; + protected string $_help; /** * Is this option required? * * @var bool */ - protected $_required; + protected bool $_required; /** * An array of valid choices for this argument. * * @var array */ - protected $_choices; + protected array $_choices; /** - * Default value for the argument. + * Default value for this argument. * * @var string|null */ - protected $_default; + protected ?string $_default = null; + + /** + * The multiple separator. + * + * @var string|null + */ + protected ?string $_separator = null; /** * Make a new Input Argument @@ -71,19 +78,35 @@ class ConsoleInputArgument * @param array $choices Valid choices for this option. * @param string|null $default The default value for this argument. */ - public function __construct($name, $help = '', $required = false, $choices = [], $default = null) - { + public function __construct( + array|string $name, + string $help = '', + bool $required = false, + array $choices = [], + ?string $default = null, + ?string $separator = null, + ) { if (is_array($name) && isset($name['name'])) { foreach ($name as $key => $value) { $this->{'_' . $key} = $value; } } else { - /** @psalm-suppress PossiblyInvalidPropertyAssignmentValue */ + /** @var string $name */ $this->_name = $name; $this->_help = $help; $this->_required = $required; $this->_choices = $choices; $this->_default = $default; + $this->_separator = $separator; + } + + if ($this->_separator !== null && str_contains($this->_separator, ' ')) { + throw new ConsoleException( + sprintf( + 'The argument separator must not contain spaces for `%s`.', + $this->_name, + ), + ); } } @@ -131,6 +154,9 @@ public function help(int $width = 0): string if ($this->_default !== null) { $optional .= sprintf(' default: "%s"', $this->_default); } + if ($this->_separator) { + $optional .= sprintf(' (separator: "%s")', $this->_separator); + } return sprintf('%s%s%s', $name, $this->_help, $optional); } @@ -148,7 +174,7 @@ public function usage(): string } $name = '<' . $name . '>'; if (!$this->isRequired()) { - $name = '[' . $name . ']'; + return '[' . $name . ']'; } return $name; @@ -159,7 +185,7 @@ public function usage(): string * * @return string|null */ - public function defaultValue() + public function defaultValue(): ?string { return $this->_default; } @@ -174,6 +200,16 @@ public function isRequired(): bool return $this->_required; } + /** + * Get the value of the separator. + * + * @return string|null Value of this->_separator. + */ + public function separator(): ?string + { + return $this->_separator; + } + /** * Check that $value is a valid choice for this argument. * @@ -183,17 +219,24 @@ public function isRequired(): bool */ public function validChoice(string $value): bool { - if (empty($this->_choices)) { + if ($this->_choices === []) { return true; } - if (!in_array($value, $this->_choices, true)) { + if ($value && $this->_separator) { + $values = explode($this->_separator, $value); + } else { + $values = [$value]; + } + + $unwanted = array_filter($values, fn($value) => !in_array($value, $this->_choices, true)); + if ($unwanted) { throw new ConsoleException( sprintf( - '"%s" is not a valid value for %s. Please use one of "%s"', + '`%s` is not a valid value for `%s`. Please use one of `%s`', $value, $this->_name, - implode(', ', $this->_choices) - ) + implode('|', $this->_choices), + ), ); } @@ -212,6 +255,9 @@ public function xml(SimpleXMLElement $parent): SimpleXMLElement $option->addAttribute('name', $this->_name); $option->addAttribute('help', $this->_help); $option->addAttribute('required', (string)(int)$this->isRequired()); + if ($this->separator() !== null) { + $option->addAttribute('separator', $this->separator()); + } $choices = $option->addChild('choices'); foreach ($this->_choices as $valid) { $choices->addChild('choice', $valid); diff --git a/app/vendor/cakephp/cakephp/src/Console/ConsoleInputOption.php b/app/vendor/cakephp/cakephp/src/Console/ConsoleInputOption.php index b6853726c..f7ec7718b 100644 --- a/app/vendor/cakephp/cakephp/src/Console/ConsoleInputOption.php +++ b/app/vendor/cakephp/cakephp/src/Console/ConsoleInputOption.php @@ -32,63 +32,70 @@ class ConsoleInputOption * * @var string */ - protected $_name; + protected string $_name; /** * Short (1 character) alias for the option. * * @var string */ - protected $_short; + protected string $_short; /** * Help text for the option. * * @var string */ - protected $_help; + protected string $_help; /** * Is the option a boolean option. Boolean options do not consume a parameter. * * @var bool */ - protected $_boolean; + protected bool $_boolean; /** * Default value for the option * * @var string|bool|null */ - protected $_default; + protected string|bool|null $_default = null; /** * Can the option accept multiple value definition. * * @var bool */ - protected $_multiple; + protected bool $_multiple; /** * An array of choices for the option. * * @var array */ - protected $_choices; + protected array $_choices; /** * The prompt string * * @var string|null */ - protected $prompt; + protected ?string $prompt = null; /** * Is the option required. * * @var bool */ - protected $required; + protected bool $required; + + /** + * The multiple separator. + * + * @var string|null + */ + protected ?string $_separator = null; /** * Make a new Input Option @@ -109,11 +116,12 @@ public function __construct( string $short = '', string $help = '', bool $isBoolean = false, - $default = null, + string|bool|null $default = null, array $choices = [], bool $multiple = false, bool $required = false, - ?string $prompt = null + ?string $prompt = null, + ?string $separator = null, ) { $this->_name = $name; $this->_short = $short; @@ -123,6 +131,7 @@ public function __construct( $this->_multiple = $multiple; $this->required = $required; $this->prompt = $prompt; + $this->_separator = $separator; if ($isBoolean) { $this->_default = (bool)$default; @@ -132,13 +141,22 @@ public function __construct( if (strlen($this->_short) > 1) { throw new ConsoleException( - sprintf('Short option "%s" is invalid, short options must be one letter.', $this->_short) + sprintf('Short option `%s` is invalid, short options must be one letter.', $this->_short), ); } - if (isset($this->_default) && $this->prompt) { + if ($this->_default !== null && $this->prompt) { throw new ConsoleException( 'You cannot set both `prompt` and `default` options. ' . - 'Use either a static `default` or interactive `prompt`' + 'Use either a static `default` or interactive `prompt`', + ); + } + + if ($this->_separator !== null && str_contains($this->_separator, ' ')) { + throw new ConsoleException( + sprintf( + 'The option separator must not contain spaces for `%s`.', + $this->_name, + ), ); } } @@ -171,13 +189,18 @@ public function short(): string */ public function help(int $width = 0): string { - $default = $short = ''; + $default = ''; + $short = ''; if ($this->_default && $this->_default !== true) { $default = sprintf(' (default: %s)', $this->_default); } if ($this->_choices) { $default .= sprintf(' (choices: %s)', implode('|', $this->_choices)); } + if ($this->_multiple && $this->_separator) { + $default .= sprintf(' (separator: `%s`)', $this->_separator); + } + if ($this->_short !== '') { $short = ', -' . $this->_short; } @@ -221,7 +244,7 @@ public function usage(): string * * @return string|bool|null */ - public function defaultValue() + public function defaultValue(): string|bool|null { return $this->_default; } @@ -263,19 +286,29 @@ public function acceptsMultiple(): bool * @return true * @throws \Cake\Console\Exception\ConsoleException */ - public function validChoice($value): bool + public function validChoice(string|bool $value): bool { - if (empty($this->_choices)) { + if ($this->_choices === []) { return true; } - if (!in_array($value, $this->_choices, true)) { + if (is_string($value) && $this->_separator) { + $values = explode($this->_separator, $value); + } else { + $values = [$value]; + } + if ($this->_boolean) { + $values = array_map('boolval', $values); + } + + $unwanted = array_filter($values, fn($value) => !in_array($value, $this->_choices, true)); + if ($unwanted) { throw new ConsoleException( sprintf( - '"%s" is not a valid value for --%s. Please use one of "%s"', - (string)$value, + '`%s` is not a valid value for `--%s`. Please use one of `%s`', + $value, $this->_name, - implode(', ', $this->_choices) - ) + implode('|', $this->_choices), + ), ); } @@ -285,7 +318,7 @@ public function validChoice($value): bool /** * Get the list of choices this option has. * - * @return array + * @return array */ public function choices(): array { @@ -334,4 +367,14 @@ public function xml(SimpleXMLElement $parent): SimpleXMLElement return $parent; } + + /** + * Get the value of the separator. + * + * @return string|null Value of this->_separator. + */ + public function separator(): ?string + { + return $this->_separator; + } } diff --git a/app/vendor/cakephp/cakephp/src/Console/ConsoleInputSubcommand.php b/app/vendor/cakephp/cakephp/src/Console/ConsoleInputSubcommand.php deleted file mode 100644 index 69d2ffeab..000000000 --- a/app/vendor/cakephp/cakephp/src/Console/ConsoleInputSubcommand.php +++ /dev/null @@ -1,144 +0,0 @@ -|string $name The long name of the subcommand, or an array with all the properties. - * @param string $help The help text for this option. - * @param \Cake\Console\ConsoleOptionParser|array|null $parser A parser for this subcommand. - * Either a ConsoleOptionParser, or an array that can be used with ConsoleOptionParser::buildFromArray(). - */ - public function __construct($name, $help = '', $parser = null) - { - if (is_array($name)) { - $data = $name + ['name' => null, 'help' => '', 'parser' => null]; - if (empty($data['name'])) { - throw new InvalidArgumentException('"name" not provided for console option parser'); - } - - $name = $data['name']; - $help = $data['help']; - $parser = $data['parser']; - } - - if (is_array($parser)) { - $parser['command'] = $name; - $parser = ConsoleOptionParser::buildFromArray($parser); - } - - $this->_name = $name; - $this->_help = $help; - $this->_parser = $parser; - } - - /** - * Get the value of the name attribute. - * - * @return string Value of this->_name. - */ - public function name(): string - { - return $this->_name; - } - - /** - * Get the raw help string for this command - * - * @return string - */ - public function getRawHelp(): string - { - return $this->_help; - } - - /** - * Generate the help for this this subcommand. - * - * @param int $width The width to make the name of the subcommand. - * @return string - */ - public function help(int $width = 0): string - { - $name = $this->_name; - if (strlen($name) < $width) { - $name = str_pad($name, $width, ' '); - } - - return $name . $this->_help; - } - - /** - * Get the usage value for this option - * - * @return \Cake\Console\ConsoleOptionParser|null - */ - public function parser(): ?ConsoleOptionParser - { - return $this->_parser; - } - - /** - * Append this subcommand to the Parent element - * - * @param \SimpleXMLElement $parent The parent element. - * @return \SimpleXMLElement The parent with this subcommand appended. - */ - public function xml(SimpleXMLElement $parent): SimpleXMLElement - { - $command = $parent->addChild('command'); - $command->addAttribute('name', $this->_name); - $command->addAttribute('help', $this->_help); - - return $parent; - } -} diff --git a/app/vendor/cakephp/cakephp/src/Console/ConsoleIo.php b/app/vendor/cakephp/cakephp/src/Console/ConsoleIo.php index 4e5e9c7ea..60c3e84fe 100644 --- a/app/vendor/cakephp/cakephp/src/Console/ConsoleIo.php +++ b/app/vendor/cakephp/cakephp/src/Console/ConsoleIo.php @@ -57,35 +57,35 @@ class ConsoleIo * * @var \Cake\Console\ConsoleOutput */ - protected $_out; + protected ConsoleOutput $_out; /** * The error stream * * @var \Cake\Console\ConsoleOutput */ - protected $_err; + protected ConsoleOutput $_err; /** * The input stream * * @var \Cake\Console\ConsoleInput */ - protected $_in; + protected ConsoleInput $_in; /** * The helper registry. * * @var \Cake\Console\HelperRegistry */ - protected $_helpers; + protected HelperRegistry $_helpers; /** * The current output level. * * @var int */ - protected $_level = self::NORMAL; + protected int $_level = self::NORMAL; /** * The number of bytes last written to the output stream @@ -93,19 +93,19 @@ class ConsoleIo * * @var int */ - protected $_lastWritten = 0; + protected int $_lastWritten = 0; /** * Whether files should be overwritten * * @var bool */ - protected $forceOverwrite = false; + protected bool $forceOverwrite = false; /** * @var bool */ - protected $interactive = true; + protected bool $interactive = true; /** * Constructor @@ -119,7 +119,7 @@ public function __construct( ?ConsoleOutput $out = null, ?ConsoleOutput $err = null, ?ConsoleInput $in = null, - ?HelperRegistry $helpers = null + ?HelperRegistry $helpers = null, ) { $this->_out = $out ?: new ConsoleOutput('php://stdout'); $this->_err = $err ?: new ConsoleOutput('php://stderr'); @@ -160,7 +160,7 @@ public function level(?int $level = null): int * @return int|null The number of bytes returned from writing to stdout * or null if current level is less than ConsoleIo::VERBOSE */ - public function verbose($message, int $newlines = 1): ?int + public function verbose(array|string $message, int $newlines = 1): ?int { return $this->out($message, $newlines, self::VERBOSE); } @@ -173,7 +173,7 @@ public function verbose($message, int $newlines = 1): ?int * @return int|null The number of bytes returned from writing to stdout * or null if current level is less than ConsoleIo::QUIET */ - public function quiet($message, int $newlines = 1): ?int + public function quiet(array|string $message, int $newlines = 1): ?int { return $this->out($message, $newlines, self::QUIET); } @@ -195,15 +195,15 @@ public function quiet($message, int $newlines = 1): ?int * @return int|null The number of bytes returned from writing to stdout * or null if provided $level is greater than current level. */ - public function out($message = '', int $newlines = 1, int $level = self::NORMAL): ?int + public function out(array|string $message = '', int $newlines = 1, int $level = self::NORMAL): ?int { - if ($level <= $this->_level) { - $this->_lastWritten = $this->_out->write($message, $newlines); - - return $this->_lastWritten; + if ($level > $this->_level) { + return null; } - return null; + $this->_lastWritten = $this->_out->write($message, $newlines); + + return $this->_lastWritten; } /** @@ -214,9 +214,9 @@ public function out($message = '', int $newlines = 1, int $level = self::NORMAL) * @param int $level The message's output level, see above. * @return int|null The number of bytes returned from writing to stdout * or null if provided $level is greater than current level. - * @see https://book.cakephp.org/4/en/console-and-shells.html#ConsoleIo::out + * @link https://book.cakephp.org/5/en/console-and-shells.html#ConsoleIo::out */ - public function info($message, int $newlines = 1, int $level = self::NORMAL): ?int + public function info(array|string $message, int $newlines = 1, int $level = self::NORMAL): ?int { $messageType = 'info'; $message = $this->wrapMessageWithType($messageType, $message); @@ -232,9 +232,9 @@ public function info($message, int $newlines = 1, int $level = self::NORMAL): ?i * @param int $level The message's output level, see above. * @return int|null The number of bytes returned from writing to stdout * or null if provided $level is greater than current level. - * @see https://book.cakephp.org/4/en/console-and-shells.html#ConsoleIo::out + * @link https://book.cakephp.org/5/en/console-and-shells.html#ConsoleIo::out */ - public function comment($message, int $newlines = 1, int $level = self::NORMAL): ?int + public function comment(array|string $message, int $newlines = 1, int $level = self::NORMAL): ?int { $messageType = 'comment'; $message = $this->wrapMessageWithType($messageType, $message); @@ -248,9 +248,9 @@ public function comment($message, int $newlines = 1, int $level = self::NORMAL): * @param array|string $message A string or an array of strings to output * @param int $newlines Number of newlines to append * @return int The number of bytes returned from writing to stderr. - * @see https://book.cakephp.org/4/en/console-and-shells.html#ConsoleIo::err + * @link https://book.cakephp.org/5/en/console-and-shells.html#ConsoleIo::err */ - public function warning($message, int $newlines = 1): int + public function warning(array|string $message, int $newlines = 1): int { $messageType = 'warning'; $message = $this->wrapMessageWithType($messageType, $message); @@ -264,9 +264,9 @@ public function warning($message, int $newlines = 1): int * @param array|string $message A string or an array of strings to output * @param int $newlines Number of newlines to append * @return int The number of bytes returned from writing to stderr. - * @see https://book.cakephp.org/4/en/console-and-shells.html#ConsoleIo::err + * @link https://book.cakephp.org/5/en/console-and-shells.html#ConsoleIo::err */ - public function error($message, int $newlines = 1): int + public function error(array|string $message, int $newlines = 1): int { $messageType = 'error'; $message = $this->wrapMessageWithType($messageType, $message); @@ -282,9 +282,9 @@ public function error($message, int $newlines = 1): int * @param int $level The message's output level, see above. * @return int|null The number of bytes returned from writing to stdout * or null if provided $level is greater than current level. - * @see https://book.cakephp.org/4/en/console-and-shells.html#ConsoleIo::out + * @link https://book.cakephp.org/5/en/console-and-shells.html#ConsoleIo::out */ - public function success($message, int $newlines = 1, int $level = self::NORMAL): ?int + public function success(array|string $message, int $newlines = 1, int $level = self::NORMAL): ?int { $messageType = 'success'; $message = $this->wrapMessageWithType($messageType, $message); @@ -297,11 +297,10 @@ public function success($message, int $newlines = 1, int $level = self::NORMAL): * * @param string $message Error message. * @param int $code Error code. - * @return void - * @psalm-return never-return + * @return never * @throws \Cake\Console\Exception\StopException */ - public function abort($message, $code = CommandInterface::CODE_ERROR): void + public function abort(string $message, int $code = CommandInterface::CODE_ERROR): never { $this->error($message); @@ -315,7 +314,7 @@ public function abort($message, $code = CommandInterface::CODE_ERROR): void * @param array|string $message The message to wrap. * @return array|string The message wrapped with the given message type. */ - protected function wrapMessageWithType(string $messageType, $message) + protected function wrapMessageWithType(string $messageType, array|string $message): array|string { if (is_array($message)) { foreach ($message as $k => $v) { @@ -342,7 +341,7 @@ protected function wrapMessageWithType(string $messageType, $message) * length of the last message output. * @return void */ - public function overwrite($message, int $newlines = 1, ?int $size = null): void + public function overwrite(array|string $message, int $newlines = 1, ?int $size = null): void { $size = $size ?: $this->_lastWritten; @@ -376,7 +375,7 @@ public function overwrite($message, int $newlines = 1, ?int $size = null): void * @param int $newlines Number of newlines to append * @return int The number of bytes returned from writing to stderr. */ - public function err($message = '', int $newlines = 1): int + public function err(array|string $message = '', int $newlines = 1): int { return $this->_err->write($message, $newlines); } @@ -474,12 +473,12 @@ public function setStyle(string $style, array $definition): void * @param string|null $default Default input value. * @return string Either the default value, or the user-provided input. */ - public function askChoice(string $prompt, $options, ?string $default = null): string + public function askChoice(string $prompt, array|string $options, ?string $default = null): string { if (is_string($options)) { - if (strpos($options, ',')) { + if (str_contains($options, ',')) { $options = explode(',', $options); - } elseif (strpos($options, '/')) { + } elseif (str_contains($options, '/')) { $options = explode('/', $options); } else { $options = [$options]; @@ -490,7 +489,7 @@ public function askChoice(string $prompt, $options, ?string $default = null): st $options = array_merge( array_map('strtolower', $options), array_map('strtoupper', $options), - $options + $options, ); $in = ''; while ($in === '' || !in_array($in, $options, true)) { @@ -515,15 +514,15 @@ protected function _getInput(string $prompt, ?string $options, ?string $default) } $optionsText = ''; - if (isset($options)) { - $optionsText = " $options "; + if ($options !== null) { + $optionsText = " {$options} "; } $defaultText = ''; if ($default !== null) { - $defaultText = "[$default] "; + $defaultText = "[{$default}] "; } - $this->_out->write('' . $prompt . "$optionsText\n$defaultText> ", 0); + $this->_out->write('' . $prompt . "{$optionsText}\n{$defaultText}> ", 0); $result = $this->_in->read(); $result = $result === null ? '' : trim($result); @@ -541,19 +540,32 @@ protected function _getInput(string $prompt, ?string $options, ?string $default) * If you don't wish all log output in stdout or stderr * through Cake's Log class, call this function with `$enable=false`. * + * If you would like to take full control of how console application logging + * to stdout works add a logger that uses `'className' => 'Console'`. By + * providing a console logger you replace the framework default behavior. + * * @param int|bool $enable Use a boolean to enable/toggle all logging. Use * one of the verbosity constants (self::VERBOSE, self::QUIET, self::NORMAL) * to control logging levels. VERBOSE enables debug logs, NORMAL does not include debug logs, * QUIET disables notice, info and debug logs. * @return void */ - public function setLoggers($enable): void + public function setLoggers(int|bool $enable): void { Log::drop('stdout'); Log::drop('stderr'); if ($enable === false) { return; } + // If the application has configured a console logger + // we don't add a redundant one. + foreach (Log::configured() as $loggerName) { + $log = Log::engine($loggerName); + if ($log instanceof ConsoleLog) { + return; + } + } + $outLevels = ['notice', 'info']; if ($enable === static::VERBOSE || $enable === true) { $outLevels[] = 'debug'; @@ -586,6 +598,7 @@ public function helper(string $name, array $config = []): Helper { $name = ucfirst($name); + /** @var \Cake\Console\Helper */ return $this->_helpers->load($name, $config); } @@ -642,7 +655,7 @@ public function createFile(string $path, string $contents, bool $forceOverwrite } $file = new SplFileObject($path, 'w'); - } catch (RuntimeException $e) { + } catch (RuntimeException) { $this->error("Could not write to `{$path}`. Permission denied.", 2); return false; diff --git a/app/vendor/cakephp/cakephp/src/Console/ConsoleOptionParser.php b/app/vendor/cakephp/cakephp/src/Console/ConsoleOptionParser.php index eee1c8b4f..29a70f7fb 100644 --- a/app/vendor/cakephp/cakephp/src/Console/ConsoleOptionParser.php +++ b/app/vendor/cakephp/cakephp/src/Console/ConsoleOptionParser.php @@ -20,6 +20,7 @@ use Cake\Console\Exception\MissingOptionException; use Cake\Utility\Inflector; use LogicException; +use function Cake\Core\deprecationWarning; /** * Handles parsing the ARGV in the command line and provides support @@ -81,7 +82,7 @@ class ConsoleOptionParser * @see \Cake\Console\ConsoleOptionParser::description() * @var string */ - protected $_description = ''; + protected string $_description = ''; /** * Epilog text - displays after options when help is generated @@ -89,7 +90,7 @@ class ConsoleOptionParser * @see \Cake\Console\ConsoleOptionParser::epilog() * @var string */ - protected $_epilog = ''; + protected string $_epilog = ''; /** * Option definitions. @@ -97,14 +98,14 @@ class ConsoleOptionParser * @see \Cake\Console\ConsoleOptionParser::addOption() * @var array */ - protected $_options = []; + protected array $_options = []; /** * Map of short -> long options, generated when using addOption() * * @var array */ - protected $_shortOptions = []; + protected array $_shortOptions = []; /** * Positional argument definitions. @@ -112,36 +113,21 @@ class ConsoleOptionParser * @see \Cake\Console\ConsoleOptionParser::addArgument() * @var array<\Cake\Console\ConsoleInputArgument> */ - protected $_args = []; - - /** - * Subcommands for this Shell. - * - * @see \Cake\Console\ConsoleOptionParser::addSubcommand() - * @var array - */ - protected $_subcommands = []; - - /** - * Subcommand sorting option - * - * @var bool - */ - protected $_subcommandSort = true; + protected array $_args = []; /** * Command name. * * @var string */ - protected $_command = ''; + protected string $_command = ''; /** * Array of args (argv). * * @var array */ - protected $_tokens = []; + protected array $_tokens = []; /** * Root alias used in help output @@ -149,7 +135,7 @@ class ConsoleOptionParser * @see \Cake\Console\HelpFormatter::setAlias() * @var string */ - protected $rootName = 'cake'; + protected string $rootName = 'cake'; /** * Construct an OptionParser so you can define its behavior @@ -188,7 +174,7 @@ public function __construct(string $command = '', bool $defaultOptions = true) * @param bool $defaultOptions Whether you want the verbose and quiet options set. * @return static */ - public static function create(string $command, bool $defaultOptions = true) + public static function create(string $command, bool $defaultOptions = true): static { return new static($command, $defaultOptions); } @@ -205,9 +191,6 @@ public static function create(string $command, bool $defaultOptions = true) * ], * 'options' => [ * // list of options compatible with addOptions - * ], - * 'subcommands' => [ - * // list of subcommands to add. * ] * ]; * ``` @@ -216,7 +199,7 @@ public static function create(string $command, bool $defaultOptions = true) * @param bool $defaultOptions Whether you want the verbose and quiet options set. * @return static */ - public static function buildFromArray(array $spec, bool $defaultOptions = true) + public static function buildFromArray(array $spec, bool $defaultOptions = true): static { $parser = new static($spec['command'], $defaultOptions); if (!empty($spec['arguments'])) { @@ -225,9 +208,6 @@ public static function buildFromArray(array $spec, bool $defaultOptions = true) if (!empty($spec['options'])) { $parser->addOptions($spec['options']); } - if (!empty($spec['subcommands'])) { - $parser->addSubcommands($spec['subcommands']); - } if (!empty($spec['description'])) { $parser->setDescription($spec['description']); } @@ -249,7 +229,6 @@ public function toArray(): array 'command' => $this->_command, 'arguments' => $this->_args, 'options' => $this->_options, - 'subcommands' => $this->_subcommands, 'description' => $this->_description, 'epilog' => $this->_epilog, ]; @@ -261,7 +240,7 @@ public function toArray(): array * @param \Cake\Console\ConsoleOptionParser|array $spec ConsoleOptionParser or spec to merge with. * @return $this */ - public function merge($spec) + public function merge(ConsoleOptionParser|array $spec) { if ($spec instanceof ConsoleOptionParser) { $spec = $spec->toArray(); @@ -270,11 +249,15 @@ public function merge($spec) $this->addArguments($spec['arguments']); } if (!empty($spec['options'])) { + foreach ($spec['options'] as $name => $params) { + if ($params instanceof ConsoleInputOption) { + $name = $params->name(); + } + $this->removeOption($name); + } + $this->addOptions($spec['options']); } - if (!empty($spec['subcommands'])) { - $this->addSubcommands($spec['subcommands']); - } if (!empty($spec['description'])) { $this->setDescription($spec['description']); } @@ -315,7 +298,7 @@ public function getCommand(): string * text will be imploded with "\n". * @return $this */ - public function setDescription($text) + public function setDescription(array|string $text) { if (is_array($text)) { $text = implode("\n", $text); @@ -343,7 +326,7 @@ public function getDescription(): string * be imploded with "\n". * @return $this */ - public function setEpilog($text) + public function setEpilog(array|string $text) { if (is_array($text)) { $text = implode("\n", $text); @@ -363,29 +346,6 @@ public function getEpilog(): string return $this->_epilog; } - /** - * Enables sorting of subcommands - * - * @param bool $value Whether to sort subcommands - * @return $this - */ - public function enableSubcommandSort(bool $value = true) - { - $this->_subcommandSort = $value; - - return $this; - } - - /** - * Checks whether sorting is enabled for subcommands. - * - * @return bool - */ - public function isSubcommandSortEnabled(): bool - { - return $this->_subcommandSort; - } - /** * Add an option to the option parser. Options allow you to define optional or required * parameters for your console application. Options are defined by the parameters they use. @@ -410,7 +370,7 @@ public function isSubcommandSortEnabled(): bool * @param array $options An array of parameters that define the behavior of the option * @return $this */ - public function addOption($name, array $options = []) + public function addOption(ConsoleInputOption|string $name, array $options = []) { if ($name instanceof ConsoleInputOption) { $option = $name; @@ -422,11 +382,18 @@ public function addOption($name, array $options = []) 'default' => null, 'boolean' => false, 'multiple' => false, + 'separator' => null, 'choices' => [], 'required' => false, 'prompt' => null, ]; + $options += $defaults; + + if ($options['default'] && (is_int($options['default']) || is_float($options['default']))) { + $options['default'] = (string)$options['default']; + } + $option = new ConsoleInputOption( $name, $options['short'], @@ -436,12 +403,17 @@ public function addOption($name, array $options = []) $options['choices'], $options['multiple'], $options['required'], - $options['prompt'] + $options['prompt'], + $options['separator'], ); } $this->_options[$name] = $option; asort($this->_options); if ($option->short()) { + if (isset($this->_shortOptions[$option->short()])) { + deprecationWarning('5.2.0', 'You cannot redefine short options. This will throw an error in 5.3.0+.'); + } + $this->_shortOptions[$option->short()] = $name; asort($this->_shortOptions); } @@ -459,6 +431,11 @@ public function removeOption(string $name) { unset($this->_options[$name]); + $key = array_search($name, $this->_shortOptions, true); + if ($key !== false) { + unset($this->_shortOptions[$key]); + } + return $this; } @@ -474,13 +451,14 @@ public function removeOption(string $name) * option will be overwritten. * - `choices` A list of valid choices for this argument. If left empty all values are valid.. * An exception will be raised when parse() encounters an invalid value. + * - `separator` A separator to allow writing argument in a list form. * * @param \Cake\Console\ConsoleInputArgument|string $name The name of the argument. * Will also accept an instance of ConsoleInputArgument. * @param array $params Parameters for the argument, see above. * @return $this */ - public function addArgument($name, array $params = []) + public function addArgument(ConsoleInputArgument|string $name, array $params = []) { if ($name instanceof ConsoleInputArgument) { $arg = $name; @@ -492,6 +470,7 @@ public function addArgument($name, array $params = []) 'index' => count($this->_args), 'required' => false, 'choices' => [], + 'separator' => null, ]; $options = $params + $defaults; $index = $options['index']; @@ -516,7 +495,7 @@ public function addArgument($name, array $params = []) * Add multiple arguments at once. Take an array of argument definitions. * The keys are used as the argument names, and the values as params for the argument. * - * @param array $args Array of arguments to add. + * @param array|\Cake\Console\ConsoleInputArgument> $args Array of arguments to add. * @see \Cake\Console\ConsoleOptionParser::addArgument() * @return $this */ @@ -554,84 +533,12 @@ public function addOptions(array $options) return $this; } - /** - * Append a subcommand to the subcommand list. - * Subcommands are usually methods on your Shell, but can also be used to document Tasks. - * - * ### Options - * - * - `help` - Help text for the subcommand. - * - `parser` - A ConsoleOptionParser for the subcommand. This allows you to create method - * specific option parsers. When help is generated for a subcommand, if a parser is present - * it will be used. - * - * @param \Cake\Console\ConsoleInputSubcommand|string $name Name of the subcommand. - * Will also accept an instance of ConsoleInputSubcommand. - * @param array $options Array of params, see above. - * @return $this - */ - public function addSubcommand($name, array $options = []) - { - if ($name instanceof ConsoleInputSubcommand) { - $command = $name; - $name = $command->name(); - } else { - $name = Inflector::underscore($name); - $defaults = [ - 'name' => $name, - 'help' => '', - 'parser' => null, - ]; - $options += $defaults; - - $command = new ConsoleInputSubcommand($options); - } - $this->_subcommands[$name] = $command; - if ($this->_subcommandSort) { - asort($this->_subcommands); - } - - return $this; - } - - /** - * Remove a subcommand from the option parser. - * - * @param string $name The subcommand name to remove. - * @return $this - */ - public function removeSubcommand(string $name) - { - unset($this->_subcommands[$name]); - - return $this; - } - - /** - * Add multiple subcommands at once. - * - * @param array $commands Array of subcommands. - * @return $this - */ - public function addSubcommands(array $commands) - { - foreach ($commands as $name => $params) { - if ($params instanceof ConsoleInputSubcommand) { - $name = $params; - $params = []; - } - $this->addSubcommand($name, $params); - } - - return $this; - } - /** * Gets the arguments defined in the parser. * * @return array<\Cake\Console\ConsoleInputArgument> */ - public function arguments() + public function arguments(): array { return $this->_args; } @@ -641,7 +548,7 @@ public function arguments() * * @return array */ - public function argumentNames() + public function argumentNames(): array { $out = []; foreach ($this->_args as $arg) { @@ -656,25 +563,13 @@ public function argumentNames() * * @return array */ - public function options() + public function options(): array { return $this->_options; } /** - * Get the array of defined subcommands - * - * @return array - */ - public function subcommands() - { - return $this->_subcommands; - } - - /** - * Parse the argv array into a set of params and args. If $command is not null - * and $command is equal to a subcommand that has a parser, that parser will be used - * to parse the $argv + * Parse the argv array into a set of params and args. * * @param array $argv Array of args (argv) to parse. * @param \Cake\Console\ConsoleIo|null $io A ConsoleIo instance or null. If null prompt options will error. @@ -683,16 +578,8 @@ public function subcommands() */ public function parse(array $argv, ?ConsoleIo $io = null): array { - $command = isset($argv[0]) ? Inflector::underscore($argv[0]) : null; - if (isset($this->_subcommands[$command])) { - array_shift($argv); - } - if (isset($this->_subcommands[$command]) && $this->_subcommands[$command]->parser()) { - /** @psalm-suppress PossiblyNullReference */ - return $this->_subcommands[$command]->parser()->parse($argv, $io); - } - - $params = $args = []; + $params = []; + $args = []; $this->_tokens = $argv; $afterDoubleDash = false; @@ -708,12 +595,9 @@ public function parse(array $argv, ?ConsoleIo $io = null): array continue; } - if (isset($this->_subcommands[$token])) { - continue; - } - if (substr($token, 0, 2) === '--') { + if (str_starts_with($token, '--')) { $params = $this->_parseLongOption($token, $params); - } elseif (substr($token, 0, 1) === '-') { + } elseif (str_starts_with($token, '-')) { $params = $this->_parseShortOption($token, $params); } else { $args = $this->_parseArg($token, $args); @@ -728,7 +612,7 @@ public function parse(array $argv, ?ConsoleIo $io = null): array if (!isset($args[$i])) { if ($arg->isRequired()) { throw new ConsoleException( - sprintf('Missing required argument. The `%s` argument is required.', $arg->name()) + sprintf('Missing required argument. The `%s` argument is required.', $arg->name()), ); } if ($arg->defaultValue() !== null) { @@ -753,7 +637,7 @@ public function parse(array $argv, ?ConsoleIo $io = null): array if (!$io) { throw new ConsoleException( 'Cannot use interactive option prompts without a ConsoleIo instance. ' . - 'Please provide a `$io` parameter to `parse()`.' + 'Please provide a `$io` parameter to `parse()`.', ); } $choices = $option->choices(); @@ -766,7 +650,7 @@ public function parse(array $argv, ?ConsoleIo $io = null): array } if ($option->isRequired() && !isset($params[$name])) { throw new ConsoleException( - sprintf('Missing required option. The `%s` option is required and has no default value.', $name) + sprintf('Missing required option. The `%s` option is required and has no default value.', $name), ); } } @@ -777,65 +661,26 @@ public function parse(array $argv, ?ConsoleIo $io = null): array /** * Gets formatted help for this parser object. * - * Generates help text based on the description, options, arguments, subcommands and epilog + * Generates help text based on the description, options, arguments and epilog * in the parser. * - * @param string|null $subcommand If present and a valid subcommand that has a linked parser. - * That subcommands help will be shown instead. * @param string $format Define the output format, can be text or XML * @param int $width The width to format user content to. Defaults to 72 * @return string Generated help. */ - public function help(?string $subcommand = null, string $format = 'text', int $width = 72): string + public function help(string $format = 'text', int $width = 72): string { - if ($subcommand === null) { - $formatter = new HelpFormatter($this); - $formatter->setAlias($this->rootName); + $formatter = new HelpFormatter($this); + $formatter->setAlias($this->rootName); - if ($format === 'text') { - return $formatter->text($width); - } - if ($format === 'xml') { - return (string)$formatter->xml(); - } + if ($format === 'text') { + return $formatter->text($width); + } + if ($format === 'xml') { + return (string)$formatter->xml(); } - $subcommand = (string)$subcommand; - - if (isset($this->_subcommands[$subcommand])) { - $command = $this->_subcommands[$subcommand]; - $subparser = $command->parser(); - // Generate a parser as the subcommand didn't define one. - if (!($subparser instanceof self)) { - // $subparser = clone $this; - $subparser = new self($subcommand); - $subparser - ->setDescription($command->getRawHelp()) - ->addOptions($this->options()) - ->addArguments($this->arguments()); - } - if ($subparser->getDescription() === '') { - $subparser->setDescription($command->getRawHelp()); - } - $subparser->setCommand($this->getCommand() . ' ' . $subcommand); - $subparser->setRootName($this->rootName); - - return $subparser->help(null, $format, $width); - } - - $rootCommand = $this->getCommand(); - $message = sprintf( - 'Unable to find the `%s %s` subcommand. See `bin/%s %s --help`.', - $rootCommand, - $subcommand, - $this->rootName, - $rootCommand - ); - throw new MissingOptionException( - $message, - $subcommand, - array_keys($this->subcommands()) - ); + throw new ConsoleException('Invalid format. Output format can be text or xml.'); } /** @@ -862,7 +707,7 @@ public function setRootName(string $name) protected function _parseLongOption(string $option, array $params): array { $name = substr($option, 2); - if (strpos($name, '=') !== false) { + if (str_contains($name, '=')) { [$name, $value] = explode('=', $name, 2); array_unshift($this->_tokens, $value); } @@ -896,9 +741,9 @@ protected function _parseShortOption(string $option, array $params): array $options[] = "{$short} (short for `--{$long}`)"; } throw new MissingOptionException( - "Unknown short option `{$key}`.", + sprintf('Unknown short option `%s`.', $key), $key, - $options + $options, ); } $name = $this->_shortOptions[$key]; @@ -918,9 +763,9 @@ protected function _parseOption(string $name, array $params): array { if (!isset($this->_options[$name])) { throw new MissingOptionException( - "Unknown option `{$name}`.", + sprintf('Unknown option `%s`.', $name), $name, - array_keys($this->_options) + array_keys($this->_options), ); } $option = $this->_options[$name]; @@ -938,7 +783,11 @@ protected function _parseOption(string $name, array $params): array $option->validChoice($value); if ($option->acceptsMultiple()) { - $params[$name][] = $value; + $values = [$value]; + if (is_string($value) && $option->separator()) { + $values = explode($option->separator(), $value); + } + $params[$name] = array_merge($params[$name] ?? [], $values); } else { $params[$name] = $value; } @@ -954,10 +803,10 @@ protected function _parseOption(string $name, array $params): array */ protected function _optionExists(string $name): bool { - if (substr($name, 0, 2) === '--') { + if (str_starts_with($name, '--')) { return isset($this->_options[substr($name, 2)]); } - if ($name[0] === '-' && $name[1] !== '-') { + if (str_starts_with($name, '-')) { return isset($this->_shortOptions[$name[1]]); } @@ -975,7 +824,7 @@ protected function _optionExists(string $name): bool */ protected function _parseArg(string $argument, array $args): array { - if (empty($this->_args)) { + if (!$this->_args) { $args[] = $argument; return $args; @@ -983,13 +832,21 @@ protected function _parseArg(string $argument, array $args): array $next = count($args); if (!isset($this->_args[$next])) { $expected = count($this->_args); - throw new ConsoleException( - "Received too many arguments. Got {$next} but only {$expected} arguments are defined." - ); + throw new ConsoleException(sprintf( + 'Received too many arguments. Got `%s` but only `%s` arguments are defined.', + $next, + $expected, + )); } - $this->_args[$next]->validChoice($argument); - $args[] = $argument; + $arg = $this->_args[$next]; + + $arg->validChoice($argument); + if ($arg->separator()) { + $args[] = explode($arg->separator(), $argument); + } else { + $args[] = $argument; + } return $args; } diff --git a/app/vendor/cakephp/cakephp/src/Console/ConsoleOutput.php b/app/vendor/cakephp/cakephp/src/Console/ConsoleOutput.php index 4c7ea902a..09485833a 100644 --- a/app/vendor/cakephp/cakephp/src/Console/ConsoleOutput.php +++ b/app/vendor/cakephp/cakephp/src/Console/ConsoleOutput.php @@ -32,6 +32,11 @@ * - `info` Informational messages. * - `comment` Additional text. * - `question` Magenta text used for user prompts + * - `success` Green foreground text + * - `info.bg` Cyan background with black text + * - `warning.bg` Yellow background with black text + * - `error.bg` Red background with black text + * - `success.bg` Green background with black text * * By defining styles with addStyle() you can create custom console styles. * @@ -90,14 +95,14 @@ class ConsoleOutput * @see setOutputAs() For manipulation. * @var int */ - protected $_outputAs = self::COLOR; + protected int $_outputAs = self::COLOR; /** * text colors used in colored output. * * @var array */ - protected static $_foregroundColors = [ + protected static array $_foregroundColors = [ 'black' => 30, 'red' => 31, 'green' => 32, @@ -113,7 +118,7 @@ class ConsoleOutput * * @var array */ - protected static $_backgroundColors = [ + protected static array $_backgroundColors = [ 'black' => 40, 'red' => 41, 'green' => 42, @@ -129,7 +134,7 @@ class ConsoleOutput * * @var array */ - protected static $_options = [ + protected static array $_options = [ 'bold' => 1, 'underline' => 4, 'blink' => 5, @@ -142,18 +147,23 @@ class ConsoleOutput * * @var array */ - protected static $_styles = [ + protected static array $_styles = [ 'emergency' => ['text' => 'red'], 'alert' => ['text' => 'red'], 'critical' => ['text' => 'red'], 'error' => ['text' => 'red'], + 'error.bg' => ['background' => 'red', 'text' => 'black'], 'warning' => ['text' => 'yellow'], + 'warning.bg' => ['background' => 'yellow', 'text' => 'black'], 'info' => ['text' => 'cyan'], + 'info.bg' => ['background' => 'white', 'text' => 'cyan'], 'debug' => ['text' => 'yellow'], 'success' => ['text' => 'green'], + 'success.bg' => ['background' => 'green', 'text' => 'black'], + 'notice' => ['text' => 'cyan'], + 'notice.bg' => ['background' => 'cyan', 'text' => 'black'], 'comment' => ['text' => 'blue'], 'question' => ['text' => 'magenta'], - 'notice' => ['text' => 'cyan'], ]; /** @@ -162,7 +172,7 @@ class ConsoleOutput * Checks for a pretty console environment. Ansicon and ConEmu allows * pretty consoles on Windows, and is supported. * - * @param string|resource $stream The identifier of the stream to write output to. + * @param resource|string $stream The identifier of the stream to write output to. * @throws \Cake\Console\Exception\ConsoleException If the given stream is not a valid resource. */ public function __construct($stream = 'php://stdout') @@ -180,8 +190,8 @@ public function __construct($stream = 'php://stdout') if ( ( DIRECTORY_SEPARATOR === '\\' && - strpos(strtolower(php_uname('v')), 'windows 10') === false && - strpos(strtolower((string)env('SHELL')), 'bash.exe') === false && + !str_contains(strtolower(php_uname('v')), 'windows 10') && + !str_contains(strtolower((string)env('SHELL')), 'bash.exe') && !(bool)env('ANSICON') && env('ConEmuANSI') !== 'ON' ) || @@ -205,7 +215,7 @@ function_exists('posix_isatty') && * @param int $newlines Number of newlines to append * @return int The number of bytes returned from writing to output. */ - public function write($message, int $newlines = 1): int + public function write(array|string $message, int $newlines = 1): int { if (is_array($message)) { $message = implode(static::LF, $message); @@ -226,10 +236,13 @@ public function styleText(string $text): string return $text; } if ($this->_outputAs !== static::PLAIN) { + /** @var \Closure $replaceTags */ + $replaceTags = $this->_replaceTags(...); + $output = preg_replace_callback( - '/<(?P[a-z0-9-_]+)>(?P.*?)<\/(\1)>/ims', - [$this, '_replaceTags'], - $text + '/<(?P[a-z0-9-_.]+)>(?P.*?)<\/(\1)>/ims', + $replaceTags, + $text, ); if ($output !== null) { return $output; @@ -239,11 +252,7 @@ public function styleText(string $text): string $tags = implode('|', array_keys(static::$_styles)); $output = preg_replace('##', '', $text); - if ($output === null) { - return $text; - } - - return $output; + return $output ?? $text; } /** @@ -255,7 +264,7 @@ public function styleText(string $text): string protected function _replaceTags(array $matches): string { $style = $this->getStyle($matches['tag']); - if (empty($style)) { + if (!$style) { return '<' . $matches['tag'] . '>' . $matches['text'] . ''; } @@ -284,6 +293,10 @@ protected function _replaceTags(array $matches): string */ protected function _write(string $message): int { + if (!isset($this->_output)) { + return 0; + } + return (int)fwrite($this->_output, $message); } @@ -358,7 +371,7 @@ public function getOutputAs(): int public function setOutputAs(int $type): void { if (!in_array($type, [self::RAW, self::PLAIN, self::COLOR], true)) { - throw new InvalidArgumentException(sprintf('Invalid output type "%s".', $type)); + throw new InvalidArgumentException(sprintf('Invalid output type `%s`.', $type)); } $this->_outputAs = $type; @@ -369,8 +382,9 @@ public function setOutputAs(int $type): void */ public function __destruct() { - if (is_resource($this->_output)) { + if (isset($this->_output) && is_resource($this->_output)) { fclose($this->_output); } + unset($this->_output); } } diff --git a/app/vendor/cakephp/cakephp/src/Console/Exception/ConsoleException.php b/app/vendor/cakephp/cakephp/src/Console/Exception/ConsoleException.php index 28be512ec..51158fe7d 100644 --- a/app/vendor/cakephp/cakephp/src/Console/Exception/ConsoleException.php +++ b/app/vendor/cakephp/cakephp/src/Console/Exception/ConsoleException.php @@ -9,7 +9,7 @@ * Redistributions of files must retain the above copyright notice. * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) - * @link https://book.cakephp.org/4/en/development/errors.html#error-exception-configuration + * @link https://book.cakephp.org/5/en/development/errors.html#error-exception-configuration * @since 3.0.0 * @license https://opensource.org/licenses/mit-license.php MIT License */ @@ -29,5 +29,5 @@ class ConsoleException extends CakeException * * @var int */ - protected $_defaultCode = CommandInterface::CODE_ERROR; + protected int $_defaultCode = CommandInterface::CODE_ERROR; } diff --git a/app/vendor/cakephp/cakephp/src/Console/Exception/MissingHelperException.php b/app/vendor/cakephp/cakephp/src/Console/Exception/MissingHelperException.php index 5a1d018a4..0b073bfd4 100644 --- a/app/vendor/cakephp/cakephp/src/Console/Exception/MissingHelperException.php +++ b/app/vendor/cakephp/cakephp/src/Console/Exception/MissingHelperException.php @@ -22,5 +22,5 @@ class MissingHelperException extends ConsoleException /** * @var string */ - protected $_messageTemplate = 'Helper class %s could not be found.'; + protected string $_messageTemplate = 'Helper class `%s` could not be found.'; } diff --git a/app/vendor/cakephp/cakephp/src/Console/Exception/MissingOptionException.php b/app/vendor/cakephp/cakephp/src/Console/Exception/MissingOptionException.php index 0d1cfe810..e6dda899e 100644 --- a/app/vendor/cakephp/cakephp/src/Console/Exception/MissingOptionException.php +++ b/app/vendor/cakephp/cakephp/src/Console/Exception/MissingOptionException.php @@ -9,7 +9,7 @@ * Redistributions of files must retain the above copyright notice. * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) - * @link https://book.cakephp.org/4/en/development/errors.html#error-exception-configuration + * @link https://book.cakephp.org/5/en/development/errors.html#error-exception-configuration * @since 4.0.0 * @license https://opensource.org/licenses/mit-license.php MIT License */ @@ -27,14 +27,14 @@ class MissingOptionException extends ConsoleException * * @var string */ - protected $requested = ''; + protected string $requested = ''; /** * The valid suggestions. * * @var array */ - protected $suggestions = []; + protected array $suggestions = []; /** * Constructor. @@ -50,7 +50,7 @@ public function __construct( string $requested = '', array $suggestions = [], ?int $code = null, - ?Throwable $previous = null + ?Throwable $previous = null, ) { $this->suggestions = $suggestions; $this->requested = $requested; @@ -88,13 +88,13 @@ public function getFullMessage(): string * * @param string $needle Unknown option name trying to be used. * @param array $haystack Suggestions to look through. - * @return string The best match + * @return string|null The best match */ - protected function findClosestItem($needle, $haystack): ?string + protected function findClosestItem(string $needle, array $haystack): ?string { $bestGuess = null; foreach ($haystack as $item) { - if (strpos($item, $needle) === 0) { + if (str_starts_with($item, $needle)) { return $item; } } diff --git a/app/vendor/cakephp/cakephp/src/Console/Exception/MissingShellException.php b/app/vendor/cakephp/cakephp/src/Console/Exception/MissingShellException.php deleted file mode 100644 index 1774b59e2..000000000 --- a/app/vendor/cakephp/cakephp/src/Console/Exception/MissingShellException.php +++ /dev/null @@ -1,28 +0,0 @@ -addPlugin(),' - . ' you may need to update bin/cake.php to match https://github.com/cakephp/app/tree/master/bin/cake.php'; -} diff --git a/app/vendor/cakephp/cakephp/src/Console/Exception/MissingShellMethodException.php b/app/vendor/cakephp/cakephp/src/Console/Exception/MissingShellMethodException.php deleted file mode 100644 index fd5c0454a..000000000 --- a/app/vendor/cakephp/cakephp/src/Console/Exception/MissingShellMethodException.php +++ /dev/null @@ -1,26 +0,0 @@ -_parser; $out = []; $description = $parser->getDescription(); - if (!empty($description)) { + if ($description) { $out[] = Text::wrap($description, $width); $out[] = ''; } $out[] = 'Usage:'; $out[] = $this->_generateUsage(); $out[] = ''; - $subcommands = $parser->subcommands(); - if (!empty($subcommands)) { - $out[] = 'Subcommands:'; - $out[] = ''; - $max = $this->_getMaxLength($subcommands) + 2; - foreach ($subcommands as $command) { - $out[] = Text::wrapBlock($command->help($max), [ - 'width' => $width, - 'indent' => str_repeat(' ', $max), - 'indentAt' => 1, - ]); - } - $out[] = ''; - $out[] = sprintf( - 'To see help on a subcommand use `' . $this->_alias . ' %s [subcommand] --help`', - $parser->getCommand() - ); - $out[] = ''; - } $options = $parser->options(); if ($options) { @@ -133,7 +114,7 @@ public function text(int $width = 72): string } $arguments = $parser->arguments(); - if (!empty($arguments)) { + if ($arguments) { $max = $this->_getMaxLength($arguments) + 2; $out[] = 'Arguments:'; $out[] = ''; @@ -147,7 +128,7 @@ public function text(int $width = 72): string $out[] = ''; } $epilog = $parser->getEpilog(); - if (!empty($epilog)) { + if ($epilog) { $out[] = Text::wrap($epilog, $width); $out[] = ''; } @@ -165,10 +146,6 @@ public function text(int $width = 72): string protected function _generateUsage(): string { $usage = [$this->_alias . ' ' . $this->_parser->getCommand()]; - $subcommands = $this->_parser->subcommands(); - if (!empty($subcommands)) { - $usage[] = '[subcommand]'; - } $options = []; foreach ($this->_parser->options() as $option) { $options[] = $option->usage(); @@ -192,14 +169,14 @@ protected function _generateUsage(): string /** * Iterate over a collection and find the longest named thing. * - * @param array<\Cake\Console\ConsoleInputOption|\Cake\Console\ConsoleInputArgument|\Cake\Console\ConsoleInputSubcommand> $collection The collection to find a max length of. + * @param array<\Cake\Console\ConsoleInputOption|\Cake\Console\ConsoleInputArgument> $collection The collection to find a max length of. * @return int */ protected function _getMaxLength(array $collection): int { $max = 0; foreach ($collection as $item) { - $max = strlen($item->name()) > $max ? strlen($item->name()) : $max; + $max = max(strlen($item->name()), $max); } return $max; @@ -211,17 +188,13 @@ protected function _getMaxLength(array $collection): int * @param bool $string Return the SimpleXml object or a string. Defaults to true. * @return \SimpleXMLElement|string See $string */ - public function xml(bool $string = true) + public function xml(bool $string = true): SimpleXMLElement|string { $parser = $this->_parser; $xml = new SimpleXMLElement(''); $xml->addChild('command', $parser->getCommand()); $xml->addChild('description', $parser->getDescription()); - $subcommands = $xml->addChild('subcommands'); - foreach ($parser->subcommands() as $command) { - $command->xml($subcommands); - } $options = $xml->addChild('options'); foreach ($parser->options() as $option) { $option->xml($options); diff --git a/app/vendor/cakephp/cakephp/src/Console/Helper.php b/app/vendor/cakephp/cakephp/src/Console/Helper.php index d9161b6af..a21c29877 100644 --- a/app/vendor/cakephp/cakephp/src/Console/Helper.php +++ b/app/vendor/cakephp/cakephp/src/Console/Helper.php @@ -34,14 +34,14 @@ abstract class Helper * * @var array */ - protected $_defaultConfig = []; + protected array $_defaultConfig = []; /** * ConsoleIo instance. * * @var \Cake\Console\ConsoleIo */ - protected $_io; + protected ConsoleIo $_io; /** * Constructor. diff --git a/app/vendor/cakephp/cakephp/src/Console/HelperRegistry.php b/app/vendor/cakephp/cakephp/src/Console/HelperRegistry.php index 30f312c1e..b45cabc26 100644 --- a/app/vendor/cakephp/cakephp/src/Console/HelperRegistry.php +++ b/app/vendor/cakephp/cakephp/src/Console/HelperRegistry.php @@ -29,11 +29,11 @@ class HelperRegistry extends ObjectRegistry { /** - * Shell to use to set params to tasks. + * IO instance. * * @var \Cake\Console\ConsoleIo */ - protected $_io; + protected ConsoleIo $_io; /** * Sets The IO instance that should be passed to the shell helpers @@ -49,23 +49,15 @@ public function setIo(ConsoleIo $io): void /** * Resolve a helper classname. * - * Will prefer helpers defined in Command\Helper over those - * defined in Shell\Helper. - * * Part of the template method for {@link \Cake\Core\ObjectRegistry::load()}. * * @param string $class Partial classname to resolve. - * @return string|null Either the correct class name or null. - * @psalm-return class-string + * @return class-string<\Cake\Console\Helper>|null Either the correct class name or null. */ protected function _resolveClassName(string $class): ?string { - $name = App::className($class, 'Command/Helper', 'Helper'); - if ($name === null) { - return App::className($class, 'Shell/Helper', 'Helper'); - } - - return $name; + /** @var class-string<\Cake\Console\Helper>|null */ + return App::className($class, 'Command/Helper', 'Helper'); } /** @@ -92,15 +84,17 @@ protected function _throwMissingClassError(string $class, ?string $plugin): void * * Part of the template method for Cake\Core\ObjectRegistry::load() * - * @param string $class The classname to create. + * @param \Cake\Console\Helper|class-string<\Cake\Console\Helper> $class The classname to create. * @param string $alias The alias of the helper. * @param array $config An array of settings to use for the helper. * @return \Cake\Console\Helper The constructed helper class. - * @psalm-suppress MoreSpecificImplementedParamType */ - protected function _create($class, string $alias, array $config): Helper + protected function _create(object|string $class, string $alias, array $config): Helper { - /** @var \Cake\Console\Helper */ + if (is_object($class)) { + return $class; + } + return new $class($this->_io, $config); } } diff --git a/app/vendor/cakephp/cakephp/src/Console/README.md b/app/vendor/cakephp/cakephp/src/Console/README.md index f2fd21504..ed9ca5a63 100644 --- a/app/vendor/cakephp/cakephp/src/Console/README.md +++ b/app/vendor/cakephp/cakephp/src/Console/README.md @@ -120,6 +120,6 @@ class HelloCommand extends BaseCommand Next we can run our command with `php bin/tool.php hello Syd`. To learn more about the various features we've used in this example read the docs: -* [Option Parsing](https://book.cakephp.org/4/en/console-commands/option-parsers.html) -* [Input & Output](https://book.cakephp.org/4/en/console-commands/input-output.html) +* [Option Parsing](https://book.cakephp.org/5/en/console-commands/option-parsers.html) +* [Input & Output](https://book.cakephp.org/5/en/console-commands/input-output.html) diff --git a/app/vendor/cakephp/cakephp/src/Console/Shell.php b/app/vendor/cakephp/cakephp/src/Console/Shell.php deleted file mode 100644 index 46bdda200..000000000 --- a/app/vendor/cakephp/cakephp/src/Console/Shell.php +++ /dev/null @@ -1,944 +0,0 @@ - - */ - public $taskNames = []; - - /** - * Task Collection for the command, used to create Tasks. - * - * @var \Cake\Console\TaskRegistry - */ - public $Tasks; - - /** - * Normalized map of tasks. - * - * @var array - */ - protected $_taskMap = []; - - /** - * ConsoleIo instance. - * - * @var \Cake\Console\ConsoleIo - */ - protected $_io; - - /** - * The root command name used when generating help output. - * - * @var string - */ - protected $rootName = 'cake'; - - /** - * Constructs this Shell instance. - * - * @param \Cake\Console\ConsoleIo|null $io An io instance. - * @param \Cake\ORM\Locator\LocatorInterface|null $locator Table locator instance. - * @link https://book.cakephp.org/4/en/console-commands/shells.html - */ - public function __construct(?ConsoleIo $io = null, ?LocatorInterface $locator = null) - { - if (!$this->name) { - [, $class] = namespaceSplit(static::class); - $this->name = str_replace(['Shell', 'Task'], '', $class); - } - $this->_io = $io ?: new ConsoleIo(); - $this->_tableLocator = $locator; - - $this->modelFactory('Table', [$this->getTableLocator(), 'get']); - $this->Tasks = new TaskRegistry($this); - - $this->_mergeVars( - ['tasks'], - ['associative' => ['tasks']] - ); - - if (isset($this->modelClass)) { - $this->loadModel(); - } - } - - /** - * Set the root command name for help output. - * - * @param string $name The name of the root command. - * @return $this - */ - public function setRootName(string $name) - { - $this->rootName = $name; - - return $this; - } - - /** - * Get the io object for this shell. - * - * @return \Cake\Console\ConsoleIo The current ConsoleIo object. - */ - public function getIo(): ConsoleIo - { - return $this->_io; - } - - /** - * Set the io object for this shell. - * - * @param \Cake\Console\ConsoleIo $io The ConsoleIo object to use. - * @return void - */ - public function setIo(ConsoleIo $io): void - { - $this->_io = $io; - } - - /** - * Initializes the Shell - * acts as constructor for subclasses - * allows configuration of tasks prior to shell execution - * - * @return void - * @link https://book.cakephp.org/4/en/console-and-shells.html#Cake\Console\ConsoleOptionParser::initialize - */ - public function initialize(): void - { - $this->loadTasks(); - } - - /** - * Starts up the Shell and displays the welcome message. - * Allows for checking and configuring prior to command or main execution - * - * Override this method if you want to remove the welcome information, - * or otherwise modify the pre-command flow. - * - * @return void - * @link https://book.cakephp.org/4/en/console-and-shells.html#Cake\Console\ConsoleOptionParser::startup - */ - public function startup(): void - { - if (!$this->param('requested')) { - $this->_welcome(); - } - } - - /** - * Displays a header for the shell - * - * @return void - */ - protected function _welcome(): void - { - } - - /** - * Loads tasks defined in public $tasks - * - * @return true - */ - public function loadTasks(): bool - { - if ($this->tasks === true || empty($this->tasks)) { - return true; - } - $this->_taskMap = $this->Tasks->normalizeArray($this->tasks); - $this->taskNames = array_merge($this->taskNames, array_keys($this->_taskMap)); - - $this->_validateTasks(); - - return true; - } - - /** - * Checks that the tasks in the task map are actually available - * - * @throws \RuntimeException - * @return void - */ - protected function _validateTasks(): void - { - foreach ($this->_taskMap as $taskName => $task) { - $class = App::className($task['class'], 'Shell/Task', 'Task'); - if ($class === null) { - throw new RuntimeException(sprintf( - 'Task `%s` not found. Maybe you made a typo or a plugin is missing or not loaded?', - $taskName - )); - } - } - } - - /** - * Check to see if this shell has a task with the provided name. - * - * @param string $task The task name to check. - * @return bool Success - * @link https://book.cakephp.org/4/en/console-and-shells.html#shell-tasks - */ - public function hasTask(string $task): bool - { - return isset($this->_taskMap[Inflector::camelize($task)]); - } - - /** - * Check to see if this shell has a callable method by the given name. - * - * @param string $name The method name to check. - * @return bool - * @link https://book.cakephp.org/4/en/console-and-shells.html#shell-tasks - */ - public function hasMethod(string $name): bool - { - try { - $method = new ReflectionMethod($this, $name); - if (!$method->isPublic()) { - return false; - } - - return $method->getDeclaringClass()->name !== self::class; - } catch (ReflectionException $e) { - return false; - } - } - - /** - * Dispatch a command to another Shell. Similar to Object::requestAction() - * but intended for running shells from other shells. - * - * ### Usage: - * - * With a string command: - * - * ``` - * return $this->dispatchShell('schema create DbAcl'); - * ``` - * - * Avoid using this form if you have string arguments, with spaces in them. - * The dispatched will be invoked incorrectly. Only use this form for simple - * command dispatching. - * - * With an array command: - * - * ``` - * return $this->dispatchShell('schema', 'create', 'i18n', '--dry'); - * ``` - * - * With an array having two key / value pairs: - * - * - `command` can accept either a string or an array. Represents the command to dispatch - * - `extra` can accept an array of extra parameters to pass on to the dispatcher. This - * parameters will be available in the `param` property of the called `Shell` - * - * `return $this->dispatchShell([ - * 'command' => 'schema create DbAcl', - * 'extra' => ['param' => 'value'] - * ]);` - * - * or - * - * `return $this->dispatchShell([ - * 'command' => ['schema', 'create', 'DbAcl'], - * 'extra' => ['param' => 'value'] - * ]);` - * - * @return int The CLI command exit code. 0 is success. - * @link https://book.cakephp.org/4/en/console-and-shells.html#invoking-other-shells-from-your-shell - */ - public function dispatchShell(): int - { - [$args, $extra] = $this->parseDispatchArguments(func_get_args()); - - $extra['requested'] = $extra['requested'] ?? true; - /** @psalm-suppress DeprecatedClass */ - $dispatcher = new ShellDispatcher($args, false); - - return $dispatcher->dispatch($extra); - } - - /** - * Parses the arguments for the dispatchShell() method. - * - * @param array $args Arguments fetch from the dispatchShell() method with - * func_get_args() - * @return array First value has to be an array of the command arguments. - * Second value has to be an array of extra parameter to pass on to the dispatcher - */ - public function parseDispatchArguments(array $args): array - { - $extra = []; - - if (is_string($args[0]) && count($args) === 1) { - $args = explode(' ', $args[0]); - - return [$args, $extra]; - } - - if (is_array($args[0]) && !empty($args[0]['command'])) { - $command = $args[0]['command']; - if (is_string($command)) { - $command = explode(' ', $command); - } - - if (!empty($args[0]['extra'])) { - $extra = $args[0]['extra']; - } - - return [$command, $extra]; - } - - return [$args, $extra]; - } - - /** - * Runs the Shell with the provided argv. - * - * Delegates calls to Tasks and resolves methods inside the class. Commands are looked - * up with the following order: - * - * - Method on the shell. - * - Matching task name. - * - `main()` method. - * - * If a shell implements a `main()` method, all missing method calls will be sent to - * `main()` with the original method name in the argv. - * - * For tasks to be invoked they *must* be exposed as subcommands. If you define any subcommands, - * you must define all the subcommands your shell needs, whether they be methods on this class - * or methods on tasks. - * - * @param array $argv Array of arguments to run the shell with. This array should be missing the shell name. - * @param bool $autoMethod Set to true to allow any public method to be called even if it - * was not defined as a subcommand. This is used by ShellDispatcher to make building simple shells easy. - * @param array $extra Extra parameters that you can manually pass to the Shell - * to be dispatched. - * Built-in extra parameter is : - * - * - `requested` : if used, will prevent the Shell welcome message to be displayed - * @return int|bool|null - * @link https://book.cakephp.org/4/en/console-and-shells.html#the-cakephp-console - */ - public function runCommand(array $argv, bool $autoMethod = false, array $extra = []) - { - $command = isset($argv[0]) ? Inflector::underscore($argv[0]) : null; - $this->OptionParser = $this->getOptionParser(); - try { - [$this->params, $this->args] = $this->OptionParser->parse($argv, $this->_io); - } catch (ConsoleException $e) { - $this->err('Error: ' . $e->getMessage()); - - return false; - } - - $this->params = array_merge($this->params, $extra); - $this->_setOutputLevel(); - $this->command = $command; - if ($command && !empty($this->params['help'])) { - return $this->_displayHelp($command); - } - - $subcommands = $this->OptionParser->subcommands(); - $method = Inflector::camelize((string)$command); - $isMethod = $this->hasMethod($method); - - if ($isMethod && $autoMethod && count($subcommands) === 0) { - array_shift($this->args); - $this->startup(); - - return $this->$method(...$this->args); - } - - if ($isMethod && isset($subcommands[$command])) { - $this->startup(); - - return $this->$method(...$this->args); - } - - if ($command && $this->hasTask($command) && isset($subcommands[$command])) { - $this->startup(); - array_shift($argv); - - return $this->{$method}->runCommand($argv, false, ['requested' => true]); - } - - if ($this->hasMethod('main')) { - $this->command = 'main'; - $this->startup(); - - return $this->main(...$this->args); - } - - $this->err('No subcommand provided. Choose one of the available subcommands.', 2); - try { - $this->_io->err($this->OptionParser->help($command)); - } catch (ConsoleException $e) { - $this->err('Error: ' . $e->getMessage()); - } - - return false; - } - - /** - * Set the output level based on the parameters. - * - * This reconfigures both the output level for out() - * and the configured stdout/stderr logging - * - * @return void - */ - protected function _setOutputLevel(): void - { - $this->_io->setLoggers(ConsoleIo::NORMAL); - if (!empty($this->params['quiet'])) { - $this->_io->level(ConsoleIo::QUIET); - $this->_io->setLoggers(ConsoleIo::QUIET); - } - if (!empty($this->params['verbose'])) { - $this->_io->level(ConsoleIo::VERBOSE); - $this->_io->setLoggers(ConsoleIo::VERBOSE); - } - } - - /** - * Display the help in the correct format - * - * @param string|null $command The command to get help for. - * @return int|null The number of bytes returned from writing to stdout. - */ - protected function _displayHelp(?string $command = null) - { - $format = 'text'; - if (!empty($this->args[0]) && $this->args[0] === 'xml') { - $format = 'xml'; - $this->_io->setOutputAs(ConsoleOutput::RAW); - } else { - $this->_welcome(); - } - - $subcommands = $this->OptionParser->subcommands(); - if ($command !== null) { - $command = isset($subcommands[$command]) ? $command : null; - } - - return $this->out($this->OptionParser->help($command, $format)); - } - - /** - * Gets the option parser instance and configures it. - * - * By overriding this method you can configure the ConsoleOptionParser before returning it. - * - * @return \Cake\Console\ConsoleOptionParser - * @link https://book.cakephp.org/4/en/console-and-shells.html#configuring-options-and-generating-help - */ - public function getOptionParser(): ConsoleOptionParser - { - $name = ($this->plugin ? $this->plugin . '.' : '') . $this->name; - $parser = new ConsoleOptionParser($name); - $parser->setRootName($this->rootName); - - return $parser; - } - - /** - * Overload get for lazy building of tasks - * - * @param string $name The task to get. - * @return \Cake\Console\Shell Object of Task - */ - public function __get(string $name) - { - if (empty($this->{$name}) && in_array($name, $this->taskNames, true)) { - $properties = $this->_taskMap[$name]; - $this->{$name} = $this->Tasks->load($properties['class'], $properties['config']); - $this->{$name}->args = &$this->args; - $this->{$name}->params = &$this->params; - $this->{$name}->initialize(); - $this->{$name}->loadTasks(); - } - - return $this->{$name}; - } - - /** - * Safely access the values in $this->params. - * - * @param string $name The name of the parameter to get. - * @return string|bool|null Value. Will return null if it doesn't exist. - */ - public function param(string $name) - { - return $this->params[$name] ?? null; - } - - /** - * Prompts the user for input, and returns it. - * - * @param string $prompt Prompt text. - * @param array|string|null $options Array or string of options. - * @param string|null $default Default input value. - * @return string|null Either the default value, or the user-provided input. - * @link https://book.cakephp.org/4/en/console-and-shells.html#Shell::in - */ - public function in(string $prompt, $options = null, ?string $default = null): ?string - { - if (!$this->interactive) { - return $default; - } - if ($options) { - return $this->_io->askChoice($prompt, $options, $default); - } - - return $this->_io->ask($prompt, $default); - } - - /** - * Wrap a block of text. - * Allows you to set the width, and indenting on a block of text. - * - * ### Options - * - * - `width` The width to wrap to. Defaults to 72 - * - `wordWrap` Only wrap on words breaks (spaces) Defaults to true. - * - `indent` Indent the text with the string provided. Defaults to null. - * - * @param string $text Text the text to format. - * @param array|int $options Array of options to use, or an integer to wrap the text to. - * @return string Wrapped / indented text - * @see \Cake\Utility\Text::wrap() - * @link https://book.cakephp.org/4/en/console-and-shells.html#Shell::wrapText - */ - public function wrapText(string $text, $options = []): string - { - return Text::wrap($text, $options); - } - - /** - * Output at the verbose level. - * - * @param array|string $message A string or an array of strings to output - * @param int $newlines Number of newlines to append - * @return int|null The number of bytes returned from writing to stdout. - */ - public function verbose($message, int $newlines = 1): ?int - { - return $this->_io->verbose($message, $newlines); - } - - /** - * Output at all levels. - * - * @param array|string $message A string or an array of strings to output - * @param int $newlines Number of newlines to append - * @return int|null The number of bytes returned from writing to stdout. - */ - public function quiet($message, int $newlines = 1): ?int - { - return $this->_io->quiet($message, $newlines); - } - - /** - * Outputs a single or multiple messages to stdout. If no parameters - * are passed outputs just a newline. - * - * ### Output levels - * - * There are 3 built-in output level. Shell::QUIET, Shell::NORMAL, Shell::VERBOSE. - * The verbose and quiet output levels, map to the `verbose` and `quiet` output switches - * present in most shells. Using Shell::QUIET for a message means it will always display. - * While using Shell::VERBOSE means it will only display when verbose output is toggled. - * - * @param array|string $message A string or an array of strings to output - * @param int $newlines Number of newlines to append - * @param int $level The message's output level, see above. - * @return int|null The number of bytes returned from writing to stdout. - * @link https://book.cakephp.org/4/en/console-and-shells.html#Shell::out - */ - public function out($message, int $newlines = 1, int $level = Shell::NORMAL): ?int - { - return $this->_io->out($message, $newlines, $level); - } - - /** - * Outputs a single or multiple error messages to stderr. If no parameters - * are passed outputs just a newline. - * - * @param array|string $message A string or an array of strings to output - * @param int $newlines Number of newlines to append - * @return int The number of bytes returned from writing to stderr. - */ - public function err($message, int $newlines = 1): int - { - return $this->_io->error($message, $newlines); - } - - /** - * Convenience method for out() that wraps message between tag - * - * @param array|string $message A string or an array of strings to output - * @param int $newlines Number of newlines to append - * @param int $level The message's output level, see above. - * @return int|null The number of bytes returned from writing to stdout. - * @see https://book.cakephp.org/4/en/console-and-shells.html#Shell::out - */ - public function info($message, int $newlines = 1, int $level = Shell::NORMAL): ?int - { - return $this->_io->info($message, $newlines, $level); - } - - /** - * Convenience method for err() that wraps message between tag - * - * @param array|string $message A string or an array of strings to output - * @param int $newlines Number of newlines to append - * @return int The number of bytes returned from writing to stderr. - * @see https://book.cakephp.org/4/en/console-and-shells.html#Shell::err - */ - public function warn($message, int $newlines = 1): int - { - return $this->_io->warning($message, $newlines); - } - - /** - * Convenience method for out() that wraps message between tag - * - * @param array|string $message A string or an array of strings to output - * @param int $newlines Number of newlines to append - * @param int $level The message's output level, see above. - * @return int|null The number of bytes returned from writing to stdout. - * @see https://book.cakephp.org/4/en/console-and-shells.html#Shell::out - */ - public function success($message, int $newlines = 1, int $level = Shell::NORMAL): ?int - { - return $this->_io->success($message, $newlines, $level); - } - - /** - * Returns a single or multiple linefeeds sequences. - * - * @param int $multiplier Number of times the linefeed sequence should be repeated - * @return string - * @link https://book.cakephp.org/4/en/console-and-shells.html#Shell::nl - */ - public function nl(int $multiplier = 1): string - { - return $this->_io->nl($multiplier); - } - - /** - * Outputs a series of minus characters to the standard output, acts as a visual separator. - * - * @param int $newlines Number of newlines to pre- and append - * @param int $width Width of the line, defaults to 63 - * @return void - * @link https://book.cakephp.org/4/en/console-and-shells.html#Shell::hr - */ - public function hr(int $newlines = 0, int $width = 63): void - { - $this->_io->hr($newlines, $width); - } - - /** - * Displays a formatted error message - * and exits the application with an error code. - * - * @param string $message The error message - * @param int $exitCode The exit code for the shell task. - * @throws \Cake\Console\Exception\StopException - * @return void - * @link https://book.cakephp.org/4/en/console-and-shells.html#styling-output - * @psalm-return never-return - */ - public function abort(string $message, int $exitCode = self::CODE_ERROR): void - { - $this->_io->err('' . $message . ''); - throw new StopException($message, $exitCode); - } - - /** - * Clear the console - * - * @return void - * @link https://book.cakephp.org/4/en/console-and-shells.html#console-output - */ - public function clear(): void - { - if (!empty($this->params['noclear'])) { - return; - } - - if (DIRECTORY_SEPARATOR === '/') { - passthru('clear'); - } else { - passthru('cls'); - } - } - - /** - * Creates a file at given path - * - * @param string $path Where to put the file. - * @param string $contents Content to put in the file. - * @return bool Success - * @link https://book.cakephp.org/4/en/console-and-shells.html#creating-files - */ - public function createFile(string $path, string $contents): bool - { - $path = str_replace(DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR, $path); - - $this->_io->out(); - - $fileExists = is_file($path); - if ($fileExists && empty($this->params['force']) && !$this->interactive) { - $this->_io->out('File exists, skipping.'); - - return false; - } - - if ($fileExists && $this->interactive && empty($this->params['force'])) { - $this->_io->out(sprintf('File `%s` exists', $path)); - $key = $this->_io->askChoice('Do you want to overwrite?', ['y', 'n', 'a', 'q'], 'n'); - - if (strtolower($key) === 'q') { - $this->_io->out('Quitting.', 2); - $this->_stop(); - - return false; - } - if (strtolower($key) === 'a') { - $this->params['force'] = true; - $key = 'y'; - } - if (strtolower($key) !== 'y') { - $this->_io->out(sprintf('Skip `%s`', $path), 2); - - return false; - } - } else { - $this->out(sprintf('Creating file %s', $path)); - } - - try { - $fs = new Filesystem(); - $fs->dumpFile($path, $contents); - - $this->_io->out(sprintf('Wrote `%s`', $path)); - } catch (CakeException $e) { - $this->_io->err(sprintf('Could not write to `%s`.', $path), 2); - - return false; - } - - return true; - } - - /** - * Makes absolute file path easier to read - * - * @param string $file Absolute file path - * @return string short path - * @link https://book.cakephp.org/4/en/console-and-shells.html#Shell::shortPath - */ - public function shortPath(string $file): string - { - $shortPath = str_replace(ROOT, '', $file); - $shortPath = str_replace('..' . DIRECTORY_SEPARATOR, '', $shortPath); - $shortPath = str_replace(DIRECTORY_SEPARATOR, '/', $shortPath); - - return str_replace('//', DIRECTORY_SEPARATOR, $shortPath); - } - - /** - * Render a Console Helper - * - * Create and render the output for a helper object. If the helper - * object has not already been loaded, it will be loaded and constructed. - * - * @param string $name The name of the helper to render - * @param array $config Configuration data for the helper. - * @return \Cake\Console\Helper The created helper instance. - */ - public function helper(string $name, array $config = []): Helper - { - return $this->_io->helper($name, $config); - } - - /** - * Stop execution of the current script. - * Raises a StopException to try and halt the execution. - * - * @param int $status see https://secure.php.net/exit for values - * @throws \Cake\Console\Exception\StopException - * @return void - */ - protected function _stop(int $status = self::CODE_SUCCESS): void - { - throw new StopException('Halting error reached', $status); - } - - /** - * Returns an array that can be used to describe the internal state of this - * object. - * - * @return array - */ - public function __debugInfo(): array - { - return [ - 'name' => $this->name, - 'plugin' => $this->plugin, - 'command' => $this->command, - 'tasks' => $this->tasks, - 'params' => $this->params, - 'args' => $this->args, - 'interactive' => $this->interactive, - ]; - } -} diff --git a/app/vendor/cakephp/cakephp/src/Console/ShellDispatcher.php b/app/vendor/cakephp/cakephp/src/Console/ShellDispatcher.php deleted file mode 100644 index a8615a168..000000000 --- a/app/vendor/cakephp/cakephp/src/Console/ShellDispatcher.php +++ /dev/null @@ -1,424 +0,0 @@ - - */ - protected static $_aliases = []; - - /** - * Constructor - * - * The execution of the script is stopped after dispatching the request with - * a status code of either 0 or 1 according to the result of the dispatch. - * - * @param array $args the argv from PHP - * @param bool $bootstrap Should the environment be bootstrapped. - */ - public function __construct(array $args = [], bool $bootstrap = true) - { - set_time_limit(0); - $this->args = $args; - - $this->addShortPluginAliases(); - - if ($bootstrap) { - $this->_initEnvironment(); - } - } - - /** - * Add an alias for a shell command. - * - * Aliases allow you to call shells by alternate names. This is most - * useful when dealing with plugin shells that you want to have shorter - * names for. - * - * If you re-use an alias the last alias set will be the one available. - * - * ### Usage - * - * Aliasing a shell named ClassName: - * - * ``` - * $this->alias('alias', 'ClassName'); - * ``` - * - * Getting the original name for a given alias: - * - * ``` - * $this->alias('alias'); - * ``` - * - * @param string $short The new short name for the shell. - * @param string|null $original The original full name for the shell. - * @return string|null The aliased class name, or null if the alias does not exist - */ - public static function alias(string $short, ?string $original = null): ?string - { - $short = Inflector::camelize($short); - if ($original) { - static::$_aliases[$short] = $original; - } - - return static::$_aliases[$short] ?? null; - } - - /** - * Clear any aliases that have been set. - * - * @return void - */ - public static function resetAliases(): void - { - static::$_aliases = []; - } - - /** - * Run the dispatcher - * - * @param array $argv The argv from PHP - * @param array $extra Extra parameters - * @return int The exit code of the shell process. - */ - public static function run(array $argv, array $extra = []): int - { - $dispatcher = new ShellDispatcher($argv); - - return $dispatcher->dispatch($extra); - } - - /** - * Defines current working environment. - * - * @return void - * @throws \Cake\Core\Exception\CakeException - */ - protected function _initEnvironment(): void - { - $this->_bootstrap(); - - if (function_exists('ini_set')) { - ini_set('html_errors', '0'); - ini_set('implicit_flush', '1'); - ini_set('max_execution_time', '0'); - } - - $this->shiftArgs(); - } - - /** - * Initializes the environment and loads the CakePHP core. - * - * @return void - */ - protected function _bootstrap() - { - if (!Configure::read('App.fullBaseUrl')) { - Configure::write('App.fullBaseUrl', 'http://localhost'); - } - } - - /** - * Dispatches a CLI request - * - * Converts a shell command result into an exit code. Null/True - * are treated as success. All other return values are an error. - * - * @param array $extra Extra parameters that you can manually pass to the Shell - * to be dispatched. - * Built-in extra parameter is : - * - * - `requested` : if used, will prevent the Shell welcome message to be displayed - * @return int The CLI command exit code. 0 is success. - */ - public function dispatch(array $extra = []): int - { - try { - $result = $this->_dispatch($extra); - } catch (StopException $e) { - return $e->getCode(); - } - if ($result === null || $result === true) { - /** @psalm-suppress DeprecatedClass */ - return Shell::CODE_SUCCESS; - } - if (is_int($result)) { - return $result; - } - - /** @psalm-suppress DeprecatedClass */ - return Shell::CODE_ERROR; - } - - /** - * Dispatch a request. - * - * @param array $extra Extra parameters that you can manually pass to the Shell - * to be dispatched. - * Built-in extra parameter is : - * - * - `requested` : if used, will prevent the Shell welcome message to be displayed - * @return int|bool|null - * @throws \Cake\Console\Exception\MissingShellMethodException - */ - protected function _dispatch(array $extra = []) - { - $shellName = $this->shiftArgs(); - - if (!$shellName) { - $this->help(); - - return false; - } - if (in_array($shellName, ['help', '--help', '-h'], true)) { - $this->help(); - - return true; - } - if (in_array($shellName, ['version', '--version'], true)) { - $this->version(); - - return true; - } - - $shell = $this->findShell($shellName); - - $shell->initialize(); - - return $shell->runCommand($this->args, true, $extra); - } - - /** - * For all loaded plugins, add a short alias - * - * This permits a plugin which implements a shell of the same name to be accessed - * Using the shell name alone - * - * @return array the resultant list of aliases - */ - public function addShortPluginAliases(): array - { - $plugins = Plugin::loaded(); - - $io = new ConsoleIo(); - $task = new CommandTask($io); - $io->setLoggers(false); - $list = $task->getShellList() + ['app' => []]; - $fixed = array_flip($list['app']) + array_flip($list['CORE']); - $aliases = $others = []; - - foreach ($plugins as $plugin) { - if (!isset($list[$plugin])) { - continue; - } - - foreach ($list[$plugin] as $shell) { - $aliases += [$shell => $plugin]; - if (!isset($others[$shell])) { - $others[$shell] = [$plugin]; - } else { - $others[$shell] = array_merge($others[$shell], [$plugin]); - } - } - } - - foreach ($aliases as $shell => $plugin) { - if (isset($fixed[$shell])) { - Log::write( - 'debug', - "command '$shell' in plugin '$plugin' was not aliased, conflicts with another shell", - ['shell-dispatcher'] - ); - continue; - } - - $other = static::alias($shell); - if ($other) { - if ($other !== $plugin) { - Log::write( - 'debug', - "command '$shell' in plugin '$plugin' was not aliased, conflicts with '$other'", - ['shell-dispatcher'] - ); - } - continue; - } - - if (isset($others[$shell])) { - $conflicts = array_diff($others[$shell], [$plugin]); - if (count($conflicts) > 0) { - $conflictList = implode("', '", $conflicts); - Log::write( - 'debug', - "command '$shell' in plugin '$plugin' was not aliased, conflicts with '$conflictList'", - ['shell-dispatcher'] - ); - } - } - - static::alias($shell, "$plugin.$shell"); - } - - return static::$_aliases; - } - - /** - * Get shell to use, either plugin shell or application shell - * - * All paths in the loaded shell paths are searched, handles alias - * dereferencing - * - * @param string $shell Optionally the name of a plugin - * @return \Cake\Console\Shell A shell instance. - * @throws \Cake\Console\Exception\MissingShellException when errors are encountered. - */ - public function findShell(string $shell): Shell - { - $className = $this->_shellExists($shell); - if (!$className) { - $shell = $this->_handleAlias($shell); - $className = $this->_shellExists($shell); - } - - if (!$className) { - throw new MissingShellException([ - 'class' => $shell, - ]); - } - - return $this->_createShell($className, $shell); - } - - /** - * If the input matches an alias, return the aliased shell name - * - * @param string $shell Optionally the name of a plugin or alias - * @return string Shell name with plugin prefix - */ - protected function _handleAlias(string $shell): string - { - $aliased = static::alias($shell); - if ($aliased) { - $shell = $aliased; - } - - $class = array_map('Cake\Utility\Inflector::camelize', explode('.', $shell)); - - return implode('.', $class); - } - - /** - * Check if a shell class exists for the given name. - * - * @param string $shell The shell name to look for. - * @return string|null Either the classname or null. - */ - protected function _shellExists(string $shell): ?string - { - $class = App::className($shell, 'Shell', 'Shell'); - if ($class) { - return $class; - } - - return null; - } - - /** - * Create the given shell name, and set the plugin property - * - * @param string $className The class name to instantiate - * @param string $shortName The plugin-prefixed shell name - * @return \Cake\Console\Shell A shell instance. - */ - protected function _createShell(string $className, string $shortName): Shell - { - [$plugin] = pluginSplit($shortName); - /** @var \Cake\Console\Shell $instance */ - $instance = new $className(); - $instance->plugin = trim((string)$plugin, '.'); - - return $instance; - } - - /** - * Removes first argument and shifts other arguments up - * - * @return mixed Null if there are no arguments otherwise the shifted argument - */ - public function shiftArgs() - { - return array_shift($this->args); - } - - /** - * Shows console help. Performs an internal dispatch to the CommandList Shell - * - * @return void - */ - public function help(): void - { - trigger_error( - 'Console help cannot be generated from Shell classes anymore. ' . - 'Upgrade your application to use Cake\Console\CommandRunner instead.', - E_USER_WARNING - ); - } - - /** - * Prints the currently installed version of CakePHP. Performs an internal dispatch to the CommandList Shell - * - * @return void - */ - public function version(): void - { - trigger_error( - 'Version information cannot be generated from Shell classes anymore. ' . - 'Upgrade your application to use Cake\Console\CommandRunner instead.', - E_USER_WARNING - ); - } -} diff --git a/app/vendor/cakephp/cakephp/src/Console/TaskRegistry.php b/app/vendor/cakephp/cakephp/src/Console/TaskRegistry.php deleted file mode 100644 index 9368a3a22..000000000 --- a/app/vendor/cakephp/cakephp/src/Console/TaskRegistry.php +++ /dev/null @@ -1,97 +0,0 @@ - - */ -class TaskRegistry extends ObjectRegistry -{ - /** - * Shell to use to set params to tasks. - * - * @var \Cake\Console\Shell - */ - protected $_Shell; - - /** - * Constructor - * - * @param \Cake\Console\Shell $shell Shell instance - */ - public function __construct(Shell $shell) - { - $this->_Shell = $shell; - } - - /** - * Resolve a task classname. - * - * Part of the template method for {@link \Cake\Core\ObjectRegistry::load()}. - * - * @param string $class Partial classname to resolve. - * @return string|null Either the correct class name or null. - * @psalm-return class-string|null - */ - protected function _resolveClassName(string $class): ?string - { - return App::className($class, 'Shell/Task', 'Task'); - } - - /** - * Throws an exception when a task is missing. - * - * Part of the template method for Cake\Core\ObjectRegistry::load() - * and Cake\Core\ObjectRegistry::unload() - * - * @param string $class The classname that is missing. - * @param string|null $plugin The plugin the task is missing in. - * @return void - * @throws \Cake\Console\Exception\MissingTaskException - */ - protected function _throwMissingClassError(string $class, ?string $plugin): void - { - throw new MissingTaskException([ - 'class' => $class, - 'plugin' => $plugin, - ]); - } - - /** - * Create the task instance. - * - * Part of the template method for Cake\Core\ObjectRegistry::load() - * - * @param string $class The classname to create. - * @param string $alias The alias of the task. - * @param array $config An array of settings to use for the task. - * @return \Cake\Console\Shell The constructed task class. - * @psalm-suppress MoreSpecificImplementedParamType - */ - protected function _create($class, string $alias, array $config): Shell - { - /** @var \Cake\Console\Shell */ - return new $class($this->_Shell->getIo()); - } -} diff --git a/app/vendor/cakephp/cakephp/src/Console/TestSuite/ConsoleIntegrationTestTrait.php b/app/vendor/cakephp/cakephp/src/Console/TestSuite/ConsoleIntegrationTestTrait.php index b10d2cd98..3f9114519 100644 --- a/app/vendor/cakephp/cakephp/src/Console/TestSuite/ConsoleIntegrationTestTrait.php +++ b/app/vendor/cakephp/cakephp/src/Console/TestSuite/ConsoleIntegrationTestTrait.php @@ -15,9 +15,10 @@ */ namespace Cake\Console\TestSuite; -use Cake\Command\Command; +use Cake\Console\CommandInterface; use Cake\Console\CommandRunner; use Cake\Console\ConsoleIo; +use Cake\Console\ConsoleOutput; use Cake\Console\Exception\StopException; use Cake\Console\TestSuite\Constraint\ContentsContain; use Cake\Console\TestSuite\Constraint\ContentsContainRow; @@ -25,8 +26,11 @@ use Cake\Console\TestSuite\Constraint\ContentsNotContain; use Cake\Console\TestSuite\Constraint\ContentsRegExp; use Cake\Console\TestSuite\Constraint\ExitCode; +use Cake\Core\ConsoleApplicationInterface; use Cake\Core\TestSuite\ContainerStubTrait; -use RuntimeException; +use Cake\Error\Debugger; +use InvalidArgumentException; +use PHPUnit\Framework\Attributes\After; /** * A bundle of methods that makes testing commands @@ -39,40 +43,33 @@ trait ConsoleIntegrationTestTrait { use ContainerStubTrait; - /** - * Whether to use the CommandRunner - * - * @var bool - */ - protected $_useCommandRunner = false; - /** * Last exit code * * @var int|null */ - protected $_exitCode; + protected ?int $_exitCode = null; /** * Console output stub * - * @var \Cake\Console\TestSuite\StubConsoleOutput + * @var \Cake\Console\TestSuite\StubConsoleOutput|null */ - protected $_out; + protected ?StubConsoleOutput $_out = null; /** * Console error output stub * - * @var \Cake\Console\TestSuite\StubConsoleOutput + * @var \Cake\Console\TestSuite\StubConsoleOutput|null */ - protected $_err; + protected ?StubConsoleOutput $_err = null; /** * Console input mock * - * @var \Cake\Console\TestSuite\StubConsoleInput + * @var \Cake\Console\TestSuite\StubConsoleInput|null */ - protected $_in; + protected ?StubConsoleInput $_in = null; /** * Runs CLI integration test @@ -80,33 +77,33 @@ trait ConsoleIntegrationTestTrait * @param string $command Command to run * @param array $input Input values to pass to an interactive shell * @throws \Cake\Console\TestSuite\MissingConsoleInputException - * @throws \RuntimeException + * @throws \InvalidArgumentException * @return void */ public function exec(string $command, array $input = []): void { $runner = $this->makeRunner(); - if ($this->_out === null) { - $this->_out = new StubConsoleOutput(); - } - if ($this->_err === null) { - $this->_err = new StubConsoleOutput(); - } + $this->_out ??= new StubConsoleOutput(); + $this->_err ??= new StubConsoleOutput(); if ($this->_in === null) { $this->_in = new StubConsoleInput($input); } elseif ($input) { - throw new RuntimeException('You can use `$input` only if `$_in` property is null and will be reset.'); + throw new InvalidArgumentException( + 'You can use `$input` only if `$_in` property is null and will be reset.', + ); } + $this->_out->clear(); + $this->_err->clear(); - $args = $this->commandStringToArgs("cake $command"); + $args = $this->commandStringToArgs("cake {$command}"); $io = new ConsoleIo($this->_out, $this->_err, $this->_in); try { $this->_exitCode = $runner->run($args, $io); } catch (MissingConsoleInputException $e) { $messages = $this->_out->messages(); - if (count($messages)) { + if ($messages !== []) { $e->setQuestion($messages[count($messages) - 1]); } throw $e; @@ -118,28 +115,15 @@ public function exec(string $command, array $input = []): void /** * Cleans state to get ready for the next test * - * @after * @return void - * @psalm-suppress PossiblyNullPropertyAssignmentValue */ + #[After] public function cleanupConsoleTrait(): void { $this->_exitCode = null; $this->_out = null; $this->_err = null; $this->_in = null; - $this->_useCommandRunner = false; - } - - /** - * Set this test case to use the CommandRunner rather than the legacy - * ShellDispatcher - * - * @return void - */ - public function useCommandRunner(): void - { - $this->_useCommandRunner = true; } /** @@ -151,29 +135,41 @@ public function useCommandRunner(): void */ public function assertExitCode(int $expected, string $message = ''): void { - $this->assertThat($expected, new ExitCode($this->_exitCode), $message); + $this->assertThat( + $expected, + new ExitCode($this->_exitCode, $this->_out->messages(), $this->_err->messages()), + $message, + ); } /** - * Asserts shell exited with the Command::CODE_SUCCESS + * Asserts shell exited with the CommandInterface::CODE_SUCCESS * * @param string $message Failure message * @return void */ - public function assertExitSuccess($message = '') + public function assertExitSuccess(string $message = ''): void { - $this->assertThat(Command::CODE_SUCCESS, new ExitCode($this->_exitCode), $message); + $this->assertThat( + CommandInterface::CODE_SUCCESS, + new ExitCode($this->_exitCode, $this->_out->messages(), $this->_err->messages()), + $message, + ); } /** - * Asserts shell exited with Command::CODE_ERROR + * Asserts shell exited with CommandInterface::CODE_ERROR * * @param string $message Failure message * @return void */ - public function assertExitError($message = '') + public function assertExitError(string $message = ''): void { - $this->assertThat(Command::CODE_ERROR, new ExitCode($this->_exitCode), $message); + $this->assertThat( + CommandInterface::CODE_ERROR, + new ExitCode($this->_exitCode, $this->_out->messages(), $this->_err->messages()), + $message, + ); } /** @@ -271,20 +267,45 @@ public function assertErrorEmpty(string $message = ''): void } /** - * Builds the appropriate command dispatcher + * Dump the exit code, stdout and stderr from the most recently run command * - * @return \Cake\Console\CommandRunner|\Cake\Console\TestSuite\LegacyCommandRunner + * @param resource|null $stream The stream to write to. Defaults to STDOUT + * @return void */ - protected function makeRunner() + public function debugOutput($stream = null): void { - if ($this->_useCommandRunner) { - /** @var \Cake\Core\ConsoleApplicationInterface $app */ - $app = $this->createApp(); + $output = new ConsoleOutput($stream ?? 'php://stdout'); + if (class_exists(Debugger::class)) { + $trace = Debugger::trace(['start' => 0, 'depth' => 1, 'format' => 'array']); + $file = $trace[0]['file']; + $line = $trace[0]['line']; + $output->write("{$file} on {$line}"); + } + $output->write('########## debugOutput() ##########'); - return new CommandRunner($app); + if ($this->_exitCode !== null) { + $output->write('Exit Code'); + $output->write((string)$this->_exitCode, 2); } + $output->write('STDOUT'); + $output->write($this->_out->messages(), 2); + + $output->write('STDERR'); + $output->write($this->_err->messages()); + $output->write('###################################'); + } + + /** + * Builds the appropriate command dispatcher + * + * @return \Cake\Console\CommandRunner + */ + protected function makeRunner(): CommandRunner + { + $app = $this->createApp(); + assert($app instanceof ConsoleApplicationInterface); - return new LegacyCommandRunner(); + return new CommandRunner($app); } /** diff --git a/app/vendor/cakephp/cakephp/src/Console/TestSuite/Constraint/ContentsBase.php b/app/vendor/cakephp/cakephp/src/Console/TestSuite/Constraint/ContentsBase.php index f9f87322d..6078b7b5c 100644 --- a/app/vendor/cakephp/cakephp/src/Console/TestSuite/Constraint/ContentsBase.php +++ b/app/vendor/cakephp/cakephp/src/Console/TestSuite/Constraint/ContentsBase.php @@ -27,12 +27,12 @@ abstract class ContentsBase extends Constraint /** * @var string */ - protected $contents; + protected string $contents; /** * @var string */ - protected $output; + protected string $output; /** * Constructor diff --git a/app/vendor/cakephp/cakephp/src/Console/TestSuite/Constraint/ContentsContain.php b/app/vendor/cakephp/cakephp/src/Console/TestSuite/Constraint/ContentsContain.php index 82747a782..c6b4cf0fd 100644 --- a/app/vendor/cakephp/cakephp/src/Console/TestSuite/Constraint/ContentsContain.php +++ b/app/vendor/cakephp/cakephp/src/Console/TestSuite/Constraint/ContentsContain.php @@ -28,7 +28,7 @@ class ContentsContain extends ContentsBase * @param mixed $other Expected * @return bool */ - public function matches($other): bool + public function matches(mixed $other): bool { return mb_strpos($this->contents, $other) !== false; } @@ -40,7 +40,15 @@ public function matches($other): bool */ public function toString(): string { - return sprintf('is in %s,' . PHP_EOL . 'actual result:' . PHP_EOL, $this->output) . $this->contents; + return sprintf('is in %s.', $this->output); + } + + /** + * @inheritDoc + */ + protected function additionalFailureDescription(mixed $other): string + { + return sprintf("actual result:\n%s", $this->contents); } } diff --git a/app/vendor/cakephp/cakephp/src/Console/TestSuite/Constraint/ContentsContainRow.php b/app/vendor/cakephp/cakephp/src/Console/TestSuite/Constraint/ContentsContainRow.php index 583abbfb5..29531acc0 100644 --- a/app/vendor/cakephp/cakephp/src/Console/TestSuite/Constraint/ContentsContainRow.php +++ b/app/vendor/cakephp/cakephp/src/Console/TestSuite/Constraint/ContentsContainRow.php @@ -15,6 +15,8 @@ */ namespace Cake\Console\TestSuite\Constraint; +use SebastianBergmann\Exporter\Exporter; + /** * ContentsContainRow * @@ -25,15 +27,14 @@ class ContentsContainRow extends ContentsRegExp /** * Checks if contents contain expected * - * @param array $other Row + * @param mixed $other Row * @return bool - * @psalm-suppress MoreSpecificImplementedParamType */ - public function matches($other): bool + public function matches(mixed $other): bool { $row = array_map(function ($cell) { return preg_quote($cell, '/'); - }, $other); + }, (array)$other); $cells = implode('\s+\|\s+', $row); $pattern = '/' . $cells . '/'; @@ -54,9 +55,9 @@ public function toString(): string * @param mixed $other Expected content * @return string */ - public function failureDescription($other): string + public function failureDescription(mixed $other): string { - return '`' . $this->exporter()->shortenedExport($other) . '` ' . $this->toString(); + return '`' . (new Exporter())->shortenedExport($other) . '` ' . $this->toString(); } } diff --git a/app/vendor/cakephp/cakephp/src/Console/TestSuite/Constraint/ContentsEmpty.php b/app/vendor/cakephp/cakephp/src/Console/TestSuite/Constraint/ContentsEmpty.php index 6cae114e3..324338c70 100644 --- a/app/vendor/cakephp/cakephp/src/Console/TestSuite/Constraint/ContentsEmpty.php +++ b/app/vendor/cakephp/cakephp/src/Console/TestSuite/Constraint/ContentsEmpty.php @@ -28,7 +28,7 @@ class ContentsEmpty extends ContentsBase * @param mixed $other Expected * @return bool */ - public function matches($other): bool + public function matches(mixed $other): bool { return $this->contents === ''; } @@ -40,7 +40,7 @@ public function matches($other): bool */ public function toString(): string { - return sprintf('%s is empty', $this->output); + return sprintf('%s is empty.', $this->output); } /** @@ -49,10 +49,18 @@ public function toString(): string * @param mixed $other Value * @return string */ - protected function failureDescription($other): string + protected function failureDescription(mixed $other): string { return $this->toString(); } + + /** + * @inheritDoc + */ + protected function additionalFailureDescription(mixed $other): string + { + return sprintf("actual result:\n%s", $this->contents); + } } // phpcs:disable diff --git a/app/vendor/cakephp/cakephp/src/Console/TestSuite/Constraint/ContentsNotContain.php b/app/vendor/cakephp/cakephp/src/Console/TestSuite/Constraint/ContentsNotContain.php index c8a666c13..4417fc134 100644 --- a/app/vendor/cakephp/cakephp/src/Console/TestSuite/Constraint/ContentsNotContain.php +++ b/app/vendor/cakephp/cakephp/src/Console/TestSuite/Constraint/ContentsNotContain.php @@ -28,7 +28,7 @@ class ContentsNotContain extends ContentsBase * @param mixed $other Expected * @return bool */ - public function matches($other): bool + public function matches(mixed $other): bool { return mb_strpos($this->contents, $other) === false; } @@ -40,7 +40,15 @@ public function matches($other): bool */ public function toString(): string { - return sprintf('is not in %s', $this->output); + return sprintf('is not in %s.', $this->output); + } + + /** + * @inheritDoc + */ + protected function additionalFailureDescription(mixed $other): string + { + return sprintf("actual result:\n%s", $this->contents); } } diff --git a/app/vendor/cakephp/cakephp/src/Console/TestSuite/Constraint/ContentsRegExp.php b/app/vendor/cakephp/cakephp/src/Console/TestSuite/Constraint/ContentsRegExp.php index a715b95d6..1ac324111 100644 --- a/app/vendor/cakephp/cakephp/src/Console/TestSuite/Constraint/ContentsRegExp.php +++ b/app/vendor/cakephp/cakephp/src/Console/TestSuite/Constraint/ContentsRegExp.php @@ -28,7 +28,7 @@ class ContentsRegExp extends ContentsBase * @param mixed $other Expected * @return bool */ - public function matches($other): bool + public function matches(mixed $other): bool { return preg_match($other, $this->contents) > 0; } @@ -47,10 +47,18 @@ public function toString(): string * @param mixed $other Expected * @return string */ - public function failureDescription($other): string + public function failureDescription(mixed $other): string { return '`' . $other . '` ' . $this->toString(); } + + /** + * @inheritDoc + */ + protected function additionalFailureDescription(mixed $other): string + { + return sprintf("actual result:\n%s", $this->contents); + } } // phpcs:disable diff --git a/app/vendor/cakephp/cakephp/src/Console/TestSuite/Constraint/ExitCode.php b/app/vendor/cakephp/cakephp/src/Console/TestSuite/Constraint/ExitCode.php index da3f46a5d..1fda99644 100644 --- a/app/vendor/cakephp/cakephp/src/Console/TestSuite/Constraint/ExitCode.php +++ b/app/vendor/cakephp/cakephp/src/Console/TestSuite/Constraint/ExitCode.php @@ -27,16 +27,30 @@ class ExitCode extends Constraint /** * @var int|null */ - private $exitCode; + private ?int $exitCode = null; + + /** + * @var array + */ + private array $out = []; + + /** + * @var array + */ + private array $err = []; /** * Constructor * * @param int|null $exitCode Exit code + * @param array $out stdout stream + * @param array $err stderr stream */ - public function __construct(?int $exitCode) + public function __construct(?int $exitCode, array $out, array $err) { $this->exitCode = $exitCode; + $this->out = $out; + $this->err = $err; } /** @@ -45,7 +59,7 @@ public function __construct(?int $exitCode) * @param mixed $other Constraint check * @return bool */ - public function matches($other): bool + public function matches(mixed $other): bool { return $other === $this->exitCode; } @@ -57,7 +71,30 @@ public function matches($other): bool */ public function toString(): string { - return sprintf('matches exit code %s', $this->exitCode ?? 'null'); + return sprintf('matches exit code `%s`', $this->exitCode ?? 'null'); + } + + /** + * Returns the description of the failure. + * + * @param mixed $other Expected + * @return string + */ + public function failureDescription(mixed $other): string + { + return '`' . $other . '` ' . $this->toString(); + } + + /** + * @inheritDoc + */ + public function additionalFailureDescription(mixed $other): string + { + return sprintf( + "STDOUT\n%s\n\nSTDERR\n%s\n", + implode("\n", $this->out), + implode("\n", $this->err), + ); } } diff --git a/app/vendor/cakephp/cakephp/src/Console/TestSuite/LegacyCommandRunner.php b/app/vendor/cakephp/cakephp/src/Console/TestSuite/LegacyCommandRunner.php deleted file mode 100644 index d1ec2c74d..000000000 --- a/app/vendor/cakephp/cakephp/src/Console/TestSuite/LegacyCommandRunner.php +++ /dev/null @@ -1,46 +0,0 @@ -dispatch(); - } -} - -// phpcs:disable -class_alias( - 'Cake\Console\TestSuite\LegacyCommandRunner', - 'Cake\TestSuite\LegacyCommandRunner' -); -// phpcs:enable diff --git a/app/vendor/cakephp/cakephp/src/Console/TestSuite/LegacyShellDispatcher.php b/app/vendor/cakephp/cakephp/src/Console/TestSuite/LegacyShellDispatcher.php deleted file mode 100644 index cc9fde4da..000000000 --- a/app/vendor/cakephp/cakephp/src/Console/TestSuite/LegacyShellDispatcher.php +++ /dev/null @@ -1,72 +0,0 @@ -_io = $io; - parent::__construct($args, $bootstrap); - } - - /** - * Injects mock and stub io components into the shell - * - * @param string $className Class name - * @param string $shortName Short name - * @return \Cake\Console\Shell - */ - protected function _createShell(string $className, string $shortName): Shell - { - [$plugin] = pluginSplit($shortName); - /** @var \Cake\Console\Shell $instance */ - $instance = new $className($this->_io); - if ($plugin) { - $instance->plugin = trim($plugin, '.'); - } - - return $instance; - } -} - -// phpcs:disable -class_alias( - 'Cake\Console\TestSuite\LegacyShellDispatcher', - 'Cake\TestSuite\LegacyShellDispatcher' -); -// phpcs:enable diff --git a/app/vendor/cakephp/cakephp/src/Console/TestSuite/MissingConsoleInputException.php b/app/vendor/cakephp/cakephp/src/Console/TestSuite/MissingConsoleInputException.php index 9b58c4659..fdd6b8c6c 100644 --- a/app/vendor/cakephp/cakephp/src/Console/TestSuite/MissingConsoleInputException.php +++ b/app/vendor/cakephp/cakephp/src/Console/TestSuite/MissingConsoleInputException.php @@ -28,7 +28,7 @@ class MissingConsoleInputException extends RuntimeException * @param string $question The question text. * @return void */ - public function setQuestion($question) + public function setQuestion(string $question): void { $this->message .= "\nThe question asked was: " . $question; } diff --git a/app/vendor/cakephp/cakephp/src/Console/TestSuite/StubConsoleInput.php b/app/vendor/cakephp/cakephp/src/Console/TestSuite/StubConsoleInput.php index cd968194a..ab8213f48 100644 --- a/app/vendor/cakephp/cakephp/src/Console/TestSuite/StubConsoleInput.php +++ b/app/vendor/cakephp/cakephp/src/Console/TestSuite/StubConsoleInput.php @@ -31,14 +31,14 @@ class StubConsoleInput extends ConsoleInput * * @var array */ - protected $replies = []; + protected array $replies = []; /** * Current message index * * @var int */ - protected $currentIndex = -1; + protected int $currentIndex = -1; /** * Constructor @@ -47,9 +47,10 @@ class StubConsoleInput extends ConsoleInput */ public function __construct(array $replies) { - parent::__construct(); - + // Don't call parent on purpose as it opens php://stdin which doesn't + // always exist in RunInSeparateProcess tests. $this->replies = $replies; + $this->_canReadline = false; } /** @@ -81,7 +82,7 @@ public function read(): string * @param int $timeout An optional time to wait for data * @return bool True for data available, false otherwise */ - public function dataAvailable($timeout = 0): bool + public function dataAvailable(int $timeout = 0): bool { return true; } diff --git a/app/vendor/cakephp/cakephp/src/Console/TestSuite/StubConsoleOutput.php b/app/vendor/cakephp/cakephp/src/Console/TestSuite/StubConsoleOutput.php index 46b234237..2c7b8cd38 100644 --- a/app/vendor/cakephp/cakephp/src/Console/TestSuite/StubConsoleOutput.php +++ b/app/vendor/cakephp/cakephp/src/Console/TestSuite/StubConsoleOutput.php @@ -38,7 +38,17 @@ class StubConsoleOutput extends ConsoleOutput * * @var array */ - protected $_out = []; + protected array $_out = []; + + /** + * Constructor + */ + public function __construct() + { + // Don't call parent on purpose as it opens php://stdin which doesn't + // always exist in RunInSeparateProcess tests. + $this->_outputAs = self::PLAIN; + } /** * Write output to the buffer. @@ -47,7 +57,7 @@ class StubConsoleOutput extends ConsoleOutput * @param int $newlines Number of newlines to append * @return int */ - public function write($message, int $newlines = 1): int + public function write(array|string $message, int $newlines = 1): int { foreach ((array)$message as $line) { $this->_out[] = $line; @@ -72,6 +82,16 @@ public function messages(): array return $this->_out; } + /** + * Clear buffered output + * + * @return void + */ + public function clear(): void + { + $this->_out = []; + } + /** * Get the output as a string * diff --git a/app/vendor/cakephp/cakephp/src/Console/composer.json b/app/vendor/cakephp/cakephp/src/Console/composer.json index d7d392b50..d3a061fe5 100644 --- a/app/vendor/cakephp/cakephp/src/Console/composer.json +++ b/app/vendor/cakephp/cakephp/src/Console/composer.json @@ -23,20 +23,25 @@ "source": "https://github.com/cakephp/console" }, "require": { - "php": ">=7.4.0", - "cakephp/core": "^4.0", - "cakephp/event": "^4.0", - "cakephp/filesystem": "^4.0", - "cakephp/log": "^4.0", - "cakephp/utility": "^4.0" + "php": ">=8.1", + "cakephp/core": "5.2.*@dev", + "cakephp/event": "5.2.*@dev", + "cakephp/log": "5.2.*@dev", + "cakephp/utility": "5.2.*@dev" }, "suggest": { - "cakephp/datasource": "To use the Shell or Command base classes", - "cakephp/orm": "To use the Shell or Command base classes" + "cakephp/datasource": "To use the Command base classes", + "cakephp/orm": "To use the Command base classes" }, "autoload": { "psr-4": { "Cake\\Console\\": "." } + }, + "prefer-stable": true, + "extra": { + "branch-alias": { + "dev-5.x": "5.2.x-dev" + } } } diff --git a/app/vendor/cakephp/cakephp/src/Controller/Component.php b/app/vendor/cakephp/cakephp/src/Controller/Component.php index 9cdc87ad1..695706b1d 100644 --- a/app/vendor/cakephp/cakephp/src/Controller/Component.php +++ b/app/vendor/cakephp/cakephp/src/Controller/Component.php @@ -19,7 +19,6 @@ use Cake\Core\InstanceConfigTrait; use Cake\Event\EventListenerInterface; use Cake\Log\LogTrait; -use function Cake\Core\deprecationWarning; /** * Base class for an individual Component. Components provide reusable bits of @@ -56,10 +55,9 @@ * While the controller is not an explicit argument for the callback methods it * is the subject of each event and can be fetched using EventInterface::getSubject(). * - * @link https://book.cakephp.org/4/en/controllers/components.html + * @link https://book.cakephp.org/5/en/controllers/components.html * @see \Cake\Controller\Controller::$components */ -#[\AllowDynamicProperties] class Component implements EventListenerInterface { use InstanceConfigTrait; @@ -70,14 +68,14 @@ class Component implements EventListenerInterface * * @var \Cake\Controller\ComponentRegistry */ - protected $_registry; + protected ComponentRegistry $_registry; /** * Other Components this component uses. * * @var array */ - protected $components = []; + protected array $components = []; /** * Default config @@ -86,14 +84,14 @@ class Component implements EventListenerInterface * * @var array */ - protected $_defaultConfig = []; + protected array $_defaultConfig = []; /** - * A component lookup table used to lazy load component objects. + * Loaded component instances. * - * @var array + * @var array */ - protected $_componentMap = []; + protected array $componentInstances = []; /** * Constructor @@ -109,7 +107,7 @@ public function __construct(ComponentRegistry $registry, array $config = []) $this->setConfig($config); if ($this->components) { - $this->_componentMap = $registry->normalizeArray($this->components); + $this->components = $registry->normalizeArray($this->components); } $this->initialize($config); } @@ -143,14 +141,22 @@ public function initialize(array $config): void * @param string $name Name of component to get. * @return \Cake\Controller\Component|null A Component object or null. */ - public function __get(string $name) + public function __get(string $name): ?Component { - if (isset($this->_componentMap[$name]) && !isset($this->{$name})) { - $config = (array)$this->_componentMap[$name]['config'] + ['enabled' => false]; - $this->{$name} = $this->_registry->load($this->_componentMap[$name]['class'], $config); + if (isset($this->componentInstances[$name])) { + return $this->componentInstances[$name]; } - return $this->{$name} ?? null; + if (isset($this->components[$name])) { + $config = $this->components[$name] + ['enabled' => false]; + + return $this->componentInstances[$name] = $this->_registry->load( + $name, + $config, + ); + } + + return null; } /** @@ -181,14 +187,6 @@ public function implementedEvents(): array } } - if (!isset($events['Controller.shutdown']) && method_exists($this, 'shutdown')) { - deprecationWarning( - '`Controller.shutdown` event callback is now `afterFilter()` instead of `shutdown()`.', - 0 - ); - $events[$event] = 'shutdown'; - } - return $events; } diff --git a/app/vendor/cakephp/cakephp/src/Controller/Component/AuthComponent.php b/app/vendor/cakephp/cakephp/src/Controller/Component/AuthComponent.php deleted file mode 100644 index 851117c75..000000000 --- a/app/vendor/cakephp/cakephp/src/Controller/Component/AuthComponent.php +++ /dev/null @@ -1,1000 +0,0 @@ -Auth->setConfig('authenticate', [ - * 'Form' => [ - * 'userModel' => 'Users.Users' - * ] - * ]); - * ``` - * - * Using the class name without 'Authenticate' as the key, you can pass in an - * array of config for each authentication object. Additionally, you can define - * config that should be set to all authentications objects using the 'all' key: - * - * ``` - * $this->Auth->setConfig('authenticate', [ - * AuthComponent::ALL => [ - * 'userModel' => 'Users.Users', - * 'scope' => ['Users.active' => 1] - * ], - * 'Form', - * 'Basic' - * ]); - * ``` - * - * - `authorize` - An array of authorization objects to use for authorizing users. - * You can configure multiple adapters and they will be checked sequentially - * when authorization checks are done. - * - * ``` - * $this->Auth->setConfig('authorize', [ - * 'Crud' => [ - * 'actionPath' => 'controllers/' - * ] - * ]); - * ``` - * - * Using the class name without 'Authorize' as the key, you can pass in an array - * of config for each authorization object. Additionally you can define config - * that should be set to all authorization objects using the AuthComponent::ALL key: - * - * ``` - * $this->Auth->setConfig('authorize', [ - * AuthComponent::ALL => [ - * 'actionPath' => 'controllers/' - * ], - * 'Crud', - * 'CustomAuth' - * ]); - * ``` - * - * - `flash` - Settings to use when Auth needs to do a flash message with - * FlashComponent::set(). Available keys are: - * - * - `key` - The message domain to use for flashes generated by this component, - * defaults to 'auth'. - * - `element` - Flash element to use, defaults to 'default'. - * - `params` - The array of additional params to use, defaults to ['class' => 'error'] - * - * - `loginAction` - A URL (defined as a string or array) to the controller action - * that handles logins. Defaults to `/users/login`. - * - * - `loginRedirect` - Normally, if a user is redirected to the `loginAction` page, - * the location they were redirected from will be stored in the session so that - * they can be redirected back after a successful login. If this session value - * is not set, redirectUrl() method will return the URL specified in `loginRedirect`. - * - * - `logoutRedirect` - The default action to redirect to after the user is logged out. - * While AuthComponent does not handle post-logout redirection, a redirect URL - * will be returned from `AuthComponent::logout()`. Defaults to `loginAction`. - * - * - `authError` - Error to display when user attempts to access an object or - * action to which they do not have access. - * - * - `unauthorizedRedirect` - Controls handling of unauthorized access. - * - * - For default value `true` unauthorized user is redirected to the referrer URL - * or `$loginRedirect` or '/'. - * - If set to a string or array the value is used as a URL to redirect to. - * - If set to false a `ForbiddenException` exception is thrown instead of redirecting. - * - * - `storage` - Storage class to use for persisting user record. When using - * stateless authenticator you should set this to 'Memory'. Defaults to 'Session'. - * - * - `checkAuthIn` - Name of event for which initial auth checks should be done. - * Defaults to 'Controller.startup'. You can set it to 'Controller.initialize' - * if you want the check to be done before controller's beforeFilter() is run. - * - * @var array - */ - protected $_defaultConfig = [ - 'authenticate' => null, - 'authorize' => null, - 'flash' => null, - 'loginAction' => null, - 'loginRedirect' => null, - 'logoutRedirect' => null, - 'authError' => null, - 'unauthorizedRedirect' => true, - 'storage' => 'Session', - 'checkAuthIn' => 'Controller.startup', - ]; - - /** - * Other components utilized by AuthComponent - * - * @var array - */ - protected $components = ['RequestHandler', 'Flash']; - - /** - * Objects that will be used for authentication checks. - * - * @var array<\Cake\Auth\BaseAuthenticate> - */ - protected $_authenticateObjects = []; - - /** - * Objects that will be used for authorization checks. - * - * @var array<\Cake\Auth\BaseAuthorize> - */ - protected $_authorizeObjects = []; - - /** - * Storage object. - * - * @var \Cake\Auth\Storage\StorageInterface|null - */ - protected $_storage; - - /** - * Controller actions for which user validation is not required. - * - * @var array - * @see \Cake\Controller\Component\AuthComponent::allow() - */ - public $allowedActions = []; - - /** - * The instance of the Authenticate provider that was used for - * successfully logging in the current user after calling `login()` - * in the same request - * - * @var \Cake\Auth\BaseAuthenticate|null - */ - protected $_authenticationProvider; - - /** - * The instance of the Authorize provider that was used to grant - * access to the current user to the URL they are requesting. - * - * @var \Cake\Auth\BaseAuthorize|null - */ - protected $_authorizationProvider; - - /** - * Initialize properties. - * - * @param array $config The config data. - * @return void - */ - public function initialize(array $config): void - { - $controller = $this->_registry->getController(); - $this->setEventManager($controller->getEventManager()); - } - - /** - * Callback for Controller.startup event. - * - * @param \Cake\Event\EventInterface $event Event instance. - * @return \Cake\Http\Response|null - */ - public function startup(EventInterface $event): ?Response - { - return $this->authCheck($event); - } - - /** - * Main execution method, handles initial authentication check and redirection - * of invalid users. - * - * The auth check is done when event name is same as the one configured in - * `checkAuthIn` config. - * - * @param \Cake\Event\EventInterface $event Event instance. - * @return \Cake\Http\Response|null - * @throws \ReflectionException - */ - public function authCheck(EventInterface $event): ?Response - { - if ($this->_config['checkAuthIn'] !== $event->getName()) { - return null; - } - - /** @var \Cake\Controller\Controller $controller */ - $controller = $event->getSubject(); - - $action = $controller->getRequest()->getParam('action'); - if ($action === null || !$controller->isAction($action)) { - return null; - } - - $this->_setDefaults(); - - if ($this->_isAllowed($controller)) { - return null; - } - - $isLoginAction = $this->_isLoginAction($controller); - - if (!$this->_getUser()) { - if ($isLoginAction) { - return null; - } - $result = $this->_unauthenticated($controller); - if ($result instanceof Response) { - $event->stopPropagation(); - } - - return $result; - } - - if ( - $isLoginAction || - empty($this->_config['authorize']) || - $this->isAuthorized($this->user()) - ) { - return null; - } - - $event->stopPropagation(); - - return $this->_unauthorized($controller); - } - - /** - * Events supported by this component. - * - * @return array - */ - public function implementedEvents(): array - { - return [ - 'Controller.initialize' => 'authCheck', - 'Controller.startup' => 'startup', - ]; - } - - /** - * Checks whether current action is accessible without authentication. - * - * @param \Cake\Controller\Controller $controller A reference to the instantiating - * controller object - * @return bool True if action is accessible without authentication else false - */ - protected function _isAllowed(Controller $controller): bool - { - $action = strtolower($controller->getRequest()->getParam('action', '')); - - return in_array($action, array_map('strtolower', $this->allowedActions), true); - } - - /** - * Handles unauthenticated access attempt. First the `unauthenticated()` method - * of the last authenticator in the chain will be called. The authenticator can - * handle sending response or redirection as appropriate and return `true` to - * indicate no further action is necessary. If authenticator returns null this - * method redirects user to login action. - * - * @param \Cake\Controller\Controller $controller A reference to the controller object. - * @return \Cake\Http\Response|null Null if current action is login action - * else response object returned by authenticate object or Controller::redirect(). - * @throws \Cake\Core\Exception\CakeException - */ - protected function _unauthenticated(Controller $controller): ?Response - { - if (empty($this->_authenticateObjects)) { - $this->constructAuthenticate(); - } - $response = $controller->getResponse(); - $auth = end($this->_authenticateObjects); - if ($auth === false) { - throw new CakeException('At least one authenticate object must be available.'); - } - $result = $auth->unauthenticated($controller->getRequest(), $response); - if ($result !== null) { - return $result instanceof Response ? $result : null; - } - - if (!$controller->getRequest()->is('ajax')) { - $this->flash($this->_config['authError']); - - return $controller->redirect($this->_loginActionRedirectUrl()); - } - - return $response->withStatus(403); - } - - /** - * Returns the URL of the login action to redirect to. - * - * This includes the redirect query string if applicable. - * - * @return array|string - */ - protected function _loginActionRedirectUrl() - { - $urlToRedirectBackTo = $this->_getUrlToRedirectBackTo(); - - $loginAction = $this->_config['loginAction']; - if ($urlToRedirectBackTo === '/') { - return $loginAction; - } - - if (is_array($loginAction)) { - $loginAction['?'][static::QUERY_STRING_REDIRECT] = $urlToRedirectBackTo; - } else { - $char = strpos($loginAction, '?') === false ? '?' : '&'; - $loginAction .= $char . static::QUERY_STRING_REDIRECT . '=' . urlencode($urlToRedirectBackTo); - } - - return $loginAction; - } - - /** - * Normalizes config `loginAction` and checks if current request URL is same as login action. - * - * @param \Cake\Controller\Controller $controller A reference to the controller object. - * @return bool True if current action is login action else false. - */ - protected function _isLoginAction(Controller $controller): bool - { - $uri = $controller->getRequest()->getUri(); - $url = Router::normalize($uri->getPath()); - $loginAction = Router::normalize($this->_config['loginAction']); - - return $loginAction === $url; - } - - /** - * Handle unauthorized access attempt - * - * @param \Cake\Controller\Controller $controller A reference to the controller object - * @return \Cake\Http\Response|null - * @throws \Cake\Http\Exception\ForbiddenException - */ - protected function _unauthorized(Controller $controller): ?Response - { - if ($this->_config['unauthorizedRedirect'] === false) { - throw new ForbiddenException($this->_config['authError']); - } - - $this->flash($this->_config['authError']); - if ($this->_config['unauthorizedRedirect'] === true) { - $default = '/'; - if (!empty($this->_config['loginRedirect'])) { - $default = $this->_config['loginRedirect']; - } - if (is_array($default)) { - $default['_base'] = false; - } - $url = $controller->referer($default, true); - } else { - $url = $this->_config['unauthorizedRedirect']; - } - - return $controller->redirect($url); - } - - /** - * Sets defaults for configs. - * - * @return void - */ - protected function _setDefaults(): void - { - $defaults = [ - 'authenticate' => ['Form'], - 'flash' => [ - 'element' => 'error', - 'key' => 'flash', - 'params' => ['class' => 'error'], - ], - 'loginAction' => [ - 'controller' => 'Users', - 'action' => 'login', - 'plugin' => null, - ], - 'logoutRedirect' => $this->_config['loginAction'], - 'authError' => __d('cake', 'You are not authorized to access that location.'), - ]; - - $config = $this->getConfig(); - foreach ($config as $key => $value) { - if ($value !== null) { - unset($defaults[$key]); - } - } - $this->setConfig($defaults); - } - - /** - * Check if the provided user is authorized for the request. - * - * Uses the configured Authorization adapters to check whether a user is authorized. - * Each adapter will be checked in sequence, if any of them return true, then the user will - * be authorized for the request. - * - * @param \ArrayAccess|array|null $user The user to check the authorization of. - * If empty the user fetched from storage will be used. - * @param \Cake\Http\ServerRequest|null $request The request to authenticate for. - * If empty, the current request will be used. - * @return bool True if $user is authorized, otherwise false - */ - public function isAuthorized($user = null, ?ServerRequest $request = null): bool - { - if (empty($user) && !$this->user()) { - return false; - } - if (empty($user)) { - $user = $this->user(); - } - if (empty($request)) { - $request = $this->getController()->getRequest(); - } - if (empty($this->_authorizeObjects)) { - $this->constructAuthorize(); - } - foreach ($this->_authorizeObjects as $authorizer) { - if ($authorizer->authorize($user, $request) === true) { - $this->_authorizationProvider = $authorizer; - - return true; - } - } - - return false; - } - - /** - * Loads the authorization objects configured. - * - * @return array|null The loaded authorization objects, or null when authorize is empty. - * @throws \Cake\Core\Exception\CakeException - */ - public function constructAuthorize(): ?array - { - if (empty($this->_config['authorize'])) { - return null; - } - $this->_authorizeObjects = []; - $authorize = Hash::normalize((array)$this->_config['authorize']); - $global = []; - if (isset($authorize[AuthComponent::ALL])) { - $global = $authorize[AuthComponent::ALL]; - unset($authorize[AuthComponent::ALL]); - } - foreach ($authorize as $alias => $config) { - if (!empty($config['className'])) { - $class = $config['className']; - unset($config['className']); - } else { - $class = $alias; - } - $className = App::className($class, 'Auth', 'Authorize'); - if ($className === null) { - throw new CakeException(sprintf('Authorization adapter "%s" was not found.', $class)); - } - if (!method_exists($className, 'authorize')) { - throw new CakeException('Authorization objects must implement an authorize() method.'); - } - $config = (array)$config + $global; - $this->_authorizeObjects[$alias] = new $className($this->_registry, $config); - } - - return $this->_authorizeObjects; - } - - /** - * Getter for authorize objects. Will return a particular authorize object. - * - * @param string $alias Alias for the authorize object - * @return \Cake\Auth\BaseAuthorize|null - */ - public function getAuthorize(string $alias): ?BaseAuthorize - { - if (empty($this->_authorizeObjects)) { - $this->constructAuthorize(); - } - - return $this->_authorizeObjects[$alias] ?? null; - } - - /** - * Takes a list of actions in the current controller for which authentication is not required, or - * no parameters to allow all actions. - * - * You can use allow with either an array or a simple string. - * - * ``` - * $this->Auth->allow('view'); - * $this->Auth->allow(['edit', 'add']); - * ``` - * or to allow all actions - * ``` - * $this->Auth->allow(); - * ``` - * - * @param array|string|null $actions Controller action name or array of actions - * @return void - * @link https://book.cakephp.org/4/en/controllers/components/authentication.html#making-actions-public - */ - public function allow($actions = null): void - { - if ($actions === null) { - $controller = $this->_registry->getController(); - $this->allowedActions = get_class_methods($controller); - - return; - } - $this->allowedActions = array_merge($this->allowedActions, (array)$actions); - } - - /** - * Removes items from the list of allowed/no authentication required actions. - * - * You can use deny with either an array or a simple string. - * - * ``` - * $this->Auth->deny('view'); - * $this->Auth->deny(['edit', 'add']); - * ``` - * or - * ``` - * $this->Auth->deny(); - * ``` - * to remove all items from the allowed list - * - * @param array|string|null $actions Controller action name or array of actions - * @return void - * @see \Cake\Controller\Component\AuthComponent::allow() - * @link https://book.cakephp.org/4/en/controllers/components/authentication.html#making-actions-require-authorization - */ - public function deny($actions = null): void - { - if ($actions === null) { - $this->allowedActions = []; - - return; - } - foreach ((array)$actions as $action) { - $i = array_search($action, $this->allowedActions, true); - if (is_int($i)) { - unset($this->allowedActions[$i]); - } - } - $this->allowedActions = array_values($this->allowedActions); - } - - /** - * Set provided user info to storage as logged in user. - * - * The storage class is configured using `storage` config key or passing - * instance to AuthComponent::storage(). - * - * @param \ArrayAccess|array $user User data. - * @return void - * @link https://book.cakephp.org/4/en/controllers/components/authentication.html#identifying-users-and-logging-them-in - */ - public function setUser($user): void - { - $this->storage()->write($user); - } - - /** - * Log a user out. - * - * Returns the logout action to redirect to. Triggers the `Auth.logout` event - * which the authenticate classes can listen for and perform custom logout logic. - * - * @return string Normalized config `logoutRedirect` - * @link https://book.cakephp.org/4/en/controllers/components/authentication.html#logging-users-out - */ - public function logout(): string - { - $this->_setDefaults(); - if (empty($this->_authenticateObjects)) { - $this->constructAuthenticate(); - } - $user = (array)$this->user(); - $this->dispatchEvent('Auth.logout', [$user]); - $this->storage()->delete(); - - return Router::normalize($this->_config['logoutRedirect']); - } - - /** - * Get the current user from storage. - * - * @param string|null $key Field to retrieve. Leave null to get entire User record. - * @return mixed|null Either User record or null if no user is logged in, or retrieved field if key is specified. - * @link https://book.cakephp.org/4/en/controllers/components/authentication.html#accessing-the-logged-in-user - */ - public function user(?string $key = null) - { - $user = $this->storage()->read(); - if (!$user) { - return null; - } - - if ($key === null) { - return $user; - } - - return Hash::get($user, $key); - } - - /** - * Similar to AuthComponent::user() except if user is not found in - * configured storage, connected authentication objects will have their - * getUser() methods called. - * - * This lets stateless authentication methods function correctly. - * - * @return bool true If a user can be found, false if one cannot. - */ - protected function _getUser(): bool - { - $user = $this->user(); - if ($user) { - return true; - } - - if (empty($this->_authenticateObjects)) { - $this->constructAuthenticate(); - } - foreach ($this->_authenticateObjects as $auth) { - $result = $auth->getUser($this->getController()->getRequest()); - if (!empty($result) && is_array($result)) { - $this->_authenticationProvider = $auth; - $event = $this->dispatchEvent('Auth.afterIdentify', [$result, $auth]); - if ($event->getResult() !== null) { - $result = $event->getResult(); - } - $this->storage()->write($result); - - return true; - } - } - - return false; - } - - /** - * Get the URL a user should be redirected to upon login. - * - * Pass a URL in to set the destination a user should be redirected to upon - * logging in. - * - * If no parameter is passed, gets the authentication redirect URL. The URL - * returned is as per following rules: - * - * - Returns the normalized redirect URL from storage if it is - * present and for the same domain the current app is running on. - * - If there is no URL returned from storage and there is a config - * `loginRedirect`, the `loginRedirect` value is returned. - * - If there is no session and no `loginRedirect`, / is returned. - * - * @param array|string|null $url Optional URL to write as the login redirect URL. - * @return string Redirect URL - */ - public function redirectUrl($url = null): string - { - $redirectUrl = $this->getController()->getRequest()->getQuery(static::QUERY_STRING_REDIRECT); - if ($redirectUrl && (substr($redirectUrl, 0, 1) !== '/' || substr($redirectUrl, 0, 2) === '//')) { - $redirectUrl = null; - } - - if ($url !== null) { - $redirectUrl = $url; - } elseif ($redirectUrl) { - if ( - $this->_config['loginAction'] - && Router::normalize($redirectUrl) === Router::normalize($this->_config['loginAction']) - ) { - $redirectUrl = $this->_config['loginRedirect']; - } - } elseif ($this->_config['loginRedirect']) { - $redirectUrl = $this->_config['loginRedirect']; - } else { - $redirectUrl = '/'; - } - if (is_array($redirectUrl)) { - return Router::url($redirectUrl + ['_base' => false]); - } - - return $redirectUrl; - } - - /** - * Use the configured authentication adapters, and attempt to identify the user - * by credentials contained in $request. - * - * Triggers `Auth.afterIdentify` event which the authenticate classes can listen - * to. - * - * @return array|false User record data, or false, if the user could not be identified. - */ - public function identify() - { - $this->_setDefaults(); - - if (empty($this->_authenticateObjects)) { - $this->constructAuthenticate(); - } - foreach ($this->_authenticateObjects as $auth) { - $result = $auth->authenticate( - $this->getController()->getRequest(), - $this->getController()->getResponse() - ); - if (!empty($result)) { - $this->_authenticationProvider = $auth; - $event = $this->dispatchEvent('Auth.afterIdentify', [$result, $auth]); - if ($event->getResult() !== null) { - return $event->getResult(); - } - - return $result; - } - } - - return false; - } - - /** - * Loads the configured authentication objects. - * - * @return array|null The loaded authorization objects, or null on empty authenticate value. - * @throws \Cake\Core\Exception\CakeException - */ - public function constructAuthenticate(): ?array - { - if (empty($this->_config['authenticate'])) { - return null; - } - $this->_authenticateObjects = []; - $authenticate = Hash::normalize((array)$this->_config['authenticate']); - $global = []; - if (isset($authenticate[AuthComponent::ALL])) { - $global = $authenticate[AuthComponent::ALL]; - unset($authenticate[AuthComponent::ALL]); - } - foreach ($authenticate as $alias => $config) { - if (!empty($config['className'])) { - $class = $config['className']; - unset($config['className']); - } else { - $class = $alias; - } - $className = App::className($class, 'Auth', 'Authenticate'); - if ($className === null) { - throw new CakeException(sprintf('Authentication adapter "%s" was not found.', $class)); - } - if (!method_exists($className, 'authenticate')) { - throw new CakeException('Authentication objects must implement an authenticate() method.'); - } - $config = array_merge($global, (array)$config); - $this->_authenticateObjects[$alias] = new $className($this->_registry, $config); - $this->getEventManager()->on($this->_authenticateObjects[$alias]); - } - - return $this->_authenticateObjects; - } - - /** - * Get/set user record storage object. - * - * @param \Cake\Auth\Storage\StorageInterface|null $storage Sets provided - * object as storage or if null returns configured storage object. - * @return \Cake\Auth\Storage\StorageInterface|null - */ - public function storage(?StorageInterface $storage = null): ?StorageInterface - { - if ($storage !== null) { - $this->_storage = $storage; - - return null; - } - - if ($this->_storage) { - return $this->_storage; - } - - $config = $this->_config['storage']; - if (is_string($config)) { - $class = $config; - $config = []; - } else { - $class = $config['className']; - unset($config['className']); - } - $className = App::className($class, 'Auth/Storage', 'Storage'); - if ($className === null) { - throw new CakeException(sprintf('Auth storage adapter "%s" was not found.', $class)); - } - $request = $this->getController()->getRequest(); - $response = $this->getController()->getResponse(); - /** @var \Cake\Auth\Storage\StorageInterface $storage */ - $storage = new $className($request, $response, $config); - - return $this->_storage = $storage; - } - - /** - * Magic accessor for backward compatibility for property `$sessionKey`. - * - * @param string $name Property name - * @return mixed - */ - public function __get(string $name) - { - if ($name === 'sessionKey') { - return $this->storage()->getConfig('key'); - } - - return parent::__get($name); - } - - /** - * Magic setter for backward compatibility for property `$sessionKey`. - * - * @param string $name Property name. - * @param mixed $value Value to set. - * @return void - */ - public function __set(string $name, $value): void - { - if ($name === 'sessionKey') { - $this->_storage = null; - - if ($value === false) { - $this->setConfig('storage', 'Memory'); - - return; - } - - $this->setConfig('storage', 'Session'); - $this->storage()->setConfig('key', $value); - - return; - } - - $this->{$name} = $value; - } - - /** - * Getter for authenticate objects. Will return a particular authenticate object. - * - * @param string $alias Alias for the authenticate object - * @return \Cake\Auth\BaseAuthenticate|null - */ - public function getAuthenticate(string $alias): ?BaseAuthenticate - { - if (empty($this->_authenticateObjects)) { - $this->constructAuthenticate(); - } - - return $this->_authenticateObjects[$alias] ?? null; - } - - /** - * Set a flash message. Uses the Flash component with values from `flash` config. - * - * @param string|false $message The message to set. False to skip. - * @return void - */ - public function flash($message): void - { - if ($message === false) { - return; - } - - $this->Flash->set($message, $this->_config['flash']); - } - - /** - * If login was called during this request and the user was successfully - * authenticated, this function will return the instance of the authentication - * object that was used for logging the user in. - * - * @return \Cake\Auth\BaseAuthenticate|null - */ - public function authenticationProvider(): ?BaseAuthenticate - { - return $this->_authenticationProvider; - } - - /** - * If there was any authorization processing for the current request, this function - * will return the instance of the Authorization object that granted access to the - * user to the current address. - * - * @return \Cake\Auth\BaseAuthorize|null - */ - public function authorizationProvider(): ?BaseAuthorize - { - return $this->_authorizationProvider; - } - - /** - * Returns the URL to redirect back to or / if not possible. - * - * This method takes the referrer into account if the - * request is not of type GET. - * - * @return string - */ - protected function _getUrlToRedirectBackTo(): string - { - $urlToRedirectBackTo = $this->getController()->getRequest()->getRequestTarget(); - if (!$this->getController()->getRequest()->is('get')) { - $urlToRedirectBackTo = $this->getController()->referer(); - } - - return $urlToRedirectBackTo; - } -} diff --git a/app/vendor/cakephp/cakephp/src/Controller/Component/CheckHttpCacheComponent.php b/app/vendor/cakephp/cakephp/src/Controller/Component/CheckHttpCacheComponent.php index e546b6e7c..dc4f1edbf 100644 --- a/app/vendor/cakephp/cakephp/src/Controller/Component/CheckHttpCacheComponent.php +++ b/app/vendor/cakephp/cakephp/src/Controller/Component/CheckHttpCacheComponent.php @@ -36,7 +36,7 @@ class CheckHttpCacheComponent extends Component /** * Before Render hook * - * @param \Cake\Event\EventInterface $event The Controller.beforeRender event. + * @param \Cake\Event\EventInterface<\Cake\Controller\Controller> $event The Controller.beforeRender event. * @return void */ public function beforeRender(EventInterface $event): void diff --git a/app/vendor/cakephp/cakephp/src/Controller/Component/FlashComponent.php b/app/vendor/cakephp/cakephp/src/Controller/Component/FlashComponent.php index 73124a51a..28b142ede 100644 --- a/app/vendor/cakephp/cakephp/src/Controller/Component/FlashComponent.php +++ b/app/vendor/cakephp/cakephp/src/Controller/Component/FlashComponent.php @@ -39,7 +39,7 @@ class FlashComponent extends Component * * @var array */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'key' => 'flash', 'element' => 'default', 'params' => [], @@ -68,7 +68,7 @@ class FlashComponent extends Component * @param array $options An array of options * @return void */ - public function set($message, array $options = []): void + public function set(Throwable|string $message, array $options = []): void { if ($message instanceof Throwable) { $this->flash()->setExceptionMessage($message, $options); @@ -91,12 +91,12 @@ protected function flash(): FlashMessage * Proxy method to FlashMessage instance. * * @param array|string $key The key to set, or a complete array of configs. - * @param mixed|null $value The value to set. + * @param mixed $value The value to set. * @param bool $merge Whether to recursively merge or overwrite existing config, defaults to true. * @return $this * @throws \Cake\Core\Exception\CakeException When trying to set a key that is invalid. */ - public function setConfig($key, $value = null, $merge = true) + public function setConfig(array|string $key, mixed $value = null, bool $merge = true) { $this->flash()->setConfig($key, $value, $merge); @@ -110,7 +110,7 @@ public function setConfig($key, $value = null, $merge = true) * @param mixed $default The return value when the key does not exist. * @return mixed Configuration data at the named key or null if the key does not exist. */ - public function getConfig(?string $key = null, $default = null) + public function getConfig(?string $key = null, mixed $default = null): mixed { return $this->flash()->getConfig($key, $default); } @@ -122,7 +122,7 @@ public function getConfig(?string $key = null, $default = null) * @return mixed Configuration data at the named key * @throws \InvalidArgumentException */ - public function getConfigOrFail(string $key) + public function getConfigOrFail(string $key): mixed { return $this->flash()->getConfigOrFail($key); } @@ -131,10 +131,10 @@ public function getConfigOrFail(string $key) * Proxy method to FlashMessage instance. * * @param array|string $key The key to set, or a complete array of configs. - * @param mixed|null $value The value to set. + * @param mixed $value The value to set. * @return $this */ - public function configShallow($key, $value = null) + public function configShallow(array|string $key, mixed $value = null) { $this->flash()->configShallow($key, $value); @@ -163,7 +163,7 @@ public function configShallow($key, $value = null) * @return void * @throws \Cake\Http\Exception\InternalErrorException If missing the flash message. */ - public function __call(string $name, array $args) + public function __call(string $name, array $args): void { $element = Inflector::underscore($name); @@ -173,6 +173,10 @@ public function __call(string $name, array $args) $options = ['element' => $element]; + if (isset($args['options'])) { + $args[1] = $args['options']; + } + if (!empty($args[1])) { if (!empty($args[1]['plugin'])) { $options = ['element' => $args[1]['plugin'] . '.' . $element]; @@ -181,6 +185,6 @@ public function __call(string $name, array $args) $options += (array)$args[1]; } - $this->set($args[0], $options); + $this->set($args[0] ?? $args['message'], $options); } } diff --git a/app/vendor/cakephp/cakephp/src/Controller/Component/FormProtectionComponent.php b/app/vendor/cakephp/cakephp/src/Controller/Component/FormProtectionComponent.php index 54a2fd99a..924fbe87a 100644 --- a/app/vendor/cakephp/cakephp/src/Controller/Component/FormProtectionComponent.php +++ b/app/vendor/cakephp/cakephp/src/Controller/Component/FormProtectionComponent.php @@ -17,10 +17,10 @@ namespace Cake\Controller\Component; use Cake\Controller\Component; +use Cake\Controller\Exception\FormProtectionException; use Cake\Core\Configure; use Cake\Event\EventInterface; use Cake\Form\FormProtector; -use Cake\Http\Exception\BadRequestException; use Cake\Http\Response; use Cake\Routing\Router; use Closure; @@ -33,7 +33,7 @@ * - Existing fields have not been removed from the form. * - Values of hidden inputs have not been changed. * - * @psalm-property array{validate:bool, unlockedFields:array, unlockedActions:array, validationFailureCallback:?\Closure} $_config + * @phpstan-property array{validate:bool, unlockedFields:array, unlockedActions:array, validationFailureCallback:?\Closure} $_config */ class FormProtectionComponent extends Component { @@ -60,7 +60,7 @@ class FormProtectionComponent extends Component * * @var array */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'validate' => true, 'unlockedFields' => [], 'unlockedActions' => [], @@ -86,7 +86,7 @@ protected function _getSessionId(): string * * Token check happens here. * - * @param \Cake\Event\EventInterface $event An Event instance + * @param \Cake\Event\EventInterface<\Cake\Controller\Controller> $event An Event instance * @return \Cake\Http\Response|null */ public function startup(EventInterface $event): ?Response @@ -107,7 +107,9 @@ public function startup(EventInterface $event): ?Response $isValid = $formProtector->validate($data, $url, $sessionId); if (!$isValid) { - return $this->validationFailure($formProtector); + $event->setResult($this->validationFailure($formProtector)); + + return null; } } @@ -148,14 +150,14 @@ public function implementedEvents(): array * * @param \Cake\Form\FormProtector $formProtector Form Protector instance. * @return \Cake\Http\Response|null If specified, validationFailureCallback's response, or no return otherwise. - * @throws \Cake\Http\Exception\BadRequestException + * @throws \Cake\Controller\Exception\FormProtectionException */ protected function validationFailure(FormProtector $formProtector): ?Response { if (Configure::read('debug')) { - $exception = new BadRequestException($formProtector->getError()); + $exception = new FormProtectionException($formProtector->getError()); } else { - $exception = new BadRequestException(static::DEFAULT_EXCEPTION_MESSAGE); + $exception = new FormProtectionException(static::DEFAULT_EXCEPTION_MESSAGE); } if ($this->_config['validationFailureCallback']) { @@ -168,11 +170,11 @@ protected function validationFailure(FormProtector $formProtector): ?Response /** * Execute callback. * - * @param \Closure $callback A valid callable - * @param \Cake\Http\Exception\BadRequestException $exception Exception instance. + * @param \Closure $callback Callback + * @param \Cake\Controller\Exception\FormProtectionException $exception Exception instance. * @return \Cake\Http\Response|null */ - protected function executeCallback(Closure $callback, BadRequestException $exception): ?Response + protected function executeCallback(Closure $callback, FormProtectionException $exception): ?Response { return $callback($exception); } diff --git a/app/vendor/cakephp/cakephp/src/Controller/Component/PaginatorComponent.php b/app/vendor/cakephp/cakephp/src/Controller/Component/PaginatorComponent.php deleted file mode 100644 index 07dc6352e..000000000 --- a/app/vendor/cakephp/cakephp/src/Controller/Component/PaginatorComponent.php +++ /dev/null @@ -1,328 +0,0 @@ -_defaultConfig)) { - throw new UnexpectedValueException('Default configuration must be set using a custom Paginator class.'); - } - - if (isset($config['paginator'])) { - $config['className'] = $config['paginator']; - deprecationWarning( - '`paginator` option is deprecated,' - . ' use `className` instead a specify a paginator name/FQCN.' - ); - } - - if (isset($config['className'])) { - if (!$config['className'] instanceof NumericPaginator) { - throw new InvalidArgumentException('Paginator must be an instance of ' . NumericPaginator::class); - } - $this->_paginator = $config['className']; - unset($config['className']); - } else { - $this->_paginator = new NumericPaginator(); - } - - parent::__construct($registry, $config); - } - - /** - * Events supported by this component. - * - * @return array - */ - public function implementedEvents(): array - { - return []; - } - - /** - * Handles automatic pagination of model records. - * - * ### Configuring pagination - * - * When calling `paginate()` you can use the $settings parameter to pass in pagination settings. - * These settings are used to build the queries made and control other pagination settings. - * - * If your settings contain a key with the current table's alias. The data inside that key will be used. - * Otherwise, the top level configuration will be used. - * - * ``` - * $settings = [ - * 'limit' => 20, - * 'maxLimit' => 100 - * ]; - * $results = $paginator->paginate($table, $settings); - * ``` - * - * The above settings will be used to paginate any Table. You can configure Table specific settings by - * keying the settings with the Table alias. - * - * ``` - * $settings = [ - * 'Articles' => [ - * 'limit' => 20, - * 'maxLimit' => 100 - * ], - * 'Comments' => [ ... ] - * ]; - * $results = $paginator->paginate($table, $settings); - * ``` - * - * This would allow you to have different pagination settings for `Articles` and `Comments` tables. - * - * ### Controlling sort fields - * - * By default CakePHP will automatically allow sorting on any column on the table object being - * paginated. Often times you will want to allow sorting on either associated columns or calculated - * fields. In these cases you will need to define an allowed list of fields you wish to allow - * sorting on. You can define the allowed fields in the `$settings` parameter: - * - * ``` - * $settings = [ - * 'Articles' => [ - * 'finder' => 'custom', - * 'sortableFields' => ['title', 'author_id', 'comment_count'], - * ] - * ]; - * ``` - * - * Passing an empty array as allowed list disallows sorting altogether. - * - * ### Paginating with custom finders - * - * You can paginate with any find type defined on your table using the `finder` option. - * - * ``` - * $settings = [ - * 'Articles' => [ - * 'finder' => 'popular' - * ] - * ]; - * $results = $paginator->paginate($table, $settings); - * ``` - * - * Would paginate using the `find('popular')` method. - * - * You can also pass an already created instance of a query to this method: - * - * ``` - * $query = $this->Articles->find('popular')->matching('Tags', function ($q) { - * return $q->where(['name' => 'CakePHP']) - * }); - * $results = $paginator->paginate($query); - * ``` - * - * ### Scoping Request parameters - * - * By using request parameter scopes you can paginate multiple queries in the same controller action: - * - * ``` - * $articles = $paginator->paginate($articlesQuery, ['scope' => 'articles']); - * $tags = $paginator->paginate($tagsQuery, ['scope' => 'tags']); - * ``` - * - * Each of the above queries will use different query string parameter sets - * for pagination data. An example URL paginating both results would be: - * - * ``` - * /dashboard?articles[page]=1&tags[page]=2 - * ``` - * - * @param \Cake\Datasource\RepositoryInterface|\Cake\Datasource\QueryInterface $object Table or query to paginate. - * @param array $settings The settings/configuration used for pagination. - * @return \Cake\Datasource\ResultSetInterface Query results - * @throws \Cake\Http\Exception\NotFoundException - */ - public function paginate(object $object, array $settings = []): ResultSetInterface - { - $request = $this->_registry->getController()->getRequest(); - - try { - $results = $this->_paginator->paginate( - $object, - $request->getQueryParams(), - $settings - ); - - $this->_setPagingParams(); - } catch (PageOutOfBoundsException $e) { - $this->_setPagingParams(); - - throw new NotFoundException(null, null, $e); - } - - return $results; - } - - /** - * Merges the various options that Pagination uses. - * Pulls settings together from the following places: - * - * - General pagination settings - * - Model specific settings. - * - Request parameters - * - * The result of this method is the aggregate of all the option sets combined together. You can change - * config value `allowedParameters` to modify which options/values can be set using request parameters. - * - * @param string $alias Model alias being paginated, if the general settings has a key with this value - * that key's settings will be used for pagination instead of the general ones. - * @param array $settings The settings to merge with the request data. - * @return array Array of merged options. - */ - public function mergeOptions(string $alias, array $settings): array - { - $request = $this->_registry->getController()->getRequest(); - - return $this->_paginator->mergeOptions( - $request->getQueryParams(), - $this->_paginator->getDefaults($alias, $settings) - ); - } - - /** - * Set paginator instance. - * - * @param \Cake\Datasource\Paging\NumericPaginator $paginator Paginator instance. - * @return $this - */ - public function setPaginator(NumericPaginator $paginator) - { - $this->_paginator = $paginator; - - return $this; - } - - /** - * Get paginator instance. - * - * @return \Cake\Datasource\Paging\NumericPaginator - */ - public function getPaginator(): NumericPaginator - { - return $this->_paginator; - } - - /** - * Set paging params to request instance. - * - * @return void - */ - protected function _setPagingParams(): void - { - $controller = $this->getController(); - $request = $controller->getRequest(); - $paging = $this->_paginator->getPagingParams() + (array)$request->getAttribute('paging', []); - - $controller->setRequest($request->withAttribute('paging', $paging)); - } - - /** - * Proxy setting config options to Paginator. - * - * @param array|string $key The key to set, or a complete array of configs. - * @param mixed|null $value The value to set. - * @param bool $merge Whether to recursively merge or overwrite existing config, defaults to true. - * @return $this - */ - public function setConfig($key, $value = null, $merge = true) - { - $this->_paginator->setConfig($key, $value, $merge); - - return $this; - } - - /** - * Proxy getting config options to Paginator. - * - * @param string|null $key The key to get or null for the whole config. - * @param mixed $default The return value when the key does not exist. - * @return mixed Config value being read. - */ - public function getConfig(?string $key = null, $default = null) - { - return $this->_paginator->getConfig($key, $default); - } - - /** - * Proxy setting config options to Paginator. - * - * @param array|string $key The key to set, or a complete array of configs. - * @param mixed|null $value The value to set. - * @return $this - */ - public function configShallow($key, $value = null) - { - $this->_paginator->configShallow($key, null); - - return $this; - } - - /** - * Proxy method calls to Paginator. - * - * @param string $method Method name. - * @param array $args Method arguments. - * @return mixed - */ - public function __call(string $method, array $args) - { - return $this->_paginator->{$method}(...$args); - } -} diff --git a/app/vendor/cakephp/cakephp/src/Controller/Component/RequestHandlerComponent.php b/app/vendor/cakephp/cakephp/src/Controller/Component/RequestHandlerComponent.php deleted file mode 100644 index 84c5a26ab..000000000 --- a/app/vendor/cakephp/cakephp/src/Controller/Component/RequestHandlerComponent.php +++ /dev/null @@ -1,524 +0,0 @@ - - */ - protected $_defaultConfig = [ - 'checkHttpCache' => true, - 'viewClassMap' => [], - ]; - - /** - * Constructor. Parses the accepted content types accepted by the client using HTTP_ACCEPT - * - * @param \Cake\Controller\ComponentRegistry $registry ComponentRegistry object. - * @param array $config Array of config. - */ - public function __construct(ComponentRegistry $registry, array $config = []) - { - $config += [ - 'viewClassMap' => [ - 'json' => 'Json', - 'xml' => 'Xml', - 'ajax' => 'Ajax', - ], - ]; - parent::__construct($registry, $config); - } - - /** - * Events supported by this component. - * - * @return array - */ - public function implementedEvents(): array - { - return [ - 'Controller.startup' => 'startup', - 'Controller.beforeRender' => 'beforeRender', - ]; - } - - /** - * Set the extension based on the `Accept` header or URL extension. - * - * Compares the accepted types and configured extensions. - * If there is one common type, that is assigned as the ext/content type for the response. - * The type with the highest weight will be set. If the highest weight has more - * than one type matching the extensions, the order in which extensions are specified - * determines which type will be set. - * - * If html is one of the preferred types, no content type will be set, this - * is to avoid issues with browsers that prefer HTML and several other content types. - * - * @param \Cake\Http\ServerRequest $request The request instance. - * @param \Cake\Http\Response $response The response instance. - * @return void - */ - protected function _setExtension(ServerRequest $request, Response $response): void - { - $content = new ContentTypeNegotiation(); - $accept = $content->parseAccept($request); - - if (empty($accept) || current($accept)[0] === 'text/html') { - return; - } - - /** @var array $accepts */ - $accepts = $response->mapType($accept); - $preferredTypes = current($accepts); - if (array_intersect($preferredTypes, ['html', 'xhtml'])) { - return; - } - - $extensions = array_unique( - array_merge(Router::extensions(), array_keys($this->getConfig('viewClassMap'))) - ); - foreach ($accepts as $types) { - $ext = array_intersect($extensions, $types); - if ($ext) { - $this->ext = current($ext); - break; - } - } - } - - /** - * The startup method of the RequestHandler enables several automatic behaviors - * related to the detection of certain properties of the HTTP request, including: - * - * If the XML data is POSTed, the data is parsed into an XML object, which is assigned - * to the $data property of the controller, which can then be saved to a model object. - * - * @param \Cake\Event\EventInterface $event The startup event that was fired. - * @return void - */ - public function startup(EventInterface $event): void - { - $controller = $this->getController(); - $request = $controller->getRequest(); - $response = $controller->getResponse(); - - $this->ext = $request->getParam('_ext'); - if (!$this->ext || in_array($this->ext, ['html', 'htm'], true)) { - $this->_setExtension($request, $response); - } - - $isAjax = $request->is('ajax'); - $controller->setRequest($request->withAttribute('isAjax', $isAjax)); - - if (!$this->ext && $isAjax) { - $this->ext = 'ajax'; - } - } - - /** - * Checks if the response can be considered different according to the request - * headers, and the caching response headers. If it was not modified, then the - * render process is skipped. And the client will get a blank response with a - * "304 Not Modified" header. - * - * - If Router::extensions() is enabled, the layout and template type are - * switched based on the parsed extension or `Accept` header. For example, - * if `controller/action.xml` is requested, the view path becomes - * `templates/Controller/xml/action.php`. Also, if `controller/action` is - * requested with `Accept: application/xml` in the headers the view - * path will become `templates/Controller/xml/action.php`. Layout and template - * types will only switch to mime-types recognized by \Cake\Http\Response. - * If you need to declare additional mime-types, you can do so using - * {@link \Cake\Http\Response::setTypeMap()} in your controller's beforeFilter() method. - * - If a helper with the same name as the extension exists, it is added to - * the controller. - * - If the extension is of a type that RequestHandler understands, it will - * set that Content-type in the response header. - * - * @param \Cake\Event\EventInterface $event The Controller.beforeRender event. - * @return void - * @throws \Cake\Http\Exception\NotFoundException If invoked extension is not configured. - */ - public function beforeRender(EventInterface $event): void - { - $controller = $this->getController(); - $response = $controller->getResponse(); - - if ($this->ext && !in_array($this->ext, ['html', 'htm'], true)) { - if (!$response->getMimeType($this->ext)) { - throw new NotFoundException('Invoked extension not recognized/configured: ' . $this->ext); - } - - $this->renderAs($controller, $this->ext); - $response = $controller->getResponse(); - } else { - $response = $response->withCharset(Configure::read('App.encoding')); - } - - $request = $controller->getRequest(); - if ($this->_config['checkHttpCache'] && $response->isNotModified($request)) { - $response = $response->withNotModified(); - $event->stopPropagation(); - } - - $controller->setResponse($response); - } - - /** - * Determines which content types the client accepts. Acceptance is based on - * the file extension parsed by the Router (if present), and by the HTTP_ACCEPT - * header. Unlike {@link \Cake\Http\ServerRequest::accepts()} this method deals entirely with mapped content types. - * - * Usage: - * - * ``` - * $this->RequestHandler->accepts(['xml', 'html', 'json']); - * ``` - * - * Returns true if the client accepts any of the supplied types. - * - * ``` - * $this->RequestHandler->accepts('xml'); - * ``` - * - * Returns true if the client accepts XML. - * - * @param array|string|null $type Can be null (or no parameter), a string type name, or an - * array of types - * @return array|bool|string|null If null or no parameter is passed, returns an array of content - * types the client accepts. If a string is passed, returns true - * if the client accepts it. If an array is passed, returns true - * if the client accepts one or more elements in the array. - * @deprecated 4.4.0 Use ContentTypeNegotiation::prefersChoice() or Controller::getViewClasses() instead. - */ - public function accepts($type = null) - { - $controller = $this->getController(); - /** @var array $accepted */ - $accepted = $controller->getRequest()->accepts(); - - if (!$type) { - return $controller->getResponse()->mapType($accepted); - } - - if (is_array($type)) { - foreach ($type as $t) { - $t = $this->mapAlias($t); - if (in_array($t, $accepted, true)) { - return true; - } - } - - return false; - } - - if (is_string($type)) { - return in_array($this->mapAlias($type), $accepted, true); - } - - return false; - } - - /** - * Determines the content type of the data the client has sent (i.e. in a POST request) - * - * @param array|string|null $type Can be null (or no parameter), a string type name, or an array of types - * @return mixed If a single type is supplied a boolean will be returned. If no type is provided - * The mapped value of CONTENT_TYPE will be returned. If an array is supplied the first type - * in the request content type will be returned. - */ - public function requestedWith($type = null) - { - $controller = $this->getController(); - $request = $controller->getRequest(); - - if ( - !$request->is('post') && - !$request->is('put') && - !$request->is('patch') && - !$request->is('delete') - ) { - return null; - } - if (is_array($type)) { - foreach ($type as $t) { - if ($this->requestedWith($t)) { - return $t; - } - } - - return false; - } - - [$contentType] = explode(';', $request->contentType() ?? ''); - if ($type === null) { - return $controller->getResponse()->mapType($contentType); - } - - if (!is_string($type)) { - return null; - } - - return $type === $controller->getResponse()->mapType($contentType); - } - - /** - * Determines which content-types the client prefers. If no parameters are given, - * the single content-type that the client most likely prefers is returned. If $type is - * an array, the first item in the array that the client accepts is returned. - * Preference is determined primarily by the file extension parsed by the Router - * if provided, and secondarily by the list of content-types provided in - * HTTP_ACCEPT. - * - * @param array|string|null $type An optional array of 'friendly' content-type names, i.e. - * 'html', 'xml', 'js', etc. - * @return string|bool|null If $type is null or not provided, the first content-type in the - * list, based on preference, is returned. If a single type is provided - * a boolean will be returned if that type is preferred. - * If an array of types are provided then the first preferred type is returned. - * If no type is provided the first preferred type is returned. - * @deprecated 4.4.0 Use Controller::getViewClasses() instead. - */ - public function prefers($type = null) - { - $controller = $this->getController(); - $request = $controller->getRequest(); - $content = new ContentTypeNegotiation(); - - $acceptRaw = $content->parseAccept($request); - if (empty($acceptRaw)) { - return $type ? $type === $this->ext : $this->ext; - } - - /** @var array $accepts */ - $accepts = $controller->getResponse()->mapType(array_shift($acceptRaw)); - if (!$type) { - if (empty($this->ext) && !empty($accepts)) { - return $accepts[0]; - } - - return $this->ext; - } - - $types = (array)$type; - if (count($types) === 1) { - if ($this->ext) { - return in_array($this->ext, $types, true); - } - - return in_array($types[0], $accepts, true); - } - - $intersect = array_values(array_intersect($accepts, $types)); - if (!$intersect) { - return false; - } - - return $intersect[0]; - } - - /** - * Sets either the view class if one exists or the layout and template path of the view. - * The names of these are derived from the $type input parameter. - * - * ### Usage: - * - * Render the response as an 'ajax' response. - * - * ``` - * $this->RequestHandler->renderAs($this, 'ajax'); - * ``` - * - * Render the response as an XML file and force the result as a file download. - * - * ``` - * $this->RequestHandler->renderAs($this, 'xml', ['attachment' => 'myfile.xml']; - * ``` - * - * @param \Cake\Controller\Controller $controller A reference to a controller object - * @param string $type Type of response to send (e.g: 'ajax') - * @param array $options Array of options to use - * @return void - * @see \Cake\Controller\Component\RequestHandlerComponent::respondAs() - */ - public function renderAs(Controller $controller, string $type, array $options = []): void - { - $defaults = ['charset' => 'UTF-8']; - $viewClassMap = $this->getConfig('viewClassMap'); - - if (Configure::read('App.encoding') !== null) { - $defaults['charset'] = Configure::read('App.encoding'); - } - $options += $defaults; - - $builder = $controller->viewBuilder(); - if (array_key_exists($type, $viewClassMap)) { - $view = $viewClassMap[$type]; - } else { - $view = Inflector::classify($type); - } - - $viewClass = null; - if ($builder->getClassName() === null) { - $viewClass = App::className($view, 'View', 'View'); - } - - if ($viewClass) { - $builder->setClassName($viewClass); - } else { - if (!$this->_renderType) { - $builder->setTemplatePath((string)$builder->getTemplatePath() . DIRECTORY_SEPARATOR . $type); - } else { - $builder->setTemplatePath(preg_replace( - "/([\/\\\\]{$this->_renderType})$/", - DIRECTORY_SEPARATOR . $type, - (string)$builder->getTemplatePath() - )); - } - - $this->_renderType = $type; - $builder->setLayoutPath($type); - } - - if ($controller->getResponse()->getMimeType($type)) { - $this->respondAs($type, $options); - } - } - - /** - * Sets the response header based on type map index name. This wraps several methods - * available on {@link \Cake\Http\Response}. It also allows you to use Content-Type aliases. - * - * @param string $type Friendly type name, i.e. 'html' or 'xml', or a full content-type, - * like 'application/x-shockwave'. - * @param array $options If $type is a friendly type name that is associated with - * more than one type of content, $index is used to select which content-type to use. - * @return bool Returns false if the friendly type name given in $type does - * not exist in the type map, or if the Content-type header has - * already been set by this method. - */ - public function respondAs($type, array $options = []): bool - { - $defaults = ['index' => null, 'charset' => null, 'attachment' => false]; - $options += $defaults; - - $cType = $type; - $controller = $this->getController(); - $response = $controller->getResponse(); - - if (strpos($type, '/') === false) { - $cType = $response->getMimeType($type); - } - if (is_array($cType)) { - $cType = $cType[$options['index']] ?? $cType; - $cType = $this->prefers($cType) ?: $cType[0]; - } - - if (!$cType) { - return false; - } - - /** @psalm-suppress PossiblyInvalidArgument */ - $response = $response->withType($cType); - - if (!empty($options['charset'])) { - $response = $response->withCharset($options['charset']); - } - if (!empty($options['attachment'])) { - $response = $response->withDownload($options['attachment']); - } - $controller->setResponse($response); - - return true; - } - - /** - * Maps a content type alias back to its mime-type(s) - * - * @param array|string $alias String alias to convert back into a content type. Or an array of aliases to map. - * @return array|string|null Null on an undefined alias. String value of the mapped alias type. If an - * alias maps to more than one content type, the first one will be returned. If an array is provided - * for $alias, an array of mapped types will be returned. - */ - public function mapAlias($alias) - { - if (is_array($alias)) { - return array_map([$this, 'mapAlias'], $alias); - } - - $type = $this->getController()->getResponse()->getMimeType($alias); - if ($type) { - if (is_array($type)) { - return $type[0]; - } - - return $type; - } - - return null; - } -} diff --git a/app/vendor/cakephp/cakephp/src/Controller/Component/SecurityComponent.php b/app/vendor/cakephp/cakephp/src/Controller/Component/SecurityComponent.php deleted file mode 100644 index 6938d0cb4..000000000 --- a/app/vendor/cakephp/cakephp/src/Controller/Component/SecurityComponent.php +++ /dev/null @@ -1,583 +0,0 @@ - - */ - protected $_defaultConfig = [ - 'blackHoleCallback' => null, - 'requireSecure' => [], - 'unlockedFields' => [], - 'unlockedActions' => [], - 'validatePost' => true, - ]; - - /** - * Holds the current action of the controller - * - * @var string - */ - protected $_action; - - /** - * Component startup. All security checking happens here. - * - * @param \Cake\Event\EventInterface $event An Event instance - * @return \Cake\Http\Response|null - */ - public function startup(EventInterface $event): ?Response - { - /** @var \Cake\Controller\Controller $controller */ - $controller = $event->getSubject(); - $request = $controller->getRequest(); - $this->_action = $request->getParam('action'); - $hasData = ($request->getData() || $request->is(['put', 'post', 'delete', 'patch'])); - try { - $this->_secureRequired($controller); - - if ($this->_action === $this->_config['blackHoleCallback']) { - throw new AuthSecurityException(sprintf( - 'Action %s is defined as the blackhole callback.', - $this->_action - )); - } - - if ( - !in_array($this->_action, (array)$this->_config['unlockedActions'], true) && - $hasData && - $this->_config['validatePost'] - ) { - $this->_validatePost($controller); - } - } catch (SecurityException $se) { - return $this->blackHole($controller, $se->getType(), $se); - } - - $request = $this->generateToken($request); - if ($hasData && is_array($controller->getRequest()->getData())) { - $request = $request->withoutData('_Token'); - } - $controller->setRequest($request); - - return null; - } - - /** - * Events supported by this component. - * - * @return array - */ - public function implementedEvents(): array - { - return [ - 'Controller.startup' => 'startup', - ]; - } - - /** - * Sets the actions that require a request that is SSL-secured, or empty for all actions - * - * @param array|string|null $actions Actions list - * @return void - */ - public function requireSecure($actions = null): void - { - $actions = (array)$actions; - $this->setConfig('requireSecure', empty($actions) ? ['*'] : $actions); - } - - /** - * Black-hole an invalid request with a 400 error or custom callback. If SecurityComponent::$blackHoleCallback - * is specified, it will use this callback by executing the method indicated in $error - * - * @param \Cake\Controller\Controller $controller Instantiating controller - * @param string $error Error method - * @param \Cake\Controller\Exception\SecurityException|null $exception Additional debug info describing the cause - * @return mixed If specified, controller blackHoleCallback's response, or no return otherwise - * @see \Cake\Controller\Component\SecurityComponent::$blackHoleCallback - * @link https://book.cakephp.org/4/en/controllers/components/security.html#handling-blackhole-callbacks - * @throws \Cake\Http\Exception\BadRequestException - */ - public function blackHole(Controller $controller, string $error = '', ?SecurityException $exception = null) - { - if (!$this->_config['blackHoleCallback']) { - $this->_throwException($exception); - } - - return $this->_callback($controller, $this->_config['blackHoleCallback'], [$error, $exception]); - } - - /** - * Check debug status and throw an Exception based on the existing one - * - * @param \Cake\Controller\Exception\SecurityException|null $exception Additional debug info describing the cause - * @throws \Cake\Http\Exception\BadRequestException - * @return void - */ - protected function _throwException(?SecurityException $exception = null): void - { - if ($exception !== null) { - if (!Configure::read('debug')) { - $exception->setReason($exception->getMessage()); - $exception->setMessage(static::DEFAULT_EXCEPTION_MESSAGE); - } - throw $exception; - } - throw new BadRequestException(static::DEFAULT_EXCEPTION_MESSAGE); - } - - /** - * Check if access requires secure connection - * - * @param \Cake\Controller\Controller $controller Instantiating controller - * @return void - * @throws \Cake\Controller\Exception\SecurityException - */ - protected function _secureRequired(Controller $controller): void - { - if ( - empty($this->_config['requireSecure']) || - !is_array($this->_config['requireSecure']) - ) { - return; - } - - $requireSecure = $this->_config['requireSecure']; - if ( - ($requireSecure[0] === '*' || - in_array($this->_action, $requireSecure, true) - ) && - !$controller->getRequest()->is('https') - ) { - throw new SecurityException( - 'Request is not SSL and the action is required to be secure' - ); - } - } - - /** - * Validate submitted form - * - * @param \Cake\Controller\Controller $controller Instantiating controller - * @return void - * @throws \Cake\Controller\Exception\AuthSecurityException - */ - protected function _validatePost(Controller $controller): void - { - $token = $this->_validToken($controller); - $hashParts = $this->_hashParts($controller); - $check = hash_hmac('sha1', implode('', $hashParts), Security::getSalt()); - - if (hash_equals($check, $token)) { - return; - } - - $msg = static::DEFAULT_EXCEPTION_MESSAGE; - if (Configure::read('debug')) { - $msg = $this->_debugPostTokenNotMatching($controller, $hashParts); - } - - throw new AuthSecurityException($msg); - } - - /** - * Check if token is valid - * - * @param \Cake\Controller\Controller $controller Instantiating controller - * @throws \Cake\Controller\Exception\SecurityException - * @return string fields token - */ - protected function _validToken(Controller $controller): string - { - $check = $controller->getRequest()->getData(); - - $message = '\'%s\' was not found in request data.'; - if (!isset($check['_Token'])) { - throw new AuthSecurityException(sprintf($message, '_Token')); - } - if (!isset($check['_Token']['fields'])) { - throw new AuthSecurityException(sprintf($message, '_Token.fields')); - } - if (!is_string($check['_Token']['fields'])) { - throw new AuthSecurityException("'_Token.fields' is invalid."); - } - if (!isset($check['_Token']['unlocked'])) { - throw new AuthSecurityException(sprintf($message, '_Token.unlocked')); - } - if (Configure::read('debug') && !isset($check['_Token']['debug'])) { - throw new SecurityException(sprintf($message, '_Token.debug')); - } - if (!Configure::read('debug') && isset($check['_Token']['debug'])) { - throw new SecurityException('Unexpected \'_Token.debug\' found in request data'); - } - - $token = urldecode($check['_Token']['fields']); - if (strpos($token, ':')) { - [$token, ] = explode(':', $token, 2); - } - - return $token; - } - - /** - * Return hash parts for the Token generation - * - * @param \Cake\Controller\Controller $controller Instantiating controller - * @return array - */ - protected function _hashParts(Controller $controller): array - { - $request = $controller->getRequest(); - - // Start the session to ensure we get the correct session id. - $session = $request->getSession(); - $session->start(); - - $data = (array)$request->getData(); - $fieldList = $this->_fieldsList($data); - $unlocked = $this->_sortedUnlocked($data); - - return [ - Router::url($request->getRequestTarget()), - serialize($fieldList), - $unlocked, - $session->id(), - ]; - } - - /** - * Return the fields list for the hash calculation - * - * @param array $check Data array - * @return array - */ - protected function _fieldsList(array $check): array - { - $locked = ''; - $token = urldecode($check['_Token']['fields']); - $unlocked = $this->_unlocked($check); - - if (strpos($token, ':')) { - [, $locked] = explode(':', $token, 2); - } - unset($check['_Token']); - - $locked = $locked ? explode('|', $locked) : []; - $unlocked = $unlocked ? explode('|', $unlocked) : []; - - $fields = Hash::flatten($check); - $fieldList = array_keys($fields); - $multi = $lockedFields = []; - $isUnlocked = false; - - foreach ($fieldList as $i => $key) { - if (is_string($key) && preg_match('/(\.\d+){1,10}$/', $key)) { - $multi[$i] = preg_replace('/(\.\d+){1,10}$/', '', $key); - unset($fieldList[$i]); - } else { - $fieldList[$i] = (string)$key; - } - } - if (!empty($multi)) { - $fieldList += array_unique($multi); - } - - $unlockedFields = array_unique( - array_merge( - (array)$this->_config['unlockedFields'], - $unlocked - ) - ); - - foreach ($fieldList as $i => $key) { - $isLocked = in_array($key, $locked, true); - - if (!empty($unlockedFields)) { - foreach ($unlockedFields as $off) { - $off = explode('.', $off); - $field = array_values(array_intersect(explode('.', $key), $off)); - $isUnlocked = ($field === $off); - if ($isUnlocked) { - break; - } - } - } - - if ($isUnlocked || $isLocked) { - unset($fieldList[$i]); - if ($isLocked) { - $lockedFields[$key] = $fields[$key]; - } - } - } - sort($fieldList, SORT_STRING); - ksort($lockedFields, SORT_STRING); - $fieldList += $lockedFields; - - return $fieldList; - } - - /** - * Get the unlocked string - * - * @param array $data Data array - * @return string - */ - protected function _unlocked(array $data): string - { - return urldecode($data['_Token']['unlocked']); - } - - /** - * Get the sorted unlocked string - * - * @param array $data Data array - * @return string - */ - protected function _sortedUnlocked(array $data): string - { - $unlocked = $this->_unlocked($data); - $unlocked = explode('|', $unlocked); - sort($unlocked, SORT_STRING); - - return implode('|', $unlocked); - } - - /** - * Create a message for humans to understand why Security token is not matching - * - * @param \Cake\Controller\Controller $controller Instantiating controller - * @param array $hashParts Elements used to generate the Token hash - * @return string Message explaining why the tokens are not matching - */ - protected function _debugPostTokenNotMatching(Controller $controller, array $hashParts): string - { - $messages = []; - $expectedParts = json_decode(urldecode($controller->getRequest()->getData('_Token.debug')), true); - if (!is_array($expectedParts) || count($expectedParts) !== 3) { - return 'Invalid security debug token.'; - } - $expectedUrl = Hash::get($expectedParts, 0); - $url = Hash::get($hashParts, 0); - if ($expectedUrl !== $url) { - $messages[] = sprintf('URL mismatch in POST data (expected \'%s\' but found \'%s\')', $expectedUrl, $url); - } - $expectedFields = Hash::get($expectedParts, 1); - $dataFields = Hash::get($hashParts, 1); - if ($dataFields) { - $dataFields = unserialize($dataFields, ['allowed_classes' => false]); - } - $fieldsMessages = $this->_debugCheckFields( - $dataFields, - $expectedFields, - 'Unexpected field \'%s\' in POST data', - 'Tampered field \'%s\' in POST data (expected value \'%s\' but found \'%s\')', - 'Missing field \'%s\' in POST data' - ); - $expectedUnlockedFields = Hash::get($expectedParts, 2); - $dataUnlockedFields = Hash::get($hashParts, 2) ?: null; - if ($dataUnlockedFields) { - $dataUnlockedFields = explode('|', $dataUnlockedFields); - } - $unlockFieldsMessages = $this->_debugCheckFields( - (array)$dataUnlockedFields, - $expectedUnlockedFields, - 'Unexpected unlocked field \'%s\' in POST data', - '', - 'Missing unlocked field: \'%s\'' - ); - - $messages = array_merge($messages, $fieldsMessages, $unlockFieldsMessages); - - return implode(', ', $messages); - } - - /** - * Iterates data array to check against expected - * - * @param array $dataFields Fields array, containing the POST data fields - * @param array $expectedFields Fields array, containing the expected fields we should have in POST - * @param string $intKeyMessage Message string if unexpected found in data fields indexed by int (not protected) - * @param string $stringKeyMessage Message string if tampered found in - * data fields indexed by string (protected). - * @param string $missingMessage Message string if missing field - * @return array Messages - */ - protected function _debugCheckFields( - array $dataFields, - array $expectedFields = [], - string $intKeyMessage = '', - string $stringKeyMessage = '', - string $missingMessage = '' - ): array { - $messages = $this->_matchExistingFields($dataFields, $expectedFields, $intKeyMessage, $stringKeyMessage); - $expectedFieldsMessage = $this->_debugExpectedFields($expectedFields, $missingMessage); - if ($expectedFieldsMessage !== null) { - $messages[] = $expectedFieldsMessage; - } - - return $messages; - } - - /** - * Manually add form tampering prevention token information into the provided - * request object. - * - * @param \Cake\Http\ServerRequest $request The request object to add into. - * @return \Cake\Http\ServerRequest The modified request. - */ - public function generateToken(ServerRequest $request): ServerRequest - { - $token = [ - 'unlockedFields' => $this->_config['unlockedFields'], - ]; - - return $request->withAttribute('formTokenData', [ - 'unlockedFields' => $token['unlockedFields'], - ]); - } - - /** - * Calls a controller callback method - * - * @param \Cake\Controller\Controller $controller Instantiating controller - * @param string $method Method to execute - * @param array $params Parameters to send to method - * @return mixed Controller callback method's response - * @throws \Cake\Http\Exception\BadRequestException When a the blackholeCallback is not callable. - */ - protected function _callback(Controller $controller, string $method, array $params = []) - { - $callable = [$controller, $method]; - - if (!is_callable($callable)) { - throw new BadRequestException('The request has been black-holed'); - } - - return $callable(...$params); - } - - /** - * Generate array of messages for the existing fields in POST data, matching dataFields in $expectedFields - * will be unset - * - * @param array $dataFields Fields array, containing the POST data fields - * @param array $expectedFields Fields array, containing the expected fields we should have in POST - * @param string $intKeyMessage Message string if unexpected found in data fields indexed by int (not protected) - * @param string $stringKeyMessage Message string if tampered found in - * data fields indexed by string (protected) - * @return array Error messages - */ - protected function _matchExistingFields( - array $dataFields, - array &$expectedFields, - string $intKeyMessage, - string $stringKeyMessage - ): array { - $messages = []; - foreach ($dataFields as $key => $value) { - if (is_int($key)) { - $foundKey = array_search($value, $expectedFields, true); - if ($foundKey === false) { - $messages[] = sprintf($intKeyMessage, $value); - } else { - unset($expectedFields[$foundKey]); - } - } else { - if (isset($expectedFields[$key]) && $value !== $expectedFields[$key]) { - $messages[] = sprintf($stringKeyMessage, $key, $expectedFields[$key], $value); - } - unset($expectedFields[$key]); - } - } - - return $messages; - } - - /** - * Generate debug message for the expected fields - * - * @param array $expectedFields Expected fields - * @param string $missingMessage Message template - * @return string|null Error message about expected fields - */ - protected function _debugExpectedFields(array $expectedFields = [], string $missingMessage = ''): ?string - { - if (count($expectedFields) === 0) { - return null; - } - - $expectedFieldNames = []; - foreach ($expectedFields as $key => $expectedField) { - if (is_int($key)) { - $expectedFieldNames[] = $expectedField; - } else { - $expectedFieldNames[] = $key; - } - } - - return sprintf($missingMessage, implode(', ', $expectedFieldNames)); - } -} diff --git a/app/vendor/cakephp/cakephp/src/Controller/ComponentRegistry.php b/app/vendor/cakephp/cakephp/src/Controller/ComponentRegistry.php index 288230519..e6e4ac909 100644 --- a/app/vendor/cakephp/cakephp/src/Controller/ComponentRegistry.php +++ b/app/vendor/cakephp/cakephp/src/Controller/ComponentRegistry.php @@ -18,53 +18,58 @@ use Cake\Controller\Exception\MissingComponentException; use Cake\Core\App; +use Cake\Core\ContainerInterface; use Cake\Core\Exception\CakeException; use Cake\Core\ObjectRegistry; use Cake\Event\EventDispatcherInterface; use Cake\Event\EventDispatcherTrait; +use League\Container\Argument\ArgumentResolverTrait; +use League\Container\Exception\NotFoundException; +use ReflectionClass; +use RuntimeException; /** * ComponentRegistry is a registry for loaded components * * Handles loading, constructing and binding events for component class objects. * + * @template TSubject of \Cake\Controller\Controller * @extends \Cake\Core\ObjectRegistry<\Cake\Controller\Component> + * @implements \Cake\Event\EventDispatcherInterface */ class ComponentRegistry extends ObjectRegistry implements EventDispatcherInterface { + /** + * @use \Cake\Event\EventDispatcherTrait + */ use EventDispatcherTrait; + use ArgumentResolverTrait; + /** - * The controller that this collection was initialized with. + * The controller that this collection is associated with. * * @var \Cake\Controller\Controller|null */ - protected $_Controller; + protected ?Controller $_Controller = null; /** - * Constructor. - * - * @param \Cake\Controller\Controller|null $controller Controller instance. + * @var \Cake\Core\ContainerInterface|null */ - public function __construct(?Controller $controller = null) - { - if ($controller) { - $this->setController($controller); - } - } + protected ?ContainerInterface $container = null; /** - * Get the controller associated with the collection. + * Constructor. * - * @return \Cake\Controller\Controller Controller instance or null if not set. + * @param \Cake\Controller\Controller|null $controller Controller instance. + * @param \Cake\Core\ContainerInterface|null $container Container instance. */ - public function getController(): Controller + public function __construct(?Controller $controller = null, ?ContainerInterface $container = null) { - if ($this->_Controller === null) { - throw new CakeException('Controller not set for ComponentRegistry'); + if ($controller !== null) { + $this->setController($controller); } - - return $this->_Controller; + $this->container = $container; } /** @@ -81,17 +86,31 @@ public function setController(Controller $controller) return $this; } + /** + * Get the controller associated with the collection. + * + * @return \Cake\Controller\Controller Controller instance. + */ + public function getController(): Controller + { + if ($this->_Controller === null) { + throw new RuntimeException('Controller must be set first.'); + } + + return $this->_Controller; + } + /** * Resolve a component classname. * * Part of the template method for {@link \Cake\Core\ObjectRegistry::load()}. * * @param string $class Partial classname to resolve. - * @return string|null Either the correct class name or null. - * @psalm-return class-string|null + * @return class-string<\Cake\Controller\Component>|null Either the correct class name or null. */ protected function _resolveClassName(string $class): ?string { + /** @var class-string<\Cake\Controller\Component>|null */ return App::className($class, 'Controller/Component', 'Component'); } @@ -120,22 +139,55 @@ protected function _throwMissingClassError(string $class, ?string $plugin): void * Part of the template method for {@link \Cake\Core\ObjectRegistry::load()} * Enabled components will be registered with the event manager. * - * @param string $class The classname to create. + * @param \Cake\Controller\Component|class-string<\Cake\Controller\Component> $class The classname to create. * @param string $alias The alias of the component. * @param array $config An array of config to use for the component. * @return \Cake\Controller\Component The constructed component class. - * @psalm-suppress MoreSpecificImplementedParamType - * @psalm-param class-string $class */ - protected function _create($class, string $alias, array $config): Component + protected function _create(object|string $class, string $alias, array $config): Component { - /** @var \Cake\Controller\Component $instance */ - $instance = new $class($this, $config); - $enable = $config['enabled'] ?? true; - if ($enable) { + if (is_object($class)) { + return $class; + } + if ($this->container?->has($class)) { + $constructor = (new ReflectionClass($class))->getConstructor(); + + if ($constructor !== null) { + $args = $this->reflectArguments($constructor, ['config' => $config]); + + try { + $this->container->extend($class) + ->addArguments($args); + } catch (NotFoundException) { + $this->container->add($class) + ->addArguments($args); + } + } + + /** @var \Cake\Controller\Component $instance */ + $instance = $this->container->get($class); + } else { + $instance = new $class($this, $config); + } + + if ($config['enabled'] ?? true) { $this->getEventManager()->on($instance); } return $instance; } + + /** + * Get container instance. + * + * @return \Cake\Core\ContainerInterface + */ + protected function getContainer(): ContainerInterface + { + if ($this->container === null) { + throw new CakeException('Container not set.'); + } + + return $this->container; + } } diff --git a/app/vendor/cakephp/cakephp/src/Controller/Controller.php b/app/vendor/cakephp/cakephp/src/Controller/Controller.php index 5d2831ff5..e67a6913e 100644 --- a/app/vendor/cakephp/cakephp/src/Controller/Controller.php +++ b/app/vendor/cakephp/cakephp/src/Controller/Controller.php @@ -18,10 +18,11 @@ use Cake\Controller\Exception\MissingActionException; use Cake\Core\App; -use Cake\Datasource\ModelAwareTrait; use Cake\Datasource\Paging\Exception\PageOutOfBoundsException; use Cake\Datasource\Paging\NumericPaginator; -use Cake\Datasource\Paging\PaginatorInterface; +use Cake\Datasource\Paging\PaginatedInterface; +use Cake\Datasource\QueryInterface; +use Cake\Datasource\RepositoryInterface; use Cake\Event\EventDispatcherInterface; use Cake\Event\EventDispatcherTrait; use Cake\Event\EventInterface; @@ -29,6 +30,7 @@ use Cake\Event\EventManagerInterface; use Cake\Http\ContentTypeNegotiation; use Cake\Http\Exception\NotFoundException; +use Cake\Http\MimeType; use Cake\Http\Response; use Cake\Http\ServerRequest; use Cake\Log\LogTrait; @@ -39,16 +41,12 @@ use Closure; use InvalidArgumentException; use Psr\Http\Message\ResponseInterface; -use ReflectionClass; +use Psr\Http\Message\UriInterface; +use Psr\Http\Server\MiddlewareInterface; use ReflectionException; use ReflectionMethod; -use RuntimeException; -use UnexpectedValueException; -use function Cake\Core\deprecationWarning; -use function Cake\Core\getTypeName; use function Cake\Core\namespaceSplit; use function Cake\Core\pluginSplit; -use function Cake\Core\triggerWarning; /** * Application controller class for organization of business logic. @@ -92,20 +90,18 @@ * * @property \Cake\Controller\Component\FlashComponent $Flash * @property \Cake\Controller\Component\FormProtectionComponent $FormProtection - * @property \Cake\Controller\Component\PaginatorComponent $Paginator - * @property \Cake\Controller\Component\RequestHandlerComponent $RequestHandler - * @property \Cake\Controller\Component\SecurityComponent $Security - * @property \Cake\Controller\Component\AuthComponent $Auth * @property \Cake\Controller\Component\CheckHttpCacheComponent $CheckHttpCache - * @link https://book.cakephp.org/4/en/controllers.html + * @link https://book.cakephp.org/5/en/controllers.html + * @implements \Cake\Event\EventDispatcherInterface<\Cake\Controller\Controller> */ -#[\AllowDynamicProperties] class Controller implements EventListenerInterface, EventDispatcherInterface { + /** + * @use \Cake\Event\EventDispatcherTrait<\Cake\Controller\Controller> + */ use EventDispatcherTrait; use LocatorAwareTrait; use LogTrait; - use ModelAwareTrait; use ViewVarsTrait; /** @@ -115,7 +111,7 @@ class Controller implements EventListenerInterface, EventDispatcherInterface * * @var string */ - protected $name; + protected string $name; /** * An instance of a \Cake\Http\ServerRequest object that contains information about the current request. @@ -123,28 +119,36 @@ class Controller implements EventListenerInterface, EventDispatcherInterface * additional information about the request. * * @var \Cake\Http\ServerRequest - * @link https://book.cakephp.org/4/en/controllers/request-response.html#request + * @link https://book.cakephp.org/5/en/controllers/request-response.html#request */ - protected $request; + protected ServerRequest $request; /** * An instance of a Response object that contains information about the impending response * * @var \Cake\Http\Response - * @link https://book.cakephp.org/4/en/controllers/request-response.html#response + * @link https://book.cakephp.org/5/en/controllers/request-response.html#response */ - protected $response; + protected Response $response; /** - * Settings for pagination. + * Pagination settings. * - * Used to pre-configure pagination preferences for the various - * tables your controller will be paginating. + * When calling paginate() these settings will be merged with the configuration + * you provide. Possible keys: * - * @var array + * - `maxLimit` - The maximum limit users can choose to view. Defaults to 100 + * - `limit` - The initial number of items per page. Defaults to 20. + * - `page` - The starting page, defaults to 1. + * - `allowedParameters` - A list of parameters users are allowed to set using request + * parameters. Modifying this list will allow users to have more influence + * over pagination, be careful with what you permit. + * - `className` - The paginator class to use. Defaults to `Cake\Datasource\Paging\NumericPaginator::class`. + * + * @var array * @see \Cake\Datasource\Paging\NumericPaginator */ - public $paginate = []; + protected array $paginate = []; /** * Set to true to automatically render the view @@ -152,36 +156,36 @@ class Controller implements EventListenerInterface, EventDispatcherInterface * * @var bool */ - protected $autoRender = true; + protected bool $autoRender = true; /** * Instance of ComponentRegistry used to create Components * * @var \Cake\Controller\ComponentRegistry|null */ - protected $_components; + protected ?ComponentRegistry $_components = null; /** * Automatically set to the name of a plugin. * * @var string|null */ - protected $plugin; + protected ?string $plugin = null; /** * Middlewares list. * * @var array - * @psalm-var array + * @phpstan-var array */ - protected $middlewares = []; + protected array $middlewares = []; /** * View classes for content negotiation. * * @var array */ - protected $viewClasses = []; + protected array $viewClasses = []; /** * Constructor. @@ -189,72 +193,50 @@ class Controller implements EventListenerInterface, EventDispatcherInterface * Sets a number of properties based on conventions if they are empty. To override the * conventions CakePHP uses you can define properties in your class declaration. * - * @param \Cake\Http\ServerRequest|null $request Request object for this controller. Can be null for testing, + * @param \Cake\Http\ServerRequest $request Request object for this controller. * but expect that features that use the request parameters will not work. - * @param \Cake\Http\Response|null $response Response object for this controller. * @param string|null $name Override the name useful in testing when using mocks. * @param \Cake\Event\EventManagerInterface|null $eventManager The event manager. Defaults to a new instance. - * @param \Cake\Controller\ComponentRegistry|null $components The component registry. Defaults to a new instance. + * @param \Cake\Controller\ComponentRegistry|null $components ComponentRegistry to use. Defaults to a new instance. */ public function __construct( - ?ServerRequest $request = null, - ?Response $response = null, + ServerRequest $request, ?string $name = null, ?EventManagerInterface $eventManager = null, - ?ComponentRegistry $components = null + ?ComponentRegistry $components = null, ) { if ($name !== null) { $this->name = $name; - } elseif ($this->name === null && $request) { - $this->name = $request->getParam('controller'); + } elseif (!isset($this->name)) { + $controller = $request->getParam('controller'); + if ($controller) { + $this->name = $controller; + } } - if ($this->name === null) { + if (!isset($this->name)) { [, $name] = namespaceSplit(static::class); $this->name = substr($name, 0, -10); } - $this->setRequest($request ?: new ServerRequest()); - $this->response = $response ?: new Response(); + $this->setRequest($request); + $this->response = new Response(); if ($eventManager !== null) { $this->setEventManager($eventManager); } - - $this->modelFactory('Table', [$this->getTableLocator(), 'get']); - - if ($this->defaultTable !== null) { - $this->modelClass = $this->defaultTable; + if ($components !== null) { + $this->_components = $components; + $components->setController($this); } - - if ($this->modelClass === null) { + if ($this->defaultTable === null) { $plugin = $this->request->getParam('plugin'); - $modelClass = ($plugin ? $plugin . '.' : '') . $this->name; - $this->_setModelClass($modelClass); - - $this->defaultTable = $modelClass; - } - - if ($components !== null) { - $this->components($components); + $tableAlias = ($plugin ? $plugin . '.' : '') . $this->name; + $this->defaultTable = $tableAlias; } $this->initialize(); - if (isset($this->components)) { - triggerWarning( - 'Support for loading components using $components property is removed. ' . - 'Use $this->loadComponent() instead in initialize().' - ); - } - - if (isset($this->helpers)) { - triggerWarning( - 'Support for loading helpers using $helpers property is removed. ' . - 'Use $this->viewBuilder()->setHelpers() instead.' - ); - } - $this->getEventManager()->on($this); } @@ -273,37 +255,24 @@ public function initialize(): void /** * Get the component registry for this controller. * - * If called with the first parameter, it will be set as the controller $this->_components property - * - * @param \Cake\Controller\ComponentRegistry|null $components Component registry. * @return \Cake\Controller\ComponentRegistry */ - public function components(?ComponentRegistry $components = null): ComponentRegistry + public function components(): ComponentRegistry { - if ($components !== null) { - $components->setController($this); - - return $this->_components = $components; - } - - if ($this->_components === null) { - $this->_components = new ComponentRegistry($this); - } - - return $this->_components; + return $this->_components ??= new ComponentRegistry($this); } /** * Add a component to the controller's registry. * - * This method will also set the component to a property. + * After loading a component it will be be accessible as a property through Controller::__get(). * For example: * * ``` * $this->loadComponent('Authentication.Authentication'); * ``` * - * Will result in a `Authentication` property being set. + * Will result in a `$this->Authentication` being a reference to that component. * * @param string $name The name of the component to load. * @param array $config The config for the component. @@ -312,77 +281,49 @@ public function components(?ComponentRegistry $components = null): ComponentRegi */ public function loadComponent(string $name, array $config = []): Component { - [, $prop] = pluginSplit($name); - - return $this->{$prop} = $this->components()->load($name, $config); + return $this->components()->load($name, $config); } /** - * Magic accessor for model autoloading. + * Magic accessor for the default table. * * @param string $name Property name - * @return \Cake\Datasource\RepositoryInterface|null The model instance or null + * @return \Cake\Controller\Component|\Cake\ORM\Table|null */ - public function __get(string $name) + public function __get(string $name): mixed { - if (!empty($this->modelClass)) { - if (strpos($this->modelClass, '\\') === false) { - [, $class] = pluginSplit($this->modelClass, true); + if ($this->defaultTable) { + if (str_contains($this->defaultTable, '\\')) { + $class = App::shortName($this->defaultTable, 'Model/Table', 'Table'); } else { - $class = App::shortName($this->modelClass, 'Model/Table', 'Table'); + [, $class] = pluginSplit($this->defaultTable, true); } if ($class === $name) { - return $this->fetchModel(); + return $this->fetchTable(); } } + if ($this->components()->has($name)) { + return $this->components()->get($name); + } + $trace = debug_backtrace(); $parts = explode('\\', static::class); trigger_error( sprintf( - 'Undefined property: %s::$%s in %s on line %s', + 'Undefined property `%s::$%s` in `%s` on line %s', array_pop($parts), $name, - $trace[0]['file'], - $trace[0]['line'] + $trace[0]['file'] ?? 'unknown', + $trace[0]['line'] ?? 'unknown', ), - E_USER_NOTICE + E_USER_NOTICE, ); return null; } - /** - * Magic setter for removed properties. - * - * @param string $name Property name. - * @param mixed $value Value to set. - * @return void - */ - public function __set(string $name, $value): void - { - if ($name === 'components') { - triggerWarning( - 'Support for loading components using $components property is removed. ' . - 'Use $this->loadComponent() instead in initialize().' - ); - - return; - } - - if ($name === 'helpers') { - triggerWarning( - 'Support for loading helpers using $helpers property is removed. ' . - 'Use $this->viewBuilder()->setHelpers() instead.' - ); - - return; - } - - $this->{$name} = $value; - } - /** * Returns the controller name. * @@ -494,7 +435,7 @@ public function getRequest(): ServerRequest public function setRequest(ServerRequest $request) { $this->request = $request; - $this->plugin = $request->getParam('plugin') ?: null; + $this->plugin = $request->getParam('plugin'); return $this; } @@ -534,17 +475,22 @@ public function getAction(): Closure { $request = $this->request; $action = $request->getParam('action'); + $controller = $this->name . 'Controller'; + if ($request->getParam('prefix')) { + $controller = $request->getParam('prefix') . '/' . $controller; + } + if ($this->plugin) { + $controller = $this->plugin . '.' . $controller; + } if (!$this->isAction($action)) { throw new MissingActionException([ - 'controller' => $this->name . 'Controller', - 'action' => $request->getParam('action'), - 'prefix' => $request->getParam('prefix') ?: '', - 'plugin' => $request->getParam('plugin'), + 'controller' => $controller, + 'action' => $action, ]); } - return Closure::fromCallable([$this, $action]); + return $this->$action(...); } /** @@ -553,19 +499,20 @@ public function getAction(): Closure * @param \Closure $action The action closure. * @param array $args The arguments to be passed when invoking action. * @return void - * @throws \UnexpectedValueException If return value of action is not `null` or `ResponseInterface` instance. */ public function invokeAction(Closure $action, array $args): void { $result = $action(...$args); - if ($result !== null && !$result instanceof ResponseInterface) { - throw new UnexpectedValueException(sprintf( - 'Controller actions can only return ResponseInterface instance or null. ' - . 'Got %s instead.', - getTypeName($result) - )); - } - if ($result === null && $this->isAutoRenderEnabled()) { + if ($result !== null) { + assert( + $result instanceof Response, + sprintf( + 'Controller actions can only return Response instance or null. ' + . 'Got %s instead.', + get_debug_type($result), + ), + ); + } elseif ($this->isAutoRenderEnabled()) { $result = $this->render(); } if ($result) { @@ -582,9 +529,9 @@ public function invokeAction(Closure $action, array $args): void * - `except`: (array|string) Run the middleware for all actions except the specified ones. * @return void * @since 4.3.0 - * @psalm-param array{only?: array|string, except?: array|string} $options + * @phpstan-param array{only?: array|string, except?: array|string} $options */ - public function middleware($middleware, array $options = []) + public function middleware(MiddlewareInterface|Closure|string $middleware, array $options = []): void { $this->middlewares[] = [ 'middleware' => $middleware, @@ -654,13 +601,14 @@ public function implementedEvents(): array */ public function startupProcess(): ?ResponseInterface { - $event = $this->dispatchEvent('Controller.initialize'); - if ($event->getResult() instanceof ResponseInterface) { - return $event->getResult(); + $result = $this->dispatchEvent('Controller.initialize')->getResult(); + if ($result instanceof ResponseInterface) { + return $result; } - $event = $this->dispatchEvent('Controller.startup'); - if ($event->getResult() instanceof ResponseInterface) { - return $event->getResult(); + + $result = $this->dispatchEvent('Controller.startup')->getResult(); + if ($result instanceof ResponseInterface) { + return $result; } return null; @@ -677,9 +625,9 @@ public function startupProcess(): ?ResponseInterface */ public function shutdownProcess(): ?ResponseInterface { - $event = $this->dispatchEvent('Controller.shutdown'); - if ($event->getResult() instanceof ResponseInterface) { - return $event->getResult(); + $result = $this->dispatchEvent('Controller.shutdown')->getResult(); + if ($result instanceof ResponseInterface) { + return $result; } return null; @@ -691,23 +639,24 @@ public function shutdownProcess(): ?ResponseInterface * @param \Psr\Http\Message\UriInterface|array|string $url A string, array-based URL or UriInterface instance. * @param int $status HTTP status code. Defaults to `302`. * @return \Cake\Http\Response|null - * @link https://book.cakephp.org/4/en/controllers.html#Controller::redirect + * @link https://book.cakephp.org/5/en/controllers.html#Controller::redirect */ - public function redirect($url, int $status = 302): ?Response + public function redirect(UriInterface|array|string $url, int $status = 302): ?Response { $this->autoRender = false; if ($status < 300 || $status > 399) { throw new InvalidArgumentException( sprintf('Invalid status code `%s`. It should be within the range ' . - '`300` - `399` for redirect responses.', $status) + '`300` - `399` for redirect responses.', $status), ); } $this->response = $this->response->withStatus($status); $event = $this->dispatchEvent('Controller.beforeRedirect', [$url, $this->response]); - if ($event->getResult() instanceof Response) { - return $this->response = $event->getResult(); + $result = $event->getResult(); + if ($result instanceof Response) { + return $this->response = $result; } if ($event->isStopped()) { return null; @@ -721,40 +670,13 @@ public function redirect($url, int $status = 302): ?Response return $this->response = $response; } - /** - * Internally redirects one action to another. Does not perform another HTTP request unlike Controller::redirect() - * - * Examples: - * - * ``` - * setAction('another_action'); - * setAction('action_with_parameters', $parameter1); - * ``` - * - * @param string $action The new action to be 'redirected' to. - * Any other parameters passed to this method will be passed as parameters to the new action. - * @param mixed ...$args Arguments passed to the action - * @return mixed Returns the return value of the called action - * @deprecated 4.2.0 Refactor your code use `redirect()` instead of forwarding actions. - */ - public function setAction(string $action, ...$args) - { - deprecationWarning( - 'Controller::setAction() is deprecated. Either refactor your code to use `redirect()`, ' . - 'or call the other action as a method.' - ); - $this->setRequest($this->request->withParam('action', $action)); - - return $this->$action(...$args); - } - /** * Instantiates the correct view class, hands it its data, and uses it to render the view output. * * @param string|null $template Template to use for rendering * @param string|null $layout Layout to use * @return \Cake\Http\Response A response object containing the rendered view. - * @link https://book.cakephp.org/4/en/controllers.html#rendering-a-view + * @link https://book.cakephp.org/5/en/controllers.html#rendering-a-view */ public function render(?string $template = null, ?string $layout = null): Response { @@ -799,7 +721,7 @@ public function render(?string $template = null, ?string $layout = null): Respon * Each view class must implement the `getContentType()` hook method * to participate in negotiation. * - * @see Cake\Http\ContentTypeNegotiation + * @see \Cake\Http\ContentTypeNegotiation * @return array */ public function viewClasses(): array @@ -813,9 +735,9 @@ public function viewClasses(): array * Each view class must implement the `getContentType()` hook method * to participate in negotiation. * - * @param array $viewClasses View classes list. + * @param array $viewClasses View classes list. * @return $this - * @see Cake\Http\ContentTypeNegotiation + * @see \Cake\Http\ContentTypeNegotiation * @since 4.5.0 */ public function addViewClasses(array $viewClasses) @@ -834,7 +756,7 @@ public function addViewClasses(array $viewClasses) protected function chooseViewClass(): ?string { $possibleViewClasses = $this->viewClasses(); - if (empty($possibleViewClasses)) { + if (!$possibleViewClasses) { return null; } // Controller or component has already made a view class decision. @@ -845,6 +767,7 @@ protected function chooseViewClass(): ?string $typeMap = []; foreach ($possibleViewClasses as $class) { + /** @var string $viewContentType */ $viewContentType = $class::contentType(); if ($viewContentType && !isset($typeMap[$viewContentType])) { $typeMap[$viewContentType] = $class; @@ -855,14 +778,14 @@ protected function chooseViewClass(): ?string // Prefer the _ext route parameter if it is defined. $ext = $request->getParam('_ext'); if ($ext) { - $extTypes = (array)($this->response->getMimeType($ext) ?: []); + $extTypes = MimeType::getMimeTypes($ext) ?? []; foreach ($extTypes as $extType) { if (isset($typeMap[$extType])) { return $typeMap[$extType]; } } - throw new NotFoundException(); + throw new NotFoundException(sprintf('View class for `%s` extension not found', $ext)); } // Use accept header based negotiation. @@ -887,7 +810,7 @@ protected function _templatePath(): string if ($this->request->getParam('prefix')) { $prefixes = array_map( 'Cake\Utility\Inflector::camelize', - explode('/', $this->request->getParam('prefix')) + explode('/', $this->request->getParam('prefix')), ); $templatePath = implode(DIRECTORY_SEPARATOR, $prefixes) . DIRECTORY_SEPARATOR . $templatePath; } @@ -903,25 +826,25 @@ protected function _templatePath(): string * Careful with trusting external sources. * @return string Referring URL */ - public function referer($default = '/', bool $local = true): string + public function referer(array|string|null $default = '/', bool $local = true): string { $referer = $this->request->referer($local); - if ($referer === null) { - $url = Router::url($default, !$local); - $base = $this->request->getAttribute('base'); - if ($local && $base && strpos($url, $base) === 0) { - $url = substr($url, strlen($base)); - if ($url[0] !== '/') { - $url = '/' . $url; - } + if ($referer !== null) { + return $referer; + } - return $url; + $url = Router::url($default, !$local); + $base = $this->request->getAttribute('base'); + if ($local && $base && str_starts_with($url, $base)) { + $url = substr($url, strlen($base)); + if (!str_starts_with($url, '/')) { + return '/' . $url; } return $url; } - return $referer; + return $url; } /** @@ -932,80 +855,42 @@ public function referer($default = '/', bool $local = true): string * * This method will also make the PaginatorHelper available in the view. * - * @param \Cake\ORM\Table|\Cake\ORM\Query|string|null $object Table to paginate + * @param \Cake\Datasource\RepositoryInterface|\Cake\Datasource\QueryInterface|string|null $object Table to paginate * (e.g: Table instance, 'TableName' or a Query object) - * @param array $settings The settings/configuration used for pagination. - * @return \Cake\ORM\ResultSet|\Cake\Datasource\ResultSetInterface Query results - * @link https://book.cakephp.org/4/en/controllers.html#paginating-a-model - * @throws \RuntimeException When no compatible table object can be found. + * @param array $settings The settings/configuration used for pagination. See {@link \Cake\Controller\Controller::$paginate}. + * @return \Cake\Datasource\Paging\PaginatedInterface + * @link https://book.cakephp.org/5/en/controllers.html#paginating-a-model + * @throws \Cake\Http\Exception\NotFoundException When a page out of bounds is requested. */ - public function paginate($object = null, array $settings = []) - { - if (is_object($object)) { - $table = $object; - } - - if (is_string($object) || $object === null) { - $try = [$object, $this->modelClass]; - foreach ($try as $tableName) { - if (empty($tableName)) { - continue; - } - $table = $this->loadModel($tableName); - break; - } - } - - if (empty($table)) { - throw new RuntimeException('Unable to locate an object compatible with paginate.'); + public function paginate( + RepositoryInterface|QueryInterface|string|null $object = null, + array $settings = [], + ): PaginatedInterface { + if (!is_object($object)) { + $object = $this->fetchTable($object); } $settings += $this->paginate; - if (isset($this->Paginator)) { - return $this->Paginator->paginate($table, $settings); - } - - if (isset($settings['paginator'])) { - $settings['className'] = $settings['paginator']; - deprecationWarning( - '`paginator` option is deprecated,' - . ' use `className` instead a specify a paginator name/FQCN.' - ); - } - - $paginator = $settings['className'] ?? NumericPaginator::class; + /** @var class-string<\Cake\Datasource\Paging\PaginatorInterface> $paginator */ + $paginator = App::className( + $settings['className'] ?? NumericPaginator::class, + 'Datasource/Paging', + 'Paginator', + ); + $paginator = new $paginator(); unset($settings['className']); - if (is_string($paginator)) { - $className = App::className($paginator, 'Datasource/Paging', 'Paginator'); - if ($className === null) { - throw new InvalidArgumentException('Invalid paginator: ' . $paginator); - } - $paginator = new $className(); - } - if (!$paginator instanceof PaginatorInterface) { - throw new InvalidArgumentException('Paginator must be an instance of ' . PaginatorInterface::class); - } - $results = null; try { $results = $paginator->paginate( - $table, + $object, $this->request->getQueryParams(), - $settings + $settings, ); - } catch (PageOutOfBoundsException $e) { - // Exception thrown below - } finally { - $paging = $paginator->getPagingParams() + (array)$this->request->getAttribute('paging', []); - $this->request = $this->request->withAttribute('paging', $paging); - } - - if (isset($e)) { - throw new NotFoundException(null, null, $e); + } catch (PageOutOfBoundsException $exception) { + throw new NotFoundException(null, null, $exception); } - /** @psalm-suppress NullableReturnStatement */ return $results; } @@ -1018,17 +903,16 @@ public function paginate($object = null, array $settings = []) * * @param string $action The action to check. * @return bool Whether the method is accessible from a URL. - * @throws \ReflectionException */ public function isAction(string $action): bool { - $baseClass = new ReflectionClass(self::class); - if ($baseClass->hasMethod($action)) { + if (method_exists(self::class, $action)) { return false; } + try { $method = new ReflectionMethod($this, $action); - } catch (ReflectionException $e) { + } catch (ReflectionException) { return false; } @@ -1039,9 +923,10 @@ public function isAction(string $action): bool * Called before the controller action. You can use this method to configure and customize components * or perform logic that needs to happen before each controller action. * - * @param \Cake\Event\EventInterface $event An Event instance - * @return \Cake\Http\Response|null|void - * @link https://book.cakephp.org/4/en/controllers.html#request-life-cycle-callbacks + * @param \Cake\Event\EventInterface<\Cake\Controller\Controller> $event An Event instance + * @return void + * @link https://book.cakephp.org/5/en/controllers.html#request-life-cycle-callbacks + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingNativeTypeHint */ public function beforeFilter(EventInterface $event) { @@ -1051,9 +936,10 @@ public function beforeFilter(EventInterface $event) * Called after the controller action is run, but before the view is rendered. You can use this method * to perform logic or set view variables that are required on every request. * - * @param \Cake\Event\EventInterface $event An Event instance - * @return \Cake\Http\Response|null|void - * @link https://book.cakephp.org/4/en/controllers.html#request-life-cycle-callbacks + * @param \Cake\Event\EventInterface<\Cake\Controller\Controller> $event An Event instance + * @return void + * @link https://book.cakephp.org/5/en/controllers.html#request-life-cycle-callbacks + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingNativeTypeHint */ public function beforeRender(EventInterface $event) { @@ -1068,23 +954,25 @@ public function beforeRender(EventInterface $event) * You can set the event result to response instance or modify the redirect location * using controller's response instance. * - * @param \Cake\Event\EventInterface $event An Event instance - * @param array|string $url A string or array-based URL pointing to another location within the app, + * @param \Cake\Event\EventInterface<\Cake\Controller\Controller> $event An Event instance + * @param \Psr\Http\Message\UriInterface|array|string $url A string or array-based URL pointing to another location within the app, * or an absolute URL * @param \Cake\Http\Response $response The response object. - * @return \Cake\Http\Response|null|void - * @link https://book.cakephp.org/4/en/controllers.html#request-life-cycle-callbacks + * @return void + * @link https://book.cakephp.org/5/en/controllers.html#request-life-cycle-callbacks + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingNativeTypeHint */ - public function beforeRedirect(EventInterface $event, $url, Response $response) + public function beforeRedirect(EventInterface $event, UriInterface|array|string $url, Response $response) { } /** * Called after the controller action is run and rendered. * - * @param \Cake\Event\EventInterface $event An Event instance - * @return \Cake\Http\Response|null|void - * @link https://book.cakephp.org/4/en/controllers.html#request-life-cycle-callbacks + * @param \Cake\Event\EventInterface<\Cake\Controller\Controller> $event An Event instance + * @return void + * @link https://book.cakephp.org/5/en/controllers.html#request-life-cycle-callbacks + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingNativeTypeHint */ public function afterFilter(EventInterface $event) { diff --git a/app/vendor/cakephp/cakephp/src/Controller/ControllerFactory.php b/app/vendor/cakephp/cakephp/src/Controller/ControllerFactory.php index 88e776c4b..e6557d6a4 100644 --- a/app/vendor/cakephp/cakephp/src/Controller/ControllerFactory.php +++ b/app/vendor/cakephp/cakephp/src/Controller/ControllerFactory.php @@ -24,7 +24,6 @@ use Cake\Http\MiddlewareQueue; use Cake\Http\Runner; use Cake\Http\ServerRequest; -use Cake\Utility\Inflector; use Closure; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -32,7 +31,9 @@ use ReflectionClass; use ReflectionFunction; use ReflectionNamedType; -use function Cake\Core\deprecationWarning; +use function Cake\Core\toBool; +use function Cake\Core\toFloat; +use function Cake\Core\toInt; /** * Factory method for building controllers for request. @@ -44,12 +45,12 @@ class ControllerFactory implements ControllerFactoryInterface, RequestHandlerInt /** * @var \Cake\Core\ContainerInterface */ - protected $container; + protected ContainerInterface $container; /** * @var \Cake\Controller\Controller */ - protected $controller; + protected Controller $controller; /** * Constructor @@ -70,6 +71,7 @@ public function __construct(ContainerInterface $container) */ public function create(ServerRequestInterface $request): Controller { + assert($request instanceof ServerRequest); $className = $this->getControllerClass($request); if ($className === null) { throw $this->missingController($request); @@ -79,13 +81,37 @@ public function create(ServerRequestInterface $request): Controller if ($reflection->isAbstract()) { throw $this->missingController($request); } + $this->container->addShared( + ComponentRegistry::class, + new ComponentRegistry(container: $this->container), + ); // Get the controller from the container if defined. // The request is in the container by default. if ($this->container->has($className)) { $controller = $this->container->get($className); } else { - $controller = $reflection->newInstance($request); + $components = $this->container->get(ComponentRegistry::class); + $constructor = $reflection->getConstructor(); + assert($constructor !== null); + $hasComponents = false; + foreach ($constructor->getParameters() as $parameter) { + $paramType = $parameter->getType(); + // TODO: In a future minor release it would be good to start requiring the components parameter + if ( + $parameter->getName() === 'components' && + $paramType !== null && + $paramType->getName() == ComponentRegistry::class + ) { + $hasComponents = true; + break; + } + } + if ($hasComponents) { + $controller = $reflection->newInstance(request: $request, components: $components); + } else { + $controller = $reflection->newInstance($request); + } } return $controller; @@ -99,7 +125,7 @@ public function create(ServerRequestInterface $request): Controller * @throws \Cake\Controller\Exception\MissingActionException If controller action is not found. * @throws \UnexpectedValueException If return value of action method is not null or ResponseInterface instance. */ - public function invoke($controller): ResponseInterface + public function invoke(mixed $controller): ResponseInterface { $this->controller = $controller; @@ -123,24 +149,24 @@ public function invoke($controller): ResponseInterface */ public function handle(ServerRequestInterface $request): ResponseInterface { + assert($request instanceof ServerRequest); $controller = $this->controller; - /** @psalm-suppress ArgumentTypeCoercion */ $controller->setRequest($request); $result = $controller->startupProcess(); - if ($result instanceof ResponseInterface) { + if ($result !== null) { return $result; } $action = $controller->getAction(); $args = $this->getActionArgs( $action, - array_values((array)$controller->getRequest()->getParam('pass')) + array_values((array)$controller->getRequest()->getParam('pass')), ); $controller->invokeAction($action, $args); $result = $controller->shutdownProcess(); - if ($result instanceof ResponseInterface) { + if ($result !== null) { return $result; } @@ -171,7 +197,7 @@ protected function getActionArgs(Closure $action, array $passedParams): array // Use passedParams as a source of typed dependencies. // The accepted types for passedParams was never defined and userland code relies on that. - if ($passedParams && is_object($passedParams[0]) && $passedParams[0] instanceof $typeName) { + if ($passedParams && $passedParams[0] instanceof $typeName) { $resolved[] = array_shift($passedParams); continue; } @@ -250,30 +276,23 @@ protected function getActionArgs(Closure $action, array $passedParams): array * @param \ReflectionNamedType $type Parameter type * @return array|string|float|int|bool|null */ - protected function coerceStringToType(string $argument, ReflectionNamedType $type) + protected function coerceStringToType(string $argument, ReflectionNamedType $type): array|string|float|int|bool|null { - switch ($type->getName()) { - case 'string': - return $argument; - case 'float': - return is_numeric($argument) ? (float)$argument : null; - case 'int': - return filter_var($argument, FILTER_VALIDATE_INT, FILTER_NULL_ON_FAILURE); - case 'bool': - return $argument === '0' ? false : ($argument === '1' ? true : null); - case 'array': - return $argument === '' ? [] : explode(',', $argument); - } - - return null; + return match ($type->getName()) { + 'string' => $argument, + 'float' => toFloat($argument), + 'int' => toInt($argument), + 'bool' => toBool($argument), + 'array' => $argument === '' ? [] : explode(',', $argument), + default => null, + }; } /** * Determine the controller class name based on current request and controller param * * @param \Cake\Http\ServerRequest $request The request to build a controller for. - * @return string|null - * @psalm-return class-string<\Cake\Controller\Controller>|null + * @return class-string<\Cake\Controller\Controller>|null */ public function getControllerClass(ServerRequest $request): ?string { @@ -285,29 +304,7 @@ public function getControllerClass(ServerRequest $request): ?string } if ($request->getParam('prefix')) { $prefix = $request->getParam('prefix'); - - $firstChar = substr($prefix, 0, 1); - if ($firstChar !== strtoupper($firstChar)) { - deprecationWarning( - "The `{$prefix}` prefix did not start with an upper case character. " . - 'Routing prefixes should be defined as CamelCase values. ' . - 'Prefix inflection will be removed in 5.0' - ); - - if (strpos($prefix, '/') === false) { - $namespace .= '/' . Inflector::camelize($prefix); - } else { - $prefixes = array_map( - function ($val) { - return Inflector::camelize($val); - }, - explode('/', $prefix) - ); - $namespace .= '/' . implode('/', $prefixes); - } - } else { - $namespace .= '/' . $prefix; - } + $namespace .= '/' . $prefix; } $firstChar = substr($controller, 0, 1); @@ -315,9 +312,9 @@ function ($val) { // controller names as they allow direct references to // be created. if ( - strpos($controller, '\\') !== false || - strpos($controller, '/') !== false || - strpos($controller, '.') !== false || + str_contains($controller, '\\') || + str_contains($controller, '/') || + str_contains($controller, '.') || $firstChar === strtolower($firstChar) ) { throw $this->missingController($request); @@ -333,14 +330,13 @@ function ($val) { * @param \Cake\Http\ServerRequest $request The request. * @return \Cake\Http\Exception\MissingControllerException */ - protected function missingController(ServerRequest $request) + protected function missingController(ServerRequest $request): MissingControllerException { return new MissingControllerException([ 'controller' => $request->getParam('controller'), 'plugin' => $request->getParam('plugin'), 'prefix' => $request->getParam('prefix'), '_ext' => $request->getParam('_ext'), - 'class' => $request->getParam('controller'), // Deprecated: Will be removed in 4.5. Use `controller` instead. ]); } } diff --git a/app/vendor/cakephp/cakephp/src/Controller/ErrorController.php b/app/vendor/cakephp/cakephp/src/Controller/ErrorController.php index c13cf838a..d5dee1452 100644 --- a/app/vendor/cakephp/cakephp/src/Controller/ErrorController.php +++ b/app/vendor/cakephp/cakephp/src/Controller/ErrorController.php @@ -17,6 +17,7 @@ namespace Cake\Controller; use Cake\Event\EventInterface; +use Cake\View\JsonView; /** * Error Handling Controller @@ -26,20 +27,22 @@ class ErrorController extends Controller { /** - * Initialization hook method. + * Get alternate view classes that can be used in + * content-type negotiation. * - * @return void + * @return array */ - public function initialize(): void + public function viewClasses(): array { - $this->loadComponent('RequestHandler'); + return [JsonView::class]; } /** * beforeRender callback. * - * @param \Cake\Event\EventInterface $event Event. + * @param \Cake\Event\EventInterface<\Cake\Controller\Controller> $event Event. * @return \Cake\Http\Response|null|void + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingNativeTypeHint */ public function beforeRender(EventInterface $event) { diff --git a/app/vendor/cakephp/cakephp/src/Controller/Exception/AuthSecurityException.php b/app/vendor/cakephp/cakephp/src/Controller/Exception/AuthSecurityException.php index e600dad5e..85b069d66 100644 --- a/app/vendor/cakephp/cakephp/src/Controller/Exception/AuthSecurityException.php +++ b/app/vendor/cakephp/cakephp/src/Controller/Exception/AuthSecurityException.php @@ -16,6 +16,8 @@ /** * Auth Security exception - used when SecurityComponent detects any issue with the current request + * + * @deprecated 5.2.0 This exception is no longer used in the CakePHP core. */ class AuthSecurityException extends SecurityException { @@ -24,5 +26,5 @@ class AuthSecurityException extends SecurityException * * @var string */ - protected $_type = 'auth'; + protected string $_type = 'auth'; } diff --git a/app/vendor/cakephp/cakephp/src/Controller/Exception/FormProtectionException.php b/app/vendor/cakephp/cakephp/src/Controller/Exception/FormProtectionException.php new file mode 100644 index 000000000..2c5cbb3b7 --- /dev/null +++ b/app/vendor/cakephp/cakephp/src/Controller/Exception/FormProtectionException.php @@ -0,0 +1,24 @@ + */ - protected $templates = [ - 'failed_coercion' => 'Unable to coerce "%s" to `%s` for `%s` in action %s::%s().', + protected array $templates = [ + 'failed_coercion' => 'Unable to coerce `%s` to `%s` for `%s` in action `%s::%s()`.', 'missing_dependency' => 'Failed to inject dependency from service container for parameter `%s` ' . - 'with type `%s` in action %s::%s().', - 'missing_parameter' => 'Missing passed parameter for `%s` in action %s::%s().', - 'unsupported_type' => 'Type declaration for `%s` in action %s::%s() is unsupported.', + 'with type `%s` in action `%s::%s()`.', + 'missing_parameter' => 'Missing passed parameter for `%s` in action `%s::%s()`.', + 'unsupported_type' => 'Type declaration for `%s` in action `%s::%s()` is unsupported.', ]; /** * Switches message template based on `template` key in message array. * - * @param string|array $message Either the string of the error message, or an array of attributes + * @param array|string $message Either the string of the error message, or an array of attributes * that are made available in the view, and sprintf()'d into Exception::$_messageTemplate * @param int|null $code The error code * @param \Throwable|null $previous the previous exception. */ - public function __construct($message = '', ?int $code = null, ?Throwable $previous = null) + public function __construct(array|string $message = '', ?int $code = null, ?Throwable $previous = null) { if (is_array($message)) { $this->_messageTemplate = $this->templates[$message['template']] ?? ''; diff --git a/app/vendor/cakephp/cakephp/src/Controller/Exception/MissingActionException.php b/app/vendor/cakephp/cakephp/src/Controller/Exception/MissingActionException.php index 3ff7fb464..7e1d6895c 100644 --- a/app/vendor/cakephp/cakephp/src/Controller/Exception/MissingActionException.php +++ b/app/vendor/cakephp/cakephp/src/Controller/Exception/MissingActionException.php @@ -15,15 +15,21 @@ namespace Cake\Controller\Exception; use Cake\Core\Exception\CakeException; +use Cake\Core\Exception\HttpErrorCodeInterface; /** * Missing Action exception - used when a controller action * cannot be found, or when the controller's isAction() method returns false. */ -class MissingActionException extends CakeException +class MissingActionException extends CakeException implements HttpErrorCodeInterface { /** * @inheritDoc */ - protected $_messageTemplate = 'Action %s::%s() could not be found, or is not accessible.'; + protected int $_defaultCode = 404; + + /** + * @inheritDoc + */ + protected string $_messageTemplate = 'Action `%s::%s()` could not be found, or is not accessible.'; } diff --git a/app/vendor/cakephp/cakephp/src/Controller/Exception/MissingComponentException.php b/app/vendor/cakephp/cakephp/src/Controller/Exception/MissingComponentException.php index 7f13621f7..cfcd055b9 100644 --- a/app/vendor/cakephp/cakephp/src/Controller/Exception/MissingComponentException.php +++ b/app/vendor/cakephp/cakephp/src/Controller/Exception/MissingComponentException.php @@ -24,5 +24,5 @@ class MissingComponentException extends CakeException /** * @inheritDoc */ - protected $_messageTemplate = 'Component class %s could not be found.'; + protected string $_messageTemplate = 'Component class `%s` could not be found.'; } diff --git a/app/vendor/cakephp/cakephp/src/Controller/Exception/SecurityException.php b/app/vendor/cakephp/cakephp/src/Controller/Exception/SecurityException.php index ec8d170bb..cf0425171 100644 --- a/app/vendor/cakephp/cakephp/src/Controller/Exception/SecurityException.php +++ b/app/vendor/cakephp/cakephp/src/Controller/Exception/SecurityException.php @@ -15,9 +15,13 @@ namespace Cake\Controller\Exception; use Cake\Http\Exception\BadRequestException; +use Throwable; +use function Cake\Core\deprecationWarning; /** * Security exception - used when SecurityComponent detects any issue with the current request + * + * @deprecated 5.2.0 This exception is no longer used in the CakePHP core. */ class SecurityException extends BadRequestException { @@ -26,14 +30,31 @@ class SecurityException extends BadRequestException * * @var string */ - protected $_type = 'secure'; + protected string $_type = 'secure'; /** * Reason for request blackhole * * @var string|null */ - protected $_reason; + protected ?string $_reason = null; + + /** + * Constructor + * + * @param string|null $message If no message is given 'Bad Request' will be the message + * @param int|null $code Status code, defaults to 400 + * @param \Throwable|null $previous The previous exception. + */ + public function __construct(?string $message = null, ?int $code = null, ?Throwable $previous = null) + { + deprecationWarning( + '5.2.0', + static::class . ' is deprecated. Use BadRequestException or a custom exception instead.', + ); + + parent::__construct($message, $code, $previous); + } /** * Getter for type diff --git a/app/vendor/cakephp/cakephp/src/Core/App.php b/app/vendor/cakephp/cakephp/src/Core/App.php index 3f5c9a0ba..09bb27be9 100644 --- a/app/vendor/cakephp/cakephp/src/Core/App.php +++ b/app/vendor/cakephp/cakephp/src/Core/App.php @@ -16,6 +16,8 @@ */ namespace Cake\Core; +use Cake\Core\Exception\CakeException; + /** * App is responsible for resource location, and path management. * @@ -38,7 +40,7 @@ * Plugins can be located with App as well. Using Plugin::path('DebugKit') for example, will * give you the full path to the DebugKit plugin. * - * @link https://book.cakephp.org/4/en/core-libraries/app.html + * @link https://book.cakephp.org/5/en/core-libraries/app.html */ class App { @@ -49,12 +51,11 @@ class App * @param string $class Class name * @param string $type Type of class * @param string $suffix Class name suffix - * @return string|null Namespaced class name, null if the class is not found. - * @psalm-return class-string|null + * @return class-string|null Namespaced class name, null if the class is not found. */ public static function className(string $class, string $type = '', string $suffix = ''): ?string { - if (strpos($class, '\\') !== false) { + if (str_contains($class, '\\')) { return class_exists($class) ? $class : null; } @@ -106,13 +107,13 @@ public static function className(string $class, string $type = '', string $suffi * * ``` * App::shortName( - * 'Cake\Controller\Component\AuthComponent', + * 'Cake\Controller\Component\FlashComponent', * 'Controller/Component', * 'Component' * ) * ``` * - * Returns: Auth + * Returns: Flash * * @param string $class Class name * @param string $type Type of class @@ -129,11 +130,11 @@ public static function shortName(string $class, string $type, string $suffix = ' return $class; } - $pluginName = (string)substr($class, 0, $pos); - $name = (string)substr($class, $pos + strlen($type)); + $pluginName = substr($class, 0, $pos); + $name = substr($class, $pos + strlen($type)); if ($suffix) { - $name = (string)substr($name, 0, -strlen($suffix)); + $name = substr($name, 0, -strlen($suffix)); } $nonPluginNamespaces = [ @@ -162,10 +163,9 @@ protected static function _classExistsInBase(string $name, string $namespace): b } /** - * Used to read information stored path. + * Used to read information of stored path. * - * The 1st character of $type argument should be lower cased and will return the - * value of `App.paths.$type` config. + * When called without the `$plugin` argument it will return the value of `App.paths.$type` config. * * Default types: * - plugins @@ -180,36 +180,27 @@ protected static function _classExistsInBase(string $name, string $namespace): b * * Will return the value of `App.paths.plugins` config. * - * Deprecated: 4.0 App::path() is deprecated for class path (inside src/ directory). - * Use \Cake\Core\App::classPath() instead or directly the method on \Cake\Core\Plugin class. + * For plugins it can be used to get paths for types `templates` or `locales`. * * @param string $type Type of path * @param string|null $plugin Plugin name - * @return array - * @link https://book.cakephp.org/4/en/core-libraries/app.html#finding-paths-to-namespaces + * @return array + * @link https://book.cakephp.org/5/en/core-libraries/app.html#finding-paths-to-namespaces */ public static function path(string $type, ?string $plugin = null): array { - if ($plugin === null && $type[0] === strtolower($type[0])) { + if ($plugin === null) { return (array)Configure::read('App.paths.' . $type); } - if ($type === 'templates') { - /** @psalm-suppress PossiblyNullArgument */ - return [Plugin::templatePath($plugin)]; - } - - if ($type === 'locales') { - /** @psalm-suppress PossiblyNullArgument */ - return [Plugin::path($plugin) . 'resources' . DIRECTORY_SEPARATOR . 'locales' . DIRECTORY_SEPARATOR]; - } - - deprecationWarning( - 'App::path() is deprecated for class path.' - . ' Use \Cake\Core\App::classPath() or \Cake\Core\Plugin::classPath() instead.' - ); - - return static::classPath($type, $plugin); + return match ($type) { + 'templates' => [Plugin::templatePath($plugin)], + 'locales' => [Plugin::path($plugin) . 'resources' . DIRECTORY_SEPARATOR . 'locales' . DIRECTORY_SEPARATOR], + default => throw new CakeException(sprintf( + 'Invalid type `%s`. Only path types `templates` and `locales` are supported for plugins.', + $type, + )) + }; } /** diff --git a/app/vendor/cakephp/cakephp/src/Core/BasePlugin.php b/app/vendor/cakephp/cakephp/src/Core/BasePlugin.php index a748be37c..67b556807 100644 --- a/app/vendor/cakephp/cakephp/src/Core/BasePlugin.php +++ b/app/vendor/cakephp/cakephp/src/Core/BasePlugin.php @@ -16,6 +16,7 @@ namespace Cake\Core; use Cake\Console\CommandCollection; +use Cake\Event\EventManagerInterface; use Cake\Http\MiddlewareQueue; use Cake\Routing\RouteBuilder; use Closure; @@ -35,70 +36,77 @@ class BasePlugin implements PluginInterface * * @var bool */ - protected $bootstrapEnabled = true; + protected bool $bootstrapEnabled = true; /** * Console middleware * * @var bool */ - protected $consoleEnabled = true; + protected bool $consoleEnabled = true; /** * Enable middleware * * @var bool */ - protected $middlewareEnabled = true; + protected bool $middlewareEnabled = true; /** * Register container services * * @var bool */ - protected $servicesEnabled = true; + protected bool $servicesEnabled = true; /** * Load routes or not * * @var bool */ - protected $routesEnabled = true; + protected bool $routesEnabled = true; + + /** + * Load events or not + * + * @var bool + */ + protected bool $eventsEnabled = true; /** * The path to this plugin. * - * @var string + * @var string|null */ - protected $path; + protected ?string $path = null; /** * The class path for this plugin. * - * @var string + * @var string|null */ - protected $classPath; + protected ?string $classPath = null; /** * The config path for this plugin. * - * @var string + * @var string|null */ - protected $configPath; + protected ?string $configPath = null; /** * The templates path for this plugin. * - * @var string + * @var string|null */ - protected $templatePath; + protected ?string $templatePath = null; /** * The name of this plugin * - * @var string + * @var string|null */ - protected $name; + protected ?string $name = null; /** * Constructor @@ -135,14 +143,13 @@ public function initialize(): void */ public function getName(): string { - if ($this->name) { + if ($this->name !== null) { return $this->name; } $parts = explode('\\', static::class); array_pop($parts); - $this->name = implode('/', $parts); - return $this->name; + return $this->name = implode('/', $parts); } /** @@ -150,19 +157,18 @@ public function getName(): string */ public function getPath(): string { - if ($this->path) { + if ($this->path !== null) { return $this->path; } $reflection = new ReflectionClass($this); - $path = dirname($reflection->getFileName()); + $path = dirname((string)$reflection->getFileName()); // Trim off src - if (substr($path, -3) === 'src') { + if (str_ends_with($path, 'src')) { $path = substr($path, 0, -3); } - $this->path = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; - return $this->path; + return $this->path = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; } /** @@ -170,7 +176,7 @@ public function getPath(): string */ public function getConfigPath(): string { - if ($this->configPath) { + if ($this->configPath !== null) { return $this->configPath; } $path = $this->getPath(); @@ -183,7 +189,7 @@ public function getConfigPath(): string */ public function getClassPath(): string { - if ($this->classPath) { + if ($this->classPath !== null) { return $this->classPath; } $path = $this->getPath(); @@ -196,7 +202,7 @@ public function getClassPath(): string */ public function getTemplatePath(): string { - if ($this->templatePath) { + if ($this->templatePath !== null) { return $this->templatePath; } $path = $this->getPath(); @@ -246,9 +252,11 @@ public function isEnabled(string $hook): bool protected function checkHook(string $hook): void { if (!in_array($hook, static::VALID_HOOKS, true)) { - throw new InvalidArgumentException( - "`$hook` is not a valid hook name. Must be one of " . implode(', ', static::VALID_HOOKS) - ); + throw new InvalidArgumentException(sprintf( + '`%s` is not a valid hook name. Must be one of `%s.`', + $hook, + implode(', ', static::VALID_HOOKS), + )); } } @@ -302,4 +310,15 @@ public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue public function services(ContainerInterface $container): void { } + + /** + * Register application events. + * + * @param \Cake\Event\EventManagerInterface $eventManager The global event manager to register listeners on + * @return \Cake\Event\EventManagerInterface + */ + public function events(EventManagerInterface $eventManager): EventManagerInterface + { + return $eventManager; + } } diff --git a/app/vendor/cakephp/cakephp/src/Core/ClassLoader.php b/app/vendor/cakephp/cakephp/src/Core/ClassLoader.php deleted file mode 100644 index e2a752556..000000000 --- a/app/vendor/cakephp/cakephp/src/Core/ClassLoader.php +++ /dev/null @@ -1,139 +0,0 @@ - - */ - protected $_prefixes = []; - - /** - * Register loader with SPL autoloader stack. - * - * @return void - */ - public function register(): void - { - /** @var callable $callable */ - $callable = [$this, 'loadClass']; - spl_autoload_register($callable); - } - - /** - * Adds a base directory for a namespace prefix. - * - * @param string $prefix The namespace prefix. - * @param string $baseDir A base directory for class files in the - * namespace. - * @param bool $prepend If true, prepend the base directory to the stack - * instead of appending it; this causes it to be searched first rather - * than last. - * @return void - */ - public function addNamespace(string $prefix, string $baseDir, bool $prepend = false): void - { - $prefix = trim($prefix, '\\') . '\\'; - - $baseDir = rtrim($baseDir, '/') . DIRECTORY_SEPARATOR; - $baseDir = rtrim($baseDir, DIRECTORY_SEPARATOR) . '/'; - - $this->_prefixes[$prefix] = $this->_prefixes[$prefix] ?? []; - - if ($prepend) { - array_unshift($this->_prefixes[$prefix], $baseDir); - } else { - $this->_prefixes[$prefix][] = $baseDir; - } - } - - /** - * Loads the class file for a given class name. - * - * @param string $class The fully-qualified class name. - * @return string|false The mapped file name on success, or boolean false on - * failure. - */ - public function loadClass(string $class) - { - $prefix = $class; - - while (($pos = strrpos($prefix, '\\')) !== false) { - $prefix = substr($class, 0, $pos + 1); - $relativeClass = substr($class, $pos + 1); - - $mappedFile = $this->_loadMappedFile($prefix, $relativeClass); - if ($mappedFile) { - return $mappedFile; - } - - $prefix = rtrim($prefix, '\\'); - } - - return false; - } - - /** - * Load the mapped file for a namespace prefix and relative class. - * - * @param string $prefix The namespace prefix. - * @param string $relativeClass The relative class name. - * @return string|false Boolean false if no mapped file can be loaded, or the - * name of the mapped file that was loaded. - */ - protected function _loadMappedFile(string $prefix, string $relativeClass) - { - if (!isset($this->_prefixes[$prefix])) { - return false; - } - - foreach ($this->_prefixes[$prefix] as $baseDir) { - $file = $baseDir . str_replace('\\', DIRECTORY_SEPARATOR, $relativeClass) . '.php'; - - if ($this->_requireFile($file)) { - return $file; - } - } - - return false; - } - - /** - * If a file exists, require it from the file system. - * - * @param string $file The file to require. - * @return bool True if the file exists, false if not. - */ - protected function _requireFile(string $file): bool - { - if (file_exists($file)) { - require $file; - - return true; - } - - return false; - } -} diff --git a/app/vendor/cakephp/cakephp/src/Core/Configure.php b/app/vendor/cakephp/cakephp/src/Core/Configure.php index 99e82592a..1fa44e8b9 100644 --- a/app/vendor/cakephp/cakephp/src/Core/Configure.php +++ b/app/vendor/cakephp/cakephp/src/Core/Configure.php @@ -21,7 +21,6 @@ use Cake\Core\Configure\Engine\PhpConfig; use Cake\Core\Exception\CakeException; use Cake\Utility\Hash; -use RuntimeException; /** * Configuration class. Used for managing runtime configuration information. @@ -30,7 +29,7 @@ * as methods for loading additional configuration files or storing runtime configuration * for future use. * - * @link https://book.cakephp.org/4/en/development/configuration.html + * @link https://book.cakephp.org/5/en/development/configuration.html */ class Configure { @@ -39,7 +38,7 @@ class Configure * * @var array */ - protected static $_values = [ + protected static array $_values = [ 'debug' => false, ]; @@ -49,14 +48,14 @@ class Configure * @see \Cake\Core\Configure::load() * @var array<\Cake\Core\Configure\ConfigEngineInterface> */ - protected static $_engines = []; + protected static array $_engines = []; /** * Flag to track whether ini_set exists. * * @var bool|null */ - protected static $_hasIniSet; + protected static ?bool $_hasIniSet = null; /** * Used to store a dynamic variable in Configure. @@ -80,9 +79,9 @@ class Configure * Alternatively can be an array containing key(s) and value(s). * @param mixed $value Value to set for the given key. * @return void - * @link https://book.cakephp.org/4/en/development/configuration.html#writing-configuration-data + * @link https://book.cakephp.org/5/en/development/configuration.html#writing-configuration-data */ - public static function write($config, $value = null): void + public static function write(array|string $config, mixed $value = null): void { if (!is_array($config)) { $config = [$config => $value]; @@ -93,9 +92,8 @@ public static function write($config, $value = null): void } if (isset($config['debug'])) { - if (static::$_hasIniSet === null) { - static::$_hasIniSet = function_exists('ini_set'); - } + static::$_hasIniSet ??= function_exists('ini_set'); + if (static::$_hasIniSet) { ini_set('display_errors', $config['debug'] ? '1' : '0'); } @@ -115,9 +113,9 @@ public static function write($config, $value = null): void * @param string|null $var Variable to obtain. Use '.' to access array elements. * @param mixed $default The return value when the configure does not exist * @return mixed Value stored in configure, or null. - * @link https://book.cakephp.org/4/en/development/configuration.html#reading-configuration-data + * @link https://book.cakephp.org/5/en/development/configuration.html#reading-configuration-data */ - public static function read(?string $var = null, $default = null) + public static function read(?string $var = null, mixed $default = null): mixed { if ($var === null) { return static::$_values; @@ -134,7 +132,7 @@ public static function read(?string $var = null, $default = null) */ public static function check(string $var): bool { - if (empty($var)) { + if (!$var) { return false; } @@ -157,13 +155,13 @@ public static function check(string $var): bool * * @param string $var Variable to obtain. Use '.' to access array elements. * @return mixed Value stored in configure. - * @throws \RuntimeException if the requested configuration is not set. - * @link https://book.cakephp.org/4/en/development/configuration.html#reading-configuration-data + * @throws \Cake\Core\Exception\CakeException if the requested configuration is not set. + * @link https://book.cakephp.org/5/en/development/configuration.html#reading-configuration-data */ - public static function readOrFail(string $var) + public static function readOrFail(string $var): mixed { if (!static::check($var)) { - throw new RuntimeException(sprintf('Expected configuration key "%s" not found.', $var)); + throw new CakeException(sprintf('Expected configuration key `%s` not found.', $var)); } return static::read($var); @@ -180,7 +178,7 @@ public static function readOrFail(string $var) * * @param string $var the var to be deleted * @return void - * @link https://book.cakephp.org/4/en/development/configuration.html#deleting-configuration-data + * @link https://book.cakephp.org/5/en/development/configuration.html#deleting-configuration-data */ public static function delete(string $var): void { @@ -197,13 +195,13 @@ public static function delete(string $var): void * * @param string $var Variable to consume. Use '.' to access array elements. * @return mixed Value stored in configure. - * @throws \RuntimeException if the requested configuration is not set. + * @throws \Cake\Core\Exception\CakeException if the requested configuration is not set. * @since 3.6.0 */ - public static function consumeOrFail(string $var) + public static function consumeOrFail(string $var): mixed { if (!static::check($var)) { - throw new RuntimeException(sprintf('Expected configuration key "%s" not found.', $var)); + throw new CakeException(sprintf('Expected configuration key `%s` not found.', $var)); } return static::consume($var); @@ -216,11 +214,11 @@ public static function consumeOrFail(string $var) * out of configure into the various other classes in CakePHP. * * @param string $var The key to read and remove. - * @return array|string|null + * @return mixed */ - public static function consume(string $var) + public static function consume(string $var): mixed { - if (strpos($var, '.') === false) { + if (!str_contains($var, '.')) { if (!isset(static::$_values[$var])) { return null; } @@ -323,7 +321,7 @@ public static function drop(string $name): bool * @param bool $merge if config files should be merged instead of simply overridden * @return bool True if load successful. * @throws \Cake\Core\Exception\CakeException if the $config engine is not found - * @link https://book.cakephp.org/4/en/development/configuration.html#reading-and-writing-configuration-files + * @link https://book.cakephp.org/5/en/development/configuration.html#reading-and-writing-configuration-files */ public static function load(string $key, string $config = 'default', bool $merge = true): bool { @@ -333,8 +331,8 @@ public static function load(string $key, string $config = 'default', bool $merge sprintf( 'Config %s engine not found when attempting to load %s.', $config, - $key - ) + $key, + ), ); } @@ -382,10 +380,10 @@ public static function dump(string $key, string $config = 'default', array $keys { $engine = static::_getEngine($config); if (!$engine) { - throw new CakeException(sprintf('There is no "%s" config engine.', $config)); + throw new CakeException(sprintf('There is no `%s` config engine.', $config)); } $values = static::$_values; - if (!empty($keys)) { + if ($keys) { $values = array_intersect_key($values, array_flip($keys)); } @@ -428,7 +426,7 @@ public static function version(): string return $version; } - $path = dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR . 'config/config.php'; + $path = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'config/config.php'; if (is_file($path)) { $config = require $path; static::write($config); @@ -448,15 +446,13 @@ public static function version(): string * @param string $cacheConfig The cache configuration to save into. Defaults to 'default' * @param array|null $data Either an array of data to store, or leave empty to store all values. * @return bool Success - * @throws \RuntimeException */ public static function store(string $name, string $cacheConfig = 'default', ?array $data = null): bool { - if ($data === null) { - $data = static::$_values; - } + $data ??= static::$_values; + if (!class_exists(Cache::class)) { - throw new RuntimeException('You must install cakephp/cache to use Configure::store()'); + throw new CakeException('You must install cakephp/cache to use Configure::store()'); } return Cache::write($name, $data, $cacheConfig); @@ -469,12 +465,11 @@ public static function store(string $name, string $cacheConfig = 'default', ?arr * @param string $name Name of the stored config file to load. * @param string $cacheConfig Name of the Cache configuration to read from. * @return bool Success. - * @throws \RuntimeException */ public static function restore(string $name, string $cacheConfig = 'default'): bool { if (!class_exists(Cache::class)) { - throw new RuntimeException('You must install cakephp/cache to use Configure::restore()'); + throw new CakeException('You must install cakephp/cache to use Configure::restore()'); } $values = Cache::read($name, $cacheConfig); if ($values) { diff --git a/app/vendor/cakephp/cakephp/src/Core/Configure/Engine/IniConfig.php b/app/vendor/cakephp/cakephp/src/Core/Configure/Engine/IniConfig.php index 54a49a4b3..13e2a5be9 100644 --- a/app/vendor/cakephp/cakephp/src/Core/Configure/Engine/IniConfig.php +++ b/app/vendor/cakephp/cakephp/src/Core/Configure/Engine/IniConfig.php @@ -18,6 +18,7 @@ use Cake\Core\Configure\ConfigEngineInterface; use Cake\Core\Configure\FileConfigTrait; +use Cake\Core\Exception\CakeException; use Cake\Utility\Hash; /** @@ -62,14 +63,14 @@ class IniConfig implements ConfigEngineInterface * * @var string */ - protected $_extension = '.ini'; + protected string $_extension = '.ini'; /** * The section to read, if null all sections will be read. * * @var string|null */ - protected $_section; + protected ?string $_section = null; /** * Build and construct a new ini file parser. The parser can be used to read @@ -81,10 +82,7 @@ class IniConfig implements ConfigEngineInterface */ public function __construct(?string $path = null, ?string $section = null) { - if ($path === null) { - $path = CONFIG; - } - $this->_path = $path; + $this->_path = $path ?? CONFIG; $this->_section = $section; } @@ -102,6 +100,10 @@ public function read(string $key): array $file = $this->_getFilePath($key, true); $contents = parse_ini_file($file, true); + if ($contents === false) { + throw new CakeException(sprintf('Cannot parse INI file `%s`', $file)); + } + if ($this->_section && isset($contents[$this->_section])) { $values = $this->_parseNestedValues($contents[$this->_section]); } else { @@ -135,7 +137,7 @@ protected function _parseNestedValues(array $values): array $value = false; } unset($values[$key]); - if (strpos((string)$key, '.') !== false) { + if (str_contains((string)$key, '.')) { $values = Hash::insert($values, $key, $value); } else { $values[$key] = $value; @@ -158,15 +160,14 @@ public function dump(string $key, array $data): bool $result = []; foreach ($data as $k => $value) { $isSection = false; - /** @psalm-suppress InvalidArrayAccess */ - if ($k[0] !== '[') { - $result[] = "[$k]"; + if (!str_starts_with($k, '[')) { + $result[] = "[{$k}]"; $isSection = true; } if (is_array($value)) { $kValues = Hash::flatten($value, '.'); foreach ($kValues as $k2 => $v) { - $result[] = "$k2 = " . $this->_value($v); + $result[] = "{$k2} = " . $this->_value($v); } } if ($isSection) { @@ -186,18 +187,13 @@ public function dump(string $key, array $data): bool * @param mixed $value Value to export. * @return string String value for ini file. */ - protected function _value($value): string + protected function _value(mixed $value): string { - if ($value === null) { - return 'null'; - } - if ($value === true) { - return 'true'; - } - if ($value === false) { - return 'false'; - } - - return (string)$value; + return match ($value) { + null => 'null', + true => 'true', + false => 'false', + default => (string)$value + }; } } diff --git a/app/vendor/cakephp/cakephp/src/Core/Configure/Engine/JsonConfig.php b/app/vendor/cakephp/cakephp/src/Core/Configure/Engine/JsonConfig.php index 66e8a902b..86f4bbba5 100644 --- a/app/vendor/cakephp/cakephp/src/Core/Configure/Engine/JsonConfig.php +++ b/app/vendor/cakephp/cakephp/src/Core/Configure/Engine/JsonConfig.php @@ -47,7 +47,7 @@ class JsonConfig implements ConfigEngineInterface * * @var string */ - protected $_extension = '.json'; + protected string $_extension = '.json'; /** * Constructor for JSON Config file reading. @@ -56,10 +56,7 @@ class JsonConfig implements ConfigEngineInterface */ public function __construct(?string $path = null) { - if ($path === null) { - $path = CONFIG; - } - $this->_path = $path; + $this->_path = $path ?? CONFIG; } /** @@ -79,18 +76,22 @@ public function read(string $key): array { $file = $this->_getFilePath($key, true); - $values = json_decode(file_get_contents($file), true); + $jsonContent = file_get_contents($file); + if ($jsonContent === false) { + throw new CakeException(sprintf('Cannot read file content of `%s`', $file)); + } + $values = json_decode($jsonContent, true); if (json_last_error() !== JSON_ERROR_NONE) { throw new CakeException(sprintf( - 'Error parsing JSON string fetched from config file "%s.json": %s', + 'Error parsing JSON string fetched from config file `%s.json`: %s', $key, - json_last_error_msg() + json_last_error_msg(), )); } if (!is_array($values)) { throw new CakeException(sprintf( - 'Decoding JSON config file "%s.json" did not return an array', - $key + 'Decoding JSON config file `%s.json` did not return an array', + $key, )); } diff --git a/app/vendor/cakephp/cakephp/src/Core/Configure/Engine/PhpConfig.php b/app/vendor/cakephp/cakephp/src/Core/Configure/Engine/PhpConfig.php index 6bdf3a42a..74273602b 100644 --- a/app/vendor/cakephp/cakephp/src/Core/Configure/Engine/PhpConfig.php +++ b/app/vendor/cakephp/cakephp/src/Core/Configure/Engine/PhpConfig.php @@ -53,7 +53,7 @@ class PhpConfig implements ConfigEngineInterface * * @var string */ - protected $_extension = '.php'; + protected string $_extension = '.php'; /** * Constructor for PHP Config file reading. @@ -62,10 +62,7 @@ class PhpConfig implements ConfigEngineInterface */ public function __construct(?string $path = null) { - if ($path === null) { - $path = CONFIG; - } - $this->_path = $path; + $this->_path = $path ?? CONFIG; } /** @@ -84,14 +81,12 @@ public function read(string $key): array { $file = $this->_getFilePath($key, true); - $config = null; - $return = include $file; if (is_array($return)) { return $return; } - throw new CakeException(sprintf('Config file "%s" did not return an array', $key . '.php')); + throw new CakeException(sprintf('Config file `%s` did not return an array', $key . '.php.')); } /** diff --git a/app/vendor/cakephp/cakephp/src/Core/Configure/FileConfigTrait.php b/app/vendor/cakephp/cakephp/src/Core/Configure/FileConfigTrait.php index 79bcdea49..67201951c 100644 --- a/app/vendor/cakephp/cakephp/src/Core/Configure/FileConfigTrait.php +++ b/app/vendor/cakephp/cakephp/src/Core/Configure/FileConfigTrait.php @@ -30,7 +30,7 @@ trait FileConfigTrait * * @var string */ - protected $_path = ''; + protected string $_path = ''; /** * Get file path @@ -44,7 +44,7 @@ trait FileConfigTrait */ protected function _getFilePath(string $key, bool $checkExists = false): string { - if (strpos($key, '..') !== false) { + if (str_contains($key, '..')) { throw new CakeException('Cannot load/dump configuration files with ../ in them.'); } @@ -67,6 +67,6 @@ protected function _getFilePath(string $key, bool $checkExists = false): string return $realPath; } - throw new CakeException(sprintf('Could not load configuration file: %s', $file)); + throw new CakeException(sprintf('Could not load configuration file: `%s`.', $file)); } } diff --git a/app/vendor/cakephp/cakephp/src/Core/ContainerInterface.php b/app/vendor/cakephp/cakephp/src/Core/ContainerInterface.php index d8d39e1bc..e942306e8 100644 --- a/app/vendor/cakephp/cakephp/src/Core/ContainerInterface.php +++ b/app/vendor/cakephp/cakephp/src/Core/ContainerInterface.php @@ -17,6 +17,7 @@ namespace Cake\Core; use League\Container\DefinitionContainerInterface; +use Psr\Container\ContainerInterface as PsrContainerInterface; /** * Interface for the Dependency Injection Container in CakePHP applications @@ -29,4 +30,9 @@ */ interface ContainerInterface extends DefinitionContainerInterface { + /** + * @param \Psr\Container\ContainerInterface $container The container instance to use as delegation + * @return \Psr\Container\ContainerInterface + */ + public function delegate(PsrContainerInterface $container): PsrContainerInterface; } diff --git a/app/vendor/cakephp/cakephp/src/Core/ConventionsTrait.php b/app/vendor/cakephp/cakephp/src/Core/ConventionsTrait.php index 08967af47..fef8ecf5d 100644 --- a/app/vendor/cakephp/cakephp/src/Core/ConventionsTrait.php +++ b/app/vendor/cakephp/cakephp/src/Core/ConventionsTrait.php @@ -132,7 +132,7 @@ protected function _pluralHumanName(string $name): string * Find the correct path for a plugin. Scans $pluginPaths for the plugin you want. * * @param string $pluginName Name of the plugin you want ie. DebugKit - * @return string path path to the correct plugin. + * @return string Path to the correct plugin. */ protected function _pluginPath(string $pluginName): string { diff --git a/app/vendor/cakephp/cakephp/src/Core/EventAwareApplicationInterface.php b/app/vendor/cakephp/cakephp/src/Core/EventAwareApplicationInterface.php new file mode 100644 index 000000000..c1bd92b0c --- /dev/null +++ b/app/vendor/cakephp/cakephp/src/Core/EventAwareApplicationInterface.php @@ -0,0 +1,35 @@ +_attributes = $message; @@ -83,41 +73,4 @@ public function getAttributes(): array { return $this->_attributes; } - - /** - * Get/set the response header to be used - * - * See also {@link \Cake\Http\Response::withHeader()} - * - * @param array|string|null $header A single header string or an associative - * array of "header name" => "header value" - * @param string|null $value The header value. - * @return array|null - * @deprecated 4.2.0 Use `HttpException::setHeaders()` instead. Response headers - * should be set for HttpException only. - */ - public function responseHeader($header = null, $value = null): ?array - { - if ($header === null) { - return $this->_responseHeaders; - } - - deprecationWarning( - 'Setting HTTP response headers from Exception directly is deprecated. ' . - 'If your exceptions extend Exception, they must now extend HttpException. ' . - 'You should only set HTTP headers on HttpException instances via the `setHeaders()` method.' - ); - if (is_array($header)) { - return $this->_responseHeaders = $header; - } - - return $this->_responseHeaders = [$header => $value]; - } } - -// phpcs:disable -class_alias( - 'Cake\Core\Exception\CakeException', - 'Cake\Core\Exception\Exception' -); -// phpcs:enable diff --git a/app/vendor/cakephp/cakephp/src/Core/Exception/Exception.php b/app/vendor/cakephp/cakephp/src/Core/Exception/Exception.php deleted file mode 100644 index bd93c509e..000000000 --- a/app/vendor/cakephp/cakephp/src/Core/Exception/Exception.php +++ /dev/null @@ -1,21 +0,0 @@ - */ - protected $_config = []; + protected array $_config = []; /** * Whether the config property has already been configured with defaults * * @var bool */ - protected $_configInitialized = false; + protected bool $_configInitialized = false; /** * Sets the config. @@ -70,7 +70,7 @@ trait InstanceConfigTrait * @return $this * @throws \Cake\Core\Exception\CakeException When trying to set a key that is invalid. */ - public function setConfig($key, $value = null, $merge = true) + public function setConfig(array|string $key, mixed $value = null, bool $merge = true) { if (!$this->_configInitialized) { $this->_config = $this->_defaultConfig; @@ -115,16 +115,14 @@ public function setConfig($key, $value = null, $merge = true) * @param mixed $default The return value when the key does not exist. * @return mixed Configuration data at the named key or null if the key does not exist. */ - public function getConfig(?string $key = null, $default = null) + public function getConfig(?string $key = null, mixed $default = null): mixed { if (!$this->_configInitialized) { $this->_config = $this->_defaultConfig; $this->_configInitialized = true; } - $return = $this->_configRead($key); - - return $return ?? $default; + return $this->_configRead($key) ?? $default; } /** @@ -136,7 +134,7 @@ public function getConfig(?string $key = null, $default = null) * @return mixed Configuration data at the named key * @throws \InvalidArgumentException */ - public function getConfigOrFail(string $key) + public function getConfigOrFail(string $key): mixed { $config = $this->getConfig($key); if ($config === null) { @@ -172,7 +170,7 @@ public function getConfigOrFail(string $key) * @param mixed|null $value The value to set. * @return $this */ - public function configShallow($key, $value = null) + public function configShallow(array|string $key, mixed $value = null) { if (!$this->_configInitialized) { $this->_config = $this->_defaultConfig; @@ -190,13 +188,13 @@ public function configShallow($key, $value = null) * @param string|null $key Key to read. * @return mixed */ - protected function _configRead(?string $key) + protected function _configRead(?string $key): mixed { if ($key === null) { return $this->_config; } - if (strpos($key, '.') === false) { + if (!str_contains($key, '.')) { return $this->_config[$key] ?? null; } @@ -224,7 +222,7 @@ protected function _configRead(?string $key) * @return void * @throws \Cake\Core\Exception\CakeException if attempting to clobber existing config */ - protected function _configWrite($key, $value, $merge = false): void + protected function _configWrite(array|string $key, mixed $value, string|bool $merge = false): void { if (is_string($key) && $value === null) { $this->_configDelete($key); @@ -251,7 +249,7 @@ protected function _configWrite($key, $value, $merge = false): void return; } - if (strpos($key, '.') === false) { + if (!str_contains($key, '.')) { $this->_config[$key] = $value; return; @@ -262,10 +260,10 @@ protected function _configWrite($key, $value, $merge = false): void foreach ($stack as $k) { if (!is_array($update)) { - throw new CakeException(sprintf('Cannot set %s value', $key)); + throw new CakeException(sprintf('Cannot set `%s` value.', $key)); } - $update[$k] = $update[$k] ?? []; + $update[$k] ??= []; $update = &$update[$k]; } @@ -282,7 +280,7 @@ protected function _configWrite($key, $value, $merge = false): void */ protected function _configDelete(string $key): void { - if (strpos($key, '.') === false) { + if (!str_contains($key, '.')) { unset($this->_config[$key]); return; @@ -294,7 +292,7 @@ protected function _configDelete(string $key): void foreach ($stack as $i => $k) { if (!is_array($update)) { - throw new CakeException(sprintf('Cannot unset %s value', $key)); + throw new CakeException(sprintf('Cannot unset `%s` value.', $key)); } if (!isset($update[$k])) { diff --git a/app/vendor/cakephp/cakephp/src/Core/ObjectRegistry.php b/app/vendor/cakephp/cakephp/src/Core/ObjectRegistry.php index 25b8d160a..36296af5b 100644 --- a/app/vendor/cakephp/cakephp/src/Core/ObjectRegistry.php +++ b/app/vendor/cakephp/cakephp/src/Core/ObjectRegistry.php @@ -17,11 +17,11 @@ namespace Cake\Core; use ArrayIterator; +use Cake\Core\Exception\CakeException; use Cake\Event\EventDispatcherInterface; use Cake\Event\EventListenerInterface; use Countable; use IteratorAggregate; -use RuntimeException; use Traversable; /** @@ -47,10 +47,10 @@ abstract class ObjectRegistry implements Countable, IteratorAggregate /** * Map of loaded objects. * - * @var array - * @psalm-var array + * @var array + * @phpstan-var array */ - protected $_loaded = []; + protected array $_loaded = []; /** * Loads/constructs an object instance. @@ -74,21 +74,29 @@ abstract class ObjectRegistry implements Countable, IteratorAggregate * * @param string $name The name/class of the object to load. * @param array $config Additional settings to use when loading the object. - * @return mixed - * @psalm-return TObject + * @return object + * @phpstan-return TObject * @throws \Exception If the class cannot be found. */ - public function load(string $name, array $config = []) + public function load(string $name, array $config = []): object { + $plugin = null; if (isset($config['className'])) { - $objName = $name; + if ($name === $config['className']) { + [$plugin, $objName] = pluginSplit($name); + } else { + $objName = $name; + } $name = $config['className']; } else { - [, $objName] = pluginSplit($name); + [$plugin, $objName] = pluginSplit($name); + if ($plugin) { + $config['className'] = $name; + } } $loaded = isset($this->_loaded[$objName]); - if ($loaded && !empty($config)) { + if ($loaded && $config !== []) { $this->_checkDuplicate($objName, $config); } if ($loaded) { @@ -104,10 +112,6 @@ public function load(string $name, array $config = []) } } - /** - * @psalm-var TObject $instance - * @psalm-suppress PossiblyNullArgument - **/ $instance = $this->_create($className, $objName, $config); $this->_loaded[$objName] = $instance; @@ -128,17 +132,17 @@ public function load(string $name, array $config = []) * @param string $name The name of the alias in the registry. * @param array $config The config data for the new instance. * @return void - * @throws \RuntimeException When a duplicate is found. + * @throws \Cake\Core\Exception\CakeException When a duplicate is found. */ protected function _checkDuplicate(string $name, array $config): void { $existing = $this->_loaded[$name]; - $msg = sprintf('The "%s" alias has already been loaded.', $name); + $msg = sprintf('The `%s` alias has already been loaded.', $name); $hasConfig = method_exists($existing, 'getConfig'); if (!$hasConfig) { - throw new RuntimeException($msg); + throw new CakeException($msg); } - if (empty($config)) { + if (!$config) { return; } $existingConfig = $existing->getConfig(); @@ -154,14 +158,14 @@ protected function _checkDuplicate(string $name, array $config): void $failure = sprintf( ' The `%s` key has a value of `%s` but previously had a value of `%s`', $key, - json_encode($value), - json_encode($existingConfig[$key]) + json_encode($value, JSON_THROW_ON_ERROR), + json_encode($existingConfig[$key], JSON_THROW_ON_ERROR), ); break; } } if ($failure) { - throw new RuntimeException($msg . $failure); + throw new CakeException($msg . $failure); } } @@ -169,8 +173,8 @@ protected function _checkDuplicate(string $name, array $config): void * Should resolve the classname for a given object type. * * @param string $class The class to resolve. - * @return string|null The resolved name or null for failure. - * @psalm-return class-string|null + * @return class-string|null The resolved name or null for failure. + * @phpstan-return class-string|null */ abstract protected function _resolveClassName(string $class): ?string; @@ -194,10 +198,10 @@ abstract protected function _throwMissingClassError(string $class, ?string $plug * @param string $alias The alias of the object. * @param array $config The Configuration settings for construction * @return object - * @psalm-param TObject|string $class - * @psalm-return TObject + * @phpstan-param TObject|class-string $class + * @phpstan-return TObject */ - abstract protected function _create($class, string $alias, array $config); + abstract protected function _create(object|string $class, string $alias, array $config): object; /** * Get the list of loaded objects. @@ -225,13 +229,13 @@ public function has(string $name): bool * * @param string $name Name of object. * @return object Object instance. - * @throws \RuntimeException If not loaded or found. - * @psalm-return TObject + * @throws \Cake\Core\Exception\CakeException If not loaded or found. + * @phpstan-return TObject */ - public function get(string $name) + public function get(string $name): object { if (!isset($this->_loaded[$name])) { - throw new RuntimeException(sprintf('Unknown object "%s"', $name)); + throw new CakeException(sprintf('Unknown object `%s`.', $name)); } return $this->_loaded[$name]; @@ -242,9 +246,9 @@ public function get(string $name) * * @param string $name Name of property to read * @return object|null - * @psalm-return TObject|null + * @phpstan-return TObject|null */ - public function __get(string $name) + public function __get(string $name): ?object { return $this->_loaded[$name] ?? null; } @@ -265,10 +269,10 @@ public function __isset(string $name): bool * * @param string $name Name of a property to set. * @param object $object Object to set. - * @psalm-param TObject $object + * @phpstan-param TObject $object * @return void */ - public function __set(string $name, $object): void + public function __set(string $name, object $object): void { $this->set($name, $object); } @@ -285,8 +289,8 @@ public function __unset(string $name): void } /** - * Normalizes an object array, creates an array that makes lazy loading - * easier + * Normalizes an object configuration array into associative form for making + * lazy loading easier. * * @param array $objects Array of child objects to normalize. * @return array Array of normalized objects. @@ -294,18 +298,18 @@ public function __unset(string $name): void public function normalizeArray(array $objects): array { $normal = []; - foreach ($objects as $i => $objectName) { - $config = []; - if (!is_int($i)) { - $config = (array)$objectName; - $objectName = $i; + foreach ($objects as $objectName => $config) { + if (is_int($objectName)) { + $objectName = $config; + $config = []; } - [, $name] = pluginSplit($objectName); - if (isset($config['class'])) { - $normal[$name] = $config + ['config' => []]; - } else { - $normal[$name] = ['class' => $objectName, 'config' => $config]; + + [$plugin, $name] = pluginSplit($objectName); + if ($plugin) { + $config['className'] = $objectName; } + + $normal[$name] = $config; } return $normal; @@ -336,12 +340,10 @@ public function reset() * @param string $name The name of the object to set in the registry. * @param object $object instance to store in the registry * @return $this - * @psalm-param TObject $object + * @phpstan-param TObject $object */ public function set(string $name, object $object) { - [, $objName] = pluginSplit($name); - // Just call unload if the object was loaded before if (array_key_exists($name, $this->_loaded)) { $this->unload($name); @@ -349,7 +351,7 @@ public function set(string $name, object $object) if ($this instanceof EventDispatcherInterface && $object instanceof EventListenerInterface) { $this->getEventManager()->on($object); } - $this->_loaded[$objName] = $object; + $this->_loaded[$name] = $object; return $this; } @@ -364,9 +366,8 @@ public function set(string $name, object $object) */ public function unload(string $name) { - if (empty($this->_loaded[$name])) { - [$plugin, $name] = pluginSplit($name); - $this->_throwMissingClassError($name, $plugin); + if (!isset($this->_loaded[$name])) { + throw new CakeException(sprintf('Object named `%s` is not loaded.', $name)); } $object = $this->_loaded[$name]; @@ -382,7 +383,7 @@ public function unload(string $name) * Returns an array iterator. * * @return \Traversable - * @psalm-return \Traversable + * @phpstan-return \Traversable */ public function getIterator(): Traversable { diff --git a/app/vendor/cakephp/cakephp/src/Core/Plugin.php b/app/vendor/cakephp/cakephp/src/Core/Plugin.php index 67e1a636d..a14270c22 100644 --- a/app/vendor/cakephp/cakephp/src/Core/Plugin.php +++ b/app/vendor/cakephp/cakephp/src/Core/Plugin.php @@ -21,7 +21,7 @@ * * It also can retrieve plugin paths and load their bootstrap and routes files. * - * @link https://book.cakephp.org/4/en/plugins.html + * @link https://book.cakephp.org/5/en/plugins.html */ class Plugin { @@ -30,7 +30,7 @@ class Plugin * * @var \Cake\Core\PluginCollection|null */ - protected static $plugins; + protected static ?PluginCollection $plugins = null; /** * Returns the filesystem path for a plugin @@ -127,10 +127,17 @@ public static function loaded(): array */ public static function getCollection(): PluginCollection { - if (!isset(static::$plugins)) { - static::$plugins = new PluginCollection(); - } + return static::$plugins ??= new PluginCollection(); + } - return static::$plugins; + /** + * Set the shared plugin collection. + * + * @param \Cake\Core\PluginCollection $collection + * @return void + */ + public static function setCollection(PluginCollection $collection): void + { + static::$plugins = $collection; } } diff --git a/app/vendor/cakephp/cakephp/src/Core/PluginApplicationInterface.php b/app/vendor/cakephp/cakephp/src/Core/PluginApplicationInterface.php index 0ba07714e..9312a6429 100644 --- a/app/vendor/cakephp/cakephp/src/Core/PluginApplicationInterface.php +++ b/app/vendor/cakephp/cakephp/src/Core/PluginApplicationInterface.php @@ -26,6 +26,9 @@ * * Events can be bound to the application event manager during * the application's bootstrap and plugin bootstrap. + * + * @template TSubject + * @extends \Cake\Event\EventDispatcherInterface<\Cake\Http\BaseApplication> */ interface PluginApplicationInterface extends EventDispatcherInterface { @@ -40,7 +43,7 @@ interface PluginApplicationInterface extends EventDispatcherInterface * @param array $config The configuration data for the plugin if using a string for $name * @return $this */ - public function addPlugin($name, array $config = []); + public function addPlugin(PluginInterface|string $name, array $config = []); /** * Run bootstrap logic for loaded plugins. diff --git a/app/vendor/cakephp/cakephp/src/Core/PluginCollection.php b/app/vendor/cakephp/cakephp/src/Core/PluginCollection.php index a9fc4a191..e97024ad0 100644 --- a/app/vendor/cakephp/cakephp/src/Core/PluginCollection.php +++ b/app/vendor/cakephp/cakephp/src/Core/PluginCollection.php @@ -17,6 +17,7 @@ use Cake\Core\Exception\CakeException; use Cake\Core\Exception\MissingPluginException; +use Cake\Utility\Hash; use Countable; use Generator; use InvalidArgumentException; @@ -45,28 +46,28 @@ class PluginCollection implements Iterator, Countable * * @var array<\Cake\Core\PluginInterface> */ - protected $plugins = []; + protected array $plugins = []; /** * Names of plugins * * @var array */ - protected $names = []; + protected array $names = []; /** * Iterator position stack. * * @var array */ - protected $positions = []; + protected array $positions = []; /** * Loop depth * * @var int */ - protected $loopDepth = -1; + protected int $loopDepth = -1; /** * Constructor @@ -78,36 +79,49 @@ public function __construct(array $plugins = []) foreach ($plugins as $plugin) { $this->add($plugin); } - $this->loadConfig(); + PluginConfig::loadInstallerConfig(); } /** - * Load the path information stored in vendor/cakephp-plugins.php + * Add plugins from config array. * - * This file is generated by the cakephp/plugin-installer package and used - * to locate plugins on the filesystem as applications can use `extra.plugin-paths` - * in their composer.json file to move plugin outside of vendor/ - * - * @internal + * @param array $config Configuration array. For e.g.: + * ``` + * [ + * 'Company/TestPluginThree', + * 'TestPlugin' => ['onlyDebug' => true, 'onlyCli' => true], + * 'Nope' => ['optional' => true], + * 'Named' => ['routes' => false, 'bootstrap' => false], + * ] + * ``` * @return void */ - protected function loadConfig(): void + public function addFromConfig(array $config): void { - if (Configure::check('plugins')) { - return; - } - $vendorFile = dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR . 'cakephp-plugins.php'; - if (!is_file($vendorFile)) { - $vendorFile = dirname(dirname(dirname(dirname(__DIR__)))) . DIRECTORY_SEPARATOR . 'cakephp-plugins.php'; - if (!is_file($vendorFile)) { - Configure::write(['plugins' => []]); + $notDebug = !Configure::read('debug'); + $notCli = PHP_SAPI !== 'cli'; + + foreach (Hash::normalize($config, default: []) as $name => $options) { + $onlyDebug = $options['onlyDebug'] ?? false; + $onlyCli = $options['onlyCli'] ?? false; + $optional = $options['optional'] ?? false; + + if ( + ($onlyDebug && $notDebug) + || ($onlyCli && $notCli) + ) { + continue; + } - return; + try { + $plugin = $this->create($name, $options); + $this->add($plugin); + } catch (MissingPluginException $e) { + if (!$optional) { + throw $e; + } } } - - $config = require $vendorFile; - Configure::write($config); } /** @@ -128,7 +142,7 @@ public function findPath(string $name): string // Ensure plugin config is loaded each time. This is necessary primarily // for testing because the Configure::clear() call in TestCase::tearDown() // wipes out all configuration including plugin paths config. - $this->loadConfig(); + PluginConfig::loadInstallerConfig(); $path = Configure::read('plugins.' . $name); if ($path) { @@ -157,6 +171,10 @@ public function findPath(string $name): string public function add(PluginInterface $plugin) { $name = $plugin->getName(); + if (isset($this->plugins[$name])) { + throw new CakeException(sprintf('Plugin named `%s` is already loaded', $name)); + } + $this->plugins[$name] = $plugin; $this->names = array_keys($this->plugins); @@ -232,6 +250,8 @@ public function get(string $name): PluginInterface * @param array $config Configuration options for the plugin. * @return \Cake\Core\PluginInterface * @throws \Cake\Core\Exception\MissingPluginException When plugin instance could not be created. + * @throws \InvalidArgumentException When class name cannot be found. + * @phpstan-param class-string<\Cake\Core\PluginInterface>|string $name */ public function create(string $name, array $config = []): PluginInterface { @@ -239,7 +259,11 @@ public function create(string $name, array $config = []): PluginInterface throw new CakeException('Cannot create a plugin with empty name'); } - if (strpos($name, '\\') !== false) { + if (str_contains($name, '\\')) { + if (!class_exists($name)) { + throw new InvalidArgumentException(sprintf('Class `%s` does not exist.', $name)); + } + /** @var \Cake\Core\PluginInterface */ return new $name($config); } @@ -352,7 +376,7 @@ public function valid(): bool public function with(string $hook): Generator { if (!in_array($hook, PluginInterface::VALID_HOOKS, true)) { - throw new InvalidArgumentException("The `{$hook}` hook is not a known plugin hook."); + throw new InvalidArgumentException(sprintf('The `%s` hook is not a known plugin hook.', $hook)); } foreach ($this as $plugin) { if ($plugin->isEnabled($hook)) { diff --git a/app/vendor/cakephp/cakephp/src/Core/PluginConfig.php b/app/vendor/cakephp/cakephp/src/Core/PluginConfig.php new file mode 100644 index 000000000..db9fb8118 --- /dev/null +++ b/app/vendor/cakephp/cakephp/src/Core/PluginConfig.php @@ -0,0 +1,187 @@ + []]); + + return; + } + } + + $config = require $vendorFile; + Configure::write($config); + } + + /** + * Get the config how plugins should be loaded + * + * @param string|null $path The absolute path to the composer.lock file to retrieve the versions from + * @return array + */ + public static function getAppConfig(?string $path = null): array + { + self::loadInstallerConfig(); + + // phpcs:ignore + $pluginLoadConfig = @include CONFIG . 'plugins.php'; + if (is_array($pluginLoadConfig)) { + $pluginLoadConfig = Hash::normalize($pluginLoadConfig); + } else { + $pluginLoadConfig = []; + } + + try { + $composerVersions = self::getVersions($path); + } catch (CakeException) { + $composerVersions = []; + } + + $result = []; + $availablePlugins = Configure::read('plugins', []); + if ($availablePlugins && is_array($availablePlugins)) { + foreach ($availablePlugins as $pluginName => $pluginPath) { + if ($pluginLoadConfig && array_key_exists($pluginName, $pluginLoadConfig)) { + $options = $pluginLoadConfig[$pluginName]; + $hooks = PluginInterface::VALID_HOOKS; + $mainConfig = [ + 'isLoaded' => true, + 'onlyDebug' => $options['onlyDebug'] ?? false, + 'onlyCli' => $options['onlyCli'] ?? false, + 'optional' => $options['optional'] ?? false, + ]; + foreach ($hooks as $hook) { + $mainConfig[$hook] = $options[$hook] ?? true; + } + $result[$pluginName] = $mainConfig; + } else { + $result[$pluginName]['isLoaded'] = false; + } + + try { + $packageName = self::getPackageNameFromPath($pluginPath); + $result[$pluginName]['packagePath'] = $pluginPath; + $result[$pluginName]['package'] = $packageName; + } catch (CakeException) { + $packageName = null; + } + if ($composerVersions && $packageName) { + if (array_key_exists($packageName, $composerVersions['packages'])) { + $result[$pluginName]['version'] = $composerVersions['packages'][$packageName]; + $result[$pluginName]['isDevPackage'] = false; + } elseif (array_key_exists($packageName, $composerVersions['devPackages'])) { + $result[$pluginName]['version'] = $composerVersions['devPackages'][$packageName]; + $result[$pluginName]['isDevPackage'] = true; + } + } + } + } + + $diff = array_diff(array_keys($pluginLoadConfig), array_keys($availablePlugins)); + foreach ($diff as $unknownPlugin) { + $result[$unknownPlugin]['isLoaded'] = false; + $result[$unknownPlugin]['isUnknown'] = true; + } + + return $result; + } + + /** + * @param string|null $path The absolute path to the composer.lock file to retrieve the versions from + * @return array + */ + public static function getVersions(?string $path = null): array + { + $lockFilePath = $path ?? ROOT . DIRECTORY_SEPARATOR . 'composer.lock'; + if (!file_exists($lockFilePath)) { + throw new CakeException(sprintf('composer.lock does not exist in %s', $lockFilePath)); + } + $lockFile = file_get_contents($lockFilePath); + if ($lockFile === false) { + throw new CakeException(sprintf('Could not read composer.lock: %s', $lockFilePath)); + } + $lockFileJson = json_decode($lockFile, true); + if (json_last_error() !== JSON_ERROR_NONE) { + throw new CakeException(sprintf( + 'Error parsing composer.lock: %s', + json_last_error_msg(), + )); + } + + $packages = Hash::combine($lockFileJson['packages'], '{n}.name', '{n}.version'); + $devPackages = Hash::combine($lockFileJson['packages-dev'], '{n}.name', '{n}.version'); + + return [ + 'packages' => $packages, + 'devPackages' => $devPackages, + ]; + } + + /** + * @param string $path + * @return string + */ + protected static function getPackageNameFromPath(string $path): string + { + $jsonPath = $path . DS . 'composer.json'; + if (!file_exists($jsonPath)) { + throw new CakeException(sprintf('composer.json does not exist in %s', $jsonPath)); + } + $jsonString = file_get_contents($jsonPath); + if ($jsonString === false) { + throw new CakeException(sprintf('Could not read composer.json: %s', $jsonPath)); + } + $json = json_decode($jsonString, true); + if (json_last_error() !== JSON_ERROR_NONE) { + throw new CakeException(sprintf( + 'Error parsing %ss: %s', + $jsonPath, + json_last_error_msg(), + )); + } + + return $json['name']; + } +} diff --git a/app/vendor/cakephp/cakephp/src/Core/PluginInterface.php b/app/vendor/cakephp/cakephp/src/Core/PluginInterface.php index e251cdf59..3f7c3f7d6 100644 --- a/app/vendor/cakephp/cakephp/src/Core/PluginInterface.php +++ b/app/vendor/cakephp/cakephp/src/Core/PluginInterface.php @@ -20,9 +20,6 @@ /** * Plugin Interface - * - * @method void services(\Cake\Core\ContainerInterface $container) Register plugin services to - * the application's container */ interface PluginInterface { @@ -31,7 +28,7 @@ interface PluginInterface * * @var array */ - public const VALID_HOOKS = ['bootstrap', 'console', 'middleware', 'routes', 'services']; + public const VALID_HOOKS = ['bootstrap', 'console', 'middleware', 'routes', 'services', 'events']; /** * Get the name of this plugin. @@ -109,6 +106,14 @@ public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue; */ public function routes(RouteBuilder $routes): void; + /** + * Register plugin services to the application's container + * + * @param \Cake\Core\ContainerInterface $container Container instance. + * @return void + */ + public function services(ContainerInterface $container): void; + /** * Disables the named hook * diff --git a/app/vendor/cakephp/cakephp/src/Core/README.md b/app/vendor/cakephp/cakephp/src/Core/README.md index f26cba696..12d115f52 100644 --- a/app/vendor/cakephp/cakephp/src/Core/README.md +++ b/app/vendor/cakephp/cakephp/src/Core/README.md @@ -34,4 +34,4 @@ Configure::dump('my_config', 'default'); ## Documentation -Please make sure you check the [official documentation](https://book.cakephp.org/4/en/development/configuration.html) +Please make sure you check the [official documentation](https://book.cakephp.org/5/en/development/configuration.html) diff --git a/app/vendor/cakephp/cakephp/src/Core/Retry/CommandRetry.php b/app/vendor/cakephp/cakephp/src/Core/Retry/CommandRetry.php index 1211695d7..e7ab321f4 100644 --- a/app/vendor/cakephp/cakephp/src/Core/Retry/CommandRetry.php +++ b/app/vendor/cakephp/cakephp/src/Core/Retry/CommandRetry.php @@ -16,6 +16,7 @@ */ namespace Cake\Core\Retry; +use Closure; use Exception; /** @@ -31,17 +32,17 @@ class CommandRetry * * @var \Cake\Core\Retry\RetryStrategyInterface */ - protected $strategy; + protected RetryStrategyInterface $strategy; /** * @var int */ - protected $maxRetries; + protected int $maxRetries; /** * @var int */ - protected $numRetries; + protected int $numRetries; /** * Creates the CommandRetry object with the given strategy and retry count @@ -58,11 +59,11 @@ public function __construct(RetryStrategyInterface $strategy, int $maxRetries = /** * The number of retries to perform in case of failure * - * @param callable $action The callable action to execute with a retry strategy + * @param \Closure $action Callback to run for each attempt * @return mixed The return value of the passed action callable - * @throws \Exception + * @throws \Exception Throws exception from last failure */ - public function run(callable $action) + public function run(Closure $action): mixed { $this->numRetries = 0; while (true) { @@ -83,7 +84,7 @@ public function run(callable $action) } /** - * Returns the last number of retry attemps. + * Returns the last number of retry attempts. * * @return int */ diff --git a/app/vendor/cakephp/cakephp/src/Core/ServiceConfig.php b/app/vendor/cakephp/cakephp/src/Core/ServiceConfig.php index 6eac909f6..aac11abf7 100644 --- a/app/vendor/cakephp/cakephp/src/Core/ServiceConfig.php +++ b/app/vendor/cakephp/cakephp/src/Core/ServiceConfig.php @@ -32,7 +32,7 @@ class ServiceConfig * @param mixed $default The default value to use if $path does not exist. * @return mixed The configuration data or $default value. */ - public function get(string $path, $default = null) + public function get(string $path, mixed $default = null): mixed { return Configure::read($path, $default); } diff --git a/app/vendor/cakephp/cakephp/src/Core/ServiceProvider.php b/app/vendor/cakephp/cakephp/src/Core/ServiceProvider.php index 8ff9b9cae..4c1c797da 100644 --- a/app/vendor/cakephp/cakephp/src/Core/ServiceProvider.php +++ b/app/vendor/cakephp/cakephp/src/Core/ServiceProvider.php @@ -19,7 +19,7 @@ use League\Container\DefinitionContainerInterface; use League\Container\ServiceProvider\AbstractServiceProvider; use League\Container\ServiceProvider\BootableServiceProviderInterface; -use RuntimeException; +use LogicException; /** * Container ServiceProvider @@ -37,28 +37,25 @@ abstract class ServiceProvider extends AbstractServiceProvider implements Bootab * @var array * @see ServiceProvider::provides() */ - protected $provides = []; + protected array $provides = []; /** * Get the container. * - * This method's actual return type and documented return type differ - * because PHP 7.2 doesn't support return type narrowing. - * * @return \Cake\Core\ContainerInterface */ public function getContainer(): DefinitionContainerInterface { $container = parent::getContainer(); - if (!($container instanceof ContainerInterface)) { - $message = sprintf( + assert( + $container instanceof ContainerInterface, + sprintf( 'Unexpected container type. Expected `%s` got `%s` instead.', ContainerInterface::class, - getTypeName($container) - ); - throw new RuntimeException($message); - } + get_debug_type($container), + ), + ); return $container; } @@ -108,7 +105,7 @@ public function register(): void * The provides method is a way to let the container know that a service * is provided by this service provider. * - * Every service that is registered via this service provider must have an + * Every service registered via this service provider must have an * alias added to this array or it will be ignored. * * @param string $id Identifier. @@ -116,6 +113,12 @@ public function register(): void */ public function provides(string $id): bool { + if (!$this->provides) { + throw new LogicException( + 'The property `$provides` should contain a list with service ids for this service provider', + ); + } + return in_array($id, $this->provides, true); } diff --git a/app/vendor/cakephp/cakephp/src/Core/StaticConfigTrait.php b/app/vendor/cakephp/cakephp/src/Core/StaticConfigTrait.php index 2db862427..12843c981 100644 --- a/app/vendor/cakephp/cakephp/src/Core/StaticConfigTrait.php +++ b/app/vendor/cakephp/cakephp/src/Core/StaticConfigTrait.php @@ -32,9 +32,9 @@ trait StaticConfigTrait /** * Configuration sets. * - * @var array + * @var array> */ - protected static $_config = []; + protected static array $_config = []; /** * This method can be used to define configuration adapters for an application. @@ -73,22 +73,24 @@ trait StaticConfigTrait * @throws \LogicException When trying to store an invalid structured config array. * @return void */ - public static function setConfig($key, $config = null): void + public static function setConfig(array|string $key, mixed $config = null): void { if ($config === null) { if (!is_array($key)) { throw new LogicException('If config is null, key must be an array.'); } foreach ($key as $name => $settings) { - static::setConfig($name, $settings); + static::setConfig((string)$name, $settings); } return; } + if (!is_string($key)) { + throw new LogicException('If config is not null, key must be a string.'); + } if (isset(static::$_config[$key])) { - /** @psalm-suppress PossiblyInvalidArgument */ - throw new BadMethodCallException(sprintf('Cannot reconfigure existing key "%s"', $key)); + throw new BadMethodCallException(sprintf('Cannot reconfigure existing key `%s`.', $key)); } if (is_object($config)) { @@ -105,7 +107,7 @@ public static function setConfig($key, $config = null): void $config['className'] = $config['engine']; unset($config['engine']); } - /** @psalm-suppress InvalidPropertyAssignmentValue */ + static::$_config[$key] = $config; } @@ -115,7 +117,7 @@ public static function setConfig($key, $config = null): void * @param string $key The name of the configuration. * @return mixed|null Configuration data at the named key or null if the key does not exist. */ - public static function getConfig(string $key) + public static function getConfig(string $key): mixed { return static::$_config[$key] ?? null; } @@ -129,7 +131,7 @@ public static function getConfig(string $key) * @return mixed Configuration data at the named key. * @throws \InvalidArgumentException If value does not exist. */ - public static function getConfigOrFail(string $key) + public static function getConfigOrFail(string $key): mixed { if (!isset(static::$_config[$key])) { throw new InvalidArgumentException(sprintf('Expected configuration `%s` not found.', $key)); @@ -155,7 +157,7 @@ public static function drop(string $config): bool if (!isset(static::$_config[$config])) { return false; } - /** @psalm-suppress RedundantPropertyInitializationCheck */ + /** @phpstan-ignore-next-line */ if (isset(static::$_registry)) { static::$_registry->unload($config); } @@ -197,7 +199,7 @@ public static function configured(): array * $dsn = 'file:///?className=\My\Cache\Engine\FileEngine'; * $config = Cache::parseDsn($dsn); * - * $dsn = 'File://?prefix=myapp_cake_core_&serialize=true&duration=+2 minutes&path=/tmp/persistent/'; + * $dsn = 'File://?prefix=myapp_cake_translations_&serialize=true&duration=+2 minutes&path=/tmp/persistent/'; * $config = Cache::parseDsn($dsn); * ``` * @@ -207,12 +209,12 @@ public static function configured(): array * Note that querystring arguments are also parsed and set as values in the returned configuration. * * @param string $dsn The DSN string to convert to a configuration array - * @return array The configuration array to be stored after parsing the DSN + * @return array The configuration array to be stored after parsing the DSN * @throws \InvalidArgumentException If not passed a string, or passed an invalid string */ public static function parseDsn(string $dsn): array { - if (empty($dsn)) { + if (!$dsn) { return []; } @@ -230,7 +232,7 @@ public static function parseDsn(string $dsn): array @ )? (?P<_host> - (?P[^?#/:@]+) + (?P\[[^]]+]|[^?#/:@]+) (?P<_port> :(?P\d+) )? @@ -251,14 +253,17 @@ public static function parseDsn(string $dsn): array preg_match($pattern, $dsn, $parsed); if (!$parsed) { - throw new InvalidArgumentException("The DSN string '{$dsn}' could not be parsed."); + throw new InvalidArgumentException(sprintf('The DSN string `%s` could not be parsed.', $dsn)); } $exists = []; + /** + * @var string|int $k + */ foreach ($parsed as $k => $v) { if (is_int($k)) { unset($parsed[$k]); - } elseif (strpos($k, '_') === 0) { + } elseif (str_starts_with($k, '_')) { $exists[substr($k, 1)] = ($v !== ''); unset($parsed[$k]); } elseif ($v === '' && !$exists[$k]) { @@ -275,6 +280,9 @@ public static function parseDsn(string $dsn): array parse_str($query, $queryArgs); + /** + * @var string $key + */ foreach ($queryArgs as $key => $value) { if ($value === 'true') { $queryArgs[$key] = true; @@ -290,10 +298,11 @@ public static function parseDsn(string $dsn): array if (empty($parsed['className'])) { $classMap = static::getDsnClassMap(); - $parsed['className'] = $parsed['scheme']; - if (isset($classMap[$parsed['scheme']])) { - /** @psalm-suppress PossiblyNullArrayOffset */ - $parsed['className'] = $classMap[$parsed['scheme']]; + /** @var string $scheme */ + $scheme = $parsed['scheme']; + $parsed['className'] = $scheme; + if (isset($classMap[$scheme])) { + $parsed['className'] = $classMap[$scheme]; } } @@ -305,7 +314,7 @@ public static function parseDsn(string $dsn): array * * @param array $map Additions/edits to the class map to apply. * @return void - * @psalm-param array $map + * @phpstan-param array $map */ public static function setDsnClassMap(array $map): void { @@ -315,8 +324,7 @@ public static function setDsnClassMap(array $map): void /** * Returns the DSN class map for this class. * - * @return array - * @psalm-return array + * @return array */ public static function getDsnClassMap(): array { diff --git a/app/vendor/cakephp/cakephp/src/Core/TestSuite/ContainerStubTrait.php b/app/vendor/cakephp/cakephp/src/Core/TestSuite/ContainerStubTrait.php index 9c6ae1e05..b59dc846a 100644 --- a/app/vendor/cakephp/cakephp/src/Core/TestSuite/ContainerStubTrait.php +++ b/app/vendor/cakephp/cakephp/src/Core/TestSuite/ContainerStubTrait.php @@ -16,11 +16,15 @@ namespace Cake\Core\TestSuite; use Cake\Core\Configure; +use Cake\Core\ConsoleApplicationInterface; use Cake\Core\ContainerInterface; +use Cake\Core\HttpApplicationInterface; use Cake\Event\EventInterface; +use Cake\Routing\Router; use Closure; use League\Container\Exception\NotFoundException; use LogicException; +use PHPUnit\Framework\Attributes\After; /** * A set of methods used for defining container services @@ -35,24 +39,24 @@ trait ContainerStubTrait /** * The customized application class name. * - * @psalm-var class-string<\Cake\Core\HttpApplicationInterface>|class-string<\Cake\Core\ConsoleApplicationInterface>|null + * @phpstan-var class-string<\Cake\Core\HttpApplicationInterface>|class-string<\Cake\Core\ConsoleApplicationInterface>|null * @var string|null */ - protected $_appClass; + protected ?string $_appClass = null; /** * The customized application constructor arguments. * * @var array|null */ - protected $_appArgs; + protected ?array $_appArgs = null; /** * The collection of container services. * - * @var array + * @var array */ - private $containerServices = []; + private array $containerServices = []; /** * Configure the application class to use in integration tests. @@ -60,7 +64,7 @@ trait ContainerStubTrait * @param string $class The application class name. * @param array|null $constructorArgs The constructor arguments for your application class. * @return void - * @psalm-param class-string<\Cake\Core\HttpApplicationInterface>|class-string<\Cake\Core\ConsoleApplicationInterface> $class + * @phpstan-param class-string<\Cake\Core\HttpApplicationInterface>|class-string<\Cake\Core\ConsoleApplicationInterface> $class */ public function configApplication(string $class, ?array $constructorArgs): void { @@ -75,24 +79,36 @@ public function configApplication(string $class, ?array $constructorArgs): void * * @return \Cake\Core\HttpApplicationInterface|\Cake\Core\ConsoleApplicationInterface */ - protected function createApp() + protected function createApp(): HttpApplicationInterface|ConsoleApplicationInterface { + if (class_exists(Router::class)) { + Router::resetRoutes(); + } + if ($this->_appClass) { $appClass = $this->_appClass; } else { - /** @psalm-var class-string<\Cake\Http\BaseApplication> */ + /** @var class-string<\Cake\Http\BaseApplication> $appClass */ $appClass = Configure::read('App.namespace') . '\Application'; } if (!class_exists($appClass)) { - throw new LogicException("Cannot load `{$appClass}` for use in integration testing."); + throw new LogicException(sprintf('Cannot load `%s` for use in integration testing.', $appClass)); } $appArgs = $this->_appArgs ?: [CONFIG]; $app = new $appClass(...$appArgs); - if (!empty($this->containerServices) && method_exists($app, 'getEventManager')) { + if ($this->containerServices && method_exists($app, 'getEventManager')) { $app->getEventManager()->on('Application.buildContainer', [$this, 'modifyContainer']); } + foreach ($this->appPluginsToLoad as $pluginName => $config) { + if (is_array($config)) { + $app->addPlugin($pluginName, $config); + } else { + $app->addPlugin($config); + } + } + return $app; } @@ -136,18 +152,18 @@ public function removeMockService(string $class) * * @param \Cake\Event\EventInterface $event The event * @param \Cake\Core\ContainerInterface $container The container to wrap. - * @return \Cake\Core\ContainerInterface|null + * @return void */ - public function modifyContainer(EventInterface $event, ContainerInterface $container): ?ContainerInterface + public function modifyContainer(EventInterface $event, ContainerInterface $container): void { - if (empty($this->containerServices)) { - return null; + if (!$this->containerServices) { + return; } foreach ($this->containerServices as $key => $factory) { if ($container->has($key)) { try { $container->extend($key)->setConcrete($factory); - } catch (NotFoundException $e) { + } catch (NotFoundException) { $container->add($key, $factory); } } else { @@ -155,16 +171,16 @@ public function modifyContainer(EventInterface $event, ContainerInterface $conta } } - return $container; + $event->setResult($container); } /** * Clears any mocks that were defined and cleans * up application class configuration. * - * @after * @return void */ + #[After] public function cleanupContainer(): void { $this->_appArgs = null; diff --git a/app/vendor/cakephp/cakephp/src/Core/composer.json b/app/vendor/cakephp/cakephp/src/Core/composer.json index d5d4cf761..55812cf18 100644 --- a/app/vendor/cakephp/cakephp/src/Core/composer.json +++ b/app/vendor/cakephp/cakephp/src/Core/composer.json @@ -22,16 +22,10 @@ "source": "https://github.com/cakephp/core" }, "require": { - "php": ">=7.4.0", - "cakephp/utility": "^4.0" - }, - "provide": { - "psr/container-implementation": "^1.0 || ^2.0" - }, - "suggest": { - "cakephp/event": "To use PluginApplicationInterface or plugin applications.", - "cakephp/cache": "To use Configure::store() and restore().", - "league/container": "To use Container and ServiceProvider classes" + "php": ">=8.1", + "cakephp/utility": "5.2.*@dev", + "league/container": "^4.2", + "psr/container": "^1.1 || ^2.0" }, "autoload": { "psr-4": { @@ -40,5 +34,19 @@ "files": [ "functions.php" ] + }, + "provide": { + "psr/container-implementation": "^2.0" + }, + "suggest": { + "cakephp/event": "To use PluginApplicationInterface or plugin applications.", + "cakephp/cache": "To use Configure::store() and restore().", + "league/container": "To use Container and ServiceProvider classes" + }, + "prefer-stable": true, + "extra": { + "branch-alias": { + "dev-5.x": "5.2.x-dev" + } } } diff --git a/app/vendor/cakephp/cakephp/src/Core/functions.php b/app/vendor/cakephp/cakephp/src/Core/functions.php index af2d2b6c3..fd9e99866 100644 --- a/app/vendor/cakephp/cakephp/src/Core/functions.php +++ b/app/vendor/cakephp/cakephp/src/Core/functions.php @@ -11,12 +11,83 @@ * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://cakephp.org CakePHP(tm) Project - * @since 4.5.0 + * @since 3.0.0 * @license https://opensource.org/licenses/mit-license.php MIT License */ -// phpcs:disable PSR1.Files.SideEffects namespace Cake\Core; +use JsonException; +use Stringable; + +if (!defined('DS')) { + /** + * Defines DS as short form of DIRECTORY_SEPARATOR. + */ + define('DS', DIRECTORY_SEPARATOR); +} + +if (!defined('CAKE_DATE_RFC7231')) { + define('CAKE_DATE_RFC7231', 'D, d M Y H:i:s \G\M\T'); +} + +if (!function_exists('Cake\Core\pathCombine')) { + /** + * Combines parts with a forward-slash `/`. + * + * Skips adding a forward-slash if either `/` or `\` already exists. + * + * @param array $parts + * @param bool|null $trailing Determines how trailing slashes are handled + * - If true, ensures a trailing forward-slash is added if one doesn't exist + * - If false, ensures any trailing slash is removed + * - if null, ignores trailing slashes + * @return string + */ + function pathCombine(array $parts, ?bool $trailing = null): string + { + $numParts = count($parts); + if ($numParts === 0) { + if ($trailing === true) { + return '/'; + } + + return ''; + } + + $path = $parts[0]; + for ($i = 1; $i < $numParts; ++$i) { + $part = $parts[$i]; + if ($part === '') { + continue; + } + + if ($path[-1] === '/' || $path[-1] === '\\') { + if ($part[0] === '/' || $part[0] === '\\') { + $path .= substr($part, 1); + } else { + $path .= $part; + } + } elseif ($part[0] === '/' || $part[0] === '\\') { + $path .= $part; + } else { + $path .= '/' . $part; + } + } + + if ($trailing === true) { + if ($path === '' || ($path[-1] !== '/' && $path[-1] !== '\\')) { + $path .= '/'; + } + } elseif ($trailing === false) { + if ($path !== '' && ($path[-1] === '/' || $path[-1] === '\\')) { + $path = substr($path, 0, -1); + } + } + + return $path; + } +} + if (!function_exists('Cake\Core\h')) { /** * Convenience method for htmlspecialchars. @@ -29,9 +100,9 @@ * @param string|null $charset Character set to use when escaping. * Defaults to config value in `mb_internal_encoding()` or 'UTF-8'. * @return mixed Wrapped text. - * @link https://book.cakephp.org/4/en/core-libraries/global-constants-and-functions.html#h + * @link https://book.cakephp.org/5/en/core-libraries/global-constants-and-functions.html#h */ - function h($text, bool $double = true, ?string $charset = null) + function h(mixed $text, bool $double = true, ?string $charset = null): mixed { if (is_string($text)) { //optimize for strings @@ -43,10 +114,10 @@ function h($text, bool $double = true, ?string $charset = null) return $texts; } elseif (is_object($text)) { - if (method_exists($text, '__toString')) { - $text = $text->__toString(); + if ($text instanceof Stringable) { + $text = (string)$text; } else { - $text = '(object)' . get_class($text); + $text = '(object)' . $text::class; } } elseif ($text === null || is_scalar($text)) { return $text; @@ -75,18 +146,18 @@ function h($text, bool $double = true, ?string $charset = null) * @param bool $dotAppend Set to true if you want the plugin to have a '.' appended to it. * @param string|null $plugin Optional default plugin to use if no plugin is found. Defaults to null. * @return array Array with 2 indexes. 0 => plugin name, 1 => class name. - * @link https://book.cakephp.org/4/en/core-libraries/global-constants-and-functions.html#pluginSplit - * @psalm-return array{string|null, string} + * @link https://book.cakephp.org/5/en/core-libraries/global-constants-and-functions.html#pluginSplit + * @phpstan-return array{string|null, string} */ function pluginSplit(string $name, bool $dotAppend = false, ?string $plugin = null): array { - if (strpos($name, '.') !== false) { + if (str_contains($name, '.')) { $parts = explode('.', $name, 2); if ($dotAppend) { $parts[0] .= '.'; } - /** @psalm-var array{string, string} */ + /** @phpstan-var array{string, string} */ return $parts; } @@ -101,7 +172,7 @@ function pluginSplit(string $name, bool $dotAppend = false, ?string $plugin = nu * Commonly used like `list($namespace, $className) = namespaceSplit($class);`. * * @param string $class The full class name, ie `Cake\Core\App`. - * @return array Array with 2 indexes. 0 => namespace, 1 => classname. + * @return array{0: string, 1: string} Array with 2 indexes. 0 => namespace, 1 => classname. */ function namespaceSplit(string $class): array { @@ -125,10 +196,10 @@ function namespaceSplit(string $class): array * * @param mixed $var Variable to print out. * @return mixed the same $var that was passed to this function - * @link https://book.cakephp.org/4/en/core-libraries/global-constants-and-functions.html#pr + * @link https://book.cakephp.org/5/en/core-libraries/global-constants-and-functions.html#pr * @see debug() */ - function pr($var) + function pr(mixed $var): mixed { if (!Configure::read('debug')) { return $var; @@ -153,16 +224,17 @@ function pr($var) * @param mixed $var Variable to print out. * @return mixed the same $var that was passed to this function * @see pr() - * @link https://book.cakephp.org/4/en/core-libraries/global-constants-and-functions.html#pj + * @link https://book.cakephp.org/5/en/core-libraries/global-constants-and-functions.html#pj */ - function pj($var) + function pj(mixed $var): mixed { if (!Configure::read('debug')) { return $var; } $template = PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg' ? '
%s
' : "\n%s\n\n"; - printf($template, trim(json_encode($var, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES))); + $flags = JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES; + printf($template, trim((string)json_encode($var, $flags))); return $var; } @@ -177,28 +249,27 @@ function pj($var) * * @param string $key Environment variable name. * @param string|bool|null $default Specify a default value in case the environment variable is not defined. - * @return string|bool|null Environment variable setting. - * @link https://book.cakephp.org/4/en/core-libraries/global-constants-and-functions.html#env + * @return string|float|int|bool|null Environment variable setting. + * @link https://book.cakephp.org/5/en/core-libraries/global-constants-and-functions.html#env */ - function env(string $key, $default = null) + function env(string $key, string|float|int|bool|null $default = null): string|float|int|bool|null { if ($key === 'HTTPS') { if (isset($_SERVER['HTTPS'])) { return !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off'; } - return strpos((string)env('SCRIPT_URI'), 'https://') === 0; + return str_starts_with((string)env('SCRIPT_URI'), 'https://'); } if ($key === 'SCRIPT_NAME' && env('CGI_MODE') && isset($_ENV['SCRIPT_URL'])) { $key = 'SCRIPT_URL'; } - /** @var string|null $val */ $val = $_SERVER[$key] ?? $_ENV[$key] ?? null; + assert($val === null || is_scalar($val)); if ($val == null && getenv($key) !== false) { - /** @var string|false $val */ - $val = getenv($key); + $val = (string)getenv($key); } if ($key === 'REMOTE_ADDR' && $val === env('SERVER_ADDR')) { @@ -217,7 +288,7 @@ function env(string $key, $default = null) $name = (string)env('SCRIPT_NAME'); $filename = (string)env('SCRIPT_FILENAME'); $offset = 0; - if (!strpos($name, '.php')) { + if (!str_ends_with($name, '.php')) { $offset = 4; } @@ -241,17 +312,6 @@ function env(string $key, $default = null) */ function triggerWarning(string $message): void { - $trace = debug_backtrace(); - if (isset($trace[1])) { - $frame = $trace[1]; - $frame += ['file' => '[internal]', 'line' => '??']; - $message = sprintf( - '%s - %s, line: %s', - $message, - $frame['file'], - $frame['line'] - ); - } trigger_error($message, E_USER_WARNING); } } @@ -260,12 +320,13 @@ function triggerWarning(string $message): void /** * Helper method for outputting deprecation warnings * + * @param string $version The version that added this deprecation warning. * @param string $message The message to output as a deprecation warning. * @param int $stackFrame The stack frame to include in the error. Defaults to 1 * as that should point to application/plugin code. * @return void */ - function deprecationWarning(string $message, int $stackFrame = 1): void + function deprecationWarning(string $version, string $message, int $stackFrame = 1): void { if (!(error_reporting() & E_USER_DEPRECATED)) { return; @@ -281,11 +342,7 @@ function deprecationWarning(string $message, int $stackFrame = 1): void if (defined('ROOT')) { $root = ROOT; } - $relative = str_replace( - DIRECTORY_SEPARATOR, - '/', - substr($frame['file'], strlen($root) + 1) - ); + $relative = str_replace(DIRECTORY_SEPARATOR, '/', substr($frame['file'], strlen($root) + 1)); $patterns = (array)Configure::read('Error.ignoredDeprecationPaths'); foreach ($patterns as $pattern) { $pattern = str_replace(DIRECTORY_SEPARATOR, '/', $pattern); @@ -295,18 +352,20 @@ function deprecationWarning(string $message, int $stackFrame = 1): void } $message = sprintf( - "%s\n%s, line: %s\n" . 'You can disable all deprecation warnings by setting `Error.errorLevel` to ' . + "Since %s: %s\n%s, line: %s\n" . + 'You can disable all deprecation warnings by setting `Error.errorLevel` to ' . '`E_ALL & ~E_USER_DEPRECATED`. Adding `%s` to `Error.ignoredDeprecationPaths` ' . 'in your `config/app.php` config will mute deprecations from that file only.', + $version, $message, $frame['file'], $frame['line'], - $relative + $relative, ); } static $errors = []; - $checksum = md5($message); + $checksum = hash('xxh128', $message); $duplicate = (bool)Configure::read('Error.allowDuplicateDeprecations', false); if (isset($errors[$checksum]) && !$duplicate) { return; @@ -319,22 +378,162 @@ function deprecationWarning(string $message, int $stackFrame = 1): void } } -if (!function_exists('Cake\Core\getTypeName')) { +if (!function_exists('Cake\Core\toString')) { /** - * Returns the objects class or var type of it's not an object + * Converts the given value to a string. * - * @param mixed $var Variable to check - * @return string Returns the class name or variable type + * This method attempts to convert the given value to a string. + * If the value is already a string, it returns the value as it is. + * ``null`` is returned if the conversion is not possible. + * + * @param mixed $value The value to be converted. + * @return ?string Returns the string representation of the value, or null if the value is not a string. + * @since 5.1.0 */ - function getTypeName($var): string + function toString(mixed $value): ?string { - return is_object($var) ? get_class($var) : gettype($var); + if (is_string($value)) { + return $value; + } + if (is_int($value)) { + return (string)$value; + } + if (is_bool($value)) { + return $value ? '1' : '0'; + } + if (is_float($value)) { + if (is_nan($value) || is_infinite($value)) { + return null; + } + try { + $return = json_encode($value, JSON_THROW_ON_ERROR); + } catch (JsonException) { + $return = null; + } + + if ($return === null || str_contains($return, 'e')) { + return rtrim(sprintf('%.' . (PHP_FLOAT_DIG + 3) . 'F', $value), '.0'); + } + + return $return; + } + if ($value instanceof Stringable) { + return (string)$value; + } + + return null; } } -/** - * Include global functions. - */ -if (!getenv('CAKE_DISABLE_GLOBAL_FUNCS')) { - include 'functions_global.php'; +if (!function_exists('Cake\Core\toInt')) { + /** + * Converts a value to an integer. + * + * This method attempts to convert the given value to an integer. + * If the conversion is successful, it returns the value as an integer. + * If the conversion fails, it returns NULL. + * + * String values are trimmed using trim(). + * + * @param mixed $value The value to be converted to an integer. + * @return int|null Returns the converted integer value or null if the conversion fails. + * @since 5.1.0 + */ + function toInt(mixed $value): ?int + { + if (is_int($value)) { + return $value; + } + if (is_string($value)) { + $value = trim($value); + if (preg_match('/^0+[^0]{1}/', $value)) { + $value = ltrim($value, '0'); + } + + $value = filter_var($value, FILTER_VALIDATE_INT, FILTER_NULL_ON_FAILURE); + + return $value === PHP_INT_MIN ? null : $value; + } + if (is_float($value)) { + if (is_nan($value) || is_infinite($value)) { + return null; + } + + return (int)$value; + } + if (is_bool($value)) { + return (int)$value; + } + + return null; + } +} + +if (!function_exists('Cake\Core\toFloat')) { + /** + * Converts a value to a float. + * + * This method attempts to convert the given value to a float. + * If the conversion is successful, it returns the value as an float. + * If the conversion fails, it returns NULL. + * + * String values are trimmed using trim(). + * + * @param mixed $value The value to be converted to a float. + * @return float|null Returns the converted float value or null if the conversion fails. + * @since 5.1.0 + */ + function toFloat(mixed $value): ?float + { + if (is_string($value)) { + $value = trim($value); + if (preg_match('/^0+[^0]{1}/', $value)) { + $value = ltrim($value, '0'); + } + + $value = filter_var($value, FILTER_VALIDATE_FLOAT, FILTER_NULL_ON_FAILURE); + + return $value === PHP_FLOAT_MIN ? null : $value; + } + if (is_float($value)) { + if (is_nan($value) || is_infinite($value)) { + return null; + } + + return $value; + } + if (is_int($value)) { + return (float)$value; + } + if (is_bool($value)) { + return (float)$value; + } + + return null; + } +} + +if (!function_exists('Cake\Core\toBool')) { + /** + * Converts a value to boolean. + * + * 1 | '1' | 1.0 | true - values returns as true + * 0 | '0' | 0.0 | false - values returns as false + * Other values returns as null. + * + * @param mixed $value The value to convert to boolean. + * @return bool|null Returns true if the value is truthy, false if it's falsy, or NULL otherwise. + * @since 5.1.0 + */ + function toBool(mixed $value): ?bool + { + if ($value === '1' || $value === 1 || $value === 1.0 || $value === true) { + return true; + } + if ($value === '0' || $value === 0 || $value === 0.0 || $value === false) { + return false; + } + + return null; + } } diff --git a/app/vendor/cakephp/cakephp/src/Core/functions_global.php b/app/vendor/cakephp/cakephp/src/Core/functions_global.php index bb2a71478..022593cbd 100644 --- a/app/vendor/cakephp/cakephp/src/Core/functions_global.php +++ b/app/vendor/cakephp/cakephp/src/Core/functions_global.php @@ -14,22 +14,39 @@ * @since 3.0.0 * @license https://opensource.org/licenses/mit-license.php MIT License */ +// phpcs:disable PSR1.Files.SideEffects use function Cake\Core\deprecationWarning as cakeDeprecationWarning; use function Cake\Core\env as cakeEnv; -use function Cake\Core\getTypeName as cakeGetTypeName; use function Cake\Core\h as cakeH; use function Cake\Core\namespaceSplit as cakeNamespaceSplit; +use function Cake\Core\pathCombine as cakePathCombine; use function Cake\Core\pj as cakePj; use function Cake\Core\pluginSplit as cakePluginSplit; use function Cake\Core\pr as cakePr; +use function Cake\Core\toBool as cakeToBool; +use function Cake\Core\toFloat as cakeToFloat; +use function Cake\Core\toInt as cakeToInt; +use function Cake\Core\toString as cakeToString; use function Cake\Core\triggerWarning as cakeTriggerWarning; -if (!defined('DS')) { +if (!function_exists('pathCombine')) { /** - * Defines DS as short form of DIRECTORY_SEPARATOR. + * Combines parts with a forward-slash `/`. + * + * Skips adding a forward-slash if either `/` or `\` already exists. + * + * @param array $parts + * @param bool|null $trailing Determines how trailing slashes are handled + * - If true, ensures a trailing forward-slash is added if one doesn't exist + * - If false, ensures any trailing slash is removed + * - if null, ignores trailing slashes + * @return string */ - define('DS', DIRECTORY_SEPARATOR); + function pathCombine(array $parts, ?bool $trailing = null): string + { + return cakePathCombine($parts, $trailing); + } } if (!function_exists('h')) { @@ -44,9 +61,9 @@ * @param string|null $charset Character set to use when escaping. * Defaults to config value in `mb_internal_encoding()` or 'UTF-8'. * @return mixed Wrapped text. - * @link https://book.cakephp.org/4/en/core-libraries/global-constants-and-functions.html#h + * @link https://book.cakephp.org/5/en/core-libraries/global-constants-and-functions.html#h */ - function h($text, bool $double = true, ?string $charset = null) + function h(mixed $text, bool $double = true, ?string $charset = null): mixed { return cakeH($text, $double, $charset); } @@ -66,8 +83,8 @@ function h($text, bool $double = true, ?string $charset = null) * @param bool $dotAppend Set to true if you want the plugin to have a '.' appended to it. * @param string|null $plugin Optional default plugin to use if no plugin is found. Defaults to null. * @return array Array with 2 indexes. 0 => plugin name, 1 => class name. - * @link https://book.cakephp.org/4/en/core-libraries/global-constants-and-functions.html#pluginSplit - * @psalm-return array{string|null, string} + * @link https://book.cakephp.org/5/en/core-libraries/global-constants-and-functions.html#pluginSplit + * @phpstan-return array{string|null, string} */ function pluginSplit(string $name, bool $dotAppend = false, ?string $plugin = null): array { @@ -82,7 +99,7 @@ function pluginSplit(string $name, bool $dotAppend = false, ?string $plugin = nu * Commonly used like `list($namespace, $className) = namespaceSplit($class);`. * * @param string $class The full class name, ie `Cake\Core\App`. - * @return array Array with 2 indexes. 0 => namespace, 1 => classname. + * @return array{0: string, 1: string} Array with 2 indexes. 0 => namespace, 1 => classname. */ function namespaceSplit(string $class): array { @@ -101,10 +118,10 @@ function namespaceSplit(string $class): array * * @param mixed $var Variable to print out. * @return mixed the same $var that was passed to this function - * @link https://book.cakephp.org/4/en/core-libraries/global-constants-and-functions.html#pr + * @link https://book.cakephp.org/5/en/core-libraries/global-constants-and-functions.html#pr * @see debug() */ - function pr($var) + function pr(mixed $var): mixed { return cakePr($var); } @@ -122,9 +139,9 @@ function pr($var) * @param mixed $var Variable to print out. * @return mixed the same $var that was passed to this function * @see pr() - * @link https://book.cakephp.org/4/en/core-libraries/global-constants-and-functions.html#pj + * @link https://book.cakephp.org/5/en/core-libraries/global-constants-and-functions.html#pj */ - function pj($var) + function pj(mixed $var): mixed { return cakePj($var); } @@ -139,10 +156,10 @@ function pj($var) * * @param string $key Environment variable name. * @param string|bool|null $default Specify a default value in case the environment variable is not defined. - * @return string|bool|null Environment variable setting. - * @link https://book.cakephp.org/4/en/core-libraries/global-constants-and-functions.html#env + * @return string|float|int|bool|null Environment variable setting. + * @link https://book.cakephp.org/5/en/core-libraries/global-constants-and-functions.html#env */ - function env(string $key, $default = null) + function env(string $key, string|float|int|bool|null $default = null): string|float|int|bool|null { return cakeEnv($key, $default); } @@ -165,26 +182,90 @@ function triggerWarning(string $message): void /** * Helper method for outputting deprecation warnings * + * @param string $version The version that added this deprecation warning. * @param string $message The message to output as a deprecation warning. * @param int $stackFrame The stack frame to include in the error. Defaults to 1 * as that should point to application/plugin code. * @return void */ - function deprecationWarning(string $message, int $stackFrame = 1): void + function deprecationWarning(string $version, string $message, int $stackFrame = 1): void + { + cakeDeprecationWarning($version, $message, $stackFrame + 1); + } +} + +if (!function_exists('toString')) { + /** + * Converts the given value to a string. + * + * This method attempts to convert the given value to a string. + * If the value is already a string, it returns the value as it is. + * ``null`` is returned if the conversion is not possible. + * + * @param mixed $value The value to be converted. + * @return ?string Returns the string representation of the value, or null if the value is not a string. + * @since 5.1.1 + */ + function toString(mixed $value): ?string { - cakeDeprecationWarning($message, $stackFrame + 1); + return cakeToString($value); } } -if (!function_exists('getTypeName')) { +if (!function_exists('toInt')) { /** - * Returns the objects class or var type of it's not an object + * Converts a value to an integer. + * + * This method attempts to convert the given value to an integer. + * If the conversion is successful, it returns the value as an integer. + * If the conversion fails, it returns NULL. + * + * String values are trimmed using trim(). + * + * @param mixed $value The value to be converted to an integer. + * @return int|null Returns the converted integer value or null if the conversion fails. + * @since 5.1.1 + */ + function toInt(mixed $value): ?int + { + return cakeToInt($value); + } +} + +if (!function_exists('toFloat')) { + /** + * Converts a value to a float. + * + * This method attempts to convert the given value to a float. + * If the conversion is successful, it returns the value as an float. + * If the conversion fails, it returns NULL. + * + * String values are trimmed using trim(). + * + * @param mixed $value The value to be converted to a float. + * @return float|null Returns the converted float value or null if the conversion fails. + * @since 5.1.1 + */ + function toFloat(mixed $value): ?float + { + return cakeToFloat($value); + } +} + +if (!function_exists('toBool')) { + /** + * Converts a value to boolean. + * + * 1 | '1' | 1.0 | true - values returns as true + * 0 | '0' | 0.0 | false - values returns as false + * Other values returns as null. * - * @param mixed $var Variable to check - * @return string Returns the class name or variable type + * @param mixed $value The value to convert to boolean. + * @return bool|null Returns true if the value is truthy, false if it's falsy, or NULL otherwise. + * @since 5.1.1 */ - function getTypeName($var): string + function toBool(mixed $value): ?bool { - return cakeGetTypeName($var); + return cakeToBool($value); } } diff --git a/app/vendor/cakephp/cakephp/src/Database/Connection.php b/app/vendor/cakephp/cakephp/src/Database/Connection.php index 83777d723..4f91a1aad 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Connection.php +++ b/app/vendor/cakephp/cakephp/src/Database/Connection.php @@ -2,32 +2,30 @@ declare(strict_types=1); /** - * CakePHP(tm) : Rapid Development Framework (https://cakephp.org) - * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) - * - * Licensed under The MIT License - * For full copyright and license information, please see the LICENSE.txt - * Redistributions of files must retain the above copyright notice. - * - * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) - * @link https://cakephp.org CakePHP(tm) Project - * @since 3.0.0 +* CakePHP(tm) : Rapid Development Framework (https://cakephp.org) +* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) +* +* Licensed under The MIT License +* For full copyright and license information, please see the LICENSE.txt +* Redistributions of files must retain the above copyright notice. +* +* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) +* @link https://cakephp.org CakePHP(tm) Project +* @since 3.0.0 * @license https://opensource.org/licenses/mit-license.php MIT License */ namespace Cake\Database; use Cake\Cache\Cache; use Cake\Core\App; +use Cake\Core\Exception\CakeException; use Cake\Core\Retry\CommandRetry; -use Cake\Database\Exception\MissingConnectionException; use Cake\Database\Exception\MissingDriverException; use Cake\Database\Exception\MissingExtensionException; use Cake\Database\Exception\NestedTransactionRollbackException; -use Cake\Database\Log\LoggedQuery; -use Cake\Database\Log\LoggingStatement; -use Cake\Database\Log\QueryLogger; use Cake\Database\Query\DeleteQuery; use Cake\Database\Query\InsertQuery; +use Cake\Database\Query\QueryFactory; use Cake\Database\Query\SelectQuery; use Cake\Database\Query\UpdateQuery; use Cake\Database\Retry\ReconnectStrategy; @@ -35,51 +33,47 @@ use Cake\Database\Schema\Collection as SchemaCollection; use Cake\Database\Schema\CollectionInterface as SchemaCollectionInterface; use Cake\Datasource\ConnectionInterface; -use Cake\Log\Engine\BaseLog; use Cake\Log\Log; -use Psr\Log\LoggerInterface; +use Closure; use Psr\SimpleCache\CacheInterface; -use RuntimeException; use Throwable; -use function Cake\Core\deprecationWarning; +use function Cake\Core\env; /** * Represents a connection with a database server. */ class Connection implements ConnectionInterface { - use TypeConverterTrait; - /** * Contains the configuration params for this connection. * * @var array */ - protected $_config; + protected array $_config; /** - * @var \Cake\Database\DriverInterface + * @var \Cake\Database\Driver */ - protected DriverInterface $readDriver; + protected Driver $readDriver; /** - * @var \Cake\Database\DriverInterface + * @var \Cake\Database\Driver */ - protected DriverInterface $writeDriver; + protected Driver $writeDriver; /** * Contains how many nested transactions have been started. * * @var int */ - protected $_transactionLevel = 0; + protected int $_transactionLevel = 0; /** * Whether a transaction is active in this connection. * * @var bool */ - protected $_transactionStarted = false; + protected bool $_transactionStarted = false; /** * Whether this connection can and should use savepoints for nested @@ -87,35 +81,21 @@ class Connection implements ConnectionInterface * * @var bool */ - protected $_useSavePoints = false; - - /** - * Whether to log queries generated during this connection. - * - * @var bool - */ - protected $_logQueries = false; - - /** - * Logger object instance. - * - * @var \Psr\Log\LoggerInterface|null - */ - protected $_logger; + protected bool $_useSavePoints = false; /** * Cacher object instance. * * @var \Psr\SimpleCache\CacheInterface|null */ - protected $cacher; + protected ?CacheInterface $cacher = null; /** * The schema collection object * * @var \Cake\Database\Schema\CollectionInterface|null */ - protected $_schemaCollection; + protected ?SchemaCollectionInterface $_schemaCollection = null; /** * NestedTransactionRollbackException object instance, will be stored if @@ -123,14 +103,16 @@ class Connection implements ConnectionInterface * * @var \Cake\Database\Exception\NestedTransactionRollbackException|null */ - protected $nestedTransactionRollbackException; + protected ?NestedTransactionRollbackException $nestedTransactionRollbackException = null; + + protected QueryFactory $queryFactory; /** * Constructor. * * ### Available options: * - * - `driver` Sort name or FCQN for driver. + * - `driver` Sort name or FQCN for driver. * - `log` Boolean indicating whether to use query logging. * - `name` Connection name. * - `cacheMetaData` Boolean indicating whether metadata (datasource schemas) should be cached. @@ -143,33 +125,29 @@ public function __construct(array $config) { $this->_config = $config; [self::ROLE_READ => $this->readDriver, self::ROLE_WRITE => $this->writeDriver] = $this->createDrivers($config); - - if (!empty($config['log'])) { - $this->enableQueryLogging((bool)$config['log']); - } } /** * Creates read and write drivers. * - * @param array $config Connection config - * @return array - * @psalm-return array{read: \Cake\Database\DriverInterface, write: \Cake\Database\DriverInterface} + * @param array $config Connection config + * @return array + * @phpstan-return array{read: \Cake\Database\Driver, write: \Cake\Database\Driver} */ protected function createDrivers(array $config): array { $driver = $config['driver'] ?? ''; if (!is_string($driver)) { - /** @var \Cake\Database\DriverInterface $driver */ + assert($driver instanceof Driver); if (!$driver->enabled()) { - throw new MissingExtensionException(['driver' => get_class($driver), 'name' => $this->configName()]); + throw new MissingExtensionException(['driver' => $driver::class, 'name' => $this->configName()]); } // Legacy support for setting instance instead of driver class return [self::ROLE_READ => $driver, self::ROLE_WRITE => $driver]; } - /** @var class-string<\Cake\Database\DriverInterface>|null $driverClass */ + /** @var class-string<\Cake\Database\Driver>|null $driverClass */ $driverClass = App::className($driver, 'Database/Driver'); if ($driverClass === null) { throw new MissingDriverException(['driver' => $driver, 'connection' => $this->configName()]); @@ -177,23 +155,26 @@ protected function createDrivers(array $config): array $sharedConfig = array_diff_key($config, array_flip([ 'name', + 'className', 'driver', - 'log', 'cacheMetaData', 'cacheKeyPrefix', + 'read', + 'write', ])); - $writeConfig = $config['write'] ?? [] + $sharedConfig; - $readConfig = $config['read'] ?? [] + $sharedConfig; - if ($readConfig == $writeConfig) { - $readDriver = $writeDriver = new $driverClass(['_role' => self::ROLE_WRITE] + $writeConfig); - } else { + $writeConfig = ($config['write'] ?? []) + $sharedConfig; + $readConfig = ($config['read'] ?? []) + $sharedConfig; + if (array_key_exists('write', $config) || array_key_exists('read', $config)) { $readDriver = new $driverClass(['_role' => self::ROLE_READ] + $readConfig); $writeDriver = new $driverClass(['_role' => self::ROLE_WRITE] + $writeConfig); + } else { + $readDriver = new $driverClass(['_role' => self::ROLE_WRITE] + $writeConfig); + $writeDriver = $readDriver; } if (!$writeDriver->enabled()) { - throw new MissingExtensionException(['driver' => get_class($writeDriver), 'name' => $this->configName()]); + throw new MissingExtensionException(['driver' => $writeDriver::class, 'name' => $this->configName()]); } return [self::ROLE_READ => $readDriver, self::ROLE_WRITE => $writeDriver]; @@ -207,7 +188,19 @@ protected function createDrivers(array $config): array public function __destruct() { if ($this->_transactionStarted && class_exists(Log::class)) { - Log::warning('The connection is going to be closed but there is an active transaction.'); + $message = 'The connection is going to be closed but there is an active transaction.'; + + $requestUrl = env('REQUEST_URI'); + if ($requestUrl) { + $message .= "\nRequest URL: " . $requestUrl; + } + + $clientIp = env('REMOTE_ADDR'); + if ($clientIp) { + $message .= "\nClient IP: " . $clientIp; + } + + Log::warning($message); } } @@ -237,55 +230,6 @@ public function role(): string return preg_match('/:read$/', $this->configName()) === 1 ? static::ROLE_READ : static::ROLE_WRITE; } - /** - * Sets the driver instance. If a string is passed it will be treated - * as a class name and will be instantiated. - * - * @param \Cake\Database\DriverInterface|string $driver The driver instance to use. - * @param array $config Config for a new driver. - * @throws \Cake\Database\Exception\MissingDriverException When a driver class is missing. - * @throws \Cake\Database\Exception\MissingExtensionException When a driver's PHP extension is missing. - * @return $this - * @deprecated 4.4.0 Setting the driver is deprecated. Use the connection config instead. - */ - public function setDriver($driver, $config = []) - { - deprecationWarning('Setting the driver is deprecated. Use the connection config instead.'); - - $driver = $this->createDriver($driver, $config); - $this->readDriver = $this->writeDriver = $driver; - - return $this; - } - - /** - * Creates driver from name, class name or instance. - * - * @param \Cake\Database\DriverInterface|string $name Driver name, class name or instance. - * @param array $config Driver config if $name is not an instance. - * @return \Cake\Database\DriverInterface - * @throws \Cake\Database\Exception\MissingDriverException When a driver class is missing. - * @throws \Cake\Database\Exception\MissingExtensionException When a driver's PHP extension is missing. - */ - protected function createDriver($name, array $config): DriverInterface - { - $driver = $name; - if (is_string($driver)) { - /** @psalm-var class-string<\Cake\Database\DriverInterface>|null $className */ - $className = App::className($driver, 'Database/Driver'); - if ($className === null) { - throw new MissingDriverException(['driver' => $driver, 'connection' => $this->configName()]); - } - $driver = new $className(['_role' => self::ROLE_WRITE] + $config); - } - - if (!$driver->enabled()) { - throw new MissingExtensionException(['driver' => get_class($driver), 'name' => $this->configName()]); - } - - return $driver; - } - /** * Get the retry wrapper object that is allows recovery from server disconnects * while performing certain database actions, such as executing a query. @@ -301,100 +245,15 @@ public function getDisconnectRetry(): CommandRetry * Gets the driver instance. * * @param string $role Connection role ('read' or 'write') - * @return \Cake\Database\DriverInterface + * @return \Cake\Database\Driver */ - public function getDriver(string $role = self::ROLE_WRITE): DriverInterface + public function getDriver(string $role = self::ROLE_WRITE): Driver { assert($role === self::ROLE_READ || $role === self::ROLE_WRITE); return $role === self::ROLE_READ ? $this->readDriver : $this->writeDriver; } - /** - * Connects to the configured database. - * - * @throws \Cake\Database\Exception\MissingConnectionException If database connection could not be established. - * @return bool true, if the connection was already established or the attempt was successful. - * @deprecated 4.5.0 Use getDriver()->connect() instead. - */ - public function connect(): bool - { - deprecationWarning( - 'If you cannot use automatic connection management, use $connection->getDriver()->connect() instead.' - ); - - $connected = true; - foreach ([self::ROLE_READ, self::ROLE_WRITE] as $role) { - try { - $connected = $connected && $this->getDriver($role)->connect(); - } catch (MissingConnectionException $e) { - throw $e; - } catch (Throwable $e) { - throw new MissingConnectionException( - [ - 'driver' => App::shortName(get_class($this->getDriver($role)), 'Database/Driver'), - 'reason' => $e->getMessage(), - ], - null, - $e - ); - } - } - - return $connected; - } - - /** - * Disconnects from database server. - * - * @return void - * @deprecated 4.5.0 Use getDriver()->disconnect() instead. - */ - public function disconnect(): void - { - deprecationWarning( - 'If you cannot use automatic connection management, use $connection->getDriver()->disconnect() instead.' - ); - - $this->getDriver(self::ROLE_READ)->disconnect(); - $this->getDriver(self::ROLE_WRITE)->disconnect(); - } - - /** - * Returns whether connection to database server was already established. - * - * @return bool - * @deprecated 4.5.0 Use getDriver()->isConnected() instead. - */ - public function isConnected(): bool - { - deprecationWarning('Use $connection->getDriver()->isConnected() instead.'); - - return $this->getDriver(self::ROLE_READ)->isConnected() && $this->getDriver(self::ROLE_WRITE)->isConnected(); - } - - /** - * Prepares a SQL statement to be executed. - * - * @param \Cake\Database\Query|string $query The SQL to convert into a prepared statement. - * @return \Cake\Database\StatementInterface - * @deprecated 4.5.0 Use getDriver()->prepare() instead. - */ - public function prepare($query): StatementInterface - { - $role = $query instanceof Query ? $query->getConnectionRole() : self::ROLE_WRITE; - - return $this->getDisconnectRetry()->run(function () use ($query, $role) { - $statement = $this->getDriver($role)->prepare($query); - - if ($this->_logQueries) { - $statement = $this->_newLogger($statement); - } - - return $statement; - }); - } - /** * Executes a query using $params for interpolating values and $types as a hint for each * those params. @@ -406,31 +265,7 @@ public function prepare($query): StatementInterface */ public function execute(string $sql, array $params = [], array $types = []): StatementInterface { - return $this->getDisconnectRetry()->run(function () use ($sql, $params, $types) { - $statement = $this->prepare($sql); - if (!empty($params)) { - $statement->bind($params, $types); - } - $statement->execute(); - - return $statement; - }); - } - - /** - * Compiles a Query object into a SQL string according to the dialect for this - * connection's driver - * - * @param \Cake\Database\Query $query The query to be compiled - * @param \Cake\Database\ValueBinder $binder Value binder - * @return string - * @deprecated 4.5.0 Use getDriver()->compileQuery() instead. - */ - public function compileQuery(Query $query, ValueBinder $binder): string - { - deprecationWarning('Use getDriver()->compileQuery() instead.'); - - return $this->getDriver($query->getConnectionRole())->compileQuery($query, $binder)[1]; + return $this->getDisconnectRetry()->run(fn() => $this->getDriver()->execute($sql, $params, $types)); } /** @@ -442,75 +277,77 @@ public function compileQuery(Query $query, ValueBinder $binder): string */ public function run(Query $query): StatementInterface { - return $this->getDisconnectRetry()->run(function () use ($query) { - $statement = $this->prepare($query); - $query->getValueBinder()->attachTo($statement); - $statement->execute(); + return $this->getDisconnectRetry()->run(fn() => $this->getDriver($query->getConnectionRole())->run($query)); + } - return $statement; - }); + /** + * Get query factory instance. + * + * @return \Cake\Database\Query\QueryFactory + */ + public function queryFactory(): QueryFactory + { + return $this->queryFactory ??= new QueryFactory($this); } /** * Create a new SelectQuery instance for this connection. * - * @param \Cake\Database\ExpressionInterface|callable|array|string $fields fields to be added to the list. + * @param \Cake\Database\ExpressionInterface|\Closure|array|string|float|int $fields Fields/columns list for the query. * @param array|string $table The table or list of tables to query. * @param array $types Associative array containing the types to be used for casting. - * @return \Cake\Database\Query\SelectQuery + * @return \Cake\Database\Query\SelectQuery */ public function selectQuery( - $fields = [], - $table = [], - array $types = [] + ExpressionInterface|Closure|array|string|float|int $fields = [], + array|string $table = [], + array $types = [], ): SelectQuery { - $query = new SelectQuery($this); - if ($table) { - $query->from($table); - } - if ($fields) { - $query->select($fields, false); - } - $query->setDefaultTypes($types); - - return $query; + return $this->queryFactory()->select($fields, $table, $types); } /** - * Executes a SQL statement and returns the Statement object as result. + * Create a new InsertQuery instance for this connection. * - * @param string $sql The SQL query to execute. - * @return \Cake\Database\StatementInterface - * @deprecated 4.5.0 Use either `selectQuery`, `insertQuery`, `deleteQuery`, `updateQuery` instead. + * @param string|null $table The table to insert rows into. + * @param array $values Associative array of column => value to be inserted. + * @param array $types Associative array containing the types to be used for casting. + * @return \Cake\Database\Query\InsertQuery */ - public function query(string $sql): StatementInterface + public function insertQuery(?string $table = null, array $values = [], array $types = []): InsertQuery { - deprecationWarning('Use either `selectQuery`, `insertQuery`, `deleteQuery`, `updateQuery` instead.'); - - return $this->getDisconnectRetry()->run(function () use ($sql) { - $statement = $this->prepare($sql); - $statement->execute(); + return $this->queryFactory()->insert($table, $values, $types); + } - return $statement; - }); + /** + * Create a new UpdateQuery instance for this connection. + * + * @param \Cake\Database\ExpressionInterface|string|null $table The table to update rows of. + * @param array $values Values to be updated. + * @param array $conditions Conditions to be set for the update statement. + * @param array $types Associative array containing the types to be used for casting. + * @return \Cake\Database\Query\UpdateQuery + */ + public function updateQuery( + ExpressionInterface|string|null $table = null, + array $values = [], + array $conditions = [], + array $types = [], + ): UpdateQuery { + return $this->queryFactory()->update($table, $values, $conditions, $types); } /** - * Create a new Query instance for this connection. + * Create a new DeleteQuery instance for this connection. * - * @return \Cake\Database\Query - * @deprecated 4.5.0 Use `insertQuery()`, `deleteQuery()`, `selectQuery()` or `updateQuery()` instead. + * @param string|null $table The table to delete rows from. + * @param array $conditions Conditions to be set for the delete statement. + * @param array $types Associative array containing the types to be used for casting. + * @return \Cake\Database\Query\DeleteQuery */ - public function newQuery(): Query + public function deleteQuery(?string $table = null, array $conditions = [], array $types = []): DeleteQuery { - deprecationWarning( - 'As of 4.5.0, using newQuery() is deprecated. Instead, use `insertQuery()`, ' . - '`deleteQuery()`, `selectQuery()` or `updateQuery()`. The query objects ' . - 'returned by these methods will emit deprecations that will become fatal errors in 5.0.' . - 'See https://book.cakephp.org/4/en/appendices/4-5-migration-guide.html for more information.' - ); - - return new Query($this); + return $this->queryFactory()->delete($table, $conditions, $types); } /** @@ -541,7 +378,7 @@ public function getSchemaCollection(): SchemaCollectionInterface return $this->_schemaCollection = new CachedCollection( new SchemaCollection($this), empty($this->_config['cacheKeyPrefix']) ? $this->configName() : $this->_config['cacheKeyPrefix'], - $this->getCacher() + $this->getCacher(), ); } @@ -553,37 +390,12 @@ public function getSchemaCollection(): SchemaCollectionInterface * * @param string $table the table to insert values in * @param array $values values to be inserted - * @param array $types Array containing the types to be used for casting + * @param array $types Array containing the types to be used for casting * @return \Cake\Database\StatementInterface */ public function insert(string $table, array $values, array $types = []): StatementInterface { - return $this->getDisconnectRetry()->run(function () use ($table, $values, $types) { - return $this->insertQuery($table, $values, $types)->execute(); - }); - } - - /** - * Create a new InsertQuery instance for this connection. - * - * @param string|null $table The table to insert rows into. - * @param array $values Associative array of column => value to be inserted. - * @param array $types Associative array containing the types to be used for casting. - * @return \Cake\Database\Query\InsertQuery - */ - public function insertQuery(?string $table = null, array $values = [], array $types = []): InsertQuery - { - $query = new InsertQuery($this); - if ($table) { - $query->into($table); - } - if ($values) { - $columns = array_keys($values); - $query->insert($columns, $types) - ->values($values); - } - - return $query; + return $this->insertQuery($table, $values, $types)->execute(); } /** @@ -592,43 +404,12 @@ public function insertQuery(?string $table = null, array $values = [], array $ty * @param string $table the table to update rows from * @param array $values values to be updated * @param array $conditions conditions to be set for update statement - * @param array $types list of associative array containing the types to be used for casting + * @param array $types list of associative array containing the types to be used for casting * @return \Cake\Database\StatementInterface */ public function update(string $table, array $values, array $conditions = [], array $types = []): StatementInterface { - return $this->getDisconnectRetry()->run(function () use ($table, $values, $conditions, $types) { - return $this->updateQuery($table, $values, $conditions, $types)->execute(); - }); - } - - /** - * Create a new UpdateQuery instance for this connection. - * - * @param \Cake\Database\ExpressionInterface|string|null $table The table to update rows of. - * @param array $values Values to be updated. - * @param array $conditions Conditions to be set for the update statement. - * @param array $types Associative array containing the types to be used for casting. - * @return \Cake\Database\Query\UpdateQuery - */ - public function updateQuery( - $table = null, - array $values = [], - array $conditions = [], - array $types = [] - ): UpdateQuery { - $query = new UpdateQuery($this); - if ($table) { - $query->update($table); - } - if ($values) { - $query->set($values, $types); - } - if ($conditions) { - $query->where($conditions, $types); - } - - return $query; + return $this->updateQuery($table, $values, $conditions, $types)->execute(); } /** @@ -636,35 +417,12 @@ public function updateQuery( * * @param string $table the table to delete rows from * @param array $conditions conditions to be set for delete statement - * @param array $types list of associative array containing the types to be used for casting + * @param array $types list of associative array containing the types to be used for casting * @return \Cake\Database\StatementInterface */ public function delete(string $table, array $conditions = [], array $types = []): StatementInterface { - return $this->getDisconnectRetry()->run(function () use ($table, $conditions, $types) { - return $this->deleteQuery($table, $conditions, $types)->execute(); - }); - } - - /** - * Create a new DeleteQuery instance for this connection. - * - * @param string|null $table The table to delete rows from. - * @param array $conditions Conditions to be set for the delete statement. - * @param array $types Associative array containing the types to be used for casting. - * @return \Cake\Database\Query\DeleteQuery - */ - public function deleteQuery(?string $table = null, array $conditions = [], array $types = []): DeleteQuery - { - $query = new DeleteQuery($this); - if ($table) { - $query->from($table); - } - if ($conditions) { - $query->where($conditions, $types); - } - - return $query; + return $this->deleteQuery($table, $conditions, $types)->execute(); } /** @@ -675,10 +433,6 @@ public function deleteQuery(?string $table = null, array $conditions = [], array public function begin(): void { if (!$this->_transactionStarted) { - if ($this->_logQueries) { - $this->log('BEGIN'); - } - $this->getDisconnectRetry()->run(function (): void { $this->getDriver()->beginTransaction(); }); @@ -709,17 +463,14 @@ public function commit(): bool if ($this->_transactionLevel === 0) { if ($this->wasNestedTransactionRolledback()) { - /** @var \Cake\Database\Exception\NestedTransactionRollbackException $e */ $e = $this->nestedTransactionRollbackException; + assert($e !== null); $this->nestedTransactionRollbackException = null; throw $e; } $this->_transactionStarted = false; $this->nestedTransactionRollbackException = null; - if ($this->_logQueries) { - $this->log('COMMIT'); - } return $this->getDriver()->commitTransaction(); } @@ -746,16 +497,11 @@ public function rollback(?bool $toBeginning = null): bool } $useSavePoint = $this->isSavePointsEnabled(); - if ($toBeginning === null) { - $toBeginning = !$useSavePoint; - } + $toBeginning ??= !$useSavePoint; if ($this->_transactionLevel === 0 || $toBeginning) { $this->_transactionLevel = 0; $this->_transactionStarted = false; $this->nestedTransactionRollbackException = null; - if ($this->_logQueries) { - $this->log('ROLLBACK'); - } $this->getDriver()->rollbackTransaction(); return true; @@ -764,8 +510,8 @@ public function rollback(?bool $toBeginning = null): bool $savePoint = $this->_transactionLevel--; if ($useSavePoint) { $this->rollbackSavepoint($savePoint); - } elseif ($this->nestedTransactionRollbackException === null) { - $this->nestedTransactionRollbackException = new NestedTransactionRollbackException(); + } else { + $this->nestedTransactionRollbackException ??= new NestedTransactionRollbackException(); } return true; @@ -785,7 +531,7 @@ public function enableSavePoints(bool $enable = true) if ($enable === false) { $this->_useSavePoints = false; } else { - $this->_useSavePoints = $this->getDriver()->supports(DriverInterface::FEATURE_SAVEPOINT); + $this->_useSavePoints = $this->getDriver()->supports(DriverFeatureEnum::SAVEPOINT); } return $this; @@ -819,9 +565,9 @@ public function isSavePointsEnabled(): bool * @param string|int $name Save point name or id * @return void */ - public function createSavePoint($name): void + public function createSavePoint(string|int $name): void { - $this->execute($this->getDriver()->savePointSQL($name))->closeCursor(); + $this->execute($this->getDriver()->savePointSQL($name)); } /** @@ -830,11 +576,11 @@ public function createSavePoint($name): void * @param string|int $name Save point name or id * @return void */ - public function releaseSavePoint($name): void + public function releaseSavePoint(string|int $name): void { $sql = $this->getDriver()->releaseSavePointSQL($name); if ($sql) { - $this->execute($sql)->closeCursor(); + $this->execute($sql); } } @@ -844,9 +590,9 @@ public function releaseSavePoint($name): void * @param string|int $name Save point name or id * @return void */ - public function rollbackSavepoint($name): void + public function rollbackSavepoint(string|int $name): void { - $this->execute($this->getDriver()->rollbackSavePointSQL($name))->closeCursor(); + $this->execute($this->getDriver()->rollbackSavePointSQL($name)); } /** @@ -857,7 +603,7 @@ public function rollbackSavepoint($name): void public function disableForeignKeys(): void { $this->getDisconnectRetry()->run(function (): void { - $this->execute($this->getDriver()->disableForeignKeySQL())->closeCursor(); + $this->execute($this->getDriver()->disableForeignKeySQL()); }); } @@ -869,26 +615,33 @@ public function disableForeignKeys(): void public function enableForeignKeys(): void { $this->getDisconnectRetry()->run(function (): void { - $this->execute($this->getDriver()->enableForeignKeySQL())->closeCursor(); + $this->execute($this->getDriver()->enableForeignKeySQL()); }); } /** - * Returns whether the driver supports adding or dropping constraints - * to already created tables. + * Executes a callback inside a transaction, if any exception occurs + * while executing the passed callback, the transaction will be rolled back + * If the result of the callback is `false`, the transaction will + * also be rolled back. Otherwise the transaction is committed after executing + * the callback. * - * @return bool true if driver supports dynamic constraints - * @deprecated 4.3.0 Fixtures no longer dynamically drop and create constraints. - */ - public function supportsDynamicConstraints(): bool - { - return $this->getDriver()->supportsDynamicConstraints(); - } - - /** - * @inheritDoc + * The callback will receive the connection instance as its first argument. + * + * ### Example: + * + * ``` + * $connection->transactional(function ($connection) { + * $connection->deleteQuery('users')->execute(); + * }); + * ``` + * + * @param \Closure $callback The callback to execute within a transaction. + * @return mixed The return value of the callback. + * @throws \Exception Will re-throw any exception raised in $callback after + * rolling back the transaction. */ - public function transactional(callable $callback) + public function transactional(Closure $callback): mixed { $this->begin(); @@ -926,9 +679,24 @@ protected function wasNestedTransactionRolledback(): bool } /** - * @inheritDoc + * Run an operation with constraints disabled. + * + * Constraints should be re-enabled after the callback succeeds/fails. + * + * ### Example: + * + * ``` + * $connection->disableConstraints(function ($connection) { + * $connection->insertQuery('users')->execute(); + * }); + * ``` + * + * @param \Closure $callback Callback to run with constraints disabled + * @return mixed The return value of the callback. + * @throws \Exception Will re-throw any exception raised in $callback after + * rolling back the transaction. */ - public function disableConstraints(callable $callback) + public function disableConstraints(Closure $callback): mixed { return $this->getDisconnectRetry()->run(function () use ($callback) { $this->disableForeignKeys(); @@ -953,56 +721,6 @@ public function inTransaction(): bool return $this->_transactionStarted; } - /** - * Quotes value to be used safely in database query. - * - * This uses `PDO::quote()` and requires `supportsQuoting()` to work. - * - * @param mixed $value The value to quote. - * @param \Cake\Database\TypeInterface|string|int $type Type to be used for determining kind of quoting to perform - * @return string Quoted value - * @deprecated 4.5.0 Use getDriver()->quote() instead. - */ - public function quote($value, $type = 'string'): string - { - deprecationWarning('Use getDriver()->quote() instead.'); - [$value, $type] = $this->cast($value, $type); - - return $this->getDriver()->quote($value, $type); - } - - /** - * Checks if using `quote()` is supported. - * - * This is not required to use `quoteIdentifier()`. - * - * @return bool - * @deprecated 4.5.0 Use getDriver()->supportsQuoting() instead. - */ - public function supportsQuoting(): bool - { - deprecationWarning('Use getDriver()->supportsQuoting() instead.'); - - return $this->getDriver()->supports(DriverInterface::FEATURE_QUOTE); - } - - /** - * Quotes a database identifier (a column name, table name, etc..) to - * be used safely in queries without the risk of using reserved words. - * - * This does not require `supportsQuoting()` to work. - * - * @param string $identifier The identifier to quote. - * @return string - * @deprecated 4.5.0 Use getDriver()->quoteIdentifier() instead. - */ - public function quoteIdentifier(string $identifier): string - { - deprecationWarning('Use getDriver()->quoteIdentifier() instead.'); - - return $this->getDriver()->quoteIdentifier($identifier); - } - /** * Enables or disables metadata caching for this connection * @@ -1012,7 +730,7 @@ public function quoteIdentifier(string $identifier): string * true to use `_cake_model_` or the name of the cache config to use. * @return void */ - public function cacheMetadata($cache): void + public function cacheMetadata(string|bool $cache): void { $this->_schemaCollection = null; $this->_config['cacheMetadata'] = $cache; @@ -1046,116 +764,15 @@ public function getCacher(): CacheInterface } if (!class_exists(Cache::class)) { - throw new RuntimeException( + throw new CakeException( 'To use caching you must either set a cacher using Connection::setCacher()' . - ' or require the cakephp/cache package in your composer config.' + ' or require the cakephp/cache package in your composer config.', ); } return $this->cacher = Cache::pool($configName); } - /** - * Enable/disable query logging - * - * @param bool $enable Enable/disable query logging - * @return $this - * @deprecated 4.5.0 Connection logging is moving to the driver in 5.x - */ - public function enableQueryLogging(bool $enable = true) - { - $this->_logQueries = $enable; - - return $this; - } - - /** - * Disable query logging - * - * @return $this - * @deprecated 4.5.0 Connection logging is moving to the driver in 5.x - */ - public function disableQueryLogging() - { - $this->_logQueries = false; - - return $this; - } - - /** - * Check if query logging is enabled. - * - * @return bool - * @deprecated 4.5.0 Connection logging is moving to the driver in 5.x - */ - public function isQueryLoggingEnabled(): bool - { - return $this->_logQueries; - } - - /** - * Sets a logger - * - * @param \Psr\Log\LoggerInterface $logger Logger object - * @return $this - * @psalm-suppress ImplementedReturnTypeMismatch - */ - public function setLogger(LoggerInterface $logger) - { - $this->_logger = $logger; - - return $this; - } - - /** - * Gets the logger object - * - * @return \Psr\Log\LoggerInterface logger instance - */ - public function getLogger(): LoggerInterface - { - if ($this->_logger !== null) { - return $this->_logger; - } - - if (!class_exists(BaseLog::class)) { - throw new RuntimeException( - 'For logging you must either set a logger using Connection::setLogger()' . - ' or require the cakephp/log package in your composer config.' - ); - } - - return $this->_logger = new QueryLogger(['connection' => $this->configName()]); - } - - /** - * Logs a Query string using the configured logger object. - * - * @param string $sql string to be logged - * @return void - */ - public function log(string $sql): void - { - $query = new LoggedQuery(); - $query->query = $sql; - $this->getLogger()->debug((string)$query, ['query' => $query]); - } - - /** - * Returns a new statement object that will log the activity - * for the passed original statement instance. - * - * @param \Cake\Database\StatementInterface $statement the instance to be decorated - * @return \Cake\Database\Log\LoggingStatement - */ - protected function _newLogger(StatementInterface $statement): LoggingStatement - { - $log = new LoggingStatement($statement, $this->getDriver()); - $log->setLogger($this->getLogger()); - - return $log; - } - /** * Returns an array that can be used to describe the internal state of this * object. @@ -1175,11 +792,9 @@ public function __debugInfo(): array $config = $replace + $this->_config; if (isset($config['read'])) { - /** @psalm-suppress PossiblyInvalidArgument */ $config['read'] = array_intersect_key($secrets, $config['read']) + $config['read']; } if (isset($config['write'])) { - /** @psalm-suppress PossiblyInvalidArgument */ $config['write'] = array_intersect_key($secrets, $config['write']) + $config['write']; } @@ -1190,8 +805,6 @@ public function __debugInfo(): array 'transactionLevel' => $this->_transactionLevel, 'transactionStarted' => $this->_transactionStarted, 'useSavePoints' => $this->_useSavePoints, - 'logQueries' => $this->_logQueries, - 'logger' => $this->_logger, ]; } } diff --git a/app/vendor/cakephp/cakephp/src/Database/ConstraintsInterface.php b/app/vendor/cakephp/cakephp/src/Database/ConstraintsInterface.php index f1fe3c161..580dc4916 100644 --- a/app/vendor/cakephp/cakephp/src/Database/ConstraintsInterface.php +++ b/app/vendor/cakephp/cakephp/src/Database/ConstraintsInterface.php @@ -21,9 +21,7 @@ /** * Defines the interface for a fixture that needs to manage constraints. * - * If an implementation of `Cake\Datasource\FixtureInterface` also implements - * this interface, the FixtureManager will use these methods to manage - * a fixtures constraints. + * @deprecated 5.2.5 This interface is no longer used. */ interface ConstraintsInterface { diff --git a/app/vendor/cakephp/cakephp/src/Database/Driver.php b/app/vendor/cakephp/cakephp/src/Database/Driver.php index 5d43f1acc..98d76ab16 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Driver.php +++ b/app/vendor/cakephp/cakephp/src/Database/Driver.php @@ -17,24 +17,41 @@ namespace Cake\Database; use Cake\Core\App; +use Cake\Core\Exception\CakeException; use Cake\Core\Retry\CommandRetry; +use Cake\Database\Exception\DatabaseException; use Cake\Database\Exception\MissingConnectionException; +use Cake\Database\Exception\QueryException; +use Cake\Database\Expression\ComparisonExpression; +use Cake\Database\Expression\IdentifierExpression; +use Cake\Database\Expression\QueryExpression; +use Cake\Database\Log\LoggedQuery; +use Cake\Database\Log\QueryLogger; +use Cake\Database\Query\DeleteQuery; +use Cake\Database\Query\InsertQuery; +use Cake\Database\Query\SelectQuery; +use Cake\Database\Query\UpdateQuery; use Cake\Database\Retry\ErrorCodeWaitStrategy; use Cake\Database\Schema\SchemaDialect; use Cake\Database\Schema\TableSchema; -use Cake\Database\Statement\PDOStatement; -use Closure; +use Cake\Database\Schema\TableSchemaInterface; +use Cake\Database\Statement\Statement; use InvalidArgumentException; use PDO; use PDOException; -use function Cake\Core\deprecationWarning; +use Psr\Log\LoggerAwareInterface; +use Psr\Log\LoggerAwareTrait; +use Psr\Log\LoggerInterface; +use Stringable; /** * Represents a database driver containing all specificities for * a database engine including its SQL dialect. */ -abstract class Driver implements DriverInterface +abstract class Driver implements LoggerAwareInterface { + use LoggerAwareTrait; + /** * @var int|null Maximum alias length or null if no limit */ @@ -45,19 +62,24 @@ abstract class Driver implements DriverInterface */ protected const RETRY_ERROR_CODES = []; + /** + * @var class-string<\Cake\Database\Statement\Statement> + */ + protected const STATEMENT_CLASS = Statement::class; + /** * Instance of PDO. * - * @var \PDO + * @var \PDO|null */ - protected $_connection; + protected ?PDO $pdo = null; /** * Configuration data. * * @var array */ - protected $_config; + protected array $_config = []; /** * Base configuration that is merged into the user @@ -65,7 +87,7 @@ abstract class Driver implements DriverInterface * * @var array */ - protected $_baseConfig = []; + protected array $_baseConfig = []; /** * Indicates whether the driver is doing automatic identifier quoting @@ -73,21 +95,56 @@ abstract class Driver implements DriverInterface * * @var bool */ - protected $_autoQuoting = false; + protected bool $_autoQuoting = false; + + /** + * String used to start a database identifier quoting to make it safe + * + * @var string + */ + protected string $_startQuote = ''; + + /** + * String used to end a database identifier quoting to make it safe + * + * @var string + */ + protected string $_endQuote = ''; + + /** + * Identifier quoter + * + * @var \Cake\Database\IdentifierQuoter|null + */ + protected ?IdentifierQuoter $quoter = null; /** * The server version * * @var string|null */ - protected $_version; + protected ?string $_version = null; + + /** + * Whether to log queries generated during this connection. + * + * @var bool + */ + protected bool $logQueries = false; /** * The last number of connection retry attempts. * * @var int */ - protected $connectRetries = 0; + protected int $connectRetries = 0; + + /** + * The schema dialect for this driver + * + * @var \Cake\Database\Schema\SchemaDialect + */ + protected SchemaDialect $_schemaDialect; /** * Constructor @@ -99,14 +156,18 @@ public function __construct(array $config = []) { if (empty($config['username']) && !empty($config['login'])) { throw new InvalidArgumentException( - 'Please pass "username" instead of "login" for connecting to the database' + 'Please pass "username" instead of "login" for connecting to the database', ); } - $config += $this->_baseConfig; + $config += $this->_baseConfig + ['log' => false]; $this->_config = $config; if (!empty($config['quoteIdentifiers'])) { $this->enableAutoQuoting(); } + if ($config['log'] !== false) { + $this->logQueries = true; + $this->logger = $this->createLogger($config['log'] === true ? null : $config['log']); + } } /** @@ -124,22 +185,20 @@ public function config(): array * * @param string $dsn A Driver-specific PDO-DSN * @param array $config configuration to be used for creating connection - * @return bool true on success + * @return \PDO */ - protected function _connect(string $dsn, array $config): bool + protected function createPdo(string $dsn, array $config): PDO { - $action = function () use ($dsn, $config) { - $this->setConnection(new PDO( - $dsn, - $config['username'] ?: null, - $config['password'] ?: null, - $config['flags'] - )); - }; + $action = fn(): PDO => new PDO( + $dsn, + $config['username'] ?: null, + $config['password'] ?: null, + $config['flags'], + ); $retry = new CommandRetry(new ErrorCodeWaitStrategy(static::RETRY_ERROR_CODES, 5), 4); try { - $retry->run($action); + return $retry->run($action); } catch (PDOException $e) { throw new MissingConnectionException( [ @@ -147,27 +206,29 @@ protected function _connect(string $dsn, array $config): bool 'reason' => $e->getMessage(), ], null, - $e + $e, ); } finally { $this->connectRetries = $retry->getRetries(); } - - return true; } /** - * @inheritDoc + * Establishes a connection to the database server. + * + * @throws \Cake\Database\Exception\MissingConnectionException If database connection could not be established. + * @return void */ - abstract public function connect(): bool; + abstract public function connect(): void; /** - * @inheritDoc + * Disconnects from database server. + * + * @return void */ public function disconnect(): void { - /** @psalm-suppress PossiblyNullPropertyAssignmentValue */ - $this->_connection = null; + $this->pdo = null; $this->_version = null; } @@ -178,98 +239,239 @@ public function disconnect(): void */ public function version(): string { - if ($this->_version === null) { + return $this->_version ??= (string)$this->getPdo()->getAttribute(PDO::ATTR_SERVER_VERSION); + } + + /** + * Get the PDO connection instance. + * + * @return \PDO + */ + protected function getPdo(): PDO + { + if ($this->pdo === null) { $this->connect(); - $this->_version = (string)$this->_connection->getAttribute(PDO::ATTR_SERVER_VERSION); } + assert($this->pdo !== null); - return $this->_version; + return $this->pdo; } /** - * Get the internal PDO connection instance. + * Execute the SQL query using the internal PDO instance. * - * @return \PDO + * @param string $sql SQL query. + * @return int|false */ - public function getConnection() + public function exec(string $sql): int|false { - if ($this->_connection === null) { - throw new MissingConnectionException([ - 'driver' => App::shortName(static::class, 'Database/Driver'), - 'reason' => 'Unknown', - ]); + try { + return $this->getPdo()->exec($sql); + } catch (PDOException $e) { + throw new QueryException($sql, $e); } + } - return $this->_connection; + /** + * Returns whether php is able to use this driver for connecting to database. + * + * @return bool True if it is valid to use this driver. + */ + abstract public function enabled(): bool; + + /** + * Executes a query using $params for interpolating values and $types as a hint for each + * those params. + * + * @param string $sql SQL to be executed and interpolated with $params + * @param array $params List or associative array of params to be interpolated in $sql as values. + * @param array $types List or associative array of types to be used for casting values in query. + * @return \Cake\Database\StatementInterface Executed statement + */ + public function execute(string $sql, array $params = [], array $types = []): StatementInterface + { + $statement = $this->prepare($sql); + if ($params) { + $statement->bind($params, $types); + } + $this->executeStatement($statement); + + return $statement; } /** - * Set the internal PDO connection instance. + * Executes the provided query after compiling it for the specific driver + * dialect and returns the executed Statement object. * - * @param \PDO $connection PDO instance. - * @return $this - * @psalm-suppress MoreSpecificImplementedParamType + * @param \Cake\Database\Query $query The query to be executed. + * @return \Cake\Database\StatementInterface Executed statement */ - public function setConnection($connection) + public function run(Query $query): StatementInterface { - $this->_connection = $connection; + $statement = $this->prepare($query); + $query->getValueBinder()->attachTo($statement); + $this->executeStatement($statement); - return $this; + return $statement; } /** - * @inheritDoc + * Execute the statement and log the query string. + * + * @param \Cake\Database\StatementInterface $statement Statement to execute. + * @param array|null $params List of values to be bound to query. + * @return void */ - abstract public function enabled(): bool; + protected function executeStatement(StatementInterface $statement, ?array $params = null): void + { + if ($this->logger === null) { + try { + $statement->execute($params); + } catch (PDOException $e) { + throw $this->createQueryException($e, $statement, $params); + } + + return; + } + + $exception = null; + $took = 0.0; + + try { + $start = microtime(true); + $statement->execute($params); + $took = (float)number_format((microtime(true) - $start) * 1000, 1); + } catch (PDOException $e) { + $exception = $e; + } + + $logContext = [ + 'driver' => $this, + 'error' => $exception, + 'params' => $params ?? $statement->getBoundParams(), + ]; + if (!$exception) { + $logContext['numRows'] = $statement->rowCount(); + $logContext['took'] = $took; + } + $this->log($statement->queryString(), $logContext); + + if ($exception) { + throw $this->createQueryException($exception, $statement, $params); + } + } /** - * @inheritDoc + * Create a QueryException from a PDOException + * + * @param \PDOException $exception + * @param \Cake\Database\StatementInterface $statement + * @param array|null $params + * @return \Cake\Database\Exception\QueryException + */ + protected function createQueryException( + PDOException $exception, + StatementInterface $statement, + ?array $params = null, + ): QueryException { + $loggedQuery = new LoggedQuery(); + $loggedQuery->setContext([ + 'query' => $statement->queryString(), + 'driver' => $this, + 'params' => $params ?? $statement->getBoundParams(), + ]); + + return new QueryException($loggedQuery, $exception); + } + + /** + * Prepares a sql statement to be executed. + * + * @param \Cake\Database\Query|string $query The query to turn into a prepared statement. + * @return \Cake\Database\StatementInterface */ - public function prepare($query): StatementInterface + public function prepare(Query|string $query): StatementInterface { - $this->connect(); - $statement = $this->_connection->prepare($query instanceof Query ? $query->sql() : $query); + try { + $statement = $this->getPdo()->prepare($query instanceof Query ? $query->sql() : $query); + } catch (PDOException $e) { + throw new QueryException( + $query instanceof Query ? $query->sql() : $query, + $e, + ); + } - return new PDOStatement($statement, $this); + /** @var \Cake\Database\StatementInterface */ + return new (static::STATEMENT_CLASS)($statement, $this, $this->getResultSetDecorators($query)); } /** - * @inheritDoc + * Returns the decorators to be applied to the result set incase of a SelectQuery. + * + * @param \Cake\Database\Query|string $query The query to be decorated. + * @return array<\Closure> + */ + protected function getResultSetDecorators(Query|string $query): array + { + if ($query instanceof SelectQuery) { + $decorators = $query->getResultDecorators(); + if ($query->isResultsCastingEnabled()) { + $typeConverter = new FieldTypeConverter($query->getSelectTypeMap(), $this); + array_unshift($decorators, $typeConverter(...)); + } + + return $decorators; + } + + return []; + } + + /** + * Starts a transaction. + * + * @return bool True on success, false otherwise. */ public function beginTransaction(): bool { - $this->connect(); - if ($this->_connection->inTransaction()) { + if ($this->getPdo()->inTransaction()) { return true; } - return $this->_connection->beginTransaction(); + $this->log('BEGIN'); + + return $this->getPdo()->beginTransaction(); } /** - * @inheritDoc + * Commits a transaction. + * + * @return bool True on success, false otherwise. */ public function commitTransaction(): bool { - $this->connect(); - if (!$this->_connection->inTransaction()) { + if (!$this->getPdo()->inTransaction()) { return false; } - return $this->_connection->commit(); + $this->log('COMMIT'); + + return $this->getPdo()->commit(); } /** - * @inheritDoc + * Rollbacks a transaction. + * + * @return bool True on success, false otherwise. */ public function rollbackTransaction(): bool { - $this->connect(); - if (!$this->_connection->inTransaction()) { + if (!$this->getPdo()->inTransaction()) { return false; } - return $this->_connection->rollBack(); + $this->log('ROLLBACK'); + + return $this->getPdo()->rollBack(); } /** @@ -279,76 +481,312 @@ public function rollbackTransaction(): bool */ public function inTransaction(): bool { - $this->connect(); + return $this->getPdo()->inTransaction(); + } - return $this->_connection->inTransaction(); + /** + * Returns a SQL snippet for creating a new transaction savepoint + * + * @param string|int $name save point name + * @return string + */ + public function savePointSQL(string|int $name): string + { + return 'SAVEPOINT LEVEL' . $name; } /** - * @inheritDoc + * Returns a SQL snippet for releasing a previously created save point + * + * @param string|int $name save point name + * @return string */ - public function supportsSavePoints(): bool + public function releaseSavePointSQL(string|int $name): string { - deprecationWarning('Feature support checks are now implemented by `supports()` with FEATURE_* constants.'); + return 'RELEASE SAVEPOINT LEVEL' . $name; + } - return $this->supports(static::FEATURE_SAVEPOINT); + /** + * Returns a SQL snippet for rollbacking a previously created save point + * + * @param string|int $name save point name + * @return string + */ + public function rollbackSavePointSQL(string|int $name): string + { + return 'ROLLBACK TO SAVEPOINT LEVEL' . $name; } /** - * Returns true if the server supports common table expressions. + * Get the SQL for disabling foreign keys. * - * @return bool - * @deprecated 4.3.0 Use `supports(DriverInterface::FEATURE_QUOTE)` instead + * @return string */ - public function supportsCTEs(): bool + abstract public function disableForeignKeySQL(): string; + + /** + * Get the SQL for enabling foreign keys. + * + * @return string + */ + abstract public function enableForeignKeySQL(): string; + + /** + * Transform the query to accommodate any specificities of the SQL dialect in use. + * + * It will also quote the identifiers if auto quoting is enabled. + * + * @param \Cake\Database\Query $query Query to transform. + * @return \Cake\Database\Query + */ + protected function transformQuery(Query $query): Query { - deprecationWarning('Feature support checks are now implemented by `supports()` with FEATURE_* constants.'); + if ($this->isAutoQuotingEnabled()) { + $query = $this->quoter()->quote($query); + } + + $query = match (true) { + $query instanceof SelectQuery => $this->_selectQueryTranslator($query), + $query instanceof InsertQuery => $this->_insertQueryTranslator($query), + $query instanceof UpdateQuery => $this->_updateQueryTranslator($query), + $query instanceof DeleteQuery => $this->_deleteQueryTranslator($query), + default => throw new InvalidArgumentException(sprintf( + 'Instance of SelectQuery, UpdateQuery, InsertQuery, DeleteQuery expected. Found `%s` instead.', + get_debug_type($query), + )), + }; + + $translators = $this->_expressionTranslators(); + if (!$translators) { + return $query; + } + + $query->traverseExpressions(function ($expression) use ($translators, $query): void { + foreach ($translators as $class => $method) { + if ($expression instanceof $class) { + $this->{$method}($expression, $query); + } + } + }); - return $this->supports(static::FEATURE_CTE); + return $query; } /** - * @inheritDoc + * Returns an associative array of methods that will transform Expression + * objects to conform with the specific SQL dialect. Keys are class names + * and values a method in this class. + * + * @return array */ - public function quote($value, $type = PDO::PARAM_STR): string + protected function _expressionTranslators(): array { - $this->connect(); + return []; + } - return $this->_connection->quote((string)$value, $type); + /** + * Apply translation steps to select queries. + * + * @param \Cake\Database\Query\SelectQuery $query The query to translate + * @return \Cake\Database\Query\SelectQuery The modified query + */ + protected function _selectQueryTranslator(SelectQuery $query): SelectQuery + { + return $this->_transformDistinct($query); } /** - * Checks if the driver supports quoting, as PDO_ODBC does not support it. + * Returns the passed query after rewriting the DISTINCT clause, so that drivers + * that do not support the "ON" part can provide the actual way it should be done * - * @return bool - * @deprecated 4.3.0 Use `supports(DriverInterface::FEATURE_QUOTE)` instead + * @param \Cake\Database\Query\SelectQuery $query The query to be transformed + * @return \Cake\Database\Query\SelectQuery */ - public function supportsQuoting(): bool + protected function _transformDistinct(SelectQuery $query): SelectQuery { - deprecationWarning('Feature support checks are now implemented by `supports()` with FEATURE_* constants.'); + if (is_array($query->clause('distinct'))) { + $query->groupBy($query->clause('distinct'), true); + $query->distinct(false); + } - return $this->supports(static::FEATURE_QUOTE); + return $query; } /** - * @inheritDoc + * Apply translation steps to delete queries. + * + * Chops out aliases on delete query conditions as most database dialects do not + * support aliases in delete queries. This also removes aliases + * in table names as they frequently don't work either. + * + * We are intentionally not supporting deletes with joins as they have even poorer support. + * + * @param \Cake\Database\Query\DeleteQuery $query The query to translate + * @return \Cake\Database\Query\DeleteQuery The modified query */ - abstract public function queryTranslator(string $type): Closure; + protected function _deleteQueryTranslator(DeleteQuery $query): DeleteQuery + { + $hadAlias = false; + $tables = []; + foreach ($query->clause('from') as $alias => $table) { + if (is_string($alias)) { + $hadAlias = true; + } + $tables[] = $table; + } + if ($hadAlias) { + $query->from($tables, true); + } + + if (!$hadAlias) { + return $query; + } + + return $this->_removeAliasesFromConditions($query); + } /** - * @inheritDoc + * Apply translation steps to update queries. + * + * Chops out aliases on update query conditions as not all database dialects do support + * aliases in update queries. + * + * Just like for delete queries, joins are currently not supported for update queries. + * + * @param \Cake\Database\Query\UpdateQuery $query The query to translate + * @return \Cake\Database\Query\UpdateQuery The modified query + */ + protected function _updateQueryTranslator(UpdateQuery $query): UpdateQuery + { + return $this->_removeAliasesFromConditions($query); + } + + /** + * Removes aliases from the `WHERE` clause of a query. + * + * @param \Cake\Database\Query\UpdateQuery|\Cake\Database\Query\DeleteQuery $query The query to process. + * @return \Cake\Database\Query\UpdateQuery|\Cake\Database\Query\DeleteQuery The modified query. + * @throws \Cake\Database\Exception\DatabaseException In case the processed query contains any joins, as removing + * aliases from the conditions can break references to the joined tables. + * @template T of \Cake\Database\Query\UpdateQuery|\Cake\Database\Query\DeleteQuery + * @phpstan-param T $query + * @phpstan-return T + */ + protected function _removeAliasesFromConditions(UpdateQuery|DeleteQuery $query): UpdateQuery|DeleteQuery + { + if ($query->clause('join')) { + throw new DatabaseException( + 'Aliases are being removed from conditions for UPDATE/DELETE queries, ' . + 'this can break references to joined tables.', + ); + } + + $conditions = $query->clause('where'); + assert($conditions === null || $conditions instanceof ExpressionInterface); + if ($conditions) { + $conditions->traverse(function ($expression) { + if ($expression instanceof ComparisonExpression) { + $field = $expression->getField(); + if ( + is_string($field) && + str_contains($field, '.') + ) { + [, $unaliasedField] = explode('.', $field, 2); + $expression->setField($unaliasedField); + } + + return $expression; + } + + if ($expression instanceof IdentifierExpression) { + $identifier = $expression->getIdentifier(); + if (str_contains($identifier, '.')) { + [, $unaliasedIdentifier] = explode('.', $identifier, 2); + $expression->setIdentifier($unaliasedIdentifier); + } + + return $expression; + } + + return $expression; + }); + } + + return $query; + } + + /** + * Apply translation steps to insert queries. + * + * @param \Cake\Database\Query\InsertQuery $query The query to translate + * @return \Cake\Database\Query\InsertQuery The modified query + */ + protected function _insertQueryTranslator(InsertQuery $query): InsertQuery + { + return $query; + } + + /** + * Get the schema dialect. + * + * Used by {@link \Cake\Database\Schema} package to reflect schema and + * generate schema. + * + * If all the tables that use this Driver specify their + * own schemas, then this may return null. + * + * @return \Cake\Database\Schema\SchemaDialect */ abstract public function schemaDialect(): SchemaDialect; /** - * @inheritDoc + * Quotes a database identifier (a column name, table name, etc..) to + * be used safely in queries without the risk of using reserved words + * + * @param string $identifier The identifier to quote. + * @return string */ - abstract public function quoteIdentifier(string $identifier): string; + public function quoteIdentifier(string $identifier): string + { + return $this->quoter()->quoteIdentifier($identifier); + } /** - * @inheritDoc + * Quotes a database value. + * + * This makes values safe for concatenation in SQL queries. + * + * Using this method **is not** recommended. You should use `execute()` + * instead, as it uses prepared statements which are safer than + * string concatenation. + * + * This method should only be used for queries that do not support placeholders. + * + * @param string $value The value to quote. + * @return string */ - public function schemaValue($value): string + public function quote(string $value): string + { + return $this->getPdo()->quote($value); + } + + /** + * Get identifier quoter instance. + * + * @return \Cake\Database\IdentifierQuoter + */ + public function quoter(): IdentifierQuoter + { + return $this->quoter ??= new IdentifierQuoter($this->_startQuote, $this->_endQuote); + } + + /** + * Escapes values for use in schema definitions. + * + * @param mixed $value The value to escape. + * @return string String for use in schema definitions. + */ + public function schemaValue(mixed $value): string { if ($value === null) { return 'NULL'; @@ -362,7 +800,6 @@ public function schemaValue($value): string if (is_float($value)) { return str_replace(',', '.', (string)$value); } - /** @psalm-suppress InvalidArgument */ if ( ( is_int($value) || @@ -370,19 +807,24 @@ public function schemaValue($value): string ) || ( is_numeric($value) && - strpos($value, ',') === false && - substr($value, 0, 1) !== '0' && - strpos($value, 'e') === false + !str_contains($value, ',') && + !str_starts_with($value, '0') && + !str_contains($value, 'e') ) ) { return (string)$value; } + if ($value instanceof QueryExpression) { + return $value->sql(new ValueBinder()); + } - return $this->_connection->quote((string)$value, PDO::PARAM_STR); + return $this->getPdo()->quote((string)$value, PDO::PARAM_STR); } /** - * @inheritDoc + * Returns the schema name that's being used. + * + * @return string */ public function schema(): string { @@ -390,39 +832,40 @@ public function schema(): string } /** - * @inheritDoc + * Returns last id generated for a table or sequence in database. + * + * @param string|null $table table name or sequence to get last insert value from. + * @return string */ - public function lastInsertId(?string $table = null, ?string $column = null) + public function lastInsertId(?string $table = null): string { - $this->connect(); - - if ($this->_connection instanceof PDO) { - return $this->_connection->lastInsertId($table); - } - - return $this->_connection->lastInsertId($table); + return (string)$this->getPdo()->lastInsertId($table); } /** - * @inheritDoc + * Checks whether the driver is connected. + * + * @return bool */ public function isConnected(): bool { - if ($this->_connection === null) { - $connected = false; - } else { - try { - $connected = (bool)$this->_connection->query('SELECT 1'); - } catch (PDOException $e) { - $connected = false; - } + if ($this->pdo === null) { + return false; } - return $connected; + try { + return (bool)$this->pdo->query('SELECT 1'); + } catch (PDOException) { + return false; + } } /** - * @inheritDoc + * Sets whether this driver should automatically quote identifiers + * in queries. + * + * @param bool $enable Whether to enable auto quoting + * @return $this */ public function enableAutoQuoting(bool $enable = true) { @@ -432,7 +875,9 @@ public function enableAutoQuoting(bool $enable = true) } /** - * @inheritDoc + * Disable auto quoting of identifiers in queries. + * + * @return $this */ public function disableAutoQuoting() { @@ -442,7 +887,10 @@ public function disableAutoQuoting() } /** - * @inheritDoc + * Returns whether this driver should automatically quote identifiers + * in queries. + * + * @return bool */ public function isAutoQuotingEnabled(): bool { @@ -452,37 +900,31 @@ public function isAutoQuotingEnabled(): bool /** * Returns whether the driver supports the feature. * - * Defaults to true for FEATURE_QUOTE and FEATURE_SAVEPOINT. + * Should return false for unknown features. * - * @param string $feature Driver feature name + * @param \Cake\Database\DriverFeatureEnum $feature Driver feature * @return bool */ - public function supports(string $feature): bool - { - switch ($feature) { - case static::FEATURE_DISABLE_CONSTRAINT_WITHOUT_TRANSACTION: - case static::FEATURE_QUOTE: - case static::FEATURE_SAVEPOINT: - return true; - } - - return false; - } + abstract public function supports(DriverFeatureEnum $feature): bool; /** - * @inheritDoc + * Transforms the passed query to this Driver's dialect and returns an instance + * of the transformed query and the full compiled SQL string. + * + * @param \Cake\Database\Query $query The query to compile. + * @param \Cake\Database\ValueBinder $binder The value binder to use. + * @return string The compiled SQL. */ - public function compileQuery(Query $query, ValueBinder $binder): array + public function compileQuery(Query $query, ValueBinder $binder): string { $processor = $this->newCompiler(); - $translator = $this->queryTranslator($query->type()); - $query = $translator($query); + $query = $this->transformQuery($query); - return [$query, $processor->compile($query, $binder)]; + return $processor->compile($query, $binder); } /** - * @inheritDoc + * @return \Cake\Database\QueryCompiler */ public function newCompiler(): QueryCompiler { @@ -490,21 +932,23 @@ public function newCompiler(): QueryCompiler } /** - * @inheritDoc + * Constructs new TableSchema. + * + * @param string $table The table name. + * @param array $columns The list of columns for the schema. + * @return \Cake\Database\Schema\TableSchemaInterface */ - public function newTableSchema(string $table, array $columns = []): TableSchema + public function newTableSchema(string $table, array $columns = []): TableSchemaInterface { - $className = TableSchema::class; - if (isset($this->_config['tableSchema'])) { - /** @var class-string<\Cake\Database\Schema\TableSchema> $className */ - $className = $this->_config['tableSchema']; - } + /** @var class-string<\Cake\Database\Schema\TableSchemaInterface> $className */ + $className = $this->_config['tableSchema'] ?? TableSchema::class; return new $className($table, $columns); } /** * Returns the maximum alias length allowed. + * * This can be different from the maximum identifier length for columns. * * @return int|null Maximum alias length or null if no limit @@ -515,13 +959,57 @@ public function getMaxAliasLength(): ?int } /** - * Returns the number of connection retry attempts made. + * Get the logger instance. * - * @return int + * @return \Psr\Log\LoggerInterface|null */ - public function getConnectRetries(): int + public function getLogger(): ?LoggerInterface { - return $this->connectRetries; + return $this->logger; + } + + /** + * Create logger instance. + * + * @param string|null $className Logger's class name + * @return \Psr\Log\LoggerInterface + */ + protected function createLogger(?string $className): LoggerInterface + { + $className ??= QueryLogger::class; + + /** @var class-string<\Psr\Log\LoggerInterface>|null $className */ + $className = App::className($className, 'Cake/Log', 'Log'); + if ($className === null) { + throw new CakeException( + 'For logging you must either set the `log` config to a FQCN which implements Psr\Log\LoggerInterface' . + ' or require the cakephp/log package in your composer config.', + ); + } + + return new $className(); + } + + /** + * Logs a message or query using the configured logger object. + * + * @param \Stringable|string $message Message string or query. + * @param array $context Logging context. + * @return bool True if message was logged. + */ + public function log(Stringable|string $message, array $context = []): bool + { + if ($this->logger === null || !$this->logQueries) { + return false; + } + + $context['query'] = $message; + $loggedQuery = new LoggedQuery(); + $loggedQuery->setContext($context); + + $this->logger->debug((string)$loggedQuery, ['query' => $loggedQuery]); + + return true; } /** @@ -534,13 +1022,45 @@ public function getRole(): string return $this->_config['_role'] ?? Connection::ROLE_WRITE; } + /** + * Enable query logging. + * + * @return $this + */ + public function enableQueryLogging() + { + $this->logQueries = true; + + return $this; + } + + /** + * Disable query logging. + * + * @return $this + */ + public function disableQueryLogging() + { + $this->logQueries = false; + + return $this; + } + + /** + * @inheritDoc + */ + public function setLogger(LoggerInterface $logger): void + { + $this->logger = $logger; + $this->enableQueryLogging(); + } + /** * Destructor */ public function __destruct() { - /** @psalm-suppress PossiblyNullPropertyAssignmentValue */ - $this->_connection = null; + $this->pdo = null; } /** @@ -552,7 +1072,7 @@ public function __destruct() public function __debugInfo(): array { return [ - 'connected' => $this->_connection !== null, + 'connected' => $this->pdo !== null, 'role' => $this->getRole(), ]; } diff --git a/app/vendor/cakephp/cakephp/src/Database/Driver/Mysql.php b/app/vendor/cakephp/cakephp/src/Database/Driver/Mysql.php index b7d30b69f..9f85d35d8 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Driver/Mysql.php +++ b/app/vendor/cakephp/cakephp/src/Database/Driver/Mysql.php @@ -17,21 +17,19 @@ namespace Cake\Database\Driver; use Cake\Database\Driver; +use Cake\Database\DriverFeatureEnum; use Cake\Database\Query; +use Cake\Database\Query\SelectQuery; use Cake\Database\Schema\MysqlSchemaDialect; use Cake\Database\Schema\SchemaDialect; -use Cake\Database\Statement\MysqlStatement; use Cake\Database\StatementInterface; use PDO; -use function Cake\Core\deprecationWarning; /** * MySQL Driver */ class Mysql extends Driver { - use SqlDialectTrait; - /** * @inheritDoc */ @@ -56,7 +54,7 @@ class Mysql extends Driver * * @var array */ - protected $_baseConfig = [ + protected array $_baseConfig = [ 'persistent' => true, 'host' => 'localhost', 'username' => 'root', @@ -69,26 +67,19 @@ class Mysql extends Driver 'init' => [], ]; - /** - * The schema dialect for this driver - * - * @var \Cake\Database\Schema\MysqlSchemaDialect|null - */ - protected $_schemaDialect; - /** * String used to start a database identifier quoting to make it safe * * @var string */ - protected $_startQuote = '`'; + protected string $_startQuote = '`'; /** * String used to end a database identifier quoting to make it safe * * @var string */ - protected $_endQuote = '`'; + protected string $_endQuote = '`'; /** * Server type. @@ -98,35 +89,37 @@ class Mysql extends Driver * * @var string */ - protected $serverType = self::SERVER_TYPE_MYSQL; + protected string $serverType = self::SERVER_TYPE_MYSQL; /** * Mapping of feature to db server version for feature availability checks. * * @var array> */ - protected $featureVersions = [ + protected array $featureVersions = [ 'mysql' => [ 'json' => '5.7.0', 'cte' => '8.0.0', 'window' => '8.0.0', + 'intersect' => '8.0.31', + 'intersect-all' => '8.0.31', ], 'mariadb' => [ 'json' => '10.2.7', 'cte' => '10.2.1', 'window' => '10.2.0', + 'intersect' => '10.3.0', + 'intersect-all' => '10.5.0', ], ]; /** - * Establishes a connection to the database server - * - * @return bool true on success + * @inheritDoc */ - public function connect(): bool + public function connect(): void { - if ($this->_connection) { - return true; + if ($this->pdo !== null) { + return; } $config = $this->_config; @@ -162,50 +155,45 @@ public function connect(): bool $dsn .= ";charset={$config['encoding']}"; } - $this->_connect($dsn, $config); + $this->pdo = $this->createPdo($dsn, $config); if (!empty($config['init'])) { - $connection = $this->getConnection(); foreach ((array)$config['init'] as $command) { - $connection->exec($command); + $this->pdo->exec($command); } } - - return true; } /** - * Returns whether php is able to use this driver for connecting to database - * - * @return bool true if it is valid to use this driver + * @inheritDoc */ - public function enabled(): bool + public function run(Query $query): StatementInterface { - return in_array('mysql', PDO::getAvailableDrivers(), true); + $statement = $this->prepare($query); + $query->getValueBinder()->attachTo($statement); + + if ($query instanceof SelectQuery) { + try { + $this->getPdo()->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, $query->isBufferedResultsEnabled()); + $this->executeStatement($statement); + } finally { + $this->getPdo()->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true); + } + } else { + $this->executeStatement($statement); + } + + return $statement; } /** - * Prepares a sql statement to be executed + * Returns whether php is able to use this driver for connecting to database * - * @param \Cake\Database\Query|string $query The query to prepare. - * @return \Cake\Database\StatementInterface + * @return bool true if it is valid to use this driver */ - public function prepare($query): StatementInterface + public function enabled(): bool { - $this->connect(); - $isObject = $query instanceof Query; - /** - * @psalm-suppress PossiblyInvalidMethodCall - * @psalm-suppress PossiblyInvalidArgument - */ - $statement = $this->_connection->prepare($isObject ? $query->sql() : $query); - $result = new MysqlStatement($statement, $this); - /** @psalm-suppress PossiblyInvalidMethodCall */ - if ($isObject && $query->isBufferedResultsEnabled() === false) { - $result->bufferResults(false); - } - - return $result; + return in_array('mysql', PDO::getAvailableDrivers(), true); } /** @@ -213,11 +201,7 @@ public function prepare($query): StatementInterface */ public function schemaDialect(): SchemaDialect { - if ($this->_schemaDialect === null) { - $this->_schemaDialect = new MysqlSchemaDialect($this); - } - - return $this->_schemaDialect; + return $this->_schemaDialect ?? ($this->_schemaDialect = new MysqlSchemaDialect($this)); } /** @@ -229,7 +213,9 @@ public function schema(): string } /** - * @inheritDoc + * Get the SQL for disabling foreign keys. + * + * @return string */ public function disableForeignKeySQL(): string { @@ -247,28 +233,29 @@ public function enableForeignKeySQL(): string /** * @inheritDoc */ - public function supports(string $feature): bool + public function supports(DriverFeatureEnum $feature): bool { - switch ($feature) { - case static::FEATURE_CTE: - case static::FEATURE_JSON: - case static::FEATURE_WINDOW: - return version_compare( - $this->version(), - $this->featureVersions[$this->serverType][$feature], - '>=' - ); - } - - return parent::supports($feature); - } - - /** - * @inheritDoc - */ - public function supportsDynamicConstraints(): bool - { - return true; + $versionCompare = function () use ($feature) { + return version_compare( + $this->version(), + $this->featureVersions[$this->serverType][$feature->value], + '>=', + ); + }; + + return match ($feature) { + DriverFeatureEnum::DISABLE_CONSTRAINT_WITHOUT_TRANSACTION, + DriverFeatureEnum::SAVEPOINT => true, + + DriverFeatureEnum::TRUNCATE_WITH_CONSTRAINTS => false, + + DriverFeatureEnum::CTE, + DriverFeatureEnum::JSON, + DriverFeatureEnum::WINDOW => $versionCompare(), + DriverFeatureEnum::INTERSECT => $versionCompare(), + DriverFeatureEnum::INTERSECT_ALL => $versionCompare(), + DriverFeatureEnum::SET_OPERATIONS_ORDER_BY => true, + }; } /** @@ -291,10 +278,9 @@ public function isMariadb(): bool public function version(): string { if ($this->_version === null) { - $this->connect(); - $this->_version = (string)$this->_connection->getAttribute(PDO::ATTR_SERVER_VERSION); + $this->_version = (string)$this->getPdo()->getAttribute(PDO::ATTR_SERVER_VERSION); - if (strpos($this->_version, 'MariaDB') !== false) { + if (str_contains($this->_version, 'MariaDB')) { $this->serverType = static::SERVER_TYPE_MARIADB; preg_match('/^(?:5\.5\.5-)?(\d+\.\d+\.\d+.*-MariaDB[^:]*)/', $this->_version, $matches); $this->_version = $matches[1]; @@ -303,43 +289,4 @@ public function version(): string return $this->_version; } - - /** - * Returns true if the server supports common table expressions. - * - * @return bool - * @deprecated 4.3.0 Use `supports(DriverInterface::FEATURE_CTE)` instead - */ - public function supportsCTEs(): bool - { - deprecationWarning('Feature support checks are now implemented by `supports()` with FEATURE_* constants.'); - - return $this->supports(static::FEATURE_CTE); - } - - /** - * Returns true if the server supports native JSON columns - * - * @return bool - * @deprecated 4.3.0 Use `supports(DriverInterface::FEATURE_JSON)` instead - */ - public function supportsNativeJson(): bool - { - deprecationWarning('Feature support checks are now implemented by `supports()` with FEATURE_* constants.'); - - return $this->supports(static::FEATURE_JSON); - } - - /** - * Returns true if the connected server supports window functions. - * - * @return bool - * @deprecated 4.3.0 Use `supports(DriverInterface::FEATURE_WINDOW)` instead - */ - public function supportsWindowFunctions(): bool - { - deprecationWarning('Feature support checks are now implemented by `supports()` with FEATURE_* constants.'); - - return $this->supports(static::FEATURE_WINDOW); - } } diff --git a/app/vendor/cakephp/cakephp/src/Database/Driver/Postgres.php b/app/vendor/cakephp/cakephp/src/Database/Driver/Postgres.php index 2a32264c0..678a43bda 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Driver/Postgres.php +++ b/app/vendor/cakephp/cakephp/src/Database/Driver/Postgres.php @@ -17,11 +17,13 @@ namespace Cake\Database\Driver; use Cake\Database\Driver; +use Cake\Database\DriverFeatureEnum; use Cake\Database\Expression\FunctionExpression; use Cake\Database\Expression\IdentifierExpression; use Cake\Database\Expression\StringExpression; use Cake\Database\PostgresCompiler; -use Cake\Database\Query; +use Cake\Database\Query\InsertQuery; +use Cake\Database\Query\SelectQuery; use Cake\Database\QueryCompiler; use Cake\Database\Schema\PostgresSchemaDialect; use Cake\Database\Schema\SchemaDialect; @@ -32,8 +34,6 @@ */ class Postgres extends Driver { - use SqlDialectTrait; - /** * @inheritDoc */ @@ -44,7 +44,7 @@ class Postgres extends Driver * * @var array */ - protected $_baseConfig = [ + protected array $_baseConfig = [ 'persistent' => true, 'host' => 'localhost', 'username' => 'root', @@ -56,38 +56,34 @@ class Postgres extends Driver 'timezone' => null, 'flags' => [], 'init' => [], + 'ssl_key' => null, + 'ssl_cert' => null, + 'ssl_ca' => null, + 'ssl' => false, + 'ssl_mode' => null, ]; - /** - * The schema dialect class for this driver - * - * @var \Cake\Database\Schema\PostgresSchemaDialect|null - */ - protected $_schemaDialect; - /** * String used to start a database identifier quoting to make it safe * * @var string */ - protected $_startQuote = '"'; + protected string $_startQuote = '"'; /** * String used to end a database identifier quoting to make it safe * * @var string */ - protected $_endQuote = '"'; + protected string $_endQuote = '"'; /** - * Establishes a connection to the database server - * - * @return bool true on success + * @inheritDoc */ - public function connect(): bool + public function connect(): void { - if ($this->_connection) { - return true; + if ($this->pdo !== null) { + return; } $config = $this->_config; $config['flags'] += [ @@ -101,8 +97,25 @@ public function connect(): bool $dsn = "pgsql:dbname={$config['database']}"; } - $this->_connect($dsn, $config); - $this->_connection = $connection = $this->getConnection(); + if ($this->_config['ssl']) { + if ($this->_config['ssl_mode']) { + $dsn .= ';sslmode=' . $this->_config['ssl_mode']; + } else { + $dsn .= ';sslmode=allow'; + } + + if ($this->_config['ssl_key']) { + $dsn .= ';sslkey=' . $this->_config['ssl_key']; + } + if ($this->_config['ssl_cert']) { + $dsn .= ';sslcert=' . $this->_config['ssl_cert']; + } + if ($this->_config['ssl_ca']) { + $dsn .= ';sslrootcert=' . $this->_config['ssl_ca']; + } + } + + $this->pdo = $this->createPdo($dsn, $config); if (!empty($config['encoding'])) { $this->setEncoding($config['encoding']); } @@ -112,14 +125,13 @@ public function connect(): bool } if (!empty($config['timezone'])) { - $config['init'][] = sprintf('SET timezone = %s', $connection->quote($config['timezone'])); + $config['init'][] = sprintf('SET timezone = %s', $this->getPdo()->quote($config['timezone'])); } foreach ($config['init'] as $command) { - $connection->exec($command); + /** @phpstan-ignore-next-line */ + $this->pdo->exec($command); } - - return true; } /** @@ -137,11 +149,7 @@ public function enabled(): bool */ public function schemaDialect(): SchemaDialect { - if ($this->_schemaDialect === null) { - $this->_schemaDialect = new PostgresSchemaDialect($this); - } - - return $this->_schemaDialect; + return $this->_schemaDialect ?? ($this->_schemaDialect = new PostgresSchemaDialect($this)); } /** @@ -152,8 +160,8 @@ public function schemaDialect(): SchemaDialect */ public function setEncoding(string $encoding): void { - $this->connect(); - $this->_connection->exec('SET NAMES ' . $this->_connection->quote($encoding)); + $pdo = $this->getPdo(); + $pdo->exec('SET NAMES ' . $pdo->quote($encoding)); } /** @@ -165,12 +173,14 @@ public function setEncoding(string $encoding): void */ public function setSchema(string $schema): void { - $this->connect(); - $this->_connection->exec('SET search_path TO ' . $this->_connection->quote($schema)); + $pdo = $this->getPdo(); + $pdo->exec('SET search_path TO ' . $pdo->quote($schema)); } /** - * @inheritDoc + * Get the SQL for disabling foreign keys. + * + * @return string */ public function disableForeignKeySQL(): string { @@ -188,34 +198,25 @@ public function enableForeignKeySQL(): string /** * @inheritDoc */ - public function supports(string $feature): bool + public function supports(DriverFeatureEnum $feature): bool { - switch ($feature) { - case static::FEATURE_CTE: - case static::FEATURE_JSON: - case static::FEATURE_TRUNCATE_WITH_CONSTRAINTS: - case static::FEATURE_WINDOW: - return true; - - case static::FEATURE_DISABLE_CONSTRAINT_WITHOUT_TRANSACTION: - return false; - } - - return parent::supports($feature); - } - - /** - * @inheritDoc - */ - public function supportsDynamicConstraints(): bool - { - return true; + return match ($feature) { + DriverFeatureEnum::CTE, + DriverFeatureEnum::JSON, + DriverFeatureEnum::SAVEPOINT, + DriverFeatureEnum::TRUNCATE_WITH_CONSTRAINTS, + DriverFeatureEnum::WINDOW => true, + DriverFeatureEnum::INTERSECT => true, + DriverFeatureEnum::INTERSECT_ALL => true, + DriverFeatureEnum::SET_OPERATIONS_ORDER_BY => true, + DriverFeatureEnum::DISABLE_CONSTRAINT_WITHOUT_TRANSACTION => false, + }; } /** * @inheritDoc */ - protected function _transformDistinct(Query $query): Query + protected function _transformDistinct(SelectQuery $query): SelectQuery { return $query; } @@ -223,7 +224,7 @@ protected function _transformDistinct(Query $query): Query /** * @inheritDoc */ - protected function _insertQueryTranslator(Query $query): Query + protected function _insertQueryTranslator(InsertQuery $query): InsertQuery { if (!$query->clause('epilog')) { $query->epilog('RETURNING *'); @@ -247,7 +248,7 @@ protected function _expressionTranslators(): array /** * Changes identifer expression into postgresql format. * - * @param \Cake\Database\Expression\IdentifierExpression $expression The expression to tranform. + * @param \Cake\Database\Expression\IdentifierExpression $expression The expression to transform. * @return void */ protected function _transformIdentifierExpression(IdentifierExpression $expression): void @@ -308,7 +309,7 @@ protected function _transformFunctionExpression(FunctionExpression $expression): ->setConjunction(' + INTERVAL') ->iterateParts(function ($p, $key) { if ($key === 1) { - $p = sprintf("'%s'", $p); + return sprintf("'%s'", $p); } return $p; @@ -321,13 +322,25 @@ protected function _transformFunctionExpression(FunctionExpression $expression): ->add(['DOW FROM' => 'literal'], [], true) ->add([') + (1' => 'literal']); // Postgres starts on index 0 but Sunday should be 1 break; + case 'JSON_VALUE': + $expression->setName('JSONB_PATH_QUERY') + ->iterateParts(function ($p, $key) { + if ($key === 0) { + $p = sprintf('%s::jsonb', $p); + } elseif ($key === 1) { + $p = sprintf("'%s'::jsonpath", $this->quoteIdentifier($p['value'])); + } + + return $p; + }); + break; } } /** * Changes string expression into postgresql format. * - * @param \Cake\Database\Expression\StringExpression $expression The string expression to tranform. + * @param \Cake\Database\Expression\StringExpression $expression The string expression to transform. * @return void */ protected function _transformStringExpression(StringExpression $expression): void diff --git a/app/vendor/cakephp/cakephp/src/Database/Driver/SqlDialectTrait.php b/app/vendor/cakephp/cakephp/src/Database/Driver/SqlDialectTrait.php deleted file mode 100644 index 94a5e88b9..000000000 --- a/app/vendor/cakephp/cakephp/src/Database/Driver/SqlDialectTrait.php +++ /dev/null @@ -1,315 +0,0 @@ -_startQuote . $identifier . $this->_endQuote; - } - - // string.string - if (preg_match('/^[\w-]+\.[^ \*]*$/u', $identifier)) { - $items = explode('.', $identifier); - - return $this->_startQuote . implode($this->_endQuote . '.' . $this->_startQuote, $items) . $this->_endQuote; - } - - // string.* - if (preg_match('/^[\w-]+\.\*$/u', $identifier)) { - return $this->_startQuote . str_replace('.*', $this->_endQuote . '.*', $identifier); - } - - // Functions - if (preg_match('/^([\w-]+)\((.*)\)$/', $identifier, $matches)) { - return $matches[1] . '(' . $this->quoteIdentifier($matches[2]) . ')'; - } - - // Alias.field AS thing - if (preg_match('/^([\w-]+(\.[\w\s-]+|\(.*\))*)\s+AS\s*([\w-]+)$/ui', $identifier, $matches)) { - return $this->quoteIdentifier($matches[1]) . ' AS ' . $this->quoteIdentifier($matches[3]); - } - - // string.string with spaces - if (preg_match('/^([\w-]+\.[\w][\w\s-]*[\w])(.*)/u', $identifier, $matches)) { - $items = explode('.', $matches[1]); - $field = implode($this->_endQuote . '.' . $this->_startQuote, $items); - - return $this->_startQuote . $field . $this->_endQuote . $matches[2]; - } - - if (preg_match('/^[\w\s-]*[\w-]+/u', $identifier)) { - return $this->_startQuote . $identifier . $this->_endQuote; - } - - return $identifier; - } - - /** - * Returns a callable function that will be used to transform a passed Query object. - * This function, in turn, will return an instance of a Query object that has been - * transformed to accommodate any specificities of the SQL dialect in use. - * - * @param string $type the type of query to be transformed - * (select, insert, update, delete) - * @return \Closure - */ - public function queryTranslator(string $type): Closure - { - return function ($query) use ($type) { - if ($this->isAutoQuotingEnabled()) { - $query = (new IdentifierQuoter($this))->quote($query); - } - - /** @var \Cake\ORM\Query $query */ - $query = $this->{'_' . $type . 'QueryTranslator'}($query); - $translators = $this->_expressionTranslators(); - if (!$translators) { - return $query; - } - - $query->traverseExpressions(function ($expression) use ($translators, $query): void { - foreach ($translators as $class => $method) { - if ($expression instanceof $class) { - $this->{$method}($expression, $query); - } - } - }); - - return $query; - }; - } - - /** - * Returns an associative array of methods that will transform Expression - * objects to conform with the specific SQL dialect. Keys are class names - * and values a method in this class. - * - * @psalm-return array - * @return array - */ - protected function _expressionTranslators(): array - { - return []; - } - - /** - * Apply translation steps to select queries. - * - * @param \Cake\Database\Query $query The query to translate - * @return \Cake\Database\Query The modified query - */ - protected function _selectQueryTranslator(Query $query): Query - { - return $this->_transformDistinct($query); - } - - /** - * Returns the passed query after rewriting the DISTINCT clause, so that drivers - * that do not support the "ON" part can provide the actual way it should be done - * - * @param \Cake\Database\Query $query The query to be transformed - * @return \Cake\Database\Query - */ - protected function _transformDistinct(Query $query): Query - { - if (is_array($query->clause('distinct'))) { - $query->group($query->clause('distinct'), true); - $query->distinct(false); - } - - return $query; - } - - /** - * Apply translation steps to delete queries. - * - * Chops out aliases on delete query conditions as most database dialects do not - * support aliases in delete queries. This also removes aliases - * in table names as they frequently don't work either. - * - * We are intentionally not supporting deletes with joins as they have even poorer support. - * - * @param \Cake\Database\Query $query The query to translate - * @return \Cake\Database\Query The modified query - */ - protected function _deleteQueryTranslator(Query $query): Query - { - $hadAlias = false; - $tables = []; - foreach ($query->clause('from') as $alias => $table) { - if (is_string($alias)) { - $hadAlias = true; - } - $tables[] = $table; - } - if ($hadAlias) { - $query->from($tables, true); - } - - if (!$hadAlias) { - return $query; - } - - return $this->_removeAliasesFromConditions($query); - } - - /** - * Apply translation steps to update queries. - * - * Chops out aliases on update query conditions as not all database dialects do support - * aliases in update queries. - * - * Just like for delete queries, joins are currently not supported for update queries. - * - * @param \Cake\Database\Query $query The query to translate - * @return \Cake\Database\Query The modified query - */ - protected function _updateQueryTranslator(Query $query): Query - { - return $this->_removeAliasesFromConditions($query); - } - - /** - * Removes aliases from the `WHERE` clause of a query. - * - * @param \Cake\Database\Query $query The query to process. - * @return \Cake\Database\Query The modified query. - * @throws \RuntimeException In case the processed query contains any joins, as removing - * aliases from the conditions can break references to the joined tables. - */ - protected function _removeAliasesFromConditions(Query $query): Query - { - if ($query->clause('join')) { - throw new RuntimeException( - 'Aliases are being removed from conditions for UPDATE/DELETE queries, ' . - 'this can break references to joined tables.' - ); - } - - $conditions = $query->clause('where'); - if ($conditions) { - $conditions->traverse(function ($expression) { - if ($expression instanceof ComparisonExpression) { - $field = $expression->getField(); - if ( - is_string($field) && - strpos($field, '.') !== false - ) { - [, $unaliasedField] = explode('.', $field, 2); - $expression->setField($unaliasedField); - } - - return $expression; - } - - if ($expression instanceof IdentifierExpression) { - $identifier = $expression->getIdentifier(); - if (strpos($identifier, '.') !== false) { - [, $unaliasedIdentifier] = explode('.', $identifier, 2); - $expression->setIdentifier($unaliasedIdentifier); - } - - return $expression; - } - - return $expression; - }); - } - - return $query; - } - - /** - * Apply translation steps to insert queries. - * - * @param \Cake\Database\Query $query The query to translate - * @return \Cake\Database\Query The modified query - */ - protected function _insertQueryTranslator(Query $query): Query - { - return $query; - } - - /** - * Returns a SQL snippet for creating a new transaction savepoint - * - * @param string|int $name save point name - * @return string - */ - public function savePointSQL($name): string - { - return 'SAVEPOINT LEVEL' . $name; - } - - /** - * Returns a SQL snippet for releasing a previously created save point - * - * @param string|int $name save point name - * @return string - */ - public function releaseSavePointSQL($name): string - { - return 'RELEASE SAVEPOINT LEVEL' . $name; - } - - /** - * Returns a SQL snippet for rollbacking a previously created save point - * - * @param string|int $name save point name - * @return string - */ - public function rollbackSavePointSQL($name): string - { - return 'ROLLBACK TO SAVEPOINT LEVEL' . $name; - } -} - -// phpcs:disable -class_alias( - 'Cake\Database\Driver\SqlDialectTrait', - 'Cake\Database\SqlDialectTrait' -); -// phpcs:enable diff --git a/app/vendor/cakephp/cakephp/src/Database/Driver/Sqlite.php b/app/vendor/cakephp/cakephp/src/Database/Driver/Sqlite.php index 65c3e7a1d..286a5b3c1 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Driver/Sqlite.php +++ b/app/vendor/cakephp/cakephp/src/Database/Driver/Sqlite.php @@ -17,29 +17,27 @@ namespace Cake\Database\Driver; use Cake\Database\Driver; +use Cake\Database\DriverFeatureEnum; use Cake\Database\Expression\FunctionExpression; use Cake\Database\Expression\TupleComparison; -use Cake\Database\Query; -use Cake\Database\QueryCompiler; use Cake\Database\Schema\SchemaDialect; use Cake\Database\Schema\SqliteSchemaDialect; -use Cake\Database\SqliteCompiler; -use Cake\Database\Statement\PDOStatement; use Cake\Database\Statement\SqliteStatement; -use Cake\Database\StatementInterface; use InvalidArgumentException; use PDO; -use RuntimeException; -use function Cake\Core\deprecationWarning; /** * Class Sqlite */ class Sqlite extends Driver { - use SqlDialectTrait; use TupleComparisonTranslatorTrait; + /** + * @inheritDoc + */ + protected const STATEMENT_CLASS = SqliteStatement::class; + /** * Base configuration settings for Sqlite driver * @@ -47,7 +45,7 @@ class Sqlite extends Driver * * @var array */ - protected $_baseConfig = [ + protected array $_baseConfig = [ 'persistent' => false, 'username' => null, 'password' => null, @@ -60,40 +58,33 @@ class Sqlite extends Driver 'init' => [], ]; - /** - * The schema dialect class for this driver - * - * @var \Cake\Database\Schema\SqliteSchemaDialect|null - */ - protected $_schemaDialect; - /** * Whether the connected server supports window functions. * * @var bool|null */ - protected $_supportsWindowFunctions; + protected ?bool $_supportsWindowFunctions = null; /** * String used to start a database identifier quoting to make it safe * * @var string */ - protected $_startQuote = '"'; + protected string $_startQuote = '"'; /** * String used to end a database identifier quoting to make it safe * * @var string */ - protected $_endQuote = '"'; + protected string $_endQuote = '"'; /** * Mapping of date parts. * * @var array */ - protected $_dateParts = [ + protected array $_dateParts = [ 'day' => 'd', 'hour' => 'H', 'month' => 'm', @@ -108,20 +99,18 @@ class Sqlite extends Driver * * @var array */ - protected $featureVersions = [ + protected array $featureVersions = [ 'cte' => '3.8.3', 'window' => '3.28.0', ]; /** - * Establishes a connection to the database server - * - * @return bool true on success + * @inheritDoc */ - public function connect(): bool + public function connect(): void { - if ($this->_connection) { - return true; + if ($this->pdo !== null) { + return; } $config = $this->_config; $config['flags'] += [ @@ -132,7 +121,7 @@ public function connect(): bool if (!is_string($config['database']) || $config['database'] === '') { $name = $config['name'] ?? 'unknown'; throw new InvalidArgumentException( - "The `database` key for the `{$name}` SQLite connection needs to be a non-empty string." + "The `database` key for the `{$name}` SQLite connection needs to be a non-empty string.", ); } @@ -150,15 +139,12 @@ public function connect(): bool } if ($params) { - if (PHP_VERSION_ID < 80100) { - throw new RuntimeException('SQLite URI support requires PHP 8.1.'); - } $dsn = 'sqlite:file:' . $config['database'] . '?' . implode('&', $params); } else { $dsn = 'sqlite:' . $config['database']; } - $this->_connect($dsn, $config); + $this->pdo = $this->createPdo($dsn, $config); if ($chmodFile) { // phpcs:disable @chmod($config['database'], $config['mask']); @@ -167,11 +153,9 @@ public function connect(): bool if (!empty($config['init'])) { foreach ((array)$config['init'] as $command) { - $this->getConnection()->exec($command); + $this->pdo->exec($command); } } - - return true; } /** @@ -185,31 +169,9 @@ public function enabled(): bool } /** - * Prepares a sql statement to be executed + * Get the SQL for disabling foreign keys. * - * @param \Cake\Database\Query|string $query The query to prepare. - * @return \Cake\Database\StatementInterface - */ - public function prepare($query): StatementInterface - { - $this->connect(); - $isObject = $query instanceof Query; - /** - * @psalm-suppress PossiblyInvalidMethodCall - * @psalm-suppress PossiblyInvalidArgument - */ - $statement = $this->_connection->prepare($isObject ? $query->sql() : $query); - $result = new SqliteStatement(new PDOStatement($statement, $this), $this); - /** @psalm-suppress PossiblyInvalidMethodCall */ - if ($isObject && $query->isBufferedResultsEnabled() === false) { - $result->bufferResults(false); - } - - return $result; - } - - /** - * @inheritDoc + * @return string */ public function disableForeignKeySQL(): string { @@ -227,30 +189,25 @@ public function enableForeignKeySQL(): string /** * @inheritDoc */ - public function supports(string $feature): bool - { - switch ($feature) { - case static::FEATURE_CTE: - case static::FEATURE_WINDOW: - return version_compare( - $this->version(), - $this->featureVersions[$feature], - '>=' - ); - - case static::FEATURE_TRUNCATE_WITH_CONSTRAINTS: - return true; - } - - return parent::supports($feature); - } - - /** - * @inheritDoc - */ - public function supportsDynamicConstraints(): bool + public function supports(DriverFeatureEnum $feature): bool { - return false; + return match ($feature) { + DriverFeatureEnum::DISABLE_CONSTRAINT_WITHOUT_TRANSACTION, + DriverFeatureEnum::SAVEPOINT, + DriverFeatureEnum::TRUNCATE_WITH_CONSTRAINTS => true, + + DriverFeatureEnum::JSON => false, + + DriverFeatureEnum::CTE, + DriverFeatureEnum::WINDOW => version_compare( + $this->version(), + $this->featureVersions[$feature->value], + '>=', + ), + DriverFeatureEnum::INTERSECT => true, + DriverFeatureEnum::INTERSECT_ALL => false, + DriverFeatureEnum::SET_OPERATIONS_ORDER_BY => false, + }; } /** @@ -258,19 +215,7 @@ public function supportsDynamicConstraints(): bool */ public function schemaDialect(): SchemaDialect { - if ($this->_schemaDialect === null) { - $this->_schemaDialect = new SqliteSchemaDialect($this); - } - - return $this->_schemaDialect; - } - - /** - * @inheritDoc - */ - public function newCompiler(): QueryCompiler - { - return new SqliteCompiler(); + return $this->_schemaDialect ?? ($this->_schemaDialect = new SqliteSchemaDialect($this)); } /** @@ -341,7 +286,7 @@ protected function _transformFunctionExpression(FunctionExpression $expression): ->setConjunction(',') ->iterateParts(function ($p, $key) { if ($key === 1) { - $p = ['value' => $p, 'type' => null]; + return ['value' => $p, 'type' => null]; } return $p; @@ -354,32 +299,9 @@ protected function _transformFunctionExpression(FunctionExpression $expression): ->add(["'%w', " => 'literal'], [], true) ->add([') + (1' => 'literal']); // Sqlite starts on index 0 but Sunday should be 1 break; + case 'JSON_VALUE': + $expression->setName('JSON_EXTRACT'); + break; } } - - /** - * Returns true if the server supports common table expressions. - * - * @return bool - * @deprecated 4.3.0 Use `supports(DriverInterface::FEATURE_CTE)` instead - */ - public function supportsCTEs(): bool - { - deprecationWarning('Feature support checks are now implemented by `supports()` with FEATURE_* constants.'); - - return $this->supports(static::FEATURE_CTE); - } - - /** - * Returns true if the connected server supports window functions. - * - * @return bool - * @deprecated 4.3.0 Use `supports(DriverInterface::FEATURE_WINDOW)` instead - */ - public function supportsWindowFunctions(): bool - { - deprecationWarning('Feature support checks are now implemented by `supports()` with FEATURE_* constants.'); - - return $this->supports(static::FEATURE_WINDOW); - } } diff --git a/app/vendor/cakephp/cakephp/src/Database/Driver/Sqlserver.php b/app/vendor/cakephp/cakephp/src/Database/Driver/Sqlserver.php index a0dc2ec58..6d579ce1f 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Driver/Sqlserver.php +++ b/app/vendor/cakephp/cakephp/src/Database/Driver/Sqlserver.php @@ -17,6 +17,7 @@ namespace Cake\Database\Driver; use Cake\Database\Driver; +use Cake\Database\DriverFeatureEnum; use Cake\Database\Expression\FunctionExpression; use Cake\Database\Expression\OrderByExpression; use Cake\Database\Expression\OrderClauseExpression; @@ -24,6 +25,7 @@ use Cake\Database\Expression\UnaryExpression; use Cake\Database\ExpressionInterface; use Cake\Database\Query; +use Cake\Database\Query\SelectQuery; use Cake\Database\QueryCompiler; use Cake\Database\Schema\SchemaDialect; use Cake\Database\Schema\SqlserverSchemaDialect; @@ -38,7 +40,6 @@ */ class Sqlserver extends Driver { - use SqlDialectTrait; use TupleComparisonTranslatorTrait; /** @@ -53,12 +54,17 @@ class Sqlserver extends Driver 40613, // Azure Sql Database paused ]; + /** + * @inheritDoc + */ + protected const STATEMENT_CLASS = SqlserverStatement::class; + /** * Base configuration settings for Sqlserver driver * * @var array */ - protected $_baseConfig = [ + protected array $_baseConfig = [ 'host' => 'localhost\SQLEXPRESS', 'username' => '', 'password' => '', @@ -79,26 +85,19 @@ class Sqlserver extends Driver 'trustServerCertificate' => null, ]; - /** - * The schema dialect class for this driver - * - * @var \Cake\Database\Schema\SqlserverSchemaDialect|null - */ - protected $_schemaDialect; - /** * String used to start a database identifier quoting to make it safe * * @var string */ - protected $_startQuote = '['; + protected string $_startQuote = '['; /** * String used to end a database identifier quoting to make it safe * * @var string */ - protected $_endQuote = ']'; + protected string $_endQuote = ']'; /** * Establishes a connection to the database server. @@ -109,19 +108,19 @@ class Sqlserver extends Driver * information see: https://github.com/Microsoft/msphpsql/issues/65). * * @throws \InvalidArgumentException if an unsupported setting is in the driver config - * @return bool true on success + * @return void */ - public function connect(): bool + public function connect(): void { - if ($this->_connection) { - return true; + if ($this->pdo !== null) { + return; } $config = $this->_config; if (isset($config['persistent']) && $config['persistent']) { throw new InvalidArgumentException( 'Config setting "persistent" cannot be set to true, ' - . 'as the Sqlserver PDO driver does not support PDO::ATTR_PERSISTENT' + . 'as the Sqlserver PDO driver does not support PDO::ATTR_PERSISTENT', ); } @@ -159,26 +158,23 @@ public function connect(): bool if ($config['trustServerCertificate'] !== null) { $dsn .= ";TrustServerCertificate={$config['trustServerCertificate']}"; } - $this->_connect($dsn, $config); - $connection = $this->getConnection(); + $this->pdo = $this->createPdo($dsn, $config); if (!empty($config['init'])) { foreach ((array)$config['init'] as $command) { - $connection->exec($command); + $this->pdo->exec($command); } } if (!empty($config['settings']) && is_array($config['settings'])) { foreach ($config['settings'] as $key => $value) { - $connection->exec("SET {$key} {$value}"); + $this->pdo->exec("SET {$key} {$value}"); } } if (!empty($config['attributes']) && is_array($config['attributes'])) { foreach ($config['attributes'] as $key => $value) { - $connection->setAttribute($key, $value); + $this->pdo->setAttribute($key, $value); } } - - return true; } /** @@ -192,20 +188,16 @@ public function enabled(): bool } /** - * Prepares a sql statement to be executed - * - * @param \Cake\Database\Query|string $query The query to prepare. - * @return \Cake\Database\StatementInterface + * @inheritDoc */ - public function prepare($query): StatementInterface + public function prepare(Query|string $query): StatementInterface { - $this->connect(); - - $sql = $query; $options = [ PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED, ]; + + $sql = $query; if ($query instanceof Query) { $sql = $query->sql(); if (count($query->getValueBinder()->bindings()) > 2100) { @@ -213,19 +205,23 @@ public function prepare($query): StatementInterface 'Exceeded maximum number of parameters (2100) for prepared statements in Sql Server. ' . 'This is probably due to a very large WHERE IN () clause which generates a parameter ' . 'for each value in the array. ' . - 'If using an Association, try changing the `strategy` from select to subquery.' + 'If using an Association, try changing the `strategy` from select to subquery.', ); } - if (!$query->isBufferedResultsEnabled()) { + if ($query instanceof SelectQuery && !$query->isBufferedResultsEnabled()) { $options = []; } } - /** @psalm-suppress PossiblyInvalidArgument */ - $statement = $this->_connection->prepare($sql, $options); + /** @var string $sql */ + $statement = $this->getPdo()->prepare( + $sql, + $options, + ); - return new SqlserverStatement($statement, $this); + /** @var \Cake\Database\StatementInterface */ + return new (static::STATEMENT_CLASS)($statement, $this, $this->getResultSetDecorators($query)); } /** @@ -272,29 +268,19 @@ public function enableForeignKeySQL(): string /** * @inheritDoc */ - public function supports(string $feature): bool - { - switch ($feature) { - case static::FEATURE_CTE: - case static::FEATURE_TRUNCATE_WITH_CONSTRAINTS: - case static::FEATURE_WINDOW: - return true; - - case static::FEATURE_QUOTE: - $this->connect(); - - return $this->_connection->getAttribute(PDO::ATTR_DRIVER_NAME) !== 'odbc'; - } - - return parent::supports($feature); - } - - /** - * @inheritDoc - */ - public function supportsDynamicConstraints(): bool + public function supports(DriverFeatureEnum $feature): bool { - return true; + return match ($feature) { + DriverFeatureEnum::CTE, + DriverFeatureEnum::DISABLE_CONSTRAINT_WITHOUT_TRANSACTION, + DriverFeatureEnum::SAVEPOINT, + DriverFeatureEnum::TRUNCATE_WITH_CONSTRAINTS, + DriverFeatureEnum::WINDOW => true, + DriverFeatureEnum::INTERSECT => true, + DriverFeatureEnum::INTERSECT_ALL => false, + DriverFeatureEnum::JSON => false, + DriverFeatureEnum::SET_OPERATIONS_ORDER_BY => false, + }; } /** @@ -302,11 +288,7 @@ public function supportsDynamicConstraints(): bool */ public function schemaDialect(): SchemaDialect { - if ($this->_schemaDialect === null) { - $this->_schemaDialect = new SqlserverSchemaDialect($this); - } - - return $this->_schemaDialect; + return $this->_schemaDialect ??= new SqlserverSchemaDialect($this); } /** @@ -322,7 +304,7 @@ public function newCompiler(): QueryCompiler /** * @inheritDoc */ - protected function _selectQueryTranslator(Query $query): Query + protected function _selectQueryTranslator(SelectQuery $query): SelectQuery { $limit = $query->clause('limit'); $offset = $query->clause('offset'); @@ -332,7 +314,7 @@ protected function _selectQueryTranslator(Query $query): Query } if ($offset !== null && !$query->clause('order')) { - $query->order($query->newExpr()->add('(SELECT NULL)')); + $query->orderBy($query->newExpr()->add('(SELECT NULL)')); } if ($this->version() < 11 && $offset !== null) { @@ -348,24 +330,25 @@ protected function _selectQueryTranslator(Query $query): Query * Prior to SQLServer 2012 there was no equivalent to LIMIT OFFSET, so a subquery must * be used. * - * @param \Cake\Database\Query $original The query to wrap in a subquery. + * @param \Cake\Database\Query\SelectQuery $original The query to wrap in a subquery. * @param int|null $limit The number of rows to fetch. * @param int|null $offset The number of rows to offset. - * @return \Cake\Database\Query Modified query object. + * @return \Cake\Database\Query\SelectQuery Modified query object. */ - protected function _pagingSubquery(Query $original, ?int $limit, ?int $offset): Query + protected function _pagingSubquery(SelectQuery $original, ?int $limit, ?int $offset): SelectQuery { $field = '_cake_paging_._cake_page_rownum_'; - if ($original->clause('order')) { + /** @var \Cake\Database\Expression\OrderByExpression $originalOrder */ + $originalOrder = $original->clause('order'); + if ($originalOrder) { // SQL server does not support column aliases in OVER clauses. But // the only practical way to specify the use of calculated columns // is with their alias. So substitute the select SQL in place of // any column aliases for those entries in the order clause. $select = $original->clause('select'); $order = new OrderByExpression(); - $original - ->clause('order') + $originalOrder ->iterateParts(function ($direction, $orderBy) use ($select, $order) { $key = $orderBy; if ( @@ -389,18 +372,18 @@ protected function _pagingSubquery(Query $original, ?int $limit, ?int $offset): '_cake_page_rownum_' => new UnaryExpression('ROW_NUMBER() OVER', $order), ])->limit(null) ->offset(null) - ->order([], true); + ->orderBy([], true); - $outer = new Query($query->getConnection()); + $outer = $query->getConnection()->selectQuery(); $outer->select('*') ->from(['_cake_paging_' => $query]); if ($offset) { - $outer->where(["$field > " . $offset]); + $outer->where(["{$field} > " . $offset]); } if ($limit) { $value = (int)$offset + $limit; - $outer->where(["$field <= $value"]); + $outer->where(["{$field} <= {$value}"]); } // Decorate the original query as that is what the @@ -419,7 +402,7 @@ protected function _pagingSubquery(Query $original, ?int $limit, ?int $offset): /** * @inheritDoc */ - protected function _transformDistinct(Query $query): Query + protected function _transformDistinct(SelectQuery $query): SelectQuery { if (!is_array($query->clause('distinct'))) { return $query; @@ -433,7 +416,7 @@ protected function _transformDistinct(Query $query): Query $order = new OrderByExpression($distinct); $query - ->select(function ($q) use ($distinct, $order) { + ->select(function (Query $q) use ($distinct, $order) { $over = $q->newExpr('ROW_NUMBER() OVER') ->add('(PARTITION BY') ->add($q->newExpr()->add($distinct)->setConjunction(',')) @@ -447,9 +430,9 @@ protected function _transformDistinct(Query $query): Query }) ->limit(null) ->offset(null) - ->order([], true); + ->orderBy([], true); - $outer = new Query($query->getConnection()); + $outer = new SelectQuery($query->getConnection()); $outer->select('*') ->from(['_cake_distinct_' => $query]) ->where(['_cake_distinct_pivot_' => 1]); @@ -493,7 +476,6 @@ protected function _transformFunctionExpression(FunctionExpression $expression): $expression->setName('')->setConjunction(' +'); break; case 'DATEDIFF': - /** @var bool $hasDay */ $hasDay = false; $visitor = function ($value) use (&$hasDay) { if ($value === 'day') { diff --git a/app/vendor/cakephp/cakephp/src/Database/Driver/TupleComparisonTranslatorTrait.php b/app/vendor/cakephp/cakephp/src/Database/Driver/TupleComparisonTranslatorTrait.php index e21a79d08..ee0bd192a 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Driver/TupleComparisonTranslatorTrait.php +++ b/app/vendor/cakephp/cakephp/src/Database/Driver/TupleComparisonTranslatorTrait.php @@ -20,7 +20,8 @@ use Cake\Database\Expression\QueryExpression; use Cake\Database\Expression\TupleComparison; use Cake\Database\Query; -use RuntimeException; +use Cake\Database\Query\SelectQuery; +use InvalidArgumentException; /** * Provides a translator method for tuple comparisons @@ -36,7 +37,7 @@ trait TupleComparisonTranslatorTrait * It transforms expressions looking like '(a, b) IN ((c, d), (e, f))' into an * equivalent expression of the form '((a = c) AND (b = d)) OR ((a = e) AND (b = f))'. * - * It can also transform transform expressions where the right hand side is a query + * It can also transform expressions where the right hand side is a query * selecting the same amount of columns as the elements in the left hand side of * the expression: * @@ -58,18 +59,19 @@ protected function _transformTupleComparison(TupleComparison $expression, Query $operator = strtoupper($expression->getOperator()); if (!in_array($operator, ['IN', '='])) { - throw new RuntimeException( + throw new InvalidArgumentException( sprintf( 'Tuple comparison transform only supports the `IN` and `=` operators, `%s` given.', - $operator - ) + $operator, + ), ); } $value = $expression->getValue(); $true = new QueryExpression('1'); - if ($value instanceof Query) { + if ($value instanceof SelectQuery) { + /** @var array $selected */ $selected = array_values($value->clause('select')); foreach ($fields as $i => $field) { $value->andWhere([$field => new IdentifierExpression($selected[$i])]); @@ -90,7 +92,8 @@ protected function _transformTupleComparison(TupleComparison $expression, Query } $surrogate = $query->getConnection() - ->selectQuery($true); + ->selectQuery() + ->select($true); if (!is_array(current($value))) { $value = [$value]; diff --git a/app/vendor/cakephp/cakephp/src/Database/DriverFeatureEnum.php b/app/vendor/cakephp/cakephp/src/Database/DriverFeatureEnum.php new file mode 100644 index 000000000..9d3723253 --- /dev/null +++ b/app/vendor/cakephp/cakephp/src/Database/DriverFeatureEnum.php @@ -0,0 +1,65 @@ +getMessage() . "\nQuery: " . $this->getQueryString(); + + parent::__construct($message, (int)$previous->getCode(), $previous); + } + + /** + * Get the query string that caused this exception. + * + * @return string + */ + public function getQueryString(): string + { + if ($this->query instanceof LoggedQuery) { + return (string)$this->query; + } + + return $this->query; + } +} diff --git a/app/vendor/cakephp/cakephp/src/Database/Expression/AggregateExpression.php b/app/vendor/cakephp/cakephp/src/Database/Expression/AggregateExpression.php index 842a77009..016147acc 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Expression/AggregateExpression.php +++ b/app/vendor/cakephp/cakephp/src/Database/Expression/AggregateExpression.php @@ -16,8 +16,10 @@ */ namespace Cake\Database\Expression; +use Cake\Database\ExpressionInterface; use Cake\Database\ValueBinder; use Closure; +use function Cake\Core\deprecationWarning; /** * This represents an SQL aggregate function expression in an SQL statement. @@ -28,14 +30,14 @@ class AggregateExpression extends FunctionExpression implements WindowInterface { /** - * @var \Cake\Database\Expression\QueryExpression + * @var \Cake\Database\Expression\QueryExpression|null */ - protected $filter; + protected ?QueryExpression $filter = null; /** - * @var \Cake\Database\Expression\WindowExpression + * @var \Cake\Database\Expression\WindowExpression|null */ - protected $window; + protected ?WindowExpression $window = null; /** * Adds conditions to the FILTER clause. The conditions are the same format as @@ -46,11 +48,9 @@ class AggregateExpression extends FunctionExpression implements WindowInterface * @return $this * @see \Cake\Database\Query::where() */ - public function filter($conditions, array $types = []) + public function filter(ExpressionInterface|Closure|array|string $conditions, array $types = []) { - if ($this->filter === null) { - $this->filter = new QueryExpression(); - } + $this->filter ??= new QueryExpression(); if ($conditions instanceof Closure) { $conditions = $conditions(new QueryExpression()); @@ -62,19 +62,17 @@ public function filter($conditions, array $types = []) } /** - * Adds an empty `OVER()` window expression or a named window epression. + * Adds an empty `OVER()` window expression or a named window expression. * * @param string|null $name Window name * @return $this */ public function over(?string $name = null) { - if ($this->window === null) { - $this->window = new WindowExpression(); - } + $window = $this->getWindow(); if ($name) { // Set name manually in case this was chained from FunctionsBuilder wrapper - $this->window->name($name); + $window->name($name); } return $this; @@ -83,10 +81,9 @@ public function over(?string $name = null) /** * @inheritDoc */ - public function partition($partitions) + public function partition(ExpressionInterface|Closure|array|string $partitions) { - $this->over(); - $this->window->partition($partitions); + $this->getWindow()->partition($partitions); return $this; } @@ -94,10 +91,22 @@ public function partition($partitions) /** * @inheritDoc */ - public function order($fields) + public function order(ExpressionInterface|Closure|array|string $fields) { - $this->over(); - $this->window->order($fields); + deprecationWarning( + '5.0.0', + 'AggregateExpression::order() is deprecated. Use AggregateExpression::orderBy() instead.', + ); + + return $this->orderBy($fields); + } + + /** + * @inheritDoc + */ + public function orderBy(ExpressionInterface|Closure|array|string $fields) + { + $this->getWindow()->orderBy($fields); return $this; } @@ -105,10 +114,9 @@ public function order($fields) /** * @inheritDoc */ - public function range($start, $end = 0) + public function range(ExpressionInterface|string|int|null $start, ExpressionInterface|string|int|null $end = 0) { - $this->over(); - $this->window->range($start, $end); + $this->getWindow()->range($start, $end); return $this; } @@ -118,8 +126,7 @@ public function range($start, $end = 0) */ public function rows(?int $start, ?int $end = 0) { - $this->over(); - $this->window->rows($start, $end); + $this->getWindow()->rows($start, $end); return $this; } @@ -129,8 +136,7 @@ public function rows(?int $start, ?int $end = 0) */ public function groups(?int $start, ?int $end = 0) { - $this->over(); - $this->window->groups($start, $end); + $this->getWindow()->groups($start, $end); return $this; } @@ -140,13 +146,12 @@ public function groups(?int $start, ?int $end = 0) */ public function frame( string $type, - $startOffset, + ExpressionInterface|string|int|null $startOffset, string $startDirection, - $endOffset, - string $endDirection + ExpressionInterface|string|int|null $endOffset, + string $endDirection, ) { - $this->over(); - $this->window->frame($type, $startOffset, $startDirection, $endOffset, $endDirection); + $this->getWindow()->frame($type, $startOffset, $startDirection, $endOffset, $endDirection); return $this; } @@ -156,8 +161,7 @@ public function frame( */ public function excludeCurrent() { - $this->over(); - $this->window->excludeCurrent(); + $this->getWindow()->excludeCurrent(); return $this; } @@ -167,8 +171,7 @@ public function excludeCurrent() */ public function excludeGroup() { - $this->over(); - $this->window->excludeGroup(); + $this->getWindow()->excludeGroup(); return $this; } @@ -178,12 +181,21 @@ public function excludeGroup() */ public function excludeTies() { - $this->over(); - $this->window->excludeTies(); + $this->getWindow()->excludeTies(); return $this; } + /** + * Returns or creates WindowExpression for function. + * + * @return \Cake\Database\Expression\WindowExpression + */ + protected function getWindow(): WindowExpression + { + return $this->window ??= new WindowExpression(); + } + /** * @inheritDoc */ @@ -229,7 +241,7 @@ public function count(): int { $count = parent::count(); if ($this->window !== null) { - $count = $count + 1; + $count += 1; } return $count; diff --git a/app/vendor/cakephp/cakephp/src/Database/Expression/BetweenExpression.php b/app/vendor/cakephp/cakephp/src/Database/Expression/BetweenExpression.php index 346614789..e6443bbd3 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Expression/BetweenExpression.php +++ b/app/vendor/cakephp/cakephp/src/Database/Expression/BetweenExpression.php @@ -34,31 +34,31 @@ class BetweenExpression implements ExpressionInterface, FieldInterface * * @var mixed */ - protected $_from; + protected mixed $_from; /** * The second value in the expression * * @var mixed */ - protected $_to; + protected mixed $_to; /** * The data type for the from and to arguments * * @var mixed */ - protected $_type; + protected mixed $_type; /** * Constructor * - * @param \Cake\Database\ExpressionInterface|string $field The field name to compare for values inbetween the range. + * @param \Cake\Database\ExpressionInterface|string $field The field name to compare for values in between the range. * @param mixed $from The initial value of the range. * @param mixed $to The ending value in the comparison range. * @param string|null $type The data type name to bind the values with. */ - public function __construct($field, $from, $to, $type = null) + public function __construct(ExpressionInterface|string $field, mixed $from, mixed $to, ?string $type = null) { if ($type !== null) { $from = $this->_castToExpression($from, $type); @@ -81,7 +81,6 @@ public function sql(ValueBinder $binder): string 'to' => $this->_to, ]; - /** @var \Cake\Database\ExpressionInterface|string $field */ $field = $this->_field; if ($field instanceof ExpressionInterface) { $field = $field->sql($binder); @@ -94,6 +93,7 @@ public function sql(ValueBinder $binder): string } $parts[$name] = $this->_bindValue($part, $binder, $this->_type); } + assert(is_string($field)); return sprintf('%s BETWEEN %s AND %s', $field, $parts['from'], $parts['to']); } @@ -117,10 +117,10 @@ public function traverse(Closure $callback) * * @param mixed $value The value to bind * @param \Cake\Database\ValueBinder $binder The value binder to use - * @param string $type The type of $value + * @param string|null $type The type of $value * @return string generated placeholder */ - protected function _bindValue($value, $binder, $type): string + protected function _bindValue(mixed $value, ValueBinder $binder, ?string $type): string { $placeholder = $binder->placeholder('c'); $binder->bind($placeholder, $value, $type); diff --git a/app/vendor/cakephp/cakephp/src/Database/Expression/CaseExpression.php b/app/vendor/cakephp/cakephp/src/Database/Expression/CaseExpression.php deleted file mode 100644 index 864a3bcce..000000000 --- a/app/vendor/cakephp/cakephp/src/Database/Expression/CaseExpression.php +++ /dev/null @@ -1,251 +0,0 @@ - :value" - * - * @var array - */ - protected $_conditions = []; - - /** - * Values that are associated with the conditions in the $_conditions array. - * Each value represents the 'true' value for the condition with the corresponding key. - * - * @var array - */ - protected $_values = []; - - /** - * The `ELSE` value for the case statement. If null then no `ELSE` will be included. - * - * @var \Cake\Database\ExpressionInterface|array|string|null - */ - protected $_elseValue; - - /** - * Constructs the case expression - * - * @param \Cake\Database\ExpressionInterface|array $conditions The conditions to test. Must be a ExpressionInterface - * instance, or an array of ExpressionInterface instances. - * @param \Cake\Database\ExpressionInterface|array $values Associative array of values to be associated with the - * conditions passed in $conditions. If there are more $values than $conditions, - * the last $value is used as the `ELSE` value. - * @param array $types Associative array of types to be associated with the values - * passed in $values - */ - public function __construct($conditions = [], $values = [], $types = []) - { - $conditions = is_array($conditions) ? $conditions : [$conditions]; - $values = is_array($values) ? $values : [$values]; - $types = is_array($types) ? $types : [$types]; - - if (!empty($conditions)) { - $this->add($conditions, $values, $types); - } - - if (count($values) > count($conditions)) { - end($values); - $key = key($values); - $this->elseValue($values[$key], $types[$key] ?? null); - } - } - - /** - * Adds one or more conditions and their respective true values to the case object. - * Conditions must be a one dimensional array or a QueryExpression. - * The trueValues must be a similar structure, but may contain a string value. - * - * @param \Cake\Database\ExpressionInterface|array $conditions Must be a ExpressionInterface instance, - * or an array of ExpressionInterface instances. - * @param \Cake\Database\ExpressionInterface|array $values Associative array of values of each condition - * @param array $types Associative array of types to be associated with the values - * @return $this - */ - public function add($conditions = [], $values = [], $types = []) - { - $conditions = is_array($conditions) ? $conditions : [$conditions]; - $values = is_array($values) ? $values : [$values]; - $types = is_array($types) ? $types : [$types]; - - $this->_addExpressions($conditions, $values, $types); - - return $this; - } - - /** - * Iterates over the passed in conditions and ensures that there is a matching true value for each. - * If no matching true value, then it is defaulted to '1'. - * - * @param array $conditions Array of ExpressionInterface instances. - * @param array $values Associative array of values of each condition - * @param array $types Associative array of types to be associated with the values - * @return void - */ - protected function _addExpressions(array $conditions, array $values, array $types): void - { - $rawValues = array_values($values); - $keyValues = array_keys($values); - - foreach ($conditions as $k => $c) { - $numericKey = is_numeric($k); - - if ($numericKey && empty($c)) { - continue; - } - - if (!$c instanceof ExpressionInterface) { - continue; - } - - $this->_conditions[] = $c; - $value = $rawValues[$k] ?? 1; - - if ($value === 'literal') { - $value = $keyValues[$k]; - $this->_values[] = $value; - continue; - } - - if ($value === 'identifier') { - /** @var string $identifier */ - $identifier = $keyValues[$k]; - $value = new IdentifierExpression($identifier); - $this->_values[] = $value; - continue; - } - - $type = $types[$k] ?? null; - - if ($type !== null && !$value instanceof ExpressionInterface) { - $value = $this->_castToExpression($value, $type); - } - - if ($value instanceof ExpressionInterface) { - $this->_values[] = $value; - continue; - } - - $this->_values[] = ['value' => $value, 'type' => $type]; - } - } - - /** - * Sets the default value - * - * @param \Cake\Database\ExpressionInterface|array|string|null $value Value to set - * @param string|null $type Type of value - * @return void - */ - public function elseValue($value = null, ?string $type = null): void - { - if (is_array($value)) { - end($value); - $value = key($value); - } - - if ($value !== null && !$value instanceof ExpressionInterface) { - $value = $this->_castToExpression($value, $type); - } - - if (!$value instanceof ExpressionInterface) { - $value = ['value' => $value, 'type' => $type]; - } - - $this->_elseValue = $value; - } - - /** - * Compiles the relevant parts into sql - * - * @param \Cake\Database\ExpressionInterface|array|string $part The part to compile - * @param \Cake\Database\ValueBinder $binder Sql generator - * @return string - */ - protected function _compile($part, ValueBinder $binder): string - { - if ($part instanceof ExpressionInterface) { - $part = $part->sql($binder); - } elseif (is_array($part)) { - $placeholder = $binder->placeholder('param'); - $binder->bind($placeholder, $part['value'], $part['type']); - $part = $placeholder; - } - - return $part; - } - - /** - * Converts the Node into a SQL string fragment. - * - * @param \Cake\Database\ValueBinder $binder Placeholder generator object - * @return string - */ - public function sql(ValueBinder $binder): string - { - $parts = []; - $parts[] = 'CASE'; - foreach ($this->_conditions as $k => $part) { - $value = $this->_values[$k]; - $parts[] = 'WHEN ' . $this->_compile($part, $binder) . ' THEN ' . $this->_compile($value, $binder); - } - if ($this->_elseValue !== null) { - $parts[] = 'ELSE'; - $parts[] = $this->_compile($this->_elseValue, $binder); - } - $parts[] = 'END'; - - return implode(' ', $parts); - } - - /** - * @inheritDoc - */ - public function traverse(Closure $callback) - { - foreach (['_conditions', '_values'] as $part) { - foreach ($this->{$part} as $c) { - if ($c instanceof ExpressionInterface) { - $callback($c); - $c->traverse($callback); - } - } - } - if ($this->_elseValue instanceof ExpressionInterface) { - $callback($this->_elseValue); - $this->_elseValue->traverse($callback); - } - - return $this; - } -} diff --git a/app/vendor/cakephp/cakephp/src/Database/Expression/CaseExpressionTrait.php b/app/vendor/cakephp/cakephp/src/Database/Expression/CaseExpressionTrait.php index 1994ec9e4..a2c2425bf 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Expression/CaseExpressionTrait.php +++ b/app/vendor/cakephp/cakephp/src/Database/Expression/CaseExpressionTrait.php @@ -17,18 +17,16 @@ namespace Cake\Database\Expression; use Cake\Chronos\ChronosDate; -use Cake\Chronos\MutableDate; use Cake\Database\ExpressionInterface; use Cake\Database\Query; use Cake\Database\TypedResultInterface; use Cake\Database\ValueBinder; use DateTimeInterface; +use Stringable; /** * Trait that holds shared functionality for case related expressions. * - * @property \Cake\Database\TypeMap $_typeMap The type map to use when using an array of conditions for the `WHEN` - * value. * @internal */ trait CaseExpressionTrait @@ -39,7 +37,7 @@ trait CaseExpressionTrait * @param mixed $value The value for which to infer the type. * @return string|null The abstract type, or `null` if it could not be inferred. */ - protected function inferType($value): ?string + protected function inferType(mixed $value): ?string { $type = null; @@ -51,16 +49,12 @@ protected function inferType($value): ?string $type = 'float'; } elseif (is_bool($value)) { $type = 'boolean'; - } elseif ( - $value instanceof ChronosDate || - $value instanceof MutableDate - ) { + } elseif ($value instanceof ChronosDate) { $type = 'date'; } elseif ($value instanceof DateTimeInterface) { $type = 'datetime'; } elseif ( - is_object($value) && - method_exists($value, '__toString') + $value instanceof Stringable ) { $type = 'string'; } elseif ( @@ -83,7 +77,7 @@ protected function inferType($value): ?string * @param string|null $type The value type. * @return string */ - protected function compileNullableValue(ValueBinder $binder, $value, ?string $type = null): string + protected function compileNullableValue(ValueBinder $binder, mixed $value, ?string $type = null): string { if ( $type !== null && diff --git a/app/vendor/cakephp/cakephp/src/Database/Expression/CaseStatementExpression.php b/app/vendor/cakephp/cakephp/src/Database/Expression/CaseStatementExpression.php index 8e6ebd18d..c68ccb01c 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Expression/CaseStatementExpression.php +++ b/app/vendor/cakephp/cakephp/src/Database/Expression/CaseStatementExpression.php @@ -24,7 +24,6 @@ use Closure; use InvalidArgumentException; use LogicException; -use function Cake\Core\getTypeName; /** * Represents a SQL case statement with a fluid API @@ -41,7 +40,7 @@ class CaseStatementExpression implements ExpressionInterface, TypedResultInterfa * * @var array */ - protected $validClauseNames = [ + protected array $validClauseNames = [ 'value', 'when', 'else', @@ -52,56 +51,56 @@ class CaseStatementExpression implements ExpressionInterface, TypedResultInterfa * * @var bool */ - protected $isSimpleVariant = false; + protected bool $isSimpleVariant = false; /** * The case value. * * @var \Cake\Database\ExpressionInterface|object|scalar|null */ - protected $value = null; + protected mixed $value = null; /** * The case value type. * * @var string|null */ - protected $valueType = null; + protected ?string $valueType = null; /** * The `WHEN ... THEN ...` expressions. * * @var array<\Cake\Database\Expression\WhenThenExpression> */ - protected $when = []; + protected array $when = []; /** * Buffer that holds values and types for use with `then()`. * * @var array|null */ - protected $whenBuffer = null; + protected ?array $whenBuffer = null; /** * The else part result value. * * @var \Cake\Database\ExpressionInterface|object|scalar|null */ - protected $else = null; + protected mixed $else = null; /** * The else part result type. * * @var string|null */ - protected $elseType = null; + protected ?string $elseType = null; /** * The return type. * * @var string|null */ - protected $returnType = null; + protected ?string $returnType = null; /** * Constructor. @@ -123,7 +122,7 @@ class CaseStatementExpression implements ExpressionInterface, TypedResultInterfa * @param string|null $type The case value type. If no type is provided, the type will be tried to be inferred * from the value. */ - public function __construct($value = null, ?string $type = null) + public function __construct(mixed $value = null, ?string $type = null) { if (func_num_args() > 0) { if ( @@ -135,7 +134,7 @@ public function __construct($value = null, ?string $type = null) 'The `$value` argument must be either `null`, a scalar value, an object, ' . 'or an instance of `\%s`, `%s` given.', ExpressionInterface::class, - getTypeName($value) + get_debug_type($value), )); } @@ -292,7 +291,7 @@ public function __construct($value = null, ?string $type = null) * @throws \LogicException In case the callable doesn't return an instance of * `\Cake\Database\Expression\WhenThenExpression`. */ - public function when($when, $type = null) + public function when(mixed $when, array|string|null $type = null) { if ($this->whenBuffer !== null) { throw new LogicException('Cannot call `when()` between `when()` and `then()`.'); @@ -304,7 +303,7 @@ public function when($when, $type = null) throw new LogicException(sprintf( '`when()` callables must return an instance of `\%s`, `%s` given.', WhenThenExpression::class, - getTypeName($when) + get_debug_type($when), )); } } @@ -374,7 +373,7 @@ public function when($when, $type = null) * @throws \LogicException In case `when()` wasn't previously called with a value other than a closure or an * instance of `\Cake\Database\Expression\WhenThenExpression`. */ - public function then($result, ?string $type = null) + public function then(mixed $result, ?string $type = null) { if ($this->whenBuffer === null) { throw new LogicException('Cannot call `then()` before `when()`.'); @@ -402,7 +401,7 @@ public function then($result, ?string $type = null) * @throws \InvalidArgumentException In case the `$result` argument is neither a scalar value, nor an object, an * instance of `\Cake\Database\ExpressionInterface`, or `null`. */ - public function else($result, ?string $type = null) + public function else(mixed $result, ?string $type = null) { if ($this->whenBuffer !== null) { throw new LogicException('Cannot call `else()` between `when()` and `then()`.'); @@ -417,13 +416,11 @@ public function else($result, ?string $type = null) 'The `$result` argument must be either `null`, a scalar value, an object, ' . 'or an instance of `\%s`, `%s` given.', ExpressionInterface::class, - getTypeName($result) + get_debug_type($result), )); } - if ($type === null) { - $type = $this->inferType($result); - } + $type ??= $this->inferType($result); $this->else = $result; $this->elseType = $type; @@ -500,15 +497,15 @@ public function setReturnType(string $type) * @return \Cake\Database\ExpressionInterface|object|array<\Cake\Database\Expression\WhenThenExpression>|scalar|null * @throws \InvalidArgumentException In case the given clause name is invalid. */ - public function clause(string $clause) + public function clause(string $clause): mixed { if (!in_array($clause, $this->validClauseNames, true)) { throw new InvalidArgumentException( sprintf( 'The `$clause` argument must be one of `%s`, the given value `%s` is invalid.', implode('`, `', $this->validClauseNames), - $clause - ) + $clause, + ), ); } @@ -524,7 +521,7 @@ public function sql(ValueBinder $binder): string throw new LogicException('Case expression has incomplete when clause. Missing `then()` after `when()`.'); } - if (empty($this->when)) { + if (!$this->when) { throw new LogicException('Case expression must have at least one when statement.'); } @@ -541,7 +538,7 @@ public function sql(ValueBinder $binder): string $else = $this->compileNullableValue($binder, $this->else, $this->elseType); - return "CASE {$value}{$whenThen} ELSE $else END"; + return "CASE {$value}{$whenThen} ELSE {$else} END"; } /** diff --git a/app/vendor/cakephp/cakephp/src/Database/Expression/CommonTableExpression.php b/app/vendor/cakephp/cakephp/src/Database/Expression/CommonTableExpression.php index f3d359eee..86eeaab0b 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Expression/CommonTableExpression.php +++ b/app/vendor/cakephp/cakephp/src/Database/Expression/CommonTableExpression.php @@ -16,10 +16,10 @@ */ namespace Cake\Database\Expression; +use Cake\Database\Exception\DatabaseException; use Cake\Database\ExpressionInterface; use Cake\Database\ValueBinder; use Closure; -use RuntimeException; /** * An expression that represents a common table expression definition. @@ -31,43 +31,43 @@ class CommonTableExpression implements ExpressionInterface * * @var \Cake\Database\Expression\IdentifierExpression */ - protected $name; + protected IdentifierExpression $name; /** * The field names to use for the CTE. * * @var array<\Cake\Database\Expression\IdentifierExpression> */ - protected $fields = []; + protected array $fields = []; /** * The CTE query definition. * * @var \Cake\Database\ExpressionInterface|null */ - protected $query; + protected ?ExpressionInterface $query = null; /** * Whether the CTE is materialized or not materialized. * * @var string|null */ - protected $materialized = null; + protected ?string $materialized = null; /** * Whether the CTE is recursive. * * @var bool */ - protected $recursive = false; + protected bool $recursive = false; /** * Constructor. * * @param string $name The CTE name. - * @param \Cake\Database\ExpressionInterface|\Closure $query CTE query + * @param \Cake\Database\ExpressionInterface|\Closure|null $query CTE query */ - public function __construct(string $name = '', $query = null) + public function __construct(string $name = '', ExpressionInterface|Closure|null $query = null) { $this->name = new IdentifierExpression($name); if ($query) { @@ -97,13 +97,13 @@ public function name(string $name) * @param \Cake\Database\ExpressionInterface|\Closure $query CTE query * @return $this */ - public function query($query) + public function query(ExpressionInterface|Closure $query) { if ($query instanceof Closure) { $query = $query(); if (!($query instanceof ExpressionInterface)) { - throw new RuntimeException( - 'You must return an `ExpressionInterface` from a Closure passed to `query()`.' + throw new DatabaseException( + 'You must return an `ExpressionInterface` from a Closure passed to `query()`.', ); } } @@ -115,18 +115,21 @@ public function query($query) /** * Adds one or more fields (arguments) to the CTE. * - * @param \Cake\Database\Expression\IdentifierExpression|array<\Cake\Database\Expression\IdentifierExpression>|array|string $fields Field names + * @param \Cake\Database\Expression\IdentifierExpression|array|array<\Cake\Database\Expression\IdentifierExpression>|string $fields Field names * @return $this */ - public function field($fields) + public function field(IdentifierExpression|array|string $fields) { $fields = (array)$fields; + /** @var array $fields */ foreach ($fields as &$field) { if (!($field instanceof IdentifierExpression)) { $field = new IdentifierExpression($field); } } - $this->fields = array_merge($this->fields, $fields); + /** @var array<\Cake\Database\Expression\IdentifierExpression> $mergedFields */ + $mergedFields = array_merge($this->fields, $fields); + $this->fields = $mergedFields; return $this; } @@ -184,9 +187,7 @@ public function sql(ValueBinder $binder): string { $fields = ''; if ($this->fields) { - $expressions = array_map(function (IdentifierExpression $e) use ($binder) { - return $e->sql($binder); - }, $this->fields); + $expressions = array_map(fn(IdentifierExpression $e) => $e->sql($binder), $this->fields); $fields = sprintf('(%s)', implode(', ', $expressions)); } @@ -197,7 +198,7 @@ public function sql(ValueBinder $binder): string $this->name->sql($binder), $fields, $suffix, - $this->query ? $this->query->sql($binder) : '' + $this->query ? $this->query->sql($binder) : '', ); } diff --git a/app/vendor/cakephp/cakephp/src/Database/Expression/Comparison.php b/app/vendor/cakephp/cakephp/src/Database/Expression/Comparison.php deleted file mode 100644 index 01bdbd583..000000000 --- a/app/vendor/cakephp/cakephp/src/Database/Expression/Comparison.php +++ /dev/null @@ -1,7 +0,0 @@ - */ - protected $_valueExpressions = []; + protected array $_valueExpressions = []; /** * Constructor @@ -76,8 +76,12 @@ class ComparisonExpression implements ExpressionInterface, FieldInterface * @param string|null $type the type name used to cast the value * @param string $operator the operator used for comparing field and value */ - public function __construct($field, $value, ?string $type = null, string $operator = '=') - { + public function __construct( + ExpressionInterface|string $field, + mixed $value, + ?string $type = null, + string $operator = '=', + ) { $this->_type = $type; $this->setField($field); $this->setValue($value); @@ -90,11 +94,11 @@ public function __construct($field, $value, ?string $type = null, string $operat * @param mixed $value The value to compare * @return void */ - public function setValue($value): void + public function setValue(mixed $value): void { $value = $this->_castToExpression($value, $this->_type); - $isMultiple = $this->_type && strpos($this->_type, '[]') !== false; + $isMultiple = $this->_type && str_contains($this->_type, '[]'); if ($isMultiple) { [$value, $this->_valueExpressions] = $this->_collectExpressions($value); } @@ -108,7 +112,7 @@ public function setValue($value): void * * @return mixed */ - public function getValue() + public function getValue(): mixed { return $this->_value; } @@ -139,7 +143,6 @@ public function getOperator(): string */ public function sql(ValueBinder $binder): string { - /** @var \Cake\Database\ExpressionInterface|string $field */ $field = $this->_field; if ($field instanceof ExpressionInterface) { @@ -155,6 +158,7 @@ public function sql(ValueBinder $binder): string } else { [$template, $value] = $this->_stringExpression($binder); } + assert(is_string($field)); return sprintf($template, $field, $this->_operator, $value); } @@ -225,9 +229,9 @@ protected function _stringExpression(ValueBinder $binder): array // better just throw an exception here if ($value === '') { $field = $this->_field instanceof ExpressionInterface ? $this->_field->sql($binder) : $this->_field; - /** @psalm-suppress PossiblyInvalidCast */ + /** @var string $field */ throw new DatabaseException( - "Impossible to generate condition with empty list of values for field ($field)" + "Impossible to generate condition with empty list of values for field ({$field})", ); } } else { @@ -246,7 +250,7 @@ protected function _stringExpression(ValueBinder $binder): array * @param string|null $type The type of $value * @return string generated placeholder */ - protected function _bindValue($value, ValueBinder $binder, ?string $type = null): string + protected function _bindValue(mixed $value, ValueBinder $binder, ?string $type = null): string { $placeholder = $binder->placeholder('c'); $binder->bind($placeholder, $value, $type); @@ -273,7 +277,7 @@ protected function _flattenValue(iterable $value, ValueBinder $binder, ?string $ } } - if (!empty($value)) { + if ($value) { $parts += $binder->generateManyNamed($value, $type); } @@ -288,18 +292,17 @@ protected function _flattenValue(iterable $value, ValueBinder $binder, ?string $ * @param \Cake\Database\ExpressionInterface|iterable $values The rows to insert * @return array */ - protected function _collectExpressions($values): array + protected function _collectExpressions(ExpressionInterface|iterable $values): array { if ($values instanceof ExpressionInterface) { return [$values, []]; } - - $expressions = $result = []; + $expressions = []; + $result = []; $isArray = is_array($values); if ($isArray) { - /** @var array $result */ - $result = $values; + $result = (array)$values; } foreach ($values as $k => $v) { @@ -315,10 +318,3 @@ protected function _collectExpressions($values): array return [$result, $expressions]; } } - -// phpcs:disable -class_alias( - 'Cake\Database\Expression\ComparisonExpression', - 'Cake\Database\Expression\Comparison' -); -// phpcs:enable diff --git a/app/vendor/cakephp/cakephp/src/Database/Expression/FieldInterface.php b/app/vendor/cakephp/cakephp/src/Database/Expression/FieldInterface.php index f0554d976..ae6576894 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Expression/FieldInterface.php +++ b/app/vendor/cakephp/cakephp/src/Database/Expression/FieldInterface.php @@ -16,6 +16,8 @@ */ namespace Cake\Database\Expression; +use Cake\Database\ExpressionInterface; + /** * Describes a getter and a setter for the a field property. Useful for expressions * that contain an identifier to compare against. @@ -28,12 +30,12 @@ interface FieldInterface * @param \Cake\Database\ExpressionInterface|array|string $field The field to compare with. * @return void */ - public function setField($field): void; + public function setField(ExpressionInterface|array|string $field): void; /** * Returns the field name * * @return \Cake\Database\ExpressionInterface|array|string */ - public function getField(); + public function getField(): ExpressionInterface|array|string; } diff --git a/app/vendor/cakephp/cakephp/src/Database/Expression/FieldTrait.php b/app/vendor/cakephp/cakephp/src/Database/Expression/FieldTrait.php index 0614a8eb3..3f32e0440 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Expression/FieldTrait.php +++ b/app/vendor/cakephp/cakephp/src/Database/Expression/FieldTrait.php @@ -16,6 +16,8 @@ */ namespace Cake\Database\Expression; +use Cake\Database\ExpressionInterface; + /** * Contains the field property with a getter and a setter for it */ @@ -26,7 +28,7 @@ trait FieldTrait * * @var \Cake\Database\ExpressionInterface|array|string */ - protected $_field; + protected ExpressionInterface|array|string $_field; /** * Sets the field name @@ -34,7 +36,7 @@ trait FieldTrait * @param \Cake\Database\ExpressionInterface|array|string $field The field to compare with. * @return void */ - public function setField($field): void + public function setField(ExpressionInterface|array|string $field): void { $this->_field = $field; } @@ -44,7 +46,7 @@ public function setField($field): void * * @return \Cake\Database\ExpressionInterface|array|string */ - public function getField() + public function getField(): ExpressionInterface|array|string { return $this->_field; } diff --git a/app/vendor/cakephp/cakephp/src/Database/Expression/FunctionExpression.php b/app/vendor/cakephp/cakephp/src/Database/Expression/FunctionExpression.php index 105655ba9..27d1f3e93 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Expression/FunctionExpression.php +++ b/app/vendor/cakephp/cakephp/src/Database/Expression/FunctionExpression.php @@ -39,7 +39,7 @@ class FunctionExpression extends QueryExpression implements TypedResultInterface * * @var string */ - protected $_name; + protected string $_name; /** * Constructor. Takes a name for the function to be invoked and a list of params @@ -99,19 +99,19 @@ public function getName(): string /** * Adds one or more arguments for the function call. * - * @param array $conditions list of arguments to be passed to the function + * @param \Cake\Database\ExpressionInterface|array|string $conditions list of arguments to be passed to the function * If associative the key would be used as argument when value is 'literal' * @param array $types Associative array of types to be associated with the * passed arguments * @param bool $prepend Whether to prepend or append to the list of arguments * @see \Cake\Database\Expression\FunctionExpression::__construct() for more details. * @return $this - * @psalm-suppress MoreSpecificImplementedParamType */ - public function add($conditions, array $types = [], bool $prepend = false) + public function add(ExpressionInterface|array|string $conditions, array $types = [], bool $prepend = false) { $put = $prepend ? 'array_unshift' : 'array_push'; $typeMap = $this->getTypeMap()->setTypes($types); + /** @var array $conditions */ foreach ($conditions as $k => $p) { if ($p === 'literal') { $put($this->_conditions, $k); @@ -161,7 +161,7 @@ public function sql(ValueBinder $binder): string return $this->_name . sprintf('(%s)', implode( $this->_conjunction . ' ', - $parts + $parts, )); } diff --git a/app/vendor/cakephp/cakephp/src/Database/Expression/IdentifierExpression.php b/app/vendor/cakephp/cakephp/src/Database/Expression/IdentifierExpression.php index 69486a41f..af4c77ebd 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Expression/IdentifierExpression.php +++ b/app/vendor/cakephp/cakephp/src/Database/Expression/IdentifierExpression.php @@ -35,12 +35,12 @@ class IdentifierExpression implements ExpressionInterface * * @var string */ - protected $_identifier; + protected string $_identifier; /** * @var string|null */ - protected $collation; + protected ?string $collation = null; /** * Constructor diff --git a/app/vendor/cakephp/cakephp/src/Database/Expression/OrderByExpression.php b/app/vendor/cakephp/cakephp/src/Database/Expression/OrderByExpression.php index 74fbf219f..dda76914b 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Expression/OrderByExpression.php +++ b/app/vendor/cakephp/cakephp/src/Database/Expression/OrderByExpression.php @@ -17,8 +17,9 @@ namespace Cake\Database\Expression; use Cake\Database\ExpressionInterface; +use Cake\Database\TypeMap; use Cake\Database\ValueBinder; -use RuntimeException; +use InvalidArgumentException; /** * An expression object for ORDER BY clauses @@ -32,8 +33,11 @@ class OrderByExpression extends QueryExpression * @param \Cake\Database\TypeMap|array $types The types for each column. * @param string $conjunction The glue used to join conditions together. */ - public function __construct($conditions = [], $types = [], $conjunction = '') - { + public function __construct( + ExpressionInterface|array|string $conditions = [], + TypeMap|array $types = [], + string $conjunction = '', + ) { parent::__construct($conditions, $types, $conjunction); } @@ -71,14 +75,14 @@ protected function _addConditions(array $conditions, array $types): void is_string($val) && !in_array(strtoupper($val), ['ASC', 'DESC'], true) ) { - throw new RuntimeException( + throw new InvalidArgumentException( sprintf( - 'Passing extra expressions by associative array (`\'%s\' => \'%s\'`) ' . + "Passing extra expressions by associative array (`'%s' => '%s'`) " . 'is not allowed to avoid potential SQL injection. ' . 'Use QueryExpression or numeric array instead.', $key, - $val - ) + $val, + ), ); } } diff --git a/app/vendor/cakephp/cakephp/src/Database/Expression/OrderClauseExpression.php b/app/vendor/cakephp/cakephp/src/Database/Expression/OrderClauseExpression.php index dfab8b97f..8f5a7574c 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Expression/OrderClauseExpression.php +++ b/app/vendor/cakephp/cakephp/src/Database/Expression/OrderClauseExpression.php @@ -33,7 +33,7 @@ class OrderClauseExpression implements ExpressionInterface, FieldInterface * * @var string */ - protected $_direction; + protected string $_direction; /** * Constructor @@ -41,7 +41,7 @@ class OrderClauseExpression implements ExpressionInterface, FieldInterface * @param \Cake\Database\ExpressionInterface|string $field The field to order on. * @param string $direction The direction to sort on. */ - public function __construct($field, $direction) + public function __construct(ExpressionInterface|string $field, string $direction) { $this->_field = $field; $this->_direction = strtolower($direction) === 'asc' ? 'ASC' : 'DESC'; @@ -52,13 +52,13 @@ public function __construct($field, $direction) */ public function sql(ValueBinder $binder): string { - /** @var \Cake\Database\ExpressionInterface|string $field */ $field = $this->_field; if ($field instanceof Query) { $field = sprintf('(%s)', $field->sql($binder)); } elseif ($field instanceof ExpressionInterface) { $field = $field->sql($binder); } + assert(is_string($field)); return sprintf('%s %s', $field, $this->_direction); } diff --git a/app/vendor/cakephp/cakephp/src/Database/Expression/QueryExpression.php b/app/vendor/cakephp/cakephp/src/Database/Expression/QueryExpression.php index 0578c0019..2e4ef9c80 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Expression/QueryExpression.php +++ b/app/vendor/cakephp/cakephp/src/Database/Expression/QueryExpression.php @@ -18,12 +18,12 @@ use Cake\Database\ExpressionInterface; use Cake\Database\Query; +use Cake\Database\TypeMap; use Cake\Database\TypeMapTrait; use Cake\Database\ValueBinder; use Closure; use Countable; use InvalidArgumentException; -use function Cake\Core\deprecationWarning; /** * Represents a SQL Query expression. Internally it stores a tree of @@ -40,7 +40,7 @@ class QueryExpression implements ExpressionInterface, Countable * * @var string */ - protected $_conjunction; + protected string $_conjunction; /** * A list of strings or other expression objects that represent the "branches" of @@ -48,7 +48,7 @@ class QueryExpression implements ExpressionInterface, Countable * * @var array */ - protected $_conditions = []; + protected array $_conditions = []; /** * Constructor. A new expression object can be created without any params and @@ -65,11 +65,14 @@ class QueryExpression implements ExpressionInterface, Countable * level of the expression tree. For example "AND", "OR", "XOR"... * @see \Cake\Database\Expression\QueryExpression::add() for more details on $conditions and $types */ - public function __construct($conditions = [], $types = [], $conjunction = 'AND') - { + public function __construct( + ExpressionInterface|array|string $conditions = [], + TypeMap|array $types = [], + string $conjunction = 'AND', + ) { $this->setTypeMap($types); $this->setConjunction(strtoupper($conjunction)); - if (!empty($conditions)) { + if ($conditions) { $this->add($conditions, $this->getTypeMap()->getTypes()); } } @@ -117,7 +120,7 @@ public function getConjunction(): string * @see \Cake\Database\Query::where() for examples on conditions * @return $this */ - public function add($conditions, array $types = []) + public function add(ExpressionInterface|array|string $conditions, array $types = []) { if (is_string($conditions) || $conditions instanceof ExpressionInterface) { $this->_conditions[] = $conditions; @@ -140,11 +143,9 @@ public function add($conditions, array $types = []) * will be created, one per each value in the array. * @return $this */ - public function eq($field, $value, ?string $type = null) + public function eq(ExpressionInterface|string $field, mixed $value, ?string $type = null) { - if ($type === null) { - $type = $this->_calculateType($field); - } + $type ??= $this->_calculateType($field); return $this->add(new ComparisonExpression($field, $value, $type, '=')); } @@ -159,11 +160,9 @@ public function eq($field, $value, ?string $type = null) * will be created, one per each value in the array. * @return $this */ - public function notEq($field, $value, $type = null) + public function notEq(ExpressionInterface|string $field, mixed $value, ?string $type = null) { - if ($type === null) { - $type = $this->_calculateType($field); - } + $type ??= $this->_calculateType($field); return $this->add(new ComparisonExpression($field, $value, $type, '!=')); } @@ -176,11 +175,9 @@ public function notEq($field, $value, $type = null) * @param string|null $type the type name for $value as configured using the Type map. * @return $this */ - public function gt($field, $value, $type = null) + public function gt(ExpressionInterface|string $field, mixed $value, ?string $type = null) { - if ($type === null) { - $type = $this->_calculateType($field); - } + $type ??= $this->_calculateType($field); return $this->add(new ComparisonExpression($field, $value, $type, '>')); } @@ -193,11 +190,9 @@ public function gt($field, $value, $type = null) * @param string|null $type the type name for $value as configured using the Type map. * @return $this */ - public function lt($field, $value, $type = null) + public function lt(ExpressionInterface|string $field, mixed $value, ?string $type = null) { - if ($type === null) { - $type = $this->_calculateType($field); - } + $type ??= $this->_calculateType($field); return $this->add(new ComparisonExpression($field, $value, $type, '<')); } @@ -210,11 +205,9 @@ public function lt($field, $value, $type = null) * @param string|null $type the type name for $value as configured using the Type map. * @return $this */ - public function gte($field, $value, $type = null) + public function gte(ExpressionInterface|string $field, mixed $value, ?string $type = null) { - if ($type === null) { - $type = $this->_calculateType($field); - } + $type ??= $this->_calculateType($field); return $this->add(new ComparisonExpression($field, $value, $type, '>=')); } @@ -227,11 +220,9 @@ public function gte($field, $value, $type = null) * @param string|null $type the type name for $value as configured using the Type map. * @return $this */ - public function lte($field, $value, $type = null) + public function lte(ExpressionInterface|string $field, mixed $value, ?string $type = null) { - if ($type === null) { - $type = $this->_calculateType($field); - } + $type ??= $this->_calculateType($field); return $this->add(new ComparisonExpression($field, $value, $type, '<=')); } @@ -243,7 +234,7 @@ public function lte($field, $value, $type = null) * tested for null * @return $this */ - public function isNull($field) + public function isNull(ExpressionInterface|string $field) { if (!($field instanceof ExpressionInterface)) { $field = new IdentifierExpression($field); @@ -259,7 +250,7 @@ public function isNull($field) * tested for not null * @return $this */ - public function isNotNull($field) + public function isNotNull(ExpressionInterface|string $field) { if (!($field instanceof ExpressionInterface)) { $field = new IdentifierExpression($field); @@ -276,11 +267,9 @@ public function isNotNull($field) * @param string|null $type the type name for $value as configured using the Type map. * @return $this */ - public function like($field, $value, $type = null) + public function like(ExpressionInterface|string $field, mixed $value, ?string $type = null) { - if ($type === null) { - $type = $this->_calculateType($field); - } + $type ??= $this->_calculateType($field); return $this->add(new ComparisonExpression($field, $value, $type, 'LIKE')); } @@ -293,11 +282,9 @@ public function like($field, $value, $type = null) * @param string|null $type the type name for $value as configured using the Type map. * @return $this */ - public function notLike($field, $value, $type = null) + public function notLike(ExpressionInterface|string $field, mixed $value, ?string $type = null) { - if ($type === null) { - $type = $this->_calculateType($field); - } + $type ??= $this->_calculateType($field); return $this->add(new ComparisonExpression($field, $value, $type, 'NOT LIKE')); } @@ -311,11 +298,12 @@ public function notLike($field, $value, $type = null) * @param string|null $type the type name for $value as configured using the Type map. * @return $this */ - public function in($field, $values, $type = null) - { - if ($type === null) { - $type = $this->_calculateType($field); - } + public function in( + ExpressionInterface|string $field, + ExpressionInterface|array|string $values, + ?string $type = null, + ) { + $type ??= $this->_calculateType($field); $type = $type ?: 'string'; $type .= '[]'; $values = $values instanceof ExpressionInterface ? $values : (array)$values; @@ -323,26 +311,6 @@ public function in($field, $values, $type = null) return $this->add(new ComparisonExpression($field, $values, $type, 'IN')); } - /** - * Adds a new case expression to the expression object - * - * @param \Cake\Database\ExpressionInterface|array $conditions The conditions to test. Must be a ExpressionInterface - * instance, or an array of ExpressionInterface instances. - * @param \Cake\Database\ExpressionInterface|array $values Associative array of values to be associated with the - * conditions passed in $conditions. If there are more $values than $conditions, - * the last $value is used as the `ELSE` value. - * @param array $types Associative array of types to be associated with the values - * passed in $values - * @return $this - * @deprecated 4.3.0 Use QueryExpression::case() or CaseStatementExpression instead - */ - public function addCase($conditions, $values = [], $types = []) - { - deprecationWarning('QueryExpression::addCase() is deprecated, use case() instead.'); - - return $this->add(new CaseExpression($conditions, $values, $types)); - } - /** * Returns a new case expression object. * @@ -364,7 +332,7 @@ public function addCase($conditions, $values = [], $types = []) * from the value. * @return \Cake\Database\Expression\CaseStatementExpression */ - public function case($value = null, ?string $type = null): CaseStatementExpression + public function case(mixed $value = null, ?string $type = null): CaseStatementExpression { if (func_num_args() > 0) { $expression = new CaseStatementExpression($value, $type); @@ -384,11 +352,12 @@ public function case($value = null, ?string $type = null): CaseStatementExpressi * @param string|null $type the type name for $value as configured using the Type map. * @return $this */ - public function notIn($field, $values, $type = null) - { - if ($type === null) { - $type = $this->_calculateType($field); - } + public function notIn( + ExpressionInterface|string $field, + ExpressionInterface|array|string $values, + ?string $type = null, + ) { + $type ??= $this->_calculateType($field); $type = $type ?: 'string'; $type .= '[]'; $values = $values instanceof ExpressionInterface ? $values : (array)$values; @@ -405,8 +374,11 @@ public function notIn($field, $values, $type = null) * @param string|null $type the type name for $value as configured using the Type map. * @return $this */ - public function notInOrNull($field, $values, ?string $type = null) - { + public function notInOrNull( + ExpressionInterface|string $field, + ExpressionInterface|array|string $values, + ?string $type = null, + ) { $or = new static([], [], 'OR'); $or ->notIn($field, $values, $type) @@ -441,17 +413,15 @@ public function notExists(ExpressionInterface $expression) * Adds a new condition to the expression object in the form * "field BETWEEN from AND to". * - * @param \Cake\Database\ExpressionInterface|string $field The field name to compare for values inbetween the range. + * @param \Cake\Database\ExpressionInterface|string $field The field name to compare for values in between the range. * @param mixed $from The initial value of the range. * @param mixed $to The ending value in the comparison range. * @param string|null $type the type name for $value as configured using the Type map. * @return $this */ - public function between($field, $from, $to, $type = null) + public function between(ExpressionInterface|string $field, mixed $from, mixed $to, ?string $type = null) { - if ($type === null) { - $type = $this->_calculateType($field); - } + $type ??= $this->_calculateType($field); return $this->add(new BetweenExpression($field, $from, $to, $type)); } @@ -463,9 +433,9 @@ public function between($field, $from, $to, $type = null) * @param \Cake\Database\ExpressionInterface|\Closure|array|string $conditions to be joined with AND * @param array $types Associative array of fields pointing to the type of the * values that are being passed. Used for correctly binding values to statements. - * @return \Cake\Database\Expression\QueryExpression + * @return static */ - public function and($conditions, $types = []) + public function and(ExpressionInterface|Closure|array|string $conditions, array $types = []): static { if ($conditions instanceof Closure) { return $conditions(new static([], $this->getTypeMap()->setTypes($types))); @@ -481,9 +451,9 @@ public function and($conditions, $types = []) * @param \Cake\Database\ExpressionInterface|\Closure|array|string $conditions to be joined with OR * @param array $types Associative array of fields pointing to the type of the * values that are being passed. Used for correctly binding values to statements. - * @return \Cake\Database\Expression\QueryExpression + * @return static */ - public function or($conditions, $types = []) + public function or(ExpressionInterface|Closure|array|string $conditions, array $types = []): static { if ($conditions instanceof Closure) { return $conditions(new static([], $this->getTypeMap()->setTypes($types), 'OR')); @@ -492,44 +462,6 @@ public function or($conditions, $types = []) return new static($conditions, $this->getTypeMap()->setTypes($types), 'OR'); } - // phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps - - /** - * Returns a new QueryExpression object containing all the conditions passed - * and set up the conjunction to be "AND" - * - * @param \Cake\Database\ExpressionInterface|\Closure|array|string $conditions to be joined with AND - * @param array $types Associative array of fields pointing to the type of the - * values that are being passed. Used for correctly binding values to statements. - * @return \Cake\Database\Expression\QueryExpression - * @deprecated 4.0.0 Use {@link and()} instead. - */ - public function and_($conditions, $types = []) - { - deprecationWarning('QueryExpression::and_() is deprecated use and() instead.'); - - return $this->and($conditions, $types); - } - - /** - * Returns a new QueryExpression object containing all the conditions passed - * and set up the conjunction to be "OR" - * - * @param \Cake\Database\ExpressionInterface|\Closure|array|string $conditions to be joined with OR - * @param array $types Associative array of fields pointing to the type of the - * values that are being passed. Used for correctly binding values to statements. - * @return \Cake\Database\Expression\QueryExpression - * @deprecated 4.0.0 Use {@link or()} instead. - */ - public function or_($conditions, $types = []) - { - deprecationWarning('QueryExpression::or_() is deprecated use or() instead.'); - - return $this->or($conditions, $types); - } - - // phpcs:enable - /** * Adds a new set of conditions to this level of the tree and negates * the final result by prepending a NOT, it will look like @@ -541,7 +473,7 @@ public function or_($conditions, $types = []) * values that are being passed. Used for correctly binding values to statements. * @return $this */ - public function not($conditions, $types = []) + public function not(ExpressionInterface|Closure|array|string $conditions, array $types = []) { return $this->add(['NOT' => $conditions], $types); } @@ -567,7 +499,7 @@ public function count(): int */ public function equalFields(string $leftField, string $rightField) { - $wrapIdentifier = function ($field) { + $wrapIdentifier = function ($field): ExpressionInterface { if ($field instanceof ExpressionInterface) { return $field; } @@ -601,7 +533,7 @@ public function sql(ValueBinder $binder): string } } - return sprintf($template, implode(" $conjunction ", $parts)); + return sprintf($template, implode(" {$conjunction} ", $parts)); } /** @@ -620,10 +552,10 @@ public function traverse(Closure $callback) } /** - * Executes a callable function for each of the parts that form this expression. + * Executes a callback for each of the parts that form this expression. * - * The callable function is required to return a value with which the currently - * visited part will be replaced. If the callable function returns null then + * The callback is required to return a value with which the currently + * visited part will be replaced. If the callback returns null then * the part will be discarded completely from this expression. * * The callback function will receive each of the conditions as first param and @@ -631,10 +563,10 @@ public function traverse(Closure $callback) * passed by reference, this will enable you to change the key under which the * modified part is stored. * - * @param callable $callback The callable to apply to each part. + * @param \Closure $callback The callback to run for each part * @return $this */ - public function iterateParts(callable $callback) + public function iterateParts(Closure $callback) { $parts = []; foreach ($this->_conditions as $k => $c) { @@ -649,30 +581,6 @@ public function iterateParts(callable $callback) return $this; } - /** - * Check whether a callable is acceptable. - * - * We don't accept ['class', 'method'] style callbacks, - * as they often contain user input and arrays of strings - * are easy to sneak in. - * - * @param \Cake\Database\ExpressionInterface|callable|array|string $callable The callable to check. - * @return bool Valid callable. - * @deprecated 4.2.0 This method is unused. - * @codeCoverageIgnore - */ - public function isCallable($callable): bool - { - if (is_string($callable)) { - return false; - } - if (is_object($callable) && is_callable($callable)) { - return true; - } - - return is_array($callable) && isset($callable[0]) && is_object($callable[0]) && is_callable($callable); - } - /** * Returns true if this expression contains any other nested * ExpressionInterface objects @@ -719,7 +627,8 @@ protected function _addConditions(array $conditions, array $types): void } $isArray = is_array($c); - $isOperator = $isNot = false; + $isOperator = false; + $isNot = false; if (!$numericKey) { $normalizedKey = strtolower($k); $isOperator = in_array($normalizedKey, $operators); @@ -763,26 +672,25 @@ protected function _addConditions(array $conditions, array $types): void * generating the placeholders and replacing the values by them, while storing * the value elsewhere for future binding. * - * @param string $field The value from which the actual field and operator will + * @param string $condition The value from which the actual field and operator will * be extracted. * @param mixed $value The value to be bound to a placeholder for the field - * @return \Cake\Database\ExpressionInterface + * @return \Cake\Database\ExpressionInterface|string * @throws \InvalidArgumentException If operator is invalid or missing on NULL usage. */ - protected function _parseCondition(string $field, $value) + protected function _parseCondition(string $condition, mixed $value): ExpressionInterface|string { - $field = trim($field); + $expression = trim($condition); $operator = '='; - $expression = $field; - $spaces = substr_count($field, ' '); - // Handle field values that contain multiple spaces, such as + $spaces = substr_count($expression, ' '); + // Handle expression values that contain multiple spaces, such as // operators with a space in them like `field IS NOT` and // `field NOT LIKE`, or combinations with function expressions // like `CONCAT(first_name, ' ', last_name) IN`. if ($spaces > 1) { - $parts = explode(' ', $field); - if (preg_match('/(is not|not \w+)$/i', $field)) { + $parts = explode(' ', $expression); + if (preg_match('/(is not|not \w+)$/i', $expression)) { $last = array_pop($parts); $second = array_pop($parts); $parts[] = "{$second} {$last}"; @@ -790,14 +698,14 @@ protected function _parseCondition(string $field, $value) $operator = array_pop($parts); $expression = implode(' ', $parts); } elseif ($spaces == 1) { - $parts = explode(' ', $field, 2); + $parts = explode(' ', $expression, 2); [$expression, $operator] = $parts; } - $operator = strtolower(trim($operator)); - $type = $this->getTypeMap()->type($expression); + $operator = strtoupper(trim($operator)); - $typeMultiple = (is_string($type) && strpos($type, '[]') !== false); - if (in_array($operator, ['in', 'not in']) || $typeMultiple) { + $type = $this->getTypeMap()->type($expression); + $typeMultiple = (is_string($type) && str_contains($type, '[]')); + if (in_array($operator, ['IN', 'NOT IN']) || $typeMultiple) { $type = $type ?: 'string'; if (!$typeMultiple) { $type .= '[]'; @@ -811,33 +719,37 @@ protected function _parseCondition(string $field, $value) $value = $value instanceof ExpressionInterface ? $value : (array)$value; } - if ($operator === 'is' && $value === null) { + if ($operator === 'IS' && $value === null) { return new UnaryExpression( 'IS NULL', new IdentifierExpression($expression), - UnaryExpression::POSTFIX + UnaryExpression::POSTFIX, ); } - if ($operator === 'is not' && $value === null) { + if ($operator === 'IS NOT' && $value === null) { return new UnaryExpression( 'IS NOT NULL', new IdentifierExpression($expression), - UnaryExpression::POSTFIX + UnaryExpression::POSTFIX, ); } - if ($operator === 'is' && $value !== null) { + if ($operator === 'IS' && $value !== null) { $operator = '='; } - if ($operator === 'is not' && $value !== null) { + if ($operator === 'IS NOT' && $value !== null) { $operator = '!='; } if ($value === null && $this->_conjunction !== ',') { throw new InvalidArgumentException( - sprintf('Expression `%s` is missing operator (IS, IS NOT) with `null` value.', $expression) + sprintf( + 'Expression `%s` has invalid `null` value.' + . ' If `null` is a valid value, operator (IS, IS NOT) is missing.', + $expression, + ), ); } @@ -850,14 +762,14 @@ protected function _parseCondition(string $field, $value) * @param \Cake\Database\ExpressionInterface|string $field The field name to get a type for. * @return string|null The computed type or null, if the type is unknown. */ - protected function _calculateType($field): ?string + protected function _calculateType(ExpressionInterface|string $field): ?string { $field = $field instanceof IdentifierExpression ? $field->getIdentifier() : $field; - if (is_string($field)) { - return $this->getTypeMap()->type($field); + if (!is_string($field)) { + return null; } - return null; + return $this->getTypeMap()->type($field); } /** diff --git a/app/vendor/cakephp/cakephp/src/Database/Expression/StringExpression.php b/app/vendor/cakephp/cakephp/src/Database/Expression/StringExpression.php index e4380744b..4291d5b3b 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Expression/StringExpression.php +++ b/app/vendor/cakephp/cakephp/src/Database/Expression/StringExpression.php @@ -28,12 +28,12 @@ class StringExpression implements ExpressionInterface /** * @var string */ - protected $string; + protected string $string; /** * @var string */ - protected $collation; + protected string $collation; /** * @param string $string String value diff --git a/app/vendor/cakephp/cakephp/src/Database/Expression/TupleComparison.php b/app/vendor/cakephp/cakephp/src/Database/Expression/TupleComparison.php index b5c36f071..39bbc0671 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Expression/TupleComparison.php +++ b/app/vendor/cakephp/cakephp/src/Database/Expression/TupleComparison.php @@ -31,9 +31,8 @@ class TupleComparison extends ComparisonExpression * The type to be used for casting the value to a database representation * * @var array - * @psalm-suppress NonInvariantDocblockPropertyType */ - protected $_type; + protected array $types; /** * Constructor @@ -44,9 +43,13 @@ class TupleComparison extends ComparisonExpression * one type per position in the value array in needed * @param string $conjunction the operator used for comparing field and value */ - public function __construct($fields, $values, array $types = [], string $conjunction = '=') - { - $this->_type = $types; + public function __construct( + ExpressionInterface|array|string $fields, + ExpressionInterface|array $values, + array $types = [], + string $conjunction = '=', + ) { + $this->types = $types; $this->setField($fields); $this->_operator = $conjunction; $this->setValue($values); @@ -59,7 +62,7 @@ public function __construct($fields, $values, array $types = [], string $conjunc */ public function getType(): array { - return $this->_type; + return $this->types; } /** @@ -68,20 +71,18 @@ public function getType(): array * @param mixed $value The value to compare * @return void */ - public function setValue($value): void + public function setValue(mixed $value): void { if ($this->isMulti()) { if (is_array($value) && !is_array(current($value))) { throw new InvalidArgumentException( - 'Multi-tuple comparisons require a multi-tuple value, single-tuple given.' - ); - } - } else { - if (is_array($value) && is_array(current($value))) { - throw new InvalidArgumentException( - 'Single-tuple comparisons require a single-tuple value, multi-tuple given.' + 'Multi-tuple comparisons require a multi-tuple value, single-tuple given.', ); } + } elseif (is_array($value) && is_array(current($value))) { + throw new InvalidArgumentException( + 'Single-tuple comparisons require a single-tuple value, multi-tuple given.', + ); } $this->_value = $value; @@ -133,17 +134,17 @@ protected function _stringifyValues(ValueBinder $binder): string continue; } - $type = $this->_type; + $type = $this->types; $isMultiOperation = $this->isMulti(); - if (empty($type)) { + if (!$type) { $type = null; } if ($isMultiOperation) { $bound = []; foreach ($value as $k => $val) { - /** @var string $valType */ $valType = $type && isset($type[$k]) ? $type[$k] : $type; + assert($valType === null || is_scalar($valType)); $bound[] = $this->_bindValue($val, $binder, $valType); } @@ -151,8 +152,8 @@ protected function _stringifyValues(ValueBinder $binder): string continue; } - /** @var string $valType */ $valType = $type && isset($type[$i]) ? $type[$i] : $type; + assert($valType === null || is_scalar($valType)); $values[] = $this->_bindValue($value, $binder, $valType); } @@ -162,7 +163,7 @@ protected function _stringifyValues(ValueBinder $binder): string /** * @inheritDoc */ - protected function _bindValue($value, ValueBinder $binder, ?string $type = null): string + protected function _bindValue(mixed $value, ValueBinder $binder, ?string $type = null): string { $placeholder = $binder->placeholder('tuple'); $binder->bind($placeholder, $value, $type); @@ -175,8 +176,7 @@ protected function _bindValue($value, ValueBinder $binder, ?string $type = null) */ public function traverse(Closure $callback) { - /** @var array $fields */ - $fields = $this->getField(); + $fields = (array)$this->getField(); foreach ($fields as $field) { $this->_traverseValue($field, $callback); } @@ -207,10 +207,10 @@ public function traverse(Closure $callback) * it is an ExpressionInterface * * @param mixed $value The value to traverse - * @param \Closure $callback The callable to use when traversing + * @param \Closure $callback The callback to use when traversing * @return void */ - protected function _traverseValue($value, Closure $callback): void + protected function _traverseValue(mixed $value, Closure $callback): void { if ($value instanceof ExpressionInterface) { $callback($value); diff --git a/app/vendor/cakephp/cakephp/src/Database/Expression/UnaryExpression.php b/app/vendor/cakephp/cakephp/src/Database/Expression/UnaryExpression.php index 1c40f913f..fdf1cff20 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Expression/UnaryExpression.php +++ b/app/vendor/cakephp/cakephp/src/Database/Expression/UnaryExpression.php @@ -44,21 +44,21 @@ class UnaryExpression implements ExpressionInterface * * @var string */ - protected $_operator; + protected string $_operator; /** * Holds the value which the unary expression operates * * @var mixed */ - protected $_value; + protected mixed $_value; /** * Where to place the operator * * @var int */ - protected $position; + protected int $position; /** * Constructor @@ -67,7 +67,7 @@ class UnaryExpression implements ExpressionInterface * @param mixed $value the value to use as the operand for the expression * @param int $position either UnaryExpression::PREFIX or UnaryExpression::POSTFIX */ - public function __construct(string $operator, $value, $position = self::PREFIX) + public function __construct(string $operator, mixed $value, int $position = self::PREFIX) { $this->_operator = $operator; $this->_value = $value; diff --git a/app/vendor/cakephp/cakephp/src/Database/Expression/ValuesExpression.php b/app/vendor/cakephp/cakephp/src/Database/Expression/ValuesExpression.php index 68dbf25c8..7a15c12a1 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Expression/ValuesExpression.php +++ b/app/vendor/cakephp/cakephp/src/Database/Expression/ValuesExpression.php @@ -41,21 +41,21 @@ class ValuesExpression implements ExpressionInterface * * @var array */ - protected $_values = []; + protected array $_values = []; /** * List of columns to ensure are part of the insert. * * @var array */ - protected $_columns = []; + protected array $_columns = []; /** * The Query object to use as a values expression * * @var \Cake\Database\Query|null */ - protected $_query; + protected ?Query $_query = null; /** * Whether values have been casted to expressions @@ -63,7 +63,7 @@ class ValuesExpression implements ExpressionInterface * * @var bool */ - protected $_castedExpressions = false; + protected bool $_castedExpressions = false; /** * Constructor @@ -83,9 +83,9 @@ public function __construct(array $columns, TypeMap $typeMap) * @param \Cake\Database\Query|array $values Array of data to append into the insert, or * a query for doing INSERT INTO .. SELECT style commands * @return void - * @throws \Cake\Database\Exception\DatabaseException When mixing array + Query data types. + * @throws \Cake\Database\Exception\DatabaseException When mixing array and Query data types. */ - public function add($values): void + public function add(Query|array $values): void { if ( ( @@ -98,7 +98,7 @@ public function add($values): void ) ) { throw new DatabaseException( - 'You cannot mix subqueries and array values in inserts.' + 'You cannot mix subqueries and array values in inserts.', ); } if ($values instanceof Query) { @@ -213,7 +213,7 @@ public function getQuery(): ?Query */ public function sql(ValueBinder $binder): string { - if (empty($this->_values) && empty($this->_query)) { + if (!$this->_values && $this->_query === null) { return ''; } @@ -310,7 +310,7 @@ protected function _processExpressions(): void $types = $this->_requiresToExpressionCasting($types); - if (empty($types)) { + if (!$types) { return; } diff --git a/app/vendor/cakephp/cakephp/src/Database/Expression/WhenThenExpression.php b/app/vendor/cakephp/cakephp/src/Database/Expression/WhenThenExpression.php index bbc415414..4cfc3f136 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Expression/WhenThenExpression.php +++ b/app/vendor/cakephp/cakephp/src/Database/Expression/WhenThenExpression.php @@ -24,7 +24,6 @@ use Closure; use InvalidArgumentException; use LogicException; -use function Cake\Core\getTypeName; /** * Represents a SQL when/then clause with a fluid API @@ -40,7 +39,7 @@ class WhenThenExpression implements ExpressionInterface * * @var array */ - protected $validClauseNames = [ + protected array $validClauseNames = [ 'when', 'then', ]; @@ -51,28 +50,28 @@ class WhenThenExpression implements ExpressionInterface * * @var \Cake\Database\TypeMap */ - protected $_typeMap; + protected TypeMap $_typeMap; /** * Then `WHEN` value. * * @var \Cake\Database\ExpressionInterface|object|scalar|null */ - protected $when = null; + protected mixed $when = null; /** * The `WHEN` value type. * * @var array|string|null */ - protected $whenType = null; + protected array|string|null $whenType = null; /** * The `THEN` value. * * @var \Cake\Database\ExpressionInterface|object|scalar|null */ - protected $then = null; + protected mixed $then = null; /** * Whether the `THEN` value has been defined, eg whether `then()` @@ -80,14 +79,14 @@ class WhenThenExpression implements ExpressionInterface * * @var bool */ - protected $hasThenBeenDefined = false; + protected bool $hasThenBeenDefined = false; /** * The `THEN` result type. * * @var string|null */ - protected $thenType = null; + protected ?string $thenType = null; /** * Constructor. @@ -97,16 +96,13 @@ class WhenThenExpression implements ExpressionInterface */ public function __construct(?TypeMap $typeMap = null) { - if ($typeMap === null) { - $typeMap = new TypeMap(); - } - $this->_typeMap = $typeMap; + $this->_typeMap = $typeMap ?? new TypeMap(); } /** * Sets the `WHEN` value. * - * @param \Cake\Database\ExpressionInterface|object|array|scalar $when The `WHEN` value. When using an array of + * @param object|array|string|float|int|bool $when The `WHEN` value. When using an array of * conditions, it must be compatible with `\Cake\Database\Query::where()`. Note that this argument is _not_ * completely safe for use with user data, as a user supplied array would allow for raw SQL to slip in! If you * plan to use user data, either pass a single type for the `$type` argument (which forces the `$when` value to be @@ -115,42 +111,20 @@ public function __construct(?TypeMap $typeMap = null) * @param array|string|null $type The when value type. Either an associative array when using array style * conditions, or else a string. If no type is provided, the type will be tried to be inferred from the value. * @return $this - * @throws \InvalidArgumentException In case the `$when` argument is neither a non-empty array, nor a scalar value, - * an object, or an instance of `\Cake\Database\ExpressionInterface`. - * @throws \InvalidArgumentException In case the `$type` argument is neither an array, a string, nor null. + * @throws \InvalidArgumentException In case the `$when` argument is an empty array. * @throws \InvalidArgumentException In case the `$when` argument is an array, and the `$type` argument is neither * an array, nor null. * @throws \InvalidArgumentException In case the `$when` argument is a non-array value, and the `$type` argument is * neither a string, nor null. * @see CaseStatementExpression::when() for a more detailed usage explanation. */ - public function when($when, $type = null) + public function when(object|array|string|float|int|bool $when, array|string|null $type = null) { - if ( - !(is_array($when) && !empty($when)) && - !is_scalar($when) && - !is_object($when) - ) { - throw new InvalidArgumentException(sprintf( - 'The `$when` argument must be either a non-empty array, a scalar value, an object, ' . - 'or an instance of `\%s`, `%s` given.', - ExpressionInterface::class, - is_array($when) ? '[]' : getTypeName($when) - )); - } - - if ( - $type !== null && - !is_array($type) && - !is_string($type) - ) { - throw new InvalidArgumentException(sprintf( - 'The `$type` argument must be either an array, a string, or `null`, `%s` given.', - getTypeName($type) - )); - } - if (is_array($when)) { + if (!$when) { + throw new InvalidArgumentException('The `$when` argument must be a non-empty array'); + } + if ( $type !== null && !is_array($type) @@ -158,7 +132,7 @@ public function when($when, $type = null) throw new InvalidArgumentException(sprintf( 'When using an array for the `$when` argument, the `$type` argument must be an ' . 'array too, `%s` given.', - getTypeName($type) + get_debug_type($type), )); } @@ -166,7 +140,7 @@ public function when($when, $type = null) $typeMap = clone $this->_typeMap; if ( is_array($type) && - count($type) > 0 + $type !== [] ) { $typeMap = $typeMap->setTypes($type); } @@ -180,7 +154,7 @@ public function when($when, $type = null) throw new InvalidArgumentException(sprintf( 'When using a non-array value for the `$when` argument, the `$type` argument must ' . 'be a string, `%s` given.', - getTypeName($type) + get_debug_type($type), )); } @@ -206,7 +180,7 @@ public function when($when, $type = null) * result value. * @return $this */ - public function then($result, ?string $type = null) + public function then(mixed $result, ?string $type = null) { if ( $result !== null && @@ -217,17 +191,13 @@ public function then($result, ?string $type = null) 'The `$result` argument must be either `null`, a scalar value, an object, ' . 'or an instance of `\%s`, `%s` given.', ExpressionInterface::class, - getTypeName($result) + get_debug_type($result), )); } $this->then = $result; - if ($type === null) { - $type = $this->inferType($result); - } - - $this->thenType = $type; + $this->thenType = $type ?? $this->inferType($result); $this->hasThenBeenDefined = true; @@ -259,15 +229,15 @@ public function getResultType(): ?string * @return \Cake\Database\ExpressionInterface|object|scalar|null * @throws \InvalidArgumentException In case the given clause name is invalid. */ - public function clause(string $clause) + public function clause(string $clause): mixed { if (!in_array($clause, $this->validClauseNames, true)) { throw new InvalidArgumentException( sprintf( 'The `$clause` argument must be one of `%s`, the given value `%s` is invalid.', implode('`, `', $this->validClauseNames), - $clause - ) + $clause, + ), ); } @@ -311,7 +281,7 @@ public function sql(ValueBinder $binder): string $then = $this->compileNullableValue($binder, $this->then, $this->thenType); - return "WHEN $when THEN $then"; + return "WHEN {$when} THEN {$then}"; } /** diff --git a/app/vendor/cakephp/cakephp/src/Database/Expression/WindowExpression.php b/app/vendor/cakephp/cakephp/src/Database/Expression/WindowExpression.php index 383e652cf..f0f46ebe7 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Expression/WindowExpression.php +++ b/app/vendor/cakephp/cakephp/src/Database/Expression/WindowExpression.php @@ -19,6 +19,7 @@ use Cake\Database\ExpressionInterface; use Cake\Database\ValueBinder; use Closure; +use function Cake\Core\deprecationWarning; /** * This represents a SQL window expression used by aggregate and window functions. @@ -28,27 +29,27 @@ class WindowExpression implements ExpressionInterface, WindowInterface /** * @var \Cake\Database\Expression\IdentifierExpression */ - protected $name; + protected IdentifierExpression $name; /** * @var array<\Cake\Database\ExpressionInterface> */ - protected $partitions = []; + protected array $partitions = []; /** * @var \Cake\Database\Expression\OrderByExpression|null */ - protected $order; + protected ?OrderByExpression $order = null; /** * @var array|null */ - protected $frame; + protected ?array $frame = null; /** * @var string|null */ - protected $exclusion; + protected ?string $exclusion = null; /** * @param string $name Window name @@ -87,7 +88,7 @@ public function name(string $name) /** * @inheritDoc */ - public function partition($partitions) + public function partition(ExpressionInterface|Closure|array|string $partitions) { if (!$partitions) { return $this; @@ -115,15 +116,26 @@ public function partition($partitions) /** * @inheritDoc */ - public function order($fields) + public function order(ExpressionInterface|Closure|array|string $fields) + { + deprecationWarning( + '5.0.0', + 'WindowExpression::order() is deprecated. Use WindowExpression::orderBy() instead.', + ); + + return $this->orderBy($fields); + } + + /** + * @inheritDoc + */ + public function orderBy(ExpressionInterface|Closure|array|string $fields) { if (!$fields) { return $this; } - if ($this->order === null) { - $this->order = new OrderByExpression(); - } + $this->order ??= new OrderByExpression(); if ($fields instanceof Closure) { $fields = $fields(new QueryExpression([], [], '')); @@ -137,7 +149,7 @@ public function order($fields) /** * @inheritDoc */ - public function range($start, $end = 0) + public function range(ExpressionInterface|string|int|null $start, ExpressionInterface|string|int|null $end = 0) { return $this->frame(self::RANGE, $start, self::PRECEDING, $end, self::FOLLOWING); } @@ -163,10 +175,10 @@ public function groups(?int $start, ?int $end = 0) */ public function frame( string $type, - $startOffset, + ExpressionInterface|string|int|null $startOffset, string $startDirection, - $endOffset, - string $endDirection + ExpressionInterface|string|int|null $endOffset, + string $endDirection, ) { $this->frame = [ 'type' => $type, @@ -240,12 +252,12 @@ public function sql(ValueBinder $binder): string $start = $this->buildOffsetSql( $binder, $this->frame['start']['offset'], - $this->frame['start']['direction'] + $this->frame['start']['direction'], ); $end = $this->buildOffsetSql( $binder, $this->frame['end']['offset'], - $this->frame['end']['direction'] + $this->frame['end']['direction'], ); $frameSql = sprintf('%s BETWEEN %s AND %s', $this->frame['type'], $start, $end); @@ -300,8 +312,11 @@ public function traverse(Closure $callback) * @param string $direction Frame offset direction * @return string */ - protected function buildOffsetSql(ValueBinder $binder, $offset, string $direction): string - { + protected function buildOffsetSql( + ValueBinder $binder, + ExpressionInterface|string|int|null $offset, + string $direction, + ): string { if ($offset === 0) { return 'CURRENT ROW'; } @@ -313,7 +328,7 @@ protected function buildOffsetSql(ValueBinder $binder, $offset, string $directio return sprintf( '%s %s', $offset ?? 'UNBOUNDED', - $direction + $direction, ); } diff --git a/app/vendor/cakephp/cakephp/src/Database/Expression/WindowInterface.php b/app/vendor/cakephp/cakephp/src/Database/Expression/WindowInterface.php index 7a7bac57b..f96441e71 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Expression/WindowInterface.php +++ b/app/vendor/cakephp/cakephp/src/Database/Expression/WindowInterface.php @@ -16,6 +16,9 @@ */ namespace Cake\Database\Expression; +use Cake\Database\ExpressionInterface; +use Closure; + /** * This defines the functions used for building window expressions. */ @@ -52,15 +55,24 @@ interface WindowInterface * @param \Cake\Database\ExpressionInterface|\Closure|array<\Cake\Database\ExpressionInterface|string>|string $partitions Partition expressions * @return $this */ - public function partition($partitions); + public function partition(ExpressionInterface|Closure|array|string $partitions); + + /** + * Adds one or more order by clauses to the window. + * + * @param \Cake\Database\ExpressionInterface|\Closure|array<\Cake\Database\ExpressionInterface|string>|string $fields Order expressions + * @return $this + * @deprecated 5.0.0 Use orderBy() instead. + */ + public function order(ExpressionInterface|Closure|array|string $fields); /** - * Adds one or more order clauses to the window. + * Adds one or more order by clauses to the window. * * @param \Cake\Database\ExpressionInterface|\Closure|array<\Cake\Database\ExpressionInterface|string>|string $fields Order expressions * @return $this */ - public function order($fields); + public function orderBy(ExpressionInterface|Closure|array|string $fields); /** * Adds a simple range frame to the window. @@ -83,7 +95,7 @@ public function order($fields); * If not passed in, only frame start SQL will be generated. * @return $this */ - public function range($start, $end = 0); + public function range(ExpressionInterface|string|int|null $start, ExpressionInterface|string|int|null $end = 0); /** * Adds a simple rows frame to the window. @@ -128,16 +140,16 @@ public function groups(?int $start, ?int $end = 0); * @param string $endDirection Frame end direction * @return $this * @throws \InvalidArgumentException WHen offsets are negative. - * @psalm-param self::RANGE|self::ROWS|self::GROUPS $type - * @psalm-param self::PRECEDING|self::FOLLOWING $startDirection - * @psalm-param self::PRECEDING|self::FOLLOWING $endDirection + * @phpstan-param self::RANGE|self::ROWS|self::GROUPS $type + * @phpstan-param self::PRECEDING|self::FOLLOWING $startDirection + * @phpstan-param self::PRECEDING|self::FOLLOWING $endDirection */ public function frame( string $type, - $startOffset, + ExpressionInterface|string|int|null $startOffset, string $startDirection, - $endOffset, - string $endDirection + ExpressionInterface|string|int|null $endOffset, + string $endDirection, ); /** diff --git a/app/vendor/cakephp/cakephp/src/Database/ExpressionInterface.php b/app/vendor/cakephp/cakephp/src/Database/ExpressionInterface.php index 3e3588c45..faee9cd3a 100644 --- a/app/vendor/cakephp/cakephp/src/Database/ExpressionInterface.php +++ b/app/vendor/cakephp/cakephp/src/Database/ExpressionInterface.php @@ -33,11 +33,11 @@ public function sql(ValueBinder $binder): string; /** * Iterates over each part of the expression recursively for every - * level of the expressions tree and executes the $callback callable + * level of the expressions tree and executes the callback, * passing as first parameter the instance of the expression currently * being iterated. * - * @param \Closure $callback The callable to apply to all nodes. + * @param \Closure $callback The callback to run for all nodes. * @return $this */ public function traverse(Closure $callback); diff --git a/app/vendor/cakephp/cakephp/src/Database/FieldTypeConverter.php b/app/vendor/cakephp/cakephp/src/Database/FieldTypeConverter.php index 3448aa1ef..a41b339e9 100644 --- a/app/vendor/cakephp/cakephp/src/Database/FieldTypeConverter.php +++ b/app/vendor/cakephp/cakephp/src/Database/FieldTypeConverter.php @@ -20,118 +20,75 @@ use Cake\Database\Type\OptionalConvertInterface; /** - * A callable class to be used for processing each of the rows in a statement + * An invokable class to be used for processing each of the rows in a statement * result, so that the values are converted to the right PHP types. + * + * @internal */ class FieldTypeConverter { /** - * An array containing the name of the fields and the Type objects - * each should use when converting them. - * - * @var array<\Cake\Database\TypeInterface> - */ - protected $_typeMap; - - /** - * An array containing the name of the fields and the Type objects - * each should use when converting them using batching. - * - * @var array - */ - protected $batchingTypeMap; - - /** - * An array containing all the types registered in the Type system - * at the moment this object is created. Used so that the types list - * is not fetched on each single row of the results. - * - * @var array<\Cake\Database\TypeInterface|\Cake\Database\Type\BatchCastingInterface> + * @var \Cake\Database\Driver */ - protected $types; + protected Driver $driver; /** - * The driver object to be used in the type conversion + * Maps type names to conversion settings. * - * @var \Cake\Database\DriverInterface + * @var array */ - protected $_driver; + protected array $conversions = []; /** * Builds the type map * * @param \Cake\Database\TypeMap $typeMap Contains the types to use for converting results - * @param \Cake\Database\DriverInterface $driver The driver to use for the type conversion + * @param \Cake\Database\Driver $driver The driver to use for the type conversion */ - public function __construct(TypeMap $typeMap, DriverInterface $driver) + public function __construct(TypeMap $typeMap, Driver $driver) { - $this->_driver = $driver; - $map = $typeMap->toArray(); - $types = TypeFactory::buildAll(); - - $simpleMap = $batchingMap = []; - $simpleResult = $batchingResult = []; - - foreach ($types as $k => $type) { - if ($type instanceof OptionalConvertInterface && !$type->requiresToPhpCast()) { - continue; - } - - if ($type instanceof BatchCastingInterface) { - $batchingMap[$k] = $type; - continue; - } - - $simpleMap[$k] = $type; - } - - foreach ($map as $field => $type) { - if (isset($simpleMap[$type])) { - $simpleResult[$field] = $simpleMap[$type]; - continue; - } - if (isset($batchingMap[$type])) { - $batchingResult[$type][] = $field; - } - } + $this->driver = $driver; - // Using batching when there is only a couple for the type is actually slower, - // so, let's check for that case here. - foreach ($batchingResult as $type => $fields) { - if (count($fields) > 2) { + $types = TypeFactory::buildAll(); + foreach ($typeMap->toArray() as $field => $typeName) { + $type = $types[$typeName] ?? null; + if (!$type || ($type instanceof OptionalConvertInterface && !$type->requiresToPhpCast())) { continue; } - foreach ($fields as $f) { - $simpleResult[$f] = $batchingMap[$type]; - } - unset($batchingResult[$type]); + $this->conversions[$typeName] ??= [ + 'type' => $type, + 'hasBatch' => $type instanceof BatchCastingInterface, + 'fields' => [], + ]; + $this->conversions[$typeName]['fields'][] = $field; } - - $this->types = $types; - $this->_typeMap = $simpleResult; - $this->batchingTypeMap = $batchingResult; } /** * Converts each of the fields in the array that are present in the type map * using the corresponding Type class. * - * @param array $row The array with the fields to be casted - * @return array + * @param mixed $row The array with the fields to be casted + * @return mixed */ - public function __invoke(array $row): array + public function __invoke(mixed $row): mixed { - if (!empty($this->_typeMap)) { - foreach ($this->_typeMap as $field => $type) { - $row[$field] = $type->toPHP($row[$field], $this->_driver); - } + if (!is_array($row)) { + return $row; } - if (!empty($this->batchingTypeMap)) { - foreach ($this->batchingTypeMap as $t => $fields) { - /** @psalm-suppress PossiblyUndefinedMethod */ - $row = $this->types[$t]->manyToPHP($row, $fields, $this->_driver); + foreach ($this->conversions as $conversion) { + /** @var \Cake\Database\TypeInterface $type */ + $type = $conversion['type']; + if ($conversion['hasBatch']) { + /** @var \Cake\Database\Type\BatchCastingInterface $type */ + $row = $type->manyToPHP($row, $conversion['fields'], $this->driver); + continue; + } + + foreach ($conversion['fields'] as $field) { + $row[$field] = $type->toPHP($row[$field], $this->driver); } } diff --git a/app/vendor/cakephp/cakephp/src/Database/FunctionsBuilder.php b/app/vendor/cakephp/cakephp/src/Database/FunctionsBuilder.php index 9e50ad63c..2afbc46b5 100644 --- a/app/vendor/cakephp/cakephp/src/Database/FunctionsBuilder.php +++ b/app/vendor/cakephp/cakephp/src/Database/FunctionsBuilder.php @@ -19,7 +19,6 @@ use Cake\Database\Expression\AggregateExpression; use Cake\Database\Expression\FunctionExpression; use InvalidArgumentException; -use function Cake\Core\deprecationWarning; /** * Contains methods related to generating FunctionExpression objects @@ -45,7 +44,7 @@ public function rand(): FunctionExpression * @param array $types list of types to bind to the arguments * @return \Cake\Database\Expression\AggregateExpression */ - public function sum($expression, $types = []): AggregateExpression + public function sum(ExpressionInterface|string $expression, array $types = []): AggregateExpression { $returnType = 'float'; if (current($types) === 'integer') { @@ -62,7 +61,7 @@ public function sum($expression, $types = []): AggregateExpression * @param array $types list of types to bind to the arguments * @return \Cake\Database\Expression\AggregateExpression */ - public function avg($expression, $types = []): AggregateExpression + public function avg(ExpressionInterface|string $expression, array $types = []): AggregateExpression { return $this->aggregate('AVG', $this->toLiteralParam($expression), $types, 'float'); } @@ -74,7 +73,7 @@ public function avg($expression, $types = []): AggregateExpression * @param array $types list of types to bind to the arguments * @return \Cake\Database\Expression\AggregateExpression */ - public function max($expression, $types = []): AggregateExpression + public function max(ExpressionInterface|string $expression, array $types = []): AggregateExpression { return $this->aggregate('MAX', $this->toLiteralParam($expression), $types, current($types) ?: 'float'); } @@ -86,7 +85,7 @@ public function max($expression, $types = []): AggregateExpression * @param array $types list of types to bind to the arguments * @return \Cake\Database\Expression\AggregateExpression */ - public function min($expression, $types = []): AggregateExpression + public function min(ExpressionInterface|string $expression, array $types = []): AggregateExpression { return $this->aggregate('MIN', $this->toLiteralParam($expression), $types, current($types) ?: 'float'); } @@ -98,7 +97,7 @@ public function min($expression, $types = []): AggregateExpression * @param array $types list of types to bind to the arguments * @return \Cake\Database\Expression\AggregateExpression */ - public function count($expression, $types = []): AggregateExpression + public function count(ExpressionInterface|string $expression, array $types = []): AggregateExpression { return $this->aggregate('COUNT', $this->toLiteralParam($expression), $types, 'integer'); } @@ -134,28 +133,14 @@ public function coalesce(array $args, array $types = []): FunctionExpression * is the default type name. Use `setReturnType()` to update it. * * @param \Cake\Database\ExpressionInterface|string $field Field or expression to cast. - * @param string $type The SQL data type + * @param string $dataType The SQL data type * @return \Cake\Database\Expression\FunctionExpression */ - public function cast($field, string $type = ''): FunctionExpression + public function cast(ExpressionInterface|string $field, string $dataType): FunctionExpression { - if (is_array($field)) { - deprecationWarning( - 'Build cast function by FunctionsBuilder::cast(array $args) is deprecated. ' . - 'Use FunctionsBuilder::cast($field, string $type) instead.' - ); - - return new FunctionExpression('CAST', $field); - } - - if (empty($type)) { - throw new InvalidArgumentException('The `$type` in a cast cannot be empty.'); - } - $expression = new FunctionExpression('CAST', $this->toLiteralParam($field)); - $expression->setConjunction(' AS')->add([$type => 'literal']); - return $expression; + return $expression->setConjunction(' AS')->add([$dataType => 'literal']); } /** @@ -179,8 +164,11 @@ public function dateDiff(array $args, array $types = []): FunctionExpression * @param array $types list of types to bind to the arguments * @return \Cake\Database\Expression\FunctionExpression */ - public function datePart(string $part, $expression, array $types = []): FunctionExpression - { + public function datePart( + string $part, + ExpressionInterface|string $expression, + array $types = [], + ): FunctionExpression { return $this->extract($part, $expression, $types); } @@ -192,12 +180,11 @@ public function datePart(string $part, $expression, array $types = []): Function * @param array $types list of types to bind to the arguments * @return \Cake\Database\Expression\FunctionExpression */ - public function extract(string $part, $expression, array $types = []): FunctionExpression + public function extract(string $part, ExpressionInterface|string $expression, array $types = []): FunctionExpression { $expression = new FunctionExpression('EXTRACT', $this->toLiteralParam($expression), $types, 'integer'); - $expression->setConjunction(' FROM')->add([$part => 'literal'], [], true); - return $expression; + return $expression->setConjunction(' FROM')->add([$part => 'literal'], [], true); } /** @@ -209,16 +196,19 @@ public function extract(string $part, $expression, array $types = []): FunctionE * @param array $types list of types to bind to the arguments * @return \Cake\Database\Expression\FunctionExpression */ - public function dateAdd($expression, $value, string $unit, array $types = []): FunctionExpression - { + public function dateAdd( + ExpressionInterface|string $expression, + string|int $value, + string $unit, + array $types = [], + ): FunctionExpression { if (!is_numeric($value)) { $value = 0; } $interval = $value . ' ' . $unit; $expression = new FunctionExpression('DATE_ADD', $this->toLiteralParam($expression), $types, 'datetime'); - $expression->setConjunction(', INTERVAL')->add([$interval => 'literal']); - return $expression; + return $expression->setConjunction(', INTERVAL')->add([$interval => 'literal']); } /** @@ -229,7 +219,7 @@ public function dateAdd($expression, $value, string $unit, array $types = []): F * @param array $types list of types to bind to the arguments * @return \Cake\Database\Expression\FunctionExpression */ - public function dayOfWeek($expression, $types = []): FunctionExpression + public function dayOfWeek(ExpressionInterface|string $expression, array $types = []): FunctionExpression { return new FunctionExpression('DAYOFWEEK', $this->toLiteralParam($expression), $types, 'integer'); } @@ -242,7 +232,7 @@ public function dayOfWeek($expression, $types = []): FunctionExpression * @param array $types list of types to bind to the arguments * @return \Cake\Database\Expression\FunctionExpression */ - public function weekday($expression, $types = []): FunctionExpression + public function weekday(ExpressionInterface|string $expression, array $types = []): FunctionExpression { return $this->dayOfWeek($expression, $types); } @@ -257,17 +247,12 @@ public function weekday($expression, $types = []): FunctionExpression */ public function now(string $type = 'datetime'): FunctionExpression { - if ($type === 'datetime') { - return new FunctionExpression('NOW', [], [], 'datetime'); - } - if ($type === 'date') { - return new FunctionExpression('CURRENT_DATE', [], [], 'date'); - } - if ($type === 'time') { - return new FunctionExpression('CURRENT_TIME', [], [], 'time'); - } - - throw new InvalidArgumentException('Invalid argument for FunctionsBuilder::now(): ' . $type); + return match ($type) { + 'datetime' => new FunctionExpression('NOW', [], [], 'datetime'), + 'date' => new FunctionExpression('CURRENT_DATE', [], [], 'date'), + 'time' => new FunctionExpression('CURRENT_TIME', [], [], 'time'), + default => throw new InvalidArgumentException('Invalid argument for FunctionsBuilder::now(): ' . $type), + }; } /** @@ -286,11 +271,15 @@ public function rowNumber(): AggregateExpression * @param \Cake\Database\ExpressionInterface|string $expression The value evaluated at offset * @param int $offset The row offset * @param mixed $default The default value if offset doesn't exist - * @param string $type The output type of the lag expression. Defaults to float. + * @param string|null $type The output type of the lag expression. Defaults to float. * @return \Cake\Database\Expression\AggregateExpression */ - public function lag($expression, int $offset, $default = null, $type = null): AggregateExpression - { + public function lag( + ExpressionInterface|string $expression, + int $offset, + mixed $default = null, + ?string $type = null, + ): AggregateExpression { $params = $this->toLiteralParam($expression) + [$offset => 'literal']; if ($default !== null) { $params[] = $default; @@ -310,11 +299,15 @@ public function lag($expression, int $offset, $default = null, $type = null): Ag * @param \Cake\Database\ExpressionInterface|string $expression The value evaluated at offset * @param int $offset The row offset * @param mixed $default The default value if offset doesn't exist - * @param string $type The output type of the lead expression. Defaults to float. + * @param string|null $type The output type of the lead expression. Defaults to float. * @return \Cake\Database\Expression\AggregateExpression */ - public function lead($expression, int $offset, $default = null, $type = null): AggregateExpression - { + public function lead( + ExpressionInterface|string $expression, + int $offset, + mixed $default = null, + ?string $type = null, + ): AggregateExpression { $params = $this->toLiteralParam($expression) + [$offset => 'literal']; if ($default !== null) { $params[] = $default; @@ -328,6 +321,24 @@ public function lead($expression, int $offset, $default = null, $type = null): A return (new AggregateExpression('LEAD', $params, $types, $type ?? 'float'))->over(); } + /** + * Returns a FunctionExpression representing the Json Value + * + * @param \Cake\Database\ExpressionInterface|string $expression The Json value or json field + * @param string $jsonPath A valid JSON PATH Query + * @param array $types list of types to bind to the arguments + * @return \Cake\Database\Expression\FunctionExpression + */ + public function jsonValue( + ExpressionInterface|string $expression, + string $jsonPath, + array $types = [], + ): FunctionExpression { + $params = $this->toLiteralParam($expression) + [$jsonPath]; + + return new FunctionExpression('JSON_VALUE', $params, $types); + } + /** * Helper method to create arbitrary SQL aggregate function calls. * @@ -340,8 +351,12 @@ public function lead($expression, int $offset, $default = null, $type = null): A * @param string $return Return type of the entire expression. Defaults to float. * @return \Cake\Database\Expression\AggregateExpression */ - public function aggregate(string $name, array $params = [], array $types = [], string $return = 'float') - { + public function aggregate( + string $name, + array $params = [], + array $types = [], + string $return = 'float', + ): AggregateExpression { return new AggregateExpression($name, $params, $types, $return); } @@ -365,7 +380,7 @@ public function __call(string $name, array $args): FunctionExpression * @param \Cake\Database\ExpressionInterface|string $expression function argument * @return array<\Cake\Database\ExpressionInterface|string> */ - protected function toLiteralParam($expression) + protected function toLiteralParam(ExpressionInterface|string $expression): array { if (is_string($expression)) { return [$expression => 'literal']; diff --git a/app/vendor/cakephp/cakephp/src/Database/IdentifierQuoter.php b/app/vendor/cakephp/cakephp/src/Database/IdentifierQuoter.php index b3a93e262..f6efb2517 100644 --- a/app/vendor/cakephp/cakephp/src/Database/IdentifierQuoter.php +++ b/app/vendor/cakephp/cakephp/src/Database/IdentifierQuoter.php @@ -16,9 +16,14 @@ */ namespace Cake\Database; +use Cake\Database\Exception\DatabaseException; use Cake\Database\Expression\FieldInterface; use Cake\Database\Expression\IdentifierExpression; use Cake\Database\Expression\OrderByExpression; +use Cake\Database\Query\DeleteQuery; +use Cake\Database\Query\InsertQuery; +use Cake\Database\Query\SelectQuery; +use Cake\Database\Query\UpdateQuery; /** * Contains all the logic related to quoting identifiers in a Query object @@ -28,20 +33,72 @@ class IdentifierQuoter { /** - * The driver instance used to do the identifier quoting + * Constructor * - * @var \Cake\Database\Driver + * @param string $startQuote String used to start a database identifier quoting to make it safe. + * @param string $endQuote String used to end a database identifier quoting to make it safe. */ - protected $_driver; + public function __construct( + protected string $startQuote, + protected string $endQuote, + ) { + } /** - * Constructor + * Quotes a database identifier (a column name, table name, etc..) to + * be used safely in queries without the risk of using reserved words * - * @param \Cake\Database\Driver $driver The driver instance used to do the identifier quoting + * @param string $identifier The identifier to quote. + * @return string */ - public function __construct(Driver $driver) + public function quoteIdentifier(string $identifier): string { - $this->_driver = $driver; + $identifier = trim($identifier); + + if ($identifier === '*' || $identifier === '') { + return $identifier; + } + + // string + if (preg_match('/^[\w-]+$/u', $identifier)) { + return $this->startQuote . $identifier . $this->endQuote; + } + + // string.string + if (preg_match('/^[\w-]+\.[^ \*]*$/u', $identifier)) { + $items = explode('.', $identifier); + + return $this->startQuote . implode($this->endQuote . '.' . $this->startQuote, $items) . $this->endQuote; + } + + // string.* + if (preg_match('/^[\w-]+\.\*$/u', $identifier)) { + return $this->startQuote . str_replace('.*', $this->endQuote . '.*', $identifier); + } + + // Functions + if (preg_match('/^([\w-]+)\((.*)\)$/', $identifier, $matches)) { + return $matches[1] . '(' . $this->quoteIdentifier($matches[2]) . ')'; + } + + // Alias.field AS thing + if (preg_match('/^([\w-]+(\.[\w\s-]+|\(.*\))*)\s+AS\s*([\w-]+)$/ui', $identifier, $matches)) { + return $this->quoteIdentifier($matches[1]) . ' AS ' . $this->quoteIdentifier($matches[3]); + } + + // string.string with spaces + if (preg_match('/^([\w-]+\.[\w][\w\s-]*[\w])(.*)/u', $identifier, $matches)) { + $items = explode('.', $matches[1]); + $field = implode($this->endQuote . '.' . $this->startQuote, $items); + + return $this->startQuote . $field . $this->endQuote . $matches[2]; + } + + if (preg_match('/^[\w\s-]*[\w-]+/u', $identifier)) { + return $this->startQuote . $identifier . $this->endQuote; + } + + return $identifier; } /** @@ -56,18 +113,21 @@ public function quote(Query $query): Query $binder = $query->getValueBinder(); $query->setValueBinder(null); - if ($query->type() === 'insert') { - $this->_quoteInsert($query); - } elseif ($query->type() === 'update') { - $this->_quoteUpdate($query); - } else { - $this->_quoteParts($query); - } - - $query->traverseExpressions([$this, 'quoteExpression']); - $query->setValueBinder($binder); - - return $query; + match (true) { + $query instanceof InsertQuery => $this->_quoteInsert($query), + $query instanceof SelectQuery => $this->_quoteSelect($query), + $query instanceof UpdateQuery => $this->_quoteUpdate($query), + $query instanceof DeleteQuery => $this->_quoteDelete($query), + default => + throw new DatabaseException(sprintf( + 'Instance of SelectQuery, UpdateQuery, InsertQuery, DeleteQuery expected. Found `%s` instead.', + get_debug_type($query), + )) + }; + + $query->traverseExpressions($this->quoteExpression(...)); + + return $query->setValueBinder($binder); } /** @@ -78,34 +138,24 @@ public function quote(Query $query): Query */ public function quoteExpression(ExpressionInterface $expression): void { - if ($expression instanceof FieldInterface) { - $this->_quoteComparison($expression); - - return; - } - - if ($expression instanceof OrderByExpression) { - $this->_quoteOrderBy($expression); - - return; - } - - if ($expression instanceof IdentifierExpression) { - $this->_quoteIdentifierExpression($expression); - - return; - } + match (true) { + $expression instanceof FieldInterface => $this->_quoteComparison($expression), + $expression instanceof OrderByExpression => $this->_quoteOrderBy($expression), + $expression instanceof IdentifierExpression => $this->_quoteIdentifierExpression($expression), + default => null // Nothing to do if there is no match + }; } /** - * Quotes all identifiers in each of the clauses of a query + * Quotes all identifiers in each of the clauses/parts of a query * * @param \Cake\Database\Query $query The query to quote. + * @param array $parts Query clauses. * @return void */ - protected function _quoteParts(Query $query): void + protected function _quoteParts(Query $query, array $parts): void { - foreach (['distinct', 'select', 'from', 'group'] as $part) { + foreach ($parts as $part) { $contents = $query->clause($part); if (!is_array($contents)) { @@ -113,16 +163,16 @@ protected function _quoteParts(Query $query): void } $result = $this->_basicQuoter($contents); - if (!empty($result)) { + if ($result) { + $part = match ($part) { + 'group' => 'groupBy', + 'order' => 'orderBy', + default => $part, + }; + $query->{$part}($result, true); } } - - $joins = $query->clause('join'); - if ($joins) { - $joins = $this->_quoteJoins($joins); - $query->join($joins, [], true); - } } /** @@ -135,8 +185,8 @@ protected function _basicQuoter(array $part): array { $result = []; foreach ($part as $alias => $value) { - $value = !is_string($value) ? $value : $this->_driver->quoteIdentifier($value); - $alias = is_numeric($alias) ? $alias : $this->_driver->quoteIdentifier($alias); + $value = !is_string($value) ? $value : $this->quoteIdentifier($value); + $alias = is_numeric($alias) ? $alias : $this->quoteIdentifier($alias); $result[$alias] = $value; } @@ -156,12 +206,12 @@ protected function _quoteJoins(array $joins): array foreach ($joins as $value) { $alias = ''; if (!empty($value['alias'])) { - $alias = $this->_driver->quoteIdentifier($value['alias']); + $alias = $this->quoteIdentifier($value['alias']); $value['alias'] = $alias; } if (is_string($value['table'])) { - $value['table'] = $this->_driver->quoteIdentifier($value['table']); + $value['table'] = $this->quoteIdentifier($value['table']); } $result[$alias] = $value; @@ -170,23 +220,57 @@ protected function _quoteJoins(array $joins): array return $result; } + /** + * Quotes all identifiers in each of the clauses of a SELECT query + * + * @param \Cake\Database\Query\SelectQuery $query The query to quote. + * @return void + */ + protected function _quoteSelect(SelectQuery $query): void + { + $this->_quoteParts($query, ['select', 'distinct', 'from', 'group']); + + $joins = $query->clause('join'); + if ($joins) { + $joins = $this->_quoteJoins($joins); + $query->join($joins, [], true); + } + } + + /** + * Quotes all identifiers in each of the clauses of a DELETE query + * + * @param \Cake\Database\Query\DeleteQuery $query The query to quote. + * @return void + */ + protected function _quoteDelete(DeleteQuery $query): void + { + $this->_quoteParts($query, ['from']); + + $joins = $query->clause('join'); + if ($joins) { + $joins = $this->_quoteJoins($joins); + $query->join($joins, [], true); + } + } + /** * Quotes the table name and columns for an insert query * - * @param \Cake\Database\Query $query The insert query to quote. + * @param \Cake\Database\Query\InsertQuery $query The insert query to quote. * @return void */ - protected function _quoteInsert(Query $query): void + protected function _quoteInsert(InsertQuery $query): void { $insert = $query->clause('insert'); if (!isset($insert[0]) || !isset($insert[1])) { return; } [$table, $columns] = $insert; - $table = $this->_driver->quoteIdentifier($table); + $table = $this->quoteIdentifier($table); foreach ($columns as &$column) { if (is_scalar($column)) { - $column = $this->_driver->quoteIdentifier((string)$column); + $column = $this->quoteIdentifier((string)$column); } } $query->insert($columns)->into($table); @@ -195,15 +279,15 @@ protected function _quoteInsert(Query $query): void /** * Quotes the table name for an update query * - * @param \Cake\Database\Query $query The update query to quote. + * @param \Cake\Database\Query\UpdateQuery $query The update query to quote. * @return void */ - protected function _quoteUpdate(Query $query): void + protected function _quoteUpdate(UpdateQuery $query): void { $table = $query->clause('update')[0]; if (is_string($table)) { - $query->update($this->_driver->quoteIdentifier($table)); + $query->update($this->quoteIdentifier($table)); } } @@ -217,14 +301,14 @@ protected function _quoteComparison(FieldInterface $expression): void { $field = $expression->getField(); if (is_string($field)) { - $expression->setField($this->_driver->quoteIdentifier($field)); + $expression->setField($this->quoteIdentifier($field)); } elseif (is_array($field)) { $quoted = []; foreach ($field as $f) { - $quoted[] = $this->_driver->quoteIdentifier($f); + $quoted[] = $this->quoteIdentifier($f); } $expression->setField($quoted); - } elseif ($field instanceof ExpressionInterface) { + } else { $this->quoteExpression($field); } } @@ -242,12 +326,12 @@ protected function _quoteOrderBy(OrderByExpression $expression): void { $expression->iterateParts(function ($part, &$field) { if (is_string($field)) { - $field = $this->_driver->quoteIdentifier($field); + $field = $this->quoteIdentifier($field); return $part; } - if (is_string($part) && strpos($part, ' ') === false) { - return $this->_driver->quoteIdentifier($part); + if (is_string($part) && !str_contains($part, ' ')) { + return $this->quoteIdentifier($part); } return $part; @@ -263,7 +347,7 @@ protected function _quoteOrderBy(OrderByExpression $expression): void protected function _quoteIdentifierExpression(IdentifierExpression $expression): void { $expression->setIdentifier( - $this->_driver->quoteIdentifier($expression->getIdentifier()) + $this->quoteIdentifier($expression->getIdentifier()), ); } } diff --git a/app/vendor/cakephp/cakephp/src/Database/Log/LoggedQuery.php b/app/vendor/cakephp/cakephp/src/Database/Log/LoggedQuery.php index f029bc935..4617136df 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Log/LoggedQuery.php +++ b/app/vendor/cakephp/cakephp/src/Database/Log/LoggedQuery.php @@ -16,8 +16,11 @@ */ namespace Cake\Database\Log; +use Cake\Database\Driver; use Cake\Database\Driver\Sqlserver; +use Exception; use JsonSerializable; +use Stringable; /** * Contains a query string, the params used to executed it, time taken to do it @@ -25,49 +28,49 @@ * * @internal */ -class LoggedQuery implements JsonSerializable +class LoggedQuery implements JsonSerializable, Stringable { /** * Driver executing the query * - * @var \Cake\Database\DriverInterface|null + * @var \Cake\Database\Driver|null */ - public $driver = null; + protected ?Driver $driver = null; /** * Query string that was executed * * @var string */ - public $query = ''; + protected string $query = ''; /** * Number of milliseconds this query took to complete * * @var float */ - public $took = 0; + protected float $took = 0; /** * Associative array with the params bound to the query string * * @var array */ - public $params = []; + protected array $params = []; /** * Number of rows affected or returned by the query execution * * @var int */ - public $numRows = 0; + protected int $numRows = 0; /** * The exception that was thrown by the execution of this query * * @var \Exception|null */ - public $error; + protected ?Exception $error = null; /** * Helper function used to replace query placeholders by the real @@ -105,7 +108,7 @@ protected function interpolate(): string $p = strtr($p, $replacements); - return "'$p'"; + return "'{$p}'"; } return $p; @@ -114,10 +117,10 @@ protected function interpolate(): string $keys = []; $limit = is_int(key($params)) ? 1 : -1; foreach ($params as $key => $param) { - $keys[] = is_string($key) ? "/:$key\b/" : '/[?]/'; + $keys[] = is_string($key) ? "/:{$key}\b/" : '/[?]/'; } - return preg_replace($keys, $params, $this->query, $limit); + return (string)preg_replace($keys, $params, $this->query, $limit); } /** @@ -128,12 +131,28 @@ protected function interpolate(): string public function getContext(): array { return [ + 'query' => $this->query, 'numRows' => $this->numRows, 'took' => $this->took, 'role' => $this->driver ? $this->driver->getRole() : '', ]; } + /** + * Set logging context for this query. + * + * @param array $context Context data. + * @return void + */ + public function setContext(array $context): void + { + foreach ($context as $key => $val) { + if (property_exists($this, $key)) { + $this->{$key} = $val; + } + } + } + /** * Returns data that will be serialized as JSON * @@ -144,7 +163,7 @@ public function jsonSerialize(): array $error = $this->error; if ($error !== null) { $error = [ - 'class' => get_class($error), + 'class' => $error::class, 'message' => $error->getMessage(), 'code' => $error->getCode(), ]; @@ -166,11 +185,10 @@ public function jsonSerialize(): array */ public function __toString(): string { - $sql = $this->query; - if (!empty($this->params)) { - $sql = $this->interpolate(); + if ($this->params) { + return $this->interpolate(); } - return $sql; + return $this->query; } } diff --git a/app/vendor/cakephp/cakephp/src/Database/Log/LoggingStatement.php b/app/vendor/cakephp/cakephp/src/Database/Log/LoggingStatement.php deleted file mode 100644 index e83f6f60e..000000000 --- a/app/vendor/cakephp/cakephp/src/Database/Log/LoggingStatement.php +++ /dev/null @@ -1,219 +0,0 @@ - - */ - protected $_compiledParams = []; - - /** - * Query execution start time. - * - * @var float - */ - protected $startTime = 0.0; - - /** - * Logged query - * - * @var \Cake\Database\Log\LoggedQuery|null - */ - protected $loggedQuery; - - /** - * Wrapper for the execute function to calculate time spent - * and log the query afterwards. - * - * @param array|null $params List of values to be bound to query - * @return bool True on success, false otherwise - * @throws \Exception Re-throws any exception raised during query execution. - */ - public function execute(?array $params = null): bool - { - $this->startTime = microtime(true); - - $this->loggedQuery = new LoggedQuery(); - $this->loggedQuery->driver = $this->_driver; - $this->loggedQuery->params = $params ?: $this->_compiledParams; - - try { - $result = parent::execute($params); - $this->loggedQuery->took = (int)round((microtime(true) - $this->startTime) * 1000, 0); - } catch (Exception $e) { - $this->loggedQuery->error = $e; - $this->_log(); - - if (Configure::read('Error.convertStatementToDatabaseException', false) === true) { - $code = $e->getCode(); - if (!is_int($code)) { - $code = null; - } - - throw new DatabaseException([ - 'message' => $e->getMessage(), - 'queryString' => $this->queryString, - ], $code, $e); - } - - if (version_compare(PHP_VERSION, '8.2.0', '<')) { - deprecationWarning( - '4.4.12 - Having queryString set on exceptions is deprecated.' . - 'If you are not using this attribute there is no action to take.' . - 'Otherwise, enable Error.convertStatementToDatabaseException.' - ); - /** @psalm-suppress UndefinedPropertyAssignment */ - $e->queryString = $this->queryString; - } - - throw $e; - } - - if (preg_match('/^(?!SELECT)/i', $this->queryString)) { - $this->rowCount(); - } - - return $result; - } - - /** - * @inheritDoc - */ - public function fetch($type = self::FETCH_TYPE_NUM) - { - $record = parent::fetch($type); - - if ($this->loggedQuery) { - $this->rowCount(); - } - - return $record; - } - - /** - * @inheritDoc - */ - public function fetchAll($type = self::FETCH_TYPE_NUM) - { - $results = parent::fetchAll($type); - - if ($this->loggedQuery) { - $this->rowCount(); - } - - return $results; - } - - /** - * @inheritDoc - */ - public function rowCount(): int - { - $result = parent::rowCount(); - - if ($this->loggedQuery) { - $this->loggedQuery->numRows = $result; - $this->_log(); - } - - return $result; - } - - /** - * Copies the logging data to the passed LoggedQuery and sends it - * to the logging system. - * - * @return void - */ - protected function _log(): void - { - if ($this->loggedQuery === null) { - return; - } - - $this->loggedQuery->query = $this->queryString; - $this->getLogger()->debug((string)$this->loggedQuery, ['query' => $this->loggedQuery]); - - $this->loggedQuery = null; - } - - /** - * Wrapper for bindValue function to gather each parameter to be later used - * in the logger function. - * - * @param string|int $column Name or param position to be bound - * @param mixed $value The value to bind to variable in query - * @param string|int|null $type PDO type or name of configured Type class - * @return void - */ - public function bindValue($column, $value, $type = 'string'): void - { - parent::bindValue($column, $value, $type); - - if ($type === null) { - $type = 'string'; - } - if (!ctype_digit($type)) { - $value = $this->cast($value, $type)[0]; - } - $this->_compiledParams[$column] = $value; - } - - /** - * Sets a logger - * - * @param \Psr\Log\LoggerInterface $logger Logger object - * @return void - */ - public function setLogger(LoggerInterface $logger): void - { - $this->_logger = $logger; - } - - /** - * Gets the logger object - * - * @return \Psr\Log\LoggerInterface logger instance - */ - public function getLogger(): LoggerInterface - { - return $this->_logger; - } -} diff --git a/app/vendor/cakephp/cakephp/src/Database/Log/QueryLogger.php b/app/vendor/cakephp/cakephp/src/Database/Log/QueryLogger.php index e2faadce2..4f0974427 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Log/QueryLogger.php +++ b/app/vendor/cakephp/cakephp/src/Database/Log/QueryLogger.php @@ -18,6 +18,7 @@ use Cake\Log\Engine\BaseLog; use Cake\Log\Log; +use Stringable; /** * This class is a bridge used to write LoggedQuery objects into a real log. @@ -34,7 +35,7 @@ class QueryLogger extends BaseLog */ public function __construct(array $config = []) { - $this->_defaultConfig['scopes'] = ['queriesLog']; + $this->_defaultConfig['scopes'] = ['queriesLog', 'cake.database.queries']; $this->_defaultConfig['connection'] = ''; parent::__construct($config); @@ -43,10 +44,13 @@ public function __construct(array $config = []) /** * @inheritDoc */ - public function log($level, $message, array $context = []) + public function log($level, string|Stringable $message, array $context = []): void { - $context['scope'] = $this->scopes() ?: ['queriesLog']; - $context['connection'] = $this->getConfig('connection'); + $context += [ + 'scope' => $this->scopes() ?: ['queriesLog', 'cake.database.queries'], + 'connection' => $this->getConfig('connection'), + 'query' => null, + ]; if ($context['query'] instanceof LoggedQuery) { $context = $context['query']->getContext() + $context; diff --git a/app/vendor/cakephp/cakephp/src/Database/PostgresCompiler.php b/app/vendor/cakephp/cakephp/src/Database/PostgresCompiler.php index 7beae8b58..e973d0a31 100644 --- a/app/vendor/cakephp/cakephp/src/Database/PostgresCompiler.php +++ b/app/vendor/cakephp/cakephp/src/Database/PostgresCompiler.php @@ -33,12 +33,14 @@ class PostgresCompiler extends QueryCompiler * * @var bool */ - protected $_quotedSelectAliases = true; + protected bool $_quotedSelectAliases = true; /** - * @inheritDoc + * {@inheritDoc} + * + * @var array */ - protected $_templates = [ + protected array $_templates = [ 'delete' => 'DELETE', 'where' => ' WHERE %s', 'group' => ' GROUP BY %s', @@ -46,6 +48,7 @@ class PostgresCompiler extends QueryCompiler 'limit' => ' LIMIT %s', 'offset' => ' OFFSET %s', 'epilog' => ' %s', + 'comment' => '/* %s */ ', ]; /** @@ -58,7 +61,7 @@ class PostgresCompiler extends QueryCompiler * @param \Cake\Database\ValueBinder $binder Value binder used to generate parameter placeholder * @return string */ - protected function _buildHavingPart($parts, $query, $binder) + protected function _buildHavingPart(array $parts, Query $query, ValueBinder $binder): string { $selectParts = $query->clause('select'); @@ -73,7 +76,7 @@ protected function _buildHavingPart($parts, $query, $binder) preg_match_all( '/\b' . trim($selectKey, '"') . '\b/i', $p, - $matches + $matches, ); if (empty($matches[0])) { @@ -83,7 +86,7 @@ protected function _buildHavingPart($parts, $query, $binder) $parts[$k] = preg_replace( ['/"/', '/\b' . trim($selectKey, '"') . '\b/i'], ['', $selectPart->sql($binder)], - $p + $p, ); } } diff --git a/app/vendor/cakephp/cakephp/src/Database/Query.php b/app/vendor/cakephp/cakephp/src/Database/Query.php index 594b8a70f..d63904974 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Query.php +++ b/app/vendor/cakephp/cakephp/src/Database/Query.php @@ -16,19 +16,15 @@ */ namespace Cake\Database; -use Cake\Database\Exception\DatabaseException; +use Cake\Core\Exception\CakeException; use Cake\Database\Expression\CommonTableExpression; use Cake\Database\Expression\IdentifierExpression; use Cake\Database\Expression\OrderByExpression; use Cake\Database\Expression\OrderClauseExpression; use Cake\Database\Expression\QueryExpression; -use Cake\Database\Expression\ValuesExpression; -use Cake\Database\Expression\WindowExpression; -use Cake\Database\Statement\CallbackStatement; use Closure; use InvalidArgumentException; -use IteratorAggregate; -use RuntimeException; +use Stringable; use Throwable; use function Cake\Core\deprecationWarning; @@ -38,7 +34,7 @@ * for dynamically constructing each query part, execute it and transform it * to a specific SQL dialect. */ -class Query implements ExpressionInterface, IteratorAggregate +abstract class Query implements ExpressionInterface, Stringable { use TypeMapTrait; @@ -57,33 +53,54 @@ class Query implements ExpressionInterface, IteratorAggregate */ public const JOIN_TYPE_RIGHT = 'RIGHT'; + /** + * @var string + */ + public const TYPE_SELECT = 'select'; + + /** + * @var string + */ + public const TYPE_INSERT = 'insert'; + + /** + * @var string + */ + public const TYPE_UPDATE = 'update'; + + /** + * @var string + */ + public const TYPE_DELETE = 'delete'; + /** * Connection instance to be used to execute this query. * * @var \Cake\Database\Connection */ - protected $_connection; + protected Connection $_connection; /** * Connection role ('read' or 'write') * * @var string */ - protected $connectionRole = Connection::ROLE_WRITE; + protected string $connectionRole = Connection::ROLE_WRITE; /** * Type of this query (select, insert, update, delete). * * @var string */ - protected $_type = 'select'; + protected string $_type; /** * List of SQL parts that will be used to build this query. * * @var array */ - protected $_parts = [ + protected array $_parts = [ + 'comment' => null, 'delete' => true, 'update' => [], 'set' => [], @@ -104,43 +121,9 @@ class Query implements ExpressionInterface, IteratorAggregate 'offset' => null, 'union' => [], 'epilog' => null, + 'intersect' => [], ]; - /** - * The list of query clauses to traverse for generating a SELECT statement - * - * @var array - * @deprecated 4.4.3 This property is unused. - */ - protected $_selectParts = [ - 'with', 'select', 'from', 'join', 'where', 'group', 'having', 'order', 'limit', - 'offset', 'union', 'epilog', - ]; - - /** - * The list of query clauses to traverse for generating an UPDATE statement - * - * @var array - * @deprecated 4.4.3 This property is unused. - */ - protected $_updateParts = ['with', 'update', 'set', 'where', 'epilog']; - - /** - * The list of query clauses to traverse for generating a DELETE statement - * - * @var array - * @deprecated 4.4.3 This property is unused. - */ - protected $_deleteParts = ['with', 'delete', 'modifier', 'from', 'where', 'epilog']; - - /** - * The list of query clauses to traverse for generating an INSERT statement - * - * @var array - * @deprecated 4.4.3 This property is unused. - */ - protected $_insertParts = ['with', 'insert', 'values', 'epilog']; - /** * Indicates whether internal state of this query was changed, this is used to * discard internal cached objects such as the transformed query or the reference @@ -148,23 +131,12 @@ class Query implements ExpressionInterface, IteratorAggregate * * @var bool */ - protected $_dirty = false; - - /** - * A list of callback functions to be called to alter each row from resulting - * statement upon retrieval. Each one of the callback function will receive - * the row array as first argument. - * - * @var array - */ - protected $_resultDecorators = []; + protected bool $_dirty = false; /** - * Statement object resulting from executing this query. - * * @var \Cake\Database\StatementInterface|null */ - protected $_iterator; + protected ?StatementInterface $_statement = null; /** * The object responsible for generating query placeholders and temporarily store values @@ -172,37 +144,14 @@ class Query implements ExpressionInterface, IteratorAggregate * * @var \Cake\Database\ValueBinder|null */ - protected $_valueBinder; + protected ?ValueBinder $_valueBinder = null; /** * Instance of functions builder object used for generating arbitrary SQL functions. * * @var \Cake\Database\FunctionsBuilder|null */ - protected $_functionsBuilder; - - /** - * Boolean for tracking whether buffered results - * are enabled. - * - * @var bool - * @deprecated 4.5.0 Results will always be buffered in 5.0. - */ - protected $_useBufferedResults = true; - - /** - * The Type map for fields in the select clause - * - * @var \Cake\Database\TypeMap|null - */ - protected $_selectTypeMap; - - /** - * Tracking flag to disable casting - * - * @var bool - */ - protected $typeCastEnabled = true; + protected ?FunctionsBuilder $_functionsBuilder = null; /** * Constructor. @@ -271,11 +220,11 @@ public function getConnectionRole(): string */ public function execute(): StatementInterface { - $statement = $this->_connection->run($this); - $this->_iterator = $this->_decorateStatement($statement); + $this->_statement = null; + $this->_statement = $this->_connection->run($this); $this->_dirty = false; - return $this->_iterator; + return $this->_statement; } /** @@ -321,6 +270,9 @@ public function rowCountAndClose(): int * values when the query is executed, hence it is most suitable to use with * prepared statements. * + * To get the fully rendered query with the placeholders replaced with the actual + * values, `(string)$query` should be used, instead. + * * @param \Cake\Database\ValueBinder|null $binder Value binder that generates parameter placeholders * @return string */ @@ -330,9 +282,8 @@ public function sql(?ValueBinder $binder = null): string $binder = $this->getValueBinder(); $binder->resetCount(); } - $connection = $this->getConnection(); - return $connection->getDriver($this->getConnectionRole())->compileQuery($this, $binder)[1]; + return $this->getConnection()->getDriver()->compileQuery($this, $binder); } /** @@ -353,10 +304,10 @@ public function sql(?ValueBinder $binder = null): string * }); * ``` * - * @param callable $callback A function or callable to be executed for each part + * @param \Closure $callback Callback to be executed for each part * @return $this */ - public function traverse($callback) + public function traverse(Closure $callback) { foreach ($this->_parts as $name => $part) { $callback($part, $name); @@ -385,11 +336,11 @@ public function traverse($callback) * }, ['select', 'from']); * ``` * - * @param callable $visitor A function or callable to be executed for each part + * @param \Closure $visitor Callback executed for each part * @param array $parts The list of query parts to traverse * @return $this */ - public function traverseParts(callable $visitor, array $parts) + public function traverseParts(Closure $visitor, array $parts) { foreach ($parts as $name) { $visitor($this->_parts[$name], $name); @@ -410,8 +361,7 @@ public function traverseParts(callable $visitor, array $parts) * $cte = new \Cake\Database\Expression\CommonTableExpression( * 'cte', * $connection - * ->newQuery() - * ->select('*') + * ->selectQuery('*') * ->from('articles') * ); * @@ -419,7 +369,7 @@ public function traverseParts(callable $visitor, array $parts) * ``` * * or returned from a closure, which will receive a new common table expression - * object as the first argument, and a new blank query object as + * object as the first argument, and a new blank select query object as * the second argument: * * ``` @@ -441,7 +391,7 @@ public function traverseParts(callable $visitor, array $parts) * @param bool $overwrite Whether to reset the list of CTEs. * @return $this */ - public function with($cte, bool $overwrite = false) + public function with(CommonTableExpression|Closure $cte, bool $overwrite = false) { if ($overwrite) { $this->_parts['with'] = []; @@ -451,8 +401,8 @@ public function with($cte, bool $overwrite = false) $query = $this->getConnection()->selectQuery(); $cte = $cte(new CommonTableExpression(), $query); if (!($cte instanceof CommonTableExpression)) { - throw new RuntimeException( - 'You must return a `CommonTableExpression` from a Closure passed to `with()`.' + throw new CakeException( + 'You must return a `CommonTableExpression` from a Closure passed to `with()`.', ); } } @@ -463,113 +413,6 @@ public function with($cte, bool $overwrite = false) return $this; } - /** - * Adds new fields to be returned by a `SELECT` statement when this query is - * executed. Fields can be passed as an array of strings, array of expression - * objects, a single expression or a single string. - * - * If an array is passed, keys will be used to alias fields using the value as the - * real field to be aliased. It is possible to alias strings, Expression objects or - * even other Query objects. - * - * If a callable function is passed, the returning array of the function will - * be used as the list of fields. - * - * By default this function will append any passed argument to the list of fields - * to be selected, unless the second argument is set to true. - * - * ### Examples: - * - * ``` - * $query->select(['id', 'title']); // Produces SELECT id, title - * $query->select(['author' => 'author_id']); // Appends author: SELECT id, title, author_id as author - * $query->select('id', true); // Resets the list: SELECT id - * $query->select(['total' => $countQuery]); // SELECT id, (SELECT ...) AS total - * $query->select(function ($query) { - * return ['article_id', 'total' => $query->count('*')]; - * }) - * ``` - * - * By default no fields are selected, if you have an instance of `Cake\ORM\Query` and try to append - * fields you should also call `Cake\ORM\Query::enableAutoFields()` to select the default fields - * from the table. - * - * @param \Cake\Database\ExpressionInterface|callable|array|string $fields fields to be added to the list. - * @param bool $overwrite whether to reset fields with passed list or not - * @return $this - */ - public function select($fields = [], bool $overwrite = false) - { - if (!is_string($fields) && is_callable($fields)) { - $fields = $fields($this); - } - - if (!is_array($fields)) { - $fields = [$fields]; - } - - if ($overwrite) { - $this->_parts['select'] = $fields; - } else { - $this->_parts['select'] = array_merge($this->_parts['select'], $fields); - } - - $this->_dirty(); - $this->_type = 'select'; - - return $this; - } - - /** - * Adds a `DISTINCT` clause to the query to remove duplicates from the result set. - * This clause can only be used for select statements. - * - * If you wish to filter duplicates based of those rows sharing a particular field - * or set of fields, you may pass an array of fields to filter on. Beware that - * this option might not be fully supported in all database systems. - * - * ### Examples: - * - * ``` - * // Filters products with the same name and city - * $query->select(['name', 'city'])->from('products')->distinct(); - * - * // Filters products in the same city - * $query->distinct(['city']); - * $query->distinct('city'); - * - * // Filter products with the same name - * $query->distinct(['name'], true); - * $query->distinct('name', true); - * ``` - * - * @param \Cake\Database\ExpressionInterface|array|string|bool $on Enable/disable distinct class - * or list of fields to be filtered on - * @param bool $overwrite whether to reset fields with passed list or not - * @return $this - */ - public function distinct($on = [], $overwrite = false) - { - if ($on === []) { - $on = true; - } elseif (is_string($on)) { - $on = [$on]; - } - - if (is_array($on)) { - $merge = []; - if (is_array($this->_parts['distinct'])) { - $merge = $this->_parts['distinct']; - } - $on = $overwrite ? array_values($on) : array_merge($merge, array_values($on)); - } - - $this->_parts['distinct'] = $on; - $this->_dirty(); - - return $this; - } - /** * Adds a single or multiple `SELECT` modifiers to be used in the `SELECT`. * @@ -592,7 +435,7 @@ public function distinct($on = [], $overwrite = false) * @param bool $overwrite whether to reset order with field list or not * @return $this */ - public function modifier($modifiers, $overwrite = false) + public function modifier(ExpressionInterface|array|string $modifiers, bool $overwrite = false) { $this->_dirty(); if ($overwrite) { @@ -635,7 +478,7 @@ public function modifier($modifiers, $overwrite = false) * @param bool $overwrite whether to reset tables with passed list or not * @return $this */ - public function from($tables = [], $overwrite = false) + public function from(array|string $tables = [], bool $overwrite = false) { $tables = (array)$tables; @@ -730,13 +573,13 @@ public function from($tables = [], $overwrite = false) * $query->join(['something' => 'different_table'], [], true); // resets joins list * ``` * - * @param array|string $tables list of tables to be joined in the query - * @param array $types Associative array of type names used to bind values to query - * @param bool $overwrite whether to reset joins with passed list or not + * @param array|string $tables List of tables to be joined in the query. + * @param array $types Associative array of type names used to bind values to query. + * @param bool $overwrite Whether to reset joins with passed list or not. * @see \Cake\Database\TypeFactory * @return $this */ - public function join($tables, $types = [], $overwrite = false) + public function join(array|string $tables, array $types = [], bool $overwrite = false) { if (is_string($tables) || isset($tables['table'])) { $tables = [$tables]; @@ -749,7 +592,7 @@ public function join($tables, $types = [], $overwrite = false) $t = ['table' => $t, 'conditions' => $this->newExpr()]; } - if (!is_string($t['conditions']) && is_callable($t['conditions'])) { + if ($t['conditions'] instanceof Closure) { $t['conditions'] = $t['conditions']($this->newExpr(), $this); } @@ -818,15 +661,18 @@ public function removeJoin(string $name) * * See `join()` for further details on conditions and types. * - * @param array|string $table The table to join with - * @param \Cake\Database\ExpressionInterface|array|string $conditions The conditions + * @param array|string $table The table to join with + * @param \Cake\Database\ExpressionInterface|\Closure|array|string $conditions The conditions * to use for joining. * @param array $types a list of types associated to the conditions used for converting * values to the corresponding database representation. * @return $this */ - public function leftJoin($table, $conditions = [], $types = []) - { + public function leftJoin( + array|string $table, + ExpressionInterface|Closure|array|string $conditions = [], + array $types = [], + ) { $this->join($this->_makeJoin($table, $conditions, static::JOIN_TYPE_LEFT), $types); return $this; @@ -840,15 +686,18 @@ public function leftJoin($table, $conditions = [], $types = []) * The arguments of this method are identical to the `leftJoin()` shorthand, please refer * to that methods description for further details. * - * @param array|string $table The table to join with - * @param \Cake\Database\ExpressionInterface|array|string $conditions The conditions + * @param array|string $table The table to join with + * @param \Cake\Database\ExpressionInterface|\Closure|array|string $conditions The conditions * to use for joining. * @param array $types a list of types associated to the conditions used for converting * values to the corresponding database representation. * @return $this */ - public function rightJoin($table, $conditions = [], $types = []) - { + public function rightJoin( + array|string $table, + ExpressionInterface|Closure|array|string $conditions = [], + array $types = [], + ) { $this->join($this->_makeJoin($table, $conditions, static::JOIN_TYPE_RIGHT), $types); return $this; @@ -862,15 +711,18 @@ public function rightJoin($table, $conditions = [], $types = []) * The arguments of this method are identical to the `leftJoin()` shorthand, please refer * to that method's description for further details. * - * @param array|string $table The table to join with - * @param \Cake\Database\ExpressionInterface|array|string $conditions The conditions + * @param array|string $table The table to join with + * @param \Cake\Database\ExpressionInterface|\Closure|array|string $conditions The conditions * to use for joining. * @param array $types a list of types associated to the conditions used for converting * values to the corresponding database representation. * @return $this */ - public function innerJoin($table, $conditions = [], $types = []) - { + public function innerJoin( + array|string $table, + ExpressionInterface|Closure|array|string $conditions = [], + array $types = [], + ) { $this->join($this->_makeJoin($table, $conditions, static::JOIN_TYPE_INNER), $types); return $this; @@ -879,24 +731,25 @@ public function innerJoin($table, $conditions = [], $types = []) /** * Returns an array that can be passed to the join method describing a single join clause * - * @param array|string $table The table to join with - * @param \Cake\Database\ExpressionInterface|array|string $conditions The conditions + * @param array|string $table The table to join with + * @param \Cake\Database\ExpressionInterface|\Closure|array|string $conditions The conditions * to use for joining. * @param string $type the join type to use - * @return array - */ - protected function _makeJoin($table, $conditions, $type): array - { - $alias = $table; - - if (is_array($table)) { + * @return array + */ + protected function _makeJoin( + array|string $table, + ExpressionInterface|Closure|array|string $conditions, + string $type, + ): array { + if (is_string($table)) { + $alias = $table; + } else { + /** @var string $alias */ $alias = key($table); - $table = current($table); + $table = $table[$alias]; } - /** - * @psalm-suppress InvalidArrayOffset - */ return [ $alias => [ 'table' => $table, @@ -982,7 +835,7 @@ protected function _makeJoin($table, $conditions, $type): array * * ### Adding conditions in multiple steps: * - * You can use callable functions to construct complex expressions, functions + * You can use callbacks to construct complex expressions, functions * receive as first argument a new QueryExpression object and this query instance * as second argument. Functions must return an expression object, that will be * added the list of conditions for the query using the `AND` operator. @@ -1039,8 +892,11 @@ protected function _makeJoin($table, $conditions, $type): array * @see \Cake\Database\Expression\QueryExpression * @return $this */ - public function where($conditions = null, array $types = [], bool $overwrite = false) - { + public function where( + ExpressionInterface|Closure|array|string|null $conditions = null, + array $types = [], + bool $overwrite = false, + ) { if ($overwrite) { $this->_parts['where'] = $this->newExpr(); } @@ -1056,7 +912,7 @@ public function where($conditions = null, array $types = [], bool $overwrite = f * that should be not null. * @return $this */ - public function whereNotNull($fields) + public function whereNotNull(ExpressionInterface|array|string $fields) { if (!is_array($fields)) { $fields = [$fields]; @@ -1078,7 +934,7 @@ public function whereNotNull($fields) * that should be null. * @return $this */ - public function whereNull($fields) + public function whereNull(ExpressionInterface|array|string $fields) { if (!is_array($fields)) { $fields = [$fields]; @@ -1181,7 +1037,7 @@ public function whereNotInListOrNull(string $field, array $values, array $option [ 'OR' => [$field . ' NOT IN' => $values, $field . ' IS' => null], ], - $options['types'] + $options['types'], ); } @@ -1241,7 +1097,7 @@ public function whereNotInListOrNull(string $field, array $values, array $option * @see \Cake\Database\TypeFactory * @return $this */ - public function andWhere($conditions, array $types = []) + public function andWhere(ExpressionInterface|Closure|array|string $conditions, array $types = []) { $this->_conjugate('where', $conditions, 'AND', $types); @@ -1264,7 +1120,76 @@ public function andWhere($conditions, array $types = []) * ### Examples: * * ``` - * $query->order(['title' => 'DESC', 'author_id' => 'ASC']); + * $query->orderBy(['title' => 'DESC', 'author_id' => 'ASC']); + * ``` + * + * Produces: + * + * `ORDER BY title DESC, author_id ASC` + * + * ``` + * $query + * ->orderBy(['title' => $query->newExpr('DESC NULLS FIRST')]) + * ->orderBy('author_id'); + * ``` + * + * Will generate: + * + * `ORDER BY title DESC NULLS FIRST, author_id` + * + * ``` + * $expression = $query->newExpr()->add(['id % 2 = 0']); + * $query->orderBy($expression)->orderBy(['title' => 'ASC']); + * ``` + * + * and + * + * ``` + * $query->orderBy(function ($exp, $query) { + * return [$exp->add(['id % 2 = 0']), 'title' => 'ASC']; + * }); + * ``` + * + * Will both become: + * + * `ORDER BY (id %2 = 0), title ASC` + * + * Order fields/directions are not sanitized by the query builder. + * You should use an allowed list of fields/directions when passing + * in user-supplied data to `order()`. + * + * If you need to set complex expressions as order conditions, you + * should use `orderByAsc()` or `orderByDesc()`. + * + * @param \Cake\Database\ExpressionInterface|\Closure|array|string $fields fields to be added to the list + * @param bool $overwrite whether to reset order with field list or not + * @return $this + * @deprecated 5.0.0 Use orderBy() instead now that CollectionInterface methods are no longer proxied. + */ + public function order(ExpressionInterface|Closure|array|string $fields, bool $overwrite = false) + { + deprecationWarning('5.0.0', 'Query::order() is deprecated. Use Query::orderBy() instead.'); + + return $this->orderBy($fields, $overwrite); + } + + /** + * Adds a single or multiple fields to be used in the ORDER clause for this query. + * Fields can be passed as an array of strings, array of expression + * objects, a single expression or a single string. + * + * If an array is passed, keys will be used as the field itself and the value will + * represent the order in which such field should be ordered. When called multiple + * times with the same fields as key, the last order definition will prevail over + * the others. + * + * By default this function will append any passed argument to the list of fields + * to be selected, unless the second argument is set to true. + * + * ### Examples: + * + * ``` + * $query->orderBy(['title' => 'DESC', 'author_id' => 'ASC']); * ``` * * Produces: @@ -1273,8 +1198,8 @@ public function andWhere($conditions, array $types = []) * * ``` * $query - * ->order(['title' => $query->newExpr('DESC NULLS FIRST')]) - * ->order('author_id'); + * ->orderBy(['title' => $query->newExpr('DESC NULLS FIRST')]) + * ->orderBy('author_id'); * ``` * * Will generate: @@ -1283,13 +1208,13 @@ public function andWhere($conditions, array $types = []) * * ``` * $expression = $query->newExpr()->add(['id % 2 = 0']); - * $query->order($expression)->order(['title' => 'ASC']); + * $query->orderBy($expression)->orderBy(['title' => 'ASC']); * ``` * * and * * ``` - * $query->order(function ($exp, $query) { + * $query->orderBy(function ($exp, $query) { * return [$exp->add(['id % 2 = 0']), 'title' => 'ASC']; * }); * ``` @@ -1303,13 +1228,13 @@ public function andWhere($conditions, array $types = []) * in user-supplied data to `order()`. * * If you need to set complex expressions as order conditions, you - * should use `orderAsc()` or `orderDesc()`. + * should use `orderByAsc()` or `orderByDesc()`. * * @param \Cake\Database\ExpressionInterface|\Closure|array|string $fields fields to be added to the list * @param bool $overwrite whether to reset order with field list or not * @return $this */ - public function order($fields, $overwrite = false) + public function orderBy(ExpressionInterface|Closure|array|string $fields, bool $overwrite = false) { if ($overwrite) { $this->_parts['order'] = null; @@ -1319,9 +1244,7 @@ public function order($fields, $overwrite = false) return $this; } - if (!$this->_parts['order']) { - $this->_parts['order'] = new OrderByExpression(); - } + $this->_parts['order'] ??= new OrderByExpression(); $this->_conjugate('order', $fields, '', []); return $this; @@ -1339,30 +1262,17 @@ public function order($fields, $overwrite = false) * @param \Cake\Database\ExpressionInterface|\Closure|string $field The field to order on. * @param bool $overwrite Whether to reset the order clauses. * @return $this + * @deprecated 5.0.0 Use orderByAsc() instead now that CollectionInterface methods are no longer proxied. */ - public function orderAsc($field, $overwrite = false) + public function orderAsc(ExpressionInterface|Closure|string $field, bool $overwrite = false) { - if ($overwrite) { - $this->_parts['order'] = null; - } - if (!$field) { - return $this; - } - - if ($field instanceof Closure) { - $field = $field($this->newExpr(), $this); - } - - if (!$this->_parts['order']) { - $this->_parts['order'] = new OrderByExpression(); - } - $this->_parts['order']->add(new OrderClauseExpression($field, 'ASC')); + deprecationWarning('5.0.0', 'Query::orderAsc() is deprecated. Use Query::orderByAsc() instead.'); - return $this; + return $this->orderByAsc($field, $overwrite); } /** - * Add an ORDER BY clause with a DESC direction. + * Add an ORDER BY clause with an ASC direction. * * This method allows you to set complex expressions * as order conditions unlike order() @@ -1374,7 +1284,7 @@ public function orderAsc($field, $overwrite = false) * @param bool $overwrite Whether to reset the order clauses. * @return $this */ - public function orderDesc($field, $overwrite = false) + public function orderByAsc(ExpressionInterface|Closure|string $field, bool $overwrite = false) { if ($overwrite) { $this->_parts['order'] = null; @@ -1387,165 +1297,89 @@ public function orderDesc($field, $overwrite = false) $field = $field($this->newExpr(), $this); } - if (!$this->_parts['order']) { - $this->_parts['order'] = new OrderByExpression(); - } - $this->_parts['order']->add(new OrderClauseExpression($field, 'DESC')); + $this->_parts['order'] ??= new OrderByExpression(); + + /** @var \Cake\Database\Expression\QueryExpression $queryExpr */ + $queryExpr = $this->_parts['order']; + $queryExpr->add(new OrderClauseExpression($field, 'ASC')); return $this; } /** - * Adds a single or multiple fields to be used in the GROUP BY clause for this query. - * Fields can be passed as an array of strings, array of expression - * objects, a single expression or a single string. - * - * By default this function will append any passed argument to the list of fields - * to be grouped, unless the second argument is set to true. - * - * ### Examples: - * - * ``` - * // Produces GROUP BY id, title - * $query->group(['id', 'title']); + * Add an ORDER BY clause with a DESC direction. * - * // Produces GROUP BY title - * $query->group('title'); - * ``` + * This method allows you to set complex expressions + * as order conditions unlike order() * - * Group fields are not suitable for use with user supplied data as they are + * Order fields are not suitable for use with user supplied data as they are * not sanitized by the query builder. * - * @param \Cake\Database\ExpressionInterface|array|string $fields fields to be added to the list - * @param bool $overwrite whether to reset fields with passed list or not + * @param \Cake\Database\ExpressionInterface|\Closure|string $field The field to order on. + * @param bool $overwrite Whether to reset the order clauses. * @return $this + * @deprecated 5.0.0 Use orderByDesc() instead now that CollectionInterface methods are no longer proxied. */ - public function group($fields, $overwrite = false) + public function orderDesc(ExpressionInterface|Closure|string $field, bool $overwrite = false) { - if ($overwrite) { - $this->_parts['group'] = []; - } - - if (!is_array($fields)) { - $fields = [$fields]; - } - - $this->_parts['group'] = array_merge($this->_parts['group'], array_values($fields)); - $this->_dirty(); + deprecationWarning('5.0.0', 'Query::orderDesc() is deprecated. Use Query::orderByDesc() instead.'); - return $this; + return $this->orderByDesc($field, $overwrite); } /** - * Adds a condition or set of conditions to be used in the `HAVING` clause for this - * query. This method operates in exactly the same way as the method `where()` - * does. Please refer to its documentation for an insight on how to using each - * parameter. + * Add an ORDER BY clause with a DESC direction. + * + * This method allows you to set complex expressions + * as order conditions unlike order() * - * Having fields are not suitable for use with user supplied data as they are + * Order fields are not suitable for use with user supplied data as they are * not sanitized by the query builder. * - * @param \Cake\Database\ExpressionInterface|\Closure|array|string|null $conditions The having conditions. - * @param array $types Associative array of type names used to bind values to query - * @param bool $overwrite whether to reset conditions with passed list or not - * @see \Cake\Database\Query::where() + * @param \Cake\Database\ExpressionInterface|\Closure|string $field The field to order on. + * @param bool $overwrite Whether to reset the order clauses. * @return $this */ - public function having($conditions = null, $types = [], $overwrite = false) + public function orderByDesc(ExpressionInterface|Closure|string $field, bool $overwrite = false) { if ($overwrite) { - $this->_parts['having'] = $this->newExpr(); + $this->_parts['order'] = null; + } + if (!$field) { + return $this; + } + + if ($field instanceof Closure) { + $field = $field($this->newExpr(), $this); } - $this->_conjugate('having', $conditions, 'AND', $types); + + $this->_parts['order'] ??= new OrderByExpression(); + + /** @var \Cake\Database\Expression\QueryExpression $queryExpr */ + $queryExpr = $this->_parts['order']; + $queryExpr->add(new OrderClauseExpression($field, 'DESC')); return $this; } /** - * Connects any previously defined set of conditions to the provided list - * using the AND operator in the HAVING clause. This method operates in exactly - * the same way as the method `andWhere()` does. Please refer to its - * documentation for an insight on how to using each parameter. + * Set the page of results you want. * - * Having fields are not suitable for use with user supplied data as they are - * not sanitized by the query builder. + * This method provides an easier to use interface to set the limit + offset + * in the record set you want as results. If empty the limit will default to + * the existing limit clause, and if that too is empty, then `25` will be used. * - * @param \Cake\Database\ExpressionInterface|\Closure|array|string $conditions The AND conditions for HAVING. - * @param array $types Associative array of type names used to bind values to query - * @see \Cake\Database\Query::andWhere() + * Pages must start at 1. + * + * @param int $num The page number you want. + * @param int|null $limit The number of rows you want in the page. If null + * the current limit clause will be used. * @return $this + * @throws \Cake\Core\Exception\CakeException If page number < 1. */ - public function andHaving($conditions, $types = []) + public function page(int $num, ?int $limit = null) { - $this->_conjugate('having', $conditions, 'AND', $types); - - return $this; - } - - /** - * Adds a named window expression. - * - * You are responsible for adding windows in the order your database requires. - * - * @param string $name Window name - * @param \Cake\Database\Expression\WindowExpression|\Closure $window Window expression - * @param bool $overwrite Clear all previous query window expressions - * @return $this - */ - public function window(string $name, $window, bool $overwrite = false) - { - if ($overwrite) { - $this->_parts['window'] = []; - } - - if ($window instanceof Closure) { - $window = $window(new WindowExpression(), $this); - if (!($window instanceof WindowExpression)) { - throw new RuntimeException('You must return a `WindowExpression` from a Closure passed to `window()`.'); - } - } - - $this->_parts['window'][] = ['name' => new IdentifierExpression($name), 'window' => $window]; - $this->_dirty(); - - return $this; - } - - /** - * Set the page of results you want. - * - * This method provides an easier to use interface to set the limit + offset - * in the record set you want as results. If empty the limit will default to - * the existing limit clause, and if that too is empty, then `25` will be used. - * - * Pages must start at 1. - * - * @param int $num The page number you want. - * @param int|null $limit The number of rows you want in the page. If null - * the current limit clause will be used. - * @return $this - * @throws \InvalidArgumentException If page number < 1. - */ - public function page(int $num, ?int $limit = null) - { - if ($num < 1) { - throw new InvalidArgumentException('Pages must start at 1.'); - } - if ($limit !== null) { - $this->limit($limit); - } - $limit = $this->clause('limit'); - if ($limit === null) { - $limit = 25; - $this->limit($limit); - } - $offset = ($num - 1) * $limit; - if (PHP_INT_MAX <= $offset) { - $offset = PHP_INT_MAX; - } - $this->offset((int)$offset); - - return $this; + throw new CakeException('Not implemented'); } /** @@ -1564,11 +1398,8 @@ public function page(int $num, ?int $limit = null) * @param \Cake\Database\ExpressionInterface|int|null $limit number of records to be returned * @return $this */ - public function limit($limit) + public function limit(ExpressionInterface|int|null $limit) { - if (is_string($limit) && !is_numeric($limit)) { - throw new InvalidArgumentException('Invalid value for `limit()`'); - } $this->_dirty(); $this->_parts['limit'] = $limit; @@ -1593,133 +1424,14 @@ public function limit($limit) * @param \Cake\Database\ExpressionInterface|int|null $offset number of records to be skipped * @return $this */ - public function offset($offset) + public function offset(ExpressionInterface|int|null $offset) { - if (is_string($offset) && !is_numeric($offset)) { - throw new InvalidArgumentException('Invalid value for `offset()`'); - } $this->_dirty(); $this->_parts['offset'] = $offset; return $this; } - /** - * Adds a complete query to be used in conjunction with an UNION operator with - * this query. This is used to combine the result set of this query with the one - * that will be returned by the passed query. You can add as many queries as you - * required by calling multiple times this method with different queries. - * - * By default, the UNION operator will remove duplicate rows, if you wish to include - * every row for all queries, use unionAll(). - * - * ### Examples - * - * ``` - * $union = (new Query($conn))->select(['id', 'title'])->from(['a' => 'articles']); - * $query->select(['id', 'name'])->from(['d' => 'things'])->union($union); - * ``` - * - * Will produce: - * - * `SELECT id, name FROM things d UNION SELECT id, title FROM articles a` - * - * @param \Cake\Database\Query|string $query full SQL query to be used in UNION operator - * @param bool $overwrite whether to reset the list of queries to be operated or not - * @return $this - */ - public function union($query, $overwrite = false) - { - if ($overwrite) { - $this->_parts['union'] = []; - } - $this->_parts['union'][] = [ - 'all' => false, - 'query' => $query, - ]; - $this->_dirty(); - - return $this; - } - - /** - * Adds a complete query to be used in conjunction with the UNION ALL operator with - * this query. This is used to combine the result set of this query with the one - * that will be returned by the passed query. You can add as many queries as you - * required by calling multiple times this method with different queries. - * - * Unlike UNION, UNION ALL will not remove duplicate rows. - * - * ``` - * $union = (new Query($conn))->select(['id', 'title'])->from(['a' => 'articles']); - * $query->select(['id', 'name'])->from(['d' => 'things'])->unionAll($union); - * ``` - * - * Will produce: - * - * `SELECT id, name FROM things d UNION ALL SELECT id, title FROM articles a` - * - * @param \Cake\Database\Query|string $query full SQL query to be used in UNION operator - * @param bool $overwrite whether to reset the list of queries to be operated or not - * @return $this - */ - public function unionAll($query, $overwrite = false) - { - if ($overwrite) { - $this->_parts['union'] = []; - } - $this->_parts['union'][] = [ - 'all' => true, - 'query' => $query, - ]; - $this->_dirty(); - - return $this; - } - - /** - * Create an insert query. - * - * Note calling this method will reset any data previously set - * with Query::values(). - * - * @param array $columns The columns to insert into. - * @param array $types A map between columns & their datatypes. - * @return $this - * @throws \RuntimeException When there are 0 columns. - */ - public function insert(array $columns, array $types = []) - { - if (empty($columns)) { - throw new RuntimeException('At least 1 column is required to perform an insert.'); - } - $this->_dirty(); - $this->_type = 'insert'; - $this->_parts['insert'][1] = $columns; - if (!$this->_parts['values']) { - $this->_parts['values'] = new ValuesExpression($columns, $this->getTypeMap()->setTypes($types)); - } else { - $this->_parts['values']->setColumns($columns); - } - - return $this; - } - - /** - * Set the table name for insert queries. - * - * @param string $table The table name to insert into. - * @return $this - */ - public function into(string $table) - { - $this->_dirty(); - $this->_type = 'insert'; - $this->_parts['insert'][0] = $table; - - return $this; - } - /** * Creates an expression that refers to an identifier. Identifiers are used to refer to field names and allow * the SQL compiler to apply quotes or escape the identifier. @@ -1742,168 +1454,47 @@ public function identifier(string $identifier): ExpressionInterface } /** - * Set the values for an insert query. - * - * Multi inserts can be performed by calling values() more than one time, - * or by providing an array of value sets. Additionally $data can be a Query - * instance to insert data from another SELECT statement. - * - * @param \Cake\Database\Expression\ValuesExpression|\Cake\Database\Query|array $data The data to insert. - * @return $this - * @throws \Cake\Database\Exception\DatabaseException if you try to set values before declaring columns. - * Or if you try to set values on non-insert queries. - */ - public function values($data) - { - if ($this->_type !== 'insert') { - throw new DatabaseException( - 'You cannot add values before defining columns to use.' - ); - } - if (empty($this->_parts['insert'])) { - throw new DatabaseException( - 'You cannot add values before defining columns to use.' - ); - } - - $this->_dirty(); - if ($data instanceof ValuesExpression) { - $this->_parts['values'] = $data; - - return $this; - } - - $this->_parts['values']->add($data); - - return $this; - } - - /** - * Create an update query. - * - * Can be combined with set() and where() methods to create update queries. - * - * @param \Cake\Database\ExpressionInterface|string $table The table you want to update. - * @return $this - */ - public function update($table) - { - if (!is_string($table) && !($table instanceof ExpressionInterface)) { - $text = 'Table must be of type string or "%s", got "%s"'; - $message = sprintf($text, ExpressionInterface::class, gettype($table)); - throw new InvalidArgumentException($message); - } - - $this->_dirty(); - $this->_type = 'update'; - $this->_parts['update'][0] = $table; - - return $this; - } - - /** - * Set one or many fields to update. - * - * ### Examples - * - * Passing a string: - * - * ``` - * $query->update('articles')->set('title', 'The Title'); - * ``` - * - * Passing an array: + * A string or expression that will be appended to the generated query * + * ### Examples: * ``` - * $query->update('articles')->set(['title' => 'The Title'], ['title' => 'string']); + * $query->select('id')->where(['author_id' => 1])->epilog('FOR UPDATE'); + * $query + * ->insert('articles', ['title']) + * ->values(['author_id' => 1]) + * ->epilog('RETURNING id'); * ``` * - * Passing a callable: - * - * ``` - * $query->update('articles')->set(function ($exp) { - * return $exp->eq('title', 'The title', 'string'); - * }); - * ``` + * Epilog content is raw SQL and not suitable for use with user supplied data. * - * @param \Cake\Database\Expression\QueryExpression|\Closure|array|string $key The column name or array of keys - * + values to set. This can also be a QueryExpression containing a SQL fragment. - * It can also be a Closure, that is required to return an expression object. - * @param mixed $value The value to update $key to. Can be null if $key is an - * array or QueryExpression. When $key is an array, this parameter will be - * used as $types instead. - * @param array|string $types The column types to treat data as. - * @return $this - */ - public function set($key, $value = null, $types = []) - { - if (empty($this->_parts['set'])) { - $this->_parts['set'] = $this->newExpr()->setConjunction(','); - } - - if ($key instanceof Closure) { - $exp = $this->newExpr()->setConjunction(','); - $this->_parts['set']->add($key($exp)); - - return $this; - } - - if (is_array($key) || $key instanceof ExpressionInterface) { - $types = (array)$value; - $this->_parts['set']->add($key, $types); - - return $this; - } - - if (!is_string($types)) { - $types = null; - } - $this->_parts['set']->eq($key, $value, $types); - - return $this; - } - - /** - * Create a delete query. - * - * Can be combined with from(), where() and other methods to - * create delete queries with specific conditions. - * - * @param string|null $table The table to use when deleting. + * @param \Cake\Database\ExpressionInterface|string|null $expression The expression to be appended * @return $this */ - public function delete(?string $table = null) + public function epilog(ExpressionInterface|string|null $expression = null) { $this->_dirty(); - $this->_type = 'delete'; - if ($table !== null) { - $this->from($table); - } + $this->_parts['epilog'] = $expression; return $this; } /** - * A string or expression that will be appended to the generated query + * A string or expression that will be appended to the generated query as a comment * * ### Examples: * ``` - * $query->select('id')->where(['author_id' => 1])->epilog('FOR UPDATE'); - * $query - * ->insert('articles', ['title']) - * ->values(['author_id' => 1]) - * ->epilog('RETURNING id'); + * $query->select('id')->where(['author_id' => 1])->comment('Filter for admin user'); * ``` * - * Epliog content is raw SQL and not suitable for use with user supplied data. + * Comment content is raw SQL and not suitable for use with user supplied data. * - * @param \Cake\Database\ExpressionInterface|string|null $expression The expression to be appended + * @param string|null $expression The comment to be added * @return $this */ - public function epilog($expression = null) + public function comment(?string $expression = null) { $this->_dirty(); - $this->_parts['epilog'] = $expression; + $this->_parts['comment'] = $expression; return $this; } @@ -1935,7 +1526,7 @@ public function type(): string * @param \Cake\Database\ExpressionInterface|array|string|null $rawExpression A string, array or anything you want wrapped in an expression object * @return \Cake\Database\Expression\QueryExpression */ - public function newExpr($rawExpression = null): QueryExpression + public function newExpr(ExpressionInterface|array|string|null $rawExpression = null): QueryExpression { return $this->expr($rawExpression); } @@ -1957,7 +1548,7 @@ public function newExpr($rawExpression = null): QueryExpression * @param \Cake\Database\ExpressionInterface|array|string|null $rawExpression A string, array or anything you want wrapped in an expression object * @return \Cake\Database\Expression\QueryExpression */ - public function expr($rawExpression = null): QueryExpression + public function expr(ExpressionInterface|array|string|null $rawExpression = null): QueryExpression { $expression = new QueryExpression([], $this->getTypeMap()); @@ -1983,30 +1574,7 @@ public function expr($rawExpression = null): QueryExpression */ public function func(): FunctionsBuilder { - if ($this->_functionsBuilder === null) { - $this->_functionsBuilder = new FunctionsBuilder(); - } - - return $this->_functionsBuilder; - } - - /** - * Executes this query and returns a results iterator. This function is required - * for implementing the IteratorAggregate interface and allows the query to be - * iterated without having to call execute() manually, thus making it look like - * a result set instead of the query itself. - * - * @return \Cake\Database\StatementInterface - * @psalm-suppress ImplementedReturnTypeMismatch - */ - #[\ReturnTypeWillChange] - public function getIterator() - { - if ($this->_iterator === null || $this->_dirty) { - $this->_iterator = $this->execute(); - } - - return $this->_iterator; + return $this->_functionsBuilder ??= new FunctionsBuilder(); } /** @@ -2014,7 +1582,7 @@ public function getIterator() * modifying any internal part of the query and it is used by the SQL dialects * to transform the query accordingly before it is executed. The valid clauses that * can be retrieved are: delete, update, set, insert, values, select, distinct, - * from, join, set, where, group, having, order, limit, offset and union. + * from, join, set, where, group, having, order, limit, offset, union and intersect. * * The return value for each of those parts may vary. Some clauses use QueryExpression * to internally store their state, some use arrays and others may use booleans or @@ -2036,62 +1604,28 @@ public function getIterator() * - limit: integer or QueryExpression, null when not set * - offset: integer or QueryExpression, null when not set * - union: array + * - intersect: array * * @param string $name name of the clause to be returned * @return mixed * @throws \InvalidArgumentException When the named clause does not exist. */ - public function clause(string $name) + public function clause(string $name): mixed { if (!array_key_exists($name, $this->_parts)) { - $clauses = implode(', ', array_keys($this->_parts)); - throw new InvalidArgumentException("The '$name' clause is not defined. Valid clauses are: $clauses"); + $clauses = array_keys($this->_parts); + array_walk($clauses, fn(&$x) => $x = "`{$x}`"); + $clauses = implode(', ', $clauses); + throw new InvalidArgumentException(sprintf( + 'The `%s` clause is not defined. Valid clauses are: %s.', + $name, + $clauses, + )); } return $this->_parts[$name]; } - /** - * Registers a callback to be executed for each result that is fetched from the - * result set, the callback function will receive as first parameter an array with - * the raw data from the database for every row that is fetched and must return the - * row with any possible modifications. - * - * Callbacks will be executed lazily, if only 3 rows are fetched for database it will - * called 3 times, event though there might be more rows to be fetched in the cursor. - * - * Callbacks are stacked in the order they are registered, if you wish to reset the stack - * the call this function with the second parameter set to true. - * - * If you wish to remove all decorators from the stack, set the first parameter - * to null and the second to true. - * - * ### Example - * - * ``` - * $query->decorateResults(function ($row) { - * $row['order_total'] = $row['subtotal'] + ($row['subtotal'] * $row['tax']); - * return $row; - * }); - * ``` - * - * @param callable|null $callback The callback to invoke when results are fetched. - * @param bool $overwrite Whether this should append or replace all existing decorators. - * @return $this - */ - public function decorateResults(?callable $callback, bool $overwrite = false) - { - if ($overwrite) { - $this->_resultDecorators = []; - } - - if ($callback !== null) { - $this->_resultDecorators[] = $callback; - } - - return $this; - } - /** * This function works similar to the traverse() function, with the difference * that it does a full depth traversal of the entire expression tree. This will execute @@ -2100,16 +1634,12 @@ public function decorateResults(?callable $callback, bool $overwrite = false) * * Callback will receive as first parameter the currently visited expression. * - * @param callable $callback the function to be executed for each ExpressionInterface + * @param \Closure $callback the function to be executed for each ExpressionInterface * found inside this query. * @return $this */ - public function traverseExpressions(callable $callback) + public function traverseExpressions(Closure $callback) { - if (!$callback instanceof Closure) { - $callback = Closure::fromCallable($callback); - } - foreach ($this->_parts as $part) { $this->_expressionsVisitor($part, $callback); } @@ -2120,13 +1650,13 @@ public function traverseExpressions(callable $callback) /** * Query parts traversal method used by traverseExpressions() * - * @param \Cake\Database\ExpressionInterface|array<\Cake\Database\ExpressionInterface> $expression Query expression or + * @param mixed $expression Query expression or * array of expressions. * @param \Closure $callback The callback to be executed for each ExpressionInterface * found inside this query. * @return void */ - protected function _expressionsVisitor($expression, Closure $callback): void + protected function _expressionsVisitor(mixed $expression, Closure $callback): void { if (is_array($expression)) { foreach ($expression as $e) { @@ -2137,9 +1667,7 @@ protected function _expressionsVisitor($expression, Closure $callback): void } if ($expression instanceof ExpressionInterface) { - $expression->traverse(function ($exp) use ($callback) { - $this->_expressionsVisitor($exp, $callback); - }); + $expression->traverse(fn($exp) => $this->_expressionsVisitor($exp, $callback)); if (!$expression instanceof self) { $callback($expression); @@ -2161,7 +1689,7 @@ protected function _expressionsVisitor($expression, Closure $callback): void * to database * @return $this */ - public function bind($param, $value, $type = null) + public function bind(string|int $param, mixed $value, string|int|null $type = null) { $this->getValueBinder()->bind($param, $value, $type); @@ -2179,11 +1707,7 @@ public function bind($param, $value, $type = null) */ public function getValueBinder(): ValueBinder { - if ($this->_valueBinder === null) { - $this->_valueBinder = new ValueBinder(); - } - - return $this->_valueBinder; + return $this->_valueBinder ??= new ValueBinder(); } /** @@ -2203,169 +1727,6 @@ public function setValueBinder(?ValueBinder $binder) return $this; } - /** - * Enables/Disables buffered results. - * - * When enabled the results returned by this Query will be - * buffered. This enables you to iterate a result set multiple times, or - * both cache and iterate it. - * - * When disabled it will consume less memory as fetched results are not - * remembered for future iterations. - * - * @param bool $enable Whether to enable buffering - * @return $this - * @deprecated 4.5.0 Results will always be buffered in 5.0. - */ - public function enableBufferedResults(bool $enable = true) - { - if (!$enable) { - deprecationWarning( - '4.5.0 enableBufferedResults() is deprecated. Results will always be buffered in 5.0.' - ); - } - $this->_dirty(); - $this->_useBufferedResults = $enable; - - return $this; - } - - /** - * Disables buffered results. - * - * Disabling buffering will consume less memory as fetched results are not - * remembered for future iterations. - * - * @return $this - * @deprecated 4.5.0 Results will always be buffered in 5.0. - */ - public function disableBufferedResults() - { - $this->_dirty(); - $this->_useBufferedResults = false; - - return $this; - } - - /** - * Returns whether buffered results are enabled/disabled. - * - * When enabled the results returned by this Query will be - * buffered. This enables you to iterate a result set multiple times, or - * both cache and iterate it. - * - * When disabled it will consume less memory as fetched results are not - * remembered for future iterations. - * - * @return bool - * @deprecated 4.5.0 Results will always be buffered in 5.0. - */ - public function isBufferedResultsEnabled(): bool - { - return $this->_useBufferedResults; - } - - /** - * Sets the TypeMap class where the types for each of the fields in the - * select clause are stored. - * - * @param \Cake\Database\TypeMap $typeMap The map object to use - * @return $this - */ - public function setSelectTypeMap(TypeMap $typeMap) - { - $this->_selectTypeMap = $typeMap; - $this->_dirty(); - - return $this; - } - - /** - * Gets the TypeMap class where the types for each of the fields in the - * select clause are stored. - * - * @return \Cake\Database\TypeMap - */ - public function getSelectTypeMap(): TypeMap - { - if ($this->_selectTypeMap === null) { - $this->_selectTypeMap = new TypeMap(); - } - - return $this->_selectTypeMap; - } - - /** - * Disables result casting. - * - * When disabled, the fields will be returned as received from the database - * driver (which in most environments means they are being returned as - * strings), which can improve performance with larger datasets. - * - * @return $this - */ - public function disableResultsCasting() - { - $this->typeCastEnabled = false; - - return $this; - } - - /** - * Enables result casting. - * - * When enabled, the fields in the results returned by this Query will be - * cast to their corresponding PHP data type. - * - * @return $this - */ - public function enableResultsCasting() - { - $this->typeCastEnabled = true; - - return $this; - } - - /** - * Returns whether result casting is enabled/disabled. - * - * When enabled, the fields in the results returned by this Query will be - * casted to their corresponding PHP data type. - * - * When disabled, the fields will be returned as received from the database - * driver (which in most environments means they are being returned as - * strings), which can improve performance with larger datasets. - * - * @return bool - */ - public function isResultsCastingEnabled(): bool - { - return $this->typeCastEnabled; - } - - /** - * Auxiliary function used to wrap the original statement from the driver with - * any registered callbacks. - * - * @param \Cake\Database\StatementInterface $statement to be decorated - * @return \Cake\Database\Statement\CallbackStatement|\Cake\Database\StatementInterface - */ - protected function _decorateStatement(StatementInterface $statement) - { - $typeMap = $this->getSelectTypeMap(); - $driver = $this->getConnection()->getDriver($this->connectionRole); - - if ($this->typeCastEnabled && $typeMap->toArray()) { - $statement = new CallbackStatement($statement, $driver, new FieldTypeConverter($typeMap, $driver)); - } - - foreach ($this->_resultDecorators as $f) { - $statement = new CallbackStatement($statement, $driver, $f); - } - - return $statement; - } - /** * Helper function used to build conditions by composing QueryExpression objects. * @@ -2376,10 +1737,15 @@ protected function _decorateStatement(StatementInterface $statement) * @param array $types Associative array of type names used to bind values to query * @return void */ - protected function _conjugate(string $part, $append, $conjunction, array $types): void - { + protected function _conjugate( + string $part, + ExpressionInterface|Closure|array|string|null $append, + string $conjunction, + array $types, + ): void { + /** @var \Cake\Database\Expression\QueryExpression $expression */ $expression = $this->_parts[$part] ?: $this->newExpr(); - if (empty($append)) { + if (!$append) { $this->_parts[$part] = $expression; return; @@ -2411,7 +1777,7 @@ protected function _dirty(): void { $this->_dirty = true; - if ($this->_iterator && $this->_valueBinder) { + if ($this->_statement && $this->_valueBinder) { $this->getValueBinder()->reset(); } } @@ -2423,15 +1789,12 @@ protected function _dirty(): void */ public function __clone() { - $this->_iterator = null; + $this->_statement = null; if ($this->_valueBinder !== null) { $this->_valueBinder = clone $this->_valueBinder; } - if ($this->_selectTypeMap !== null) { - $this->_selectTypeMap = clone $this->_selectTypeMap; - } foreach ($this->_parts as $name => $part) { - if (empty($part)) { + if (!$part) { continue; } if (is_array($part)) { @@ -2439,7 +1802,6 @@ public function __clone() if (is_array($piece)) { foreach ($piece as $j => $value) { if ($value instanceof ExpressionInterface) { - /** @psalm-suppress PossiblyUndefinedMethod */ $this->_parts[$name][$i][$j] = clone $value; } } @@ -2475,42 +1837,27 @@ public function __debugInfo(): array try { set_error_handler( /** @return no-return */ - function ($errno, $errstr) { - throw new RuntimeException($errstr, $errno); + function ($errno, $errstr): void { + throw new CakeException($errstr, $errno); }, - E_ALL + E_ALL, ); $sql = $this->sql(); $params = $this->getValueBinder()->bindings(); - } catch (Throwable $e) { + } catch (Throwable) { $sql = 'SQL could not be generated for this query as it is incomplete.'; $params = []; } finally { restore_error_handler(); - } - return [ - '(help)' => 'This is a Query object, to get the results execute or iterate it.', - 'sql' => $sql, - 'params' => $params, - 'defaultTypes' => $this->getDefaultTypes(), - 'decorators' => count($this->_resultDecorators), - 'executed' => $this->_iterator ? true : false, - ]; - } - - /** - * Helper for Query deprecation methods. - * - * @param string $method The method that is invalid. - * @param string $message An additional message. - * @return void - * @internal - */ - protected function _deprecatedMethod($method, $message = '') - { - $class = static::class; - $text = "As of 4.5.0 calling {$method}() on {$class} is deprecated. " . $message; - deprecationWarning($text); + return [ + '(help)' => 'This is a Query object, to get the results execute or iterate it.', + 'sql' => $sql, + 'params' => $params, + 'role' => $this->connectionRole, + 'defaultTypes' => $this->getDefaultTypes(), + 'executed' => (bool)$this->_statement, + ]; + } } } diff --git a/app/vendor/cakephp/cakephp/src/Database/Query/DeleteQuery.php b/app/vendor/cakephp/cakephp/src/Database/Query/DeleteQuery.php index 5c11ae9d7..db1087cfb 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Query/DeleteQuery.php +++ b/app/vendor/cakephp/cakephp/src/Database/Query/DeleteQuery.php @@ -19,204 +19,51 @@ use Cake\Database\Query; /** - * Delete Query forward compatibility shim. + * This class is used to generate DELETE queries for the relational database. */ class DeleteQuery extends Query { /** - * Type of this query (select, insert, update, delete). + * Type of this query. * * @var string */ - protected $_type = 'delete'; + protected string $_type = self::TYPE_DELETE; /** - * @inheritDoc - */ - public function select($fields = [], bool $overwrite = false) - { - $this->_deprecatedMethod('select()', 'Create your query with selectQuery() instead.'); - - return parent::select($fields, $overwrite); - } - - /** - * @inheritDoc - */ - public function distinct($on = [], $overwrite = false) - { - $this->_deprecatedMethod('distint()'); - - return parent::distinct($on, $overwrite); - } - - /** - * @inheritDoc - */ - public function modifier($modifiers, $overwrite = false) - { - $this->_deprecatedMethod('modifier()'); - - return parent::modifier($modifiers, $overwrite); - } - - /** - * @inheritDoc - */ - public function order($fields, $overwrite = false) - { - $this->_deprecatedMethod('order()'); - - return parent::order($fields, $overwrite); - } - - /** - * @inheritDoc - */ - public function orderAsc($field, $overwrite = false) - { - $this->_deprecatedMethod('orderAsc()'); - - return parent::orderAsc($field, $overwrite); - } - - /** - * @inheritDoc - */ - public function orderDesc($field, $overwrite = false) - { - $this->_deprecatedMethod('orderDesc()'); - - return parent::orderDesc($field, $overwrite); - } - - /** - * @inheritDoc - */ - public function group($fields, $overwrite = false) - { - $this->_deprecatedMethod('group()'); - - return parent::group($fields, $overwrite); - } - - /** - * @inheritDoc - */ - public function having($conditions = null, $types = [], $overwrite = false) - { - $this->_deprecatedMethod('having()'); - - return parent::having($conditions, $types, $overwrite); - } - - /** - * @inheritDoc - */ - public function andHaving($conditions, $types = []) - { - $this->_deprecatedMethod('andHaving()'); - - return parent::andHaving($conditions, $types); - } - - /** - * @inheritDoc - */ - public function page(int $num, ?int $limit = null) - { - $this->_deprecatedMethod('page()'); - - return parent::page($num, $limit); - } - - /** - * @inheritDoc - */ - public function limit($limit) - { - $this->_deprecatedMethod('limit()'); - - return parent::limit($limit); - } - - /** - * @inheritDoc - */ - public function offset($offset) - { - $this->_deprecatedMethod('offset()'); - - return parent::offset($offset); - } - - /** - * @inheritDoc - */ - public function union($query, $overwrite = false) - { - $this->_deprecatedMethod('union()'); - - return parent::union($query, $overwrite); - } - - /** - * @inheritDoc - */ - public function unionAll($query, $overwrite = false) - { - $this->_deprecatedMethod('unionAll()'); - - return parent::unionAll($query, $overwrite); - } - - /** - * @inheritDoc - */ - public function insert(array $columns, array $types = []) - { - $this->_deprecatedMethod('insert()', 'Create your query with insertQuery() instead.'); - - return parent::insert($columns, $types); - } - - /** - * @inheritDoc - */ - public function into(string $table) - { - $this->_deprecatedMethod('into()'); - - return parent::into($table); - } - - /** - * @inheritDoc - */ - public function values($data) - { - $this->_deprecatedMethod('values()'); - - return parent::values($data); - } - - /** - * @inheritDoc - */ - public function update($table) - { - $this->_deprecatedMethod('update()', 'Create your query with updateQuery() instead.'); - - return parent::update($table); - } - - /** - * @inheritDoc + * List of SQL parts that will be used to build this query. + * + * @var array + */ + protected array $_parts = [ + 'comment' => null, + 'with' => [], + 'delete' => true, + 'modifier' => [], + 'from' => [], + 'join' => [], + 'where' => null, + 'order' => null, + 'limit' => null, + 'epilog' => null, + ]; + + /** + * Create a delete query. + * + * Can be combined with from(), where() and other methods to + * create delete queries with specific conditions. + * + * @param string|null $table The table to use when deleting. + * @return $this */ - public function set($key, $value = null, $types = []) + public function delete(?string $table = null) { - $this->_deprecatedMethod('set()'); + $this->_dirty(); + if ($table !== null) { + $this->from($table); + } - return parent::set($key, $value, $types); + return $this; } } diff --git a/app/vendor/cakephp/cakephp/src/Database/Query/InsertQuery.php b/app/vendor/cakephp/cakephp/src/Database/Query/InsertQuery.php index 7d326239b..93f8a8dbd 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Query/InsertQuery.php +++ b/app/vendor/cakephp/cakephp/src/Database/Query/InsertQuery.php @@ -16,307 +16,111 @@ */ namespace Cake\Database\Query; +use Cake\Database\Exception\DatabaseException; +use Cake\Database\Expression\ValuesExpression; use Cake\Database\Query; +use InvalidArgumentException; /** - * Insert Query forward compatibility shim. + * This class is used to generate INSERT queries for the relational database. */ class InsertQuery extends Query { /** - * Type of this query (select, insert, update, delete). + * Type of this query. * * @var string */ - protected $_type = 'insert'; + protected string $_type = self::TYPE_INSERT; /** - * @inheritDoc - */ - public function delete(?string $table = null) - { - $this->_deprecatedMethod('delete()', 'Create your query with deleteQuery() instead.'); - - return parent::delete($table); - } - - /** - * @inheritDoc - */ - public function select($fields = [], bool $overwrite = false) - { - $this->_deprecatedMethod('select()', 'Create your query with selectQuery() instead.'); - - return parent::select($fields, $overwrite); - } - - /** - * @inheritDoc - */ - public function distinct($on = [], $overwrite = false) - { - $this->_deprecatedMethod('distinct()'); - - return parent::distinct($on, $overwrite); - } - - /** - * @inheritDoc - */ - public function modifier($modifiers, $overwrite = false) - { - $this->_deprecatedMethod('modifier()'); - - return parent::modifier($modifiers, $overwrite); - } - - /** - * @inheritDoc - */ - public function join($tables, $types = [], $overwrite = false) - { - $this->_deprecatedMethod('join()'); - - return parent::join($tables, $types, $overwrite); - } - - /** - * @inheritDoc - */ - public function removeJoin(string $name) - { - $this->_deprecatedMethod('removeJoin()'); - - return parent::removeJoin($name); - } - - /** - * @inheritDoc - */ - public function leftJoin($table, $conditions = [], $types = []) - { - $this->_deprecatedMethod('leftJoin()'); - - return parent::leftJoin($table, $conditions, $types); - } - - /** - * @inheritDoc - */ - public function rightJoin($table, $conditions = [], $types = []) - { - $this->_deprecatedMethod('rightJoin()'); - - return parent::rightJoin($table, $conditions, $types); - } - - /** - * @inheritDoc - */ - public function innerJoin($table, $conditions = [], $types = []) - { - $this->_deprecatedMethod('innerJoin()'); - - return parent::innerJoin($table, $conditions, $types); - } - - /** - * @inheritDoc - */ - public function group($fields, $overwrite = false) - { - $this->_deprecatedMethod('group()'); - - return parent::group($fields, $overwrite); - } - - /** - * @inheritDoc - */ - public function having($conditions = null, $types = [], $overwrite = false) - { - $this->_deprecatedMethod('having()'); - - return parent::having($conditions, $types, $overwrite); - } - - /** - * @inheritDoc - */ - public function andHaving($conditions, $types = []) - { - $this->_deprecatedMethod('andHaving()'); - - return parent::andHaving($conditions, $types); - } - - /** - * @inheritDoc - */ - public function page(int $num, ?int $limit = null) - { - $this->_deprecatedMethod('page()'); - - return parent::page($num, $limit); - } - - /** - * @inheritDoc - */ - public function offset($offset) - { - $this->_deprecatedMethod('offset()'); - - return parent::offset($offset); - } - - /** - * @inheritDoc - */ - public function union($query, $overwrite = false) - { - $this->_deprecatedMethod('union()'); - - return parent::union($query, $overwrite); - } - - /** - * @inheritDoc - */ - public function unionAll($query, $overwrite = false) - { - $this->_deprecatedMethod('union()'); - - return parent::unionAll($query, $overwrite); - } - - /** - * @inheritDoc - */ - public function from($tables = [], $overwrite = false) - { - $this->_deprecatedMethod('from()', 'Use into() instead.'); - - return parent::from($tables, $overwrite); - } - - /** - * @inheritDoc - */ - public function update($table) - { - $this->_deprecatedMethod('update()', 'Create your query with updateQuery() instead.'); - - return parent::update($table); - } - - /** - * @inheritDoc - */ - public function set($key, $value = null, $types = []) - { - $this->_deprecatedMethod('set()'); - - return parent::set($key, $value, $types); - } - - /** - * @inheritDoc - */ - public function where($conditions = null, array $types = [], bool $overwrite = false) - { - $this->_deprecatedMethod('where()'); - - return parent::where($conditions, $types, $overwrite); - } - - /** - * @inheritDoc - */ - public function whereNotNull($fields) - { - $this->_deprecatedMethod('whereNotNull()'); - - return parent::whereNotNull($fields); - } - - /** - * @inheritDoc - */ - public function whereNull($fields) - { - $this->_deprecatedMethod('whereNull()'); - - return parent::whereNull($fields); - } - - /** - * @inheritDoc - */ - public function whereInList(string $field, array $values, array $options = []) - { - $this->_deprecatedMethod('whereInList()'); - - return parent::whereInList($field, $values, $options); - } - - /** - * @inheritDoc + * List of SQL parts that will be used to build this query. + * + * @var array */ - public function whereNotInList(string $field, array $values, array $options = []) - { - $this->_deprecatedMethod('whereNotInList()'); - - return parent::whereNotInList($field, $values, $options); - } + protected array $_parts = [ + 'comment' => null, + 'with' => [], + 'insert' => [], + 'modifier' => [], + 'values' => [], + 'epilog' => null, + ]; /** - * @inheritDoc + * Create an insert query. + * + * Note calling this method will reset any data previously set + * with Query::values(). + * + * @param array $columns The columns to insert into. + * @param array $types A map between columns & their datatypes. + * @return $this + * @throws \InvalidArgumentException When there are 0 columns. */ - public function whereNotInListOrNull(string $field, array $values, array $options = []) + public function insert(array $columns, array $types = []) { - $this->_deprecatedMethod('whereNotInListOrNull()'); + if (!$columns) { + throw new InvalidArgumentException('At least 1 column is required to perform an insert.'); + } + $this->_dirty(); + $this->_parts['insert'][1] = $columns; + if (!$this->_parts['values']) { + $this->_parts['values'] = new ValuesExpression($columns, $this->getTypeMap()->setTypes($types)); + } else { + /** @var \Cake\Database\Expression\ValuesExpression $valuesExpr */ + $valuesExpr = $this->_parts['values']; + $valuesExpr->setColumns($columns); + } - return parent::whereNotInListOrNull($field, $values, $options); + return $this; } /** - * @inheritDoc + * Set the table name for insert queries. + * + * @param string $table The table name to insert into. + * @return $this */ - public function andWhere($conditions, array $types = []) + public function into(string $table) { - $this->_deprecatedMethod('andWhere()'); + $this->_dirty(); + $this->_parts['insert'][0] = $table; - return parent::andWhere($conditions, $types); + return $this; } /** - * @inheritDoc + * Set the values for an insert query. + * + * Multi inserts can be performed by calling values() more than one time, + * or by providing an array of value sets. Additionally $data can be a Query + * instance to insert data from another SELECT statement. + * + * @param \Cake\Database\Expression\ValuesExpression|\Cake\Database\Query|array $data The data to insert. + * @return $this + * @throws \Cake\Database\Exception\DatabaseException if you try to set values before declaring columns. + * Or if you try to set values on non-insert queries. */ - public function order($fields, $overwrite = false) + public function values(ValuesExpression|Query|array $data) { - $this->_deprecatedMethod('order()'); + if (empty($this->_parts['insert'])) { + throw new DatabaseException( + 'You cannot add values before defining columns to use.', + ); + } - return parent::order($fields, $overwrite); - } + $this->_dirty(); + if ($data instanceof ValuesExpression) { + $this->_parts['values'] = $data; - /** - * @inheritDoc - */ - public function orderAsc($field, $overwrite = false) - { - $this->_deprecatedMethod('orderAsc()'); + return $this; + } - return parent::orderAsc($field, $overwrite); - } - - /** - * @inheritDoc - */ - public function orderDesc($field, $overwrite = false) - { - $this->_deprecatedMethod('orderDesc()'); + /** @var \Cake\Database\Expression\ValuesExpression $valuesExpr */ + $valuesExpr = $this->_parts['values']; + $valuesExpr->add($data); - return parent::orderDesc($field, $overwrite); + return $this; } } diff --git a/app/vendor/cakephp/cakephp/src/Database/Query/QueryFactory.php b/app/vendor/cakephp/cakephp/src/Database/Query/QueryFactory.php new file mode 100644 index 000000000..90a4346ba --- /dev/null +++ b/app/vendor/cakephp/cakephp/src/Database/Query/QueryFactory.php @@ -0,0 +1,136 @@ + $types Associative array containing the types to be used for casting. + * @return \Cake\Database\Query\SelectQuery + */ + public function select( + ExpressionInterface|Closure|array|string|float|int $fields = [], + array|string $table = [], + array $types = [], + ): SelectQuery { + $query = new SelectQuery($this->connection); + + $query + ->select($fields) + ->from($table) + ->setDefaultTypes($types); + + return $query; + } + + /** + * Create a new InsertQuery instance. + * + * @param string|null $table The table to insert rows into. + * @param array $values Associative array of column => value to be inserted. + * @param array $types Associative array containing the types to be used for casting. + * @return \Cake\Database\Query\InsertQuery + */ + public function insert(?string $table = null, array $values = [], array $types = []): InsertQuery + { + $query = new InsertQuery($this->connection); + + if ($table) { + $query->into($table); + } + + if ($values) { + $columns = array_keys($values); + $query + ->insert($columns, $types) + ->values($values); + } + + return $query; + } + + /** + * Create a new UpdateQuery instance. + * + * @param \Cake\Database\ExpressionInterface|string|null $table The table to update rows of. + * @param array $values Values to be updated. + * @param array $conditions Conditions to be set for the update statement. + * @param array $types Associative array containing the types to be used for casting. + * @return \Cake\Database\Query\UpdateQuery + */ + public function update( + ExpressionInterface|string|null $table = null, + array $values = [], + array $conditions = [], + array $types = [], + ): UpdateQuery { + $query = new UpdateQuery($this->connection); + + if ($table) { + $query->update($table); + } + if ($values) { + $query->set($values, $types); + } + if ($conditions) { + $query->where($conditions, $types); + } + + return $query; + } + + /** + * Create a new DeleteQuery instance. + * + * @param string|null $table The table to delete rows from. + * @param array $conditions Conditions to be set for the delete statement. + * @param array $types Associative array containing the types to be used for casting. + * @return \Cake\Database\Query\DeleteQuery + */ + public function delete(?string $table = null, array $conditions = [], array $types = []): DeleteQuery + { + $query = (new DeleteQuery($this->connection)) + ->delete($table); + + if ($conditions) { + $query->where($conditions, $types); + } + + return $query; + } +} diff --git a/app/vendor/cakephp/cakephp/src/Database/Query/SelectQuery.php b/app/vendor/cakephp/cakephp/src/Database/Query/SelectQuery.php index 7d2986aba..9e2d6ceba 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Query/SelectQuery.php +++ b/app/vendor/cakephp/cakephp/src/Database/Query/SelectQuery.php @@ -16,79 +16,786 @@ */ namespace Cake\Database\Query; +use ArrayIterator; +use Cake\Core\Exception\CakeException; use Cake\Database\Connection; +use Cake\Database\Expression\IdentifierExpression; +use Cake\Database\Expression\WindowExpression; +use Cake\Database\ExpressionInterface; use Cake\Database\Query; +use Cake\Database\StatementInterface; +use Cake\Database\TypeMap; +use Closure; +use InvalidArgumentException; +use IteratorAggregate; +use Traversable; +use function Cake\Core\deprecationWarning; /** - * Select Query forward compatibility shim. + * This class is used to generate SELECT queries for the relational database. + * + * @template T of mixed + * @implements \IteratorAggregate */ -class SelectQuery extends Query +class SelectQuery extends Query implements IteratorAggregate { /** - * Type of this query (select, insert, update, delete). + * Type of this query. * * @var string */ - protected $_type = 'select'; + protected string $_type = self::TYPE_SELECT; + + /** + * List of SQL parts that will be used to build this query. + * + * @var array + */ + protected array $_parts = [ + 'comment' => null, + 'modifier' => [], + 'with' => [], + 'select' => [], + 'distinct' => false, + 'from' => [], + 'join' => [], + 'where' => null, + 'group' => [], + 'having' => null, + 'window' => [], + 'order' => null, + 'limit' => null, + 'offset' => null, + 'union' => [], + 'epilog' => null, + 'intersect' => [], + ]; + + /** + * A list of callbacks to be called to alter each row from resulting + * statement upon retrieval. Each one of the callback function will receive + * the row array as first argument. + * + * @var array<\Closure> + */ + protected array $_resultDecorators = []; + + /** + * Result set from executed SELECT query. + * + * @var iterable|null + */ + protected ?iterable $_results = null; + + /** + * Boolean for tracking whether buffered results + * are enabled. + * + * @var bool + */ + protected bool $bufferedResults = true; + + /** + * The Type map for fields in the select clause + * + * @var \Cake\Database\TypeMap|null + */ + protected ?TypeMap $_selectTypeMap = null; + + /** + * Tracking flag to disable casting + * + * @var bool + */ + protected bool $typeCastEnabled = true; + + /** + * Executes query and returns set of decorated results. + * + * The results are cached until the query is modified and marked dirty. + * + * @return iterable + * @throws \Cake\Core\Exception\CakeException When query is not a SELECT query. + */ + public function all(): iterable + { + if ($this->_results === null || $this->_dirty) { + $this->_results = $this->execute()->fetchAll(StatementInterface::FETCH_TYPE_ASSOC); + } + + return $this->_results; + } + + /** + * Adds new fields to be returned by a `SELECT` statement when this query is + * executed. Fields can be passed as an array of strings, array of expression + * objects, a single expression or a single string. + * + * If an array is passed, keys will be used to alias fields using the value as the + * real field to be aliased. It is possible to alias strings, Expression objects or + * even other Query objects. + * + * If a callback is passed, the returning array of the function will + * be used as the list of fields. + * + * By default this function will append any passed argument to the list of fields + * to be selected, unless the second argument is set to true. + * + * ### Examples: + * + * ``` + * $query->select(['id', 'title']); // Produces SELECT id, title + * $query->select(['author' => 'author_id']); // Appends author: SELECT id, title, author_id as author + * $query->select('id', true); // Resets the list: SELECT id + * $query->select(['total' => $countQuery]); // SELECT id, (SELECT ...) AS total + * $query->select(function ($query) { + * return ['article_id', 'total' => $query->func()->count('*')]; + * }) + * ``` + * + * By default no fields are selected, if you have an instance of `Cake\ORM\Query` and try to append + * fields you should also call `Cake\ORM\Query::enableAutoFields()` to select the default fields + * from the table. + * + * @param \Cake\Database\ExpressionInterface|\Closure|array|string|float|int $fields fields to be added to the list. + * @param bool $overwrite whether to reset fields with passed list or not + * @return $this + */ + public function select(ExpressionInterface|Closure|array|string|float|int $fields = [], bool $overwrite = false) + { + if (!is_string($fields) && $fields instanceof Closure) { + $fields = $fields($this); + } + + if (!is_array($fields)) { + $fields = [$fields]; + } + + if ($overwrite) { + $this->_parts['select'] = $fields; + } else { + $this->_parts['select'] = array_merge($this->_parts['select'], $fields); + } + + $this->_dirty(); + + return $this; + } + + /** + * Adds a `DISTINCT` clause to the query to remove duplicates from the result set. + * This clause can only be used for select statements. + * + * If you wish to filter duplicates based of those rows sharing a particular field + * or set of fields, you may pass an array of fields to filter on. Beware that + * this option might not be fully supported in all database systems. + * + * ### Examples: + * + * ``` + * // Filters products with the same name and city + * $query->select(['name', 'city'])->from('products')->distinct(); + * + * // Filters products in the same city + * $query->distinct(['city']); + * $query->distinct('city'); + * + * // Filter products with the same name + * $query->distinct(['name'], true); + * $query->distinct('name', true); + * ``` + * + * @param \Cake\Database\ExpressionInterface|array|string|bool $on Enable/disable distinct class + * or list of fields to be filtered on + * @param bool $overwrite whether to reset fields with passed list or not + * @return $this + */ + public function distinct(ExpressionInterface|array|string|bool $on = [], bool $overwrite = false) + { + if ($on === []) { + $on = true; + } elseif (is_string($on)) { + $on = [$on]; + } + + if (is_array($on)) { + $merge = []; + if (is_array($this->_parts['distinct'])) { + $merge = $this->_parts['distinct']; + } + $on = $overwrite ? array_values($on) : array_merge($merge, array_values($on)); + } + + $this->_parts['distinct'] = $on; + $this->_dirty(); + + return $this; + } + + /** + * Adds a single or multiple fields to be used in the GROUP BY clause for this query. + * Fields can be passed as an array of strings, array of expression + * objects, a single expression or a single string. + * + * By default this function will append any passed argument to the list of fields + * to be grouped, unless the second argument is set to true. + * + * ### Examples: + * + * ``` + * // Produces GROUP BY id, title + * $query->groupBy(['id', 'title']); + * + * // Produces GROUP BY title + * $query->groupBy('title'); + * ``` + * + * Group fields are not suitable for use with user supplied data as they are + * not sanitized by the query builder. + * + * @param \Cake\Database\ExpressionInterface|array|string $fields fields to be added to the list + * @param bool $overwrite whether to reset fields with passed list or not + * @return $this + * @deprecated 5.0.0 Use groupBy() instead now that CollectionInterface methods are no longer proxied. + */ + public function group(ExpressionInterface|array|string $fields, bool $overwrite = false) + { + deprecationWarning('5.0.0', 'SelectQuery::group() is deprecated. Use SelectQuery::groupBy() instead.'); + + return $this->groupBy($fields, $overwrite); + } + + /** + * Adds a single or multiple fields to be used in the GROUP BY clause for this query. + * Fields can be passed as an array of strings, array of expression + * objects, a single expression or a single string. + * + * By default this function will append any passed argument to the list of fields + * to be grouped, unless the second argument is set to true. + * + * ### Examples: + * + * ``` + * // Produces GROUP BY id, title + * $query->groupBy(['id', 'title']); + * + * // Produces GROUP BY title + * $query->groupBy('title'); + * ``` + * + * Group fields are not suitable for use with user supplied data as they are + * not sanitized by the query builder. + * + * @param \Cake\Database\ExpressionInterface|array|string $fields fields to be added to the list + * @param bool $overwrite whether to reset fields with passed list or not + * @return $this + */ + public function groupBy(ExpressionInterface|array|string $fields, bool $overwrite = false) + { + if ($overwrite) { + $this->_parts['group'] = []; + } + + if (!is_array($fields)) { + $fields = [$fields]; + } + + $this->_parts['group'] = array_merge($this->_parts['group'], array_values($fields)); + $this->_dirty(); + + return $this; + } + + /** + * Adds a condition or set of conditions to be used in the `HAVING` clause for this + * query. This method operates in exactly the same way as the method `where()` + * does. Please refer to its documentation for an insight on how to using each + * parameter. + * + * Having fields are not suitable for use with user supplied data as they are + * not sanitized by the query builder. + * + * @param \Cake\Database\ExpressionInterface|\Closure|array|string|null $conditions The having conditions. + * @param array $types Associative array of type names used to bind values to query + * @param bool $overwrite whether to reset conditions with passed list or not + * @see \Cake\Database\Query::where() + * @return $this + */ + public function having( + ExpressionInterface|Closure|array|string|null $conditions = null, + array $types = [], + bool $overwrite = false, + ) { + if ($overwrite) { + $this->_parts['having'] = $this->newExpr(); + } + $this->_conjugate('having', $conditions, 'AND', $types); + + return $this; + } /** - * @inheritDoc + * Connects any previously defined set of conditions to the provided list + * using the AND operator in the HAVING clause. This method operates in exactly + * the same way as the method `andWhere()` does. Please refer to its + * documentation for an insight on how to using each parameter. + * + * Having fields are not suitable for use with user supplied data as they are + * not sanitized by the query builder. + * + * @param \Cake\Database\ExpressionInterface|\Closure|array|string $conditions The AND conditions for HAVING. + * @param array $types Associative array of type names used to bind values to query + * @see \Cake\Database\Query::andWhere() + * @return $this */ - public function delete(?string $table = null) + public function andHaving(ExpressionInterface|Closure|array|string $conditions, array $types = []) { - $this->_deprecatedMethod('delete()', 'Create your query with deleteQuery() instead.'); + $this->_conjugate('having', $conditions, 'AND', $types); - return parent::delete($table); + return $this; } /** - * @inheritDoc + * Adds a named window expression. + * + * You are responsible for adding windows in the order your database requires. + * + * @param string $name Window name + * @param \Cake\Database\Expression\WindowExpression|\Closure $window Window expression + * @param bool $overwrite Clear all previous query window expressions + * @return $this */ - public function insert(array $columns, array $types = []) + public function window(string $name, WindowExpression|Closure $window, bool $overwrite = false) { - $this->_deprecatedMethod('insert()', 'Create your query with insertQuery() instead.'); + if ($overwrite) { + $this->_parts['window'] = []; + } + + if ($window instanceof Closure) { + $window = $window(new WindowExpression(), $this); + if (!($window instanceof WindowExpression)) { + throw new CakeException('You must return a `WindowExpression` from a Closure passed to `window()`.'); + } + } - return parent::insert($columns, $types); + $this->_parts['window'][] = ['name' => new IdentifierExpression($name), 'window' => $window]; + $this->_dirty(); + + return $this; } /** - * @inheritDoc + * Set the page of results you want. + * + * This method provides an easier to use interface to set the limit + offset + * in the record set you want as results. If empty the limit will default to + * the existing limit clause, and if that too is empty, then `25` will be used. + * + * Pages must start at 1. + * + * @param int $num The page number you want. + * @param int|null $limit The number of rows you want in the page. If null + * the current limit clause will be used. + * @return $this + * @throws \InvalidArgumentException If page number < 1. */ - public function into(string $table) + public function page(int $num, ?int $limit = null) { - $this->_deprecatedMethod('into()', 'Use from() instead.'); + if ($num < 1) { + throw new InvalidArgumentException('Pages must start at 1.'); + } + if ($limit !== null) { + $this->limit($limit); + } + $limit = $this->clause('limit'); + if ($limit === null) { + $limit = 25; + $this->limit($limit); + } + $offset = ($num - 1) * $limit; + if (PHP_INT_MAX <= $offset) { + $offset = PHP_INT_MAX; + } + $this->offset((int)$offset); - return parent::into($table); + return $this; } /** - * @inheritDoc + * Adds a complete query to be used in conjunction with an UNION operator with + * this query. This is used to combine the result set of this query with the one + * that will be returned by the passed query. You can add as many queries as you + * required by calling multiple times this method with different queries. + * + * By default, the UNION operator will remove duplicate rows, if you wish to include + * every row for all queries, use unionAll(). + * + * ### Examples + * + * ``` + * $union = (new SelectQuery($conn))->select(['id', 'title'])->from(['a' => 'articles']); + * $query->select(['id', 'name'])->from(['d' => 'things'])->union($union); + * ``` + * + * Will produce: + * + * `SELECT id, name FROM things d UNION SELECT id, title FROM articles a` + * + * @param \Cake\Database\Query|string $query full SQL query to be used in UNION operator + * @param bool $overwrite whether to reset the list of queries to be operated or not + * @return $this */ - public function values($data) + public function union(Query|string $query, bool $overwrite = false) { - $this->_deprecatedMethod('values()'); + if ($overwrite) { + $this->_parts['union'] = []; + } + $this->_parts['union'][] = [ + 'all' => false, + 'query' => $query, + ]; + $this->_dirty(); - return parent::values($data); + return $this; } /** - * @inheritDoc + * Adds a complete query to be used in conjunction with the UNION ALL operator with + * this query. This is used to combine the result set of this query with the one + * that will be returned by the passed query. You can add as many queries as you + * required by calling multiple times this method with different queries. + * + * Unlike UNION, UNION ALL will not remove duplicate rows. + * + * ``` + * $union = (new SelectQuery($conn))->select(['id', 'title'])->from(['a' => 'articles']); + * $query->select(['id', 'name'])->from(['d' => 'things'])->unionAll($union); + * ``` + * + * Will produce: + * + * `SELECT id, name FROM things d UNION ALL SELECT id, title FROM articles a` + * + * @param \Cake\Database\Query|string $query full SQL query to be used in UNION operator + * @param bool $overwrite whether to reset the list of queries to be operated or not + * @return $this */ - public function update($table) + public function unionAll(Query|string $query, bool $overwrite = false) { - $this->_deprecatedMethod('update()', 'Create your query with updateQuery() instead.'); + if ($overwrite) { + $this->_parts['union'] = []; + } + $this->_parts['union'][] = [ + 'all' => true, + 'query' => $query, + ]; + $this->_dirty(); - return parent::update($table); + return $this; } /** - * @inheritDoc + * Adds a complete query to be used in conjunction with an INTERSECT operator with + * this query. This is used to combine the result set of this query with the one + * that will be returned by the passed query. You can add as many queries as you + * required by calling multiple times this method with different queries. + * + * By default, the INTERSECT operator will remove duplicate rows, if you wish to include + * every row for all queries, use intersectAll(). + * + * ### Examples + * + * ``` + * $intersect = (new SelectQuery($conn))->select(['id', 'title'])->from(['a' => 'articles']); + * $query->select(['id', 'name'])->from(['d' => 'things'])->intersect($intersect); + * ``` + * + * Will produce: + * + * `SELECT id, name FROM things d INTERSECT SELECT id, title FROM articles a` + * + * @param \Cake\Database\Query|string $query full SQL query to be used in INTERSECT operator + * @param bool $overwrite whether to reset the list of queries to be operated or not + * @return $this + */ + public function intersect(Query|string $query, bool $overwrite = false) + { + if ($overwrite) { + $this->_parts['intersect'] = []; + } + $this->_parts['intersect'][] = [ + 'all' => false, + 'query' => $query, + ]; + $this->_dirty(); + + return $this; + } + + /** + * Adds a complete query to be used in conjunction with the INTERSECT ALL operator with + * this query. This is used to combine the result set of this query with the one + * that will be returned by the passed query. You can add as many queries as you + * required by calling multiple times this method with different queries. + * + * Unlike INTERSECT, INTERSECT ALL will not remove duplicate rows. + * + * ``` + * $intersect = (new SelectQuery($conn))->select(['id', 'title'])->from(['a' => 'articles']); + * $query->select(['id', 'name'])->from(['d' => 'things'])->intersectAll($intersect); + * ``` + * + * Will produce: + * + * `SELECT id, name FROM things d INTERSECT ALL SELECT id, title FROM articles a` + * + * @param \Cake\Database\Query|string $query full SQL query to be used in INTERSECT operator + * @param bool $overwrite whether to reset the list of queries to be operated or not + * @return $this + */ + public function intersectAll(Query|string $query, bool $overwrite = false) + { + if ($overwrite) { + $this->_parts['intersect'] = []; + } + $this->_parts['intersect'][] = [ + 'all' => true, + 'query' => $query, + ]; + $this->_dirty(); + + return $this; + } + + /** + * Executes this query and returns a results iterator. This function is required + * for implementing the IteratorAggregate interface and allows the query to be + * iterated without having to call all() manually, thus making it look like + * a result set instead of the query itself. + * + * @return \Traversable + */ + public function getIterator(): Traversable + { + if ($this->bufferedResults) { + /** @var \Traversable|array $results */ + $results = $this->all(); + if (is_array($results)) { + return new ArrayIterator($results); + } + + return $results; + } + + return $this->execute(); + } + + /** + * Registers a callback to be executed for each result that is fetched from the + * result set, the callback function will receive as first parameter an array with + * the raw data from the database for every row that is fetched and must return the + * row with any possible modifications. + * + * Callbacks will be executed lazily, if only 3 rows are fetched for database it will + * be called 3 times, event though there might be more rows to be fetched in the cursor. + * + * Callbacks are stacked in the order they are registered, if you wish to reset the stack + * the call this function with the second parameter set to true. + * + * If you wish to remove all decorators from the stack, set the first parameter + * to null and the second to true. + * + * ### Example + * + * ``` + * $query->decorateResults(function ($row) { + * $row['order_total'] = $row['subtotal'] + ($row['subtotal'] * $row['tax']); + * return $row; + * }); + * ``` + * + * @param \Closure|null $callback The callback to invoke when results are fetched. + * @param bool $overwrite Whether this should append or replace all existing decorators. + * @return $this + */ + public function decorateResults(?Closure $callback, bool $overwrite = false) + { + $this->_dirty(); + if ($overwrite) { + $this->_resultDecorators = []; + } + + if ($callback !== null) { + $this->_resultDecorators[] = $callback; + } + + return $this; + } + + /** + * Get result decorators. + * + * @return array + */ + public function getResultDecorators(): array + { + return $this->_resultDecorators; + } + + /** + * Enables buffered results. + * + * When enabled the results returned by this query will be + * buffered. This enables you to iterate a result set multiple times, or + * both cache and iterate it. + * + * When disabled it will consume less memory as fetched results are not + * remembered for future iterations. + * + * @return $this + */ + public function enableBufferedResults() + { + $this->_dirty(); + $this->bufferedResults = true; + + return $this; + } + + /** + * Disables buffered results. + * + * Disabling buffering will consume less memory as fetched results are not + * remembered for future iterations. + * + * @return $this + */ + public function disableBufferedResults() + { + $this->_dirty(); + $this->bufferedResults = false; + + return $this; + } + + /** + * Returns whether buffered results are enabled/disabled. + * + * When enabled the results returned by this query will be + * buffered. This enables you to iterate a result set multiple times, or + * both cache and iterate it. + * + * When disabled it will consume less memory as fetched results are not + * remembered for future iterations. + * + * @return bool + */ + public function isBufferedResultsEnabled(): bool + { + return $this->bufferedResults; + } + + /** + * Sets the TypeMap class where the types for each of the fields in the + * select clause are stored. + * + * @param \Cake\Database\TypeMap|array $typeMap Creates a TypeMap if array, otherwise sets the given TypeMap. + * @return $this + */ + public function setSelectTypeMap(TypeMap|array $typeMap) + { + $this->_selectTypeMap = is_array($typeMap) ? new TypeMap($typeMap) : $typeMap; + $this->_dirty(); + + return $this; + } + + /** + * Gets the TypeMap class where the types for each of the fields in the + * select clause are stored. + * + * @return \Cake\Database\TypeMap + */ + public function getSelectTypeMap(): TypeMap + { + return $this->_selectTypeMap ??= new TypeMap(); + } + + /** + * Disables result casting. + * + * When disabled, the fields will be returned as received from the database + * driver (which in most environments means they are being returned as + * strings), which can improve performance with larger datasets. + * + * @return $this + */ + public function disableResultsCasting() + { + $this->typeCastEnabled = false; + + return $this; + } + + /** + * Enables result casting. + * + * When enabled, the fields in the results returned by this Query will be + * cast to their corresponding PHP data type. + * + * @return $this + */ + public function enableResultsCasting() + { + $this->typeCastEnabled = true; + + return $this; + } + + /** + * Returns whether result casting is enabled/disabled. + * + * When enabled, the fields in the results returned by this Query will be + * casted to their corresponding PHP data type. + * + * When disabled, the fields will be returned as received from the database + * driver (which in most environments means they are being returned as + * strings), which can improve performance with larger datasets. + * + * @return bool + */ + public function isResultsCastingEnabled(): bool + { + return $this->typeCastEnabled; + } + + /** + * Handles clearing iterator and cloning all expressions and value binders. + * + * @return void + */ + public function __clone() + { + parent::__clone(); + + $this->_results = null; + if ($this->_selectTypeMap !== null) { + $this->_selectTypeMap = clone $this->_selectTypeMap; + } + } + + /** + * Returns an array that can be used to describe the internal state of this + * object. + * + * @return array */ - public function set($key, $value = null, $types = []) + public function __debugInfo(): array { - $this->_deprecatedMethod('set()'); + $return = parent::__debugInfo(); + $return['decorators'] = count($this->_resultDecorators); - return parent::set($key, $value, $types); + return $return; } /** diff --git a/app/vendor/cakephp/cakephp/src/Database/Query/UpdateQuery.php b/app/vendor/cakephp/cakephp/src/Database/Query/UpdateQuery.php index 0f84cce6c..4f72902d6 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Query/UpdateQuery.php +++ b/app/vendor/cakephp/cakephp/src/Database/Query/UpdateQuery.php @@ -16,167 +16,134 @@ */ namespace Cake\Database\Query; +use Cake\Database\Expression\ComparisonExpression; +use Cake\Database\Expression\QueryExpression; +use Cake\Database\ExpressionInterface; use Cake\Database\Query; +use Closure; /** - * Update Query forward compatibility shim. + * This class is used to generate UPDATE queries for the relational database. */ class UpdateQuery extends Query { /** - * Type of this query (select, insert, update, delete). + * Type of this query. * * @var string */ - protected $_type = 'update'; + protected string $_type = self::TYPE_UPDATE; /** - * @inheritDoc - */ - public function delete(?string $table = null) - { - $this->_deprecatedMethod('delete()', 'Create your query with deleteQuery() instead.'); - - return parent::delete($table); - } - - /** - * @inheritDoc - */ - public function select($fields = [], bool $overwrite = false) - { - $this->_deprecatedMethod('select()', 'Create your query with selectQuery() instead.'); - - return parent::select($fields, $overwrite); - } - - /** - * @inheritDoc - */ - public function distinct($on = [], $overwrite = false) - { - $this->_deprecatedMethod('distinct()'); - - return parent::distinct($on, $overwrite); - } - - /** - * @inheritDoc - */ - public function modifier($modifiers, $overwrite = false) - { - $this->_deprecatedMethod('modifier()'); - - return parent::modifier($modifiers, $overwrite); - } - - /** - * @inheritDoc - */ - public function group($fields, $overwrite = false) - { - $this->_deprecatedMethod('group()'); - - return parent::group($fields, $overwrite); - } - - /** - * @inheritDoc - */ - public function having($conditions = null, $types = [], $overwrite = false) - { - $this->_deprecatedMethod('having()'); - - return parent::having($conditions, $types, $overwrite); - } - - /** - * @inheritDoc - */ - public function andHaving($conditions, $types = []) - { - $this->_deprecatedMethod('andHaving()'); - - return parent::andHaving($conditions, $types); - } - - /** - * @inheritDoc - */ - public function page(int $num, ?int $limit = null) - { - $this->_deprecatedMethod('page()'); - - return parent::page($num, $limit); - } - - /** - * @inheritDoc - */ - public function offset($offset) - { - $this->_deprecatedMethod('offset()'); - - return parent::offset($offset); - } - - /** - * @inheritDoc - */ - public function union($query, $overwrite = false) - { - $this->_deprecatedMethod('union()'); - - return parent::union($query, $overwrite); - } - - /** - * @inheritDoc - */ - public function unionAll($query, $overwrite = false) - { - $this->_deprecatedMethod('union()'); - - return parent::unionAll($query, $overwrite); - } - - /** - * @inheritDoc - */ - public function insert(array $columns, array $types = []) - { - $this->_deprecatedMethod('insert()', 'Create your query with insertQuery() instead.'); - - return parent::insert($columns, $types); - } - - /** - * @inheritDoc - */ - public function into(string $table) - { - $this->_deprecatedMethod('into()', 'Use update() instead.'); - - return parent::into($table); - } - - /** - * @inheritDoc + * List of SQL parts that will be used to build this query. + * + * @var array + */ + protected array $_parts = [ + 'comment' => null, + 'with' => [], + 'update' => [], + 'modifier' => [], + 'join' => [], + 'set' => [], + 'where' => null, + 'order' => null, + 'limit' => null, + 'epilog' => null, + ]; + + /** + * Create an update query. + * + * Can be combined with set() and where() methods to create update queries. + * + * @param \Cake\Database\ExpressionInterface|string $table The table you want to update. + * @return $this */ - public function values($data) + public function update(ExpressionInterface|string $table) { - $this->_deprecatedMethod('values()'); + $this->_dirty(); + $this->_parts['update'][0] = $table; - return parent::values($data); + return $this; } /** - * @inheritDoc - */ - public function from($tables = [], $overwrite = false) + * Set one or many fields to update. + * + * ### Examples + * + * Passing a string: + * + * ``` + * $query->update('articles')->set('title', 'The Title'); + * ``` + * + * Passing an array: + * + * ``` + * $query->update('articles')->set(['title' => 'The Title'], ['title' => 'string']); + * ``` + * + * Passing a callback: + * + * ``` + * $query->update('articles')->set(function (ExpressionInterface $exp) { + * return $exp->eq('title', 'The title', 'string'); + * }); + * ``` + * + * @param \Cake\Database\Expression\QueryExpression|\Closure|array|string $key The column name or array of keys + * + values to set. This can also be a QueryExpression containing a SQL fragment. + * It can also be a Closure, that is required to return an expression object. + * @param mixed $value The value to update $key to. Can be null if $key is an + * array or QueryExpression. When $key is an array, this parameter will be + * used as $types instead. + * @param array|string $types The column types to treat data as. + * @return $this + */ + public function set(QueryExpression|Closure|array|string $key, mixed $value = null, array|string $types = []) { - $this->_deprecatedMethod('from()', 'Use update() instead.'); - - return parent::from($tables, $overwrite); + if (empty($this->_parts['set'])) { + $this->_parts['set'] = $this->newExpr()->setConjunction(','); + } + + if ($key instanceof Closure) { + $exp = $this->newExpr()->setConjunction(','); + /** @var \Cake\Database\Expression\QueryExpression $setExpr */ + $setExpr = $this->_parts['set']; + $setExpr->add($key($exp)); + + return $this; + } + + if (is_array($key) && !isset($key[0])) { + $typeMap = $this->getTypeMap()->setTypes($value ?? []); + /** @var \Cake\Database\Expression\QueryExpression $setExpr */ + $setExpr = $this->_parts['set']; + foreach ($key as $k => $v) { + $setExpr->add(new ComparisonExpression($k, $v, $typeMap->type($k))); + } + + return $this; + } + + if (is_array($key) || $key instanceof ExpressionInterface) { + $types = (array)$value; + /** @var \Cake\Database\Expression\QueryExpression $setExpr */ + $setExpr = $this->_parts['set']; + $setExpr->add($key, $types); + + return $this; + } + + if (!is_string($types)) { + $types = null; + } + /** @var \Cake\Database\Expression\QueryExpression $setExpr */ + $setExpr = $this->_parts['set']; + $setExpr->eq($key, $value, $types); + + return $this; } } diff --git a/app/vendor/cakephp/cakephp/src/Database/QueryCompiler.php b/app/vendor/cakephp/cakephp/src/Database/QueryCompiler.php index 9ed44223e..f4a44bc12 100644 --- a/app/vendor/cakephp/cakephp/src/Database/QueryCompiler.php +++ b/app/vendor/cakephp/cakephp/src/Database/QueryCompiler.php @@ -34,7 +34,7 @@ class QueryCompiler * * @var array */ - protected $_templates = [ + protected array $_templates = [ 'delete' => 'DELETE', 'where' => ' WHERE %s', 'group' => ' GROUP BY %s ', @@ -43,6 +43,7 @@ class QueryCompiler 'limit' => ' LIMIT %s', 'offset' => ' OFFSET %s', 'epilog' => ' %s', + 'comment' => '/* %s */ ', ]; /** @@ -50,9 +51,9 @@ class QueryCompiler * * @var array */ - protected $_selectParts = [ - 'with', 'select', 'from', 'join', 'where', 'group', 'having', 'window', 'order', - 'limit', 'offset', 'union', 'epilog', + protected array $_selectParts = [ + 'comment', 'with', 'select', 'from', 'join', 'where', 'group', 'having', 'window', 'order', + 'limit', 'offset', 'union', 'epilog', 'intersect', ]; /** @@ -60,37 +61,28 @@ class QueryCompiler * * @var array */ - protected $_updateParts = ['with', 'update', 'set', 'where', 'epilog']; + protected array $_updateParts = ['comment', 'with', 'update', 'set', 'where', 'epilog']; /** * The list of query clauses to traverse for generating a DELETE statement * * @var array */ - protected $_deleteParts = ['with', 'delete', 'modifier', 'from', 'where', 'epilog']; + protected array $_deleteParts = ['comment', 'with', 'delete', 'modifier', 'from', 'where', 'epilog']; /** * The list of query clauses to traverse for generating an INSERT statement * * @var array */ - protected $_insertParts = ['with', 'insert', 'values', 'epilog']; - - /** - * Indicate whether this query dialect supports ordered unions. - * - * Overridden in subclasses. - * - * @var bool - */ - protected $_orderedUnion = true; + protected array $_insertParts = ['comment', 'with', 'insert', 'values', 'epilog']; /** * Indicate whether aliases in SELECT clause need to be always quoted. * * @var bool */ - protected $_quotedSelectAliases = false; + protected bool $_quotedSelectAliases = false; /** * Returns the SQL representation of the provided query after generating @@ -106,7 +98,7 @@ public function compile(Query $query, ValueBinder $binder): string $type = $query->type(); $query->traverseParts( $this->_sqlCompiler($sql, $query, $binder), - $this->{"_{$type}Parts"} + $this->{"_{$type}Parts"}, ); // Propagate bound parameters from sub-queries if the @@ -124,7 +116,7 @@ public function compile(Query $query, ValueBinder $binder): string } /** - * Returns a callable object that can be used to compile a SQL string representation + * Returns a closure that can be used to compile a SQL string representation * of this query. * * @param string $sql initial sql string to append to @@ -134,10 +126,10 @@ public function compile(Query $query, ValueBinder $binder): string */ protected function _sqlCompiler(string &$sql, Query $query, ValueBinder $binder): Closure { - return function ($part, $partName) use (&$sql, $query, $binder) { + return function ($part, $partName) use (&$sql, $query, $binder): void { if ( $part === null || - (is_array($part) && empty($part)) || + ($part === []) || ($part instanceof Countable && count($part) === 0) ) { return; @@ -152,7 +144,6 @@ protected function _sqlCompiler(string &$sql, Query $query, ValueBinder $binder) return; } - $sql .= $this->{'_build' . $partName . 'Part'}($part, $query, $binder); }; } @@ -162,7 +153,7 @@ protected function _sqlCompiler(string &$sql, Query $query, ValueBinder $binder) * it constructs the CTE definitions list and generates the `RECURSIVE` * keyword when required. * - * @param array $parts List of CTEs to be transformed to string + * @param array<\Cake\Database\Expression\CommonTableExpression> $parts List of CTEs to be transformed to string * @param \Cake\Database\Query $query The query that is being compiled * @param \Cake\Database\ValueBinder $binder Value binder used to generate parameter placeholder * @return string @@ -194,20 +185,23 @@ protected function _buildWithPart(array $parts, Query $query, ValueBinder $binde */ protected function _buildSelectPart(array $parts, Query $query, ValueBinder $binder): string { + $driver = $query->getConnection()->getDriver($query->getConnectionRole()); $select = 'SELECT%s %s%s'; - if ($this->_orderedUnion && $query->clause('union')) { + if ( + ($query->clause('union') || $query->clause('intersect')) && + $driver->supports(DriverFeatureEnum::SET_OPERATIONS_ORDER_BY) + ) { $select = '(SELECT%s %s%s'; } $distinct = $query->clause('distinct'); $modifiers = $this->_buildModifierPart($query->clause('modifier'), $query, $binder); - $driver = $query->getConnection()->getDriver($query->getConnectionRole()); $quoteIdentifiers = $driver->isAutoQuotingEnabled() || $this->_quotedSelectAliases; $normalized = []; $parts = $this->_stringifyExpressions($parts, $binder); foreach ($parts as $k => $p) { if (!is_numeric($k)) { - $p = $p . ' AS '; + $p .= ' AS '; if ($quoteIdentifiers) { $p .= $driver->quoteIdentifier($k); } else { @@ -273,7 +267,7 @@ protected function _buildJoinPart(array $parts, Query $query, ValueBinder $binde throw new DatabaseException(sprintf( 'Could not compile join clause for alias `%s`. No table was specified. ' . 'Use the `table` key to define a table.', - $join['alias'] + $join['alias'], )); } if ($join['table'] instanceof ExpressionInterface) { @@ -308,7 +302,11 @@ protected function _buildWindowPart(array $parts, Query $query, ValueBinder $bin { $windows = []; foreach ($parts as $window) { - $windows[] = $window['name']->sql($binder) . ' AS (' . $window['window']->sql($binder) . ')'; + /** @var \Cake\Database\Expression\IdentifierExpression $expr */ + $expr = $window['name']; + /** @var \Cake\Database\Expression\IdentifierExpression $windowExpr */ + $windowExpr = $window['window']; + $windows[] = $expr->sql($binder) . ' AS (' . $windowExpr->sql($binder) . ')'; } return ' WINDOW ' . implode(', ', $windows); @@ -317,7 +315,7 @@ protected function _buildWindowPart(array $parts, Query $query, ValueBinder $bin /** * Helper function to generate SQL for SET expressions. * - * @param array $parts List of keys & values to set. + * @param array $parts List of keys and values to set. * @param \Cake\Database\Query $query The query that is being compiled * @param \Cake\Database\ValueBinder $binder Value binder used to generate parameter placeholder * @return string @@ -329,7 +327,7 @@ protected function _buildSetPart(array $parts, Query $query, ValueBinder $binder if ($part instanceof ExpressionInterface) { $part = $part->sql($binder); } - if ($part[0] === '(') { + if (str_starts_with($part, '(')) { $part = substr($part, 1, -1); } $set[] = $part; @@ -339,33 +337,75 @@ protected function _buildSetPart(array $parts, Query $query, ValueBinder $binder } /** - * Builds the SQL string for all the UNION clauses in this query, when dealing + * Builds the SQL string for all the `operation` clauses in this query, when dealing * with query objects it will also transform them using their configured SQL * dialect. * - * @param array $parts list of queries to be operated with UNION - * @param \Cake\Database\Query $query The query that is being compiled - * @param \Cake\Database\ValueBinder $binder Value binder used to generate parameter placeholder + * @param string $operation + * @param array $parts + * @param \Cake\Database\Query $query + * @param \Cake\Database\ValueBinder $binder * @return string */ - protected function _buildUnionPart(array $parts, Query $query, ValueBinder $binder): string - { - $parts = array_map(function ($p) use ($binder) { - $p['query'] = $p['query']->sql($binder); - $p['query'] = $p['query'][0] === '(' ? trim($p['query'], '()') : $p['query']; + protected function _buildSetOperationPart( + string $operation, + array $parts, + Query $query, + ValueBinder $binder, + ): string { + $setOperationsOrderBy = $query + ->getConnection() + ->getDriver($query->getConnectionRole()) + ->supports(DriverFeatureEnum::SET_OPERATIONS_ORDER_BY); + + $parts = array_map(function ($p) use ($binder, $setOperationsOrderBy) { + /** @var \Cake\Database\Expression\IdentifierExpression $expr */ + $expr = $p['query']; + $p['query'] = $expr->sql($binder); + $p['query'] = str_starts_with($p['query'], '(') ? trim($p['query'], '()') : $p['query']; $prefix = $p['all'] ? 'ALL ' : ''; - if ($this->_orderedUnion) { + if ($setOperationsOrderBy) { return "{$prefix}({$p['query']})"; } return $prefix . $p['query']; }, $parts); - if ($this->_orderedUnion) { - return sprintf(")\nUNION %s", implode("\nUNION ", $parts)); + if ($setOperationsOrderBy) { + return sprintf(")\n$operation %s", implode("\n$operation ", $parts)); } - return sprintf("\nUNION %s", implode("\nUNION ", $parts)); + return sprintf("\n$operation %s", implode("\n$operation ", $parts)); + } + + /** + * Builds the SQL string for all the INTERSECT clauses in this query, when dealing + * with query objects it will also transform them using their configured SQL + * dialect. + * + * @param array $parts list of queries to be operated with INTERSECT + * @param \Cake\Database\Query $query The query that is being compiled + * @param \Cake\Database\ValueBinder $binder Value binder used to generate parameter placeholder + * @return string + */ + protected function _buildIntersectPart(array $parts, Query $query, ValueBinder $binder): string + { + return $this->_buildSetOperationPart('INTERSECT', $parts, $query, $binder); + } + + /** + * Builds the SQL string for all the UNION clauses in this query, when dealing + * with query objects it will also transform them using their configured SQL + * dialect. + * + * @param array $parts list of queries to be operated with UNION + * @param \Cake\Database\Query $query The query that is being compiled + * @param \Cake\Database\ValueBinder $binder Value binder used to generate parameter placeholder + * @return string + */ + protected function _buildUnionPart(array $parts, Query $query, ValueBinder $binder): string + { + return $this->_buildSetOperationPart('UNION', $parts, $query, $binder); } /** @@ -381,7 +421,7 @@ protected function _buildInsertPart(array $parts, Query $query, ValueBinder $bin if (!isset($parts[0])) { throw new DatabaseException( 'Could not compile insert query. No table was specified. ' . - 'Use `into()` to define a table.' + 'Use `into()` to define a table.', ); } $table = $parts[0]; diff --git a/app/vendor/cakephp/cakephp/src/Database/README.md b/app/vendor/cakephp/cakephp/src/Database/README.md index d7bbe8eb1..46806ecf7 100644 --- a/app/vendor/cakephp/cakephp/src/Database/README.md +++ b/app/vendor/cakephp/cakephp/src/Database/README.md @@ -69,8 +69,9 @@ This is a list of possible options that can be passed when creating a connection ## Using connections After creating a connection, you can immediately interact with the database. You can choose -either to use the shorthand methods `execute()`, `insert()`, `update()`, `delete()` or use the -`newQuery()` for using a query builder. +either to use the shorthand methods `execute()`, `insert()`, `update()`, `delete()` or use +one of `selectQuery()`, `updateQuery()`, `insertQuery()` or `deleteQuery()` +to get a query builder for particular type of query. The easiest way of executing queries is by using the `execute()` method, it will return a `Cake\Database\StatementInterface` that you can use to get the data back: @@ -78,7 +79,7 @@ The easiest way of executing queries is by using the `execute()` method, it will ```php $statement = $connection->execute('SELECT * FROM articles'); -while($row = $statement->fetch('assoc')) { +while($row = $statement->fetch(\PDO::FETCH_ASSOC)) { echo $row['title'] . PHP_EOL; } ``` @@ -86,7 +87,7 @@ Binding values to parametrized arguments is also possible with the execute funct ```php $statement = $connection->execute('SELECT * FROM articles WHERE id = :id', ['id' => 1], ['id' => 'integer']); -$results = $statement->fetch('assoc'); +$results = $statement->fetch(\PDO::FETCH_ASSOC); ``` The third parameter is the types the passed values should be converted to when passed to the database. If @@ -97,7 +98,7 @@ Alternatively you can construct a statement manually and then fetch rows from it ```php $statement = $connection->prepare('SELECT * from articles WHERE id != :id'); $statement->bind(['id' => 1], ['id' => 'integer']); -$results = $statement->fetchAll('assoc'); +$results = $statement->fetchAll(\PDO::FETCH_ASSOC); ``` The default types that are understood by this library and can be passed to the `bind()` function or to `execute()` @@ -121,10 +122,10 @@ Statements can be reused by binding new values to the parameters in the query: ```php $statement = $connection->prepare('SELECT * from articles WHERE id = :id'); $statement->bind(['id' => 1], ['id' => 'integer']); -$results = $statement->fetchAll('assoc'); +$results = $statement->fetchAll(\PDO::FETCH_ASSOC); $statement->bind(['id' => 1], ['id' => 'integer']); -$results = $statement->fetchAll('assoc'); +$results = $statement->fetchAll(\PDO::FETCH_ASSOC); ``` ### Updating Rows @@ -190,7 +191,7 @@ One of the goals of this library is to allow the generation of both simple and c ease. The query builder can be accessed by getting a new instance of a query: ```php -$query = $connection->newQuery(); +$query = $connection->selectQuery(); ``` ### Selecting Fields @@ -217,7 +218,7 @@ Generating conditions: // WHERE id = 1 $query->where(['id' => 1]); -// WHERE id > 2 +// WHERE id > 1 $query->where(['id >' => 1]); ``` @@ -240,7 +241,7 @@ $query->where(['OR' => ['id >' => 1, 'title' => 'My title']]); For even more complex conditions you can use closures and expression objects: ```php -$query->where(function ($exp) { +$query->where(function (ExpressionInterface $exp) { return $exp ->eq('author_id', 2) ->eq('published', true) @@ -263,7 +264,7 @@ WHERE Combining expressions is also possible: ```php -$query->where(function ($exp) { +$query->where(function (ExpressionInterface $exp) { $orConditions = $exp->or(['author_id' => 2]) ->eq('author_id', 5); return $exp @@ -349,10 +350,10 @@ foreach ($query as $row) { } // Get the statement and fetch all results -$results = $query->execute()->fetchAll('assoc'); +$results = $query->execute()->fetchAll(\PDO::FETCH_ASSOC); ``` ## Official API -You can read the official [official API docs](https://api.cakephp.org/4.x/namespace-Cake.Database.html) to learn more of what this library +You can read the official [official API docs](https://api.cakephp.org/5.x/namespace-Cake.Database.html) to learn more of what this library has to offer. diff --git a/app/vendor/cakephp/cakephp/src/Database/Retry/ErrorCodeWaitStrategy.php b/app/vendor/cakephp/cakephp/src/Database/Retry/ErrorCodeWaitStrategy.php index 010a531ef..963b30b29 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Retry/ErrorCodeWaitStrategy.php +++ b/app/vendor/cakephp/cakephp/src/Database/Retry/ErrorCodeWaitStrategy.php @@ -30,12 +30,12 @@ class ErrorCodeWaitStrategy implements RetryStrategyInterface /** * @var array */ - protected $errorCodes; + protected array $errorCodes; /** * @var int */ - protected $retryInterval; + protected int $retryInterval; /** * @param array $errorCodes DB-specific error codes that allow retrying diff --git a/app/vendor/cakephp/cakephp/src/Database/Retry/ReconnectStrategy.php b/app/vendor/cakephp/cakephp/src/Database/Retry/ReconnectStrategy.php index 25fb69f80..78fd336a1 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Retry/ReconnectStrategy.php +++ b/app/vendor/cakephp/cakephp/src/Database/Retry/ReconnectStrategy.php @@ -35,7 +35,7 @@ class ReconnectStrategy implements RetryStrategyInterface * * @var array */ - protected static $causes = [ + protected static array $causes = [ 'gone away', 'Lost connection', 'Transaction() on null', @@ -57,7 +57,7 @@ class ReconnectStrategy implements RetryStrategyInterface * * @var \Cake\Database\Connection */ - protected $connection; + protected Connection $connection; /** * Creates the ReconnectStrategy object by storing a reference to the @@ -82,7 +82,7 @@ public function shouldRetry(Exception $exception, int $retryCount): bool $message = $exception->getMessage(); foreach (static::$causes as $cause) { - if (strstr($message, $cause) !== false) { + if (str_contains($message, $cause)) { return $this->reconnect(); } } @@ -105,17 +105,18 @@ protected function reconnect(): bool try { // Make sure we free any resources associated with the old connection $this->connection->getDriver()->disconnect(); - } catch (Exception $e) { + } catch (Exception) { } try { $this->connection->getDriver()->connect(); - if ($this->connection->isQueryLoggingEnabled()) { - $this->connection->log('[RECONNECT]'); - } + $this->connection->getDriver()->log( + 'connection={connection} [RECONNECT]', + ['connection' => $this->connection->configName()], + ); return true; - } catch (Exception $e) { + } catch (Exception) { // If there was an error connecting again, don't report it back, // let the retry handler do it. return false; diff --git a/app/vendor/cakephp/cakephp/src/Database/Schema/BaseSchema.php b/app/vendor/cakephp/cakephp/src/Database/Schema/BaseSchema.php deleted file mode 100644 index 8d32d3448..000000000 --- a/app/vendor/cakephp/cakephp/src/Database/Schema/BaseSchema.php +++ /dev/null @@ -1,10 +0,0 @@ - $options The options to use, see above. + * @return \Cake\Database\Schema\TableSchemaInterface Object with column metadata. + * @throws \Cake\Database\Exception\DatabaseException when table cannot be described. */ public function describe(string $name, array $options = []): TableSchemaInterface { diff --git a/app/vendor/cakephp/cakephp/src/Database/Schema/Collection.php b/app/vendor/cakephp/cakephp/src/Database/Schema/Collection.php index 181ef24be..75e15369b 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Schema/Collection.php +++ b/app/vendor/cakephp/cakephp/src/Database/Schema/Collection.php @@ -17,14 +17,14 @@ namespace Cake\Database\Schema; use Cake\Database\Connection; -use Cake\Database\Exception\DatabaseException; -use PDOException; /** * Represents a database schema collection * - * Used to access information about the tables, - * and other data in a database. + * Gives a simple high-level schema reflection API that can be + * decorated or extended with additional behavior like caching. + * + * @see \Cake\Database\Schema\SchemaDialect For lower level schema reflection API */ class Collection implements CollectionInterface { @@ -33,14 +33,14 @@ class Collection implements CollectionInterface * * @var \Cake\Database\Connection */ - protected $_connection; + protected Connection $_connection; /** * Schema dialect instance. * - * @var \Cake\Database\Schema\SchemaDialect + * @var \Cake\Database\Schema\SchemaDialect|null */ - protected $_dialect; + protected ?SchemaDialect $_dialect = null; /** * Constructor. @@ -50,7 +50,6 @@ class Collection implements CollectionInterface public function __construct(Connection $connection) { $this->_connection = $connection; - $this->_dialect = $connection->getDriver()->schemaDialect(); } /** @@ -60,15 +59,7 @@ public function __construct(Connection $connection) */ public function listTablesWithoutViews(): array { - [$sql, $params] = $this->_dialect->listTablesWithoutViewsSql($this->_connection->getDriver()->config()); - $result = []; - $statement = $this->_connection->execute($sql, $params); - while ($row = $statement->fetch()) { - $result[] = $row[0]; - } - $statement->closeCursor(); - - return $result; + return $this->getDialect()->listTablesWithoutViews(); } /** @@ -78,15 +69,7 @@ public function listTablesWithoutViews(): array */ public function listTables(): array { - [$sql, $params] = $this->_dialect->listTablesSql($this->_connection->getDriver()->config()); - $result = []; - $statement = $this->_connection->execute($sql, $params); - while ($row = $statement->fetch()) { - $result[] = $row[0]; - } - $statement->closeCursor(); - - return $result; + return $this->getDialect()->listTables(); } /** @@ -94,75 +77,23 @@ public function listTables(): array * * The name can include a database schema name in the form 'schema.table'. * - * Caching will be applied if `cacheMetadata` key is present in the Connection - * configuration options. Defaults to _cake_model_ when true. - * - * ### Options - * - * - `forceRefresh` - Set to true to force rebuilding the cached metadata. - * Defaults to false. - * * @param string $name The name of the table to describe. - * @param array $options The options to use, see above. - * @return \Cake\Database\Schema\TableSchema Object with column metadata. + * @param array $options Unused + * @return \Cake\Database\Schema\TableSchemaInterface Object with column metadata. * @throws \Cake\Database\Exception\DatabaseException when table cannot be described. */ public function describe(string $name, array $options = []): TableSchemaInterface { - $config = $this->_connection->getDriver()->config(); - if (strpos($name, '.')) { - [$config['schema'], $name] = explode('.', $name); - } - $table = $this->_connection->getDriver()->newTableSchema($name); - - $this->_reflect('Column', $name, $config, $table); - if (count($table->columns()) === 0) { - throw new DatabaseException(sprintf('Cannot describe %s. It has 0 columns.', $name)); - } - - $this->_reflect('Index', $name, $config, $table); - $this->_reflect('ForeignKey', $name, $config, $table); - $this->_reflect('Options', $name, $config, $table); - - return $table; + return $this->getDialect()->describe($name); } /** - * Helper method for running each step of the reflection process. + * Setups the schema dialect to be used for this collection. * - * @param string $stage The stage name. - * @param string $name The table name. - * @param array $config The config data. - * @param \Cake\Database\Schema\TableSchema $schema The table schema instance. - * @return void - * @throws \Cake\Database\Exception\DatabaseException on query failure. - * @uses \Cake\Database\Schema\SchemaDialect::describeColumnSql - * @uses \Cake\Database\Schema\SchemaDialect::describeIndexSql - * @uses \Cake\Database\Schema\SchemaDialect::describeForeignKeySql - * @uses \Cake\Database\Schema\SchemaDialect::describeOptionsSql - * @uses \Cake\Database\Schema\SchemaDialect::convertColumnDescription - * @uses \Cake\Database\Schema\SchemaDialect::convertIndexDescription - * @uses \Cake\Database\Schema\SchemaDialect::convertForeignKeyDescription - * @uses \Cake\Database\Schema\SchemaDialect::convertOptionsDescription + * @return \Cake\Database\Schema\SchemaDialect */ - protected function _reflect(string $stage, string $name, array $config, TableSchema $schema): void + protected function getDialect(): SchemaDialect { - $describeMethod = "describe{$stage}Sql"; - $convertMethod = "convert{$stage}Description"; - - [$sql, $params] = $this->_dialect->{$describeMethod}($name, $config); - if (empty($sql)) { - return; - } - try { - $statement = $this->_connection->execute($sql, $params); - } catch (PDOException $e) { - throw new DatabaseException($e->getMessage(), 500, $e); - } - /** @psalm-suppress PossiblyFalseIterator */ - foreach ($statement->fetchAll('assoc') as $row) { - $this->_dialect->{$convertMethod}($schema, $row); - } - $statement->closeCursor(); + return $this->_dialect ??= $this->_connection->getDriver()->schemaDialect(); } } diff --git a/app/vendor/cakephp/cakephp/src/Database/Schema/MysqlSchema.php b/app/vendor/cakephp/cakephp/src/Database/Schema/MysqlSchema.php deleted file mode 100644 index 30b20db6e..000000000 --- a/app/vendor/cakephp/cakephp/src/Database/Schema/MysqlSchema.php +++ /dev/null @@ -1,10 +0,0 @@ -_driver->quoteIdentifier($config['database']), []]; + return [ + 'SHOW FULL TABLES FROM ' . $this->_driver->quoteIdentifier($config['database']) + . " WHERE TABLE_TYPE IN ('BASE TABLE', 'VIEW')" + , []]; } /** @@ -65,7 +62,132 @@ public function listTablesWithoutViewsSql(array $config): array */ public function describeColumnSql(string $tableName, array $config): array { - return ['SHOW FULL COLUMNS FROM ' . $this->_driver->quoteIdentifier($tableName), []]; + $sql = $this->describeColumnQuery($tableName); + + return [$sql, []]; + } + + /** + * Helper method for creating SQL to describe columns in a table. + * + * @param string $tableName The table to describe. + * @return string SQL to reflect columns + */ + private function describeColumnQuery(string $tableName): string + { + return 'SHOW FULL COLUMNS FROM ' . $this->_driver->quoteIdentifier($tableName); + } + + /** + * Split a table name into a tuple of database, table + * If the table does not have a database name included, the connection + * database will be used. + * + * @param string $tableName The table name to split + * @return array A tuple of [database, tablename] + */ + private function splitTableName(string $tableName): array + { + $config = $this->_driver->config(); + $db = $config['database']; + if (str_contains($tableName, '.')) { + return explode('.', $tableName); + } + + return [$db, $tableName]; + } + + /** + * Get a list of column metadata as a array + * + * Each item in the array will contain the following: + * + * - name : the name of the column. + * - type : the abstract type of the column. + * - length : the length of the column. + * - default : the default value of the column or null. + * - null : boolean indicating whether the column can be null. + * - comment : the column comment or null. + * + * The following keys will be set as required: + * + * - autoIncrement : set for columns that are an integer primary key. + * - onUpdate : set for datetime/timestamp columns with `ON UPDATE` clauses. + * + * @param string $tableName The name of the table to describe columns on. + * @return array + */ + public function describeColumns(string $tableName): array + { + $sql = $this->describeColumnQuery($tableName); + $columns = []; + try { + $statement = $this->_driver->execute($sql); + } catch (PDOException $e) { + throw new DatabaseException("Could not describe columns on `{$tableName}`", null, $e); + } + foreach ($statement->fetchAll('assoc') as $row) { + $field = $this->_convertColumn($row['Type']); + $default = $this->parseDefault($field['type'], $row); + + $field += [ + 'name' => $row['Field'], + 'null' => $row['Null'] === 'YES', + 'default' => $default, + 'collate' => $row['Collation'], + 'comment' => $row['Comment'], + 'length' => null, + ]; + if (isset($row['Extra']) && $row['Extra'] === 'auto_increment') { + $field['autoIncrement'] = true; + } + if ($row['Extra'] === 'on update CURRENT_TIMESTAMP') { + $field['onUpdate'] = 'CURRENT_TIMESTAMP'; + } elseif ($row['Extra'] === 'on update current_timestamp()') { + $field['onUpdate'] = 'CURRENT_TIMESTAMP'; + } + $columns[] = $field; + } + + return $columns; + } + + /** + * Parse the default value if required. + * + * @param string $type The type of column + * @param array $row a Row of schema reflection data + * @return ?string The default value of a column. + */ + protected function parseDefault(string $type, array $row): ?string + { + $default = $row['Default']; + if ( + is_string($default) && + in_array( + $type, + array_merge( + TableSchema::GEOSPATIAL_TYPES, + [TableSchema::TYPE_BINARY, TableSchema::TYPE_JSON, TableSchema::TYPE_TEXT], + ), + ) + ) { + // The default that comes back from MySQL for these types prefixes the collation type and + // surrounds the value with escaped single quotes, for example "_utf8mbf4\'abc\'", and so + // this converts that then down to the default value of "abc" to correspond to what the user + // would have specified in a migration. + $default = (string)preg_replace("/^_(?:[a-zA-Z0-9]+?)\\\'(.*)\\\'$/", '\1', $default); + + // If the default is wrapped in a function, and has a collation marker on it, strip + // the collation marker out + $default = (string)preg_replace( + "/^(?[a-zA-Z0-9_]*\()(?_[a-zA-Z0-9]+)\\\'(?.*)\\\'\)$/", + "\\1'\\3')", + $default, + ); + } + + return $default; } /** @@ -73,7 +195,63 @@ public function describeColumnSql(string $tableName, array $config): array */ public function describeIndexSql(string $tableName, array $config): array { - return ['SHOW INDEXES FROM ' . $this->_driver->quoteIdentifier($tableName), []]; + $sql = $this->describeIndexQuery($tableName); + + return [$sql, []]; + } + + /** + * Helper method for creating SQL to reflect indexes in a table. + * + * @param string $tableName The table to get indexes from. + * @return string SQL to reflect indexes + */ + private function describeIndexQuery(string $tableName): string + { + return 'SHOW INDEXES FROM ' . $this->_driver->quoteIdentifier($tableName); + } + + /** + * @inheritDoc + */ + public function describeIndexes(string $tableName): array + { + $sql = $this->describeIndexQuery($tableName); + $statement = $this->_driver->execute($sql); + $indexes = []; + + foreach ($statement->fetchAll('assoc') as $row) { + $name = $row['Key_name']; + $type = null; + if ($name === 'PRIMARY') { + $name = TableSchema::CONSTRAINT_PRIMARY; + $type = TableSchema::CONSTRAINT_PRIMARY; + } + if ($row['Index_type'] === 'FULLTEXT') { + $type = TableSchema::INDEX_FULLTEXT; + } elseif ((int)$row['Non_unique'] === 0 && $type !== TableSchema::CONSTRAINT_PRIMARY) { + $type = TableSchema::CONSTRAINT_UNIQUE; + } elseif ($type !== TableSchema::CONSTRAINT_PRIMARY) { + $type = TableSchema::INDEX_INDEX; + } + if (!isset($indexes[$name])) { + $indexes[$name] = [ + 'name' => $name, + 'type' => $type, + 'columns' => [], + 'length' => [], + ]; + } + // conditional indexes can have null columns + if ($row['Column_name'] !== null) { + $indexes[$name]['columns'][] = $row['Column_name']; + } + if (!empty($row['Sub_part'])) { + $indexes[$name]['length'][$row['Column_name']] = $row['Sub_part']; + } + } + + return array_values($indexes); } /** @@ -95,6 +273,22 @@ public function convertOptionsDescription(TableSchema $schema, array $row): void ]); } + /** + * @inheritDoc + */ + public function describeOptions(string $tableName): array + { + [, $name] = $this->splitTableName($tableName); + $sql = 'SHOW TABLE STATUS WHERE Name = ?'; + $statement = $this->_driver->execute($sql, [$name]); + $row = $statement->fetch('assoc'); + + return [ + 'engine' => $row['Engine'], + 'collation' => $row['Collation'], + ]; + } + /** * Convert a MySQL column type into an abstract type. * @@ -107,15 +301,17 @@ public function convertOptionsDescription(TableSchema $schema, array $row): void protected function _convertColumn(string $column): array { preg_match('/([a-z]+)(?:\(([0-9,]+)\))?\s*([a-z]+)?/i', $column, $matches); - if (empty($matches)) { - throw new DatabaseException(sprintf('Unable to parse column type from "%s"', $column)); + if (!$matches) { + throw new DatabaseException(sprintf('Unable to parse column type from `%s`', $column)); } $col = strtolower($matches[1]); - $length = $precision = $scale = null; + $length = null; + $precision = null; + $scale = null; if (isset($matches[2]) && strlen($matches[2])) { $length = $matches[2]; - if (strpos($matches[2], ',') !== false) { + if (str_contains($matches[2], ',')) { [$length, $precision] = explode(',', $length); } $length = (int)$length; @@ -124,7 +320,7 @@ protected function _convertColumn(string $column): array $type = $this->_applyTypeSpecificColumnConversion( $col, - compact('length', 'precision', 'scale') + compact('length', 'precision', 'scale'), ); if ($type !== null) { return $type; @@ -143,68 +339,79 @@ protected function _convertColumn(string $column): array } if (($col === 'tinyint' && $length === 1) || $col === 'boolean') { - return ['type' => TableSchema::TYPE_BOOLEAN, 'length' => null]; + return ['type' => TableSchemaInterface::TYPE_BOOLEAN, 'length' => null]; } $unsigned = (isset($matches[3]) && strtolower($matches[3]) === 'unsigned'); - if (strpos($col, 'bigint') !== false || $col === 'bigint') { - return ['type' => TableSchema::TYPE_BIGINTEGER, 'length' => null, 'unsigned' => $unsigned]; + if (str_contains($col, 'bigint') || $col === 'bigint') { + return ['type' => TableSchemaInterface::TYPE_BIGINTEGER, 'length' => null, 'unsigned' => $unsigned]; } if ($col === 'tinyint') { - return ['type' => TableSchema::TYPE_TINYINTEGER, 'length' => null, 'unsigned' => $unsigned]; + return ['type' => TableSchemaInterface::TYPE_TINYINTEGER, 'length' => null, 'unsigned' => $unsigned]; } if ($col === 'smallint') { - return ['type' => TableSchema::TYPE_SMALLINTEGER, 'length' => null, 'unsigned' => $unsigned]; + return ['type' => TableSchemaInterface::TYPE_SMALLINTEGER, 'length' => null, 'unsigned' => $unsigned]; } if (in_array($col, ['int', 'integer', 'mediumint'])) { - return ['type' => TableSchema::TYPE_INTEGER, 'length' => null, 'unsigned' => $unsigned]; + return ['type' => TableSchemaInterface::TYPE_INTEGER, 'length' => null, 'unsigned' => $unsigned]; } if ($col === 'char' && $length === 36) { - return ['type' => TableSchema::TYPE_UUID, 'length' => null]; + return ['type' => TableSchemaInterface::TYPE_UUID, 'length' => null]; } if ($col === 'char') { - return ['type' => TableSchema::TYPE_CHAR, 'length' => $length]; + return ['type' => TableSchemaInterface::TYPE_CHAR, 'length' => $length]; } - if (strpos($col, 'char') !== false) { - return ['type' => TableSchema::TYPE_STRING, 'length' => $length]; + if (str_contains($col, 'char')) { + return ['type' => TableSchemaInterface::TYPE_STRING, 'length' => $length]; } - if (strpos($col, 'text') !== false) { + if (str_contains($col, 'text')) { $lengthName = substr($col, 0, -4); $length = TableSchema::$columnLengths[$lengthName] ?? null; - return ['type' => TableSchema::TYPE_TEXT, 'length' => $length]; + return ['type' => TableSchemaInterface::TYPE_TEXT, 'length' => $length]; } if ($col === 'binary' && $length === 16) { - return ['type' => TableSchema::TYPE_BINARY_UUID, 'length' => null]; + return ['type' => TableSchemaInterface::TYPE_BINARY_UUID, 'length' => null]; + } + if ($col === 'uuid') { + return ['type' => TableSchemaInterface::TYPE_NATIVE_UUID, 'length' => null]; } - if (strpos($col, 'blob') !== false || in_array($col, ['binary', 'varbinary'])) { + if (str_contains($col, 'blob') || in_array($col, ['binary', 'varbinary'])) { $lengthName = substr($col, 0, -4); $length = TableSchema::$columnLengths[$lengthName] ?? $length; - return ['type' => TableSchema::TYPE_BINARY, 'length' => $length]; + return ['type' => TableSchemaInterface::TYPE_BINARY, 'length' => $length]; } - if (strpos($col, 'float') !== false || strpos($col, 'double') !== false) { + if (str_contains($col, 'float') || str_contains($col, 'double')) { return [ - 'type' => TableSchema::TYPE_FLOAT, + 'type' => TableSchemaInterface::TYPE_FLOAT, 'length' => $length, 'precision' => $precision, 'unsigned' => $unsigned, ]; } - if (strpos($col, 'decimal') !== false) { + if (str_contains($col, 'decimal')) { return [ - 'type' => TableSchema::TYPE_DECIMAL, + 'type' => TableSchemaInterface::TYPE_DECIMAL, 'length' => $length, 'precision' => $precision, 'unsigned' => $unsigned, ]; } - if (strpos($col, 'json') !== false) { - return ['type' => TableSchema::TYPE_JSON, 'length' => null]; + if (str_contains($col, 'json')) { + return ['type' => TableSchemaInterface::TYPE_JSON, 'length' => null]; + } + if (in_array($col, TableSchemaInterface::GEOSPATIAL_TYPES)) { + // TODO how can srid be preserved? It doesn't come back + // in the output of show full columns from ... + return [ + 'type' => $col, + 'length' => null, + ]; } - return ['type' => TableSchema::TYPE_STRING, 'length' => null]; + return ['type' => TableSchemaInterface::TYPE_STRING, 'length' => null]; } /** @@ -213,9 +420,10 @@ protected function _convertColumn(string $column): array public function convertColumnDescription(TableSchema $schema, array $row): void { $field = $this->_convertColumn($row['Type']); + $default = $this->parseDefault($field['type'], $row); $field += [ 'null' => $row['Null'] === 'YES', - 'default' => $row['Default'], + 'default' => $default, 'collate' => $row['Collation'], 'comment' => $row['Comment'], ]; @@ -231,11 +439,13 @@ public function convertColumnDescription(TableSchema $schema, array $row): void public function convertIndexDescription(TableSchema $schema, array $row): void { $type = null; - $columns = $length = []; + $columns = []; + $length = []; $name = $row['Key_name']; if ($name === 'PRIMARY') { - $name = $type = TableSchema::CONSTRAINT_PRIMARY; + $name = TableSchema::CONSTRAINT_PRIMARY; + $type = TableSchema::CONSTRAINT_PRIMARY; } if (!empty($row['Column_name'])) { @@ -264,7 +474,7 @@ public function convertIndexDescription(TableSchema $schema, array $row): void } // MySQL multi column indexes come back as multiple rows. - if (!empty($existing)) { + if ($existing) { $columns = array_merge($existing['columns'], $columns); $length = array_merge($existing['length'], $length); } @@ -316,6 +526,48 @@ public function convertForeignKeyDescription(TableSchema $schema, array $row): v $schema->addConstraint($name, $data); } + /** + * @inheritDoc + */ + public function describeForeignKeys(string $tableName): array + { + [$database, $name] = $this->splitTableName($tableName); + $sql = 'SELECT * FROM information_schema.key_column_usage AS kcu + INNER JOIN information_schema.referential_constraints AS rc + ON ( + kcu.CONSTRAINT_NAME = rc.CONSTRAINT_NAME + AND kcu.CONSTRAINT_SCHEMA = rc.CONSTRAINT_SCHEMA + ) + WHERE kcu.TABLE_SCHEMA = ? AND kcu.TABLE_NAME = ? AND rc.TABLE_NAME = ? + ORDER BY kcu.ORDINAL_POSITION ASC'; + $statement = $this->_driver->execute($sql, [$database, $name, $name]); + $keys = []; + foreach ($statement->fetchAll('assoc') as $row) { + $name = $row['CONSTRAINT_NAME']; + if (!isset($keys[$name])) { + $keys[$name] = [ + 'name' => $name, + 'type' => TableSchema::CONSTRAINT_FOREIGN, + 'columns' => [], + 'references' => [$row['REFERENCED_TABLE_NAME'], []], + 'update' => $this->_convertOnClause($row['UPDATE_RULE'] ?? ''), + 'delete' => $this->_convertOnClause($row['DELETE_RULE'] ?? ''), + 'length' => [], + ]; + } + // Add the columns incrementally + $keys[$name]['columns'][] = $row['COLUMN_NAME']; + $keys[$name]['references'][1][] = $row['REFERENCED_COLUMN_NAME']; + } + foreach ($keys as $id => $key) { + if (count($key['references'][1]) === 1) { + $keys[$id]['references'][1] = $key['references'][1][0]; + } + } + + return array_values($keys); + } + /** * @inheritDoc */ @@ -347,40 +599,46 @@ public function createTableSql(TableSchema $schema, array $columns, array $const } /** - * @inheritDoc + * Create a SQL snippet for a column based on the array shape + * that `describeColumns()` creates. + * + * @param array $column The column metadata + * @return string Generated SQL fragment for a column */ - public function columnSql(TableSchema $schema, string $name): string + public function columnDefinitionSql(array $column): string { - /** @var array $data */ - $data = $schema->getColumn($name); - - $sql = $this->_getTypeSpecificColumnSql($data['type'], $schema, $name); - if ($sql !== null) { - return $sql; - } + $name = $column['name']; + $column += [ + 'length' => null, + ]; $out = $this->_driver->quoteIdentifier($name); - $nativeJson = $this->_driver->supports(DriverInterface::FEATURE_JSON); + $nativeJson = $this->_driver->supports(DriverFeatureEnum::JSON); $typeMap = [ - TableSchema::TYPE_TINYINTEGER => ' TINYINT', - TableSchema::TYPE_SMALLINTEGER => ' SMALLINT', - TableSchema::TYPE_INTEGER => ' INTEGER', - TableSchema::TYPE_BIGINTEGER => ' BIGINT', - TableSchema::TYPE_BINARY_UUID => ' BINARY(16)', - TableSchema::TYPE_BOOLEAN => ' BOOLEAN', - TableSchema::TYPE_FLOAT => ' FLOAT', - TableSchema::TYPE_DECIMAL => ' DECIMAL', - TableSchema::TYPE_DATE => ' DATE', - TableSchema::TYPE_TIME => ' TIME', - TableSchema::TYPE_DATETIME => ' DATETIME', - TableSchema::TYPE_DATETIME_FRACTIONAL => ' DATETIME', - TableSchema::TYPE_TIMESTAMP => ' TIMESTAMP', - TableSchema::TYPE_TIMESTAMP_FRACTIONAL => ' TIMESTAMP', - TableSchema::TYPE_TIMESTAMP_TIMEZONE => ' TIMESTAMP', - TableSchema::TYPE_CHAR => ' CHAR', - TableSchema::TYPE_UUID => ' CHAR(36)', - TableSchema::TYPE_JSON => $nativeJson ? ' JSON' : ' LONGTEXT', + TableSchemaInterface::TYPE_TINYINTEGER => ' TINYINT', + TableSchemaInterface::TYPE_SMALLINTEGER => ' SMALLINT', + TableSchemaInterface::TYPE_INTEGER => ' INTEGER', + TableSchemaInterface::TYPE_BIGINTEGER => ' BIGINT', + TableSchemaInterface::TYPE_BINARY_UUID => ' BINARY(16)', + TableSchemaInterface::TYPE_BOOLEAN => ' BOOLEAN', + TableSchemaInterface::TYPE_FLOAT => ' FLOAT', + TableSchemaInterface::TYPE_DECIMAL => ' DECIMAL', + TableSchemaInterface::TYPE_DATE => ' DATE', + TableSchemaInterface::TYPE_TIME => ' TIME', + TableSchemaInterface::TYPE_DATETIME => ' DATETIME', + TableSchemaInterface::TYPE_DATETIME_FRACTIONAL => ' DATETIME', + TableSchemaInterface::TYPE_TIMESTAMP => ' TIMESTAMP', + TableSchemaInterface::TYPE_TIMESTAMP_FRACTIONAL => ' TIMESTAMP', + TableSchemaInterface::TYPE_TIMESTAMP_TIMEZONE => ' TIMESTAMP', + TableSchemaInterface::TYPE_CHAR => ' CHAR', + TableSchemaInterface::TYPE_UUID => ' CHAR(36)', + TableSchemaInterface::TYPE_NATIVE_UUID => ' UUID', + TableSchemaInterface::TYPE_JSON => $nativeJson ? ' JSON' : ' LONGTEXT', + TableSchemaInterface::TYPE_GEOMETRY => ' GEOMETRY', + TableSchemaInterface::TYPE_POINT => ' POINT', + TableSchemaInterface::TYPE_LINESTRING => ' LINESTRING', + TableSchemaInterface::TYPE_POLYGON => ' POLYGON', ]; $specialMap = [ 'string' => true, @@ -388,169 +646,217 @@ public function columnSql(TableSchema $schema, string $name): string 'char' => true, 'binary' => true, ]; - if (isset($typeMap[$data['type']])) { - $out .= $typeMap[$data['type']]; + if (isset($typeMap[$column['type']])) { + $out .= $typeMap[$column['type']]; } - if (isset($specialMap[$data['type']])) { - switch ($data['type']) { - case TableSchema::TYPE_STRING: + if (isset($specialMap[$column['type']])) { + switch ($column['type']) { + case TableSchemaInterface::TYPE_STRING: $out .= ' VARCHAR'; - if (!isset($data['length'])) { - $data['length'] = 255; + if (!isset($column['length'])) { + $column['length'] = 255; } break; - case TableSchema::TYPE_TEXT: - $isKnownLength = in_array($data['length'], TableSchema::$columnLengths); - if (empty($data['length']) || !$isKnownLength) { + case TableSchemaInterface::TYPE_TEXT: + $isKnownLength = in_array($column['length'], TableSchema::$columnLengths); + if (empty($column['length']) || !$isKnownLength) { $out .= ' TEXT'; break; } - /** @var string $length */ - $length = array_search($data['length'], TableSchema::$columnLengths); + $length = array_search($column['length'], TableSchema::$columnLengths); + assert(is_string($length)); $out .= ' ' . strtoupper($length) . 'TEXT'; break; - case TableSchema::TYPE_BINARY: - $isKnownLength = in_array($data['length'], TableSchema::$columnLengths); + case TableSchemaInterface::TYPE_BINARY: + $isKnownLength = in_array($column['length'], TableSchema::$columnLengths); if ($isKnownLength) { - /** @var string $length */ - $length = array_search($data['length'], TableSchema::$columnLengths); + $length = array_search($column['length'], TableSchema::$columnLengths); + assert(is_string($length)); $out .= ' ' . strtoupper($length) . 'BLOB'; break; } - if (empty($data['length'])) { + if (empty($column['length'])) { $out .= ' BLOB'; break; } - if ($data['length'] > 2) { - $out .= ' VARBINARY(' . $data['length'] . ')'; + if ($column['length'] > 2) { + $out .= ' VARBINARY(' . $column['length'] . ')'; } else { - $out .= ' BINARY(' . $data['length'] . ')'; + $out .= ' BINARY(' . $column['length'] . ')'; } break; } } $hasLength = [ - TableSchema::TYPE_INTEGER, - TableSchema::TYPE_CHAR, - TableSchema::TYPE_SMALLINTEGER, - TableSchema::TYPE_TINYINTEGER, - TableSchema::TYPE_STRING, + TableSchemaInterface::TYPE_INTEGER, + TableSchemaInterface::TYPE_CHAR, + TableSchemaInterface::TYPE_SMALLINTEGER, + TableSchemaInterface::TYPE_TINYINTEGER, + TableSchemaInterface::TYPE_STRING, ]; - if (in_array($data['type'], $hasLength, true) && isset($data['length'])) { - $out .= '(' . $data['length'] . ')'; + if (in_array($column['type'], $hasLength, true) && isset($column['length'])) { + $out .= '(' . $column['length'] . ')'; } - $lengthAndPrecisionTypes = [TableSchema::TYPE_FLOAT, TableSchema::TYPE_DECIMAL]; - if (in_array($data['type'], $lengthAndPrecisionTypes, true) && isset($data['length'])) { - if (isset($data['precision'])) { - $out .= '(' . (int)$data['length'] . ',' . (int)$data['precision'] . ')'; + $lengthAndPrecisionTypes = [ + TableSchemaInterface::TYPE_FLOAT, + TableSchemaInterface::TYPE_DECIMAL, + ]; + if (in_array($column['type'], $lengthAndPrecisionTypes, true) && isset($column['length'])) { + if (isset($column['precision'])) { + $out .= '(' . (int)$column['length'] . ',' . (int)$column['precision'] . ')'; } else { - $out .= '(' . (int)$data['length'] . ')'; + $out .= '(' . (int)$column['length'] . ')'; } } - $precisionTypes = [TableSchema::TYPE_DATETIME_FRACTIONAL, TableSchema::TYPE_TIMESTAMP_FRACTIONAL]; - if (in_array($data['type'], $precisionTypes, true) && isset($data['precision'])) { - $out .= '(' . (int)$data['precision'] . ')'; + $precisionTypes = [ + TableSchemaInterface::TYPE_DATETIME_FRACTIONAL, + TableSchemaInterface::TYPE_TIMESTAMP_FRACTIONAL, + ]; + if (in_array($column['type'], $precisionTypes, true) && isset($column['precision'])) { + $out .= '(' . (int)$column['precision'] . ')'; } $hasUnsigned = [ - TableSchema::TYPE_TINYINTEGER, - TableSchema::TYPE_SMALLINTEGER, - TableSchema::TYPE_INTEGER, - TableSchema::TYPE_BIGINTEGER, - TableSchema::TYPE_FLOAT, - TableSchema::TYPE_DECIMAL, + TableSchemaInterface::TYPE_TINYINTEGER, + TableSchemaInterface::TYPE_SMALLINTEGER, + TableSchemaInterface::TYPE_INTEGER, + TableSchemaInterface::TYPE_BIGINTEGER, + TableSchemaInterface::TYPE_FLOAT, + TableSchemaInterface::TYPE_DECIMAL, ]; if ( - in_array($data['type'], $hasUnsigned, true) && - isset($data['unsigned']) && - $data['unsigned'] === true + in_array($column['type'], $hasUnsigned, true) && + isset($column['unsigned']) && + $column['unsigned'] === true ) { $out .= ' UNSIGNED'; } $hasCollate = [ - TableSchema::TYPE_TEXT, - TableSchema::TYPE_CHAR, - TableSchema::TYPE_STRING, + TableSchemaInterface::TYPE_TEXT, + TableSchemaInterface::TYPE_CHAR, + TableSchemaInterface::TYPE_STRING, ]; - if (in_array($data['type'], $hasCollate, true) && isset($data['collate']) && $data['collate'] !== '') { - $out .= ' COLLATE ' . $data['collate']; + if (in_array($column['type'], $hasCollate, true) && isset($column['collate']) && $column['collate'] !== '') { + $out .= ' COLLATE ' . $column['collate']; } - if (isset($data['null']) && $data['null'] === false) { + if (isset($column['null']) && $column['null'] === false) { $out .= ' NOT NULL'; } - $addAutoIncrement = ( - $schema->getPrimaryKey() === [$name] && - !$schema->hasAutoincrement() && - !isset($data['autoIncrement']) - ); - if ( - in_array($data['type'], [TableSchema::TYPE_INTEGER, TableSchema::TYPE_BIGINTEGER]) && - ( - $data['autoIncrement'] === true || - $addAutoIncrement - ) - ) { + + if (isset($column['autoIncrement']) && $column['autoIncrement']) { $out .= ' AUTO_INCREMENT'; + unset($column['default']); } $timestampTypes = [ - TableSchema::TYPE_TIMESTAMP, - TableSchema::TYPE_TIMESTAMP_FRACTIONAL, - TableSchema::TYPE_TIMESTAMP_TIMEZONE, + TableSchemaInterface::TYPE_TIMESTAMP, + TableSchemaInterface::TYPE_TIMESTAMP_FRACTIONAL, + TableSchemaInterface::TYPE_TIMESTAMP_TIMEZONE, ]; - if (isset($data['null']) && $data['null'] === true && in_array($data['type'], $timestampTypes, true)) { + if (isset($column['null']) && $column['null'] === true && in_array($column['type'], $timestampTypes, true)) { $out .= ' NULL'; - unset($data['default']); + unset($column['default']); + } + if (isset($column['srid']) && in_array($column['type'], TableSchemaInterface::GEOSPATIAL_TYPES)) { + $out .= " SRID {$column['srid']}"; + } + + $defaultExpressionTypes = array_merge( + TableSchemaInterface::GEOSPATIAL_TYPES, + [TableSchemaInterface::TYPE_BINARY, TableSchemaInterface::TYPE_TEXT, TableSchemaInterface::TYPE_JSON], + ); + if (in_array($column['type'], $defaultExpressionTypes) && isset($column['default'])) { + // Geospatial, blob and text types need to be wrapped in () to create an expression. + $out .= ' DEFAULT (' . $this->_driver->schemaValue($column['default']) . ')'; + unset($column['default']); } $dateTimeTypes = [ - TableSchema::TYPE_DATETIME, - TableSchema::TYPE_DATETIME_FRACTIONAL, - TableSchema::TYPE_TIMESTAMP, - TableSchema::TYPE_TIMESTAMP_FRACTIONAL, - TableSchema::TYPE_TIMESTAMP_TIMEZONE, + TableSchemaInterface::TYPE_DATETIME, + TableSchemaInterface::TYPE_DATETIME_FRACTIONAL, + TableSchemaInterface::TYPE_TIMESTAMP, + TableSchemaInterface::TYPE_TIMESTAMP_FRACTIONAL, + TableSchemaInterface::TYPE_TIMESTAMP_TIMEZONE, ]; if ( - isset($data['default']) && - in_array($data['type'], $dateTimeTypes) && - strpos(strtolower($data['default']), 'current_timestamp') !== false + isset($column['default']) && + in_array($column['type'], $dateTimeTypes) && + is_string($column['default']) && + str_contains(strtolower($column['default']), 'current_timestamp') ) { $out .= ' DEFAULT CURRENT_TIMESTAMP'; - if (isset($data['precision'])) { - $out .= '(' . $data['precision'] . ')'; + if (isset($column['precision'])) { + $out .= '(' . $column['precision'] . ')'; } - unset($data['default']); + unset($column['default']); } - if (isset($data['default'])) { - $out .= ' DEFAULT ' . $this->_driver->schemaValue($data['default']); - unset($data['default']); + if (isset($column['default'])) { + $out .= ' DEFAULT ' . $this->_driver->schemaValue($column['default']); + unset($column['default']); } - if (isset($data['comment']) && $data['comment'] !== '') { - $out .= ' COMMENT ' . $this->_driver->schemaValue($data['comment']); + if (isset($column['comment']) && $column['comment'] !== '') { + // Always quote comments as strings to prevent SQL syntax errors with numeric comments + // See: https://github.com/cakephp/migrations/issues/889 + $out .= ' COMMENT ' . $this->_driver->quote((string)$column['comment']); + } + if (isset($column['onUpdate']) && $column['onUpdate'] !== '') { + $out .= ' ON UPDATE ' . $column['onUpdate']; } return $out; } + /** + * @inheritDoc + */ + public function columnSql(TableSchema $schema, string $name): string + { + $data = $schema->getColumn($name); + assert($data !== null); + + // TODO deprecrate Type defined schema mappings? + $sql = $this->_getTypeSpecificColumnSql($data['type'], $schema, $name); + if ($sql !== null) { + return $sql; + } + $data['name'] = $name; + + $autoIncrementTypes = [ + TableSchemaInterface::TYPE_TINYINTEGER, + TableSchemaInterface::TYPE_SMALLINTEGER, + TableSchemaInterface::TYPE_INTEGER, + TableSchemaInterface::TYPE_BIGINTEGER, + ]; + if ( + in_array($data['type'], $autoIncrementTypes, true) && + $schema->getPrimaryKey() === [$name] && + $name === 'id' + ) { + $data['autoIncrement'] = true; + } + + return $this->columnDefinitionSql($data); + } + /** * @inheritDoc */ public function constraintSql(TableSchema $schema, string $name): string { - /** @var array $data */ $data = $schema->getConstraint($name); + assert($data !== null); if ($data['type'] === TableSchema::CONSTRAINT_PRIMARY) { $columns = array_map( - [$this->_driver, 'quoteIdentifier'], - $data['columns'] + $this->_driver->quoteIdentifier(...), + $data['columns'], ); return sprintf('PRIMARY KEY (%s)', implode(', ', $columns)); @@ -577,8 +883,8 @@ public function addConstraintSql(TableSchema $schema): array $sql = []; foreach ($schema->constraints() as $name) { - /** @var array $constraint */ $constraint = $schema->getConstraint($name); + assert($constraint !== null); if ($constraint['type'] === TableSchema::CONSTRAINT_FOREIGN) { $tableName = $this->_driver->quoteIdentifier($schema->name()); $sql[] = sprintf($sqlPattern, $tableName, $this->constraintSql($schema, $name)); @@ -597,8 +903,8 @@ public function dropConstraintSql(TableSchema $schema): array $sql = []; foreach ($schema->constraints() as $name) { - /** @var array $constraint */ $constraint = $schema->getConstraint($name); + assert($constraint !== null); if ($constraint['type'] === TableSchema::CONSTRAINT_FOREIGN) { $tableName = $this->_driver->quoteIdentifier($schema->name()); $constraintName = $this->_driver->quoteIdentifier($name); @@ -614,8 +920,8 @@ public function dropConstraintSql(TableSchema $schema): array */ public function indexSql(TableSchema $schema, string $name): string { - /** @var array $data */ $data = $schema->getIndex($name); + assert($data !== null); $out = ''; if ($data['type'] === TableSchema::INDEX_INDEX) { $out = 'KEY '; @@ -638,8 +944,8 @@ public function indexSql(TableSchema $schema, string $name): string protected function _keySql(string $prefix, array $data): string { $columns = array_map( - [$this->_driver, 'quoteIdentifier'], - $data['columns'] + $this->_driver->quoteIdentifier(...), + $data['columns'], ); foreach ($data['columns'] as $i => $column) { if (isset($data['length'][$column])) { @@ -653,17 +959,10 @@ protected function _keySql(string $prefix, array $data): string $this->_driver->quoteIdentifier($data['references'][0]), $this->_convertConstraintColumns($data['references'][1]), $this->_foreignOnClause($data['update']), - $this->_foreignOnClause($data['delete']) + $this->_foreignOnClause($data['delete']), ); } return $prefix . ' (' . implode(', ', $columns) . ')'; } } - -// phpcs:disable -class_alias( - 'Cake\Database\Schema\MysqlSchemaDialect', - 'Cake\Database\Schema\MysqlSchema' -); -// phpcs:enable diff --git a/app/vendor/cakephp/cakephp/src/Database/Schema/PostgresSchema.php b/app/vendor/cakephp/cakephp/src/Database/Schema/PostgresSchema.php deleted file mode 100644 index bd4599136..000000000 --- a/app/vendor/cakephp/cakephp/src/Database/Schema/PostgresSchema.php +++ /dev/null @@ -1,10 +0,0 @@ -describeColumnQuery(); + $schema = $config['schema'] ?? 'public'; + + return [$sql, [$tableName, $schema, $config['database']]]; + } + + /** + * Helper method for creating SQL to describe columns in a table. + * + * @return string SQL to reflect columns + */ + private function describeColumnQuery(): string + { + return 'SELECT DISTINCT table_schema AS schema, column_name AS name, data_type AS type, - is_nullable AS null, column_default AS default, + udt_name, + is_identity, + is_nullable AS null, + column_default AS default, character_maximum_length AS char_length, c.collation_name, d.description as comment, @@ -73,6 +94,7 @@ public function describeColumnSql(string $tableName, array $config): array c.datetime_precision, c.numeric_precision as column_precision, c.numeric_scale as column_scale, + c.identity_generation, pg_get_serial_sequence(attr.attrelid::regclass::text, attr.attname) IS NOT NULL AS has_serial FROM information_schema.columns c INNER JOIN pg_catalog.pg_namespace ns ON (ns.nspname = table_schema) @@ -82,10 +104,6 @@ public function describeColumnSql(string $tableName, array $config): array LEFT JOIN pg_catalog.pg_attribute attr ON (cl.oid = attr.attrelid AND column_name = attr.attname) WHERE table_name = ? AND table_schema = ? AND table_catalog = ? ORDER BY ordinal_position'; - - $schema = empty($config['schema']) ? 'public' : $config['schema']; - - return [$sql, [$tableName, $schema, $config['database']]]; } /** @@ -100,20 +118,22 @@ public function describeColumnSql(string $tableName, array $config): array */ protected function _convertColumn(string $column): array { - preg_match('/([a-z\s]+)(?:\(([0-9,]+)\))?/i', $column, $matches); - if (empty($matches)) { - throw new DatabaseException(sprintf('Unable to parse column type from "%s"', $column)); + preg_match('/([a-z\s]+)(?:\(([a-z0-9,]+)(?:,\s*([0-9]+))?\))?/i', $column, $matches); + if (!$matches) { + throw new DatabaseException(sprintf('Unable to parse column type from `%s`', $column)); } $col = strtolower($matches[1]); - $length = $precision = $scale = null; + $length = null; + $precision = null; + $scale = null; if (isset($matches[2])) { $length = (int)$matches[2]; } $type = $this->_applyTypeSpecificColumnConversion( $col, - compact('length', 'precision', 'scale') + compact('length', 'precision', 'scale'), ); if ($type !== null) { return $type; @@ -123,63 +143,65 @@ protected function _convertColumn(string $column): array return ['type' => $col, 'length' => null]; } if (in_array($col, ['timestamptz', 'timestamp with time zone'], true)) { - return ['type' => TableSchema::TYPE_TIMESTAMP_TIMEZONE, 'length' => null]; + return ['type' => TableSchemaInterface::TYPE_TIMESTAMP_TIMEZONE, 'length' => null]; } - if (strpos($col, 'timestamp') !== false) { - return ['type' => TableSchema::TYPE_TIMESTAMP_FRACTIONAL, 'length' => null]; + if (str_contains($col, 'timestamp')) { + return ['type' => TableSchemaInterface::TYPE_TIMESTAMP_FRACTIONAL, 'length' => null]; } - if (strpos($col, 'time') !== false) { - return ['type' => TableSchema::TYPE_TIME, 'length' => null]; + if (str_contains($col, 'time')) { + return ['type' => TableSchemaInterface::TYPE_TIME, 'length' => null]; } if ($col === 'serial' || $col === 'integer') { - return ['type' => TableSchema::TYPE_INTEGER, 'length' => 10]; + return ['type' => TableSchemaInterface::TYPE_INTEGER, 'length' => 10]; } if ($col === 'bigserial' || $col === 'bigint') { - return ['type' => TableSchema::TYPE_BIGINTEGER, 'length' => 20]; + return ['type' => TableSchemaInterface::TYPE_BIGINTEGER, 'length' => 20]; } if ($col === 'smallint') { - return ['type' => TableSchema::TYPE_SMALLINTEGER, 'length' => 5]; + return ['type' => TableSchemaInterface::TYPE_SMALLINTEGER, 'length' => 5]; } if ($col === 'inet') { - return ['type' => TableSchema::TYPE_STRING, 'length' => 39]; + return ['type' => TableSchemaInterface::TYPE_STRING, 'length' => 39]; } if ($col === 'uuid') { - return ['type' => TableSchema::TYPE_UUID, 'length' => null]; + return ['type' => TableSchemaInterface::TYPE_UUID, 'length' => null]; } if ($col === 'char') { - return ['type' => TableSchema::TYPE_CHAR, 'length' => $length]; + return ['type' => TableSchemaInterface::TYPE_CHAR, 'length' => $length]; } - if (strpos($col, 'character') !== false) { - return ['type' => TableSchema::TYPE_STRING, 'length' => $length]; + if (str_contains($col, 'character')) { + return ['type' => TableSchemaInterface::TYPE_STRING, 'length' => $length]; } // money is 'string' as it includes arbitrary text content // before the number value. - if (strpos($col, 'money') !== false || $col === 'string') { - return ['type' => TableSchema::TYPE_STRING, 'length' => $length]; + if (str_contains($col, 'money') || $col === 'string') { + return ['type' => TableSchemaInterface::TYPE_STRING, 'length' => $length]; } - if (strpos($col, 'text') !== false) { - return ['type' => TableSchema::TYPE_TEXT, 'length' => null]; + if (str_contains($col, 'text')) { + return ['type' => TableSchemaInterface::TYPE_TEXT, 'length' => null]; } if ($col === 'bytea') { - return ['type' => TableSchema::TYPE_BINARY, 'length' => null]; + return ['type' => TableSchemaInterface::TYPE_BINARY, 'length' => null]; } - if ($col === 'real' || strpos($col, 'double') !== false) { - return ['type' => TableSchema::TYPE_FLOAT, 'length' => null]; + if ($col === 'real' || str_contains($col, 'double')) { + return ['type' => TableSchemaInterface::TYPE_FLOAT, 'length' => null]; } - if ( - strpos($col, 'numeric') !== false || - strpos($col, 'decimal') !== false - ) { - return ['type' => TableSchema::TYPE_DECIMAL, 'length' => null]; + if (str_contains($col, 'numeric') || str_contains($col, 'decimal')) { + return ['type' => TableSchemaInterface::TYPE_DECIMAL, 'length' => null]; + } + if (str_contains($col, 'json')) { + return ['type' => TableSchemaInterface::TYPE_JSON, 'length' => null]; } + if ($col === 'geography') { + $srid = (int)($matches[3] ?? self::DEFAULT_SRID); + $type = strtolower($matches[2] ?? 'point'); - if (strpos($col, 'json') !== false) { - return ['type' => TableSchema::TYPE_JSON, 'length' => null]; + return ['type' => $type, 'length' => null, 'srid' => $srid]; } $length = is_numeric($length) ? $length : null; - return ['type' => TableSchema::TYPE_STRING, 'length' => $length]; + return ['type' => TableSchemaInterface::TYPE_STRING, 'length' => $length]; } /** @@ -189,7 +211,7 @@ public function convertColumnDescription(TableSchema $schema, array $row): void { $field = $this->_convertColumn($row['type']); - if ($field['type'] === TableSchema::TYPE_BOOLEAN) { + if ($field['type'] === TableSchemaInterface::TYPE_BOOLEAN) { if ($row['default'] === 'true') { $row['default'] = 1; } @@ -214,20 +236,98 @@ public function convertColumnDescription(TableSchema $schema, array $row): void $field['precision'] = $row['column_scale'] ?: null; } - if ($field['type'] === TableSchema::TYPE_TIMESTAMP_FRACTIONAL) { + if ($field['type'] === TableSchemaInterface::TYPE_TIMESTAMP_FRACTIONAL) { $field['precision'] = $row['datetime_precision']; if ($field['precision'] === 0) { - $field['type'] = TableSchema::TYPE_TIMESTAMP; + $field['type'] = TableSchemaInterface::TYPE_TIMESTAMP; } } - if ($field['type'] === TableSchema::TYPE_TIMESTAMP_TIMEZONE) { + if ($field['type'] === TableSchemaInterface::TYPE_TIMESTAMP_TIMEZONE) { $field['precision'] = $row['datetime_precision']; } $schema->addColumn($row['name'], $field); } + /** + * Split a tablename into a tuple of schema, table + * If the table does not have a schema name included, the connection + * schema will be used. + * + * @param string $tableName The table name to split + * @param array $config Additional configuration data + * @return array A tuple of [schema, tablename] + */ + private function splitTablename(string $tableName, array $config = []): array + { + if (str_contains($tableName, '.')) { + return explode('.', $tableName); + } + $driverConfig = $this->_driver->config(); + $schema = $config['schema'] ?? $driverConfig['schema'] ?? 'public'; + + return [$schema, $tableName]; + } + + /** + * @inheritDoc + */ + public function describeColumns(string $tableName): array + { + $config = $this->_driver->config(); + [$schema, $name] = $this->splitTablename($tableName); + + $sql = $this->describeColumnQuery(); + $statement = $this->_driver->execute($sql, [$name, $schema, $config['database']]); + $columns = []; + foreach ($statement->fetchAll('assoc') as $row) { + $field = $this->_convertColumn($row['type']); + if ($field['type'] === TableSchemaInterface::TYPE_BOOLEAN) { + if ($row['default'] === 'true') { + $row['default'] = 1; + } elseif ($row['default'] === 'false') { + $row['default'] = 0; + } + } + if (!empty($row['has_serial'])) { + $field['autoIncrement'] = true; + } + + $field += [ + 'name' => $row['name'], + 'default' => $this->_defaultValue($row['default']), + 'null' => $row['null'] === 'YES', + 'collate' => $row['collation_name'], + 'comment' => $row['comment'], + ]; + $field['length'] = $row['char_length'] ?: $field['length']; + + if ($field['type'] === 'numeric' || $field['type'] === 'decimal') { + $field['length'] = $row['column_precision']; + $field['precision'] = $row['column_scale'] ?: null; + } + + if ($field['type'] === TableSchemaInterface::TYPE_TIMESTAMP_FRACTIONAL) { + $field['precision'] = $row['datetime_precision']; + if ($field['precision'] === 0) { + $field['type'] = TableSchemaInterface::TYPE_TIMESTAMP; + } + } + + if ($field['type'] === TableSchemaInterface::TYPE_TIMESTAMP_TIMEZONE) { + $field['precision'] = $row['datetime_precision']; + } + if (isset($row['identity_generation']) && $row['identity_generation']) { + $field['generated'] = $row['identity_generation']; + } + + $columns[] = $field; + } + + return $columns; + } + /** * Manipulate the default value. * @@ -237,17 +337,17 @@ public function convertColumnDescription(TableSchema $schema, array $row): void * @param string|int|null $default The default value. * @return string|int|null */ - protected function _defaultValue($default) + protected function _defaultValue(string|int|null $default): string|int|null { if (is_numeric($default) || $default === null) { return $default; } // Sequences - if (strpos($default, 'nextval') === 0) { + if (str_starts_with($default, 'nextval')) { return null; } - if (strpos($default, 'NULL::') === 0) { + if (str_starts_with($default, 'NULL::')) { return null; } @@ -255,16 +355,18 @@ protected function _defaultValue($default) return preg_replace( "/^'(.*)'(?:::.*)$/", '$1', - $default + $default, ); } /** - * @inheritDoc + * Get the query to describe indexes + * + * @return string */ - public function describeIndexSql(string $tableName, array $config): array + private function describeIndexQuery(): string { - $sql = 'SELECT + return 'SELECT c2.relname, a.attname, i.indisprimary, @@ -278,13 +380,17 @@ public function describeIndexSql(string $tableName, array $config): array AND a.attnum = ANY(i.indkey) AND c.relname = ? ORDER BY i.indisprimary DESC, i.indisunique DESC, c.relname, a.attnum'; + } - $schema = 'public'; - if (!empty($config['schema'])) { - $schema = $config['schema']; - } + /** + * @inheritDoc + */ + public function describeIndexSql(string $tableName, array $config): array + { + $sql = $this->describeIndexQuery(); + [$schema, $name] = $this->splitTablename($tableName, $config); - return [$sql, [$schema, $tableName]]; + return [$sql, [$schema, $name]]; } /** @@ -295,7 +401,8 @@ public function convertIndexDescription(TableSchema $schema, array $row): void $type = TableSchema::INDEX_INDEX; $name = $row['relname']; if ($row['indisprimary']) { - $name = $type = TableSchema::CONSTRAINT_PRIMARY; + $name = TableSchema::CONSTRAINT_PRIMARY; + $type = TableSchema::CONSTRAINT_PRIMARY; } if ($row['indisunique'] && $type === TableSchema::INDEX_INDEX) { $type = TableSchema::CONSTRAINT_UNIQUE; @@ -316,6 +423,45 @@ public function convertIndexDescription(TableSchema $schema, array $row): void $schema->addIndex($name, $index); } + /** + * @inheritDoc + */ + public function describeIndexes(string $tableName): array + { + [$schema, $name] = $this->splitTablename($tableName); + $sql = $this->describeIndexQuery(); + + $indexes = []; + $statement = $this->_driver->execute($sql, [$schema, $name]); + foreach ($statement->fetchAll('assoc') as $row) { + $type = TableSchema::INDEX_INDEX; + $name = $row['relname']; + $constraint = null; + if ($row['indisprimary']) { + $constraint = $name; + $name = TableSchema::CONSTRAINT_PRIMARY; + $type = TableSchema::CONSTRAINT_PRIMARY; + } + if ($row['indisunique'] && $type === TableSchema::INDEX_INDEX) { + $type = TableSchema::CONSTRAINT_UNIQUE; + } + if (!isset($indexes[$name])) { + $indexes[$name] = [ + 'name' => $name, + 'type' => $type, + 'columns' => [], + 'length' => [], + ]; + } + if ($constraint) { + $indexes[$name]['constraint'] = $constraint; + } + $indexes[$name]['columns'][] = $row['attname']; + } + + return array_values($indexes); + } + /** * Add/update a constraint into the schema object. * @@ -342,17 +488,86 @@ protected function _convertConstraint(TableSchema $schema, string $name, string * @inheritDoc */ public function describeForeignKeySql(string $tableName, array $config): array + { + $sql = $this->describeForeignKeyQuery(); + [$schema, $name] = $this->splitTablename($tableName, $config); + + return [$sql, [$schema, $name]]; + } + + /** + * @inheritDoc + */ + public function convertForeignKeyDescription(TableSchema $schema, array $row): void + { + $data = [ + 'type' => TableSchema::CONSTRAINT_FOREIGN, + 'columns' => $row['column_name'], + 'references' => [$row['references_table'], $row['references_field']], + 'update' => $this->_convertOnClause($row['on_update']), + 'delete' => $this->_convertOnClause($row['on_delete']), + ]; + $schema->addConstraint($row['name'], $data); + } + + /** + * @inheritDoc + */ + public function describeForeignKeys(string $tableName): array + { + [$schema, $name] = $this->splitTablename($tableName); + $sql = $this->describeForeignKeyQuery(); + $keys = []; + $statement = $this->_driver->execute($sql, [$schema, $name]); + foreach ($statement->fetchAll('assoc') as $row) { + $name = $row['name']; + if (!isset($keys[$name])) { + $keys[$name] = [ + 'name' => $name, + 'type' => TableSchema::CONSTRAINT_FOREIGN, + 'columns' => [], + 'references' => [$row['references_table'], []], + 'update' => $this->_convertOnClause($row['on_update']), + 'delete' => $this->_convertOnClause($row['on_delete']), + ]; + } + // column indexes start at 1 + $columnOrder = $row['column_order'] - 1; + $referencedColumnOrder = $row['references_field_order'] - 1; + + $keys[$name]['columns'][$columnOrder] = $row['column_name']; + $keys[$name]['references'][1][$referencedColumnOrder] = $row['references_field']; + } + foreach ($keys as $id => $key) { + // references.1 is the referenced columns. Backwards compat + // requires a single column to be a string, but multiple to be an array. + if (count($key['references'][1]) === 1) { + $keys[$id]['references'][1] = $key['references'][1][0]; + } + } + + return array_values($keys); + } + + /** + * Get the query to describe foreign keys + * + * @return string + */ + private function describeForeignKeyQuery(): string { // phpcs:disable Generic.Files.LineLength $sql = 'SELECT c.conname AS name, c.contype AS type, a.attname AS column_name, + array_position(c.conkey, a.attnum) AS column_order, c.confmatchtype AS match_type, c.confupdtype AS on_update, c.confdeltype AS on_delete, c.confrelid::regclass AS references_table, - ab.attname AS references_field + ab.attname AS references_field, + array_position(c.confkey, ab.attnum) AS references_field_order FROM pg_catalog.pg_namespace n INNER JOIN pg_catalog.pg_class cl ON (n.oid = cl.relnamespace) INNER JOIN pg_catalog.pg_constraint c ON (n.oid = c.connamespace) @@ -360,27 +575,18 @@ public function describeForeignKeySql(string $tableName, array $config): array INNER JOIN pg_catalog.pg_attribute ab ON (a.attrelid = cl.oid AND c.confrelid = ab.attrelid AND ab.attnum = ANY(c.confkey)) WHERE n.nspname = ? AND cl.relname = ? - ORDER BY name, a.attnum, ab.attnum DESC'; + ORDER BY name, column_order ASC, references_field_order ASC'; // phpcs:enable Generic.Files.LineLength - $schema = empty($config['schema']) ? 'public' : $config['schema']; - - return [$sql, [$schema, $tableName]]; + return $sql; } /** * @inheritDoc */ - public function convertForeignKeyDescription(TableSchema $schema, array $row): void + public function describeOptions(string $tableName): array { - $data = [ - 'type' => TableSchema::CONSTRAINT_FOREIGN, - 'columns' => $row['column_name'], - 'references' => [$row['references_table'], $row['references_field']], - 'update' => $this->_convertOnClause($row['on_update']), - 'delete' => $this->_convertOnClause($row['on_delete']), - ]; - $schema->addConstraint($row['name'], $data); + return []; } /** @@ -406,122 +612,170 @@ protected function _convertOnClause(string $clause): string */ public function columnSql(TableSchema $schema, string $name): string { - /** @var array $data */ $data = $schema->getColumn($name); + assert($data !== null); + $data['name'] = $name; $sql = $this->_getTypeSpecificColumnSql($data['type'], $schema, $name); if ($sql !== null) { return $sql; } + $autoIncrementTypes = [ + TableSchemaInterface::TYPE_TINYINTEGER, + TableSchemaInterface::TYPE_SMALLINTEGER, + TableSchemaInterface::TYPE_INTEGER, + TableSchemaInterface::TYPE_BIGINTEGER, + ]; + $primaryKey = $schema->getPrimaryKey(); + if ( + in_array($data['type'], $autoIncrementTypes, true) && + $primaryKey === [$name] && $name === 'id' + ) { + $data['autoIncrement'] = true; + } + + return $this->columnDefinitionSql($data); + } + /** + * @inheritDoc + */ + public function columnDefinitionSql(array $column): string + { + $name = $column['name']; + $column += [ + 'length' => null, + 'precision' => null, + ]; $out = $this->_driver->quoteIdentifier($name); $typeMap = [ - TableSchema::TYPE_TINYINTEGER => ' SMALLINT', - TableSchema::TYPE_SMALLINTEGER => ' SMALLINT', - TableSchema::TYPE_BINARY_UUID => ' UUID', - TableSchema::TYPE_BOOLEAN => ' BOOLEAN', - TableSchema::TYPE_FLOAT => ' FLOAT', - TableSchema::TYPE_DECIMAL => ' DECIMAL', - TableSchema::TYPE_DATE => ' DATE', - TableSchema::TYPE_TIME => ' TIME', - TableSchema::TYPE_DATETIME => ' TIMESTAMP', - TableSchema::TYPE_DATETIME_FRACTIONAL => ' TIMESTAMP', - TableSchema::TYPE_TIMESTAMP => ' TIMESTAMP', - TableSchema::TYPE_TIMESTAMP_FRACTIONAL => ' TIMESTAMP', - TableSchema::TYPE_TIMESTAMP_TIMEZONE => ' TIMESTAMPTZ', - TableSchema::TYPE_UUID => ' UUID', - TableSchema::TYPE_CHAR => ' CHAR', - TableSchema::TYPE_JSON => ' JSONB', + TableSchemaInterface::TYPE_TINYINTEGER => ' SMALLINT', + TableSchemaInterface::TYPE_SMALLINTEGER => ' SMALLINT', + TableSchemaInterface::TYPE_INTEGER => ' INT', + TableSchemaInterface::TYPE_BIGINTEGER => ' BIGINT', + TableSchemaInterface::TYPE_BINARY_UUID => ' UUID', + TableSchemaInterface::TYPE_BOOLEAN => ' BOOLEAN', + TableSchemaInterface::TYPE_FLOAT => ' FLOAT', + TableSchemaInterface::TYPE_DECIMAL => ' DECIMAL', + TableSchemaInterface::TYPE_DATE => ' DATE', + TableSchemaInterface::TYPE_TIME => ' TIME', + TableSchemaInterface::TYPE_DATETIME => ' TIMESTAMP', + TableSchemaInterface::TYPE_DATETIME_FRACTIONAL => ' TIMESTAMP', + TableSchemaInterface::TYPE_TIMESTAMP => ' TIMESTAMP', + TableSchemaInterface::TYPE_TIMESTAMP_FRACTIONAL => ' TIMESTAMP', + TableSchemaInterface::TYPE_TIMESTAMP_TIMEZONE => ' TIMESTAMPTZ', + TableSchemaInterface::TYPE_UUID => ' UUID', + TableSchemaInterface::TYPE_NATIVE_UUID => ' UUID', + TableSchemaInterface::TYPE_CHAR => ' CHAR', + TableSchemaInterface::TYPE_JSON => ' JSONB', + TableSchemaInterface::TYPE_GEOMETRY => ' GEOGRAPHY(GEOMETRY, %s)', + TableSchemaInterface::TYPE_POINT => ' GEOGRAPHY(POINT, %s)', + TableSchemaInterface::TYPE_LINESTRING => ' GEOGRAPHY(LINESTRING, %s)', + TableSchemaInterface::TYPE_POLYGON => ' GEOGRAPHY(POLYGON, %s)', ]; - if (isset($typeMap[$data['type']])) { - $out .= $typeMap[$data['type']]; + $autoIncrementTypes = [ + TableSchemaInterface::TYPE_TINYINTEGER, + TableSchemaInterface::TYPE_SMALLINTEGER, + TableSchemaInterface::TYPE_INTEGER, + TableSchemaInterface::TYPE_BIGINTEGER, + ]; + $autoIncrement = (bool)($column['autoIncrement'] ?? false); + if ( + in_array($column['type'], $autoIncrementTypes, true) && + $autoIncrement + ) { + $typeMap[$column['type']] = str_replace('INT', 'SERIAL', $typeMap[$column['type']]); + unset($column['default']); } - if ($data['type'] === TableSchema::TYPE_INTEGER || $data['type'] === TableSchema::TYPE_BIGINTEGER) { - $type = $data['type'] === TableSchema::TYPE_INTEGER ? ' INTEGER' : ' BIGINT'; - if ($schema->getPrimaryKey() === [$name] || $data['autoIncrement'] === true) { - $type = $data['type'] === TableSchema::TYPE_INTEGER ? ' SERIAL' : ' BIGSERIAL'; - unset($data['null'], $data['default']); - } - $out .= $type; + if (isset($typeMap[$column['type']])) { + $out .= $typeMap[$column['type']]; } - if ($data['type'] === TableSchema::TYPE_TEXT && $data['length'] !== TableSchema::LENGTH_TINY) { + if ($column['type'] === TableSchemaInterface::TYPE_TEXT && $column['length'] !== TableSchema::LENGTH_TINY) { $out .= ' TEXT'; } - if ($data['type'] === TableSchema::TYPE_BINARY) { + if ($column['type'] === TableSchemaInterface::TYPE_BINARY) { $out .= ' BYTEA'; } - if ($data['type'] === TableSchema::TYPE_CHAR) { - $out .= '(' . $data['length'] . ')'; + if ($column['type'] === TableSchemaInterface::TYPE_CHAR) { + $out .= '(' . $column['length'] . ')'; } if ( - $data['type'] === TableSchema::TYPE_STRING || + $column['type'] === TableSchemaInterface::TYPE_STRING || ( - $data['type'] === TableSchema::TYPE_TEXT && - $data['length'] === TableSchema::LENGTH_TINY + $column['type'] === TableSchemaInterface::TYPE_TEXT && + $column['length'] === TableSchema::LENGTH_TINY ) ) { $out .= ' VARCHAR'; - if (isset($data['length']) && $data['length'] !== '') { - $out .= '(' . $data['length'] . ')'; + if (isset($column['length']) && $column['length'] !== '') { + $out .= '(' . $column['length'] . ')'; } } - $hasCollate = [TableSchema::TYPE_TEXT, TableSchema::TYPE_STRING, TableSchema::TYPE_CHAR]; - if (in_array($data['type'], $hasCollate, true) && isset($data['collate']) && $data['collate'] !== '') { - $out .= ' COLLATE "' . $data['collate'] . '"'; + $hasCollate = [ + TableSchemaInterface::TYPE_TEXT, + TableSchemaInterface::TYPE_STRING, + TableSchemaInterface::TYPE_CHAR, + ]; + if (in_array($column['type'], $hasCollate, true) && isset($column['collate']) && $column['collate'] !== '') { + $out .= ' COLLATE "' . $column['collate'] . '"'; } $hasPrecision = [ - TableSchema::TYPE_FLOAT, - TableSchema::TYPE_DATETIME, - TableSchema::TYPE_DATETIME_FRACTIONAL, - TableSchema::TYPE_TIMESTAMP, - TableSchema::TYPE_TIMESTAMP_FRACTIONAL, - TableSchema::TYPE_TIMESTAMP_TIMEZONE, + TableSchemaInterface::TYPE_FLOAT, + TableSchemaInterface::TYPE_DATETIME, + TableSchemaInterface::TYPE_DATETIME_FRACTIONAL, + TableSchemaInterface::TYPE_TIMESTAMP, + TableSchemaInterface::TYPE_TIMESTAMP_FRACTIONAL, + TableSchemaInterface::TYPE_TIMESTAMP_TIMEZONE, ]; - if (in_array($data['type'], $hasPrecision) && isset($data['precision'])) { - $out .= '(' . $data['precision'] . ')'; + if (in_array($column['type'], $hasPrecision) && isset($column['precision'])) { + $out .= '(' . $column['precision'] . ')'; } if ( - $data['type'] === TableSchema::TYPE_DECIMAL && + $column['type'] === TableSchemaInterface::TYPE_DECIMAL && ( - isset($data['length']) || - isset($data['precision']) + isset($column['length']) || + isset($column['precision']) ) ) { - $out .= '(' . $data['length'] . ',' . (int)$data['precision'] . ')'; + $out .= '(' . $column['length'] . ',' . (int)$column['precision'] . ')'; + } + if (in_array($column['type'], TableSchemaInterface::GEOSPATIAL_TYPES)) { + $out = sprintf($out, $column['srid'] ?? self::DEFAULT_SRID); } - if (isset($data['null']) && $data['null'] === false) { + if (isset($column['null']) && $column['null'] === false) { $out .= ' NOT NULL'; } $datetimeTypes = [ - TableSchema::TYPE_DATETIME, - TableSchema::TYPE_DATETIME_FRACTIONAL, - TableSchema::TYPE_TIMESTAMP, - TableSchema::TYPE_TIMESTAMP_FRACTIONAL, - TableSchema::TYPE_TIMESTAMP_TIMEZONE, + TableSchemaInterface::TYPE_DATETIME, + TableSchemaInterface::TYPE_DATETIME_FRACTIONAL, + TableSchemaInterface::TYPE_TIMESTAMP, + TableSchemaInterface::TYPE_TIMESTAMP_FRACTIONAL, + TableSchemaInterface::TYPE_TIMESTAMP_TIMEZONE, ]; if ( - isset($data['default']) && - in_array($data['type'], $datetimeTypes) && - strtolower($data['default']) === 'current_timestamp' + isset($column['default']) && + in_array($column['type'], $datetimeTypes) && + is_string($column['default']) && + strtolower($column['default']) === 'current_timestamp' ) { $out .= ' DEFAULT CURRENT_TIMESTAMP'; - } elseif (isset($data['default'])) { - $defaultValue = $data['default']; - if ($data['type'] === 'boolean') { + } elseif (isset($column['default'])) { + $defaultValue = $column['default']; + if ($column['type'] === 'boolean') { $defaultValue = (bool)$defaultValue; } $out .= ' DEFAULT ' . $this->_driver->schemaValue($defaultValue); - } elseif (isset($data['null']) && $data['null'] !== false) { + } elseif (isset($column['null']) && $column['null'] !== false) { $out .= ' DEFAULT NULL'; } @@ -537,8 +791,8 @@ public function addConstraintSql(TableSchema $schema): array $sql = []; foreach ($schema->constraints() as $name) { - /** @var array $constraint */ $constraint = $schema->getConstraint($name); + assert($constraint !== null); if ($constraint['type'] === TableSchema::CONSTRAINT_FOREIGN) { $tableName = $this->_driver->quoteIdentifier($schema->name()); $sql[] = sprintf($sqlPattern, $tableName, $this->constraintSql($schema, $name)); @@ -557,8 +811,8 @@ public function dropConstraintSql(TableSchema $schema): array $sql = []; foreach ($schema->constraints() as $name) { - /** @var array $constraint */ $constraint = $schema->getConstraint($name); + assert($constraint !== null); if ($constraint['type'] === TableSchema::CONSTRAINT_FOREIGN) { $tableName = $this->_driver->quoteIdentifier($schema->name()); $constraintName = $this->_driver->quoteIdentifier($name); @@ -574,18 +828,18 @@ public function dropConstraintSql(TableSchema $schema): array */ public function indexSql(TableSchema $schema, string $name): string { - /** @var array $data */ $data = $schema->getIndex($name); + assert($data !== null); $columns = array_map( - [$this->_driver, 'quoteIdentifier'], - $data['columns'] + $this->_driver->quoteIdentifier(...), + $data['columns'], ); return sprintf( 'CREATE INDEX %s ON %s (%s)', $this->_driver->quoteIdentifier($name), $this->_driver->quoteIdentifier($schema->name()), - implode(', ', $columns) + implode(', ', $columns), ); } @@ -594,8 +848,8 @@ public function indexSql(TableSchema $schema, string $name): string */ public function constraintSql(TableSchema $schema, string $name): string { - /** @var array $data */ $data = $schema->getConstraint($name); + assert($data !== null); $out = 'CONSTRAINT ' . $this->_driver->quoteIdentifier($name); if ($data['type'] === TableSchema::CONSTRAINT_PRIMARY) { $out = 'PRIMARY KEY'; @@ -617,8 +871,8 @@ public function constraintSql(TableSchema $schema, string $name): string protected function _keySql(string $prefix, array $data): string { $columns = array_map( - [$this->_driver, 'quoteIdentifier'], - $data['columns'] + $this->_driver->quoteIdentifier(...), + $data['columns'], ); if ($data['type'] === TableSchema::CONSTRAINT_FOREIGN) { return $prefix . sprintf( @@ -627,7 +881,7 @@ protected function _keySql(string $prefix, array $data): string $this->_driver->quoteIdentifier($data['references'][0]), $this->_convertConstraintColumns($data['references'][1]), $this->_foreignOnClause($data['update']), - $this->_foreignOnClause($data['delete']) + $this->_foreignOnClause($data['delete']), ); } @@ -643,7 +897,7 @@ public function createTableSql(TableSchema $schema, array $columns, array $const $content = implode(",\n", array_filter($content)); $tableName = $this->_driver->quoteIdentifier($schema->name()); $dbSchema = $this->_driver->schema(); - if ($dbSchema != 'public') { + if ($dbSchema !== 'public') { $tableName = $this->_driver->quoteIdentifier($dbSchema) . '.' . $tableName; } $temporary = $schema->isTemporary() ? ' TEMPORARY ' : ' '; @@ -659,7 +913,7 @@ public function createTableSql(TableSchema $schema, array $columns, array $const 'COMMENT ON COLUMN %s.%s IS %s', $tableName, $this->_driver->quoteIdentifier($column), - $this->_driver->schemaValue($columnData['comment']) + $this->_driver->schemaValue($columnData['comment']), ); } } @@ -689,16 +943,9 @@ public function dropTableSql(TableSchema $schema): array { $sql = sprintf( 'DROP TABLE %s CASCADE', - $this->_driver->quoteIdentifier($schema->name()) + $this->_driver->quoteIdentifier($schema->name()), ); return [$sql]; } } - -// phpcs:disable -class_alias( - 'Cake\Database\Schema\PostgresSchemaDialect', - 'Cake\Database\Schema\PostgresSchema' -); -// phpcs:enable diff --git a/app/vendor/cakephp/cakephp/src/Database/Schema/SchemaDialect.php b/app/vendor/cakephp/cakephp/src/Database/Schema/SchemaDialect.php index a9a1f9168..400763ed8 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Schema/SchemaDialect.php +++ b/app/vendor/cakephp/cakephp/src/Database/Schema/SchemaDialect.php @@ -16,10 +16,14 @@ */ namespace Cake\Database\Schema; -use Cake\Database\DriverInterface; +use Cake\Database\Driver; +use Cake\Database\Exception\DatabaseException; +use Cake\Database\Exception\QueryException; use Cake\Database\Type\ColumnSchemaAwareInterface; use Cake\Database\TypeFactory; use InvalidArgumentException; +use PDOException; +use function Cake\Core\deprecationWarning; /** * Base class for schema implementations. @@ -27,6 +31,11 @@ * This class contains methods that are common across * the various SQL dialects. * + * Provides methods for performing schema reflection. Results + * will be in the form of structured arrays. The structure + * of each result will be documented in this class. Subclasses + * are free to include *additional* data that is not documented. + * * @method array listTablesWithoutViewsSql(array $config) Generate the SQL to list the tables, excluding all views. */ abstract class SchemaDialect @@ -34,9 +43,9 @@ abstract class SchemaDialect /** * The driver instance being used. * - * @var \Cake\Database\DriverInterface + * @var \Cake\Database\Driver */ - protected $_driver; + protected Driver $_driver; /** * Constructor @@ -44,9 +53,9 @@ abstract class SchemaDialect * This constructor will connect the driver so that methods like columnSql() and others * will fail when the driver has not been connected. * - * @param \Cake\Database\DriverInterface $driver The driver to use. + * @param \Cake\Database\Driver $driver The driver to use. */ - public function __construct(DriverInterface $driver) + public function __construct(Driver $driver) { $driver->connect(); $this->_driver = $driver; @@ -104,15 +113,15 @@ protected function _convertOnClause(string $clause): string * @param array|string $references The referenced columns of a foreign key constraint statement * @return string */ - protected function _convertConstraintColumns($references): string + protected function _convertConstraintColumns(array|string $references): string { if (is_string($references)) { return $this->_driver->quoteIdentifier($references); } return implode(', ', array_map( - [$this->_driver, 'quoteIdentifier'], - $references + $this->_driver->quoteIdentifier(...), + $references, )); } @@ -129,7 +138,7 @@ protected function _convertConstraintColumns($references): string protected function _getTypeSpecificColumnSql( string $columnType, TableSchemaInterface $schema, - string $column + string $column, ): ?string { if (!TypeFactory::getMap($columnType)) { return null; @@ -176,7 +185,7 @@ public function dropTableSql(TableSchema $schema): array { $sql = sprintf( 'DROP TABLE %s', - $this->_driver->quoteIdentifier($schema->name()) + $this->_driver->quoteIdentifier($schema->name()), ); return [$sql]; @@ -188,6 +197,7 @@ public function dropTableSql(TableSchema $schema): array * @param array $config The connection configuration to use for * getting tables from. * @return array An array of (sql, params) to execute. + * @deprecated 5.2.0 Use `listTables()` instead. */ abstract public function listTablesSql(array $config): array; @@ -197,6 +207,7 @@ abstract public function listTablesSql(array $config): array; * @param string $tableName The table name to get information on. * @param array $config The connection configuration. * @return array An array of (sql, params) to execute. + * @deprecated 5.2.0 Use `describeColumns()` instead. */ abstract public function describeColumnSql(string $tableName, array $config): array; @@ -206,6 +217,7 @@ abstract public function describeColumnSql(string $tableName, array $config): ar * @param string $tableName The table name to get information on. * @param array $config The connection configuration. * @return array An array of (sql, params) to execute. + * @deprecated 5.2.0 Use `describeIndexes()` instead. */ abstract public function describeIndexSql(string $tableName, array $config): array; @@ -215,6 +227,7 @@ abstract public function describeIndexSql(string $tableName, array $config): arr * @param string $tableName The table name to get information on. * @param array $config The connection configuration. * @return array An array of (sql, params) to execute. + * @deprecated 5.2.0 Use `describeForeignKeys()` instead. */ abstract public function describeForeignKeySql(string $tableName, array $config): array; @@ -224,6 +237,7 @@ abstract public function describeForeignKeySql(string $tableName, array $config) * @param string $tableName Table name. * @param array $config The connection configuration. * @return array SQL statements to get options for a table. + * @deprecated 5.2.0 Use `describeOptions()` instead. */ public function describeOptionsSql(string $tableName, array $config): array { @@ -236,6 +250,7 @@ public function describeOptionsSql(string $tableName, array $config): array * @param \Cake\Database\Schema\TableSchema $schema The table object to append fields to. * @param array $row The row data from `describeColumnSql`. * @return void + * @deprecated 5.2.0 Use `describeColumns()` instead. */ abstract public function convertColumnDescription(TableSchema $schema, array $row): void; @@ -246,6 +261,7 @@ abstract public function convertColumnDescription(TableSchema $schema, array $ro * an index or constraint to. * @param array $row The row data from `describeIndexSql`. * @return void + * @deprecated 5.2.0 Use `describeIndexes()` instead. */ abstract public function convertIndexDescription(TableSchema $schema, array $row): void; @@ -256,6 +272,7 @@ abstract public function convertIndexDescription(TableSchema $schema, array $row * a constraint to. * @param array $row The row data from `describeForeignKeySql`. * @return void + * @deprecated 5.2.0 Use `describeForeignKeys()` instead. */ abstract public function convertForeignKeyDescription(TableSchema $schema, array $row): void; @@ -265,6 +282,7 @@ abstract public function convertForeignKeyDescription(TableSchema $schema, array * @param \Cake\Database\Schema\TableSchema $schema Table instance. * @param array $row The row of data. * @return void + * @deprecated 5.2.0 Use `describeOptions()` instead. */ public function convertOptionsDescription(TableSchema $schema, array $row): void { @@ -283,7 +301,7 @@ abstract public function createTableSql( TableSchema $schema, array $columns, array $constraints, - array $indexes + array $indexes, ): array; /** @@ -336,11 +354,374 @@ abstract public function indexSql(TableSchema $schema, string $name): string; * @return array SQL statements to truncate a table. */ abstract public function truncateTableSql(TableSchema $schema): array; -} -// phpcs:disable -class_alias( - 'Cake\Database\Schema\SchemaDialect', - 'Cake\Database\Schema\BaseSchema' -); -// phpcs:enable + /** + * Create a SQL snippet for a column based on the array shape + * that `describeColumns()` creates. + * + * @param array $column The column metadata + * @return string Generated SQL fragment for a column + */ + public function columnDefinitionSql(array $column): string + { + deprecationWarning( + '5.2.0', + 'SchemaDialect subclasses need to implement `columnDefinitionSql` before 6.0.0', + ); + $table = new TableSchema('placeholder'); + $table->addColumn($column['name'], $column); + + return $this->columnSql($table, $column['name']); + } + + /** + * Get the list of tables, excluding any views, available in the current connection. + * + * @return array The list of tables in the connected database/schema. + */ + public function listTablesWithoutViews(): array + { + [$sql, $params] = $this->listTablesWithoutViewsSql($this->_driver->config()); + $result = []; + $statement = $this->_driver->execute($sql, $params); + while ($row = $statement->fetch()) { + $result[] = $row[0]; + } + + return $result; + } + + /** + * Get the list of tables and views available in the current connection. + * + * @return array The list of tables and views in the connected database/schema. + */ + public function listTables(): array + { + [$sql, $params] = $this->listTablesSql($this->_driver->config()); + $result = []; + $statement = $this->_driver->execute($sql, $params); + while ($row = $statement->fetch()) { + $result[] = $row[0]; + } + + return $result; + } + + /** + * Get the column metadata for a table. + * + * The name can include a database schema name in the form 'schema.table'. + * + * @param string $name The name of the table to describe. + * @return \Cake\Database\Schema\TableSchemaInterface Object with column metadata. + * @throws \Cake\Database\Exception\DatabaseException when table cannot be described. + */ + public function describe(string $name): TableSchemaInterface + { + $tableName = $name; + if (str_contains($name, '.')) { + $tableName = explode('.', $name)[1]; + } + $table = $this->_driver->newTableSchema($tableName); + foreach ($this->describeColumns($name) as $column) { + $table->addColumn($column['name'], $column); + } + foreach ($this->describeIndexes($name) as $index) { + if (in_array($index['type'], [TableSchema::CONSTRAINT_UNIQUE, TableSchema::CONSTRAINT_PRIMARY])) { + $table->addConstraint($index['name'], $index); + } else { + $table->addIndex($index['name'], $index); + } + } + foreach ($this->describeForeignKeys($name) as $key) { + $table->addConstraint($key['name'], $key); + } + $options = $this->describeOptions($name); + if ($options) { + $table->setOptions($options); + } + if ($table->columns() === []) { + throw new DatabaseException(sprintf('Cannot describe %s. It has 0 columns.', $name)); + } + + return $table; + } + + /** + * Get a list of column metadata as a array + * + * Each item in the array will contain the following: + * + * - name : the name of the column. + * - type : the abstract type of the column. + * - length : the length of the column. + * - default : the default value of the column or null. + * - null : boolean indicating whether the column can be null. + * - comment : the column comment or null. + * + * Additionaly the `autoIncrement` key will be set for columns that are a primary key. + * + * @param string $tableName The name of the table to describe columns on. + * @return array + */ + public function describeColumns(string $tableName): array + { + deprecationWarning( + '5.2.0', + 'SchemaDialect subclasses need to implement `describeColumns` before 6.0.0', + ); + $config = $this->_driver->config(); + if (str_contains($tableName, '.')) { + [$config['schema'], $tableName] = explode('.', $tableName); + } + /** @var \Cake\Database\Schema\TableSchema $table */ + $table = $this->_driver->newTableSchema($tableName); + + [$sql, $params] = $this->describeColumnSql($tableName, $config); + $statement = $this->_driver->execute($sql, $params); + foreach ($statement->fetchAll('assoc') as $row) { + $this->convertColumnDescription($table, $row); + } + $columns = []; + foreach ($table->columns() as $columnName) { + $column = $table->getColumn($columnName); + $column['name'] = $columnName; + $columns[] = $column; + } + + return $columns; + } + + /** + * Get a list of constraint metadata as a array + * + * Each item in the array will contain the following: + * + * - name : The name of the constraint + * - type : the type of the constraint. Generally `foreign`. + * - columns : the columns in the constraint on the. + * - references : A list of the table + all columns in the referenced table + * - update : The update action or null + * - delete : The delete action or null + * + * @param string $tableName The name of the table to describe foreign keys on. + * @return array + */ + public function describeForeignKeys(string $tableName): array + { + deprecationWarning( + '5.2.0', + 'SchemaDialect subclasses need to implement `describeForeignKeys` before 6.0.0', + ); + $config = $this->_driver->config(); + if (str_contains($tableName, '.')) { + [$config['schema'], $tableName] = explode('.', $tableName); + } + /** @var \Cake\Database\Schema\TableSchema $table */ + $table = $this->_driver->newTableSchema($tableName); + // Add the columns because TableSchema needs them. + foreach ($this->describeColumns($tableName) as $column) { + $table->addColumn($column['name'], $column); + } + + [$sql, $params] = $this->describeForeignKeySql($tableName, $config); + $statement = $this->_driver->execute($sql, $params); + foreach ($statement->fetchAll('assoc') as $row) { + $this->convertForeignKeyDescription($table, $row); + } + $keys = []; + foreach ($table->constraints() as $name) { + $key = $table->getConstraint($name); + $key['name'] = $name; + $keys[] = $key; + } + + return $keys; + } + + /** + * Get a list of index metadata as a array + * + * Each item in the array will contain the following: + * + * - name : the name of the index. + * - type : the type of the index. One of `unique`, `index`, `primary`. + * - columns : the columns in the index. + * - length : the length of the index if applicable. + * + * @param string $tableName The name of the table to describe indexes on. + * @return array + */ + public function describeIndexes(string $tableName): array + { + deprecationWarning( + '5.2.0', + 'SchemaDialect subclasses need to implement `describeIndexes` before 6.0.0', + ); + $config = $this->_driver->config(); + if (str_contains($tableName, '.')) { + [$config['schema'], $tableName] = explode('.', $tableName); + } + /** @var \Cake\Database\Schema\TableSchema $table */ + $table = $this->_driver->newTableSchema($tableName); + // Add the columns because TableSchema needs them. + foreach ($this->describeColumns($tableName) as $column) { + $table->addColumn($column['name'], $column); + } + + [$sql, $params] = $this->describeIndexSql($tableName, $config); + $statement = $this->_driver->execute($sql, $params); + foreach ($statement->fetchAll('assoc') as $row) { + $this->convertIndexDescription($table, $row); + } + $indexes = []; + foreach ($table->indexes() as $name) { + $index = $table->getIndex($name); + $index['name'] = $name; + $indexes[] = $index; + } + + return $indexes; + } + + /** + * Get platform specific options + * + * No keys are guaranteed to be present as they are database driver dependent. + * + * @param string $tableName The name of the table to describe options on. + * @return array + */ + public function describeOptions(string $tableName): array + { + deprecationWarning( + '5.2.0', + 'SchemaDialect subclasses need to implement `describeOptions` before 6.0.0', + ); + $config = $this->_driver->config(); + if (str_contains($tableName, '.')) { + [$config['schema'], $tableName] = explode('.', $tableName); + } + /** @var \Cake\Database\Schema\TableSchema $table */ + $table = $this->_driver->newTableSchema($tableName); + + [$sql, $params] = $this->describeOptionsSql($tableName, $config); + if ($sql) { + $statement = $this->_driver->execute($sql, $params); + foreach ($statement->fetchAll('assoc') as $row) { + $this->convertOptionsDescription($table, $row); + } + } + + return $table->getOptions(); + } + + /** + * Check if a table has a column with a given name. + * + * @param string $tableName The name of the table + * @param string $columnName The name of the column + * @return bool + */ + public function hasColumn(string $tableName, string $columnName): bool + { + try { + $columns = $this->describeColumns($tableName); + } catch (PDOException | DatabaseException) { + return false; + } + foreach ($columns as $column) { + if ($column['name'] === $columnName) { + return true; + } + } + + return false; + } + + /** + * Check if a table exists + * + * @param string $tableName The name of the table + * @return bool + */ + public function hasTable(string $tableName): bool + { + $tables = $this->listTables(); + + return in_array($tableName, $tables, true); + } + + /** + * Check if a table has an index with a given name. + * + * @param string $tableName The name of the table + * @param array $columns The columns in the index. Specific + * ordering matters. + * @param string $name The name of the index to match on. Can be used alone, + * or with $columns to match indexes more precisely. + * @return bool + */ + public function hasIndex(string $tableName, array $columns = [], ?string $name = null): bool + { + try { + $indexes = $this->describeIndexes($tableName); + } catch (QueryException) { + return false; + } + $found = null; + foreach ($indexes as $index) { + if ($columns && $index['columns'] === $columns) { + $found = $index; + break; + } + if ($columns === [] && $name !== null && $index['name'] === $name) { + $found = $index; + break; + } + } + // Both columns and name provided, both must match; + if ($found !== null && $name !== null && $found['name'] !== $name) { + return false; + } + + return $found !== null; + } + + /** + * Check if a table has a foreign key with a given name. + * + * @param string $tableName The name of the table + * @param array $columns The columns in the foriegn key. Specific + * ordering matters. + * @param string $name The name of the foreign key to match on. Can be used alone, + * or with $columns to match keys more precisely. + * @return bool + */ + public function hasForeignKey(string $tableName, array $columns = [], ?string $name = null): bool + { + try { + $keys = $this->describeForeignKeys($tableName); + } catch (QueryException) { + return false; + } + $found = null; + foreach ($keys as $key) { + if ($columns && $key['columns'] === $columns) { + $found = $key; + break; + } + if (!$columns && $name !== null && $key['name'] === $name) { + $found = $key; + break; + } + } + // Both columns and name provided, both must match; + if ($found !== null && $name !== null && $found['name'] !== $name) { + return false; + } + + return $found !== null; + } +} diff --git a/app/vendor/cakephp/cakephp/src/Database/Schema/SqliteSchema.php b/app/vendor/cakephp/cakephp/src/Database/Schema/SqliteSchema.php deleted file mode 100644 index d08f08b8a..000000000 --- a/app/vendor/cakephp/cakephp/src/Database/Schema/SqliteSchema.php +++ /dev/null @@ -1,10 +0,0 @@ - - */ - protected $_constraintsIdMap = []; - /** * Whether there is any table in this connection to SQLite containing sequences. * * @var bool */ - protected $_hasSequences; + protected bool $_hasSequences; /** * Convert a column definition to the abstract types. @@ -53,12 +47,12 @@ class SqliteSchemaDialect extends SchemaDialect protected function _convertColumn(string $column): array { if ($column === '') { - return ['type' => TableSchema::TYPE_TEXT, 'length' => null]; + return ['type' => TableSchemaInterface::TYPE_TEXT, 'length' => null]; } preg_match('/(unsigned)?\s*([a-z]+)(?:\(([0-9,]+)\))?/i', $column, $matches); - if (empty($matches)) { - throw new DatabaseException(sprintf('Unable to parse column type from "%s"', $column)); + if (!$matches) { + throw new DatabaseException(sprintf('Unable to parse column type from `%s`', $column)); } $unsigned = false; @@ -67,10 +61,12 @@ protected function _convertColumn(string $column): array } $col = strtolower($matches[2]); - $length = $precision = $scale = null; + $length = null; + $precision = null; + $scale = null; if (isset($matches[3])) { $length = $matches[3]; - if (strpos($length, ',') !== false) { + if (str_contains($length, ',')) { [$length, $precision] = explode(',', $length); } $length = (int)$length; @@ -79,27 +75,27 @@ protected function _convertColumn(string $column): array $type = $this->_applyTypeSpecificColumnConversion( $col, - compact('length', 'precision', 'scale') + compact('length', 'precision', 'scale'), ); if ($type !== null) { return $type; } if ($col === 'bigint') { - return ['type' => TableSchema::TYPE_BIGINTEGER, 'length' => $length, 'unsigned' => $unsigned]; + return ['type' => TableSchemaInterface::TYPE_BIGINTEGER, 'length' => $length, 'unsigned' => $unsigned]; } if ($col === 'smallint') { - return ['type' => TableSchema::TYPE_SMALLINTEGER, 'length' => $length, 'unsigned' => $unsigned]; + return ['type' => TableSchemaInterface::TYPE_SMALLINTEGER, 'length' => $length, 'unsigned' => $unsigned]; } if ($col === 'tinyint') { - return ['type' => TableSchema::TYPE_TINYINTEGER, 'length' => $length, 'unsigned' => $unsigned]; + return ['type' => TableSchemaInterface::TYPE_TINYINTEGER, 'length' => $length, 'unsigned' => $unsigned]; } - if (strpos($col, 'int') !== false) { - return ['type' => TableSchema::TYPE_INTEGER, 'length' => $length, 'unsigned' => $unsigned]; + if (str_contains($col, 'int') && $col !== 'point') { + return ['type' => TableSchemaInterface::TYPE_INTEGER, 'length' => $length, 'unsigned' => $unsigned]; } - if (strpos($col, 'decimal') !== false) { + if (str_contains($col, 'decimal')) { return [ - 'type' => TableSchema::TYPE_DECIMAL, + 'type' => TableSchemaInterface::TYPE_DECIMAL, 'length' => $length, 'precision' => $precision, 'unsigned' => $unsigned, @@ -107,32 +103,32 @@ protected function _convertColumn(string $column): array } if (in_array($col, ['float', 'real', 'double'])) { return [ - 'type' => TableSchema::TYPE_FLOAT, + 'type' => TableSchemaInterface::TYPE_FLOAT, 'length' => $length, 'precision' => $precision, 'unsigned' => $unsigned, ]; } - if (strpos($col, 'boolean') !== false) { - return ['type' => TableSchema::TYPE_BOOLEAN, 'length' => null]; + if (str_contains($col, 'boolean')) { + return ['type' => TableSchemaInterface::TYPE_BOOLEAN, 'length' => null]; } + if (($col === 'binary' && $length === 16) || strtolower($column) === 'uuid_blob') { + return ['type' => TableSchemaInterface::TYPE_BINARY_UUID, 'length' => null]; + } if (($col === 'char' && $length === 36) || $col === 'uuid') { - return ['type' => TableSchema::TYPE_UUID, 'length' => null]; + return ['type' => TableSchemaInterface::TYPE_UUID, 'length' => null]; } if ($col === 'char') { - return ['type' => TableSchema::TYPE_CHAR, 'length' => $length]; + return ['type' => TableSchemaInterface::TYPE_CHAR, 'length' => $length]; } - if (strpos($col, 'char') !== false) { - return ['type' => TableSchema::TYPE_STRING, 'length' => $length]; + if (str_contains($col, 'char')) { + return ['type' => TableSchemaInterface::TYPE_STRING, 'length' => $length]; } - if ($col === 'binary' && $length === 16) { - return ['type' => TableSchema::TYPE_BINARY_UUID, 'length' => null]; - } if (in_array($col, ['blob', 'clob', 'binary', 'varbinary'])) { - return ['type' => TableSchema::TYPE_BINARY, 'length' => $length]; + return ['type' => TableSchemaInterface::TYPE_BINARY, 'length' => $length]; } $datetimeTypes = [ @@ -148,7 +144,22 @@ protected function _convertColumn(string $column): array return ['type' => $col, 'length' => null]; } - return ['type' => TableSchema::TYPE_TEXT, 'length' => null]; + if (Configure::read('ORM.mapJsonTypeForSqlite') === true) { + if (str_contains($col, TableSchemaInterface::TYPE_JSON) && !str_contains($col, 'jsonb')) { + return ['type' => TableSchemaInterface::TYPE_JSON, 'length' => null]; + } + } + + if (in_array($col, TableSchemaInterface::GEOSPATIAL_TYPES)) { + // TODO how can srid be preserved? It doesn't come back + // in the output of show full columns from ... + return [ + 'type' => $col, + 'length' => null, + ]; + } + + return ['type' => TableSchemaInterface::TYPE_TEXT, 'length' => null]; } /** @@ -189,10 +200,7 @@ public function listTablesWithoutViewsSql(array $config): array */ public function describeColumnSql(string $tableName, array $config): array { - $sql = sprintf( - 'PRAGMA table_info(%s)', - $this->_driver->quoteIdentifier($tableName) - ); + $sql = $this->describeColumnQuery($tableName); return [$sql, []]; } @@ -217,7 +225,6 @@ public function convertColumnDescription(TableSchema $schema, array $row): void // SQLite does not support autoincrement on composite keys. if ($row['pk'] && !empty($primary)) { $existingColumn = $primary['columns'][0]; - /** @psalm-suppress PossiblyNullOperand */ $schema->addColumn($existingColumn, ['autoIncrement' => null] + $schema->getColumn($existingColumn)); } @@ -232,6 +239,64 @@ public function convertColumnDescription(TableSchema $schema, array $row): void } } + /** + * Helper method for creating SQL to describe columns in a table. + * + * @param string $tableName The table to describe. + * @return string SQL to reflect columns + */ + private function describeColumnQuery(string $tableName): string + { + $pragma = 'table_xinfo'; + if (version_compare($this->_driver->version(), '3.26.0', '<')) { + $pragma = 'table_info'; + } + + return sprintf( + 'PRAGMA %s(%s)', + $pragma, + $this->_driver->quoteIdentifier($tableName), + ); + } + + /** + * @inheritDoc + */ + public function describeColumns(string $tableName): array + { + $config = $this->_driver->config(); + if (str_contains($tableName, '.')) { + [$config['schema'], $tableName] = explode('.', $tableName); + } + $sql = $this->describeColumnQuery($tableName); + $columns = []; + $statement = $this->_driver->execute($sql); + $primary = []; + foreach ($statement->fetchAll('assoc') as $i => $row) { + $name = $row['name']; + $field = $this->_convertColumn($row['type']); + $field += [ + 'name' => $name, + 'null' => !$row['notnull'], + 'default' => $this->_defaultValue($row['dflt_value']), + 'comment' => null, + 'length' => null, + ]; + if ($row['pk']) { + $primary[] = $i; + } + $columns[] = $field; + } + // If sqlite has a single primary column, it can be marked as autoIncrement + if (count($primary) == 1) { + $offset = $primary[0]; + $columns[$offset]['autoIncrement'] = true; + $columns[$offset]['null'] = false; + } + + return $columns; + } + /** * Manipulate the default value. * @@ -241,7 +306,7 @@ public function convertColumnDescription(TableSchema $schema, array $row): void * @param string|int|null $default The default value. * @return string|int|null */ - protected function _defaultValue($default) + protected function _defaultValue(string|int|null $default): string|int|null { if ($default === 'NULL' || $default === null) { return null; @@ -260,14 +325,55 @@ protected function _defaultValue($default) */ public function describeIndexSql(string $tableName, array $config): array { - $sql = sprintf( - 'PRAGMA index_list(%s)', - $this->_driver->quoteIdentifier($tableName) - ); + $sql = $this->describeIndexQuery($tableName); return [$sql, []]; } + /** + * Generates a regular expression to match identifiers that may or + * may not be quoted with any of the supported quotes. + * + * @param string $identifier The identifier to match. + * @return string + */ + protected function possiblyQuotedIdentifierRegex(string $identifier): string + { + // Trim all quoting characters from the provided identifier, + // and double all quotes up because that's how sqlite returns them. + $identifier = trim($identifier, '\'"`[]'); + $identifier = str_replace(["'", '"', '`'], ["''", '""', '``'], $identifier); + $quoted = preg_quote($identifier, '/'); + + return "[\['\"`]?{$quoted}[\]'\"`]?"; + } + + /** + * Removes possible escape characters and surrounding quotes from + * identifiers. + * + * @param string $value The identifier to normalize. + * @return string + */ + protected function normalizePossiblyQuotedIdentifier(string $value): string + { + $value = trim($value); + + if (str_starts_with($value, '[') && str_ends_with($value, ']')) { + return mb_substr($value, 1, -1); + } + + foreach (['`', "'", '"'] as $quote) { + if (str_starts_with($value, $quote) && str_ends_with($value, $quote)) { + $value = str_replace($quote . $quote, $quote, $value); + + return mb_substr($value, 1, -1); + } + } + + return $value; + } + /** * {@inheritDoc} * @@ -280,22 +386,33 @@ public function describeIndexSql(string $tableName, array $config): array * an index or constraint to. * @param array $row The row data from `describeIndexSql`. * @return void + * @deprecated 5.2.0 Use `describeIndexes` instead. */ public function convertIndexDescription(TableSchema $schema, array $row): void { + // Skip auto-indexes created for non-ROWID primary keys. + if ($row['origin'] === 'pk') { + return; + } + $sql = sprintf( 'PRAGMA index_info(%s)', - $this->_driver->quoteIdentifier($row['name']) + $this->_driver->quoteIdentifier($row['name']), ); - $statement = $this->_driver->prepare($sql); - $statement->execute(); + $statement = $this->_driver->execute($sql); $columns = []; - /** @psalm-suppress PossiblyFalseIterator */ - foreach ($statement->fetchAll('assoc') as $column) { + foreach ($statement->fetchAll(PDO::FETCH_ASSOC) as $column) { $columns[] = $column['name']; } - $statement->closeCursor(); if ($row['unique']) { + if ($row['origin'] === 'u') { + $createTableSql = $this->getCreateTableSql($schema->name()); + $name = $this->extractIndexName($createTableSql, 'UNIQUE', $columns); + if ($name !== null) { + $row['name'] = $name; + } + } + $schema->addConstraint($row['name'], [ 'type' => TableSchema::CONSTRAINT_UNIQUE, 'columns' => $columns, @@ -308,12 +425,143 @@ public function convertIndexDescription(TableSchema $schema, array $row): void } } + /** + * Helper method for creating SQL to reflect indexes in a table. + * + * @param string $tableName The table to get indexes from. + * @return string SQL to reflect indexes + */ + private function describeIndexQuery(string $tableName): string + { + return sprintf( + 'PRAGMA index_list(%s)', + $this->_driver->quoteIdentifier($tableName), + ); + } + + /** + * Try to extract the original constraint name from table sql. + * + * @param string $tableSql The create table statement + * @param string $type The type of index/constraint + * @param array $columns The columns in the index. + * @return string|null The name of the unique index if it could be inferred. + */ + private function extractIndexName(string $tableSql, string $type, array $columns): ?string + { + $columnsPattern = implode( + '\s*,\s*', + array_map( + fn($column) => '(?:' . $this->possiblyQuotedIdentifierRegex($column) . ')', + $columns, + ), + ); + + $regex = "/CONSTRAINT\s*(?.+?)\s*{$type}\s*\(\s*{$columnsPattern}\s*\)/i"; + if (preg_match($regex, $tableSql, $matches)) { + return $this->normalizePossiblyQuotedIdentifier($matches['name']); + } + + return null; + } + + /** + * Get the normalized SQL query used to create a table. + * + * @param string $tableName The tablename + * @return string + */ + private function getCreateTableSql(string $tableName): string + { + $masterSql = "SELECT sql FROM sqlite_master WHERE \"type\" = 'table' AND \"name\" = ?"; + $statement = $this->_driver->execute($masterSql, [$tableName]); + $result = $statement->fetchColumn(0); + + return $result ?: ''; + } + + /** + * @inheritDoc + */ + public function describeIndexes(string $tableName): array + { + $config = $this->_driver->config(); + if (str_contains($tableName, '.')) { + [$config['schema'], $tableName] = explode('.', $tableName); + } + $sql = $this->describeIndexQuery($tableName); + $statement = $this->_driver->execute($sql); + $indexes = []; + $createTableSql = $this->getCreateTableSql($tableName); + + $foundPrimary = false; + foreach ($statement->fetchAll('assoc') as $row) { + $indexName = $row['name']; + $indexSql = sprintf( + 'PRAGMA index_info(%s)', + $this->_driver->quoteIdentifier($indexName), + ); + $columns = []; + $indexData = $this->_driver->execute($indexSql)->fetchAll('assoc'); + foreach ($indexData as $indexItem) { + $columns[] = $indexItem['name']; + } + + $indexType = TableSchema::INDEX_INDEX; + if ($row['unique']) { + $indexType = TableSchema::CONSTRAINT_UNIQUE; + } + if ($row['origin'] === 'pk') { + $indexType = TableSchema::CONSTRAINT_PRIMARY; + $foundPrimary = true; + } + if ($indexType == TableSchema::CONSTRAINT_UNIQUE) { + $name = $this->extractIndexName($createTableSql, 'UNIQUE', $columns); + if ($name !== null) { + $indexName = $name; + } + } + + $indexes[$indexName] = [ + 'name' => $indexName, + 'type' => $indexType, + 'columns' => $columns, + 'length' => [], + ]; + } + // Primary keys aren't always available from the index_info pragma + // instead we have to read the columns again. + if (!$foundPrimary) { + $sql = $this->describeColumnQuery($tableName); + $statement = $this->_driver->execute($sql); + foreach ($statement->fetchAll('assoc') as $row) { + if (!$row['pk']) { + continue; + } + if (!isset($indexes['primary'])) { + $indexes['primary'] = [ + 'name' => 'primary', + 'type' => TableSchema::CONSTRAINT_PRIMARY, + 'columns' => [], + 'length' => [], + ]; + } + $indexes['primary']['columns'][] = $row['name']; + } + } + + return array_values($indexes); + } + /** * @inheritDoc */ public function describeForeignKeySql(string $tableName, array $config): array { - $sql = sprintf('PRAGMA foreign_key_list(%s)', $this->_driver->quoteIdentifier($tableName)); + $sql = sprintf( + 'SELECT id FROM pragma_foreign_key_list(%s) GROUP BY id', + $this->_driver->quoteIdentifier($tableName), + ); return [$sql, []]; } @@ -323,27 +571,97 @@ public function describeForeignKeySql(string $tableName, array $config): array */ public function convertForeignKeyDescription(TableSchema $schema, array $row): void { - $name = $row['from'] . '_fk'; + $sql = sprintf( + 'SELECT * FROM pragma_foreign_key_list(%s) WHERE id = %d ORDER BY seq', + $this->_driver->quoteIdentifier($schema->name()), + $row['id'], + ); + $statement = $this->_driver->prepare($sql); + $statement->execute(); - $update = $row['on_update'] ?? ''; - $delete = $row['on_delete'] ?? ''; $data = [ 'type' => TableSchema::CONSTRAINT_FOREIGN, - 'columns' => [$row['from']], - 'references' => [$row['table'], $row['to']], - 'update' => $this->_convertOnClause($update), - 'delete' => $this->_convertOnClause($delete), + 'columns' => [], + 'references' => [], ]; - if (isset($this->_constraintsIdMap[$schema->name()][$row['id']])) { - $name = $this->_constraintsIdMap[$schema->name()][$row['id']]; + $foreignKey = null; + foreach ($statement->fetchAll(PDO::FETCH_ASSOC) as $foreignKey) { + $data['columns'][] = $foreignKey['from']; + $data['references'][] = $foreignKey['to']; + } + + if (count($data['references']) === 1) { + $data['references'] = [$foreignKey['table'], $data['references'][0]]; } else { - $this->_constraintsIdMap[$schema->name()][$row['id']] = $name; + $data['references'] = [$foreignKey['table'], $data['references']]; } + $data['update'] = $this->_convertOnClause($foreignKey['on_update'] ?? ''); + $data['delete'] = $this->_convertOnClause($foreignKey['on_delete'] ?? ''); + + $name = implode('_', $data['columns']) . '_' . $row['id'] . '_fk'; $schema->addConstraint($name, $data); } + /** + * @inheritDoc + */ + public function describeForeignKeys(string $tableName): array + { + $config = $this->_driver->config(); + if (str_contains($tableName, '.')) { + [$config['schema'], $tableName] = explode('.', $tableName); + } + + $keys = []; + $sql = sprintf('PRAGMA foreign_key_list(%s)', $this->_driver->quoteIdentifier($tableName)); + $statement = $this->_driver->execute($sql); + foreach ($statement->fetchAll('assoc') as $row) { + $id = $row['id']; + if (!isset($keys[$id])) { + $keys[$id] = [ + 'name' => $id, + 'type' => TableSchema::CONSTRAINT_FOREIGN, + 'columns' => [], + 'references' => [$row['table'], []], + 'update' => $this->_convertOnClause($row['on_update'] ?? ''), + 'delete' => $this->_convertOnClause($row['on_delete'] ?? ''), + 'length' => [], + ]; + } + $keys[$id]['columns'][$row['seq']] = $row['from']; + $keys[$id]['references'][1][$row['seq']] = $row['to']; + } + + $createTableSql = $this->getCreateTableSql($tableName); + foreach ($keys as $id => $data) { + // sqlite doesn't provide a simple way to get foreign key names, but we + // can extract them from the normalized create table sql. + $name = $this->extractIndexName($createTableSql, 'FOREIGN\s*KEY', $data['columns']); + if ($name === null) { + $name = implode('_', $data['columns']) . '_' . $id . '_fk'; + } + $keys[$id]['name'] = $name; + + // Collapse single columns to a string. + // Long term this should go away, as we can narrow the types on `references` + if (count($data['references'][1]) === 1) { + $keys[$id]['references'][1] = $data['references'][1][0]; + } + } + + return array_values($keys); + } + + /** + * @inheritDoc + */ + public function describeOptions(string $tableName): array + { + return []; + } + /** * {@inheritDoc} * @@ -354,133 +672,175 @@ public function convertForeignKeyDescription(TableSchema $schema, array $row): v */ public function columnSql(TableSchema $schema, string $name): string { - /** @var array $data */ $data = $schema->getColumn($name); + assert($data !== null); $sql = $this->_getTypeSpecificColumnSql($data['type'], $schema, $name); if ($sql !== null) { return $sql; } + $data['name'] = $name; + $autoIncrementTypes = [ + TableSchemaInterface::TYPE_TINYINTEGER, + TableSchemaInterface::TYPE_SMALLINTEGER, + TableSchemaInterface::TYPE_INTEGER, + TableSchemaInterface::TYPE_BIGINTEGER, + ]; + $primaryKey = $schema->getPrimaryKey(); + if ( + in_array($data['type'], $autoIncrementTypes, true) && + $primaryKey === [$name] + ) { + $data['autoIncrement'] = true; + } + // Composite autoincrement columns are not supported. + if (count($primaryKey) > 1) { + unset($data['autoIncrement']); + } + + return $this->columnDefinitionSql($data); + } + + /** + * Create a SQL snippet for a column based on the array shape + * that `describeColumns()` creates. + * + * @param array $column The column metadata + * @return string Generated SQL fragment for a column + */ + public function columnDefinitionSql(array $column): string + { + $name = $column['name']; + $column += [ + 'length' => null, + 'precision' => null, + ]; $typeMap = [ - TableSchema::TYPE_BINARY_UUID => ' BINARY(16)', - TableSchema::TYPE_UUID => ' CHAR(36)', - TableSchema::TYPE_CHAR => ' CHAR', - TableSchema::TYPE_TINYINTEGER => ' TINYINT', - TableSchema::TYPE_SMALLINTEGER => ' SMALLINT', - TableSchema::TYPE_INTEGER => ' INTEGER', - TableSchema::TYPE_BIGINTEGER => ' BIGINT', - TableSchema::TYPE_BOOLEAN => ' BOOLEAN', - TableSchema::TYPE_FLOAT => ' FLOAT', - TableSchema::TYPE_DECIMAL => ' DECIMAL', - TableSchema::TYPE_DATE => ' DATE', - TableSchema::TYPE_TIME => ' TIME', - TableSchema::TYPE_DATETIME => ' DATETIME', - TableSchema::TYPE_DATETIME_FRACTIONAL => ' DATETIMEFRACTIONAL', - TableSchema::TYPE_TIMESTAMP => ' TIMESTAMP', - TableSchema::TYPE_TIMESTAMP_FRACTIONAL => ' TIMESTAMPFRACTIONAL', - TableSchema::TYPE_TIMESTAMP_TIMEZONE => ' TIMESTAMPTIMEZONE', - TableSchema::TYPE_JSON => ' TEXT', + TableSchemaInterface::TYPE_BINARY_UUID => ' BINARY(16)', + TableSchemaInterface::TYPE_UUID => ' CHAR(36)', + TableSchemaInterface::TYPE_CHAR => ' CHAR', + TableSchemaInterface::TYPE_TINYINTEGER => ' TINYINT', + TableSchemaInterface::TYPE_SMALLINTEGER => ' SMALLINT', + TableSchemaInterface::TYPE_INTEGER => ' INTEGER', + TableSchemaInterface::TYPE_BIGINTEGER => ' BIGINT', + TableSchemaInterface::TYPE_BOOLEAN => ' BOOLEAN', + TableSchemaInterface::TYPE_FLOAT => ' FLOAT', + TableSchemaInterface::TYPE_DECIMAL => ' DECIMAL', + TableSchemaInterface::TYPE_DATE => ' DATE', + TableSchemaInterface::TYPE_TIME => ' TIME', + TableSchemaInterface::TYPE_DATETIME => ' DATETIME', + TableSchemaInterface::TYPE_DATETIME_FRACTIONAL => ' DATETIMEFRACTIONAL', + TableSchemaInterface::TYPE_TIMESTAMP => ' TIMESTAMP', + TableSchemaInterface::TYPE_TIMESTAMP_FRACTIONAL => ' TIMESTAMPFRACTIONAL', + TableSchemaInterface::TYPE_TIMESTAMP_TIMEZONE => ' TIMESTAMPTIMEZONE', + TableSchemaInterface::TYPE_JSON => ' TEXT', + TableSchemaInterface::TYPE_GEOMETRY => ' GEOMETRY_TEXT', + TableSchemaInterface::TYPE_POINT => ' POINT_TEXT', + TableSchemaInterface::TYPE_LINESTRING => ' LINESTRING_TEXT', + TableSchemaInterface::TYPE_POLYGON => ' POLYGON_TEXT', ]; $out = $this->_driver->quoteIdentifier($name); $hasUnsigned = [ - TableSchema::TYPE_TINYINTEGER, - TableSchema::TYPE_SMALLINTEGER, - TableSchema::TYPE_INTEGER, - TableSchema::TYPE_BIGINTEGER, - TableSchema::TYPE_FLOAT, - TableSchema::TYPE_DECIMAL, + TableSchemaInterface::TYPE_TINYINTEGER, + TableSchemaInterface::TYPE_SMALLINTEGER, + TableSchemaInterface::TYPE_INTEGER, + TableSchemaInterface::TYPE_BIGINTEGER, + TableSchemaInterface::TYPE_FLOAT, + TableSchemaInterface::TYPE_DECIMAL, ]; + $autoIncrement = (bool)($column['autoIncrement'] ?? false); if ( - in_array($data['type'], $hasUnsigned, true) && - isset($data['unsigned']) && - $data['unsigned'] === true + $autoIncrement !== true && + isset($column['unsigned']) && $column['unsigned'] === true && + in_array($column['type'], $hasUnsigned, true) ) { - if ($data['type'] !== TableSchema::TYPE_INTEGER || $schema->getPrimaryKey() !== [$name]) { - $out .= ' UNSIGNED'; - } + $out .= ' UNSIGNED'; } - if (isset($typeMap[$data['type']])) { - $out .= $typeMap[$data['type']]; + if (isset($typeMap[$column['type']])) { + $out .= $typeMap[$column['type']]; } - if ($data['type'] === TableSchema::TYPE_TEXT && $data['length'] !== TableSchema::LENGTH_TINY) { + if ($column['type'] === TableSchemaInterface::TYPE_TEXT && $column['length'] !== TableSchema::LENGTH_TINY) { $out .= ' TEXT'; } - if ($data['type'] === TableSchema::TYPE_CHAR) { - $out .= '(' . $data['length'] . ')'; + if ($column['type'] === TableSchemaInterface::TYPE_CHAR) { + $out .= '(' . $column['length'] . ')'; } if ( - $data['type'] === TableSchema::TYPE_STRING || + $column['type'] === TableSchemaInterface::TYPE_STRING || ( - $data['type'] === TableSchema::TYPE_TEXT && - $data['length'] === TableSchema::LENGTH_TINY + $column['type'] === TableSchemaInterface::TYPE_TEXT && + $column['length'] === TableSchema::LENGTH_TINY ) ) { $out .= ' VARCHAR'; - if (isset($data['length'])) { - $out .= '(' . $data['length'] . ')'; + if (isset($column['length'])) { + $out .= '(' . $column['length'] . ')'; } } - if ($data['type'] === TableSchema::TYPE_BINARY) { - if (isset($data['length'])) { - $out .= ' BLOB(' . $data['length'] . ')'; + if ($column['type'] === TableSchemaInterface::TYPE_BINARY) { + if (isset($column['length'])) { + $out .= ' BLOB(' . $column['length'] . ')'; } else { $out .= ' BLOB'; } } $integerTypes = [ - TableSchema::TYPE_TINYINTEGER, - TableSchema::TYPE_SMALLINTEGER, - TableSchema::TYPE_INTEGER, + TableSchemaInterface::TYPE_TINYINTEGER, + TableSchemaInterface::TYPE_SMALLINTEGER, + TableSchemaInterface::TYPE_INTEGER, ]; if ( - in_array($data['type'], $integerTypes, true) && - isset($data['length']) && - $schema->getPrimaryKey() !== [$name] + in_array($column['type'], $integerTypes, true) && + isset($column['length']) && $autoIncrement !== true ) { - $out .= '(' . (int)$data['length'] . ')'; + $out .= '(' . (int)$column['length'] . ')'; } - $hasPrecision = [TableSchema::TYPE_FLOAT, TableSchema::TYPE_DECIMAL]; + $hasPrecision = [TableSchemaInterface::TYPE_FLOAT, TableSchemaInterface::TYPE_DECIMAL]; if ( - in_array($data['type'], $hasPrecision, true) && + in_array($column['type'], $hasPrecision, true) && ( - isset($data['length']) || - isset($data['precision']) + isset($column['length']) || + isset($column['precision']) ) ) { - $out .= '(' . (int)$data['length'] . ',' . (int)$data['precision'] . ')'; + $out .= '(' . (int)$column['length'] . ',' . (int)$column['precision'] . ')'; } - if (isset($data['null']) && $data['null'] === false) { + if (isset($column['null']) && $column['null'] === false) { $out .= ' NOT NULL'; } - if ($data['type'] === TableSchema::TYPE_INTEGER && $schema->getPrimaryKey() === [$name]) { + if ($column['type'] === TableSchemaInterface::TYPE_INTEGER && $autoIncrement) { $out .= ' PRIMARY KEY AUTOINCREMENT'; + unset($column['default']); } $timestampTypes = [ - TableSchema::TYPE_DATETIME, - TableSchema::TYPE_DATETIME_FRACTIONAL, - TableSchema::TYPE_TIMESTAMP, - TableSchema::TYPE_TIMESTAMP_FRACTIONAL, - TableSchema::TYPE_TIMESTAMP_TIMEZONE, + TableSchemaInterface::TYPE_DATETIME, + TableSchemaInterface::TYPE_DATETIME_FRACTIONAL, + TableSchemaInterface::TYPE_TIMESTAMP, + TableSchemaInterface::TYPE_TIMESTAMP_FRACTIONAL, + TableSchemaInterface::TYPE_TIMESTAMP_TIMEZONE, ]; - if (isset($data['null']) && $data['null'] === true && in_array($data['type'], $timestampTypes, true)) { + if (isset($column['null']) && $column['null'] === true && in_array($column['type'], $timestampTypes, true)) { $out .= ' DEFAULT NULL'; } - if (isset($data['default'])) { - $out .= ' DEFAULT ' . $this->_driver->schemaValue($data['default']); + if (isset($column['default'])) { + $out .= ' DEFAULT ' . $this->_driver->schemaValue($column['default']); + } + if (isset($column['comment']) && $column['comment']) { + $out .= " /* {$column['comment']} */"; } return $out; @@ -498,13 +858,16 @@ public function columnSql(TableSchema $schema, string $name): string */ public function constraintSql(TableSchema $schema, string $name): string { - /** @var array $data */ $data = $schema->getConstraint($name); - /** @psalm-suppress PossiblyNullArrayAccess */ + assert($data !== null, 'Data does not exist'); + + $column = $schema->getColumn($data['columns'][0]); + assert($column !== null, 'Data does not exist'); + if ( $data['type'] === TableSchema::CONSTRAINT_PRIMARY && count($data['columns']) === 1 && - $schema->getColumn($data['columns'][0])['type'] === TableSchema::TYPE_INTEGER + $column['type'] === TableSchemaInterface::TYPE_INTEGER ) { return ''; } @@ -524,12 +887,12 @@ public function constraintSql(TableSchema $schema, string $name): string $this->_driver->quoteIdentifier($data['references'][0]), $this->_convertConstraintColumns($data['references'][1]), $this->_foreignOnClause($data['update']), - $this->_foreignOnClause($data['delete']) + $this->_foreignOnClause($data['delete']), ); } $columns = array_map( - [$this->_driver, 'quoteIdentifier'], - $data['columns'] + $this->_driver->quoteIdentifier(...), + $data['columns'], ); return sprintf( @@ -537,7 +900,7 @@ public function constraintSql(TableSchema $schema, string $name): string $this->_driver->quoteIdentifier($name), $type, implode(', ', $columns), - $clause + $clause, ); } @@ -574,18 +937,18 @@ public function dropConstraintSql(TableSchema $schema): array */ public function indexSql(TableSchema $schema, string $name): string { - /** @var array $data */ $data = $schema->getIndex($name); + assert($data !== null); $columns = array_map( - [$this->_driver, 'quoteIdentifier'], - $data['columns'] + $this->_driver->quoteIdentifier(...), + $data['columns'], ); return sprintf( 'CREATE INDEX %s ON %s (%s)', $this->_driver->quoteIdentifier($name), $this->_driver->quoteIdentifier($schema->name()), - implode(', ', $columns) + implode(', ', $columns), ); } @@ -631,19 +994,11 @@ public function truncateTableSql(TableSchema $schema): array public function hasSequences(): bool { $result = $this->_driver->prepare( - 'SELECT 1 FROM sqlite_master WHERE name = "sqlite_sequence"' + 'SELECT 1 FROM sqlite_master WHERE name = "sqlite_sequence"', ); $result->execute(); - $this->_hasSequences = (bool)$result->rowCount(); - $result->closeCursor(); + $this->_hasSequences = (bool)$result->fetch(); return $this->_hasSequences; } } - -// phpcs:disable -class_alias( - 'Cake\Database\Schema\SqliteSchemaDialect', - 'Cake\Database\Schema\SqliteSchema' -); -// phpcs:enable diff --git a/app/vendor/cakephp/cakephp/src/Database/Schema/SqlserverSchema.php b/app/vendor/cakephp/cakephp/src/Database/Schema/SqlserverSchema.php deleted file mode 100644 index 62e3f6981..000000000 --- a/app/vendor/cakephp/cakephp/src/Database/Schema/SqlserverSchema.php +++ /dev/null @@ -1,10 +0,0 @@ -describeColumnQuery(); + $schema = $config['schema'] ?? static::DEFAULT_SCHEMA_NAME; + + return [$sql, [$tableName, $schema]]; + } + + /** + * Helper method for creating SQL to describe columns in a table. + * + * @return string SQL to reflect columns + */ + private function describeColumnQuery(): string + { + return 'SELECT DISTINCT AC.column_id AS [column_id], AC.name AS [name], TY.name AS [type], @@ -81,17 +94,18 @@ public function describeColumnSql(string $tableName, array $config): array AC.is_identity AS [autoincrement], AC.is_nullable AS [null], OBJECT_DEFINITION(AC.default_object_id) AS [default], - AC.collation_name AS [collation_name] + AC.collation_name AS [collation_name], + EP.[value] AS [comment] FROM sys.[objects] T INNER JOIN sys.[schemas] S ON S.[schema_id] = T.[schema_id] INNER JOIN sys.[all_columns] AC ON T.[object_id] = AC.[object_id] INNER JOIN sys.[types] TY ON TY.[user_type_id] = AC.[user_type_id] + LEFT JOIN sys.[extended_properties] as EP + ON T.[object_id] = EP.[major_id] + AND AC.[column_id] = EP.[minor_id] + AND EP.[name] = \'MS_Description\' WHERE T.[name] = ? AND S.[name] = ? ORDER BY column_id'; - - $schema = empty($config['schema']) ? static::DEFAULT_SCHEMA_NAME : $config['schema']; - - return [$sql, [$tableName, $schema]]; } /** @@ -111,13 +125,13 @@ protected function _convertColumn( string $col, ?int $length = null, ?int $precision = null, - ?int $scale = null + ?int $scale = null, ): array { $col = strtolower($col); $type = $this->_applyTypeSpecificColumnConversion( $col, - compact('length', 'precision', 'scale') + compact('length', 'precision', 'scale'), ); if ($type !== null) { return $type; @@ -129,82 +143,90 @@ protected function _convertColumn( if ($col === 'datetime') { // datetime cannot parse more than 3 digits of precision and isn't accurate - return ['type' => TableSchema::TYPE_DATETIME, 'length' => null]; + return ['type' => TableSchemaInterface::TYPE_DATETIME, 'length' => null]; } - if (strpos($col, 'datetime') !== false) { - $typeName = TableSchema::TYPE_DATETIME; + if (str_contains($col, 'datetime')) { + $typeName = TableSchemaInterface::TYPE_DATETIME; if ($scale > 0) { - $typeName = TableSchema::TYPE_DATETIME_FRACTIONAL; + $typeName = TableSchemaInterface::TYPE_DATETIME_FRACTIONAL; } return ['type' => $typeName, 'length' => null, 'precision' => $scale]; } if ($col === 'char') { - return ['type' => TableSchema::TYPE_CHAR, 'length' => $length]; + return ['type' => TableSchemaInterface::TYPE_CHAR, 'length' => $length]; } if ($col === 'tinyint') { - return ['type' => TableSchema::TYPE_TINYINTEGER, 'length' => $precision ?: 3]; + return ['type' => TableSchemaInterface::TYPE_TINYINTEGER, 'length' => $precision ?: 3]; } if ($col === 'smallint') { - return ['type' => TableSchema::TYPE_SMALLINTEGER, 'length' => $precision ?: 5]; + return ['type' => TableSchemaInterface::TYPE_SMALLINTEGER, 'length' => $precision ?: 5]; } if ($col === 'int' || $col === 'integer') { - return ['type' => TableSchema::TYPE_INTEGER, 'length' => $precision ?: 10]; + return ['type' => TableSchemaInterface::TYPE_INTEGER, 'length' => $precision ?: 10]; } if ($col === 'bigint') { - return ['type' => TableSchema::TYPE_BIGINTEGER, 'length' => $precision ?: 20]; + return ['type' => TableSchemaInterface::TYPE_BIGINTEGER, 'length' => $precision ?: 20]; } if ($col === 'bit') { - return ['type' => TableSchema::TYPE_BOOLEAN, 'length' => null]; + return ['type' => TableSchemaInterface::TYPE_BOOLEAN, 'length' => null]; } if ( - strpos($col, 'numeric') !== false || - strpos($col, 'money') !== false || - strpos($col, 'decimal') !== false + str_contains($col, 'numeric') || + str_contains($col, 'money') || + str_contains($col, 'decimal') ) { - return ['type' => TableSchema::TYPE_DECIMAL, 'length' => $precision, 'precision' => $scale]; + return ['type' => TableSchemaInterface::TYPE_DECIMAL, 'length' => $precision, 'precision' => $scale]; } if ($col === 'real' || $col === 'float') { - return ['type' => TableSchema::TYPE_FLOAT, 'length' => null]; + return ['type' => TableSchemaInterface::TYPE_FLOAT, 'length' => null]; } // SqlServer schema reflection returns double length for unicode // columns because internally it uses UTF16/UCS2 if ($col === 'nvarchar' || $col === 'nchar' || $col === 'ntext') { $length /= 2; } - if (strpos($col, 'varchar') !== false && $length < 0) { - return ['type' => TableSchema::TYPE_TEXT, 'length' => null]; + if (str_contains($col, 'varchar') && $length < 0) { + return ['type' => TableSchemaInterface::TYPE_TEXT, 'length' => null]; } - if (strpos($col, 'varchar') !== false) { - return ['type' => TableSchema::TYPE_STRING, 'length' => $length ?: 255]; + if (str_contains($col, 'varchar')) { + return ['type' => TableSchemaInterface::TYPE_STRING, 'length' => $length ?: 255]; } - if (strpos($col, 'char') !== false) { - return ['type' => TableSchema::TYPE_CHAR, 'length' => $length]; + if (str_contains($col, 'char')) { + return ['type' => TableSchemaInterface::TYPE_CHAR, 'length' => $length]; } - if (strpos($col, 'text') !== false) { - return ['type' => TableSchema::TYPE_TEXT, 'length' => null]; + if (str_contains($col, 'text')) { + return ['type' => TableSchemaInterface::TYPE_TEXT, 'length' => null]; } - if ($col === 'image' || strpos($col, 'binary') !== false) { + if ($col === 'image' || str_contains($col, 'binary')) { // -1 is the value for MAX which we treat as a 'long' binary if ($length == -1) { $length = TableSchema::LENGTH_LONG; } - return ['type' => TableSchema::TYPE_BINARY, 'length' => $length]; + return ['type' => TableSchemaInterface::TYPE_BINARY, 'length' => $length]; } if ($col === 'uniqueidentifier') { - return ['type' => TableSchema::TYPE_UUID]; + return ['type' => TableSchemaInterface::TYPE_UUID]; + } + if ($col === 'geometry') { + return ['type' => TableSchemaInterface::TYPE_GEOMETRY]; + } + if ($col === 'geography') { + // SQLserver only has one generic geometry type that + // we map to point. + return ['type' => TableSchemaInterface::TYPE_POINT]; } - return ['type' => TableSchema::TYPE_STRING, 'length' => null]; + return ['type' => TableSchemaInterface::TYPE_STRING, 'length' => null]; } /** @@ -216,7 +238,7 @@ public function convertColumnDescription(TableSchema $schema, array $row): void $row['type'], $row['char_length'] !== null ? (int)$row['char_length'] : null, $row['precision'] !== null ? (int)$row['precision'] : null, - $row['scale'] !== null ? (int)$row['scale'] : null + $row['scale'] !== null ? (int)$row['scale'] : null, ); if (!empty($row['autoincrement'])) { @@ -231,6 +253,60 @@ public function convertColumnDescription(TableSchema $schema, array $row): void $schema->addColumn($row['name'], $field); } + /** + * Split a tablename into a tuple of schema, table + * If the table does not have a schema name included, the connection + * schema will be used. + * + * @param string $tableName The table name to split + * @return array A tuple of [schema, tablename] + */ + private function splitTablename(string $tableName): array + { + $config = $this->_driver->config(); + $schema = $config['schema'] ?? static::DEFAULT_SCHEMA_NAME; + if (str_contains($tableName, '.')) { + return explode('.', $tableName); + } + + return [$schema, $tableName]; + } + + /** + * @inheritDoc + */ + public function describeColumns(string $tableName): array + { + [$schema, $name] = $this->splitTablename($tableName); + + $sql = $this->describeColumnQuery(); + $statement = $this->_driver->execute($sql, [$name, $schema]); + $columns = []; + foreach ($statement->fetchAll('assoc') as $row) { + $field = $this->_convertColumn( + $row['type'], + $row['char_length'] !== null ? (int)$row['char_length'] : null, + $row['precision'] !== null ? (int)$row['precision'] : null, + $row['scale'] !== null ? (int)$row['scale'] : null, + ); + + if (!empty($row['autoincrement'])) { + $field['autoIncrement'] = true; + } + + $field += [ + 'name' => $row['name'], + 'null' => $row['null'] === '1', + 'default' => $this->_defaultValue($field['type'], $row['default']), + 'comment' => $row['comment'] ?? null, + 'collate' => $row['collation_name'], + ]; + $columns[] = $field; + } + + return $columns; + } + /** * Manipulate the default value. * @@ -241,7 +317,7 @@ public function convertColumnDescription(TableSchema $schema, array $row): void * @param string|null $default The default value. * @return string|int|null */ - protected function _defaultValue($type, $default) + protected function _defaultValue(string $type, ?string $default): string|int|null { if ($default === null) { return null; @@ -257,7 +333,7 @@ protected function _defaultValue($type, $default) return null; } - if ($type === TableSchema::TYPE_BOOLEAN) { + if ($type === TableSchemaInterface::TYPE_BOOLEAN) { return (int)$default; } @@ -274,7 +350,20 @@ protected function _defaultValue($type, $default) */ public function describeIndexSql(string $tableName, array $config): array { - $sql = "SELECT + $sql = $this->describeIndexQuery(); + $schema = $config['schema'] ?? static::DEFAULT_SCHEMA_NAME; + + return [$sql, [$tableName, $schema]]; + } + + /** + * Get the query to describe indexes + * + * @return string + */ + private function describeIndexQuery(): string + { + return "SELECT I.[name] AS [index_name], IC.[index_column_id] AS [index_order], AC.[name] AS [column_name], @@ -287,10 +376,6 @@ public function describeIndexSql(string $tableName, array $config): array INNER JOIN sys.[all_columns] AC ON T.[object_id] = AC.[object_id] AND IC.[column_id] = AC.[column_id] WHERE T.[is_ms_shipped] = 0 AND I.[type_desc] <> 'HEAP' AND T.[name] = ? AND S.[name] = ? ORDER BY I.[index_id], IC.[index_column_id]"; - - $schema = empty($config['schema']) ? static::DEFAULT_SCHEMA_NAME : $config['schema']; - - return [$sql, [$tableName, $schema]]; } /** @@ -301,9 +386,10 @@ public function convertIndexDescription(TableSchema $schema, array $row): void $type = TableSchema::INDEX_INDEX; $name = $row['index_name']; if ($row['is_primary_key']) { - $name = $type = TableSchema::CONSTRAINT_PRIMARY; + $name = TableSchema::CONSTRAINT_PRIMARY; + $type = TableSchema::CONSTRAINT_PRIMARY; } - if ($row['is_unique_constraint'] && $type === TableSchema::INDEX_INDEX) { + if (($row['is_unique'] || $row['is_unique_constraint']) && $type === TableSchema::INDEX_INDEX) { $type = TableSchema::CONSTRAINT_UNIQUE; } @@ -314,7 +400,7 @@ public function convertIndexDescription(TableSchema $schema, array $row): void } $columns = [$row['column_name']]; - if (!empty($existing)) { + if ($existing) { $columns = array_merge($existing['columns'], $columns); } @@ -335,12 +421,57 @@ public function convertIndexDescription(TableSchema $schema, array $row): void /** * @inheritDoc */ - public function describeForeignKeySql(string $tableName, array $config): array + public function describeIndexes(string $tableName): array + { + [$schema, $name] = $this->splitTablename($tableName); + $sql = $this->describeIndexQuery(); + $indexes = []; + $statement = $this->_driver->execute($sql, [$name, $schema]); + foreach ($statement->fetchAll('assoc') as $row) { + $type = TableSchema::INDEX_INDEX; + $name = $row['index_name']; + $constraint = null; + if ($row['is_primary_key']) { + $constraint = $name; + $name = TableSchema::CONSTRAINT_PRIMARY; + $type = TableSchema::CONSTRAINT_PRIMARY; + } + if (($row['is_unique'] || $row['is_unique_constraint']) && $type === TableSchema::INDEX_INDEX) { + $type = TableSchema::CONSTRAINT_UNIQUE; + } + + if (!isset($indexes[$name])) { + $indexes[$name] = [ + 'name' => $name, + 'type' => $type, + 'columns' => [], + 'length' => [], + ]; + } + $indexes[$name]['columns'][] = $row['column_name']; + if ($constraint) { + $indexes[$name]['constraint'] = $constraint; + } + } + + return array_values($indexes); + } + + /** + * Get the query to describe foreign keys + * + * @return string + */ + private function describeForeignKeyQuery(): string { // phpcs:disable Generic.Files.LineLength - $sql = 'SELECT FK.[name] AS [foreign_key_name], FK.[delete_referential_action_desc] AS [delete_type], - FK.[update_referential_action_desc] AS [update_type], C.name AS [column], RT.name AS [reference_table], - RC.name AS [reference_column] + + return 'SELECT FK.[name] AS [foreign_key_name], + FK.[delete_referential_action_desc] AS [delete_type], + FK.[update_referential_action_desc] AS [update_type], + C.name AS [column], + RT.name AS [reference_table], + RC.name AS [reference_column] FROM sys.foreign_keys FK INNER JOIN sys.foreign_key_columns FKC ON FKC.constraint_object_id = FK.object_id INNER JOIN sys.tables T ON T.object_id = FKC.parent_object_id @@ -351,8 +482,51 @@ public function describeForeignKeySql(string $tableName, array $config): array WHERE FK.is_ms_shipped = 0 AND T.name = ? AND S.name = ? ORDER BY FKC.constraint_column_id'; // phpcs:enable Generic.Files.LineLength + } - $schema = empty($config['schema']) ? static::DEFAULT_SCHEMA_NAME : $config['schema']; + /** + * @inheritDoc + */ + public function describeForeignKeys(string $tableName): array + { + [$schema, $name] = $this->splitTablename($tableName); + $sql = $this->describeForeignKeyQuery(); + $keys = []; + $statement = $this->_driver->execute($sql, [$name, $schema]); + foreach ($statement->fetchAll('assoc') as $row) { + $name = $row['foreign_key_name']; + if (!isset($keys[$name])) { + $keys[$name] = [ + 'name' => $name, + 'type' => TableSchema::CONSTRAINT_FOREIGN, + 'columns' => [], + 'references' => [$row['reference_table'], []], + 'update' => $this->_convertOnClause($row['update_type']), + 'delete' => $this->_convertOnClause($row['delete_type']), + ]; + } + $keys[$name]['columns'][] = $row['column']; + $keys[$name]['references'][1][] = $row['reference_column']; + } + + foreach ($keys as $id => $key) { + // references.1 is the referenced columns. Backwards compat + // requires a single column to be a string, but multiple to be an array. + if (count($key['references'][1]) === 1) { + $keys[$id]['references'][1] = $key['references'][1][0]; + } + } + + return array_values($keys); + } + + /** + * @inheritDoc + */ + public function describeForeignKeySql(string $tableName, array $config): array + { + $sql = $this->describeForeignKeyQuery(); + $schema = $config['schema'] ?? static::DEFAULT_SCHEMA_NAME; return [$sql, [$tableName, $schema]]; } @@ -373,6 +547,14 @@ public function convertForeignKeyDescription(TableSchema $schema, array $row): v $schema->addConstraint($name, $data); } + /** + * @inheritDoc + */ + public function describeOptions(string $tableName): array + { + return []; + } + /** * @inheritDoc */ @@ -388,18 +570,13 @@ protected function _foreignOnClause(string $on): string */ protected function _convertOnClause(string $clause): string { - switch ($clause) { - case 'NO_ACTION': - return TableSchema::ACTION_NO_ACTION; - case 'CASCADE': - return TableSchema::ACTION_CASCADE; - case 'SET_NULL': - return TableSchema::ACTION_SET_NULL; - case 'SET_DEFAULT': - return TableSchema::ACTION_SET_DEFAULT; - } - - return TableSchema::ACTION_SET_NULL; + return match ($clause) { + 'NO_ACTION' => TableSchema::ACTION_NO_ACTION, + 'CASCADE' => TableSchema::ACTION_CASCADE, + 'SET_NULL' => TableSchema::ACTION_SET_NULL, + 'SET_DEFAULT' => TableSchema::ACTION_SET_DEFAULT, + default => TableSchema::ACTION_SET_NULL, + }; } /** @@ -407,119 +584,164 @@ protected function _convertOnClause(string $clause): string */ public function columnSql(TableSchema $schema, string $name): string { - /** @var array $data */ $data = $schema->getColumn($name); + assert($data !== null); + $data['name'] = $name; $sql = $this->_getTypeSpecificColumnSql($data['type'], $schema, $name); if ($sql !== null) { return $sql; } + $autoIncrementTypes = [ + TableSchemaInterface::TYPE_TINYINTEGER, + TableSchemaInterface::TYPE_SMALLINTEGER, + TableSchemaInterface::TYPE_INTEGER, + TableSchemaInterface::TYPE_BIGINTEGER, + ]; + $primaryKey = $schema->getPrimaryKey(); + if ( + in_array($data['type'], $autoIncrementTypes, true) && + $primaryKey === [$name] && + $name === 'id' + ) { + $data['autoIncrement'] = true; + } + + return $this->columnDefinitionSql($data); + } + /** + * @inheritDoc + */ + public function columnDefinitionSql(array $column): string + { + $name = $column['name']; + $column += [ + 'length' => null, + 'precision' => null, + ]; $out = $this->_driver->quoteIdentifier($name); $typeMap = [ - TableSchema::TYPE_TINYINTEGER => ' TINYINT', - TableSchema::TYPE_SMALLINTEGER => ' SMALLINT', - TableSchema::TYPE_INTEGER => ' INTEGER', - TableSchema::TYPE_BIGINTEGER => ' BIGINT', - TableSchema::TYPE_BINARY_UUID => ' UNIQUEIDENTIFIER', - TableSchema::TYPE_BOOLEAN => ' BIT', - TableSchema::TYPE_CHAR => ' NCHAR', - TableSchema::TYPE_FLOAT => ' FLOAT', - TableSchema::TYPE_DECIMAL => ' DECIMAL', - TableSchema::TYPE_DATE => ' DATE', - TableSchema::TYPE_TIME => ' TIME', - TableSchema::TYPE_DATETIME => ' DATETIME2', - TableSchema::TYPE_DATETIME_FRACTIONAL => ' DATETIME2', - TableSchema::TYPE_TIMESTAMP => ' DATETIME2', - TableSchema::TYPE_TIMESTAMP_FRACTIONAL => ' DATETIME2', - TableSchema::TYPE_TIMESTAMP_TIMEZONE => ' DATETIME2', - TableSchema::TYPE_UUID => ' UNIQUEIDENTIFIER', - TableSchema::TYPE_JSON => ' NVARCHAR(MAX)', + TableSchemaInterface::TYPE_TINYINTEGER => ' TINYINT', + TableSchemaInterface::TYPE_SMALLINTEGER => ' SMALLINT', + TableSchemaInterface::TYPE_INTEGER => ' INTEGER', + TableSchemaInterface::TYPE_BIGINTEGER => ' BIGINT', + TableSchemaInterface::TYPE_BINARY_UUID => ' UNIQUEIDENTIFIER', + TableSchemaInterface::TYPE_BOOLEAN => ' BIT', + TableSchemaInterface::TYPE_CHAR => ' NCHAR', + TableSchemaInterface::TYPE_FLOAT => ' FLOAT', + TableSchemaInterface::TYPE_DECIMAL => ' DECIMAL', + TableSchemaInterface::TYPE_DATE => ' DATE', + TableSchemaInterface::TYPE_TIME => ' TIME', + TableSchemaInterface::TYPE_DATETIME => ' DATETIME2', + TableSchemaInterface::TYPE_DATETIME_FRACTIONAL => ' DATETIME2', + TableSchemaInterface::TYPE_TIMESTAMP => ' DATETIME2', + TableSchemaInterface::TYPE_TIMESTAMP_FRACTIONAL => ' DATETIME2', + TableSchemaInterface::TYPE_TIMESTAMP_TIMEZONE => ' DATETIME2', + TableSchemaInterface::TYPE_UUID => ' UNIQUEIDENTIFIER', + TableSchemaInterface::TYPE_NATIVE_UUID => ' UNIQUEIDENTIFIER', + TableSchemaInterface::TYPE_JSON => ' NVARCHAR(MAX)', + TableSchemaInterface::TYPE_GEOMETRY => ' GEOMETRY', + TableSchemaInterface::TYPE_POINT => ' GEOGRAPHY', + TableSchemaInterface::TYPE_LINESTRING => ' GEOGRAPHY', + TableSchemaInterface::TYPE_POLYGON => ' GEOGRAPHY', ]; - if (isset($typeMap[$data['type']])) { - $out .= $typeMap[$data['type']]; + if (isset($typeMap[$column['type']])) { + $out .= $typeMap[$column['type']]; } - if ($data['type'] === TableSchema::TYPE_INTEGER || $data['type'] === TableSchema::TYPE_BIGINTEGER) { - if ($schema->getPrimaryKey() === [$name] || $data['autoIncrement'] === true) { - unset($data['null'], $data['default']); - $out .= ' IDENTITY(1, 1)'; - } + $autoIncrementTypes = [ + TableSchemaInterface::TYPE_TINYINTEGER, + TableSchemaInterface::TYPE_SMALLINTEGER, + TableSchemaInterface::TYPE_INTEGER, + TableSchemaInterface::TYPE_BIGINTEGER, + ]; + $autoIncrement = (bool)($column['autoIncrement'] ?? false); + if ( + in_array($column['type'], $autoIncrementTypes, true) && + $autoIncrement + ) { + $out .= ' IDENTITY(1, 1)'; + unset($column['default']); } - if ($data['type'] === TableSchema::TYPE_TEXT && $data['length'] !== TableSchema::LENGTH_TINY) { + if ($column['type'] === TableSchemaInterface::TYPE_TEXT && $column['length'] !== TableSchema::LENGTH_TINY) { $out .= ' NVARCHAR(MAX)'; } - if ($data['type'] === TableSchema::TYPE_CHAR) { - $out .= '(' . $data['length'] . ')'; + if ($column['type'] === TableSchemaInterface::TYPE_CHAR) { + $out .= '(' . $column['length'] . ')'; } - if ($data['type'] === TableSchema::TYPE_BINARY) { + if ($column['type'] === TableSchemaInterface::TYPE_BINARY) { if ( - !isset($data['length']) - || in_array($data['length'], [TableSchema::LENGTH_MEDIUM, TableSchema::LENGTH_LONG], true) + !isset($column['length']) + || in_array($column['length'], [TableSchema::LENGTH_MEDIUM, TableSchema::LENGTH_LONG], true) ) { - $data['length'] = 'MAX'; + $column['length'] = 'MAX'; } - if ($data['length'] === 1) { + if ($column['length'] === 1) { $out .= ' BINARY(1)'; } else { $out .= ' VARBINARY'; - $out .= sprintf('(%s)', $data['length']); + $out .= sprintf('(%s)', $column['length']); } } if ( - $data['type'] === TableSchema::TYPE_STRING || + $column['type'] === TableSchemaInterface::TYPE_STRING || ( - $data['type'] === TableSchema::TYPE_TEXT && - $data['length'] === TableSchema::LENGTH_TINY + $column['type'] === TableSchemaInterface::TYPE_TEXT && + $column['length'] === TableSchema::LENGTH_TINY ) ) { $type = ' NVARCHAR'; - $length = $data['length'] ?? TableSchema::LENGTH_TINY; + $length = $column['length'] ?? TableSchema::LENGTH_TINY; $out .= sprintf('%s(%d)', $type, $length); } - $hasCollate = [TableSchema::TYPE_TEXT, TableSchema::TYPE_STRING, TableSchema::TYPE_CHAR]; - if (in_array($data['type'], $hasCollate, true) && isset($data['collate']) && $data['collate'] !== '') { - $out .= ' COLLATE ' . $data['collate']; + $hasCollate = [ + TableSchemaInterface::TYPE_TEXT, + TableSchemaInterface::TYPE_STRING, + TableSchemaInterface::TYPE_CHAR, + ]; + if (in_array($column['type'], $hasCollate, true) && isset($column['collate']) && $column['collate'] !== '') { + $out .= ' COLLATE ' . $column['collate']; } $precisionTypes = [ - TableSchema::TYPE_FLOAT, - TableSchema::TYPE_DATETIME, - TableSchema::TYPE_DATETIME_FRACTIONAL, - TableSchema::TYPE_TIMESTAMP, - TableSchema::TYPE_TIMESTAMP_FRACTIONAL, + TableSchemaInterface::TYPE_FLOAT, + TableSchemaInterface::TYPE_DATETIME, + TableSchemaInterface::TYPE_DATETIME_FRACTIONAL, + TableSchemaInterface::TYPE_TIMESTAMP, + TableSchemaInterface::TYPE_TIMESTAMP_FRACTIONAL, ]; - if (in_array($data['type'], $precisionTypes, true) && isset($data['precision'])) { - $out .= '(' . (int)$data['precision'] . ')'; + if (in_array($column['type'], $precisionTypes, true) && isset($column['precision'])) { + $out .= '(' . (int)$column['precision'] . ')'; } if ( - $data['type'] === TableSchema::TYPE_DECIMAL && + $column['type'] === TableSchemaInterface::TYPE_DECIMAL && ( - isset($data['length']) || - isset($data['precision']) + isset($column['length']) || + isset($column['precision']) ) ) { - $out .= '(' . (int)$data['length'] . ',' . (int)$data['precision'] . ')'; + $out .= '(' . (int)$column['length'] . ',' . (int)$column['precision'] . ')'; } - if (isset($data['null']) && $data['null'] === false) { + if (isset($column['null']) && $column['null'] === false) { $out .= ' NOT NULL'; } $dateTimeTypes = [ - TableSchema::TYPE_DATETIME, - TableSchema::TYPE_DATETIME_FRACTIONAL, - TableSchema::TYPE_TIMESTAMP, - TableSchema::TYPE_TIMESTAMP_FRACTIONAL, + TableSchemaInterface::TYPE_DATETIME, + TableSchemaInterface::TYPE_DATETIME_FRACTIONAL, + TableSchemaInterface::TYPE_TIMESTAMP, + TableSchemaInterface::TYPE_TIMESTAMP_FRACTIONAL, ]; $dateTimeDefaults = [ 'current_timestamp', @@ -530,17 +752,18 @@ public function columnSql(TableSchema $schema, string $name): string 'sysdatetimeoffset()', ]; if ( - isset($data['default']) && - in_array($data['type'], $dateTimeTypes, true) && - in_array(strtolower($data['default']), $dateTimeDefaults, true) + isset($column['default']) && + in_array($column['type'], $dateTimeTypes, true) && + is_string($column['default']) && + in_array(strtolower($column['default']), $dateTimeDefaults, true) ) { - $out .= ' DEFAULT ' . strtoupper($data['default']); - } elseif (isset($data['default'])) { - $default = is_bool($data['default']) - ? (int)$data['default'] - : $this->_driver->schemaValue($data['default']); + $out .= ' DEFAULT ' . strtoupper($column['default']); + } elseif (isset($column['default'])) { + $default = is_bool($column['default']) + ? (int)$column['default'] + : $this->_driver->schemaValue($column['default']); $out .= ' DEFAULT ' . $default; - } elseif (isset($data['null']) && $data['null'] !== false) { + } elseif (isset($column['null']) && $column['null'] !== false) { $out .= ' DEFAULT NULL'; } @@ -556,8 +779,8 @@ public function addConstraintSql(TableSchema $schema): array $sql = []; foreach ($schema->constraints() as $name) { - /** @var array $constraint */ $constraint = $schema->getConstraint($name); + assert($constraint !== null); if ($constraint['type'] === TableSchema::CONSTRAINT_FOREIGN) { $tableName = $this->_driver->quoteIdentifier($schema->name()); $sql[] = sprintf($sqlPattern, $tableName, $this->constraintSql($schema, $name)); @@ -576,8 +799,8 @@ public function dropConstraintSql(TableSchema $schema): array $sql = []; foreach ($schema->constraints() as $name) { - /** @var array $constraint */ $constraint = $schema->getConstraint($name); + assert($constraint !== null); if ($constraint['type'] === TableSchema::CONSTRAINT_FOREIGN) { $tableName = $this->_driver->quoteIdentifier($schema->name()); $constraintName = $this->_driver->quoteIdentifier($name); @@ -593,18 +816,18 @@ public function dropConstraintSql(TableSchema $schema): array */ public function indexSql(TableSchema $schema, string $name): string { - /** @var array $data */ $data = $schema->getIndex($name); + assert($data !== null); $columns = array_map( - [$this->_driver, 'quoteIdentifier'], - $data['columns'] + $this->_driver->quoteIdentifier(...), + $data['columns'], ); return sprintf( 'CREATE INDEX %s ON %s (%s)', $this->_driver->quoteIdentifier($name), $this->_driver->quoteIdentifier($schema->name()), - implode(', ', $columns) + implode(', ', $columns), ); } @@ -613,8 +836,8 @@ public function indexSql(TableSchema $schema, string $name): string */ public function constraintSql(TableSchema $schema, string $name): string { - /** @var array $data */ $data = $schema->getConstraint($name); + assert($data !== null); $out = 'CONSTRAINT ' . $this->_driver->quoteIdentifier($name); if ($data['type'] === TableSchema::CONSTRAINT_PRIMARY) { $out = 'PRIMARY KEY'; @@ -636,8 +859,8 @@ public function constraintSql(TableSchema $schema, string $name): string protected function _keySql(string $prefix, array $data): string { $columns = array_map( - [$this->_driver, 'quoteIdentifier'], - $data['columns'] + $this->_driver->quoteIdentifier(...), + $data['columns'], ); if ($data['type'] === TableSchema::CONSTRAINT_FOREIGN) { return $prefix . sprintf( @@ -646,7 +869,7 @@ protected function _keySql(string $prefix, array $data): string $this->_driver->quoteIdentifier($data['references'][0]), $this->_convertConstraintColumns($data['references'][1]), $this->_foreignOnClause($data['update']), - $this->_foreignOnClause($data['delete']) + $this->_foreignOnClause($data['delete']), ); } @@ -666,10 +889,39 @@ public function createTableSql(TableSchema $schema, array $columns, array $const foreach ($indexes as $index) { $out[] = $index; } + foreach ($schema->columns() as $name) { + $column = $schema->getColumn($name); + $comment = $column['comment'] ?? null; + if ($comment !== null) { + $out[] = $this->columnCommentSql($schema, $name, $comment); + } + } return $out; } + /** + * Generate the SQL to create a column comment. + * + * @param \Cake\Database\Schema\TableSchema $schema The table schema. + * @param string $name The column name. + * @param string $comment The column comment. + * @return string + */ + protected function columnCommentSql(TableSchema $schema, string $name, string $comment): string + { + $tableName = $this->_driver->quoteIdentifier($schema->name()); + $columnName = $this->_driver->quoteIdentifier($name); + $comment = $this->_driver->schemaValue($comment); + + return sprintf( + "EXEC sp_addextendedproperty N'MS_Description', %s, N'SCHEMA', N'dbo', N'TABLE', %s, N'COLUMN', %s;", + $comment, + $tableName, + $columnName, + ); + } + /** * @inheritDoc */ @@ -683,14 +935,14 @@ public function truncateTableSql(TableSchema $schema): array // Restart identity sequences $pk = $schema->getPrimaryKey(); if (count($pk) === 1) { - /** @var array $column */ $column = $schema->getColumn($pk[0]); + assert($column !== null); if (in_array($column['type'], ['integer', 'biginteger'])) { $queries[] = sprintf( "IF EXISTS (SELECT * FROM sys.identity_columns WHERE OBJECT_NAME(OBJECT_ID) = '%s' AND " . "last_value IS NOT NULL) DBCC CHECKIDENT('%s', RESEED, 0)", $schema->name(), - $schema->name() + $schema->name(), ); } } @@ -698,10 +950,3 @@ public function truncateTableSql(TableSchema $schema): array return $queries; } } - -// phpcs:disable -class_alias( - 'Cake\Database\Schema\SqlserverSchemaDialect', - 'Cake\Database\Schema\SqlserverSchema' -); -// phpcs:enable diff --git a/app/vendor/cakephp/cakephp/src/Database/Schema/TableSchema.php b/app/vendor/cakephp/cakephp/src/Database/Schema/TableSchema.php index a58779a8c..86dd2e442 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Schema/TableSchema.php +++ b/app/vendor/cakephp/cakephp/src/Database/Schema/TableSchema.php @@ -19,7 +19,6 @@ use Cake\Database\Connection; use Cake\Database\Exception\DatabaseException; use Cake\Database\TypeFactory; -use function Cake\Core\deprecationWarning; /** * Represents a single table in a database schema. @@ -39,49 +38,49 @@ class TableSchema implements TableSchemaInterface, SqlGeneratorInterface * * @var string */ - protected $_table; + protected string $_table; /** * Columns in the table. * * @var array */ - protected $_columns = []; + protected array $_columns = []; /** * A map with columns to types * * @var array */ - protected $_typeMap = []; + protected array $_typeMap = []; /** * Indexes in the table. * * @var array */ - protected $_indexes = []; + protected array $_indexes = []; /** * Constraints in the table. * * @var array> */ - protected $_constraints = []; + protected array $_constraints = []; /** * Options for the table. * * @var array */ - protected $_options = []; + protected array $_options = []; /** * Whether the table is temporary * * @var bool */ - protected $_temporary = false; + protected bool $_temporary = false; /** * Column length when using a `tiny` column type @@ -109,7 +108,7 @@ class TableSchema implements TableSchemaInterface, SqlGeneratorInterface * * @var array */ - public static $columnLengths = [ + public static array $columnLengths = [ 'tiny' => self::LENGTH_TINY, 'medium' => self::LENGTH_MEDIUM, 'long' => self::LENGTH_LONG, @@ -121,7 +120,7 @@ class TableSchema implements TableSchemaInterface, SqlGeneratorInterface * * @var array */ - protected static $_columnKeys = [ + protected static array $_columnKeys = [ 'type' => null, 'baseType' => null, 'length' => null, @@ -136,7 +135,7 @@ class TableSchema implements TableSchemaInterface, SqlGeneratorInterface * * @var array> */ - protected static $_columnExtras = [ + protected static array $_columnExtras = [ 'string' => [ 'collate' => null, ], @@ -148,9 +147,11 @@ class TableSchema implements TableSchemaInterface, SqlGeneratorInterface ], 'tinyinteger' => [ 'unsigned' => null, + 'autoIncrement' => null, ], 'smallinteger' => [ 'unsigned' => null, + 'autoIncrement' => null, ], 'integer' => [ 'unsigned' => null, @@ -166,6 +167,33 @@ class TableSchema implements TableSchemaInterface, SqlGeneratorInterface 'float' => [ 'unsigned' => null, ], + 'geometry' => [ + 'srid' => null, + ], + 'point' => [ + 'srid' => null, + ], + 'linestring' => [ + 'srid' => null, + ], + 'polygon' => [ + 'srid' => null, + ], + 'datetime' => [ + 'onUpdate' => null, + ], + 'datetimefractional' => [ + 'onUpdate' => null, + ], + 'timestamp' => [ + 'onUpdate' => null, + ], + 'timestampfractional' => [ + 'onUpdate' => null, + ], + 'timestamptimezone' => [ + 'onUpdate' => null, + ], ]; /** @@ -174,7 +202,7 @@ class TableSchema implements TableSchemaInterface, SqlGeneratorInterface * * @var array */ - protected static $_indexKeys = [ + protected static array $_indexKeys = [ 'type' => null, 'columns' => [], 'length' => [], @@ -188,7 +216,7 @@ class TableSchema implements TableSchemaInterface, SqlGeneratorInterface * * @var array */ - protected static $_validIndexTypes = [ + protected static array $_validIndexTypes = [ self::INDEX_INDEX, self::INDEX_FULLTEXT, ]; @@ -198,7 +226,7 @@ class TableSchema implements TableSchemaInterface, SqlGeneratorInterface * * @var array */ - protected static $_validConstraintTypes = [ + protected static array $_validConstraintTypes = [ self::CONSTRAINT_PRIMARY, self::CONSTRAINT_UNIQUE, self::CONSTRAINT_FOREIGN, @@ -209,7 +237,7 @@ class TableSchema implements TableSchemaInterface, SqlGeneratorInterface * * @var array */ - protected static $_validForeignKeyActions = [ + protected static array $_validForeignKeyActions = [ self::ACTION_CASCADE, self::ACTION_SET_NULL, self::ACTION_SET_DEFAULT, @@ -378,12 +406,22 @@ public function getColumnType(string $name): ?string public function setColumnType(string $name, string $type) { if (!isset($this->_columns[$name])) { - return $this; + $message = sprintf( + 'Column `%s` of table `%s`: The column type `%s` can only be set if the column already exists;', + $name, + $this->_table, + $type, + ); + $message .= ' can be checked using `hasColumn()`.'; + + throw new DatabaseException($message); } $this->_columns[$name]['type'] = $type; $this->_typeMap[$name] = $type; + unset($this->_columns[$name]['baseType']); + return $this; } @@ -470,21 +508,21 @@ public function addIndex(string $name, $attrs) if (!in_array($attrs['type'], static::$_validIndexTypes, true)) { throw new DatabaseException(sprintf( - 'Invalid index type "%s" in index "%s" in table "%s".', + 'Invalid index type `%s` in index `%s` in table `%s`.', $attrs['type'], $name, - $this->_table + $this->_table, )); } $attrs['columns'] = (array)$attrs['columns']; foreach ($attrs['columns'] as $field) { if (empty($this->_columns[$field])) { $msg = sprintf( - 'Columns used in index "%s" in table "%s" must be added to the Table schema first. ' . - 'The column "%s" was not found.', + 'Columns used in index `%s` in table `%s` must be added to the Table schema first. ' . + 'The column `%s` was not found.', $name, $this->_table, - $field + $field, ); throw new DatabaseException($msg); } @@ -514,20 +552,6 @@ public function getIndex(string $name): ?array return $this->_indexes[$name]; } - /** - * Get the column(s) used for the primary key. - * - * @return array Column name(s) for the primary key. An - * empty list will be returned when the table has no primary key. - * @deprecated 4.0.0 Renamed to {@link getPrimaryKey()}. - */ - public function primaryKey(): array - { - deprecationWarning('`TableSchema::primaryKey()` is deprecated. Use `TableSchema::getPrimaryKey()`.'); - - return $this->getPrimarykey(); - } - /** * @inheritDoc */ @@ -554,15 +578,15 @@ public function addConstraint(string $name, $attrs) $attrs += static::$_indexKeys; if (!in_array($attrs['type'], static::$_validConstraintTypes, true)) { throw new DatabaseException(sprintf( - 'Invalid constraint type "%s" in table "%s".', + 'Invalid constraint type `%s` in table `%s`.', $attrs['type'], - $this->_table + $this->_table, )); } if (empty($attrs['columns'])) { throw new DatabaseException(sprintf( - 'Constraints in table "%s" must have at least one column.', - $this->_table + 'Constraints in table `%s` must have at least one column.', + $this->_table, )); } $attrs['columns'] = (array)$attrs['columns']; @@ -570,9 +594,9 @@ public function addConstraint(string $name, $attrs) if (empty($this->_columns[$field])) { $msg = sprintf( 'Columns used in constraints must be added to the Table schema first. ' . - 'The column "%s" was not found in table "%s".', + 'The column `%s` was not found in table `%s`.', $field, - $this->_table + $this->_table, ); throw new DatabaseException($msg); } @@ -584,13 +608,13 @@ public function addConstraint(string $name, $attrs) if (isset($this->_constraints[$name])) { $this->_constraints[$name]['columns'] = array_unique(array_merge( $this->_constraints[$name]['columns'], - $attrs['columns'] + $attrs['columns'], )); if (isset($this->_constraints[$name]['references'])) { $this->_constraints[$name]['references'][1] = array_unique(array_merge( (array)$this->_constraints[$name]['references'][1], - [$attrs['references'][1]] + [$attrs['references'][1]], )); } @@ -648,13 +672,13 @@ protected function _checkForeignKey(array $attrs): array if (!in_array($attrs['update'], static::$_validForeignKeyActions)) { throw new DatabaseException(sprintf( 'Update action is invalid. Must be one of %s', - implode(',', static::$_validForeignKeyActions) + implode(',', static::$_validForeignKeyActions), )); } if (!in_array($attrs['delete'], static::$_validForeignKeyActions)) { throw new DatabaseException(sprintf( 'Delete action is invalid. Must be one of %s', - implode(',', static::$_validForeignKeyActions) + implode(',', static::$_validForeignKeyActions), )); } @@ -719,7 +743,9 @@ public function isTemporary(): bool public function createSql(Connection $connection): array { $dialect = $connection->getDriver()->schemaDialect(); - $columns = $constraints = $indexes = []; + $columns = []; + $constraints = []; + $indexes = []; foreach (array_keys($this->_columns) as $name) { $columns[] = $dialect->columnSql($this, $name); } diff --git a/app/vendor/cakephp/cakephp/src/Database/Schema/TableSchemaAwareInterface.php b/app/vendor/cakephp/cakephp/src/Database/Schema/TableSchemaAwareInterface.php deleted file mode 100644 index d4045f9d2..000000000 --- a/app/vendor/cakephp/cakephp/src/Database/Schema/TableSchemaAwareInterface.php +++ /dev/null @@ -1,38 +0,0 @@ -cacheMetadata(true); } - /** @var \Cake\Database\Schema\CachedCollection $schemaCollection */ - $schemaCollection = $connection->getSchemaCollection(); - - return $schemaCollection; + /** @var \Cake\Database\Schema\CachedCollection */ + return $connection->getSchemaCollection(); } } diff --git a/app/vendor/cakephp/cakephp/src/Database/SqlDialectTrait.php b/app/vendor/cakephp/cakephp/src/Database/SqlDialectTrait.php deleted file mode 100644 index 763f8fd70..000000000 --- a/app/vendor/cakephp/cakephp/src/Database/SqlDialectTrait.php +++ /dev/null @@ -1,10 +0,0 @@ - */ - protected $_orderedUnion = false; - - /** - * @inheritDoc - */ - protected $_templates = [ + protected array $_templates = [ 'delete' => 'DELETE', 'where' => ' WHERE %s', 'group' => ' GROUP BY %s', 'order' => ' %s', 'offset' => ' OFFSET %s ROWS', 'epilog' => ' %s', + 'comment' => '/* %s */ ', ]; /** - * @inheritDoc + * {@inheritDoc} + * + * @var array */ - protected $_selectParts = [ - 'with', 'select', 'from', 'join', 'where', 'group', 'having', 'window', 'order', - 'offset', 'limit', 'union', 'epilog', + protected array $_selectParts = [ + 'comment', 'with', 'select', 'from', 'join', 'where', 'group', 'having', 'window', 'order', + 'offset', 'limit', 'union', 'epilog', 'intersect', ]; /** @@ -59,7 +57,7 @@ class SqlserverCompiler extends QueryCompiler * it constructs the CTE definitions list without generating the `RECURSIVE` * keyword that is neither required nor valid. * - * @param array $parts List of CTEs to be transformed to string + * @param array<\Cake\Database\Expression\CommonTableExpression> $parts List of CTEs to be transformed to string * @param \Cake\Database\Query $query The query that is being compiled * @param \Cake\Database\ValueBinder $binder Value binder used to generate parameter placeholder * @return string @@ -91,7 +89,7 @@ protected function _buildInsertPart(array $parts, Query $query, ValueBinder $bin if (!isset($parts[0])) { throw new DatabaseException( 'Could not compile insert query. No table was specified. ' . - 'Use `into()` to define a table.' + 'Use `into()` to define a table.', ); } $table = $parts[0]; @@ -102,7 +100,7 @@ protected function _buildInsertPart(array $parts, Query $query, ValueBinder $bin 'INSERT%s INTO %s (%s) OUTPUT INSERTED.*', $modifiers, $table, - implode(', ', $columns) + implode(', ', $columns), ); } @@ -132,7 +130,7 @@ protected function _buildLimitPart(int $limit, Query $query): string * @param \Cake\Database\ValueBinder $binder Value binder used to generate parameter placeholder * @return string */ - protected function _buildHavingPart($parts, $query, $binder) + protected function _buildHavingPart(array $parts, Query $query, ValueBinder $binder): string { $selectParts = $query->clause('select'); @@ -147,7 +145,7 @@ protected function _buildHavingPart($parts, $query, $binder) preg_match_all( '/\b' . trim($selectKey, '[]') . '\b/i', $p, - $matches + $matches, ); if (empty($matches[0])) { @@ -157,7 +155,7 @@ protected function _buildHavingPart($parts, $query, $binder) $parts[$k] = preg_replace( ['/\[|\]/', '/\b' . trim($selectKey, '[]') . '\b/i'], ['', $selectPart->sql($binder)], - $p + $p, ); } } diff --git a/app/vendor/cakephp/cakephp/src/Database/Statement/BufferResultsTrait.php b/app/vendor/cakephp/cakephp/src/Database/Statement/BufferResultsTrait.php deleted file mode 100644 index b6a5e579e..000000000 --- a/app/vendor/cakephp/cakephp/src/Database/Statement/BufferResultsTrait.php +++ /dev/null @@ -1,45 +0,0 @@ -_bufferResults = $buffer; - - return $this; - } -} diff --git a/app/vendor/cakephp/cakephp/src/Database/Statement/BufferedStatement.php b/app/vendor/cakephp/cakephp/src/Database/Statement/BufferedStatement.php deleted file mode 100644 index 59aea0d14..000000000 --- a/app/vendor/cakephp/cakephp/src/Database/Statement/BufferedStatement.php +++ /dev/null @@ -1,361 +0,0 @@ - - */ -class BufferedStatement implements Iterator, StatementInterface -{ - use TypeConverterTrait; - - /** - * If true, all rows were fetched - * - * @var bool - */ - protected $_allFetched = false; - - /** - * The decorated statement - * - * @var \Cake\Database\StatementInterface - */ - protected $statement; - - /** - * The driver for the statement - * - * @var \Cake\Database\DriverInterface - */ - protected $_driver; - - /** - * The in-memory cache containing results from previous iterators - * - * @var array - */ - protected $buffer = []; - - /** - * Whether this statement has already been executed - * - * @var bool - */ - protected $_hasExecuted = false; - - /** - * The current iterator index. - * - * @var int - */ - protected $index = 0; - - /** - * Constructor - * - * @param \Cake\Database\StatementInterface $statement Statement implementation such as PDOStatement - * @param \Cake\Database\DriverInterface $driver Driver instance - */ - public function __construct(StatementInterface $statement, DriverInterface $driver) - { - $this->statement = $statement; - $this->_driver = $driver; - } - - /** - * Returns the connection driver. - * - * @return \Cake\Database\DriverInterface - */ - protected function getDriver(): DriverInterface - { - return $this->_driver; - } - - /** - * Magic getter to return $queryString as read-only. - * - * @param string $property internal property to get - * @return string|null - */ - public function __get(string $property) - { - if ($property === 'queryString') { - /** @psalm-suppress NoInterfaceProperties */ - return $this->statement->queryString; - } - - return null; - } - - /** - * @inheritDoc - */ - public function bindValue($column, $value, $type = 'string'): void - { - $this->statement->bindValue($column, $value, $type); - } - - /** - * @inheritDoc - */ - public function closeCursor(): void - { - $this->statement->closeCursor(); - } - - /** - * @inheritDoc - */ - public function columnCount(): int - { - return $this->statement->columnCount(); - } - - /** - * @inheritDoc - */ - public function errorCode() - { - return $this->statement->errorCode(); - } - - /** - * @inheritDoc - */ - public function errorInfo(): array - { - return $this->statement->errorInfo(); - } - - /** - * @inheritDoc - */ - public function execute(?array $params = null): bool - { - $this->_reset(); - $this->_hasExecuted = true; - - return $this->statement->execute($params); - } - - /** - * @inheritDoc - */ - public function fetchColumn(int $position) - { - $result = $this->fetch(static::FETCH_TYPE_NUM); - if ($result !== false && isset($result[$position])) { - return $result[$position]; - } - - return false; - } - - /** - * Statements can be passed as argument for count() to return the number - * for affected rows from last execution. - * - * @return int - */ - public function count(): int - { - return $this->rowCount(); - } - - /** - * @inheritDoc - */ - public function bind(array $params, array $types): void - { - $this->statement->bind($params, $types); - } - - /** - * @inheritDoc - */ - public function lastInsertId(?string $table = null, ?string $column = null) - { - return $this->statement->lastInsertId($table, $column); - } - - /** - * {@inheritDoc} - * - * @param string|int $type The type to fetch. - * @return array|false - */ - public function fetch($type = self::FETCH_TYPE_NUM) - { - if ($this->_allFetched) { - $row = false; - if (isset($this->buffer[$this->index])) { - $row = $this->buffer[$this->index]; - } - $this->index += 1; - - if ($row && $type === static::FETCH_TYPE_NUM) { - return array_values($row); - } - - return $row; - } - - $record = $this->statement->fetch($type); - if ($record === false) { - $this->_allFetched = true; - $this->statement->closeCursor(); - - return false; - } - $this->buffer[] = $record; - - return $record; - } - - /** - * @return array - */ - public function fetchAssoc(): array - { - $result = $this->fetch(static::FETCH_TYPE_ASSOC); - - return $result ?: []; - } - - /** - * @inheritDoc - */ - public function fetchAll($type = self::FETCH_TYPE_NUM) - { - if ($this->_allFetched) { - return $this->buffer; - } - $results = $this->statement->fetchAll($type); - if ($results !== false) { - $this->buffer = array_merge($this->buffer, $results); - } - $this->_allFetched = true; - $this->statement->closeCursor(); - - return $this->buffer; - } - - /** - * @inheritDoc - */ - public function rowCount(): int - { - if (!$this->_allFetched) { - $this->fetchAll(static::FETCH_TYPE_ASSOC); - } - - return count($this->buffer); - } - - /** - * Reset all properties - * - * @return void - */ - protected function _reset(): void - { - $this->buffer = []; - $this->_allFetched = false; - $this->index = 0; - } - - /** - * Returns the current key in the iterator - * - * @return mixed - */ - #[\ReturnTypeWillChange] - public function key() - { - return $this->index; - } - - /** - * Returns the current record in the iterator - * - * @return mixed - */ - #[\ReturnTypeWillChange] - public function current() - { - return $this->buffer[$this->index]; - } - - /** - * Rewinds the collection - * - * @return void - */ - public function rewind(): void - { - $this->index = 0; - } - - /** - * Returns whether the iterator has more elements - * - * @return bool - */ - public function valid(): bool - { - $old = $this->index; - $row = $this->fetch(self::FETCH_TYPE_ASSOC); - - // Restore the index as fetch() increments during - // the cache scenario. - $this->index = $old; - - return $row !== false; - } - - /** - * Advances the iterator pointer to the next element - * - * @return void - */ - public function next(): void - { - $this->index += 1; - } - - /** - * Get the wrapped statement - * - * @return \Cake\Database\StatementInterface - */ - public function getInnerStatement(): StatementInterface - { - return $this->statement; - } -} diff --git a/app/vendor/cakephp/cakephp/src/Database/Statement/CallbackStatement.php b/app/vendor/cakephp/cakephp/src/Database/Statement/CallbackStatement.php deleted file mode 100644 index a0b59c36e..000000000 --- a/app/vendor/cakephp/cakephp/src/Database/Statement/CallbackStatement.php +++ /dev/null @@ -1,77 +0,0 @@ -_callback = $callback; - } - - /** - * Fetch a row from the statement. - * - * The result will be processed by the callback when it is not `false`. - * - * @param string|int $type Either 'num' or 'assoc' to indicate the result format you would like. - * @return array|false - */ - public function fetch($type = parent::FETCH_TYPE_NUM) - { - $callback = $this->_callback; - $row = $this->_statement->fetch($type); - - return $row === false ? $row : $callback($row); - } - - /** - * {@inheritDoc} - * - * Each row in the result will be processed by the callback when it is not `false. - */ - public function fetchAll($type = parent::FETCH_TYPE_NUM) - { - $results = $this->_statement->fetchAll($type); - - return $results !== false ? array_map($this->_callback, $results) : false; - } -} diff --git a/app/vendor/cakephp/cakephp/src/Database/Statement/MysqlStatement.php b/app/vendor/cakephp/cakephp/src/Database/Statement/MysqlStatement.php deleted file mode 100644 index 2ad4ec008..000000000 --- a/app/vendor/cakephp/cakephp/src/Database/Statement/MysqlStatement.php +++ /dev/null @@ -1,46 +0,0 @@ -_driver->getConnection(); - - try { - $connection->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, $this->_bufferResults); - $result = $this->_statement->execute($params); - } finally { - $connection->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true); - } - - return $result; - } -} diff --git a/app/vendor/cakephp/cakephp/src/Database/Statement/PDOStatement.php b/app/vendor/cakephp/cakephp/src/Database/Statement/PDOStatement.php deleted file mode 100644 index 6cbc82720..000000000 --- a/app/vendor/cakephp/cakephp/src/Database/Statement/PDOStatement.php +++ /dev/null @@ -1,176 +0,0 @@ -_statement = $statement; - $this->_driver = $driver; - } - - /** - * Magic getter to return PDOStatement::$queryString as read-only. - * - * @param string $property internal property to get - * @return string|null - */ - public function __get(string $property) - { - if ($property === 'queryString' && isset($this->_statement->queryString)) { - return $this->_statement->queryString; - } - - return null; - } - - /** - * Assign a value to a positional or named variable in prepared query. If using - * positional variables you need to start with index one, if using named params then - * just use the name in any order. - * - * You can pass PDO compatible constants for binding values with a type or optionally - * any type name registered in the Type class. Any value will be converted to the valid type - * representation if needed. - * - * It is not allowed to combine positional and named variables in the same statement - * - * ### Examples: - * - * ``` - * $statement->bindValue(1, 'a title'); - * $statement->bindValue(2, 5, PDO::INT); - * $statement->bindValue('active', true, 'boolean'); - * $statement->bindValue(5, new \DateTime(), 'date'); - * ``` - * - * @param string|int $column name or param position to be bound - * @param mixed $value The value to bind to variable in query - * @param string|int|null $type PDO type or name of configured Type class - * @return void - */ - public function bindValue($column, $value, $type = 'string'): void - { - if ($type === null) { - $type = 'string'; - } - if (!is_int($type)) { - [$value, $type] = $this->cast($value, $type); - } - $this->_statement->bindValue($column, $value, $type); - } - - /** - * Returns the next row for the result set after executing this statement. - * Rows can be fetched to contain columns as names or positions. If no - * rows are left in result set, this method will return false - * - * ### Example: - * - * ``` - * $statement = $connection->prepare('SELECT id, title from articles'); - * $statement->execute(); - * print_r($statement->fetch('assoc')); // will show ['id' => 1, 'title' => 'a title'] - * ``` - * - * @param string|int $type 'num' for positional columns, assoc for named columns - * @return mixed Result array containing columns and values or false if no results - * are left - */ - public function fetch($type = parent::FETCH_TYPE_NUM) - { - if ($type === static::FETCH_TYPE_NUM) { - return $this->_statement->fetch(PDO::FETCH_NUM); - } - if ($type === static::FETCH_TYPE_ASSOC) { - return $this->_statement->fetch(PDO::FETCH_ASSOC); - } - if ($type === static::FETCH_TYPE_OBJ) { - return $this->_statement->fetch(PDO::FETCH_OBJ); - } - - if (!is_int($type)) { - throw new CakeException(sprintf( - 'Fetch type for PDOStatement must be an integer, found `%s` instead', - getTypeName($type) - )); - } - - return $this->_statement->fetch($type); - } - - /** - * Returns an array with all rows resulting from executing this statement - * - * ### Example: - * - * ``` - * $statement = $connection->prepare('SELECT id, title from articles'); - * $statement->execute(); - * print_r($statement->fetchAll('assoc')); // will show [0 => ['id' => 1, 'title' => 'a title']] - * ``` - * - * @param string|int $type num for fetching columns as positional keys or assoc for column names as keys - * @return array|false list of all results from database for this statement, false on failure - * @psalm-assert string $type - */ - public function fetchAll($type = parent::FETCH_TYPE_NUM) - { - if ($type === static::FETCH_TYPE_NUM) { - return $this->_statement->fetchAll(PDO::FETCH_NUM); - } - if ($type === static::FETCH_TYPE_ASSOC) { - return $this->_statement->fetchAll(PDO::FETCH_ASSOC); - } - if ($type === static::FETCH_TYPE_OBJ) { - return $this->_statement->fetchAll(PDO::FETCH_OBJ); - } - - if (!is_int($type)) { - throw new CakeException(sprintf( - 'Fetch type for PDOStatement must be an integer, found `%s` instead', - getTypeName($type) - )); - } - - return $this->_statement->fetchAll($type); - } -} diff --git a/app/vendor/cakephp/cakephp/src/Database/Statement/SqliteStatement.php b/app/vendor/cakephp/cakephp/src/Database/Statement/SqliteStatement.php index cc965a812..7c2425c41 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Statement/SqliteStatement.php +++ b/app/vendor/cakephp/cakephp/src/Database/Statement/SqliteStatement.php @@ -21,50 +21,45 @@ * * @internal */ -class SqliteStatement extends StatementDecorator +class SqliteStatement extends Statement { - use BufferResultsTrait; + /** + * @var int|null + */ + protected ?int $affectedRows = null; /** * @inheritDoc */ public function execute(?array $params = null): bool { - if ($this->_statement instanceof BufferedStatement) { - $this->_statement = $this->_statement->getInnerStatement(); - } - - if ($this->_bufferResults) { - $this->_statement = new BufferedStatement($this->_statement, $this->_driver); - } + $this->affectedRows = null; - return $this->_statement->execute($params); + return parent::execute($params); } /** - * Returns the number of rows returned of affected by last execution - * - * @return int + * @inheritDoc */ public function rowCount(): int { - /** @psalm-suppress NoInterfaceProperties */ + if ($this->affectedRows !== null) { + return $this->affectedRows; + } + if ( - $this->_statement->queryString && - preg_match('/^(?:DELETE|UPDATE|INSERT)/i', $this->_statement->queryString) + $this->statement->queryString && + preg_match('/^(?:DELETE|UPDATE|INSERT)/i', $this->statement->queryString) ) { $changes = $this->_driver->prepare('SELECT CHANGES()'); $changes->execute(); $row = $changes->fetch(); - $changes->closeCursor(); - - if (!$row) { - return 0; - } - return (int)$row[0]; + $this->affectedRows = $row ? (int)$row[0] : 0; + } else { + $this->affectedRows = parent::rowCount(); } - return parent::rowCount(); + return $this->affectedRows; } } diff --git a/app/vendor/cakephp/cakephp/src/Database/Statement/SqlserverStatement.php b/app/vendor/cakephp/cakephp/src/Database/Statement/SqlserverStatement.php index 39f3be8c3..49ee07a9f 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Statement/SqlserverStatement.php +++ b/app/vendor/cakephp/cakephp/src/Database/Statement/SqlserverStatement.php @@ -23,31 +23,17 @@ * * @internal */ -class SqlserverStatement extends PDOStatement +class SqlserverStatement extends Statement { /** - * {@inheritDoc} - * - * The SQL Server PDO driver requires that binary parameters be bound with the SQLSRV_ENCODING_BINARY attribute. - * This overrides the PDOStatement::bindValue method in order to bind binary columns using the required attribute. - * - * @param string|int $column name or param position to be bound - * @param mixed $value The value to bind to variable in query - * @param string|int|null $type PDO type or name of configured Type class - * @return void + * @inheritDoc */ - public function bindValue($column, $value, $type = 'string'): void + protected function performBind(string|int $column, mixed $value, int $type): void { - if ($type === null) { - $type = 'string'; - } - if (!is_int($type)) { - [$value, $type] = $this->cast($value, $type); - } if ($type === PDO::PARAM_LOB) { - $this->_statement->bindParam($column, $value, $type, 0, PDO::SQLSRV_ENCODING_BINARY); + $this->statement->bindParam($column, $value, $type, 0, PDO::SQLSRV_ENCODING_BINARY); } else { - $this->_statement->bindValue($column, $value, $type); + parent::performBind($column, $value, $type); } } } diff --git a/app/vendor/cakephp/cakephp/src/Database/Statement/Statement.php b/app/vendor/cakephp/cakephp/src/Database/Statement/Statement.php new file mode 100644 index 000000000..21912c788 --- /dev/null +++ b/app/vendor/cakephp/cakephp/src/Database/Statement/Statement.php @@ -0,0 +1,305 @@ + + */ + protected const MODE_NAME_MAP = [ + self::FETCH_TYPE_ASSOC => PDO::FETCH_ASSOC, + self::FETCH_TYPE_NUM => PDO::FETCH_NUM, + self::FETCH_TYPE_OBJ => PDO::FETCH_OBJ, + ]; + + /** + * @var \Cake\Database\Driver + */ + protected Driver $_driver; + + /** + * Cached bound parameters used for logging + * + * @var array + */ + protected array $params = []; + + /** + * @param \PDOStatement $statement PDO statement + * @param \Cake\Database\Driver $driver Database driver + * @param array<\Closure> $resultDecorators Results decorators + */ + public function __construct( + protected PDOStatement $statement, + Driver $driver, + protected array $resultDecorators = [], + ) { + $this->_driver = $driver; + } + + /** + * @inheritDoc + */ + public function bind(array $params, array $types): void + { + if (!$params) { + return; + } + + $anonymousParams = is_int(key($params)); + $offset = 1; + foreach ($params as $index => $value) { + $type = $types[$index] ?? null; + if ($anonymousParams) { + $index += $offset; + } + $this->bindValue($index, $value, $type); + } + } + + /** + * @inheritDoc + */ + public function bindValue(string|int $column, mixed $value, string|int|null $type = 'string'): void + { + $type ??= 'string'; + if (!is_int($type)) { + [$value, $type] = $this->cast($value, $type); + } + + $this->params[$column] = $value; + $this->performBind($column, $value, $type); + } + + /** + * Converts a give value to a suitable database value based on type and + * return relevant internal statement type. + * + * @param mixed $value The value to cast. + * @param \Cake\Database\TypeInterface|string|int $type The type name or type instance to use. + * @return array List containing converted value and internal type. + * @phpstan-return array{0:mixed, 1:int} + */ + protected function cast(mixed $value, TypeInterface|string|int $type = 'string'): array + { + if (is_string($type)) { + $type = TypeFactory::build($type); + } + if ($type instanceof TypeInterface) { + $value = $type->toDatabase($value, $this->_driver); + $type = $type->toStatement($value, $this->_driver); + } + + return [$value, $type]; + } + + /** + * @inheritDoc + */ + public function getBoundParams(): array + { + return $this->params; + } + + /** + * @param string|int $column + * @param mixed $value + * @param int $type + * @return void + */ + protected function performBind(string|int $column, mixed $value, int $type): void + { + $this->statement->bindValue($column, $value, $type); + } + + /** + * @inheritDoc + */ + public function execute(?array $params = null): bool + { + return $this->statement->execute($params); + } + + /** + * @inheritDoc + */ + public function fetch(string|int $mode = PDO::FETCH_NUM): mixed + { + $mode = $this->convertMode($mode); + $row = $this->statement->fetch($mode); + if ($row === false) { + return false; + } + + foreach ($this->resultDecorators as $decorator) { + $row = $decorator($row); + } + + return $row; + } + + /** + * @inheritDoc + */ + public function fetchAssoc(): array + { + return $this->fetch(PDO::FETCH_ASSOC) ?: []; + } + + /** + * @inheritDoc + */ + public function fetchColumn(int $position): mixed + { + $row = $this->fetch(PDO::FETCH_NUM); + if ($row && isset($row[$position])) { + return $row[$position]; + } + + return false; + } + + /** + * @inheritDoc + */ + public function fetchAll(string|int $mode = PDO::FETCH_NUM): array + { + $mode = $this->convertMode($mode); + $rows = $this->statement->fetchAll($mode); + + foreach ($this->resultDecorators as $decorator) { + $rows = array_map($decorator, $rows); + } + + return $rows; + } + + /** + * Converts mode name to PDO constant. + * + * @param string|int $mode Mode name or PDO constant + * @return int + * @throws \InvalidArgumentException + */ + protected function convertMode(string|int $mode): int + { + if (is_int($mode)) { + // We don't try to validate the PDO constants + return $mode; + } + + return static::MODE_NAME_MAP[$mode] + ?? + throw new InvalidArgumentException("Invalid fetch mode requested. Expected 'assoc', 'num' or 'obj'."); + } + + /** + * @inheritDoc + */ + public function closeCursor(): void + { + $this->statement->closeCursor(); + } + + /** + * @inheritDoc + */ + public function rowCount(): int + { + return $this->statement->rowCount(); + } + + /** + * @inheritDoc + */ + public function columnCount(): int + { + return $this->statement->columnCount(); + } + + /** + * @inheritDoc + */ + public function errorCode(): string + { + return $this->statement->errorCode() ?: ''; + } + + /** + * @inheritDoc + */ + public function errorInfo(): array + { + return $this->statement->errorInfo(); + } + + /** + * @inheritDoc + */ + public function lastInsertId(?string $table = null, ?string $column = null): string|int + { + if ($column && $this->columnCount()) { + $row = $this->fetch(static::FETCH_TYPE_ASSOC); + + if ($row && isset($row[$column])) { + return $row[$column]; + } + } + + return $this->_driver->lastInsertId($table); + } + + /** + * Returns prepared query string stored in PDOStatement. + * + * @return string + */ + public function queryString(): string + { + return $this->statement->queryString; + } + + /** + * Get the inner iterator + * + * @return \Generator + */ + public function getIterator(): Generator + { + $this->statement->setFetchMode(PDO::FETCH_ASSOC); + + foreach ($this->statement as $row) { + foreach ($this->resultDecorators as $decorator) { + $row = $decorator($row); + } + + yield $row; + } + + $this->closeCursor(); + } +} diff --git a/app/vendor/cakephp/cakephp/src/Database/Statement/StatementDecorator.php b/app/vendor/cakephp/cakephp/src/Database/Statement/StatementDecorator.php deleted file mode 100644 index 2c2f46bfa..000000000 --- a/app/vendor/cakephp/cakephp/src/Database/Statement/StatementDecorator.php +++ /dev/null @@ -1,373 +0,0 @@ - - */ -class StatementDecorator implements StatementInterface, Countable, IteratorAggregate -{ - use TypeConverterTrait; - - /** - * Statement instance implementation, such as PDOStatement - * or any other custom implementation. - * - * @var \Cake\Database\StatementInterface - */ - protected $_statement; - - /** - * Reference to the driver object associated to this statement. - * - * @var \Cake\Database\DriverInterface - */ - protected $_driver; - - /** - * Whether this statement has already been executed - * - * @var bool - */ - protected $_hasExecuted = false; - - /** - * Constructor - * - * @param \Cake\Database\StatementInterface $statement Statement implementation - * such as PDOStatement. - * @param \Cake\Database\DriverInterface $driver Driver instance - */ - public function __construct(StatementInterface $statement, DriverInterface $driver) - { - $this->_statement = $statement; - $this->_driver = $driver; - } - - /** - * Returns the connection driver. - * - * @return \Cake\Database\DriverInterface - */ - protected function getDriver(): DriverInterface - { - return $this->_driver; - } - - /** - * Magic getter to return $queryString as read-only. - * - * @param string $property internal property to get - * @return string|null - */ - public function __get(string $property) - { - if ($property === 'queryString') { - /** @psalm-suppress NoInterfaceProperties */ - return $this->_statement->queryString; - } - - return null; - } - - /** - * Assign a value to a positional or named variable in prepared query. If using - * positional variables you need to start with index one, if using named params then - * just use the name in any order. - * - * It is not allowed to combine positional and named variables in the same statement. - * - * ### Examples: - * - * ``` - * $statement->bindValue(1, 'a title'); - * $statement->bindValue('active', true, 'boolean'); - * $statement->bindValue(5, new \DateTime(), 'date'); - * ``` - * - * @param string|int $column name or param position to be bound - * @param mixed $value The value to bind to variable in query - * @param string|int|null $type name of configured Type class - * @return void - */ - public function bindValue($column, $value, $type = 'string'): void - { - $this->_statement->bindValue($column, $value, $type); - } - - /** - * Closes a cursor in the database, freeing up any resources and memory - * allocated to it. In most cases you don't need to call this method, as it is - * automatically called after fetching all results from the result set. - * - * @return void - */ - public function closeCursor(): void - { - $this->_statement->closeCursor(); - } - - /** - * Returns the number of columns this statement's results will contain. - * - * ### Example: - * - * ``` - * $statement = $connection->prepare('SELECT id, title from articles'); - * $statement->execute(); - * echo $statement->columnCount(); // outputs 2 - * ``` - * - * @return int - */ - public function columnCount(): int - { - return $this->_statement->columnCount(); - } - - /** - * Returns the error code for the last error that occurred when executing this statement. - * - * @return string|int - */ - public function errorCode() - { - return $this->_statement->errorCode(); - } - - /** - * Returns the error information for the last error that occurred when executing - * this statement. - * - * @return array - */ - public function errorInfo(): array - { - return $this->_statement->errorInfo(); - } - - /** - * Executes the statement by sending the SQL query to the database. It can optionally - * take an array or arguments to be bound to the query variables. Please note - * that binding parameters from this method will not perform any custom type conversion - * as it would normally happen when calling `bindValue`. - * - * @param array|null $params list of values to be bound to query - * @return bool true on success, false otherwise - */ - public function execute(?array $params = null): bool - { - $this->_hasExecuted = true; - - return $this->_statement->execute($params); - } - - /** - * Returns the next row for the result set after executing this statement. - * Rows can be fetched to contain columns as names or positions. If no - * rows are left in result set, this method will return false. - * - * ### Example: - * - * ``` - * $statement = $connection->prepare('SELECT id, title from articles'); - * $statement->execute(); - * print_r($statement->fetch('assoc')); // will show ['id' => 1, 'title' => 'a title'] - * ``` - * - * @param string|int $type 'num' for positional columns, assoc for named columns - * @return mixed Result array containing columns and values or false if no results - * are left - */ - public function fetch($type = self::FETCH_TYPE_NUM) - { - return $this->_statement->fetch($type); - } - - /** - * Returns the next row in a result set as an associative array. Calling this function is the same as calling - * $statement->fetch(StatementDecorator::FETCH_TYPE_ASSOC). If no results are found an empty array is returned. - * - * @return array - */ - public function fetchAssoc(): array - { - $result = $this->fetch(static::FETCH_TYPE_ASSOC); - - return $result ?: []; - } - - /** - * Returns the value of the result at position. - * - * @param int $position The numeric position of the column to retrieve in the result - * @return mixed Returns the specific value of the column designated at $position - */ - public function fetchColumn(int $position) - { - $result = $this->fetch(static::FETCH_TYPE_NUM); - if ($result && isset($result[$position])) { - return $result[$position]; - } - - return false; - } - - /** - * Returns an array with all rows resulting from executing this statement. - * - * ### Example: - * - * ``` - * $statement = $connection->prepare('SELECT id, title from articles'); - * $statement->execute(); - * print_r($statement->fetchAll('assoc')); // will show [0 => ['id' => 1, 'title' => 'a title']] - * ``` - * - * @param string|int $type num for fetching columns as positional keys or assoc for column names as keys - * @return array|false List of all results from database for this statement. False on failure. - */ - public function fetchAll($type = self::FETCH_TYPE_NUM) - { - return $this->_statement->fetchAll($type); - } - - /** - * Returns the number of rows affected by this SQL statement. - * - * ### Example: - * - * ``` - * $statement = $connection->prepare('SELECT id, title from articles'); - * $statement->execute(); - * print_r($statement->rowCount()); // will show 1 - * ``` - * - * @return int - */ - public function rowCount(): int - { - return $this->_statement->rowCount(); - } - - /** - * Statements are iterable as arrays, this method will return - * the iterator object for traversing all items in the result. - * - * ### Example: - * - * ``` - * $statement = $connection->prepare('SELECT id, title from articles'); - * foreach ($statement as $row) { - * //do stuff - * } - * ``` - * - * @return \Cake\Database\StatementInterface - * @psalm-suppress ImplementedReturnTypeMismatch - */ - #[\ReturnTypeWillChange] - public function getIterator() - { - if (!$this->_hasExecuted) { - $this->execute(); - } - - return $this->_statement; - } - - /** - * Statements can be passed as argument for count() to return the number - * for affected rows from last execution. - * - * @return int - */ - public function count(): int - { - return $this->rowCount(); - } - - /** - * Binds a set of values to statement object with corresponding type. - * - * @param array $params list of values to be bound - * @param array $types list of types to be used, keys should match those in $params - * @return void - */ - public function bind(array $params, array $types): void - { - if (empty($params)) { - return; - } - - $anonymousParams = is_int(key($params)); - $offset = 1; - foreach ($params as $index => $value) { - $type = $types[$index] ?? null; - if ($anonymousParams) { - /** @psalm-suppress InvalidOperand */ - $index += $offset; - } - $this->bindValue($index, $value, $type); - } - } - - /** - * Returns the latest primary inserted using this statement. - * - * @param string|null $table table name or sequence to get last insert value from - * @param string|null $column the name of the column representing the primary key - * @return string|int - */ - public function lastInsertId(?string $table = null, ?string $column = null) - { - if ($column && $this->columnCount()) { - $row = $this->fetch(static::FETCH_TYPE_ASSOC); - - if ($row && isset($row[$column])) { - return $row[$column]; - } - } - - return $this->_driver->lastInsertId($table, $column); - } - - /** - * Returns the statement object that was decorated by this class. - * - * @return \Cake\Database\StatementInterface - */ - public function getInnerStatement() - { - return $this->_statement; - } -} diff --git a/app/vendor/cakephp/cakephp/src/Database/StatementInterface.php b/app/vendor/cakephp/cakephp/src/Database/StatementInterface.php index dc5bbc67e..27d849c15 100644 --- a/app/vendor/cakephp/cakephp/src/Database/StatementInterface.php +++ b/app/vendor/cakephp/cakephp/src/Database/StatementInterface.php @@ -16,32 +16,35 @@ */ namespace Cake\Database; +use IteratorAggregate; +use PDO; + /** - * Represents a database statement. Concrete implementations - * can either use PDOStatement or a native driver - * - * @property-read string $queryString + * @template-extends \IteratorAggregate */ -interface StatementInterface +interface StatementInterface extends IteratorAggregate { /** - * Used to designate that numeric indexes be returned in a result when calling fetch methods + * Maps to PDO::FETCH_NUM. * * @var string + * @link https://www.php.net/manual/en/pdo.constants.php */ public const FETCH_TYPE_NUM = 'num'; /** - * Used to designate that an associated array be returned in a result when calling fetch methods + * Maps to PDO::FETCH_ASSOC. * * @var string + * @link https://www.php.net/manual/en/pdo.constants.php */ public const FETCH_TYPE_ASSOC = 'assoc'; /** - * Used to designate that a stdClass object be returned in a result when calling fetch methods + * Maps to PDO::FETCH_OBJ. * * @var string + * @link https://www.php.net/manual/en/pdo.constants.php */ public const FETCH_TYPE_OBJ = 'obj'; @@ -50,7 +53,7 @@ interface StatementInterface * positional variables you need to start with index one, if using named params then * just use the name in any order. * - * It is not allowed to combine positional and named variables in the same statement + * It is not allowed to combine positional and named variables in the same statement. * * ### Examples: * @@ -62,47 +65,47 @@ interface StatementInterface * * @param string|int $column name or param position to be bound * @param mixed $value The value to bind to variable in query - * @param string|int|null $type name of configured Type class, or PDO type constant. + * @param string|int|null $type name of configured Type class * @return void */ - public function bindValue($column, $value, $type = 'string'): void; + public function bindValue(string|int $column, mixed $value, string|int|null $type = 'string'): void; /** - * Closes a cursor in the database, freeing up any resources and memory - * allocated to it. In most cases you don't need to call this method, as it is - * automatically called after fetching all results from the result set. + * Closes the cursor, enabling the statement to be executed again. + * + * This behaves the same as `PDOStatement::closeCursor()`. * * @return void */ public function closeCursor(): void; /** - * Returns the number of columns this statement's results will contain + * Returns the number of columns in the result set. * - * ### Example: - * - * ``` - * $statement = $connection->prepare('SELECT id, title from articles'); - * $statement->execute(); - * echo $statement->columnCount(); // outputs 2 - * ``` + * This behaves the same as `PDOStatement::columnCount()`. * * @return int + * @link https://php.net/manual/en/pdostatement.columncount.php */ public function columnCount(): int; /** - * Returns the error code for the last error that occurred when executing this statement + * Fetch the SQLSTATE associated with the last operation on the statement handle. * - * @return string|int + * This behaves the same as `PDOStatement::errorCode()`. + * + * @return string + * @link https://www.php.net/manual/en/pdostatement.errorcode.php */ - public function errorCode(); + public function errorCode(): string; /** - * Returns the error information for the last error that occurred when executing - * this statement + * Fetch extended error information associated with the last operation on the statement handle. + * + * This behaves the same as `PDOStatement::errorInfo()`. * * @return array + * @link https://www.php.net/manual/en/pdostatement.errorinfo.php */ public function errorInfo(): array; @@ -110,7 +113,7 @@ public function errorInfo(): array; * Executes the statement by sending the SQL query to the database. It can optionally * take an array or arguments to be bound to the query variables. Please note * that binding parameters from this method will not perform any custom type conversion - * as it would normally happen when calling `bindValue` + * as it would normally happen when calling `bindValue`. * * @param array|null $params list of values to be bound to query * @return bool true on success, false otherwise @@ -118,73 +121,68 @@ public function errorInfo(): array; public function execute(?array $params = null): bool; /** - * Returns the next row for the result set after executing this statement. - * Rows can be fetched to contain columns as names or positions. If no - * rows are left in result set, this method will return false + * Fetches the next row from a result set + * and converts fields to types based on TypeMap. * - * ### Example: + * This behaves the same as `PDOStatement::fetch()`. * - * ``` - * $statement = $connection->prepare('SELECT id, title from articles'); - * $statement->execute(); - * print_r($statement->fetch('assoc')); // will show ['id' => 1, 'title' => 'a title'] - * ``` - * - * @param string|int $type 'num' for positional columns, assoc for named columns, or PDO fetch mode constants. - * @return mixed Result array containing columns and values or false if no results - * are left + * @param string|int $mode PDO::FETCH_* constant or fetch mode name. + * Valid names are 'assoc', 'num' or 'obj'. + * @return mixed + * @throws \InvalidArgumentException + * @link https://www.php.net/manual/en/pdo.constants.php */ - public function fetch($type = 'num'); + public function fetch(string|int $mode = PDO::FETCH_NUM): mixed; /** - * Returns an array with all rows resulting from executing this statement - * - * ### Example: + * Fetches the remaining rows from a result set + * and converts fields to types based on TypeMap. * - * ``` - * $statement = $connection->prepare('SELECT id, title from articles'); - * $statement->execute(); - * print_r($statement->fetchAll('assoc')); // will show [0 => ['id' => 1, 'title' => 'a title']] - * ``` + * This behaves the same as `PDOStatement::fetchAll()`. * - * @param string|int $type num for fetching columns as positional keys or assoc for column names as keys - * @return array|false list of all results from database for this statement or false on failure. + * @param string|int $mode PDO::FETCH_* constant or fetch mode name. + * Valid names are 'assoc', 'num' or 'obj'. + * @return array + * @throws \InvalidArgumentException + * @link https://www.php.net/manual/en/pdo.constants.php */ - public function fetchAll($type = 'num'); + public function fetchAll(string|int $mode = PDO::FETCH_NUM): array; /** - * Returns the value of the result at position. + * Fetches the next row from a result set using PDO::FETCH_NUM + * and converts fields to types based on TypeMap. + * + * This behaves the same as `PDOStatement::fetch()` except only + * a specific column from the row is returned. * - * @param int $position The numeric position of the column to retrieve in the result - * @return mixed Returns the specific value of the column designated at $position + * @param int $position Column index in result row. + * @return mixed */ - public function fetchColumn(int $position); + public function fetchColumn(int $position): mixed; /** - * Returns the number of rows affected by this SQL statement + * Fetches the next row from a result set using PDO::FETCH_ASSOC + * and converts fields to types based on TypeMap. * - * ### Example: + * This behaves the same as `PDOStatement::fetch()` except an + * empty array is returned instead of false. * - * ``` - * $statement = $connection->prepare('SELECT id, title from articles'); - * $statement->execute(); - * print_r($statement->rowCount()); // will show 1 - * ``` - * - * @return int + * @return array */ - public function rowCount(): int; + public function fetchAssoc(): array; /** - * Statements can be passed as argument for count() - * to return the number for affected rows from last execution + * Returns the number of rows affected by the last SQL statement. + * + * This behaves the same as `PDOStatement::rowCount()`. * * @return int + * @link https://www.php.net/manual/en/pdostatement.rowcount.php */ - public function count(): int; + public function rowCount(): int; /** - * Binds a set of values to statement object with corresponding type + * Binds a set of values to statement object with corresponding type. * * @param array $params list of values to be bound * @param array $types list of types to be used, keys should match those in $params @@ -193,11 +191,25 @@ public function count(): int; public function bind(array $params, array $types): void; /** - * Returns the latest primary inserted using this statement + * Returns the latest primary inserted using this statement. * * @param string|null $table table name or sequence to get last insert value from * @param string|null $column the name of the column representing the primary key * @return string|int */ - public function lastInsertId(?string $table = null, ?string $column = null); + public function lastInsertId(?string $table = null, ?string $column = null): string|int; + + /** + * Returns prepared query string. + * + * @return string + */ + public function queryString(): string; + + /** + * Get the bound params. + * + * @return array + */ + public function getBoundParams(): array; } diff --git a/app/vendor/cakephp/cakephp/src/Database/Type.php b/app/vendor/cakephp/cakephp/src/Database/Type.php deleted file mode 100644 index 164419c87..000000000 --- a/app/vendor/cakephp/cakephp/src/Database/Type.php +++ /dev/null @@ -1,9 +0,0 @@ - $fields The field keys to cast - * @param \Cake\Database\DriverInterface $driver Object from which database preferences and configuration will be extracted. + * @param \Cake\Database\Driver $driver Object from which database preferences and configuration will be extracted. * @return array */ - public function manyToPHP(array $values, array $fields, DriverInterface $driver): array; + public function manyToPHP(array $values, array $fields, Driver $driver): array; } diff --git a/app/vendor/cakephp/cakephp/src/Database/Type/BinaryType.php b/app/vendor/cakephp/cakephp/src/Database/Type/BinaryType.php index e570f53df..a2da78b50 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Type/BinaryType.php +++ b/app/vendor/cakephp/cakephp/src/Database/Type/BinaryType.php @@ -17,7 +17,7 @@ namespace Cake\Database\Type; use Cake\Core\Exception\CakeException; -use Cake\Database\DriverInterface; +use Cake\Database\Driver; use PDO; /** @@ -34,10 +34,10 @@ class BinaryType extends BaseType * As PDO will handle reading file handles. * * @param mixed $value The value to convert. - * @param \Cake\Database\DriverInterface $driver The driver instance to convert with. + * @param \Cake\Database\Driver $driver The driver instance to convert with. * @return resource|string */ - public function toDatabase($value, DriverInterface $driver) + public function toDatabase(mixed $value, Driver $driver): mixed { return $value; } @@ -46,32 +46,28 @@ public function toDatabase($value, DriverInterface $driver) * Convert binary into resource handles * * @param mixed $value The value to convert. - * @param \Cake\Database\DriverInterface $driver The driver instance to convert with. + * @param \Cake\Database\Driver $driver The driver instance to convert with. * @return resource|null * @throws \Cake\Core\Exception\CakeException */ - public function toPHP($value, DriverInterface $driver) + public function toPHP(mixed $value, Driver $driver): mixed { if ($value === null) { return null; } if (is_string($value)) { - return fopen('data:text/plain;base64,' . base64_encode($value), 'rb'); + return fopen('data:text/plain;base64,' . base64_encode($value), 'rb') ?: null; } if (is_resource($value)) { return $value; } - throw new CakeException(sprintf('Unable to convert %s into binary.', gettype($value))); + throw new CakeException(sprintf('Unable to convert `%s` into binary.', gettype($value))); } /** - * Get the correct PDO binding type for Binary data. - * - * @param mixed $value The value being bound. - * @param \Cake\Database\DriverInterface $driver The driver. - * @return int + * @inheritDoc */ - public function toStatement($value, DriverInterface $driver): int + public function toStatement(mixed $value, Driver $driver): int { return PDO::PARAM_LOB; } @@ -85,7 +81,7 @@ public function toStatement($value, DriverInterface $driver): int * @param mixed $value The value to convert. * @return mixed Converted value. */ - public function marshal($value) + public function marshal(mixed $value): mixed { return $value; } diff --git a/app/vendor/cakephp/cakephp/src/Database/Type/BinaryUuidType.php b/app/vendor/cakephp/cakephp/src/Database/Type/BinaryUuidType.php index 559f27673..b6e942f61 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Type/BinaryUuidType.php +++ b/app/vendor/cakephp/cakephp/src/Database/Type/BinaryUuidType.php @@ -17,7 +17,7 @@ namespace Cake\Database\Type; use Cake\Core\Exception\CakeException; -use Cake\Database\DriverInterface; +use Cake\Database\Driver; use Cake\Utility\Text; use PDO; @@ -35,10 +35,10 @@ class BinaryUuidType extends BaseType * As PDO will handle reading file handles. * * @param mixed $value The value to convert. - * @param \Cake\Database\DriverInterface $driver The driver instance to convert with. - * @return resource|string|null + * @param \Cake\Database\Driver $driver The driver instance to convert with. + * @return mixed */ - public function toDatabase($value, DriverInterface $driver) + public function toDatabase(mixed $value, Driver $driver): mixed { if (!is_string($value)) { return $value; @@ -66,11 +66,11 @@ public function newId(): string * Convert binary uuid into resource handles * * @param mixed $value The value to convert. - * @param \Cake\Database\DriverInterface $driver The driver instance to convert with. + * @param \Cake\Database\Driver $driver The driver instance to convert with. * @return resource|string|null * @throws \Cake\Core\Exception\CakeException */ - public function toPHP($value, DriverInterface $driver) + public function toPHP(mixed $value, Driver $driver): mixed { if ($value === null) { return null; @@ -86,13 +86,9 @@ public function toPHP($value, DriverInterface $driver) } /** - * Get the correct PDO binding type for Binary data. - * - * @param mixed $value The value being bound. - * @param \Cake\Database\DriverInterface $driver The driver. - * @return int + * @inheritDoc */ - public function toStatement($value, DriverInterface $driver): int + public function toStatement(mixed $value, Driver $driver): int { return PDO::PARAM_LOB; } @@ -106,7 +102,7 @@ public function toStatement($value, DriverInterface $driver): int * @param mixed $value The value to convert. * @return mixed Converted value. */ - public function marshal($value) + public function marshal(mixed $value): mixed { return $value; } @@ -117,14 +113,16 @@ public function marshal($value) * @param mixed $binary The value to convert. * @return string Converted value. */ - protected function convertBinaryUuidToString($binary): string + protected function convertBinaryUuidToString(mixed $binary): string { $string = unpack('H*', $binary); + assert($string !== false, 'Could not unpack uuid'); + /** @var array $string */ $string = preg_replace( '/([0-9a-f]{8})([0-9a-f]{4})([0-9a-f]{4})([0-9a-f]{4})([0-9a-f]{12})/', '$1-$2-$3-$4-$5', - $string + $string, ); return $string[1]; @@ -136,7 +134,7 @@ protected function convertBinaryUuidToString($binary): string * @param string $string The value to convert. * @return string Converted value. */ - protected function convertStringToBinaryUuid($string): string + protected function convertStringToBinaryUuid(string $string): string { $string = str_replace('-', '', $string); diff --git a/app/vendor/cakephp/cakephp/src/Database/Type/BoolType.php b/app/vendor/cakephp/cakephp/src/Database/Type/BoolType.php index 0bb8b0ab3..e91c3a37d 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Type/BoolType.php +++ b/app/vendor/cakephp/cakephp/src/Database/Type/BoolType.php @@ -16,10 +16,9 @@ */ namespace Cake\Database\Type; -use Cake\Database\DriverInterface; +use Cake\Database\Driver; use InvalidArgumentException; use PDO; -use function Cake\Core\getTypeName; /** * Bool type converter. @@ -32,10 +31,10 @@ class BoolType extends BaseType implements BatchCastingInterface * Convert bool data into the database format. * * @param mixed $value The value to convert. - * @param \Cake\Database\DriverInterface $driver The driver instance to convert with. + * @param \Cake\Database\Driver $driver The driver instance to convert with. * @return bool|null */ - public function toDatabase($value, DriverInterface $driver): ?bool + public function toDatabase(mixed $value, Driver $driver): ?bool { if ($value === true || $value === false || $value === null) { return $value; @@ -46,8 +45,9 @@ public function toDatabase($value, DriverInterface $driver): ?bool } throw new InvalidArgumentException(sprintf( - 'Cannot convert value of type `%s` to bool', - getTypeName($value) + 'Cannot convert value `%s` of type `%s` to bool', + print_r($value, true), + get_debug_type($value), )); } @@ -55,10 +55,10 @@ public function toDatabase($value, DriverInterface $driver): ?bool * Convert bool values to PHP booleans * * @param mixed $value The value to convert. - * @param \Cake\Database\DriverInterface $driver The driver instance to convert with. + * @param \Cake\Database\Driver $driver The driver instance to convert with. * @return bool|null */ - public function toPHP($value, DriverInterface $driver): ?bool + public function toPHP(mixed $value, Driver $driver): ?bool { if ($value === null || is_bool($value)) { return $value; @@ -74,7 +74,7 @@ public function toPHP($value, DriverInterface $driver): ?bool /** * @inheritDoc */ - public function manyToPHP(array $values, array $fields, DriverInterface $driver): array + public function manyToPHP(array $values, array $fields, Driver $driver): array { foreach ($fields as $field) { $value = $values[$field] ?? null; @@ -94,13 +94,9 @@ public function manyToPHP(array $values, array $fields, DriverInterface $driver) } /** - * Get the correct PDO binding type for bool data. - * - * @param mixed $value The value being bound. - * @param \Cake\Database\DriverInterface $driver The driver. - * @return int + * @inheritDoc */ - public function toStatement($value, DriverInterface $driver): int + public function toStatement(mixed $value, Driver $driver): int { if ($value === null) { return PDO::PARAM_NULL; @@ -115,7 +111,7 @@ public function toStatement($value, DriverInterface $driver): int * @param mixed $value The value to convert. * @return bool|null Converted value. */ - public function marshal($value): ?bool + public function marshal(mixed $value): ?bool { if ($value === null || $value === '') { return null; diff --git a/app/vendor/cakephp/cakephp/src/Database/Type/ColumnSchemaAwareInterface.php b/app/vendor/cakephp/cakephp/src/Database/Type/ColumnSchemaAwareInterface.php index 1c7bdac45..a8653d12a 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Type/ColumnSchemaAwareInterface.php +++ b/app/vendor/cakephp/cakephp/src/Database/Type/ColumnSchemaAwareInterface.php @@ -3,7 +3,7 @@ namespace Cake\Database\Type; -use Cake\Database\DriverInterface; +use Cake\Database\Driver; use Cake\Database\Schema\TableSchemaInterface; interface ColumnSchemaAwareInterface @@ -13,17 +13,17 @@ interface ColumnSchemaAwareInterface * * @param \Cake\Database\Schema\TableSchemaInterface $schema The table schema instance the column is in. * @param string $column The name of the column. - * @param \Cake\Database\DriverInterface $driver The driver instance being used. + * @param \Cake\Database\Driver $driver The driver instance being used. * @return string|null An SQL fragment, or `null` in case the column isn't processed by this type. */ - public function getColumnSql(TableSchemaInterface $schema, string $column, DriverInterface $driver): ?string; + public function getColumnSql(TableSchemaInterface $schema, string $column, Driver $driver): ?string; /** * Convert a SQL column definition to an abstract type definition. * * @param array $definition The column definition. - * @param \Cake\Database\DriverInterface $driver The driver instance being used. + * @param \Cake\Database\Driver $driver The driver instance being used. * @return array|null Array of column information, or `null` in case the column isn't processed by this type. */ - public function convertColumnDefinition(array $definition, DriverInterface $driver): ?array; + public function convertColumnDefinition(array $definition, Driver $driver): ?array; } diff --git a/app/vendor/cakephp/cakephp/src/Database/Type/DateTimeFractionalType.php b/app/vendor/cakephp/cakephp/src/Database/Type/DateTimeFractionalType.php index 3cc149d91..cdfee8390 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Type/DateTimeFractionalType.php +++ b/app/vendor/cakephp/cakephp/src/Database/Type/DateTimeFractionalType.php @@ -24,19 +24,5 @@ class DateTimeFractionalType extends DateTimeType /** * @inheritDoc */ - protected $_format = 'Y-m-d H:i:s.u'; - - /** - * @inheritDoc - */ - protected $_marshalFormats = [ - 'Y-m-d H:i', - 'Y-m-d H:i:s', - 'Y-m-d H:i:s.u', - 'Y-m-d\TH:i', - 'Y-m-d\TH:i:s', - 'Y-m-d\TH:i:sP', - 'Y-m-d\TH:i:s.u', - 'Y-m-d\TH:i:s.uP', - ]; + protected string $_format = 'Y-m-d H:i:s.u'; } diff --git a/app/vendor/cakephp/cakephp/src/Database/Type/DateTimeTimezoneType.php b/app/vendor/cakephp/cakephp/src/Database/Type/DateTimeTimezoneType.php index 1dd931848..b48e9a59a 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Type/DateTimeTimezoneType.php +++ b/app/vendor/cakephp/cakephp/src/Database/Type/DateTimeTimezoneType.php @@ -24,12 +24,15 @@ class DateTimeTimezoneType extends DateTimeType /** * @inheritDoc */ - protected $_format = 'Y-m-d H:i:s.uP'; + protected string $_format = 'Y-m-d H:i:s.uP'; /** - * @inheritDoc + * {@inheritDoc} + * + * @var array */ - protected $_marshalFormats = [ + + protected array $_marshalFormats = [ 'Y-m-d H:i', 'Y-m-d H:i:s', 'Y-m-d H:i:sP', diff --git a/app/vendor/cakephp/cakephp/src/Database/Type/DateTimeType.php b/app/vendor/cakephp/cakephp/src/Database/Type/DateTimeType.php index 6daca0bd2..4ec62659f 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Type/DateTimeType.php +++ b/app/vendor/cakephp/cakephp/src/Database/Type/DateTimeType.php @@ -17,19 +17,16 @@ namespace Cake\Database\Type; use Cake\Chronos\ChronosDate; -use Cake\Database\DriverInterface; -use Cake\I18n\FrozenTime; -use Cake\I18n\I18nDateTimeInterface; -use Cake\I18n\Time; -use DateTime; +use Cake\Database\Driver; +use Cake\Database\Exception\DatabaseException; +use Cake\I18n\DateTime; +use DateTime as NativeDateTime; use DateTimeImmutable; use DateTimeInterface; use DateTimeZone; use Exception; use InvalidArgumentException; use PDO; -use RuntimeException; -use function Cake\Core\deprecationWarning; /** * Datetime type converter. @@ -38,34 +35,27 @@ */ class DateTimeType extends BaseType implements BatchCastingInterface { - /** - * Whether we want to override the time of the converted Time objects - * so it points to the start of the day. - * - * This is primarily to avoid subclasses needing to re-implement the same functionality. - * - * @var bool - */ - protected $setToDateStart = false; - /** * The DateTime format used when converting to string. * * @var string */ - protected $_format = 'Y-m-d H:i:s'; + protected string $_format = 'Y-m-d H:i:s'; /** * The DateTime formats allowed by `marshal()`. * * @var array */ - protected $_marshalFormats = [ + protected array $_marshalFormats = [ 'Y-m-d H:i', 'Y-m-d H:i:s', + 'Y-m-d H:i:s.u', 'Y-m-d\TH:i', 'Y-m-d\TH:i:s', 'Y-m-d\TH:i:sP', + 'Y-m-d\TH:i:s.u', + 'Y-m-d\TH:i:s.uP', ]; /** @@ -73,52 +63,51 @@ class DateTimeType extends BaseType implements BatchCastingInterface * * @var bool */ - protected $_useLocaleMarshal = false; + protected bool $_useLocaleMarshal = false; /** * The locale-aware format `marshal()` uses when `_useLocaleParser` is true. * * See `Cake\I18n\Time::parseDateTime()` for accepted formats. * - * @var array|string|int + * @var array|string|int|null */ - protected $_localeMarshalFormat; + protected array|string|int|null $_localeMarshalFormat = null; /** * The classname to use when creating objects. * - * @var string - * @psalm-var class-string<\DateTime>|class-string<\DateTimeImmutable> + * @var class-string<\Cake\I18n\DateTime>|class-string<\DateTimeImmutable> */ - protected $_className; + protected string $_className; /** * Database time zone. * * @var \DateTimeZone|null */ - protected $dbTimezone; + protected ?DateTimeZone $dbTimezone = null; /** * User time zone. * * @var \DateTimeZone|null */ - protected $userTimezone; + protected ?DateTimeZone $userTimezone = null; /** * Default time zone. * * @var \DateTimeZone */ - protected $defaultTimezone; + protected DateTimeZone $defaultTimezone; /** * Whether database time zone is kept when converting * * @var bool */ - protected $keepDatabaseTimezone = false; + protected bool $keepDatabaseTimezone = false; /** * {@inheritDoc} @@ -130,26 +119,34 @@ public function __construct(?string $name = null) parent::__construct($name); $this->defaultTimezone = new DateTimeZone(date_default_timezone_get()); - $this->_setClassName(FrozenTime::class, DateTimeImmutable::class); + $this->_className = class_exists(DateTime::class) ? DateTime::class : DateTimeImmutable::class; } /** * Convert DateTime instance into strings. * * @param mixed $value The value to convert. - * @param \Cake\Database\DriverInterface $driver The driver instance to convert with. + * @param \Cake\Database\Driver $driver The driver instance to convert with. * @return string|null */ - public function toDatabase($value, DriverInterface $driver): ?string + public function toDatabase(mixed $value, Driver $driver): ?string { if ($value === null || is_string($value)) { return $value; } - if (is_int($value)) { + if (is_int($value) || is_float($value)) { $class = $this->_className; $value = new $class('@' . $value); } + if ($value instanceof ChronosDate) { + return $value->format($this->_format); + } + + if (!$value instanceof DateTimeInterface) { + return null; + } + if ( $this->dbTimezone !== null && $this->dbTimezone->getName() !== $value->getTimezone()->getName() @@ -163,20 +160,6 @@ public function toDatabase($value, DriverInterface $driver): ?string return $value->format($this->_format); } - /** - * Alias for `setDatabaseTimezone()`. - * - * @param \DateTimeZone|string|null $timezone Database timezone. - * @return $this - * @deprecated 4.1.0 Use {@link setDatabaseTimezone()} instead. - */ - public function setTimezone($timezone) - { - deprecationWarning('DateTimeType::setTimezone() is deprecated. Use setDatabaseTimezone() instead.'); - - return $this->setDatabaseTimezone($timezone); - } - /** * Set database timezone. * @@ -187,7 +170,7 @@ public function setTimezone($timezone) * @param \DateTimeZone|string|null $timezone Database timezone. * @return $this */ - public function setDatabaseTimezone($timezone) + public function setDatabaseTimezone(DateTimeZone|string|null $timezone) { if (is_string($timezone)) { $timezone = new DateTimeZone($timezone); @@ -200,12 +183,12 @@ public function setDatabaseTimezone($timezone) /** * Set user timezone. * - * This is the time zone used when marshalling strings to DateTime instances. + * This is the time zone used when marshaling strings to DateTime instances. * * @param \DateTimeZone|string|null $timezone User timezone. * @return $this */ - public function setUserTimezone($timezone) + public function setUserTimezone(DateTimeZone|string|null $timezone) { if (is_string($timezone)) { $timezone = new DateTimeZone($timezone); @@ -219,33 +202,30 @@ public function setUserTimezone($timezone) * {@inheritDoc} * * @param mixed $value Value to be converted to PHP equivalent - * @param \Cake\Database\DriverInterface $driver Object from which database preferences and configuration will be extracted - * @return \DateTimeInterface|null + * @param \Cake\Database\Driver $driver Object from which database preferences and configuration will be extracted + * @return \Cake\I18n\DateTime|\DateTimeImmutable|null */ - public function toPHP($value, DriverInterface $driver) + public function toPHP(mixed $value, Driver $driver): DateTime|DateTimeImmutable|null { if ($value === null) { return null; } $class = $this->_className; - if (is_int($value)) { + if (is_numeric($value)) { $instance = new $class('@' . $value); - } elseif (strpos($value, '0000-00-00') === 0) { + } elseif (str_starts_with($value, '0000-00-00')) { return null; } else { $instance = new $class($value, $this->dbTimezone); } if ( - !$this->keepDatabaseTimezone && - $instance->getTimezone()->getName() !== $this->defaultTimezone->getName() + !$this->keepDatabaseTimezone + && $instance->getTimezone() + && $instance->getTimezone()->getName() !== $this->defaultTimezone->getName() ) { - $instance = $instance->setTimezone($this->defaultTimezone); - } - - if ($this->setToDateStart) { - $instance = $instance->setTime(0, 0, 0); + return $instance->setTimezone($this->defaultTimezone); } return $instance; @@ -275,7 +255,7 @@ public function setKeepDatabaseTimezone(bool $keep) /** * @inheritDoc */ - public function manyToPHP(array $values, array $fields, DriverInterface $driver): array + public function manyToPHP(array $values, array $fields, Driver $driver): array { foreach ($fields as $field) { if (!isset($values[$field])) { @@ -287,7 +267,7 @@ public function manyToPHP(array $values, array $fields, DriverInterface $driver) $class = $this->_className; if (is_int($value)) { $instance = new $class('@' . $value); - } elseif (strpos($value, '0000-00-00') === 0) { + } elseif (str_starts_with($value, '0000-00-00')) { $values[$field] = null; continue; } else { @@ -295,16 +275,13 @@ public function manyToPHP(array $values, array $fields, DriverInterface $driver) } if ( - !$this->keepDatabaseTimezone && - $instance->getTimezone()->getName() !== $this->defaultTimezone->getName() + !$this->keepDatabaseTimezone + && $instance->getTimezone() + && $instance->getTimezone()->getName() !== $this->defaultTimezone->getName() ) { $instance = $instance->setTimezone($this->defaultTimezone); } - if ($this->setToDateStart) { - $instance = $instance->setTime(0, 0, 0); - } - $values[$field] = $instance; } @@ -317,30 +294,23 @@ public function manyToPHP(array $values, array $fields, DriverInterface $driver) * @param mixed $value Request data * @return \DateTimeInterface|null */ - public function marshal($value): ?DateTimeInterface + public function marshal(mixed $value): ?DateTimeInterface { if ($value instanceof DateTimeInterface) { - if ($value instanceof DateTime) { + if ($value instanceof NativeDateTime) { $value = clone $value; } - if ($value instanceof ChronosDate) { - return $value; - } - /** @var \Datetime|\DateTimeImmutable $value */ return $value->setTimezone($this->defaultTimezone); } + if ($value instanceof ChronosDate) { + return $value->toNative(); + } - /** @var class-string<\DateTimeInterface> $class */ $class = $this->_className; try { - if ($value === '' || $value === null || is_bool($value)) { - return null; - } - if (is_int($value) || (is_string($value) && ctype_digit($value))) { - /** @var \DateTime|\DateTimeImmutable $dateTime */ $dateTime = new $class('@' . $value); return $dateTime->setTimezone($this->defaultTimezone); @@ -353,32 +323,30 @@ public function marshal($value): ?DateTimeInterface $dateTime = $this->_parseValue($value); } - /** @var \DateTime|\DateTimeImmutable $dateTime */ - if ($dateTime !== null) { - $dateTime = $dateTime->setTimezone($this->defaultTimezone); + if ($dateTime) { + return $dateTime->setTimezone($this->defaultTimezone); } return $dateTime; } - } catch (Exception $e) { + } catch (Exception) { return null; } - if (is_array($value) && implode('', $value) === '') { + if (!is_array($value)) { return null; } - $value += ['hour' => 0, 'minute' => 0, 'second' => 0, 'microsecond' => 0]; - $format = ''; + $value += [ + 'year' => null, 'month' => null, 'day' => null, + 'hour' => 0, 'minute' => 0, 'second' => 0, 'microsecond' => 0, + ]; if ( - isset($value['year'], $value['month'], $value['day']) && - ( - is_numeric($value['year']) && - is_numeric($value['month']) && - is_numeric($value['day']) - ) + !is_numeric($value['year']) || !is_numeric($value['month']) || !is_numeric($value['day']) || + !is_numeric($value['hour']) || !is_numeric($value['minute']) || !is_numeric($value['second']) || + !is_numeric($value['microsecond']) ) { - $format .= sprintf('%d-%02d-%02d', $value['year'], $value['month'], $value['day']); + return null; } if (isset($value['meridian']) && (int)$value['hour'] === 12) { @@ -387,16 +355,17 @@ public function marshal($value): ?DateTimeInterface if (isset($value['meridian'])) { $value['hour'] = strtolower($value['meridian']) === 'am' ? $value['hour'] : $value['hour'] + 12; } - $format .= sprintf( - '%s%02d:%02d:%02d.%06d', - empty($format) ? '' : ' ', + $format = sprintf( + '%d-%02d-%02d %02d:%02d:%02d.%06d', + $value['year'], + $value['month'], + $value['day'], $value['hour'], $value['minute'], $value['second'], - $value['microsecond'] + $value['microsecond'], ); - /** @var \DateTime|\DateTimeImmutable $dateTime */ $dateTime = new $class($format, $value['timezone'] ?? $this->userTimezone); return $dateTime->setTimezone($this->defaultTimezone); @@ -416,13 +385,13 @@ public function useLocaleParser(bool $enable = true) return $this; } - if (is_subclass_of($this->_className, I18nDateTimeInterface::class)) { + if (is_a($this->_className, DateTime::class, true)) { $this->_useLocaleMarshal = $enable; return $this; } - throw new RuntimeException( - sprintf('Cannot use locale parsing with the %s class', $this->_className) + throw new DatabaseException( + sprintf('Cannot use locale parsing with the %s class', $this->_className), ); } @@ -435,87 +404,33 @@ public function useLocaleParser(bool $enable = true) * @see \Cake\I18n\Time::parseDateTime() * @return $this */ - public function setLocaleFormat($format) + public function setLocaleFormat(array|string $format) { $this->_localeMarshalFormat = $format; return $this; } - /** - * Change the preferred class name to the FrozenTime implementation. - * - * @return $this - * @deprecated 4.3.0 This method is no longer needed as using immutable datetime class is the default behavior. - */ - public function useImmutable() - { - deprecationWarning( - 'Configuring immutable or mutable classes is deprecated and immutable' - . ' classes will be the permanent configuration in 5.0. Calling `useImmutable()` is unnecessary.' - ); - - $this->_setClassName(FrozenTime::class, DateTimeImmutable::class); - - return $this; - } - - /** - * Set the classname to use when building objects. - * - * @param string $class The classname to use. - * @param string $fallback The classname to use when the preferred class does not exist. - * @return void - * @psalm-param class-string<\DateTime>|class-string<\DateTimeImmutable> $class - * @psalm-param class-string<\DateTime>|class-string<\DateTimeImmutable> $fallback - */ - protected function _setClassName(string $class, string $fallback): void - { - if (!class_exists($class)) { - $class = $fallback; - } - $this->_className = $class; - } - /** * Get the classname used for building objects. * - * @return string - * @psalm-return class-string<\DateTime>|class-string<\DateTimeImmutable> + * @return class-string<\Cake\I18n\DateTime>|class-string<\DateTimeImmutable> */ public function getDateTimeClassName(): string { return $this->_className; } - /** - * Change the preferred class name to the mutable Time implementation. - * - * @return $this - * @deprecated 4.3.0 Using mutable datetime objects is deprecated. - */ - public function useMutable() - { - deprecationWarning( - 'Configuring immutable or mutable classes is deprecated and immutable' - . ' classes will be the permanent configuration in 5.0. Calling `useImmutable()` is unnecessary.' - ); - - $this->_setClassName(Time::class, DateTime::class); - - return $this; - } - /** * Converts a string into a DateTime object after parsing it using the locale * aware parser with the format set by `setLocaleFormat()`. * * @param string $value The value to parse and convert to an object. - * @return \Cake\I18n\I18nDateTimeInterface|null + * @return \Cake\I18n\DateTime|null */ - protected function _parseLocaleValue(string $value): ?I18nDateTimeInterface + protected function _parseLocaleValue(string $value): ?DateTime { - /** @psalm-var class-string<\Cake\I18n\I18nDateTimeInterface> $class */ + /** @var class-string<\Cake\I18n\DateTime> $class */ $class = $this->_className; return $class::parseDateTime($value, $this->_localeMarshalFormat, $this->userTimezone); @@ -526,21 +441,21 @@ protected function _parseLocaleValue(string $value): ?I18nDateTimeInterface * formats in `_marshalFormats`. * * @param string $value The value to parse and convert to an object. - * @return \DateTimeInterface|null + * @return \Cake\I18n\DateTime|\DateTimeImmutable|null */ - protected function _parseValue(string $value): ?DateTimeInterface + protected function _parseValue(string $value): DateTime|DateTimeImmutable|null { $class = $this->_className; foreach ($this->_marshalFormats as $format) { try { $dateTime = $class::createFromFormat($format, $value, $this->userTimezone); - // Check for false in case DateTime is used directly + // Check for false in case DateTimeImmutable is used if ($dateTime !== false) { return $dateTime; } - } catch (InvalidArgumentException $e) { - // Chronos wraps DateTime::createFromFormat and throws + } catch (InvalidArgumentException) { + // Chronos wraps DateTimeImmutable::createFromFormat and throws // exception if parse fails. continue; } @@ -550,13 +465,9 @@ protected function _parseValue(string $value): ?DateTimeInterface } /** - * Casts given value to Statement equivalent - * - * @param mixed $value value to be converted to PDO statement - * @param \Cake\Database\DriverInterface $driver object from which database preferences and configuration will be extracted - * @return mixed + * @inheritDoc */ - public function toStatement($value, DriverInterface $driver) + public function toStatement(mixed $value, Driver $driver): int { return PDO::PARAM_STR; } diff --git a/app/vendor/cakephp/cakephp/src/Database/Type/DateType.php b/app/vendor/cakephp/cakephp/src/Database/Type/DateType.php index 0cd63cd6c..b2f64f182 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Type/DateType.php +++ b/app/vendor/cakephp/cakephp/src/Database/Type/DateType.php @@ -16,39 +16,53 @@ */ namespace Cake\Database\Type; +use Cake\Chronos\ChronosDate; +use Cake\Database\Driver; +use Cake\Database\Exception\DatabaseException; use Cake\I18n\Date; -use Cake\I18n\FrozenDate; -use Cake\I18n\I18nDateTimeInterface; -use DateTime; -use DateTimeImmutable; use DateTimeInterface; use Exception; -use function Cake\Core\deprecationWarning; +use InvalidArgumentException; /** * Class DateType */ -class DateType extends DateTimeType +class DateType extends BaseType implements BatchCastingInterface { /** - * @inheritDoc + * @var string */ - protected $_format = 'Y-m-d'; + protected string $_format = 'Y-m-d'; /** - * @inheritDoc + * @var array */ - protected $_marshalFormats = [ + protected array $_marshalFormats = [ 'Y-m-d', ]; /** - * In this class we want Date objects to have their time - * set to the beginning of the day. + * Whether `marshal()` should use locale-aware parser with `_localeMarshalFormat`. * * @var bool */ - protected $setToDateStart = true; + protected bool $_useLocaleMarshal = false; + + /** + * The locale-aware format `marshal()` uses when `_useLocaleParser` is true. + * + * See `Cake\I18n\Date::parseDate()` for accepted formats. + * + * @var string|int|null + */ + protected string|int|null $_localeMarshalFormat = null; + + /** + * The classname to use when creating objects. + * + * @var class-string<\Cake\Chronos\ChronosDate> + */ + protected string $_className; /** * @inheritDoc @@ -57,118 +71,210 @@ public function __construct(?string $name = null) { parent::__construct($name); - $this->_setClassName(FrozenDate::class, DateTimeImmutable::class); + $this->_className = class_exists(Date::class) ? Date::class : ChronosDate::class; } /** - * Change the preferred class name to the FrozenDate implementation. + * Convert DateTime instance into strings. * - * @return $this - * @deprecated 4.3.0 This method is no longer needed as using immutable datetime class is the default behavior. + * @param mixed $value The value to convert. + * @param \Cake\Database\Driver $driver The driver instance to convert with. + * @return string|null */ - public function useImmutable() + public function toDatabase(mixed $value, Driver $driver): ?string { - deprecationWarning( - 'Configuring immutable or mutable classes is deprecated and immutable' - . ' classes will be the permanent configuration in 5.0. Calling `useImmutable()` is unnecessary.' - ); + if ($value === null || is_string($value)) { + return $value; + } + if (is_int($value)) { + $class = $this->_className; + $value = new $class('@' . $value); + } - $this->_setClassName(FrozenDate::class, DateTimeImmutable::class); + assert(is_object($value) && method_exists($value, 'format')); - return $this; + return $value->format($this->_format); } /** - * Change the preferred class name to the mutable Date implementation. + * {@inheritDoc} * - * @return $this - * @deprecated 4.3.0 Using mutable datetime objects is deprecated. + * @param mixed $value Value to be converted to PHP equivalent + * @param \Cake\Database\Driver $driver Object from which database preferences and configuration will be extracted + * @return \Cake\Chronos\ChronosDate|null */ - public function useMutable() + public function toPHP(mixed $value, Driver $driver): ?ChronosDate { - deprecationWarning( - 'Configuring immutable or mutable classes is deprecated and immutable' - . ' classes will be the permanent configuration in 5.0. Calling `useImmutable()` is unnecessary.' - ); + if ($value === null) { + return null; + } + + $class = $this->_className; + if (is_int($value)) { + $instance = new $class('@' . $value); + } elseif (str_starts_with($value, '0000-00-00')) { + return null; + } else { + $instance = new $class($value); + } - $this->_setClassName(Date::class, DateTime::class); + return $instance; + } - return $this; + /** + * @inheritDoc + */ + public function manyToPHP(array $values, array $fields, Driver $driver): array + { + foreach ($fields as $field) { + if (!isset($values[$field])) { + continue; + } + + $value = $values[$field]; + + $class = $this->_className; + if (is_int($value)) { + $instance = new $class('@' . $value); + } elseif (str_starts_with($value, '0000-00-00')) { + $values[$field] = null; + continue; + } else { + $instance = new $class($value); + } + + $values[$field] = $instance; + } + + return $values; } /** * Convert request data into a datetime object. * * @param mixed $value Request data - * @return \DateTimeInterface|null + * @return \Cake\Chronos\ChronosDate|null */ - public function marshal($value): ?DateTimeInterface + public function marshal(mixed $value): ?ChronosDate { - if ($value instanceof DateTimeInterface) { - return new FrozenDate($value); + if ($value instanceof $this->_className) { + return $value; + } + + if ($value instanceof DateTimeInterface || $value instanceof ChronosDate) { + return new $this->_className($value->format($this->_format)); } - /** @var class-string<\Cake\Chronos\ChronosDate> $class */ $class = $this->_className; try { - if ($value === '' || $value === null || is_bool($value)) { - return null; - } - if (is_int($value) || (is_string($value) && ctype_digit($value))) { - /** @var \Cake\I18n\FrozenDate|\DateTimeImmutable $dateTime */ - $dateTime = new $class('@' . $value); - - return $dateTime; + return new $class('@' . $value); } if (is_string($value)) { if ($this->_useLocaleMarshal) { - $dateTime = $this->_parseLocaleValue($value); - } else { - $dateTime = $this->_parseValue($value); + return $this->_parseLocaleValue($value); } - return $dateTime; + return $this->_parseValue($value); } - } catch (Exception $e) { + } catch (Exception) { return null; } - if (is_array($value) && implode('', $value) === '') { - return null; - } - $format = ''; if ( - isset($value['year'], $value['month'], $value['day']) && - ( - is_numeric($value['year']) && - is_numeric($value['month']) && - is_numeric($value['day']) - ) + !is_array($value) || + !isset($value['year'], $value['month'], $value['day']) || + !is_numeric($value['year']) || !is_numeric($value['month']) || !is_numeric($value['day']) ) { - $format .= sprintf('%d-%02d-%02d', $value['year'], $value['month'], $value['day']); + return null; } - if (empty($format)) { - // Invalid array format. - return null; + $format = sprintf('%d-%02d-%02d', $value['year'], $value['month'], $value['day']); + + return new $class($format); + } + + /** + * Sets whether to parse strings passed to `marshal()` using + * the locale-aware format set by `setLocaleFormat()`. + * + * @param bool $enable Whether to enable + * @return $this + */ + public function useLocaleParser(bool $enable = true) + { + if ($enable === false) { + $this->_useLocaleMarshal = $enable; + + return $this; } + if (is_a($this->_className, Date::class, true)) { + $this->_useLocaleMarshal = $enable; - /** @var \Cake\I18n\FrozenDate|\DateTimeImmutable $dateTime */ - $dateTime = new $class($format); + return $this; + } + throw new DatabaseException( + sprintf('Cannot use locale parsing with %s', $this->_className), + ); + } - return $dateTime; + /** + * Sets the locale-aware format used by `marshal()` when parsing strings. + * + * See `Cake\I18n\Date::parseDate()` for accepted formats. + * + * @param string|int $format The locale-aware format + * @see \Cake\I18n\Date::parseDate() + * @return $this + */ + public function setLocaleFormat(string|int $format) + { + $this->_localeMarshalFormat = $format; + + return $this; } /** - * @inheritDoc + * Get the classname used for building objects. + * + * @return class-string<\Cake\Chronos\ChronosDate> + */ + public function getDateClassName(): string + { + return $this->_className; + } + + /** + * @param string $value + * @return \Cake\I18n\Date|null */ - protected function _parseLocaleValue(string $value): ?I18nDateTimeInterface + protected function _parseLocaleValue(string $value): ?Date { - /** @psalm-var class-string<\Cake\I18n\I18nDateTimeInterface> $class */ + /** @var class-string<\Cake\I18n\Date> $class */ $class = $this->_className; return $class::parseDate($value, $this->_localeMarshalFormat); } + + /** + * Converts a string into a DateTime object after parsing it using the + * formats in `_marshalFormats`. + * + * @param string $value The value to parse and convert to an object. + * @return \Cake\Chronos\ChronosDate|null + */ + protected function _parseValue(string $value): ?ChronosDate + { + $class = $this->_className; + foreach ($this->_marshalFormats as $format) { + try { + return $class::createFromFormat($format, $value); + } catch (InvalidArgumentException) { + continue; + } + } + + return null; + } } diff --git a/app/vendor/cakephp/cakephp/src/Database/Type/DecimalType.php b/app/vendor/cakephp/cakephp/src/Database/Type/DecimalType.php index f06f6d795..496bc18c2 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Type/DecimalType.php +++ b/app/vendor/cakephp/cakephp/src/Database/Type/DecimalType.php @@ -16,12 +16,12 @@ */ namespace Cake\Database\Type; -use Cake\Database\DriverInterface; +use Cake\Database\Driver; +use Cake\Database\Exception\DatabaseException; use Cake\I18n\Number; use InvalidArgumentException; use PDO; -use RuntimeException; -use function Cake\Core\getTypeName; +use Stringable; /** * Decimal type converter. @@ -33,27 +33,27 @@ class DecimalType extends BaseType implements BatchCastingInterface /** * The class to use for representing number objects * - * @var string + * @var class-string<\Cake\I18n\Number>|string */ - public static $numberClass = Number::class; + public static string $numberClass = Number::class; /** * Whether numbers should be parsed using a locale aware parser - * when marshalling string inputs. + * when marshaling string inputs. * * @var bool */ - protected $_useLocaleParser = false; + protected bool $_useLocaleParser = false; /** * Convert decimal strings into the database format. * * @param mixed $value The value to convert. - * @param \Cake\Database\DriverInterface $driver The driver instance to convert with. + * @param \Cake\Database\Driver $driver The driver instance to convert with. * @return string|float|int|null * @throws \InvalidArgumentException */ - public function toDatabase($value, DriverInterface $driver) + public function toDatabase(mixed $value, Driver $driver): string|float|int|null { if ($value === null || $value === '') { return null; @@ -63,17 +63,18 @@ public function toDatabase($value, DriverInterface $driver) return $value; } - if ( - is_object($value) - && method_exists($value, '__toString') - && is_numeric(strval($value)) - ) { - return strval($value); + if ($value instanceof Stringable) { + $str = (string)$value; + + if (is_numeric($str)) { + return $str; + } } throw new InvalidArgumentException(sprintf( - 'Cannot convert value of type `%s` to a decimal', - getTypeName($value) + 'Cannot convert value `%s` of type `%s` to a decimal', + print_r($value, true), + get_debug_type($value), )); } @@ -81,10 +82,10 @@ public function toDatabase($value, DriverInterface $driver) * {@inheritDoc} * * @param mixed $value The value to convert. - * @param \Cake\Database\DriverInterface $driver The driver instance to convert with. + * @param \Cake\Database\Driver $driver The driver instance to convert with. * @return string|null */ - public function toPHP($value, DriverInterface $driver): ?string + public function toPHP(mixed $value, Driver $driver): ?string { if ($value === null) { return null; @@ -96,7 +97,7 @@ public function toPHP($value, DriverInterface $driver): ?string /** * @inheritDoc */ - public function manyToPHP(array $values, array $fields, DriverInterface $driver): array + public function manyToPHP(array $values, array $fields, Driver $driver): array { foreach ($fields as $field) { if (!isset($values[$field])) { @@ -110,13 +111,9 @@ public function manyToPHP(array $values, array $fields, DriverInterface $driver) } /** - * Get the correct PDO binding type for decimal data. - * - * @param mixed $value The value being bound. - * @param \Cake\Database\DriverInterface $driver The driver. - * @return int + * @inheritDoc */ - public function toStatement($value, DriverInterface $driver): int + public function toStatement(mixed $value, Driver $driver): int { return PDO::PARAM_STR; } @@ -127,7 +124,7 @@ public function toStatement($value, DriverInterface $driver): int * @param mixed $value The value to convert. * @return string|null Converted value. */ - public function marshal($value): ?string + public function marshal(mixed $value): ?string { if ($value === null || $value === '') { return null; @@ -151,7 +148,7 @@ public function marshal($value): ?string * * @param bool $enable Whether to enable * @return $this - * @throws \RuntimeException + * @throws \Cake\Database\Exception\DatabaseException */ public function useLocaleParser(bool $enable = true) { @@ -168,8 +165,8 @@ public function useLocaleParser(bool $enable = true) return $this; } - throw new RuntimeException( - sprintf('Cannot use locale parsing with the %s class', static::$numberClass) + throw new DatabaseException( + sprintf('Cannot use locale parsing with the %s class', static::$numberClass), ); } @@ -182,7 +179,6 @@ public function useLocaleParser(bool $enable = true) */ protected function _parseValue(string $value): string { - /** @var \Cake\I18n\Number $class */ $class = static::$numberClass; return (string)$class::parseFloat($value); diff --git a/app/vendor/cakephp/cakephp/src/Database/Type/EnumLabelInterface.php b/app/vendor/cakephp/cakephp/src/Database/Type/EnumLabelInterface.php new file mode 100644 index 000000000..ca5feb3f1 --- /dev/null +++ b/app/vendor/cakephp/cakephp/src/Database/Type/EnumLabelInterface.php @@ -0,0 +1,31 @@ + + */ + protected string $enumClassName; + + /** + * @param string $name The name identifying this type + * @param class-string<\BackedEnum> $enumClassName The associated enum class name + */ + public function __construct( + string $name, + string $enumClassName, + ) { + parent::__construct($name); + $this->enumClassName = $enumClassName; + + try { + $reflectionEnum = new ReflectionEnum($enumClassName); + } catch (ReflectionException $e) { + throw new DatabaseException(sprintf( + 'Unable to use `%s` for type `%s`. %s.', + $enumClassName, + $name, + $e->getMessage(), + )); + } + + $namedType = $reflectionEnum->getBackingType(); + if ($namedType == null) { + throw new DatabaseException( + sprintf('Unable to use enum `%s` for type `%s`, must be a backed enum.', $enumClassName, $name), + ); + } + + $this->backingType = (string)$namedType; + } + + /** + * Convert enum instances into the database format. + * + * @param mixed $value The value to convert. + * @param \Cake\Database\Driver $driver The driver instance to convert with. + * @return string|int|null + */ + public function toDatabase(mixed $value, Driver $driver): string|int|null + { + if ($value === null) { + return null; + } + + if ($value instanceof BackedEnum) { + if (!$value instanceof $this->enumClassName) { + throw new InvalidArgumentException(sprintf( + 'Given value type `%s` does not match associated `%s` backed enum in `%s`', + get_debug_type($value), + $this->backingType, + $this->enumClassName, + )); + } + + return $value->value; + } + + if (!is_string($value) && !is_int($value)) { + throw new InvalidArgumentException(sprintf( + 'Cannot convert value `%s` of type `%s` to string or int', + print_r($value, true), + get_debug_type($value), + )); + } + + if ($this->enumClassName::tryFrom($value) === null) { + throw new InvalidArgumentException(sprintf( + '`%s` is not a valid value for `%s`', + $value, + $this->enumClassName, + )); + } + + return $value; + } + + /** + * Transform DB value to backed enum instance + * + * @param mixed $value The value to convert. + * @param \Cake\Database\Driver $driver The driver instance to convert with. + * @return \BackedEnum|null + */ + public function toPHP(mixed $value, Driver $driver): ?BackedEnum + { + if ($value === null) { + return null; + } + + if ($this->backingType === 'int' && is_string($value)) { + $intVal = filter_var($value, FILTER_VALIDATE_INT); + if ($intVal !== false) { + $value = $intVal; + } + } + + return $this->enumClassName::from($value); + } + + /** + * @inheritDoc + */ + public function toStatement(mixed $value, Driver $driver): int + { + if ($this->backingType === 'int') { + return PDO::PARAM_INT; + } + + return PDO::PARAM_STR; + } + + /** + * Marshals request data + * + * @param mixed $value The value to convert. + * @return \BackedEnum|null Converted value. + */ + public function marshal(mixed $value): ?BackedEnum + { + if ($value === null) { + return null; + } + + if ($value instanceof $this->enumClassName) { + return $value; + } + + if (!is_string($value) && !is_int($value)) { + throw new InvalidArgumentException(sprintf( + 'Unable to marshal value `%s` of type `%s` to `%s`', + print_r($value, true), + get_debug_type($value), + $this->enumClassName, + )); + } + + if ($this->backingType === 'int') { + if ($value === '') { + return null; + } + + if (is_numeric($value)) { + $value = (int)$value; + } + } + + if (get_debug_type($value) !== $this->backingType) { + throw new InvalidArgumentException(sprintf( + 'Given value type `%s` does not match associated `%s` backed enum in `%s`', + get_debug_type($value), + $this->backingType, + $this->enumClassName, + )); + } + + $enumInstance = $this->enumClassName::tryFrom($value); + if ($enumInstance === null) { + if ($value === '' && $this->backingType === 'string') { + return null; + } + + throw new InvalidArgumentException(sprintf( + 'Unable to marshal value `%s` of type `%s` to `%s`', + print_r($value, true), + get_debug_type($value), + $this->enumClassName, + )); + } + + return $enumInstance; + } + + /** + * Create an `EnumType` that is paired with the provided `$enumClassName`. + * + * ### Usage + * + * ``` + * // In a table class + * $this->getSchema()->setColumnType('status', EnumType::from(StatusEnum::class)); + * ``` + * + * @param class-string<\BackedEnum> $enumClassName The enum class name + * @return string + */ + public static function from(string $enumClassName): string + { + $typeName = 'enum-' . strtolower(Text::slug($enumClassName)); + $instance = new EnumType($typeName, $enumClassName); + TypeFactory::set($typeName, $instance); + + return $typeName; + } + + /** + * @return class-string<\BackedEnum> + */ + public function getEnumClassName(): string + { + return $this->enumClassName; + } +} diff --git a/app/vendor/cakephp/cakephp/src/Database/Type/ExpressionTypeCasterTrait.php b/app/vendor/cakephp/cakephp/src/Database/Type/ExpressionTypeCasterTrait.php index 621bb9db8..1de47b09f 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Type/ExpressionTypeCasterTrait.php +++ b/app/vendor/cakephp/cakephp/src/Database/Type/ExpressionTypeCasterTrait.php @@ -33,7 +33,7 @@ trait ExpressionTypeCasterTrait * @param string|null $type The type name * @return mixed */ - protected function _castToExpression($value, ?string $type = null) + protected function _castToExpression(mixed $value, ?string $type = null): mixed { if ($type === null) { return $value; @@ -49,8 +49,8 @@ protected function _castToExpression($value, ?string $type = null) $multi = $type !== $baseType; if ($multi) { - /** @var \Cake\Database\Type\ExpressionTypeInterface $converter */ - return array_map([$converter, 'toExpression'], $value); + /** @var \Cake\Database\Type\ExpressionTypeInterface&\Cake\Database\TypeInterface $converter */ + return array_map($converter->toExpression(...), $value); } return $converter->toExpression($value); diff --git a/app/vendor/cakephp/cakephp/src/Database/Type/ExpressionTypeInterface.php b/app/vendor/cakephp/cakephp/src/Database/Type/ExpressionTypeInterface.php index 7615cf9e0..43a490170 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Type/ExpressionTypeInterface.php +++ b/app/vendor/cakephp/cakephp/src/Database/Type/ExpressionTypeInterface.php @@ -32,5 +32,5 @@ interface ExpressionTypeInterface * @param mixed $value The value to be converted to an expression * @return \Cake\Database\ExpressionInterface */ - public function toExpression($value): ExpressionInterface; + public function toExpression(mixed $value): ExpressionInterface; } diff --git a/app/vendor/cakephp/cakephp/src/Database/Type/FloatType.php b/app/vendor/cakephp/cakephp/src/Database/Type/FloatType.php index 27da61a5d..c976cca6e 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Type/FloatType.php +++ b/app/vendor/cakephp/cakephp/src/Database/Type/FloatType.php @@ -16,10 +16,10 @@ */ namespace Cake\Database\Type; -use Cake\Database\DriverInterface; +use Cake\Database\Driver; +use Cake\Database\Exception\DatabaseException; use Cake\I18n\Number; use PDO; -use RuntimeException; /** * Float type converter. @@ -33,24 +33,24 @@ class FloatType extends BaseType implements BatchCastingInterface * * @var string */ - public static $numberClass = Number::class; + public static string $numberClass = Number::class; /** * Whether numbers should be parsed using a locale aware parser - * when marshalling string inputs. + * when marshaling string inputs. * * @var bool */ - protected $_useLocaleParser = false; + protected bool $_useLocaleParser = false; /** * Convert integer data into the database format. * * @param mixed $value The value to convert. - * @param \Cake\Database\DriverInterface $driver The driver instance to convert with. + * @param \Cake\Database\Driver $driver The driver instance to convert with. * @return float|null */ - public function toDatabase($value, DriverInterface $driver): ?float + public function toDatabase(mixed $value, Driver $driver): ?float { if ($value === null || $value === '') { return null; @@ -63,11 +63,10 @@ public function toDatabase($value, DriverInterface $driver): ?float * {@inheritDoc} * * @param mixed $value The value to convert. - * @param \Cake\Database\DriverInterface $driver The driver instance to convert with. + * @param \Cake\Database\Driver $driver The driver instance to convert with. * @return float|null - * @throws \Cake\Core\Exception\CakeException */ - public function toPHP($value, DriverInterface $driver): ?float + public function toPHP(mixed $value, Driver $driver): ?float { if ($value === null) { return null; @@ -79,7 +78,7 @@ public function toPHP($value, DriverInterface $driver): ?float /** * @inheritDoc */ - public function manyToPHP(array $values, array $fields, DriverInterface $driver): array + public function manyToPHP(array $values, array $fields, Driver $driver): array { foreach ($fields as $field) { if (!isset($values[$field])) { @@ -93,13 +92,9 @@ public function manyToPHP(array $values, array $fields, DriverInterface $driver) } /** - * Get the correct PDO binding type for float data. - * - * @param mixed $value The value being bound. - * @param \Cake\Database\DriverInterface $driver The driver. - * @return int + * @inheritDoc */ - public function toStatement($value, DriverInterface $driver): int + public function toStatement(mixed $value, Driver $driver): int { return PDO::PARAM_STR; } @@ -110,7 +105,7 @@ public function toStatement($value, DriverInterface $driver): int * @param mixed $value The value to convert. * @return string|float|null Converted value. */ - public function marshal($value) + public function marshal(mixed $value): string|float|null { if ($value === null || $value === '') { return null; @@ -150,8 +145,8 @@ public function useLocaleParser(bool $enable = true) return $this; } - throw new RuntimeException( - sprintf('Cannot use locale parsing with the %s class', static::$numberClass) + throw new DatabaseException( + sprintf('Cannot use locale parsing with the %s class', static::$numberClass), ); } diff --git a/app/vendor/cakephp/cakephp/src/Database/Type/IntegerType.php b/app/vendor/cakephp/cakephp/src/Database/Type/IntegerType.php index 973d7ca55..9198ed715 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Type/IntegerType.php +++ b/app/vendor/cakephp/cakephp/src/Database/Type/IntegerType.php @@ -16,10 +16,9 @@ */ namespace Cake\Database\Type; -use Cake\Database\DriverInterface; +use Cake\Database\Driver; use InvalidArgumentException; use PDO; -use function Cake\Core\getTypeName; /** * Integer type converter. @@ -35,12 +34,13 @@ class IntegerType extends BaseType implements BatchCastingInterface * @param mixed $value Value to check * @return void */ - protected function checkNumeric($value): void + protected function checkNumeric(mixed $value): void { - if (!is_numeric($value)) { + if (!is_numeric($value) && !is_bool($value)) { throw new InvalidArgumentException(sprintf( - 'Cannot convert value of type `%s` to integer', - getTypeName($value) + 'Cannot convert value `%s` of type `%s` to int', + print_r($value, true), + get_debug_type($value), )); } } @@ -49,10 +49,10 @@ protected function checkNumeric($value): void * Convert integer data into the database format. * * @param mixed $value The value to convert. - * @param \Cake\Database\DriverInterface $driver The driver instance to convert with. + * @param \Cake\Database\Driver $driver The driver instance to convert with. * @return int|null */ - public function toDatabase($value, DriverInterface $driver): ?int + public function toDatabase(mixed $value, Driver $driver): ?int { if ($value === null || $value === '') { return null; @@ -67,10 +67,10 @@ public function toDatabase($value, DriverInterface $driver): ?int * {@inheritDoc} * * @param mixed $value The value to convert. - * @param \Cake\Database\DriverInterface $driver The driver instance to convert with. + * @param \Cake\Database\Driver $driver The driver instance to convert with. * @return int|null */ - public function toPHP($value, DriverInterface $driver): ?int + public function toPHP(mixed $value, Driver $driver): ?int { if ($value === null) { return null; @@ -82,7 +82,7 @@ public function toPHP($value, DriverInterface $driver): ?int /** * @inheritDoc */ - public function manyToPHP(array $values, array $fields, DriverInterface $driver): array + public function manyToPHP(array $values, array $fields, Driver $driver): array { foreach ($fields as $field) { if (!isset($values[$field])) { @@ -98,13 +98,9 @@ public function manyToPHP(array $values, array $fields, DriverInterface $driver) } /** - * Get the correct PDO binding type for integer data. - * - * @param mixed $value The value being bound. - * @param \Cake\Database\DriverInterface $driver The driver. - * @return int + * @inheritDoc */ - public function toStatement($value, DriverInterface $driver): int + public function toStatement(mixed $value, Driver $driver): int { return PDO::PARAM_INT; } @@ -115,15 +111,12 @@ public function toStatement($value, DriverInterface $driver): int * @param mixed $value The value to convert. * @return int|null Converted value. */ - public function marshal($value): ?int + public function marshal(mixed $value): ?int { - if ($value === null || $value === '') { + if ($value === '' || !is_numeric($value)) { return null; } - if (is_numeric($value)) { - return (int)$value; - } - return null; + return (int)$value; } } diff --git a/app/vendor/cakephp/cakephp/src/Database/Type/JsonType.php b/app/vendor/cakephp/cakephp/src/Database/Type/JsonType.php index 5648d94d6..2c9b1fd0f 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Type/JsonType.php +++ b/app/vendor/cakephp/cakephp/src/Database/Type/JsonType.php @@ -16,31 +16,39 @@ */ namespace Cake\Database\Type; -use Cake\Database\DriverInterface; +use Cake\Database\Driver; use InvalidArgumentException; use PDO; /** * JSON type converter. * - * Use to convert JSON data between PHP and the database types. + * Used to convert JSON data between PHP and the database types. */ class JsonType extends BaseType implements BatchCastingInterface { /** * @var int */ - protected $_encodingOptions = 0; + protected int $_encodingOptions = 0; + + /** + * Flags for json_decode() + * + * @var int + */ + protected int $_decodingOptions = JSON_OBJECT_AS_ARRAY; /** * Convert a value data into a JSON string * * @param mixed $value The value to convert. - * @param \Cake\Database\DriverInterface $driver The driver instance to convert with. + * @param \Cake\Database\Driver $driver The driver instance to convert with. * @return string|null * @throws \InvalidArgumentException + * @throws \JsonException */ - public function toDatabase($value, DriverInterface $driver): ?string + public function toDatabase(mixed $value, Driver $driver): ?string { if (is_resource($value)) { throw new InvalidArgumentException('Cannot convert a resource value to JSON'); @@ -50,49 +58,45 @@ public function toDatabase($value, DriverInterface $driver): ?string return null; } - return json_encode($value, $this->_encodingOptions); + return json_encode($value, JSON_THROW_ON_ERROR | $this->_encodingOptions); } /** * {@inheritDoc} * * @param mixed $value The value to convert. - * @param \Cake\Database\DriverInterface $driver The driver instance to convert with. - * @return array|string|null + * @param \Cake\Database\Driver $driver The driver instance to convert with. + * @return mixed */ - public function toPHP($value, DriverInterface $driver) + public function toPHP(mixed $value, Driver $driver): mixed { if (!is_string($value)) { return null; } - return json_decode($value, true); + return json_decode($value, flags: $this->_decodingOptions); } /** * @inheritDoc */ - public function manyToPHP(array $values, array $fields, DriverInterface $driver): array + public function manyToPHP(array $values, array $fields, Driver $driver): array { foreach ($fields as $field) { if (!isset($values[$field])) { continue; } - $values[$field] = json_decode($values[$field], true); + $values[$field] = json_decode($values[$field], flags: $this->_decodingOptions); } return $values; } /** - * Get the correct PDO binding type for string data. - * - * @param mixed $value The value being bound. - * @param \Cake\Database\DriverInterface $driver The driver. - * @return int + * @inheritDoc */ - public function toStatement($value, DriverInterface $driver): int + public function toStatement(mixed $value, Driver $driver): int { return PDO::PARAM_STR; } @@ -103,7 +107,7 @@ public function toStatement($value, DriverInterface $driver): int * @param mixed $value The value to convert. * @return mixed Converted value. */ - public function marshal($value) + public function marshal(mixed $value): mixed { return $value; } @@ -121,4 +125,19 @@ public function setEncodingOptions(int $options) return $this; } + + /** + * Set json_decode() options. + * + * By default, the value is `JSON_OBJECT_AS_ARRAY`. + * + * @param int $options Decoding flags. Use JSON_* flags. Set `0` to reset. + * @return $this + */ + public function setDecodingOptions(int $options) + { + $this->_decodingOptions = $options; + + return $this; + } } diff --git a/app/vendor/cakephp/cakephp/src/Database/Type/StringType.php b/app/vendor/cakephp/cakephp/src/Database/Type/StringType.php index 4c98e3369..07ee033c1 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Type/StringType.php +++ b/app/vendor/cakephp/cakephp/src/Database/Type/StringType.php @@ -16,10 +16,10 @@ */ namespace Cake\Database\Type; -use Cake\Database\DriverInterface; +use Cake\Database\Driver; use InvalidArgumentException; use PDO; -use function Cake\Core\getTypeName; +use Stringable; /** * String type converter. @@ -32,17 +32,17 @@ class StringType extends BaseType implements OptionalConvertInterface * Convert string data into the database format. * * @param mixed $value The value to convert. - * @param \Cake\Database\DriverInterface $driver The driver instance to convert with. + * @param \Cake\Database\Driver $driver The driver instance to convert with. * @return string|null */ - public function toDatabase($value, DriverInterface $driver): ?string + public function toDatabase(mixed $value, Driver $driver): ?string { if ($value === null || is_string($value)) { return $value; } - if (is_object($value) && method_exists($value, '__toString')) { - return $value->__toString(); + if ($value instanceof Stringable) { + return (string)$value; } if (is_scalar($value)) { @@ -50,8 +50,9 @@ public function toDatabase($value, DriverInterface $driver): ?string } throw new InvalidArgumentException(sprintf( - 'Cannot convert value of type `%s` to string', - getTypeName($value) + 'Cannot convert value `%s` of type `%s` to string', + print_r($value, true), + get_debug_type($value), )); } @@ -59,10 +60,10 @@ public function toDatabase($value, DriverInterface $driver): ?string * Convert string values to PHP strings. * * @param mixed $value The value to convert. - * @param \Cake\Database\DriverInterface $driver The driver instance to convert with. + * @param \Cake\Database\Driver $driver The driver instance to convert with. * @return string|null */ - public function toPHP($value, DriverInterface $driver): ?string + public function toPHP(mixed $value, Driver $driver): ?string { if ($value === null) { return null; @@ -72,13 +73,9 @@ public function toPHP($value, DriverInterface $driver): ?string } /** - * Get the correct PDO binding type for string data. - * - * @param mixed $value The value being bound. - * @param \Cake\Database\DriverInterface $driver The driver. - * @return int + * @inheritDoc */ - public function toStatement($value, DriverInterface $driver): int + public function toStatement(mixed $value, Driver $driver): int { return PDO::PARAM_STR; } @@ -89,7 +86,7 @@ public function toStatement($value, DriverInterface $driver): int * @param mixed $value The value to convert. * @return string|null Converted value. */ - public function marshal($value): ?string + public function marshal(mixed $value): ?string { if ($value === null || is_array($value)) { return null; diff --git a/app/vendor/cakephp/cakephp/src/Database/Type/TimeType.php b/app/vendor/cakephp/cakephp/src/Database/Type/TimeType.php index 4c021e214..41af787fc 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Type/TimeType.php +++ b/app/vendor/cakephp/cakephp/src/Database/Type/TimeType.php @@ -16,37 +16,245 @@ */ namespace Cake\Database\Type; -use Cake\I18n\I18nDateTimeInterface; +use Cake\Chronos\ChronosTime; +use Cake\Core\Exception\CakeException; +use Cake\Database\Driver; +use Cake\I18n\Time; +use DateTimeInterface; +use InvalidArgumentException; /** * Time type converter. * - * Use to convert time instances to strings & back. + * Use to convert time instances to strings and back. */ -class TimeType extends DateTimeType +class TimeType extends BaseType implements BatchCastingInterface { /** - * @inheritDoc + * The PHP Time format used when converting to string. + * + * @var string */ - protected $_format = 'H:i:s'; + protected string $_format = 'H:i:s'; /** - * @inheritDoc + * Whether `marshal()` should use locale-aware parser with `_localeMarshalFormat`. + * + * @var bool + */ + protected bool $_useLocaleMarshal = false; + + /** + * The locale-aware format `marshal()` uses when `_useLocaleParser` is true. + * + * See `Cake\I18n\Time::parseTime()` for accepted formats. + * + * @var string|int|null + */ + protected string|int|null $_localeMarshalFormat = null; + + /** + * The classname to use when creating objects. + * + * @var class-string<\Cake\Chronos\ChronosTime> + */ + protected string $_className; + + /** + * Constructor + * + * @param string|null $name The name identifying this type. + * @param class-string<\Cake\Chronos\ChronosTime>|null $className Class name for time representation. */ - protected $_marshalFormats = [ - 'H:i:s', - 'H:i', - ]; + public function __construct(?string $name = null, ?string $className = null) + { + parent::__construct($name); + + if ($className === null) { + $className = class_exists(Time::class) ? Time::class : ChronosTime::class; + } + + $this->_className = $className; + } + + /** + * Convert request data into a datetime object. + * + * @param mixed $value Request data + * @return \Cake\Chronos\ChronosTime|null + */ + public function marshal(mixed $value): ?ChronosTime + { + if ($value instanceof $this->_className) { + return $value; + } + + if ($value instanceof DateTimeInterface || $value instanceof ChronosTime) { + return new $this->_className($value->format($this->_format)); + } + + if (is_string($value)) { + if ($this->_useLocaleMarshal) { + return $this->_parseLocalTimeValue($value); + } + + return $this->_parseTimeValue($value); + } + + if (!is_array($value)) { + return null; + } + + $value += ['hour' => null, 'minute' => null, 'second' => 0, 'microsecond' => 0]; + if ( + !is_numeric($value['hour']) || !is_numeric($value['minute']) || !is_numeric($value['second']) || + !is_numeric($value['microsecond']) + ) { + return null; + } + + if (isset($value['meridian']) && (int)$value['hour'] === 12) { + $value['hour'] = 0; + } + if (isset($value['meridian'])) { + $value['hour'] = strtolower($value['meridian']) === 'am' ? $value['hour'] : $value['hour'] + 12; + } + $format = sprintf( + '%02d:%02d:%02d.%06d', + $value['hour'], + $value['minute'], + $value['second'], + $value['microsecond'], + ); + + return new $this->_className($format); + } /** * @inheritDoc */ - protected function _parseLocaleValue(string $value): ?I18nDateTimeInterface + public function manyToPHP(array $values, array $fields, Driver $driver): array + { + foreach ($fields as $field) { + if (!isset($values[$field])) { + continue; + } + + $value = $values[$field]; + $instance = new $this->_className($value); + $values[$field] = $instance; + } + + return $values; + } + + /** + * Convert time data into the database time format. + * + * @param mixed $value The value to convert. + * @param \Cake\Database\Driver $driver The driver instance to convert with. + * @return mixed + */ + public function toDatabase(mixed $value, Driver $driver): mixed + { + if ($value === null || is_string($value)) { + return $value; + } + + assert(method_exists($value, 'format')); + + return $value->format($this->_format); + } + + /** + * Convert time values to PHP time instances + * + * @param mixed $value The value to convert. + * @param \Cake\Database\Driver $driver The driver instance to convert with. + * @return \Cake\Chronos\ChronosTime|null + */ + public function toPHP(mixed $value, Driver $driver): ?ChronosTime + { + if ($value === null) { + return null; + } + + return new $this->_className($value); + } + + /** + * Get the classname used for building objects. + * + * @return class-string<\Cake\Chronos\ChronosTime> + */ + public function getTimeClassName(): string + { + return $this->_className; + } + + /** + * Converts a string into a Time object + * + * @param string $value The value to parse and convert to an object. + * @return \Cake\Chronos\ChronosTime|null + */ + protected function _parseTimeValue(string $value): ?ChronosTime + { + try { + return $this->_className::parse($value); + } catch (InvalidArgumentException) { + return null; + } + } + + /** + * Converts a string into a Time object after parsing it using the locale + * aware parser with the format set by `setLocaleFormat()`. + * + * @param string $value The value to parse and convert to an object. + * @return \Cake\Chronos\ChronosTime|null + */ + protected function _parseLocalTimeValue(string $value): ?ChronosTime + { + assert(is_a($this->_className, Time::class, true)); + + return $this->_className::parseTime($value, $this->_localeMarshalFormat); + } + + /** + * Sets whether to parse strings passed to `marshal()` using + * the locale-aware format set by `setLocaleFormat()`. + * + * @param bool $enable Whether to enable + * @return $this + */ + public function useLocaleParser(bool $enable = true) + { + if ( + $enable && + ($this->_className !== Time::class && !is_subclass_of($this->_className, Time::class)) + ) { + throw new CakeException('You must install the `cakephp/i18n` package to use locale aware parsing.'); + } + + $this->_useLocaleMarshal = $enable; + + return $this; + } + + /** + * Sets the locale-aware format used by `marshal()` when parsing strings. + * + * See `Cake\I18n\Time::parseTime()` for accepted formats. + * + * @param string|int|null $format The locale-aware format + * @see \Cake\I18n\Time::parseTime() + * @return $this + */ + public function setLocaleFormat(string|int|null $format) { - /** @psalm-var class-string<\Cake\I18n\I18nDateTimeInterface> $class */ - $class = $this->_className; + $this->_localeMarshalFormat = $format; - /** @psalm-suppress PossiblyInvalidArgument */ - return $class::parseTime($value, $this->_localeMarshalFormat); + return $this; } } diff --git a/app/vendor/cakephp/cakephp/src/Database/Type/UuidType.php b/app/vendor/cakephp/cakephp/src/Database/Type/UuidType.php index ce7f754fb..8b48af830 100644 --- a/app/vendor/cakephp/cakephp/src/Database/Type/UuidType.php +++ b/app/vendor/cakephp/cakephp/src/Database/Type/UuidType.php @@ -16,7 +16,7 @@ */ namespace Cake\Database\Type; -use Cake\Database\DriverInterface; +use Cake\Database\Driver; use Cake\Utility\Text; /** @@ -28,10 +28,10 @@ class UuidType extends StringType * Casts given value from a PHP type to one acceptable by database * * @param mixed $value value to be converted to database equivalent - * @param \Cake\Database\DriverInterface $driver object from which database preferences and configuration will be extracted + * @param \Cake\Database\Driver $driver object from which database preferences and configuration will be extracted * @return string|null */ - public function toDatabase($value, DriverInterface $driver): ?string + public function toDatabase(mixed $value, Driver $driver): ?string { if ($value === null || $value === '' || $value === false) { return null; @@ -56,7 +56,7 @@ public function newId(): string * @param mixed $value The value to convert. * @return string|null Converted value. */ - public function marshal($value): ?string + public function marshal(mixed $value): ?string { if ($value === null || $value === '' || is_array($value)) { return null; diff --git a/app/vendor/cakephp/cakephp/src/Database/TypeConverterTrait.php b/app/vendor/cakephp/cakephp/src/Database/TypeConverterTrait.php deleted file mode 100644 index 2f04b814e..000000000 --- a/app/vendor/cakephp/cakephp/src/Database/TypeConverterTrait.php +++ /dev/null @@ -1,66 +0,0 @@ -toDatabase($value, $this->getDriver()); - $type = $type->toStatement($value, $this->getDriver()); - } - - return [$value, $type]; - } - - /** - * Matches columns to corresponding types - * - * Both $columns and $types should either be numeric based or string key based at - * the same time. - * - * @param array $columns list or associative array of columns and parameters to be bound with types - * @param array $types list or associative array of types - * @return array - */ - public function matchTypes(array $columns, array $types): array - { - if (!is_int(key($types))) { - $positions = array_intersect_key(array_flip($columns), $types); - $types = array_intersect_key($types, $positions); - $types = array_combine($positions, $types); - } - - return $types; - } -} diff --git a/app/vendor/cakephp/cakephp/src/Database/TypeFactory.php b/app/vendor/cakephp/cakephp/src/Database/TypeFactory.php index 617bad6d9..ae373cc15 100644 --- a/app/vendor/cakephp/cakephp/src/Database/TypeFactory.php +++ b/app/vendor/cakephp/cakephp/src/Database/TypeFactory.php @@ -29,9 +29,9 @@ class TypeFactory * representing the class that will do actual type conversions. * * @var array - * @psalm-var array> + * @phpstan-var array> */ - protected static $_types = [ + protected static array $_types = [ 'tinyinteger' => Type\IntegerType::class, 'smallinteger' => Type\IntegerType::class, 'integer' => Type\IntegerType::class, @@ -53,6 +53,11 @@ class TypeFactory 'timestampfractional' => Type\DateTimeFractionalType::class, 'timestamptimezone' => Type\DateTimeTimezoneType::class, 'uuid' => Type\UuidType::class, + 'nativeuuid' => Type\UuidType::class, + 'linestring' => Type\StringType::class, + 'geometry' => Type\StringType::class, + 'point' => Type\StringType::class, + 'polygon' => Type\StringType::class, ]; /** @@ -60,7 +65,7 @@ class TypeFactory * * @var array<\Cake\Database\TypeInterface> */ - protected static $_builtTypes = []; + protected static array $_builtTypes = []; /** * Returns a Type object capable of converting a type identified by name. @@ -75,7 +80,7 @@ public static function build(string $name): TypeInterface return static::$_builtTypes[$name]; } if (!isset(static::$_types[$name])) { - throw new InvalidArgumentException(sprintf('Unknown type "%s"', $name)); + throw new InvalidArgumentException(sprintf('Unknown type `%s`', $name)); } return static::$_builtTypes[$name] = new static::$_types[$name]($name); @@ -88,12 +93,11 @@ public static function build(string $name): TypeInterface */ public static function buildAll(): array { - $result = []; foreach (static::$_types as $name => $type) { - $result[$name] = static::$_builtTypes[$name] ?? static::build($name); + static::$_builtTypes[$name] ??= static::build($name); } - return $result; + return static::$_builtTypes; } /** @@ -106,7 +110,6 @@ public static function buildAll(): array public static function set(string $name, TypeInterface $instance): void { static::$_builtTypes[$name] = $instance; - static::$_types[$name] = get_class($instance); } /** @@ -115,7 +118,7 @@ public static function set(string $name, TypeInterface $instance): void * @param string $type Name of type to map. * @param string $className The classname to register. * @return void - * @psalm-param class-string<\Cake\Database\TypeInterface> $className + * @phpstan-param class-string<\Cake\Database\TypeInterface> $className */ public static function map(string $type, string $className): void { @@ -126,9 +129,9 @@ public static function map(string $type, string $className): void /** * Set type to classname mapping. * - * @param array $map List of types to be mapped. + * @param array $map List of types to be mapped. * @return void - * @psalm-param array> $map + * @phpstan-param array> $map */ public static function setMap(array $map): void { @@ -140,9 +143,9 @@ public static function setMap(array $map): void * Get mapped class name for given type or map array. * * @param string|null $type Type name to get mapped class for or null to get map array. - * @return array|string|null Configured class name for given $type or map array. + * @return array>|string|null Configured class name for given $type or map array. */ - public static function getMap(?string $type = null) + public static function getMap(?string $type = null): array|string|null { if ($type === null) { return static::$_types; @@ -162,10 +165,3 @@ public static function clear(): void static::$_builtTypes = []; } } - -// phpcs:disable -class_alias( - 'Cake\Database\TypeFactory', - 'Cake\Database\Type' -); -// phpcs:enable diff --git a/app/vendor/cakephp/cakephp/src/Database/TypeInterface.php b/app/vendor/cakephp/cakephp/src/Database/TypeInterface.php index fca0fdbe8..378945270 100644 --- a/app/vendor/cakephp/cakephp/src/Database/TypeInterface.php +++ b/app/vendor/cakephp/cakephp/src/Database/TypeInterface.php @@ -26,28 +26,28 @@ interface TypeInterface * Casts given value from a PHP type to one acceptable by a database. * * @param mixed $value Value to be converted to a database equivalent. - * @param \Cake\Database\DriverInterface $driver Object from which database preferences and configuration will be extracted. + * @param \Cake\Database\Driver $driver Object from which database preferences and configuration will be extracted. * @return mixed Given PHP type casted to one acceptable by a database. */ - public function toDatabase($value, DriverInterface $driver); + public function toDatabase(mixed $value, Driver $driver): mixed; /** * Casts given value from a database type to a PHP equivalent. * * @param mixed $value Value to be converted to PHP equivalent - * @param \Cake\Database\DriverInterface $driver Object from which database preferences and configuration will be extracted + * @param \Cake\Database\Driver $driver Object from which database preferences and configuration will be extracted * @return mixed Given value casted from a database to a PHP equivalent. */ - public function toPHP($value, DriverInterface $driver); + public function toPHP(mixed $value, Driver $driver): mixed; /** - * Casts given value to its Statement equivalent. + * Get the binding type to use in a PDO statement. * - * @param mixed $value Value to be converted to PDO statement. - * @param \Cake\Database\DriverInterface $driver Object from which database preferences and configuration will be extracted. - * @return mixed Given value casted to its Statement equivalent. + * @param mixed $value The value being bound. + * @param \Cake\Database\Driver $driver Object from which database preferences and configuration will be extracted. + * @return int One of PDO::PARAM_* constants. */ - public function toStatement($value, DriverInterface $driver); + public function toStatement(mixed $value, Driver $driver): int; /** * Marshals flat data into PHP objects. @@ -58,7 +58,7 @@ public function toStatement($value, DriverInterface $driver); * @param mixed $value The value to convert. * @return mixed Converted value. */ - public function marshal($value); + public function marshal(mixed $value): mixed; /** * Returns the base type name that this class is inheriting. @@ -87,5 +87,5 @@ public function getName(): ?string; * @return mixed A new primary key value. * @see \Cake\Database\Type\UuidType */ - public function newId(); + public function newId(): mixed; } diff --git a/app/vendor/cakephp/cakephp/src/Database/TypeMap.php b/app/vendor/cakephp/cakephp/src/Database/TypeMap.php index ac3f8eb8b..c7ecdf42f 100644 --- a/app/vendor/cakephp/cakephp/src/Database/TypeMap.php +++ b/app/vendor/cakephp/cakephp/src/Database/TypeMap.php @@ -29,7 +29,7 @@ class TypeMap * * @var array */ - protected $_defaults = []; + protected array $_defaults = []; /** * Array with the fields and the related types that override defaults this query might contain @@ -39,7 +39,7 @@ class TypeMap * * @var array */ - protected $_types = []; + protected array $_types = []; /** * Creates an instance with the given defaults @@ -143,7 +143,7 @@ public function getTypes(): array * @param string|int $column The type for a given column * @return string|null */ - public function type($column): ?string + public function type(string|int $column): ?string { return $this->_types[$column] ?? $this->_defaults[$column] ?? null; } diff --git a/app/vendor/cakephp/cakephp/src/Database/TypeMapTrait.php b/app/vendor/cakephp/cakephp/src/Database/TypeMapTrait.php index 402a9b5fe..ffb699fe7 100644 --- a/app/vendor/cakephp/cakephp/src/Database/TypeMapTrait.php +++ b/app/vendor/cakephp/cakephp/src/Database/TypeMapTrait.php @@ -27,7 +27,7 @@ trait TypeMapTrait /** * @var \Cake\Database\TypeMap|null */ - protected $_typeMap; + protected ?TypeMap $_typeMap = null; /** * Creates a new TypeMap if $typeMap is an array, otherwise exchanges it for the given one. @@ -35,7 +35,7 @@ trait TypeMapTrait * @param \Cake\Database\TypeMap|array $typeMap Creates a TypeMap if array, otherwise sets the given TypeMap * @return $this */ - public function setTypeMap($typeMap) + public function setTypeMap(TypeMap|array $typeMap) { $this->_typeMap = is_array($typeMap) ? new TypeMap($typeMap) : $typeMap; @@ -49,11 +49,7 @@ public function setTypeMap($typeMap) */ public function getTypeMap(): TypeMap { - if ($this->_typeMap === null) { - $this->_typeMap = new TypeMap(); - } - - return $this->_typeMap; + return $this->_typeMap ??= new TypeMap(); } /** diff --git a/app/vendor/cakephp/cakephp/src/Database/TypedResultTrait.php b/app/vendor/cakephp/cakephp/src/Database/TypedResultTrait.php index 313c01bd3..1ab16c2b4 100644 --- a/app/vendor/cakephp/cakephp/src/Database/TypedResultTrait.php +++ b/app/vendor/cakephp/cakephp/src/Database/TypedResultTrait.php @@ -26,7 +26,7 @@ trait TypedResultTrait * * @var string */ - protected $_returnType = 'string'; + protected string $_returnType = 'string'; /** * Gets the type of the value this object will generate. diff --git a/app/vendor/cakephp/cakephp/src/Database/ValueBinder.php b/app/vendor/cakephp/cakephp/src/Database/ValueBinder.php index 0e5dd3d08..4e152647a 100644 --- a/app/vendor/cakephp/cakephp/src/Database/ValueBinder.php +++ b/app/vendor/cakephp/cakephp/src/Database/ValueBinder.php @@ -30,14 +30,14 @@ class ValueBinder * * @var array */ - protected $_bindings = []; + protected array $_bindings = []; /** - * A counter of the number of parameters bound in this expression object + * A counter of parameters bound in this expression object * * @var int */ - protected $_bindingsCount = 0; + protected int $_bindingsCount = 0; /** * Associates a query placeholder to a value and a type @@ -49,7 +49,7 @@ class ValueBinder * to database * @return void */ - public function bind($param, $value, $type = null): void + public function bind(string|int $param, mixed $value, string|int|null $type = null): void { $this->_bindings[$param] = compact('value', 'type') + [ 'placeholder' => is_int($param) ? $param : substr($param, 1), @@ -68,8 +68,8 @@ public function bind($param, $value, $type = null): void public function placeholder(string $token): string { $number = $this->_bindingsCount++; - if ($token[0] !== ':' && $token !== '?') { - $token = sprintf(':%s%s', $token, $number); + if (!str_starts_with($token, ':') && $token !== '?') { + return sprintf(':%s%s', $token, $number); } return $token; @@ -83,7 +83,7 @@ public function placeholder(string $token): string * @param string|int|null $type The type with which all values will be bound * @return array with the placeholders to insert in the query */ - public function generateManyNamed(iterable $values, $type = null): array + public function generateManyNamed(iterable $values, string|int|null $type = null): array { $placeholders = []; foreach ($values as $k => $value) { @@ -140,7 +140,7 @@ public function resetCount(): void public function attachTo(StatementInterface $statement): void { $bindings = $this->bindings(); - if (empty($bindings)) { + if (!$bindings) { return; } diff --git a/app/vendor/cakephp/cakephp/src/Database/composer.json b/app/vendor/cakephp/cakephp/src/Database/composer.json index 64c22e80b..00b65f86f 100644 --- a/app/vendor/cakephp/cakephp/src/Database/composer.json +++ b/app/vendor/cakephp/cakephp/src/Database/composer.json @@ -24,17 +24,29 @@ "source": "https://github.com/cakephp/database" }, "require": { - "php": ">=7.4.0", - "cakephp/core": "^4.0", - "cakephp/datasource": "^4.0" + "php": ">=8.1", + "cakephp/core": "5.2.*@dev", + "cakephp/chronos": "^3.1", + "cakephp/datasource": "5.2.*@dev", + "psr/log": "^3.0" }, - "suggest": { - "cakephp/i18n": "If you are using locale-aware datetime formats or Chronos types.", - "cakephp/log": "If you want to use query logging without providing a logger yourself." + "require-dev": { + "cakephp/i18n": "5.2.*@dev", + "cakephp/log": "5.2.*@dev" }, "autoload": { "psr-4": { "Cake\\Database\\": "." } + }, + "suggest": { + "cakephp/i18n": "If you are using locale-aware datetime formats.", + "cakephp/log": "If you want to use query logging without providing a logger yourself." + }, + "prefer-stable": true, + "extra": { + "branch-alias": { + "dev-5.x": "5.2.x-dev" + } } } diff --git a/app/vendor/cakephp/cakephp/src/Datasource/ConnectionInterface.php b/app/vendor/cakephp/cakephp/src/Datasource/ConnectionInterface.php index 7efef433d..2c1fb6b24 100644 --- a/app/vendor/cakephp/cakephp/src/Datasource/ConnectionInterface.php +++ b/app/vendor/cakephp/cakephp/src/Datasource/ConnectionInterface.php @@ -16,29 +16,12 @@ */ namespace Cake\Datasource; -use Psr\Log\LoggerAwareInterface; -use Psr\Log\LoggerInterface; use Psr\SimpleCache\CacheInterface; /** - * This interface defines the methods you can depend on in - * a connection. - * - * @method object getDriver() Gets the driver instance. {@see \Cake\Database\Connnection::getDriver()} - * @method $this setLogger($logger) Set the current logger. {@see \Cake\Database\Connnection::setLogger()} - * @method bool supportsDynamicConstraints() Returns whether the driver supports adding or dropping constraints to - * already created tables. {@see \Cake\Database\Connnection::supportsDynamicConstraints()} - * @method \Cake\Database\Schema\Collection getSchemaCollection() Gets a Schema\Collection object for this connection. - * {@see \Cake\Database\Connnection::getSchemaCollection()} - * @method \Cake\Database\StatementInterface prepare($sql) Prepares a SQL statement to be executed. - * {@see \Cake\Database\Connnection::prepare()} - * @method \Cake\Database\StatementInterface execute($query, $params = [], array $types = []) Executes a query using - * `$params` for interpolating values and $types as a hint for each those params. - * {@see \Cake\Database\Connnection::execute()} - * @method \Cake\Database\StatementInterface query(string $sql) Executes a SQL statement and returns the Statement - * object as result. {@see \Cake\Database\Connnection::query()} + * This interface defines the methods you can depend on in a connection. */ -interface ConnectionInterface extends LoggerAwareInterface +interface ConnectionInterface { /** * @var string @@ -51,11 +34,12 @@ interface ConnectionInterface extends LoggerAwareInterface public const ROLE_READ = 'read'; /** - * Gets the current logger object. + * Gets the driver instance. * - * @return \Psr\Log\LoggerInterface logger instance + * @param string $role + * @return object */ - public function getLogger(): LoggerInterface; + public function getDriver(string $role = self::ROLE_WRITE): object; /** * Set a cacher. @@ -85,70 +69,4 @@ public function configName(): string; * @return array */ public function config(): array; - - /** - * Executes a callable function inside a transaction, if any exception occurs - * while executing the passed callable, the transaction will be rolled back - * If the result of the callable function is `false`, the transaction will - * also be rolled back. Otherwise, the transaction is committed after executing - * the callback. - * - * The callback will receive the connection instance as its first argument. - * - * ### Example: - * - * ``` - * $connection->transactional(function ($connection) { - * $connection->newQuery()->delete('users')->execute(); - * }); - * ``` - * - * @param callable $callback The callback to execute within a transaction. - * @return mixed The return value of the callback. - * @throws \Exception Will re-throw any exception raised in $callback after - * rolling back the transaction. - */ - public function transactional(callable $callback); - - /** - * Run an operation with constraints disabled. - * - * Constraints should be re-enabled after the callback succeeds/fails. - * - * ### Example: - * - * ``` - * $connection->disableConstraints(function ($connection) { - * $connection->newQuery()->delete('users')->execute(); - * }); - * ``` - * - * @param callable $callback The callback to execute within a transaction. - * @return mixed The return value of the callback. - * @throws \Exception Will re-throw any exception raised in $callback after - * rolling back the transaction. - */ - public function disableConstraints(callable $callback); - - /** - * Enable/disable query logging - * - * @param bool $enable Enable/disable query logging - * @return $this - */ - public function enableQueryLogging(bool $enable = true); - - /** - * Disable query logging - * - * @return $this - */ - public function disableQueryLogging(); - - /** - * Check if query logging is enabled. - * - * @return bool - */ - public function isQueryLoggingEnabled(): bool; } diff --git a/app/vendor/cakephp/cakephp/src/Datasource/ConnectionManager.php b/app/vendor/cakephp/cakephp/src/Datasource/ConnectionManager.php index 8f244ea16..820d02fc2 100644 --- a/app/vendor/cakephp/cakephp/src/Datasource/ConnectionManager.php +++ b/app/vendor/cakephp/cakephp/src/Datasource/ConnectionManager.php @@ -23,6 +23,7 @@ use Cake\Database\Driver\Sqlite; use Cake\Database\Driver\Sqlserver; use Cake\Datasource\Exception\MissingDatasourceConfigException; +use Closure; /** * Manages and loads instances of Connection @@ -45,15 +46,15 @@ class ConnectionManager * * @var array */ - protected static $_aliasMap = []; + protected static array $_aliasMap = []; /** * An array mapping url schemes to fully qualified driver class names * * @var array - * @psalm-var array + * @phpstan-var array */ - protected static $_dsnClassMap = [ + protected static array $_dsnClassMap = [ 'mysql' => Mysql::class, 'postgres' => Postgres::class, 'sqlite' => Sqlite::class, @@ -63,9 +64,9 @@ class ConnectionManager /** * The ConnectionRegistry used by the manager. * - * @var \Cake\Datasource\ConnectionRegistry|null + * @var \Cake\Datasource\ConnectionRegistry */ - protected static $_registry; + protected static ConnectionRegistry $_registry; /** * Configure a new connection object. @@ -73,12 +74,12 @@ class ConnectionManager * The connection will not be constructed until it is first used. * * @param array|string $key The name of the connection config, or an array of multiple configs. - * @param array|null $config An array of name => config data for adapter. + * @param \Cake\Datasource\ConnectionInterface|\Closure|array|null $config An array of name => config data for adapter. * @return void * @throws \Cake\Core\Exception\CakeException When trying to modify an existing config. * @see \Cake\Core\StaticConfigTrait::config() */ - public static function setConfig($key, $config = null): void + public static function setConfig(array|string $key, ConnectionInterface|Closure|array|null $config = null): void { if (is_array($config)) { $config['name'] = $key; @@ -109,19 +110,19 @@ public static function setConfig($key, $config = null): void * * Note that query-string arguments are also parsed and set as values in the returned configuration. * - * @param string $config The DSN string to convert to a configuration array - * @return array The configuration array to be stored after parsing the DSN + * @param string $dsn The DSN string to convert to a configuration array + * @return array The configuration array to be stored after parsing the DSN */ - public static function parseDsn(string $config): array + public static function parseDsn(string $dsn): array { - $config = static::_parseDsn($config); + $config = static::_parseDsn($dsn); - if (isset($config['path']) && empty($config['database'])) { + if (isset($config['path']) && empty($config['database']) && is_string($config['path'])) { $config['database'] = substr($config['path'], 1); } if (empty($config['driver'])) { - $config['driver'] = $config['className']; + $config['driver'] = $config['className'] ?? null; $config['className'] = Connection::class; } @@ -197,7 +198,7 @@ public static function aliases(): array * @throws \Cake\Datasource\Exception\MissingDatasourceConfigException When config * data is missing. */ - public static function get(string $name, bool $useAliases = true) + public static function get(string $name, bool $useAliases = true): ConnectionInterface { if ($useAliases && isset(static::$_aliasMap[$name])) { $name = static::$_aliasMap[$name]; @@ -206,10 +207,7 @@ public static function get(string $name, bool $useAliases = true) if (!isset(static::$_config[$name])) { throw new MissingDatasourceConfigException(['name' => $name]); } - - if (!isset(static::$_registry)) { - static::$_registry = new ConnectionRegistry(); - } + static::$_registry ??= new ConnectionRegistry(); return static::$_registry->{$name} ?? static::$_registry->load($name, static::$_config[$name]); } diff --git a/app/vendor/cakephp/cakephp/src/Datasource/ConnectionRegistry.php b/app/vendor/cakephp/cakephp/src/Datasource/ConnectionRegistry.php index 34a5aca51..b5455f3f5 100644 --- a/app/vendor/cakephp/cakephp/src/Datasource/ConnectionRegistry.php +++ b/app/vendor/cakephp/cakephp/src/Datasource/ConnectionRegistry.php @@ -19,6 +19,7 @@ use Cake\Core\App; use Cake\Core\ObjectRegistry; use Cake\Datasource\Exception\MissingDatasourceException; +use Closure; /** * A registry object for connection instances. @@ -34,11 +35,11 @@ class ConnectionRegistry extends ObjectRegistry * Part of the template method for Cake\Core\ObjectRegistry::load() * * @param string $class Partial classname to resolve. - * @return string|null Either the correct class name or null. - * @psalm-return class-string|null + * @return class-string<\Cake\Datasource\ConnectionInterface>|null Either the correct class name or null. */ protected function _resolveClassName(string $class): ?string { + /** @var class-string<\Cake\Datasource\ConnectionInterface>|null */ return App::className($class, 'Datasource'); } @@ -65,28 +66,27 @@ protected function _throwMissingClassError(string $class, ?string $plugin): void * * Part of the template method for Cake\Core\ObjectRegistry::load() * - * If a callable is passed as first argument, The returned value of this - * function will be the result of the callable. + * If a closure is passed as first argument, The returned value of this + * function will be the result from calling the closure. * - * @param \Cake\Datasource\ConnectionInterface|callable|string $class The classname or object to make. + * @param \Cake\Datasource\ConnectionInterface|\Closure|class-string<\Cake\Datasource\ConnectionInterface> $class The classname or object to make. * @param string $alias The alias of the object. * @param array $config An array of settings to use for the datasource. * @return \Cake\Datasource\ConnectionInterface A connection with the correct settings. */ - protected function _create($class, string $alias, array $config) + protected function _create(object|string $class, string $alias, array $config): ConnectionInterface { - if (is_callable($class)) { - return $class($alias); - } + if (is_string($class)) { + unset($config['className']); - if (is_object($class)) { - return $class; + return new $class($config); } - unset($config['className']); + if ($class instanceof Closure) { + return $class($alias); + } - /** @var \Cake\Datasource\ConnectionInterface */ - return new $class($config); + return $class; } /** diff --git a/app/vendor/cakephp/cakephp/src/Datasource/EntityInterface.php b/app/vendor/cakephp/cakephp/src/Datasource/EntityInterface.php index 58876709b..c5105533e 100644 --- a/app/vendor/cakephp/cakephp/src/Datasource/EntityInterface.php +++ b/app/vendor/cakephp/cakephp/src/Datasource/EntityInterface.php @@ -18,16 +18,18 @@ use ArrayAccess; use JsonSerializable; +use Stringable; /** * Describes the methods that any class representing a data storage should * comply with. * * @property mixed $id Alias for commonly used primary key. - * @method bool[] getAccessible() Accessible configuration for this entity. * @template-extends \ArrayAccess + * @method bool hasValue(string $field) + * @method static patch(array $values, array $options = []) */ -interface EntityInterface extends ArrayAccess, JsonSerializable +interface EntityInterface extends ArrayAccess, JsonSerializable, Stringable { /** * Sets hidden fields. @@ -61,6 +63,23 @@ public function setVirtual(array $fields, bool $merge = false); */ public function getVirtual(): array; + /** + * Returns whether a field is an original one. + * Original fields are those that an entity was instantiated with. + * + * @param string $name Name + * @return bool + */ + public function isOriginalField(string $name): bool; + + /** + * Returns an array of original fields. + * Original fields are those that an entity was initialized with. + * + * @return array + */ + public function getOriginalFields(): array; + /** * Sets the dirty status of a single field. * @@ -126,7 +145,7 @@ public function setErrors(array $errors, bool $overwrite = false); * @param bool $overwrite Whether to overwrite pre-existing errors for $field * @return $this */ - public function setError(string $field, $errors, bool $overwrite = false); + public function setError(string $field, array|string $errors, bool $overwrite = false); /** * Stores whether a field value can be changed or set in this entity. @@ -136,7 +155,14 @@ public function setError(string $field, $errors, bool $overwrite = false); * mark it as protected. * @return $this */ - public function setAccess($field, bool $set); + public function setAccess(array|string $field, bool $set); + + /** + * Accessible configuration for this entity. + * + * @return array + */ + public function getAccessible(): array; /** * Checks if a field is accessible @@ -166,7 +192,7 @@ public function getSource(): string; * stored in this entity, indexed by field name. * * @param array $fields List of fields to be returned - * @return array + * @return array */ public function extractOriginal(array $fields): array; @@ -175,7 +201,7 @@ public function extractOriginal(array $fields): array; * stored in this entity, indexed by field name. * * @param array $fields List of fields to be returned - * @return array + * @return array */ public function extractOriginalChanged(array $fields): array; @@ -190,7 +216,7 @@ public function extractOriginalChanged(array $fields): array; * keys are `setter` and `guard` * @return $this */ - public function set($field, $value = null, array $options = []); + public function set(array|string $field, mixed $value = null, array $options = []); /** * Returns the value of a field by name @@ -198,15 +224,33 @@ public function set($field, $value = null, array $options = []); * @param string $field the name of the field to retrieve * @return mixed */ - public function &get(string $field); + public function &get(string $field): mixed; + + /** + * Enable/disable field presence check when accessing a property. + * + * If enabled an exception will be thrown when trying to access a non-existent property. + * + * @param bool $value `true` to enable, `false` to disable. + */ + public function requireFieldPresence(bool $value = true): void; + + /** + * Returns whether a field has an original value + * + * @param string $field + * @return bool + */ + public function hasOriginal(string $field): bool; /** * Returns the original value of a field. * * @param string $field The name of the field. + * @param bool $allowFallback whether to allow falling back to the current field value if no original exists * @return mixed */ - public function getOriginal(string $field); + public function getOriginal(string $field, bool $allowFallback = true): mixed; /** * Gets all original values of the entity. @@ -216,13 +260,14 @@ public function getOriginal(string $field); public function getOriginalValues(): array; /** - * Returns whether this entity contains a field named $field - * and is not set to null. + * Returns whether this entity contains a field named $field. + * + * The method will return `true` even when the field is set to `null`. * * @param array|string $field The field to check. * @return bool */ - public function has($field): bool; + public function has(array|string $field): bool; /** * Removes a field or list of fields from this entity @@ -230,7 +275,7 @@ public function has($field): bool; * @param array|string $field The field to unset. * @return $this */ - public function unset($field); + public function unset(array|string $field); /** * Get the list of visible fields. @@ -245,7 +290,7 @@ public function getVisible(): array; * *Note* hidden fields are not visible, and will not be output * by toArray(). * - * @return array + * @return array */ public function toArray(): array; @@ -255,7 +300,7 @@ public function toArray(): array; * * @param array $fields list of fields to be returned * @param bool $onlyDirty Return the requested field only if it is dirty - * @return array + * @return array */ public function extract(array $fields, bool $onlyDirty = false): array; @@ -285,4 +330,12 @@ public function setNew(bool $new); * @return bool Whether the entity has been persisted. */ public function isNew(): bool; + + /** + * Returns a string representation of this object. + * + * @return string + * @deprecated 5.2.0 Casting an entity to string is deprecated. Use `json_encode()` instead to get a string representation of the entity. + */ + public function __toString(): string; } diff --git a/app/vendor/cakephp/cakephp/src/Datasource/EntityTrait.php b/app/vendor/cakephp/cakephp/src/Datasource/EntityTrait.php index 3cd54bb1a..0d903ecd2 100644 --- a/app/vendor/cakephp/cakephp/src/Datasource/EntityTrait.php +++ b/app/vendor/cakephp/cakephp/src/Datasource/EntityTrait.php @@ -17,11 +17,11 @@ namespace Cake\Datasource; use Cake\Collection\Collection; +use Cake\Datasource\Exception\MissingPropertyException; use Cake\ORM\Entity; use Cake\Utility\Hash; use Cake\Utility\Inflector; use InvalidArgumentException; -use Traversable; use function Cake\Core\deprecationWarning; /** @@ -35,14 +35,21 @@ trait EntityTrait * * @var array */ - protected $_fields = []; + protected array $_fields = []; /** * Holds all fields that have been changed and their original values for this entity. * * @var array */ - protected $_original = []; + protected array $_original = []; + + /** + * Holds all fields that have been initially set on instantiation, or after marking as clean + * + * @var array + */ + protected array $_originalFields = []; /** * List of field names that should **not** be included in JSON or Array @@ -50,7 +57,7 @@ trait EntityTrait * * @var array */ - protected $_hidden = []; + protected array $_hidden = []; /** * List of computed or virtual fields that **should** be included in JSON or array @@ -59,22 +66,22 @@ trait EntityTrait * * @var array */ - protected $_virtual = []; + protected array $_virtual = []; /** * Holds a list of the fields that were modified or added after this object * was originally created. * - * @var array + * @var array */ - protected $_dirty = []; + protected array $_dirty = []; /** * Holds a cached list of getters/setters per class * * @var array>> */ - protected static $_accessors = []; + protected static array $_accessors = []; /** * Indicates whether this entity is yet to be persisted. @@ -83,58 +90,58 @@ trait EntityTrait * * @var bool */ - protected $_new = true; + protected bool $_new = true; /** * List of errors per field as stored in this object. * * @var array */ - protected $_errors = []; + protected array $_errors = []; /** * List of invalid fields and their data for errors upon validation/patching. * * @var array */ - protected $_invalid = []; + protected array $_invalid = []; /** - * Map of fields in this entity that can be safely assigned, each + * Map of fields in this entity that can be safely mass assigned, each * field name points to a boolean indicating its status. An empty array - * means no fields are accessible + * means no fields are accessible for mass assigment. * * The special field '\*' can also be mapped, meaning that any other field * not defined in the map will take its value. For example, `'*' => true` - * means that any field not defined in the map will be accessible by default + * means that any field not defined in the map will be accessible for mass + * assignment by default. * * @var array */ - protected $_accessible = ['*' => true]; + protected array $_accessible = ['*' => true]; /** * The alias of the repository this entity came from * * @var string */ - protected $_registryAlias = ''; + protected string $_registryAlias = ''; /** * Storing the current visitation status while recursing through entities getting errors. * * @var bool */ - protected $_hasBeenVisited = false; + protected bool $_hasBeenVisited = false; /** - * Set to true in your entity's class definition or - * via application logic. When true. has() and related - * methods will use `array_key_exists` instead of `isset` - * to decide if fields are 'defined' in an entity. + * Whether the presence of a field is checked when accessing a property. + * + * If enabled an exception will be thrown when trying to access a non-existent property. * * @var bool */ - protected $_hasAllowsNull = false; + protected bool $requireFieldPresence = false; /** * Magic getter to access fields that have been set in this entity @@ -142,7 +149,7 @@ trait EntityTrait * @param string $field Name of the field to access * @return mixed */ - public function &__get(string $field) + public function &__get(string $field): mixed { return $this->get($field); } @@ -154,7 +161,7 @@ public function &__get(string $field) * @param mixed $value The value to set to the field * @return void */ - public function __set(string $field, $value): void + public function __set(string $field, mixed $value): void { $this->set($field, $value); } @@ -165,11 +172,10 @@ public function __set(string $field, $value): void * * @param string $field The field to check. * @return bool - * @see \Cake\ORM\Entity::has() */ public function __isset(string $field): bool { - return $this->has($field); + return $this->has($field) && $this->get($field) !== null; } /** @@ -192,14 +198,62 @@ public function __unset(string $field): void * $entity->set('name', 'Andrew'); * ``` * - * It is also possible to mass-assign multiple fields to this entity - * with one call by passing a hashed array as fields in the form of - * field => value pairs + * Some times it is handy to bypass setter functions in this entity when assigning + * fields. You can achieve this by disabling the `setter` option using the + * `$options` parameter: + * + * ``` + * $entity->set('name', 'Andrew', ['setter' => false]); + * ``` + * + * You can use the `asOriginal` option to set the given field as original, if it wasn't + * present when the entity was instantiated. + * + * ``` + * $entity = new Entity(['name' => 'andrew', 'id' => 1]); + * + * $entity->set('phone_number', '555-0134'); + * print_r($entity->getOriginalFields()) // prints ['name', 'id'] + * + * $entity->set('phone_number', '555-0134', ['asOriginal' => true]); + * print_r($entity->getOriginalFields()) // prints ['name', 'id', 'phone_number'] + * ``` + * + * @param array|string $field The name of field to set. + * @param mixed $value The value to set to the field. + * @param array $options Options to be used for setting the field. Allowed option + * keys are `setter`, `guard` and `asOriginal` + * @return $this + * @throws \InvalidArgumentException + */ + public function set(array|string $field, mixed $value = null, array $options = []) + { + if (is_string($field)) { + $options += ['guard' => false]; + + return $this->patch([$field => $value], $options); + } + + deprecationWarning( + '5.2.0', + sprintf( + 'Passing an array as the first argument to `%s::set()` is deprecated. ' + . 'Use `%s::patch()` instead.', + static::class, + static::class, + ), + ); + + return $this->patch($field, (array)$value); + } + + /** + * Patch (mass-assign) multiple fields to this entity. * * ### Example: * * ``` - * $entity->set(['name' => 'andrew', 'id' => 1]); + * $entity->patch(['name' => 'andrew', 'id' => 1]); * echo $entity->name // prints andrew * echo $entity->id // prints 1 * ``` @@ -209,8 +263,7 @@ public function __unset(string $field): void * `$options` parameter: * * ``` - * $entity->set('name', 'Andrew', ['setter' => false]); - * $entity->set(['name' => 'Andrew', 'id' => 1], ['setter' => false]); + * $entity->patch(['name' => 'Andrew', 'id' => 1], ['setter' => false]); * ``` * * Mass assignment should be treated carefully when accepting user input, by default @@ -218,71 +271,109 @@ public function __unset(string $field): void * the guarding for a single set call with the `guard` option: * * ``` - * $entity->set(['name' => 'Andrew', 'id' => 1], ['guard' => false]); + * $entity->patch(['name' => 'Andrew', 'id' => 1], ['guard' => false]); * ``` * - * You do not need to use the guard option when assigning fields individually: + * You can use the `asOriginal` option to set the given field as original, if it wasn't + * present when the entity was instantiated. * * ``` - * // No need to use the guard option. - * $entity->set('name', 'Andrew'); + * $entity = new Entity(['name' => 'andrew', 'id' => 1]); + * + * $entity->patch(['phone_number' => '555-0134']); + * print_r($entity->getOriginalFields()) // prints ['name', 'id'] + * + * $entity->patch(['phone_number' => '555-0134'], ['asOriginal' => true]); + * print_r($entity->getOriginalFields()) // prints ['name', 'id', 'phone_number'] * ``` * - * @param array|string $field the name of field to set or a list of - * fields with their respective values - * @param mixed $value The value to set to the field or an array if the - * first argument is also an array, in which case will be treated as $options + * @param array $values Map of fields with their respective values. * @param array $options Options to be used for setting the field. Allowed option - * keys are `setter` and `guard` + * keys are `setter`, `guard` and `asOriginal` * @return $this * @throws \InvalidArgumentException */ - public function set($field, $value = null, array $options = []) + public function patch(array $values, array $options = []) { - if (is_string($field) && $field !== '') { - $guard = false; - $field = [$field => $value]; - } else { - $guard = true; - $options = (array)$value; - } + $options += ['setter' => true, 'guard' => true, 'asOriginal' => false]; - if (!is_array($field)) { - throw new InvalidArgumentException('Cannot set an empty field'); + if ($options['asOriginal'] === true) { + $this->setOriginalField(array_keys($values)); } - $options += ['setter' => true, 'guard' => $guard]; - foreach ($field as $name => $value) { + foreach ($values as $name => $value) { $name = (string)$name; + if ($name === '') { + throw new InvalidArgumentException('Cannot set an empty field'); + } + if ($options['guard'] === true && !$this->isAccessible($name)) { continue; } - $this->setDirty($name, true); + if ($options['asOriginal'] || $this->isModified($name, $value)) { + $this->setDirty($name, true); + } else { + continue; + } + + if ($options['setter']) { + $setter = static::_accessor($name, 'set'); + if ($setter) { + $value = $this->{$setter}($value); + } + } if ( + $this->isOriginalField($name) && !array_key_exists($name, $this->_original) && array_key_exists($name, $this->_fields) && - $this->_fields[$name] !== $value + $value !== $this->_fields[$name] ) { $this->_original[$name] = $this->_fields[$name]; } - if (!$options['setter']) { - $this->_fields[$name] = $value; - continue; - } - - $setter = static::_accessor($name, 'set'); - if ($setter) { - $value = $this->{$setter}($value); - } $this->_fields[$name] = $value; } return $this; } + /** + * Check if the provided value is same as existing value for a field. + * + * This check is used to determine if a field should be set as dirty or not. + * It will return `false` for scalar values and objects which haven't changed. + * For arrays `true` will be returned always because the original/updated list + * could contain references to the same objects, even though those objects + * may have changed internally. + * + * @param string $field The field to check. + * @return bool + */ + protected function isModified(string $field, mixed $value): bool + { + if (!array_key_exists($field, $this->_fields)) { + return true; + } + + $existing = $this->_fields[$field] ?? null; + + if (($value === null || is_scalar($value)) && $existing === $value) { + return false; + } + + if ( + is_object($value) + && !($value instanceof EntityInterface) + && $existing == $value + ) { + return false; + } + + return true; + } + /** * Returns the value of a field by name * @@ -290,36 +381,69 @@ public function set($field, $value = null, array $options = []) * @return mixed * @throws \InvalidArgumentException if an empty field name is passed */ - public function &get(string $field) + public function &get(string $field): mixed { if ($field === '') { throw new InvalidArgumentException('Cannot get an empty field'); } $value = null; - - if (isset($this->_fields[$field])) { + $fieldIsPresent = false; + if (array_key_exists($field, $this->_fields)) { + $fieldIsPresent = true; $value = &$this->_fields[$field]; } $method = static::_accessor($field, 'get'); if ($method) { + // Must be variable before returning: Only variable references should be returned by reference. $result = $this->{$method}($value); return $result; } + if (!$fieldIsPresent && $this->requireFieldPresence) { + throw new MissingPropertyException([ + 'property' => $field, + 'entity' => $this::class, + ]); + } + return $value; } + /** + * Enable/disable field presence check when accessing a property. + * + * If enabled an exception will be thrown when trying to access a non-existent property. + * + * @param bool $value `true` to enable, `false` to disable. + */ + public function requireFieldPresence(bool $value = true): void + { + $this->requireFieldPresence = $value; + } + + /** + * Returns whether a field has an original value + * + * @param string $field + * @return bool + */ + public function hasOriginal(string $field): bool + { + return array_key_exists($field, $this->_original); + } + /** * Returns the value of an original field by name * * @param string $field the name of the field for which original value is retrieved. + * @param bool $allowFallback whether to allow falling back to the current field value if no original exists * @return mixed * @throws \InvalidArgumentException if an empty field name is passed. */ - public function getOriginal(string $field) + public function getOriginal(string $field, bool $allowFallback = true): mixed { if ($field === '') { throw new InvalidArgumentException('Cannot get an empty field'); @@ -328,6 +452,10 @@ public function getOriginal(string $field) return $this->_original[$field]; } + if (!$allowFallback) { + throw new InvalidArgumentException(sprintf('Cannot retrieve original value for field `%s`', $field)); + } + return $this->get($field); } @@ -341,7 +469,10 @@ public function getOriginalValues(): array $originals = $this->_original; $originalKeys = array_keys($originals); foreach ($this->_fields as $key => $value) { - if (!in_array($key, $originalKeys, true)) { + if ( + !in_array($key, $originalKeys, true) && + $this->isOriginalField($key) + ) { $originals[$key] = $value; } } @@ -350,15 +481,16 @@ public function getOriginalValues(): array } /** - * Returns whether this entity contains a field named $field - * that contains a non-null value. + * Returns whether this entity contains a field named $field. + * + * It will return `true` even for fields set to `null`. * * ### Example: * * ``` * $entity = new Entity(['id' => 1, 'name' => null]); * $entity->has('id'); // true - * $entity->has('name'); // false + * $entity->has('name'); // true * $entity->has('last_name'); // false * ``` * @@ -368,22 +500,16 @@ public function getOriginalValues(): array * $entity->has(['name', 'last_name']); * ``` * - * All fields must not be null to get a truthy result. - * - * When checking multiple fields. All fields must not be null - * in order for true to be returned. + * When checking multiple fields all fields must have a value (even `null`) + * present for the method to return `true`. * * @param array|string $field The field or fields to check. * @return bool */ - public function has($field): bool + public function has(array|string $field): bool { foreach ((array)$field as $prop) { - if ($this->_hasAllowsNull) { - if (!array_key_exists($prop, $this->_fields) && !static::_accessor($prop, 'get')) { - return false; - } - } elseif ($this->get($prop) === null) { + if (!array_key_exists($prop, $this->_fields) && !static::_accessor($prop, 'get')) { return false; } } @@ -412,12 +538,8 @@ public function isEmpty(string $field): bool if ( $value === null || ( - is_array($value) && - empty($value) || - ( - is_string($value) && - $value === '' - ) + $value === [] || + $value === '' ) ) { return true; @@ -460,30 +582,16 @@ public function hasValue(string $field): bool * @param array|string $field The field to unset. * @return $this */ - public function unset($field) + public function unset(array|string $field) { $field = (array)$field; foreach ($field as $p) { - unset($this->_fields[$p], $this->_original[$p], $this->_dirty[$p]); + unset($this->_fields[$p], $this->_dirty[$p]); } return $this; } - /** - * Removes a field or list of fields from this entity - * - * @deprecated 4.0.0 Use {@link unset()} instead. Will be removed in 5.0. - * @param array|string $field The field to unset. - * @return $this - */ - public function unsetProperty($field) - { - deprecationWarning('EntityTrait::unsetProperty() is deprecated. Use unset() instead.'); - - return $this->unset($field); - } - /** * Sets hidden fields. * @@ -570,7 +678,7 @@ public function getVisible(): array * This method will recursively transform entities assigned to fields * into arrays as well. * - * @return array + * @return array */ public function toArray(): array { @@ -599,7 +707,7 @@ public function toArray(): array /** * Returns the fields that will be serialized as JSON * - * @return array + * @return array */ public function jsonSerialize(): array { @@ -612,9 +720,9 @@ public function jsonSerialize(): array * @param string $offset The offset to check. * @return bool Success */ - public function offsetExists($offset): bool + public function offsetExists(mixed $offset): bool { - return $this->has($offset); + return $this->__isset($offset); } /** @@ -623,8 +731,7 @@ public function offsetExists($offset): bool * @param string $offset The offset to get. * @return mixed */ - #[\ReturnTypeWillChange] - public function &offsetGet($offset) + public function &offsetGet(mixed $offset): mixed { return $this->get($offset); } @@ -636,7 +743,7 @@ public function &offsetGet($offset) * @param mixed $value The value to set. * @return void */ - public function offsetSet($offset, $value): void + public function offsetSet(mixed $offset, mixed $value): void { $this->set($offset, $value); } @@ -647,7 +754,7 @@ public function offsetSet($offset, $value): void * @param string $offset The offset to remove. * @return void */ - public function offsetUnset($offset): void + public function offsetUnset(mixed $offset): void { $this->unset($offset); } @@ -678,7 +785,7 @@ protected static function _accessor(string $property, string $type): string foreach (get_class_methods($class) as $method) { $prefix = substr($method, 1, 3); - if ($method[0] !== '_' || ($prefix !== 'get' && $prefix !== 'set')) { + if (!str_starts_with($method, '_') || ($prefix !== 'get' && $prefix !== 'set')) { continue; } $field = lcfirst(substr($method, 4)); @@ -702,14 +809,14 @@ protected static function _accessor(string $property, string $type): string * * @param array $fields list of fields to be returned * @param bool $onlyDirty Return the requested field only if it is dirty - * @return array + * @return array */ public function extract(array $fields, bool $onlyDirty = false): array { $result = []; foreach ($fields as $field) { if (!$onlyDirty || $this->isDirty($field)) { - $result[$field] = $this->get($field); + $result[$field] = $this->has($field) ? $this->get($field) : null; } } @@ -718,19 +825,23 @@ public function extract(array $fields, bool $onlyDirty = false): array /** * Returns an array with the requested original fields - * stored in this entity, indexed by field name. + * stored in this entity, indexed by field name, if they exist. * * Fields that are unchanged from their original value will be included in the * return of this method. * * @param array $fields List of fields to be returned - * @return array + * @return array */ public function extractOriginal(array $fields): array { $result = []; foreach ($fields as $field) { - $result[$field] = $this->getOriginal($field); + if ($this->hasOriginal($field)) { + $result[$field] = $this->getOriginal($field); + } elseif ($this->isOriginalField($field)) { + $result[$field] = $this->get($field); + } } return $result; @@ -738,18 +849,22 @@ public function extractOriginal(array $fields): array /** * Returns an array with only the original fields - * stored in this entity, indexed by field name. + * stored in this entity, indexed by field name, if they exist. * * This method will only return fields that have been modified since * the entity was built. Unchanged fields will be omitted. * * @param array $fields List of fields to be returned - * @return array + * @return array */ public function extractOriginalChanged(array $fields): array { $result = []; foreach ($fields as $field) { + if (!$this->hasOriginal($field)) { + continue; + } + $original = $this->getOriginal($field); if ($original !== $this->get($field)) { $result[$field] = $original; @@ -759,6 +874,54 @@ public function extractOriginalChanged(array $fields): array return $result; } + /** + * Returns whether a field is an original one + * + * @return bool + */ + public function isOriginalField(string $name): bool + { + return in_array($name, $this->_originalFields, true); + } + + /** + * Returns an array of original fields. + * Original fields are those that the entity was initialized with. + * + * @return array + */ + public function getOriginalFields(): array + { + return $this->_originalFields; + } + + /** + * Sets the given field or a list of fields to as original. + * Normally there is no need to call this method manually. + * + * @param array|string $field the name of a field or a list of fields to set as original + * @param bool $merge + * @return $this + */ + protected function setOriginalField(string|array $field, bool $merge = true) + { + if (!$merge) { + $this->_originalFields = (array)$field; + + return $this; + } + + $fields = (array)$field; + foreach ($fields as $field) { + $field = (string)$field; + if (!$this->isOriginalField($field)) { + $this->_originalFields[] = $field; + } + } + + return $this; + } + /** * Sets the dirty status of a single field. * @@ -770,7 +933,9 @@ public function extractOriginalChanged(array $fields): array public function setDirty(string $field, bool $isDirty = true) { if ($isDirty === false) { - unset($this->_dirty[$field]); + $this->setOriginalField($field); + + unset($this->_dirty[$field], $this->_original[$field]); return $this; } @@ -789,11 +954,9 @@ public function setDirty(string $field, bool $isDirty = true) */ public function isDirty(?string $field = null): bool { - if ($field === null) { - return !empty($this->_dirty); - } - - return isset($this->_dirty[$field]); + return $field === null + ? $this->_dirty !== [] + : isset($this->_dirty[$field]); } /** @@ -819,6 +982,7 @@ public function clean(): void $this->_errors = []; $this->_invalid = []; $this->_original = []; + $this->setOriginalField(array_keys($this->_fields), false); } /** @@ -850,12 +1014,6 @@ public function setNew(bool $new) */ public function isNew(): bool { - if (func_num_args()) { - deprecationWarning('Using isNew() as setter is deprecated. Use setNew() instead.'); - - $this->setNew(func_get_arg(0)); - } - return $this->_new; } @@ -934,12 +1092,7 @@ public function getErrors(): array */ public function getError(string $field): array { - $errors = $this->_errors[$field] ?? []; - if ($errors) { - return $errors; - } - - return $this->_nestedErrors($field); + return $this->_errors[$field] ?? $this->_nestedErrors($field); } /** @@ -999,7 +1152,7 @@ public function setErrors(array $errors, bool $overwrite = false) * @param bool $overwrite Whether to overwrite pre-existing errors for $field * @return $this */ - public function setError(string $field, $errors, bool $overwrite = false) + public function setError(string $field, array|string $errors, bool $overwrite = false) { if (is_string($errors)) { $errors = [$errors]; @@ -1012,13 +1165,22 @@ public function setError(string $field, $errors, bool $overwrite = false) * Auxiliary method for getting errors in nested entities * * @param string $field the field in this entity to check for errors - * @return array errors in nested entity if any + * @return array Errors in nested entity if any */ protected function _nestedErrors(string $field): array { // Only one path element, check for nested entity with error. - if (strpos($field, '.') === false) { - return $this->_readError($this->get($field)); + if (!str_contains($field, '.')) { + if (!$this->has($field)) { + return []; + } + + $entity = $this->get($field); + if ($entity instanceof EntityInterface || is_iterable($entity)) { + return $this->_readError($entity); + } + + return []; } // Try reading the errors data with field as a simple path $error = Hash::get($this->_errors, $field); @@ -1032,18 +1194,20 @@ protected function _nestedErrors(string $field): array $entity = $this; $len = count($path); while ($len) { + /** @var string $part */ $part = array_shift($path); $len = count($path); $val = null; if ($entity instanceof EntityInterface) { - $val = $entity->get($part); + if ($entity->has($part)) { + $val = $entity->get($part); + } } elseif (is_array($entity)) { $val = $entity[$part] ?? false; } if ( - is_array($val) || - $val instanceof Traversable || + is_iterable($val) || $val instanceof EntityInterface ) { $entity = $val; @@ -1065,7 +1229,7 @@ protected function _nestedErrors(string $field): array * @param \Cake\Datasource\EntityInterface|array $object The object to read errors from. * @return bool */ - protected function _readHasErrors($object): bool + protected function _readHasErrors(mixed $object): bool { if ($object instanceof EntityInterface && $object->hasErrors()) { return true; @@ -1089,7 +1253,7 @@ protected function _readHasErrors($object): bool * @param string|null $path The field name for errors. * @return array */ - protected function _readError($object, $path = null): array + protected function _readError(EntityInterface|iterable $object, ?string $path = null): array { if ($path !== null && $object instanceof EntityInterface) { return $object->getError($path); @@ -1097,19 +1261,14 @@ protected function _readError($object, $path = null): array if ($object instanceof EntityInterface) { return $object->getErrors(); } - if (is_iterable($object)) { - $array = array_map(function ($val) { - if ($val instanceof EntityInterface) { - return $val->getErrors(); - } - return null; - }, (array)$object); - - return array_filter($array); - } + $array = array_map(function ($val) { + if ($val instanceof EntityInterface) { + return $val->getErrors(); + } + }, (array)$object); - return []; + return array_filter($array); } /** @@ -1128,7 +1287,7 @@ public function getInvalid(): array * @param string $field The name of the field. * @return mixed|null */ - public function getInvalidField(string $field) + public function getInvalidField(string $field): mixed { return $this->_invalid[$field] ?? null; } @@ -1147,7 +1306,7 @@ public function getInvalidField(string $field) public function setInvalid(array $fields, bool $overwrite = false) { foreach ($fields as $field => $value) { - if ($overwrite === true) { + if ($overwrite) { $this->_invalid[$field] = $value; continue; } @@ -1164,7 +1323,7 @@ public function setInvalid(array $fields, bool $overwrite = false) * @param mixed $value The invalid value to be set for $field. * @return $this */ - public function setInvalidField(string $field, $value) + public function setInvalidField(string $field, mixed $value) { $this->_invalid[$field] = $value; @@ -1195,12 +1354,10 @@ public function setInvalidField(string $field, $value) * mark it as protected. * @return $this */ - public function setAccess($field, bool $set) + public function setAccess(array|string $field, bool $set) { if ($field === '*') { - $this->_accessible = array_map(function ($p) use ($set) { - return $set; - }, $this->_accessible); + $this->_accessible = array_map(fn($p) => $set, $this->_accessible); $this->_accessible['*'] = $set; return $this; @@ -1270,9 +1427,16 @@ public function setSource(string $alias) * Returns a string representation of this object in a human readable format. * * @return string + * @deprecated 5.2.0 Casting an entity to string is deprecated. Use json_encode() instead to get a string representation of the entity. */ public function __toString(): string { + deprecationWarning( + '5.2.0', + 'Casting an entity to string is deprecated. ' . + 'Use json_encode() instead to get a string representation of the entity.', + ); + return (string)json_encode($this, JSON_PRETTY_PRINT); } @@ -1294,6 +1458,7 @@ public function __debugInfo(): array '[accessible]' => $this->_accessible, '[dirty]' => $this->_dirty, '[original]' => $this->_original, + '[originalFields]' => $this->_originalFields, '[virtual]' => $this->_virtual, '[hasErrors]' => $this->hasErrors(), '[errors]' => $this->_errors, diff --git a/app/vendor/cakephp/cakephp/src/Datasource/Exception/MissingDatasourceConfigException.php b/app/vendor/cakephp/cakephp/src/Datasource/Exception/MissingDatasourceConfigException.php index aa9a7111d..5d5a03087 100644 --- a/app/vendor/cakephp/cakephp/src/Datasource/Exception/MissingDatasourceConfigException.php +++ b/app/vendor/cakephp/cakephp/src/Datasource/Exception/MissingDatasourceConfigException.php @@ -24,5 +24,5 @@ class MissingDatasourceConfigException extends CakeException /** * @var string */ - protected $_messageTemplate = 'The datasource configuration "%s" was not found.'; + protected string $_messageTemplate = 'The datasource configuration `%s` was not found.'; } diff --git a/app/vendor/cakephp/cakephp/src/Datasource/Exception/MissingDatasourceException.php b/app/vendor/cakephp/cakephp/src/Datasource/Exception/MissingDatasourceException.php index decb529ed..b23dea13a 100644 --- a/app/vendor/cakephp/cakephp/src/Datasource/Exception/MissingDatasourceException.php +++ b/app/vendor/cakephp/cakephp/src/Datasource/Exception/MissingDatasourceException.php @@ -24,5 +24,5 @@ class MissingDatasourceException extends CakeException /** * @var string */ - protected $_messageTemplate = 'Datasource class %s could not be found. %s'; + protected string $_messageTemplate = 'Datasource class `%s` could not be found. %s'; } diff --git a/app/vendor/cakephp/cakephp/src/Datasource/Exception/MissingModelException.php b/app/vendor/cakephp/cakephp/src/Datasource/Exception/MissingModelException.php index 0e13432d1..25a0a6cf1 100644 --- a/app/vendor/cakephp/cakephp/src/Datasource/Exception/MissingModelException.php +++ b/app/vendor/cakephp/cakephp/src/Datasource/Exception/MissingModelException.php @@ -26,5 +26,5 @@ class MissingModelException extends CakeException /** * @var string */ - protected $_messageTemplate = 'Model class "%s" of type "%s" could not be found.'; + protected string $_messageTemplate = 'Model class `%s` of type `%s` could not be found.'; } diff --git a/app/vendor/cakephp/cakephp/src/Datasource/Exception/MissingPropertyException.php b/app/vendor/cakephp/cakephp/src/Datasource/Exception/MissingPropertyException.php new file mode 100644 index 000000000..1dee738ee --- /dev/null +++ b/app/vendor/cakephp/cakephp/src/Datasource/Exception/MissingPropertyException.php @@ -0,0 +1,30 @@ + + * @var array */ - protected static $_modelFactories = []; + protected static array $_modelFactories = []; /** - * Register a callable to generate repositories of a given type. + * Register a locator to return repositories of a given type. * * @param string $type The name of the repository type the factory function is for. - * @param \Cake\Datasource\Locator\LocatorInterface|callable $factory The factory function used to create instances. + * @param \Cake\Datasource\Locator\LocatorInterface $factory The factory function used to create instances. * @return void */ - public static function add(string $type, $factory): void + public static function add(string $type, LocatorInterface $factory): void { - if ($factory instanceof LocatorInterface) { - static::$_modelFactories[$type] = $factory; - - return; - } - - if (is_callable($factory)) { - deprecationWarning( - 'Using a callable as a locator has been deprecated.' - . ' Use an instance of Cake\Datasource\Locator\LocatorInterface instead.' - ); - - static::$_modelFactories[$type] = $factory; - - return; - } - - throw new InvalidArgumentException(sprintf( - '`$factory` must be an instance of Cake\Datasource\Locator\LocatorInterface or a callable.' - . ' Got type `%s` instead.', - getTypeName($factory) - )); + static::$_modelFactories[$type] = $factory; } /** @@ -83,21 +59,17 @@ public static function drop(string $type): void * * @param string $type The repository type to get the factory for. * @throws \InvalidArgumentException If the specified repository type has no factory. - * @return \Cake\Datasource\Locator\LocatorInterface|callable The factory for the repository type. + * @return \Cake\Datasource\Locator\LocatorInterface The factory for the repository type. */ - public static function get(string $type) + public static function get(string $type): LocatorInterface { - if (!isset(static::$_modelFactories['Table'])) { - static::$_modelFactories['Table'] = new TableLocator(); - } - - if (!isset(static::$_modelFactories[$type])) { - throw new InvalidArgumentException(sprintf( - 'Unknown repository type "%s". Make sure you register a type before trying to use it.', - $type - )); + if (isset(static::$_modelFactories[$type])) { + return static::$_modelFactories[$type]; } - return static::$_modelFactories[$type]; + throw new InvalidArgumentException(sprintf( + 'Unknown repository type `%s`. Make sure you register a type before trying to use it.', + $type, + )); } } diff --git a/app/vendor/cakephp/cakephp/src/Datasource/FixtureInterface.php b/app/vendor/cakephp/cakephp/src/Datasource/FixtureInterface.php index 64c28e4fc..9c0b95ed8 100644 --- a/app/vendor/cakephp/cakephp/src/Datasource/FixtureInterface.php +++ b/app/vendor/cakephp/cakephp/src/Datasource/FixtureInterface.php @@ -21,22 +21,6 @@ */ interface FixtureInterface { - /** - * Create the fixture schema/mapping/definition - * - * @param \Cake\Datasource\ConnectionInterface $connection An instance of the connection the fixture should be created on. - * @return bool True on success, false on failure. - */ - public function create(ConnectionInterface $connection): bool; - - /** - * Run after all tests executed, should remove the table/collection from the connection. - * - * @param \Cake\Datasource\ConnectionInterface $connection An instance of the connection the fixture should be removed from. - * @return bool True on success, false on failure. - */ - public function drop(ConnectionInterface $connection): bool; - /** * Run before each test is executed. * @@ -44,10 +28,9 @@ public function drop(ConnectionInterface $connection): bool; * * @param \Cake\Datasource\ConnectionInterface $connection An instance of the connection * into which the records will be inserted. - * @return \Cake\Database\StatementInterface|bool on success or if there are no records to insert, - * or false on failure. + * @return bool */ - public function insert(ConnectionInterface $connection); + public function insert(ConnectionInterface $connection): bool; /** * Truncates the current fixture. diff --git a/app/vendor/cakephp/cakephp/src/Datasource/InvalidPropertyInterface.php b/app/vendor/cakephp/cakephp/src/Datasource/InvalidPropertyInterface.php index 4411a0d46..b775010f8 100644 --- a/app/vendor/cakephp/cakephp/src/Datasource/InvalidPropertyInterface.php +++ b/app/vendor/cakephp/cakephp/src/Datasource/InvalidPropertyInterface.php @@ -48,7 +48,7 @@ public function setInvalid(array $fields, bool $overwrite = false); * @param string $field The name of the field. * @return mixed|null */ - public function getInvalidField(string $field); + public function getInvalidField(string $field): mixed; /** * Sets a field as invalid and not patchable into the entity. @@ -57,5 +57,5 @@ public function getInvalidField(string $field); * @param mixed $value The invalid value to be set for $field. * @return $this */ - public function setInvalidField(string $field, $value); + public function setInvalidField(string $field, mixed $value); } diff --git a/app/vendor/cakephp/cakephp/src/Datasource/Locator/AbstractLocator.php b/app/vendor/cakephp/cakephp/src/Datasource/Locator/AbstractLocator.php index 1d925c09d..ff576234c 100644 --- a/app/vendor/cakephp/cakephp/src/Datasource/Locator/AbstractLocator.php +++ b/app/vendor/cakephp/cakephp/src/Datasource/Locator/AbstractLocator.php @@ -16,8 +16,8 @@ */ namespace Cake\Datasource\Locator; +use Cake\Core\Exception\CakeException; use Cake\Datasource\RepositoryInterface; -use RuntimeException; /** * Provides an abstract registry/factory for repository objects. @@ -29,14 +29,14 @@ abstract class AbstractLocator implements LocatorInterface * * @var array */ - protected $instances = []; + protected array $instances = []; /** * Contains a list of options that were passed to get() method. * * @var array */ - protected $options = []; + protected array $options = []; /** * {@inheritDoc} @@ -44,19 +44,19 @@ abstract class AbstractLocator implements LocatorInterface * @param string $alias The alias name you want to get. * @param array $options The options you want to build the table with. * @return \Cake\Datasource\RepositoryInterface - * @throws \RuntimeException When trying to get alias for which instance + * @throws \Cake\Core\Exception\CakeException When trying to get alias for which instance * has already been created with different options. */ - public function get(string $alias, array $options = []) + public function get(string $alias, array $options = []): RepositoryInterface { $storeOptions = $options; unset($storeOptions['allowFallbackClass']); if (isset($this->instances[$alias])) { - if (!empty($storeOptions) && isset($this->options[$alias]) && $this->options[$alias] !== $storeOptions) { - throw new RuntimeException(sprintf( - 'You cannot configure "%s", it already exists in the registry.', - $alias + if ($storeOptions && isset($this->options[$alias]) && $this->options[$alias] !== $storeOptions) { + throw new CakeException(sprintf( + 'You cannot configure `%s`, it already exists in the registry.', + $alias, )); } @@ -75,12 +75,12 @@ public function get(string $alias, array $options = []) * @param array $options The options you want to build the instance with. * @return \Cake\Datasource\RepositoryInterface */ - abstract protected function createInstance(string $alias, array $options); + abstract protected function createInstance(string $alias, array $options): RepositoryInterface; /** * @inheritDoc */ - public function set(string $alias, RepositoryInterface $repository) + public function set(string $alias, RepositoryInterface $repository): RepositoryInterface { return $this->instances[$alias] = $repository; } @@ -100,7 +100,7 @@ public function remove(string $alias): void { unset( $this->instances[$alias], - $this->options[$alias] + $this->options[$alias], ); } diff --git a/app/vendor/cakephp/cakephp/src/Datasource/Locator/LocatorInterface.php b/app/vendor/cakephp/cakephp/src/Datasource/Locator/LocatorInterface.php index 256ae0a10..7a7a2b000 100644 --- a/app/vendor/cakephp/cakephp/src/Datasource/Locator/LocatorInterface.php +++ b/app/vendor/cakephp/cakephp/src/Datasource/Locator/LocatorInterface.php @@ -32,7 +32,7 @@ interface LocatorInterface * @throws \RuntimeException When trying to get alias for which instance * has already been created with different options. */ - public function get(string $alias, array $options = []); + public function get(string $alias, array $options = []): RepositoryInterface; /** * Set a repository instance. @@ -41,7 +41,7 @@ public function get(string $alias, array $options = []); * @param \Cake\Datasource\RepositoryInterface $repository The repository to set. * @return \Cake\Datasource\RepositoryInterface */ - public function set(string $alias, RepositoryInterface $repository); + public function set(string $alias, RepositoryInterface $repository): RepositoryInterface; /** * Check to see if an instance exists in the registry. diff --git a/app/vendor/cakephp/cakephp/src/Datasource/ModelAwareTrait.php b/app/vendor/cakephp/cakephp/src/Datasource/ModelAwareTrait.php index cfc71c86b..530337156 100644 --- a/app/vendor/cakephp/cakephp/src/Datasource/ModelAwareTrait.php +++ b/app/vendor/cakephp/cakephp/src/Datasource/ModelAwareTrait.php @@ -18,18 +18,15 @@ use Cake\Datasource\Exception\MissingModelException; use Cake\Datasource\Locator\LocatorInterface; -use InvalidArgumentException; use UnexpectedValueException; -use function Cake\Core\deprecationWarning; -use function Cake\Core\getTypeName; use function Cake\Core\pluginSplit; /** * Provides functionality for loading table classes * and other repositories onto properties of the host object. * - * Example users of this trait are Cake\Controller\Controller and - * Cake\Console\Shell. + * Example users of this trait are {@link \Cake\Controller\Controller} and + * {@link \Cake\Command\Command}. */ trait ModelAwareTrait { @@ -46,21 +43,21 @@ trait ModelAwareTrait * * @var string|null */ - protected $modelClass; + protected ?string $modelClass = null; /** * A list of overridden model factory functions. * * @var array */ - protected $_modelFactories = []; + protected array $_modelFactories = []; /** * The model type to use. * * @var string */ - protected $_modelType = 'Table'; + protected string $_modelType = 'Table'; /** * Set the modelClass property based on conventions. @@ -72,74 +69,7 @@ trait ModelAwareTrait */ protected function _setModelClass(string $name): void { - if ($this->modelClass === null) { - $this->modelClass = $name; - } - } - - /** - * Fetch or construct a model and set it to a property on this object. - * - * Uses a modelFactory based on `$modelType` to fetch and construct a `RepositoryInterface` - * and set it as a property on the current object. The default `modelType` - * can be defined with `setModelType()`. - * - * If a repository provider does not return an object a MissingModelException will - * be thrown. - * - * @param string|null $modelClass Name of model class to load. Defaults to $this->modelClass. - * The name can be an alias like `'Post'` or FQCN like `App\Model\Table\PostsTable::class`. - * @param string|null $modelType The type of repository to load. Defaults to the getModelType() value. - * @return \Cake\Datasource\RepositoryInterface The model instance created. - * @throws \Cake\Datasource\Exception\MissingModelException If the model class cannot be found. - * @throws \UnexpectedValueException If $modelClass argument is not provided - * and ModelAwareTrait::$modelClass property value is empty. - * @deprecated 4.3.0 Prefer `LocatorAwareTrait::fetchTable()` or `ModelAwareTrait::fetchModel()` instead. - */ - public function loadModel(?string $modelClass = null, ?string $modelType = null): RepositoryInterface - { - $modelClass = $modelClass ?? $this->modelClass; - if (empty($modelClass)) { - throw new UnexpectedValueException('Default modelClass is empty'); - } - $modelType = $modelType ?? $this->getModelType(); - - $options = []; - if (strpos($modelClass, '\\') === false) { - [, $alias] = pluginSplit($modelClass, true); - } else { - $options['className'] = $modelClass; - /** @psalm-suppress PossiblyFalseOperand */ - $alias = substr( - $modelClass, - strrpos($modelClass, '\\') + 1, - -strlen($modelType) - ); - $modelClass = $alias; - } - if (!property_exists($this, $alias)) { - deprecationWarning( - '4.5.0 - Dynamic properties will be removed in PHP 8.2. ' . - "Add `public \${$alias} = null;` to your class definition or use `#[AllowDynamicProperties]` attribute." - ); - } - - if (isset($this->{$alias})) { - return $this->{$alias}; - } - - $factory = $this->_modelFactories[$modelType] ?? FactoryLocator::get($modelType); - if ($factory instanceof LocatorInterface) { - $this->{$alias} = $factory->get($modelClass, $options); - } else { - $this->{$alias} = $factory($modelClass, $options); - } - - if (!$this->{$alias}) { - throw new MissingModelException([$modelClass, $modelType]); - } - - return $this->{$alias}; + $this->modelClass ??= $name; } /** @@ -163,22 +93,21 @@ public function loadModel(?string $modelClass = null, ?string $modelType = null) */ public function fetchModel(?string $modelClass = null, ?string $modelType = null): RepositoryInterface { - $modelClass = $modelClass ?? $this->modelClass; - if (empty($modelClass)) { + $modelClass ??= $this->modelClass; + if (!$modelClass) { throw new UnexpectedValueException('Default modelClass is empty'); } - $modelType = $modelType ?? $this->getModelType(); + $modelType ??= $this->getModelType(); $options = []; - if (strpos($modelClass, '\\') === false) { + if (!str_contains($modelClass, '\\')) { [, $alias] = pluginSplit($modelClass, true); } else { $options['className'] = $modelClass; - /** @psalm-suppress PossiblyFalseOperand */ $alias = substr( $modelClass, strrpos($modelClass, '\\') + 1, - -strlen($modelType) + -strlen($modelType), ); $modelClass = $alias; } @@ -203,16 +132,8 @@ public function fetchModel(?string $modelClass = null, ?string $modelType = null * @param \Cake\Datasource\Locator\LocatorInterface|callable $factory The factory function used to create instances. * @return void */ - public function modelFactory(string $type, $factory): void + public function modelFactory(string $type, LocatorInterface|callable $factory): void { - if (!$factory instanceof LocatorInterface && !is_callable($factory)) { - throw new InvalidArgumentException(sprintf( - '`$factory` must be an instance of Cake\Datasource\Locator\LocatorInterface or a callable.' - . ' Got type `%s` instead.', - getTypeName($factory) - )); - } - $this->_modelFactories[$type] = $factory; } diff --git a/app/vendor/cakephp/cakephp/src/Datasource/Paginator.php b/app/vendor/cakephp/cakephp/src/Datasource/Paginator.php deleted file mode 100644 index 926b2318f..000000000 --- a/app/vendor/cakephp/cakephp/src/Datasource/Paginator.php +++ /dev/null @@ -1,10 +0,0 @@ - */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'page' => 1, 'limit' => 20, 'maxLimit' => 100, 'allowedParameters' => ['limit', 'sort', 'page', 'direction'], 'sortableFields' => null, 'finder' => 'all', + 'scope' => null, ]; /** - * Paging params after pagination operation is done. + * Calculated paging params. * - * @var array + * @var array */ - protected $_pagingParams = []; + protected array $pagingParams = [ + 'limit' => null, + 'count' => null, + 'totalCount' => null, + 'perPage' => null, + 'pageCount' => null, + 'currentPage' => null, + 'requestedPage' => null, + 'start' => null, + 'end' => null, + 'hasPrevPage' => null, + 'hasNextPage' => null, + 'sort' => null, + 'sortDefault' => null, + 'direction' => null, + 'directionDefault' => null, + 'completeSort' => null, + 'alias' => null, + 'scope' => null, + ]; /** * Handles automatic pagination of model records. @@ -168,43 +192,62 @@ class NumericPaginator implements PaginatorInterface * /dashboard?articles[page]=1&tags[page]=2 * ``` * - * @param \Cake\Datasource\RepositoryInterface|\Cake\Datasource\QueryInterface $object The repository or query + * @param mixed $target The repository or query * to paginate. * @param array $params Request params * @param array $settings The settings/configuration used for pagination. - * @return \Cake\Datasource\ResultSetInterface Query results + * @return \Cake\Datasource\Paging\PaginatedInterface * @throws \Cake\Datasource\Paging\Exception\PageOutOfBoundsException */ - public function paginate(object $object, array $params = [], array $settings = []): ResultSetInterface - { + public function paginate( + mixed $target, + array $params = [], + array $settings = [], + ): PaginatedInterface { $query = null; - if ($object instanceof QueryInterface) { - $query = $object; - $object = $query->getRepository(); - if ($object === null) { + if ($target instanceof QueryInterface) { + $query = $target; + $target = $query->getRepository(); + if ($target === null) { throw new CakeException('No repository set for query.'); } } - $data = $this->extractData($object, $params, $settings); - $query = $this->getQuery($object, $query, $data); + assert( + $target instanceof RepositoryInterface, + 'Pagination target must be an instance of `' . QueryInterface::class + . '` or `' . RepositoryInterface::class . '`.', + ); - $cleanQuery = clone $query; - $results = $query->all(); - $data['numResults'] = count($results); - $data['count'] = $this->getCount($cleanQuery, $data); + $data = $this->extractData($target, $params, $settings); + $query = $this->getQuery($target, $query, $data); + + $countQuery = clone $query; + $items = $this->getItems($query, $data); + $this->pagingParams['count'] = count($items); + $this->pagingParams['totalCount'] = $this->getCount($countQuery, $data); $pagingParams = $this->buildParams($data); - $alias = $object->getAlias(); - $this->_pagingParams = [$alias => $pagingParams]; - if ($pagingParams['requestedPage'] > $pagingParams['page']) { + if ($pagingParams['requestedPage'] > $pagingParams['currentPage']) { throw new PageOutOfBoundsException([ 'requestedPage' => $pagingParams['requestedPage'], - 'pagingParams' => $this->_pagingParams, + 'pagingParams' => $pagingParams, ]); } - return $results; + return $this->buildPaginated($items, $pagingParams); + } + + /** + * Build paginated result set. + * + * @param \Cake\Datasource\ResultSetInterface $items + * @param array $pagingParams + * @return \Cake\Datasource\Paging\PaginatedInterface + */ + protected function buildPaginated(ResultSetInterface $items, array $pagingParams): PaginatedInterface + { + return new PaginatedResultSet($items, $pagingParams); } /** @@ -218,21 +261,41 @@ public function paginate(object $object, array $params = [], array $settings = [ protected function getQuery(RepositoryInterface $object, ?QueryInterface $query, array $data): QueryInterface { $options = $data['options']; - unset( - $options['scope'], - $options['sort'], - $options['direction'], + $queryOptions = array_intersect_key( + $options, + ['order' => null, 'page' => null, 'limit' => null], ); + $args = []; + $type = $options['finder'] ?? null; + if (is_array($type)) { + $args = (array)current($type); + $type = key($type); + } + if ($query === null) { - $query = $object->find($data['finder'], $options); - } else { - $query->applyOptions($options); + $query = $object->find($type ?? 'all', ...$args); + } elseif ($type !== null) { + $query->find($type, ...$args); } + $query->applyOptions($queryOptions); + return $query; } + /** + * Get paginated items. + * + * @param \Cake\Datasource\QueryInterface $query Query to fetch items. + * @param array $data Paging data. + * @return \Cake\Datasource\ResultSetInterface + */ + protected function getItems(QueryInterface $query, array $data): ResultSetInterface + { + return $query->all(); + } + /** * Get total count of records. * @@ -251,23 +314,21 @@ protected function getCount(QueryInterface $query, array $data): ?int * @param \Cake\Datasource\RepositoryInterface $object The repository object. * @param array $params Request params * @param array $settings The settings/configuration used for pagination. - * @return array Array with keys 'defaults', 'options' and 'finder' + * @return array */ protected function extractData(RepositoryInterface $object, array $params, array $settings): array { $alias = $object->getAlias(); $defaults = $this->getDefaults($alias, $settings); - $validSettings = array_merge( - array_keys($this->_defaultConfig), - ['whitelist', 'sortWhitelist', 'order', 'scope'] - ); + $validSettings = array_keys($this->_defaultConfig); + $validSettings[] = 'order'; $extraSettings = array_diff_key($defaults, array_flip($validSettings)); if ($extraSettings) { - deprecationWarning( - 'Passing query options as paginator settings is deprecated.' - . ' Use a custom finder through `finder` config instead.' - . ' Extra keys found are: ' . implode(',', array_keys($extraSettings)) + triggerWarning( + 'Passing query options as paginator settings is no longer supported.' + . ' Use a custom finder through the `finder` config or pass a SelectQuery instance to paginate().' + . ' Extra keys found are: `' . implode('`, `', array_keys($extraSettings)) . '`.', ); } @@ -275,217 +336,122 @@ protected function extractData(RepositoryInterface $object, array $params, array $options = $this->validateSort($object, $options); $options = $this->checkLimit($options); - $options += ['page' => 1, 'scope' => null]; - $options['page'] = (int)$options['page'] < 1 ? 1 : (int)$options['page']; - [$finder, $options] = $this->_extractFinder($options); + $options['page'] = max((int)$options['page'], 1); - return compact('defaults', 'options', 'finder'); + return compact('defaults', 'options', 'alias'); } /** * Build pagination params. * * @param array $data Paginator data containing keys 'options', - * 'count', 'defaults', 'finder', 'numResults'. + * 'defaults', 'alias'. * @return array Paging params. */ protected function buildParams(array $data): array { - $limit = $data['options']['limit']; - - $paging = [ - 'count' => $data['count'], - 'current' => $data['numResults'], - 'perPage' => $limit, - 'page' => $data['options']['page'], + $this->pagingParams = [ + 'perPage' => $data['options']['limit'], 'requestedPage' => $data['options']['page'], - ]; + 'alias' => $data['alias'], + 'scope' => $data['options']['scope'], + ] + $this->pagingParams; - $paging = $this->addPageCountParams($paging, $data); - $paging = $this->addStartEndParams($paging, $data); - $paging = $this->addPrevNextParams($paging, $data); - $paging = $this->addSortingParams($paging, $data); + $this->addPageCountParams($data); + $this->addStartEndParams($data); + $this->addPrevNextParams($data); + $this->addSortingParams($data); - $paging += [ - 'limit' => $data['defaults']['limit'] != $limit ? $limit : null, - 'scope' => $data['options']['scope'], - 'finder' => $data['finder'], - ]; + $this->pagingParams['limit'] = $data['defaults']['limit'] != $data['options']['limit'] + ? $data['options']['limit'] + : null; - return $paging; + return $this->pagingParams; } /** - * Add "page" and "pageCount" params. + * Add "currentPage" and "pageCount" params. * - * @param array $params Paging params. * @param array $data Paginator data. - * @return array Updated params. + * @return void */ - protected function addPageCountParams(array $params, array $data): array + protected function addPageCountParams(array $data): void { - $page = $params['page']; - $pageCount = 0; + $page = $data['options']['page']; + $pageCount = null; - if ($params['count'] !== null) { - $pageCount = max((int)ceil($params['count'] / $params['perPage']), 1); + if ($this->pagingParams['totalCount'] !== null) { + $pageCount = max((int)ceil($this->pagingParams['totalCount'] / $this->pagingParams['perPage']), 1); $page = min($page, $pageCount); - } elseif ($params['current'] === 0 && $params['requestedPage'] > 1) { + } elseif ($this->pagingParams['count'] === 0 && $this->pagingParams['requestedPage'] > 1) { $page = 1; } - $params['page'] = $page; - $params['pageCount'] = $pageCount; - - return $params; + $this->pagingParams['currentPage'] = $page; + $this->pagingParams['pageCount'] = $pageCount; } /** * Add "start" and "end" params. * - * @param array $params Paging params. * @param array $data Paginator data. - * @return array Updated params. + * @return void */ - protected function addStartEndParams(array $params, array $data): array + protected function addStartEndParams(array $data): void { - $start = $end = 0; - - if ($params['current'] > 0) { - $start = (($params['page'] - 1) * $params['perPage']) + 1; - $end = $start + $params['current'] - 1; + $start = 0; + $end = 0; + if ($this->pagingParams['count'] > 0) { + $start = (($this->pagingParams['currentPage'] - 1) * $this->pagingParams['perPage']) + 1; + $end = $start + $this->pagingParams['count'] - 1; } - $params['start'] = $start; - $params['end'] = $end; - - return $params; + $this->pagingParams['start'] = $start; + $this->pagingParams['end'] = $end; } /** * Add "prevPage" and "nextPage" params. * - * @param array $params Paginator params. * @param array $data Paging data. - * @return array Updated params. + * @return void */ - protected function addPrevNextParams(array $params, array $data): array + protected function addPrevNextParams(array $data): void { - $params['prevPage'] = $params['page'] > 1; - if ($params['count'] === null) { - $params['nextPage'] = true; + $this->pagingParams['hasPrevPage'] = $this->pagingParams['currentPage'] > 1; + if ($this->pagingParams['totalCount'] === null) { + $this->pagingParams['hasNextPage'] = true; } else { - $params['nextPage'] = $params['count'] > $params['page'] * $params['perPage']; + $this->pagingParams['hasNextPage'] = $this->pagingParams['totalCount'] + > $this->pagingParams['currentPage'] * $this->pagingParams['perPage']; } - - return $params; } /** * Add sorting / ordering params. * - * @param array $params Paginator params. * @param array $data Paging data. - * @return array Updated params. + * @return void */ - protected function addSortingParams(array $params, array $data): array + protected function addSortingParams(array $data): void { $defaults = $data['defaults']; $order = (array)$data['options']['order']; - $sortDefault = $directionDefault = false; + $sortDefault = false; + $directionDefault = false; if (!empty($defaults['order']) && count($defaults['order']) >= 1) { $sortDefault = key($defaults['order']); $directionDefault = current($defaults['order']); } - $params += [ + $this->pagingParams = [ 'sort' => $data['options']['sort'], 'direction' => isset($data['options']['sort']) && count($order) ? current($order) : null, 'sortDefault' => $sortDefault, 'directionDefault' => $directionDefault, 'completeSort' => $order, - ]; - - return $params; - } - - /** - * Extracts the finder name and options out of the provided pagination options. - * - * @param array $options the pagination options. - * @return array An array containing in the first position the finder name - * and in the second the options to be passed to it. - */ - protected function _extractFinder(array $options): array - { - $type = !empty($options['finder']) ? $options['finder'] : 'all'; - unset( - $options['finder'], - $options['maxLimit'], - $options['allowedParameters'], - $options['whitelist'], - $options['sortableFields'], - $options['sortWhitelist'], - ); - - if (is_array($type)) { - $options = (array)current($type) + $options; - $type = key($type); - } - - return [$type, $options]; - } - - /** - * Get paging params after pagination operation. - * - * @return array - */ - public function getPagingParams(): array - { - return $this->_pagingParams; - } - - /** - * Shim method for reading the deprecated whitelist or allowedParameters options - * - * @return array - */ - protected function getAllowedParameters(): array - { - $allowed = $this->getConfig('allowedParameters'); - if (!$allowed) { - $allowed = []; - } - $whitelist = $this->getConfig('whitelist'); - if ($whitelist) { - deprecationWarning('The `whitelist` option is deprecated. Use the `allowedParameters` option instead.'); - - return array_merge($allowed, $whitelist); - } - - return $allowed; - } - - /** - * Shim method for reading the deprecated sortWhitelist or sortableFields options. - * - * @param array $config The configuration data to coalesce and emit warnings on. - * @return array|null - */ - protected function getSortableFields(array $config): ?array - { - $allowed = $config['sortableFields'] ?? null; - if ($allowed !== null) { - return $allowed; - } - $deprecated = $config['sortWhitelist'] ?? null; - if ($deprecated !== null) { - deprecationWarning('The `sortWhitelist` option is deprecated. Use `sortableFields` instead.'); - } - - return $deprecated; + ] + $this->pagingParams; } /** @@ -504,15 +470,13 @@ protected function getSortableFields(array $config): ?array * @param array $settings The settings to merge with the request data. * @return array Array of merged options. */ - public function mergeOptions(array $params, array $settings): array + protected function mergeOptions(array $params, array $settings): array { if (!empty($settings['scope'])) { $scope = $settings['scope']; $params = !empty($params[$scope]) ? (array)$params[$scope] : []; } - - $allowed = $this->getAllowedParameters(); - $params = array_intersect_key($params, array_flip($allowed)); + $params = array_intersect_key($params, array_flip($this->getConfig('allowedParameters'))); return array_merge($settings, $params); } @@ -526,14 +490,13 @@ public function mergeOptions(array $params, array $settings): array * @return array An array of pagination settings for a model, * or the general settings. */ - public function getDefaults(string $alias, array $settings): array + protected function getDefaults(string $alias, array $settings): array { if (isset($settings[$alias])) { $settings = $settings[$alias]; } $defaults = $this->getConfig(); - $defaults['whitelist'] = $defaults['allowedParameters'] = $this->getAllowedParameters(); $maxLimit = $settings['maxLimit'] ?? $defaults['maxLimit']; $limit = $settings['limit'] ?? $defaults['limit']; @@ -574,7 +537,7 @@ public function getDefaults(string $alias, array $settings): array * @return array An array of options with sort + direction removed and * replaced with order if possible. */ - public function validateSort(RepositoryInterface $object, array $options): array + protected function validateSort(RepositoryInterface $object, array $options): array { if (isset($options['sort'])) { $direction = null; @@ -586,7 +549,7 @@ public function validateSort(RepositoryInterface $object, array $options): array } $order = isset($options['order']) && is_array($options['order']) ? $options['order'] : []; - if ($order && $options['sort'] && strpos($options['sort'], '.') === false) { + if ($order && $options['sort'] && !str_contains($options['sort'], '.')) { $order = $this->_removeAliases($order, $object->getAlias()); } @@ -604,12 +567,9 @@ public function validateSort(RepositoryInterface $object, array $options): array } $sortAllowed = false; - $allowed = $this->getSortableFields($options); - if ($allowed !== null) { - $options['sortableFields'] = $options['sortWhitelist'] = $allowed; - + if (isset($options['sortableFields'])) { $field = key($options['order']); - $sortAllowed = in_array($field, $allowed, true); + $sortAllowed = in_array($field, $options['sortableFields'], true); if (!$sortAllowed) { $options['order'] = []; $options['sort'] = null; @@ -645,11 +605,11 @@ protected function _removeAliases(array $fields, string $model): array if (is_int($field)) { throw new CakeException(sprintf( 'The `order` config must be an associative array. Found invalid value with numeric key: `%s`', - $sort + $sort, )); } - if (strpos($field, '.') === false) { + if (!str_contains($field, '.')) { $result[$field] = $sort; continue; } @@ -687,7 +647,7 @@ protected function _prefix(RepositoryInterface $object, array $order, bool $allo $field = $key; $alias = $tableAlias; - if (strpos($key, '.') !== false) { + if (str_contains($key, '.')) { [$alias, $field] = explode('.', $key); } $correctAlias = ($tableAlias === $alias); @@ -714,7 +674,7 @@ protected function _prefix(RepositoryInterface $object, array $order, bool $allo * @param array $options An array of options with a limit key to be checked. * @return array An array of options for pagination. */ - public function checkLimit(array $options): array + protected function checkLimit(array $options): array { $options['limit'] = (int)$options['limit']; if ($options['limit'] < 1) { @@ -725,10 +685,3 @@ public function checkLimit(array $options): array return $options; } } - -// phpcs:disable -class_alias( - 'Cake\Datasource\Paging\NumericPaginator', - 'Cake\Datasource\Paginator' -); -// phpcs:enable diff --git a/app/vendor/cakephp/cakephp/src/Datasource/Paging/PaginatedInterface.php b/app/vendor/cakephp/cakephp/src/Datasource/Paging/PaginatedInterface.php new file mode 100644 index 000000000..3f8ea9e18 --- /dev/null +++ b/app/vendor/cakephp/cakephp/src/Datasource/Paging/PaginatedInterface.php @@ -0,0 +1,93 @@ + + * @method array toArray() Get the paginated items as an array + */ +interface PaginatedInterface extends Countable, Traversable +{ + /** + * Get current page number. + * + * @return int + */ + public function currentPage(): int; + + /** + * Get items per page. + * + * @return int + */ + public function perPage(): int; + + /** + * Get Total items counts. + * + * @return int|null + */ + public function totalCount(): ?int; + + /** + * Get total page count. + * + * @return int|null + */ + public function pageCount(): ?int; + + /** + * Get whether there's a previous page. + * + * @return bool + */ + public function hasPrevPage(): bool; + + /** + * Get whether there's a next page. + * + * @return bool + */ + public function hasNextPage(): bool; + + /** + * Get paginated items. + * + * @return iterable + */ + public function items(): iterable; + + /** + * Get paging param. + * + * @param string $name + * @return mixed + */ + public function pagingParam(string $name): mixed; + + /** + * Get all paging params. + * + * @return array + */ + public function pagingParams(): array; +} diff --git a/app/vendor/cakephp/cakephp/src/Datasource/Paging/PaginatedResultSet.php b/app/vendor/cakephp/cakephp/src/Datasource/Paging/PaginatedResultSet.php new file mode 100644 index 000000000..bf1e8fd4b --- /dev/null +++ b/app/vendor/cakephp/cakephp/src/Datasource/Paging/PaginatedResultSet.php @@ -0,0 +1,192 @@ + + */ +class PaginatedResultSet implements IteratorAggregate, JsonSerializable, PaginatedInterface +{ + /** + * Resultset instance. + * + * @var \Traversable + */ + protected Traversable $results; + + /** + * Paging params. + * + * @var array + */ + protected array $params = []; + + /** + * Constructor + * + * @param \Traversable $results Resultset instance. + * @param array $params Paging params. + */ + public function __construct(Traversable $results, array $params) + { + $this->results = $results; + $this->params = $params; + } + + /** + * @inheritDoc + */ + public function count(): int + { + return $this->params['count']; + } + + /** + * Get the paginated items as an array. + * + * This will exhaust the iterator `items`. + * + * @return array + */ + public function toArray(): array + { + return $this->jsonSerialize(); + } + + /** + * Get paginated items. + * + * @return \Traversable The paginated items result set. + */ + public function items(): Traversable + { + return $this->results; + } + + /** + * Provide data which should be serialized to JSON. + * + * @return array + */ + public function jsonSerialize(): array + { + return iterator_to_array($this->items()); + } + + /** + * @inheritDoc + */ + public function totalCount(): ?int + { + return $this->params['totalCount']; + } + + /** + * @inheritDoc + */ + public function perPage(): int + { + return $this->params['perPage']; + } + + /** + * @inheritDoc + */ + public function pageCount(): ?int + { + return $this->params['pageCount']; + } + + /** + * @inheritDoc + */ + public function currentPage(): int + { + return $this->params['currentPage']; + } + + /** + * @inheritDoc + */ + public function hasPrevPage(): bool + { + return $this->params['hasPrevPage']; + } + + /** + * @inheritDoc + */ + public function hasNextPage(): bool + { + return $this->params['hasNextPage']; + } + + /** + * @inheritDoc + */ + public function pagingParam(string $name): mixed + { + return $this->params[$name] ?? null; + } + + /** + * @inheritDoc + */ + public function pagingParams(): array + { + return $this->params; + } + + /** + * @inheritDoc + */ + public function getIterator(): Traversable + { + return $this->results; + } + + /** + * Proxies method calls to internal result set instance. + * + * @param string $name Method name + * @param array $arguments Arguments + * @return mixed + */ + public function __call(string $name, array $arguments): mixed + { + deprecationWarning( + '5.1.0', + sprintf( + 'Calling `%s` methods, such as `%s()`, on PaginatedResultSet is deprecated. ' . + 'You must call `items()` first (for example, `items()->%s()`).', + $this->results::class, + $name, + $name, + ), + ); + + return $this->results->$name(...$arguments); + } +} diff --git a/app/vendor/cakephp/cakephp/src/Datasource/Paging/PaginatorInterface.php b/app/vendor/cakephp/cakephp/src/Datasource/Paging/PaginatorInterface.php index 4d30597b2..96e7678d7 100644 --- a/app/vendor/cakephp/cakephp/src/Datasource/Paging/PaginatorInterface.php +++ b/app/vendor/cakephp/cakephp/src/Datasource/Paging/PaginatorInterface.php @@ -9,42 +9,29 @@ * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice. * - * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) - * @link https://cakephp.org CakePHP(tm) Project - * @since 3.5.0 - * @license https://www.opensource.org/licenses/mit-license.php MIT License + * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://cakephp.org CakePHP(tm) Project + * @since 5.0.0 + * @license http://www.opensource.org/licenses/mit-license.php MIT License */ namespace Cake\Datasource\Paging; -use Cake\Datasource\ResultSetInterface; - /** * This interface describes the methods for paginator instance. */ interface PaginatorInterface { /** - * Handles pagination of datasource records. + * Handles pagination of data. * - * @param \Cake\Datasource\RepositoryInterface|\Cake\Datasource\QueryInterface $object The repository or query - * to paginate. - * @param array $params Request params + * @param mixed $target Anything that needs to be paginated. + * @param array $params Request params. * @param array $settings The settings/configuration used for pagination. - * @return \Cake\Datasource\ResultSetInterface Query results - */ - public function paginate(object $object, array $params = [], array $settings = []): ResultSetInterface; - - /** - * Get paging params after pagination operation. - * - * @return array + * @return \Cake\Datasource\Paging\PaginatedInterface */ - public function getPagingParams(): array; + public function paginate( + mixed $target, + array $params = [], + array $settings = [], + ): PaginatedInterface; } - -// phpcs:disable -class_alias( - 'Cake\Datasource\Paging\PaginatorInterface', - 'Cake\Datasource\PaginatorInterface' -); -// phpcs:enable diff --git a/app/vendor/cakephp/cakephp/src/Datasource/Paging/SimplePaginator.php b/app/vendor/cakephp/cakephp/src/Datasource/Paging/SimplePaginator.php index 7fd1b5380..746d62955 100644 --- a/app/vendor/cakephp/cakephp/src/Datasource/Paging/SimplePaginator.php +++ b/app/vendor/cakephp/cakephp/src/Datasource/Paging/SimplePaginator.php @@ -17,17 +17,68 @@ namespace Cake\Datasource\Paging; use Cake\Datasource\QueryInterface; +use Cake\Datasource\ResultSetInterface; /** - * Simplified paginator which avoids potentially expensives queries + * Simplified paginator which avoids potentially expensive queries * to get the total count of records. * * When using a simple paginator you will not be able to generate page numbers. - * Instead use only the prev/next pagination controls, and handle 404 errors - * when pagination goes past the available result set. + * Instead use only the prev/next pagination controls. */ class SimplePaginator extends NumericPaginator { + /** + * Get paginated items. + * + * Get one additional record than the limit. This helps deduce if next page exits. + * + * @param \Cake\Datasource\QueryInterface $query Query to fetch items. + * @param array $data Paging data. + * @return \Cake\Datasource\ResultSetInterface + */ + protected function getItems(QueryInterface $query, array $data): ResultSetInterface + { + return $query->limit($data['options']['limit'] + 1)->all(); + } + + /** + * @inheritDoc + */ + protected function buildParams(array $data): array + { + $hasNextPage = false; + if ($this->pagingParams['count'] > $data['options']['limit']) { + $hasNextPage = true; + $this->pagingParams['count'] -= 1; + } + + parent::buildParams($data); + + $this->pagingParams['hasNextPage'] = $hasNextPage; + + return $this->pagingParams; + } + + /** + * Build paginated result set. + * + * Since the query fetches an extra record, drop the last record if records + * fetched exceeds the limit/per page. + * + * @param \Cake\Datasource\ResultSetInterface $items + * @param array $pagingParams + * @return \Cake\Datasource\Paging\PaginatedInterface + */ + protected function buildPaginated(ResultSetInterface $items, array $pagingParams): PaginatedInterface + { + if (count($items) > $this->pagingParams['perPage']) { + $items = $items->take($this->pagingParams['perPage']); + } + + return new PaginatedResultSet($items, $pagingParams); + } + /** * Simple pagination does not perform any count query, so this method returns `null`. * @@ -40,10 +91,3 @@ protected function getCount(QueryInterface $query, array $data): ?int return null; } } - -// phpcs:disable -class_alias( - 'Cake\Datasource\Paging\SimplePaginator', - 'Cake\Datasource\SimplePaginator' -); -// phpcs:enable diff --git a/app/vendor/cakephp/cakephp/src/Datasource/QueryCacher.php b/app/vendor/cakephp/cakephp/src/Datasource/QueryCacher.php index c421f91b9..040ef3e86 100644 --- a/app/vendor/cakephp/cakephp/src/Datasource/QueryCacher.php +++ b/app/vendor/cakephp/cakephp/src/Datasource/QueryCacher.php @@ -17,9 +17,9 @@ namespace Cake\Datasource; use Cake\Cache\Cache; +use Cake\Core\Exception\CakeException; use Closure; use Psr\SimpleCache\CacheInterface; -use RuntimeException; use Traversable; /** @@ -37,32 +37,24 @@ class QueryCacher * * @var \Closure|string */ - protected $_key; + protected Closure|string $_key; /** * Config for cache engine. * * @var \Psr\SimpleCache\CacheInterface|string */ - protected $_config; + protected CacheInterface|string $_config; /** * Constructor. * * @param \Closure|string $key The key or function to generate a key. * @param \Psr\SimpleCache\CacheInterface|string $config The cache config name or cache engine instance. - * @throws \RuntimeException */ - public function __construct($key, $config) + public function __construct(Closure|string $key, CacheInterface|string $config) { - if (!is_string($key) && !($key instanceof Closure)) { - throw new RuntimeException('Cache keys must be strings or callables.'); - } $this->_key = $key; - - if (!is_string($config) && !($config instanceof CacheInterface)) { - throw new RuntimeException('Cache configs must be strings or \Psr\SimpleCache\CacheInterface instances.'); - } $this->_config = $config; } @@ -72,12 +64,12 @@ public function __construct($key, $config) * @param object $query The query the cache read is for. * @return mixed|null Either the cached results or null. */ - public function fetch(object $query) + public function fetch(object $query): mixed { $key = $this->_resolveKey($query); $storage = $this->_resolveCacher(); $result = $storage->get($key); - if (empty($result)) { + if (!$result) { return null; } @@ -104,7 +96,7 @@ public function store(object $query, Traversable $results): bool * * @param object $query The query to generate a key for. * @return string - * @throws \RuntimeException + * @throws \Cake\Core\Exception\CakeException */ protected function _resolveKey(object $query): string { @@ -115,7 +107,7 @@ protected function _resolveKey(object $query): string $key = $func($query); if (!is_string($key)) { $msg = sprintf('Cache key functions must return a string. Got %s.', var_export($key, true)); - throw new RuntimeException($msg); + throw new CakeException($msg); } return $key; @@ -126,7 +118,7 @@ protected function _resolveKey(object $query): string * * @return \Psr\SimpleCache\CacheInterface */ - protected function _resolveCacher() + protected function _resolveCacher(): CacheInterface { if (is_string($this->_config)) { return Cache::pool($this->_config); diff --git a/app/vendor/cakephp/cakephp/src/Datasource/QueryInterface.php b/app/vendor/cakephp/cakephp/src/Datasource/QueryInterface.php index 47fede090..f943adb5c 100644 --- a/app/vendor/cakephp/cakephp/src/Datasource/QueryInterface.php +++ b/app/vendor/cakephp/cakephp/src/Datasource/QueryInterface.php @@ -17,6 +17,8 @@ namespace Cake\Datasource; +use Closure; + /** * The basis for every query object * @@ -24,7 +26,6 @@ * provided list using the AND operator. {@see \Cake\Database\Query::andWhere()} * @method \Cake\Datasource\EntityInterface|array firstOrFail() Get the first result from the executing query or raise an exception. * {@see \Cake\Database\Query::firstOrFail()} - * @method $this setRepository(\Cake\Datasource\RepositoryInterface $repository) Set the default repository object that will be used by this query. */ interface QueryInterface { @@ -37,11 +38,11 @@ interface QueryInterface * If `true` is passed in the second argument, any previous selections will * be overwritten with the list passed in the first argument. * - * @param \Cake\Database\ExpressionInterface|\Cake\ORM\Association|\Cake\ORM\Table|callable|array|string $fields Fields. + * @param \Closure|array|string|float|int $fields Fields. * @param bool $overwrite whether to reset fields with passed list or not * @return $this */ - public function select($fields, bool $overwrite = false); + public function select(Closure|array|string|float|int $fields, bool $overwrite = false); /** * Returns a key => value array representing a single aliased field @@ -61,9 +62,9 @@ public function aliasField(string $field, ?string $alias = null): array; * Runs `aliasField()` for each field in the provided list and returns * the result under a single array. * - * @param array $fields The fields to alias + * @param array $fields The fields to alias * @param string|null $defaultAlias The default alias - * @return array + * @return array */ public function aliasFields(array $fields, ?string $defaultAlias = null): array; @@ -76,7 +77,8 @@ public function aliasFields(array $fields, ?string $defaultAlias = null): array; * ResultSetDecorator is a traversable object that implements the methods found * on Cake\Collection\Collection. * - * @return \Cake\Datasource\ResultSetInterface + * @template T of mixed + * @return \Cake\Datasource\ResultSetInterface */ public function all(): ResultSetInterface; @@ -134,10 +136,10 @@ public function applyOptions(array $options); * a single query. * * @param string $finder The finder method to use. - * @param array $options The options for the finder. + * @param mixed ...$args Arguments that match up to finder-specific parameters * @return static Returns a modified query. */ - public function find(string $finder, array $options = []); + public function find(string $finder, mixed ...$args): static; /** * Returns the first result out of executing this query, if the query has not been @@ -149,9 +151,9 @@ public function find(string $finder, array $options = []); * $singleUser = $query->select(['id', 'username'])->first(); * ``` * - * @return \Cake\Datasource\EntityInterface|array|null the first result from the ResultSet + * @return mixed the first result from the ResultSet */ - public function first(); + public function first(): mixed; /** * Returns the total amount of results for the query. @@ -173,10 +175,10 @@ public function count(): int; * $query->limit($query->newExpr()->add(['1 + 1'])); // LIMIT (1 + 1) * ``` * - * @param \Cake\Database\ExpressionInterface|int|null $limit number of records to be returned + * @param int|null $limit number of records to be returned * @return $this */ - public function limit($limit); + public function limit(?int $limit); /** * Sets the number of records that should be skipped from the original result set @@ -193,10 +195,62 @@ public function limit($limit); * $query->offset($query->newExpr()->add(['1 + 1'])); // OFFSET (1 + 1) * ``` * - * @param \Cake\Database\ExpressionInterface|int|null $offset number of records to be skipped + * @param int|null $offset number of records to be skipped + * @return $this + */ + public function offset(?int $offset); + + /** + * Adds a single or multiple fields to be used in the ORDER clause for this query. + * Fields can be passed as an array of strings, array of expression + * objects, a single expression or a single string. + * + * If an array is passed, keys will be used as the field itself and the value will + * represent the order in which such field should be ordered. When called multiple + * times with the same fields as key, the last order definition will prevail over + * the others. + * + * By default this function will append any passed argument to the list of fields + * to be selected, unless the second argument is set to true. + * + * ### Examples: + * + * ``` + * $query->orderBy(['title' => 'DESC', 'author_id' => 'ASC']); + * ``` + * + * Produces: + * + * `ORDER BY title DESC, author_id ASC` + * + * ``` + * $query + * ->orderBy(['title' => $query->newExpr('DESC NULLS FIRST')]) + * ->orderBy('author_id'); + * ``` + * + * Will generate: + * + * `ORDER BY title DESC NULLS FIRST, author_id` + * + * ``` + * $expression = $query->newExpr()->add(['id % 2 = 0']); + * $query->orderBy($expression)->orderBy(['title' => 'ASC']); + * ``` + * + * Will become: + * + * `ORDER BY (id %2 = 0), title ASC` + * + * If you need to set complex expressions as order conditions, you + * should use `orderByAsc()` or `orderByDesc()`. + * + * @param \Closure|array|string $fields fields to be added to the list + * @param bool $overwrite whether to reset order with field list or not * @return $this + * @deprecated 5.0.0 Use orderBy() instead now that CollectionInterface methods are no longer proxied. */ - public function offset($offset); + public function order(Closure|array|string $fields, bool $overwrite = false); /** * Adds a single or multiple fields to be used in the ORDER clause for this query. @@ -214,7 +268,7 @@ public function offset($offset); * ### Examples: * * ``` - * $query->order(['title' => 'DESC', 'author_id' => 'ASC']); + * $query->orderBy(['title' => 'DESC', 'author_id' => 'ASC']); * ``` * * Produces: @@ -223,8 +277,8 @@ public function offset($offset); * * ``` * $query - * ->order(['title' => $query->newExpr('DESC NULLS FIRST')]) - * ->order('author_id'); + * ->orderBy(['title' => $query->newExpr('DESC NULLS FIRST')]) + * ->orderBy('author_id'); * ``` * * Will generate: @@ -233,7 +287,7 @@ public function offset($offset); * * ``` * $expression = $query->newExpr()->add(['id % 2 = 0']); - * $query->order($expression)->order(['title' => 'ASC']); + * $query->orderBy($expression)->orderBy(['title' => 'ASC']); * ``` * * Will become: @@ -241,13 +295,13 @@ public function offset($offset); * `ORDER BY (id %2 = 0), title ASC` * * If you need to set complex expressions as order conditions, you - * should use `orderAsc()` or `orderDesc()`. + * should use `orderByAsc()` or `orderByDesc()`. * - * @param \Cake\Database\ExpressionInterface|\Closure|array|string $fields fields to be added to the list + * @param \Closure|array|string $fields fields to be added to the list * @param bool $overwrite whether to reset order with field list or not * @return $this */ - public function order($fields, $overwrite = false); + public function orderBy(Closure|array|string $fields, bool $overwrite = false); /** * Set the page of results you want. @@ -279,13 +333,12 @@ public function toArray(): array; * * @param \Cake\Datasource\RepositoryInterface $repository The default repository object to use * @return $this - * @deprecated */ - public function repository(RepositoryInterface $repository); + public function setRepository(RepositoryInterface $repository); /** * Returns the default repository object that will be used by this query, - * that is, the repository that will appear in the from clause. + * that is, the repository that will appear in the "from" clause. * * @return \Cake\Datasource\RepositoryInterface|null $repository The default repository object to use */ @@ -303,7 +356,7 @@ public function getRepository(): ?RepositoryInterface; * conditions specified using the AND operator. Additionally, values can be * expressed using expression objects which can include other query objects. * - * Any conditions created with this methods can be used with any SELECT, UPDATE + * Any conditions created with this method can be used with any SELECT, UPDATE * and DELETE type of queries. * * ### Conditions using operators: @@ -363,10 +416,10 @@ public function getRepository(): ?RepositoryInterface; * * ### Adding conditions in multiple steps: * - * You can use callable functions to construct complex expressions, functions + * You can use callback to construct complex expressions, functions * receive as first argument a new QueryExpression object and this query instance - * as second argument. Functions must return an expression object, that will be - * added the list of conditions for the query using the AND operator. + * as second argument. Functions must return an expression object that will be + * added to the list of conditions for the query using the AND operator. * * ``` * $query @@ -395,7 +448,7 @@ public function getRepository(): ?RepositoryInterface; * Please note that when using the array notation or the expression objects, all * values will be correctly quoted and transformed to the correspondent database * data type automatically for you, thus securing your application from SQL injections. - * If you use string conditions make sure that your values are correctly quoted. + * If you use string conditions, make sure that your values are correctly quoted. * The safest thing you can do is to never use string conditions. * * @param \Closure|array|string|null $conditions The conditions to filter on. @@ -403,5 +456,5 @@ public function getRepository(): ?RepositoryInterface; * @param bool $overwrite whether to reset conditions with passed list or not * @return $this */ - public function where($conditions = null, array $types = [], bool $overwrite = false); + public function where(Closure|array|string|null $conditions = null, array $types = [], bool $overwrite = false); } diff --git a/app/vendor/cakephp/cakephp/src/Datasource/QueryTrait.php b/app/vendor/cakephp/cakephp/src/Datasource/QueryTrait.php deleted file mode 100644 index 15525612e..000000000 --- a/app/vendor/cakephp/cakephp/src/Datasource/QueryTrait.php +++ /dev/null @@ -1,1465 +0,0 @@ - - */ - protected $_formatters = []; - - /** - * A query cacher instance if this query has caching enabled. - * - * @var \Cake\Datasource\QueryCacher|null - */ - protected $_cache; - - /** - * Holds any custom options passed using applyOptions that could not be processed - * by any method in this class. - * - * @var array - */ - protected $_options = []; - - /** - * Whether the query is standalone or the product of an eager load operation. - * - * @var bool - */ - protected $_eagerLoaded = false; - - /** - * Set the default Table object that will be used by this query - * and form the `FROM` clause. - * - * @param \Cake\Datasource\RepositoryInterface|\Cake\ORM\Table $repository The default table object to use - * @return $this - * @deprecated 4.5.0 Use `setRepository()` instead. - */ - public function repository(RepositoryInterface $repository) - { - deprecationWarning('`repository() method is deprecated. Use `setRepository()` instead.'); - - return $this->setRepository($repository); - } - - /** - * Set the default Table object that will be used by this query - * and form the `FROM` clause. - * - * @param \Cake\Datasource\RepositoryInterface|\Cake\ORM\Table $repository The default table object to use - * @return $this - */ - public function setRepository(RepositoryInterface $repository) - { - $this->_repository = $repository; - - return $this; - } - - /** - * Returns the default table object that will be used by this query, - * that is, the table that will appear in the from clause. - * - * @return \Cake\Datasource\RepositoryInterface - */ - public function getRepository(): RepositoryInterface - { - return $this->_repository; - } - - /** - * Set the result set for a query. - * - * Setting the resultset of a query will make execute() a no-op. Instead - * of executing the SQL query and fetching results, the ResultSet provided to this - * method will be returned. - * - * This method is most useful when combined with results stored in a persistent cache. - * - * @param iterable $results The results this query should return. - * @return $this - */ - public function setResult(iterable $results) - { - $this->_results = $results; - - return $this; - } - - /** - * Executes this query and returns a results iterator. This function is required - * for implementing the IteratorAggregate interface and allows the query to be - * iterated without having to call execute() manually, thus making it look like - * a result set instead of the query itself. - * - * @return \Cake\Datasource\ResultSetInterface - * @psalm-suppress ImplementedReturnTypeMismatch - */ - #[\ReturnTypeWillChange] - public function getIterator() - { - return $this->all(); - } - - /** - * Enable result caching for this query. - * - * If a query has caching enabled, it will do the following when executed: - * - * - Check the cache for $key. If there are results no SQL will be executed. - * Instead the cached results will be returned. - * - When the cached data is stale/missing the result set will be cached as the query - * is executed. - * - * ### Usage - * - * ``` - * // Simple string key + config - * $query->cache('my_key', 'db_results'); - * - * // Function to generate key. - * $query->cache(function ($q) { - * $key = serialize($q->clause('select')); - * $key .= serialize($q->clause('where')); - * return md5($key); - * }); - * - * // Using a pre-built cache engine. - * $query->cache('my_key', $engine); - * - * // Disable caching - * $query->cache(false); - * ``` - * - * @param \Closure|string|false $key Either the cache key or a function to generate the cache key. - * When using a function, this query instance will be supplied as an argument. - * @param \Psr\SimpleCache\CacheInterface|string $config Either the name of the cache config to use, or - * a cache engine instance. - * @return $this - */ - public function cache($key, $config = 'default') - { - if ($key === false) { - $this->_cache = null; - - return $this; - } - $this->_cache = new QueryCacher($key, $config); - - return $this; - } - - /** - * Returns the current configured query `_eagerLoaded` value - * - * @return bool - */ - public function isEagerLoaded(): bool - { - return $this->_eagerLoaded; - } - - /** - * Sets the query instance to be an eager loaded query. If no argument is - * passed, the current configured query `_eagerLoaded` value is returned. - * - * @param bool $value Whether to eager load. - * @return $this - */ - public function eagerLoaded(bool $value) - { - $this->_eagerLoaded = $value; - - return $this; - } - - /** - * Returns a key => value array representing a single aliased field - * that can be passed directly to the select() method. - * The key will contain the alias and the value the actual field name. - * - * If the field is already aliased, then it will not be changed. - * If no $alias is passed, the default table for this query will be used. - * - * @param string $field The field to alias - * @param string|null $alias the alias used to prefix the field - * @return array - */ - public function aliasField(string $field, ?string $alias = null): array - { - if (strpos($field, '.') === false) { - $alias = $alias ?: $this->getRepository()->getAlias(); - $aliasedField = $alias . '.' . $field; - } else { - $aliasedField = $field; - [$alias, $field] = explode('.', $field); - } - - $key = sprintf('%s__%s', $alias, $field); - - return [$key => $aliasedField]; - } - - /** - * Runs `aliasField()` for each field in the provided list and returns - * the result under a single array. - * - * @param array $fields The fields to alias - * @param string|null $defaultAlias The default alias - * @return array - */ - public function aliasFields(array $fields, ?string $defaultAlias = null): array - { - $aliased = []; - foreach ($fields as $alias => $field) { - if (is_numeric($alias) && is_string($field)) { - $aliased += $this->aliasField($field, $defaultAlias); - continue; - } - $aliased[$alias] = $field; - } - - return $aliased; - } - - /** - * Fetch the results for this query. - * - * Will return either the results set through setResult(), or execute this query - * and return the ResultSetDecorator object ready for streaming of results. - * - * ResultSetDecorator is a traversable object that implements the methods found - * on Cake\Collection\Collection. - * - * @return \Cake\Datasource\ResultSetInterface - */ - public function all(): ResultSetInterface - { - if ($this->_results !== null) { - return $this->_results; - } - - $results = null; - if ($this->_cache) { - $results = $this->_cache->fetch($this); - } - if ($results === null) { - $results = $this->_decorateResults($this->_execute()); - if ($this->_cache) { - $this->_cache->store($this, $results); - } - } - $this->_results = $results; - - return $this->_results; - } - - /** - * Returns an array representation of the results after executing the query. - * - * @return array - */ - public function toArray(): array - { - return $this->all()->toArray(); - } - - /** - * Register a new MapReduce routine to be executed on top of the database results - * Both the mapper and caller callable should be invokable objects. - * - * The MapReduce routing will only be run when the query is executed and the first - * result is attempted to be fetched. - * - * If the third argument is set to true, it will erase previous map reducers - * and replace it with the arguments passed. - * - * @param callable|null $mapper The mapper callable. - * @param callable|null $reducer The reducing function. - * @param bool $overwrite Set to true to overwrite existing map + reduce functions. - * @return $this - * @see \Cake\Collection\Iterator\MapReduce for details on how to use emit data to the map reducer. - */ - public function mapReduce(?callable $mapper = null, ?callable $reducer = null, bool $overwrite = false) - { - if ($overwrite) { - $this->_mapReduce = []; - } - if ($mapper === null) { - if (!$overwrite) { - throw new InvalidArgumentException('$mapper can be null only when $overwrite is true.'); - } - - return $this; - } - $this->_mapReduce[] = compact('mapper', 'reducer'); - - return $this; - } - - /** - * Returns the list of previously registered map reduce routines. - * - * @return array - */ - public function getMapReducers(): array - { - return $this->_mapReduce; - } - - /** - * Registers a new formatter callback function that is to be executed when trying - * to fetch the results from the database. - * - * If the second argument is set to true, it will erase previous formatters - * and replace them with the passed first argument. - * - * Callbacks are required to return an iterator object, which will be used as - * the return value for this query's result. Formatter functions are applied - * after all the `MapReduce` routines for this query have been executed. - * - * Formatting callbacks will receive two arguments, the first one being an object - * implementing `\Cake\Collection\CollectionInterface`, that can be traversed and - * modified at will. The second one being the query instance on which the formatter - * callback is being applied. - * - * Usually the query instance received by the formatter callback is the same query - * instance on which the callback was attached to, except for in a joined - * association, in that case the callback will be invoked on the association source - * side query, and it will receive that query instance instead of the one on which - * the callback was originally attached to - see the examples below! - * - * ### Examples: - * - * Return all results from the table indexed by id: - * - * ``` - * $query->select(['id', 'name'])->formatResults(function ($results) { - * return $results->indexBy('id'); - * }); - * ``` - * - * Add a new column to the ResultSet: - * - * ``` - * $query->select(['name', 'birth_date'])->formatResults(function ($results) { - * return $results->map(function ($row) { - * $row['age'] = $row['birth_date']->diff(new DateTime)->y; - * - * return $row; - * }); - * }); - * ``` - * - * Add a new column to the results with respect to the query's hydration configuration: - * - * ``` - * $query->formatResults(function ($results, $query) { - * return $results->map(function ($row) use ($query) { - * $data = [ - * 'bar' => 'baz', - * ]; - * - * if ($query->isHydrationEnabled()) { - * $row['foo'] = new Foo($data) - * } else { - * $row['foo'] = $data; - * } - * - * return $row; - * }); - * }); - * ``` - * - * Retaining access to the association target query instance of joined associations, - * by inheriting the contain callback's query argument: - * - * ``` - * // Assuming a `Articles belongsTo Authors` association that uses the join strategy - * - * $articlesQuery->contain('Authors', function ($authorsQuery) { - * return $authorsQuery->formatResults(function ($results, $query) use ($authorsQuery) { - * // Here `$authorsQuery` will always be the instance - * // where the callback was attached to. - * - * // The instance passed to the callback in the second - * // argument (`$query`), will be the one where the - * // callback is actually being applied to, in this - * // example that would be `$articlesQuery`. - * - * // ... - * - * return $results; - * }); - * }); - * ``` - * - * @param callable|null $formatter The formatting callable. - * @param int|bool $mode Whether to overwrite, append or prepend the formatter. - * @return $this - * @throws \InvalidArgumentException - */ - public function formatResults(?callable $formatter = null, $mode = self::APPEND) - { - if ($mode === self::OVERWRITE) { - $this->_formatters = []; - } - if ($formatter === null) { - if ($mode !== self::OVERWRITE) { - throw new InvalidArgumentException('$formatter can be null only when $mode is overwrite.'); - } - - return $this; - } - - if ($mode === self::PREPEND) { - array_unshift($this->_formatters, $formatter); - - return $this; - } - - $this->_formatters[] = $formatter; - - return $this; - } - - /** - * Returns the list of previously registered format routines. - * - * @return array - */ - public function getResultFormatters(): array - { - return $this->_formatters; - } - - /** - * Returns the first result out of executing this query, if the query has not been - * executed before, it will set the limit clause to 1 for performance reasons. - * - * ### Example: - * - * ``` - * $singleUser = $query->select(['id', 'username'])->first(); - * ``` - * - * @return \Cake\Datasource\EntityInterface|array|null The first result from the ResultSet. - */ - public function first() - { - if ($this->_dirty) { - $this->limit(1); - } - - return $this->all()->first(); - } - - /** - * Get the first result from the executing query or raise an exception. - * - * @throws \Cake\Datasource\Exception\RecordNotFoundException When there is no first record. - * @return \Cake\Datasource\EntityInterface|array The first result from the ResultSet. - */ - public function firstOrFail() - { - $entity = $this->first(); - if (!$entity) { - $table = $this->getRepository(); - throw new RecordNotFoundException(sprintf( - 'Record not found in table "%s"', - $table->getTable() - )); - } - - return $entity; - } - - /** - * Returns an array with the custom options that were applied to this query - * and that were not already processed by another method in this class. - * - * ### Example: - * - * ``` - * $query->applyOptions(['doABarrelRoll' => true, 'fields' => ['id', 'name']); - * $query->getOptions(); // Returns ['doABarrelRoll' => true] - * ``` - * - * @see \Cake\Datasource\QueryInterface::applyOptions() to read about the options that will - * be processed by this class and not returned by this function - * @return array - * @see applyOptions() - */ - public function getOptions(): array - { - return $this->_options; - } - - /** - * Enables calling methods from the result set as if they were from this class - * - * @param string $method the method to call - * @param array $arguments list of arguments for the method to call - * @return mixed - * @throws \BadMethodCallException if no such method exists in result set - */ - public function __call(string $method, array $arguments) - { - $resultSetClass = $this->_decoratorClass(); - if (in_array($method, get_class_methods($resultSetClass), true)) { - deprecationWarning(sprintf( - 'Calling `%s` methods, such as `%s()`, on queries is deprecated. ' . - 'You must call `all()` first (for example, `all()->%s()`).', - ResultSetInterface::class, - $method, - $method, - ), 2); - $results = $this->all(); - - return $results->$method(...$arguments); - } - throw new BadMethodCallException( - sprintf('Unknown method "%s"', $method) - ); - } - - /** - * @param callable $callback The callback to apply - * @see \Cake\Collection\CollectionInterface::each() - * @return \Cake\Collection\CollectionInterface - * @deprecated - */ - public function each(callable $callback): CollectionInterface - { - deprecationWarning( - '4.3.0 - Calling each() on a Query is deprecated. ' . - 'Instead call `$query->all()->each(...)` instead.' - ); - - return $this->all()->each($callback); - } - - /** - * @param ?callable $callback The callback to apply - * @see \Cake\Collection\CollectionInterface::filter() - * @return \Cake\Collection\CollectionInterface - * @deprecated - */ - public function filter(?callable $callback = null): CollectionInterface - { - deprecationWarning( - '4.3.0 - Calling filter() on a Query is deprecated. ' . - 'Instead call `$query->all()->filter(...)` instead.' - ); - - return $this->all()->filter($callback); - } - - /** - * @param callable $callback The callback to apply - * @see \Cake\Collection\CollectionInterface::reject() - * @return \Cake\Collection\CollectionInterface - * @deprecated - */ - public function reject(callable $callback): CollectionInterface - { - deprecationWarning( - '4.3.0 - Calling reject() on a Query is deprecated. ' . - 'Instead call `$query->all()->reject(...)` instead.' - ); - - return $this->all()->reject($callback); - } - - /** - * @param callable $callback The callback to apply - * @see \Cake\Collection\CollectionInterface::every() - * @return bool - * @deprecated - */ - public function every(callable $callback): bool - { - deprecationWarning( - '4.3.0 - Calling every() on a Query is deprecated. ' . - 'Instead call `$query->all()->every(...)` instead.' - ); - - return $this->all()->every($callback); - } - - /** - * @param callable $callback The callback to apply - * @see \Cake\Collection\CollectionInterface::some() - * @return bool - * @deprecated - */ - public function some(callable $callback): bool - { - deprecationWarning( - '4.3.0 - Calling some() on a Query is deprecated. ' . - 'Instead call `$query->all()->some(...)` instead.' - ); - - return $this->all()->some($callback); - } - - /** - * @param mixed $value The value to check. - * @see \Cake\Collection\CollectionInterface::contains() - * @return bool - * @deprecated - */ - public function contains($value): bool - { - deprecationWarning( - '4.3.0 - Calling contains() on a Query is deprecated. ' . - 'Instead call `$query->all()->contains(...)` instead.' - ); - - return $this->all()->contains($value); - } - - /** - * @param callable $callback The callback to apply - * @see \Cake\Collection\CollectionInterface::map() - * @return \Cake\Collection\CollectionInterface - * @deprecated - */ - public function map(callable $callback): CollectionInterface - { - deprecationWarning( - '4.3.0 - Calling map() on a Query is deprecated. ' . - 'Instead call `$query->all()->map(...)` instead.' - ); - - return $this->all()->map($callback); - } - - /** - * @param callable $callback The callback to apply - * @param mixed $initial The initial value - * @see \Cake\Collection\CollectionInterface::reduce() - * @return mixed - * @deprecated - */ - public function reduce(callable $callback, $initial = null) - { - deprecationWarning( - '4.3.0 - Calling reduce() on a Query is deprecated. ' . - 'Instead call `$query->all()->reduce(...)` instead.' - ); - - return $this->all()->reduce($callback, $initial); - } - - /** - * @param callable|string $path The path to extract - * @see \Cake\Collection\CollectionInterface::extract() - * @return \Cake\Collection\CollectionInterface - * @deprecated - */ - public function extract($path): CollectionInterface - { - deprecationWarning( - '4.3.0 - Calling extract() on a Query is deprecated. ' . - 'Instead call `$query->all()->extract(...)` instead.' - ); - - return $this->all()->extract($path); - } - - /** - * @param callable|string $path The path to max - * @param int $sort The SORT_ constant to order by. - * @see \Cake\Collection\CollectionInterface::max() - * @return mixed - * @deprecated - */ - public function max($path, int $sort = \SORT_NUMERIC) - { - deprecationWarning( - '4.3.0 - Calling max() on a Query is deprecated. ' . - 'Instead call `$query->all()->max(...)` instead.' - ); - - return $this->all()->max($path, $sort); - } - - /** - * @param callable|string $path The path to max - * @param int $sort The SORT_ constant to order by. - * @see \Cake\Collection\CollectionInterface::min() - * @return mixed - * @deprecated - */ - public function min($path, int $sort = \SORT_NUMERIC) - { - deprecationWarning( - '4.3.0 - Calling min() on a Query is deprecated. ' . - 'Instead call `$query->all()->min(...)` instead.' - ); - - return $this->all()->min($path, $sort); - } - - /** - * @param callable|string|null $path the path to average - * @see \Cake\Collection\CollectionInterface::avg() - * @return float|int|null - * @deprecated - */ - public function avg($path = null) - { - deprecationwarning( - '4.3.0 - calling avg() on a query is deprecated. ' . - 'instead call `$query->all()->avg(...)` instead.' - ); - - return $this->all()->avg($path); - } - - /** - * @param callable|string|null $path the path to average - * @see \Cake\Collection\CollectionInterface::median() - * @return float|int|null - * @deprecated - */ - public function median($path = null) - { - deprecationwarning( - '4.3.0 - calling median() on a query is deprecated. ' . - 'instead call `$query->all()->median(...)` instead.' - ); - - return $this->all()->median($path); - } - - /** - * @param callable|string $path the path to average - * @param int $order The \SORT_ constant for the direction you want results in. - * @param int $sort The \SORT_ method to use. - * @see \Cake\Collection\CollectionInterface::sortBy() - * @return \Cake\Collection\CollectionInterface - * @deprecated - */ - public function sortBy($path, int $order = SORT_DESC, int $sort = \SORT_NUMERIC): CollectionInterface - { - deprecationwarning( - '4.3.0 - calling sortBy() on a query is deprecated. ' . - 'instead call `$query->all()->sortBy(...)` instead.' - ); - - return $this->all()->sortBy($path, $order, $sort); - } - - /** - * @param callable|string $path The path to group by - * @see \Cake\Collection\CollectionInterface::groupBy() - * @return \Cake\Collection\CollectionInterface - * @deprecated - */ - public function groupBy($path): CollectionInterface - { - deprecationWarning( - '4.3.0 - Calling groupBy() on a Query is deprecated. ' . - 'Instead call `$query->all()->groupBy(...)` instead.' - ); - - return $this->all()->groupBy($path); - } - - /** - * @param string|callable $path The path to extract - * @see \Cake\Collection\CollectionInterface::indexBy() - * @return \Cake\Collection\CollectionInterface - * @deprecated - */ - public function indexBy($path): CollectionInterface - { - deprecationWarning( - '4.3.0 - Calling indexBy() on a Query is deprecated. ' . - 'Instead call `$query->all()->indexBy(...)` instead.' - ); - - return $this->all()->indexBy($path); - } - - /** - * @param string|callable $path The path to count by - * @see \Cake\Collection\CollectionInterface::countBy() - * @return \Cake\Collection\CollectionInterface - * @deprecated - */ - public function countBy($path): CollectionInterface - { - deprecationWarning( - '4.3.0 - Calling countBy() on a Query is deprecated. ' . - 'Instead call `$query->all()->countBy(...)` instead.' - ); - - return $this->all()->countBy($path); - } - - /** - * @param string|callable $path The path to sum - * @see \Cake\Collection\CollectionInterface::sumOf() - * @return int|float - * @deprecated - */ - public function sumOf($path = null) - { - deprecationWarning( - '4.3.0 - Calling sumOf() on a Query is deprecated. ' . - 'Instead call `$query->all()->sumOf(...)` instead.' - ); - - return $this->all()->sumOf($path); - } - - /** - * @see \Cake\Collection\CollectionInterface::shuffle() - * @return \Cake\Collection\CollectionInterface - * @deprecated - */ - public function shuffle(): CollectionInterface - { - deprecationWarning( - '4.3.0 - Calling shuffle() on a Query is deprecated. ' . - 'Instead call `$query->all()->shuffle(...)` instead.' - ); - - return $this->all()->shuffle(); - } - - /** - * @param int $length The number of samples to select - * @see \Cake\Collection\CollectionInterface::sample() - * @return \Cake\Collection\CollectionInterface - * @deprecated - */ - public function sample(int $length = 10): CollectionInterface - { - deprecationWarning( - '4.3.0 - Calling sample() on a Query is deprecated. ' . - 'Instead call `$query->all()->sample(...)` instead.' - ); - - return $this->all()->sample($length); - } - - /** - * @param int $length The number of elements to take - * @param int $offset The offset of the first element to take. - * @see \Cake\Collection\CollectionInterface::take() - * @return \Cake\Collection\CollectionInterface - * @deprecated - */ - public function take(int $length = 1, int $offset = 0): CollectionInterface - { - deprecationWarning( - '4.3.0 - Calling take() on a Query is deprecated. ' . - 'Instead call `$query->all()->take(...)` instead.' - ); - - return $this->all()->take($length, $offset); - } - - /** - * @param int $length The number of items to take. - * @see \Cake\Collection\CollectionInterface::takeLast() - * @return \Cake\Collection\CollectionInterface - * @deprecated - */ - public function takeLast(int $length): CollectionInterface - { - deprecationWarning( - '4.3.0 - Calling takeLast() on a Query is deprecated. ' . - 'Instead call `$query->all()->takeLast(...)` instead.' - ); - - return $this->all()->takeLast($length); - } - - /** - * @param int $length The number of items to skip - * @see \Cake\Collection\CollectionInterface::skip() - * @return \Cake\Collection\CollectionInterface - * @deprecated - */ - public function skip(int $length): CollectionInterface - { - deprecationWarning( - '4.3.0 - Calling skip() on a Query is deprecated. ' . - 'Instead call `$query->all()->skip(...)` instead.' - ); - - return $this->all()->skip($length); - } - - /** - * @param array $conditions The conditions to use. - * @see \Cake\Collection\CollectionInterface::match() - * @return \Cake\Collection\CollectionInterface - * @deprecated - */ - public function match(array $conditions): CollectionInterface - { - deprecationWarning( - '4.3.0 - Calling match() on a Query is deprecated. ' . - 'Instead call `$query->all()->match(...)` instead.' - ); - - return $this->all()->match($conditions); - } - - /** - * @param array $conditions The conditions to apply - * @see \Cake\Collection\CollectionInterface::firstMatch() - * @return mixed - * @deprecated - */ - public function firstMatch(array $conditions) - { - deprecationWarning( - '4.3.0 - Calling firstMatch() on a Query is deprecated. ' . - 'Instead call `$query->all()->firstMatch(...)` instead.' - ); - - return $this->all()->firstMatch($conditions); - } - - /** - * @see \Cake\Collection\CollectionInterface::last() - * @deprecated - * @return mixed - */ - public function last() - { - deprecationWarning( - '4.3.0 - Calling last() on a Query is deprecated. ' . - 'Instead call `$query->all()->last(...)` instead.' - ); - - return $this->all()->last(); - } - - /** - * @param mixed $items The items to append - * @see \Cake\Collection\CollectionInterface::append() - * @return \Cake\Collection\CollectionInterface - * @deprecated - */ - public function append($items): CollectionInterface - { - deprecationWarning( - '4.3.0 - Calling append() on a Query is deprecated. ' . - 'Instead call `$query->all()->append(...)` instead.' - ); - - return $this->all()->append($items); - } - - /** - * @param mixed $item The item to apply - * @param mixed $key The key to append with - * @see \Cake\Collection\CollectionInterface::appendItem() - * @return \Cake\Collection\CollectionInterface - * @deprecated - */ - public function appendItem($item, $key = null): CollectionInterface - { - deprecationWarning( - '4.3.0 - Calling appendItem() on a Query is deprecated. ' . - 'Instead call `$query->all()->appendItem(...)` instead.' - ); - - return $this->all()->appendItem($item, $key); - } - - /** - * @param mixed $items The items to prepend. - * @see \Cake\Collection\CollectionInterface::prepend() - * @return \Cake\Collection\CollectionInterface - * @deprecated - */ - public function prepend($items): CollectionInterface - { - deprecationWarning( - '4.3.0 - Calling prepend() on a Query is deprecated. ' . - 'Instead call `$query->all()->prepend(...)` instead.' - ); - - return $this->all()->prepend($items); - } - - /** - * @param mixed $item The item to prepend - * @param mixed $key The key to use. - * @see \Cake\Collection\CollectionInterface::prependItem() - * @return \Cake\Collection\CollectionInterface - * @deprecated - */ - public function prependItem($item, $key = null): CollectionInterface - { - deprecationWarning( - '4.3.0 - Calling prependItem() on a Query is deprecated. ' . - 'Instead call `$query->all()->prependItem(...)` instead.' - ); - - return $this->all()->prependItem($item, $key); - } - - /** - * @param callable|string $keyPath The path for keys - * @param callable|string $valuePath The path for values - * @param callable|string|null $groupPath The path for grouping - * @see \Cake\Collection\CollectionInterface::combine() - * @return \Cake\Collection\CollectionInterface - * @deprecated - */ - public function combine($keyPath, $valuePath, $groupPath = null): CollectionInterface - { - deprecationWarning( - '4.3.0 - Calling combine() on a Query is deprecated. ' . - 'Instead call `$query->all()->combine(...)` instead.' - ); - - return $this->all()->combine($keyPath, $valuePath, $groupPath); - } - - /** - * @param callable|string $idPath The path to ids - * @param callable|string $parentPath The path to parents - * @param string $nestingKey Key used for nesting children. - * @see \Cake\Collection\CollectionInterface::nest() - * @return \Cake\Collection\CollectionInterface - * @deprecated - */ - public function nest($idPath, $parentPath, string $nestingKey = 'children'): CollectionInterface - { - deprecationWarning( - '4.3.0 - Calling nest() on a Query is deprecated. ' . - 'Instead call `$query->all()->nest(...)` instead.' - ); - - return $this->all()->nest($idPath, $parentPath, $nestingKey); - } - - /** - * @param string $path The path to insert on - * @param mixed $values The values to insert. - * @see \Cake\Collection\CollectionInterface::insert() - * @return \Cake\Collection\CollectionInterface - * @deprecated - */ - public function insert(string $path, $values): CollectionInterface - { - deprecationWarning( - '4.3.0 - Calling insert() on a Query is deprecated. ' . - 'Instead call `$query->all()->insert(...)` instead.' - ); - - return $this->all()->insert($path, $values); - } - - /** - * @see \Cake\Collection\CollectionInterface::toList() - * @return array - * @deprecated - */ - public function toList(): array - { - deprecationWarning( - '4.3.0 - Calling toList() on a Query is deprecated. ' . - 'Instead call `$query->all()->toList(...)` instead.' - ); - - return $this->all()->toList(); - } - - /** - * @param bool $keepKeys Whether or not keys should be kept - * @see \Cake\Collection\CollectionInterface::compile() - * @return \Cake\Collection\CollectionInterface - * @deprecated - */ - public function compile(bool $keepKeys = true): CollectionInterface - { - deprecationWarning( - '4.3.0 - Calling compile() on a Query is deprecated. ' . - 'Instead call `$query->all()->compile(...)` instead.' - ); - - return $this->all()->compile($keepKeys); - } - - /** - * @see \Cake\Collection\CollectionInterface::lazy() - * @return \Cake\Collection\CollectionInterface - * @deprecated - */ - public function lazy(): CollectionInterface - { - deprecationWarning( - '4.3.0 - Calling lazy() on a Query is deprecated. ' . - 'Instead call `$query->all()->lazy(...)` instead.' - ); - - return $this->all()->lazy(); - } - - /** - * @see \Cake\Collection\CollectionInterface::buffered() - * @return \Cake\Collection\CollectionInterface - * @deprecated - */ - public function buffered(): CollectionInterface - { - deprecationWarning( - '4.3.0 - Calling buffered() on a Query is deprecated. ' . - 'Instead call `$query->all()->buffered(...)` instead.' - ); - - return $this->all()->buffered(); - } - - /** - * @param string|int $order The order in which to return the elements - * @param callable|string $nestingKey The key name under which children are nested - * @see \Cake\Collection\CollectionInterface::listNested() - * @return \Cake\Collection\CollectionInterface - * @deprecated - */ - public function listNested($order = 'desc', $nestingKey = 'children'): CollectionInterface - { - deprecationWarning( - '4.3.0 - Calling listNested() on a Query is deprecated. ' . - 'Instead call `$query->all()->listNested(...)` instead.' - ); - - return $this->all()->listNested($order, $nestingKey); - } - - /** - * @param callable|array $condition the method that will receive each of the elements and - * returns true when the iteration should be stopped. - * @see \Cake\Collection\CollectionInterface::stopWhen() - * @return \Cake\Collection\CollectionInterface - * @deprecated - */ - public function stopWhen($condition): CollectionInterface - { - deprecationWarning( - '4.3.0 - Calling stopWhen() on a Query is deprecated. ' . - 'Instead call `$query->all()->stopWhen(...)` instead.' - ); - - return $this->all()->stopWhen($condition); - } - - /** - * @param callable|null $callback A callable function that will receive each of - * items in the collection. - * @see \Cake\Collection\CollectionInterface::unfold() - * @return \Cake\Collection\CollectionInterface - * @deprecated - */ - public function unfold(?callable $callback = null): CollectionInterface - { - deprecationWarning( - '4.3.0 - Calling unfold() on a Query is deprecated. ' . - 'Instead call `$query->all()->unfold(...)` instead.' - ); - - return $this->all()->unfold($callback); - } - - /** - * @param callable $callback A callable function that will receive each of - * items in the collection. - * @see \Cake\Collection\CollectionInterface::through() - * @return \Cake\Collection\CollectionInterface - * @deprecated - */ - public function through(callable $callback): CollectionInterface - { - deprecationWarning( - '4.3.0 - Calling through() on a Query is deprecated. ' . - 'Instead call `$query->all()->through(...)` instead.' - ); - - return $this->all()->through($callback); - } - - /** - * @param iterable ...$items The collections to zip. - * @see \Cake\Collection\CollectionInterface::zip() - * @return \Cake\Collection\CollectionInterface - * @deprecated - */ - public function zip(iterable $items): CollectionInterface - { - deprecationWarning( - '4.3.0 - Calling zip() on a Query is deprecated. ' . - 'Instead call `$query->all()->zip(...)` instead.' - ); - - return $this->all()->zip($items); - } - - /** - * @param iterable ...$items The collections to zip. - * @param callable $callback The function to use for zipping the elements together. - * @see \Cake\Collection\CollectionInterface::zipWith() - * @return \Cake\Collection\CollectionInterface - * @deprecated - */ - public function zipWith(iterable $items, $callback): CollectionInterface - { - deprecationWarning( - '4.3.0 - Calling zipWith() on a Query is deprecated. ' . - 'Instead call `$query->all()->zipWith(...)` instead.' - ); - - return $this->all()->zipWith($items, $callback); - } - - /** - * @param int $chunkSize The maximum size for each chunk - * @see \Cake\Collection\CollectionInterface::chunk() - * @return \Cake\Collection\CollectionInterface - * @deprecated - */ - public function chunk(int $chunkSize): CollectionInterface - { - deprecationWarning( - '4.3.0 - Calling chunk() on a Query is deprecated. ' . - 'Instead call `$query->all()->chunk(...)` instead.' - ); - - return $this->all()->chunk($chunkSize); - } - - /** - * @param int $chunkSize The maximum size for each chunk - * @param bool $keepKeys If the keys of the array should be kept - * @see \Cake\Collection\CollectionInterface::chunkWithKeys() - * @return \Cake\Collection\CollectionInterface - * @deprecated - */ - public function chunkWithKeys(int $chunkSize, bool $keepKeys = true): CollectionInterface - { - deprecationWarning( - '4.3.0 - Calling chunkWithKeys() on a Query is deprecated. ' . - 'Instead call `$query->all()->chunkWithKeys(...)` instead.' - ); - - return $this->all()->chunkWithKeys($chunkSize, $keepKeys); - } - - /** - * @see \Cake\Collection\CollectionInterface::isEmpty() - * @return bool - * @deprecated - */ - public function isEmpty(): bool - { - deprecationWarning( - '4.3.0 - Calling isEmpty() on a Query is deprecated. ' . - 'Instead call `$query->all()->isEmpty(...)` instead.' - ); - - return $this->all()->isEmpty(); - } - - /** - * @see \Cake\Collection\CollectionInterface::unwrap() - * @return \Traversable - * @deprecated - */ - public function unwrap(): Traversable - { - deprecationWarning( - '4.3.0 - Calling unwrap() on a Query is deprecated. ' . - 'Instead call `$query->all()->unwrap(...)` instead.' - ); - - return $this->all()->unwrap(); - } - - /** - * @see \Cake\Collection\CollectionInterface::transpose() - * @return \Cake\Collection\CollectionInterface - * @deprecated - */ - public function transpose(): CollectionInterface - { - deprecationWarning( - '4.3.0 - Calling transpose() on a Query is deprecated. ' . - 'Instead call `$query->all()->transpose(...)` instead.' - ); - - return $this->all()->transpose(); - } - - /** - * @see \Cake\Collection\CollectionInterface::count() - * @return int - * @deprecated - */ - public function count(): int - { - deprecationWarning( - '4.3.0 - Calling count() on a Query is deprecated. ' . - 'Instead call `$query->all()->count(...)` instead.' - ); - - return $this->all()->count(); - } - - /** - * @see \Cake\Collection\CollectionInterface::countKeys() - * @return int - * @deprecated - */ - public function countKeys(): int - { - deprecationWarning( - '4.3.0 - Calling countKeys() on a Query is deprecated. ' . - 'Instead call `$query->all()->countKeys(...)` instead.' - ); - - return $this->all()->countKeys(); - } - - /** - * @param callable|null $operation A callable that allows you to customize the product result. - * @param callable|null $filter A filtering callback that must return true for a result to be part - * of the final results. - * @see \Cake\Collection\CollectionInterface::cartesianProduct() - * @return \Cake\Collection\CollectionInterface - * @deprecated - */ - public function cartesianProduct(?callable $operation = null, ?callable $filter = null): CollectionInterface - { - deprecationWarning( - '4.3.0 - Calling cartesianProduct() on a Query is deprecated. ' . - 'Instead call `$query->all()->cartesianProduct(...)` instead.' - ); - - return $this->all()->cartesianProduct($operation, $filter); - } - - /** - * Populates or adds parts to current query clauses using an array. - * This is handy for passing all query clauses at once. - * - * @param array $options the options to be applied - * @return $this - */ - abstract public function applyOptions(array $options); - - /** - * Executes this query and returns a traversable object containing the results - * - * @return \Cake\Datasource\ResultSetInterface - */ - abstract protected function _execute(): ResultSetInterface; - - /** - * Decorates the results iterator with MapReduce routines and formatters - * - * @param \Traversable $result Original results - * @return \Cake\Datasource\ResultSetInterface - */ - protected function _decorateResults(Traversable $result): ResultSetInterface - { - $decorator = $this->_decoratorClass(); - foreach ($this->_mapReduce as $functions) { - $result = new MapReduce($result, $functions['mapper'], $functions['reducer']); - } - - if (!empty($this->_mapReduce)) { - $result = new $decorator($result); - } - - foreach ($this->_formatters as $formatter) { - $result = $formatter($result, $this); - } - - if (!empty($this->_formatters) && !($result instanceof $decorator)) { - $result = new $decorator($result); - } - - return $result; - } - - /** - * Returns the name of the class to be used for decorating results - * - * @return string - * @psalm-return class-string<\Cake\Datasource\ResultSetInterface> - */ - protected function _decoratorClass(): string - { - return ResultSetDecorator::class; - } -} diff --git a/app/vendor/cakephp/cakephp/src/Datasource/README.md b/app/vendor/cakephp/cakephp/src/Datasource/README.md index 38626ea63..768e4f4e6 100644 --- a/app/vendor/cakephp/cakephp/src/Datasource/README.md +++ b/app/vendor/cakephp/cakephp/src/Datasource/README.md @@ -11,7 +11,7 @@ interfaces provided by this package. A repository is a class capable of interfacing with a data source using operations such as `find`, `save` and `delete` by using intermediate query objects for expressing commands to -the data store and returning Entities as the single result unit of such system. +the data store and returning Entities as the single result unit of such a system. In the case of a Relational database, a Repository would be a `Table`, which can be return single or multiple `Entity` objects by using a `Query`. diff --git a/app/vendor/cakephp/cakephp/src/Datasource/RepositoryInterface.php b/app/vendor/cakephp/cakephp/src/Datasource/RepositoryInterface.php index 93a00416c..59d8594d4 100644 --- a/app/vendor/cakephp/cakephp/src/Datasource/RepositoryInterface.php +++ b/app/vendor/cakephp/cakephp/src/Datasource/RepositoryInterface.php @@ -16,6 +16,9 @@ */ namespace Cake\Datasource; +use Closure; +use Psr\SimpleCache\CacheInterface; + /** * Describes the methods that any class representing a data storage should * comply with. @@ -37,6 +40,16 @@ public function setAlias(string $alias); */ public function getAlias(): string; + /** + * Alias a field with the repository's current alias. + * + * If field is already aliased, it will result in no-op. + * + * @param string $field The field to alias. + * @return string The field prefixed with the repository alias. + */ + public function aliasField(string $field): string; + /** * Sets the table registry key used to create this table instance. * @@ -65,10 +78,10 @@ public function hasField(string $field): bool; * type of search that was selected. * * @param string $type the type of query to perform - * @param array $options An array that will be passed to Query::applyOptions() + * @param mixed ...$args Arguments that match up to finder-specific parameters * @return \Cake\Datasource\QueryInterface */ - public function find(string $type = 'all', array $options = []); + public function find(string $type = 'all', mixed ...$args): QueryInterface; /** * Returns a single record after finding it by its primary key, if no record is @@ -80,64 +93,74 @@ public function find(string $type = 'all', array $options = []); * $id = 10; * $article = $articles->get($id); * - * $article = $articles->get($id, ['contain' => ['Comments]]); + * $article = $articles->get($id, ['contain' => ['Comments']]); * ``` * * @param mixed $primaryKey primary key value to find - * @param array $options options accepted by `Table::find()` + * @param array|string $finder The finder to use. Passing an options array is deprecated. + * @param \Psr\SimpleCache\CacheInterface|string|null $cache The cache config to use. + * Defaults to `null`, i.e. no caching. + * @param \Closure|string|null $cacheKey The cache key to use. If not provided + * one will be autogenerated if `$cache` is not null. * @throws \Cake\Datasource\Exception\RecordNotFoundException if the record with such id * could not be found * @return \Cake\Datasource\EntityInterface * @see \Cake\Datasource\RepositoryInterface::find() */ - public function get($primaryKey, array $options = []): EntityInterface; + public function get( + mixed $primaryKey, + array|string $finder = 'all', + CacheInterface|string|null $cache = null, + Closure|string|null $cacheKey = null, + mixed ...$args, + ): EntityInterface; /** * Creates a new Query instance for this repository * * @return \Cake\Datasource\QueryInterface */ - public function query(); + public function query(): QueryInterface; /** * Update all matching records. * * Sets the $fields to the provided values based on $conditions. - * This method will *not* trigger beforeSave/afterSave events. If you need those + * This method will *not* trigger beforeSave/afterSave events. If you need those, * first load a collection of records and update them. * - * @param \Cake\Database\Expression\QueryExpression|\Closure|array|string $fields A hash of field => new value. - * @param mixed $conditions Conditions to be used, accepts anything Query::where() + * @param \Closure|array|string $fields A hash of field => new value. + * @param \Closure|array|string|null $conditions Conditions to be used, accepts anything Query::where() * can take. * @return int Count Returns the affected rows. */ - public function updateAll($fields, $conditions): int; + public function updateAll(Closure|array|string $fields, Closure|array|string|null $conditions): int; /** * Deletes all records matching the provided conditions. * * This method will *not* trigger beforeDelete/afterDelete events. If you - * need those first load a collection of records and delete them. + * need those, first load a collection of records and delete them. * * This method will *not* execute on associations' `cascade` attribute. You should * use database foreign keys + ON CASCADE rules if you need cascading deletes combined * with this method. * - * @param mixed $conditions Conditions to be used, accepts anything Query::where() + * @param \Closure|array|string|null $conditions Conditions to be used, accepts anything Query::where() * can take. * @return int Returns the number of affected rows. * @see \Cake\Datasource\RepositoryInterface::delete() */ - public function deleteAll($conditions): int; + public function deleteAll(Closure|array|string|null $conditions): int; /** * Returns true if there is any record in this repository matching the specified * conditions. * - * @param array $conditions list of conditions to pass to the query + * @param \Closure|array|string|null $conditions list of conditions to pass to the query * @return bool */ - public function exists($conditions): bool; + public function exists(Closure|array|string|null $conditions): bool; /** * Persists an entity based on the fields that are marked as dirty and @@ -145,10 +168,10 @@ public function exists($conditions): bool; * of any error. * * @param \Cake\Datasource\EntityInterface $entity the entity to be saved - * @param \ArrayAccess|array $options The options to use when saving. + * @param array $options The options to use when saving. * @return \Cake\Datasource\EntityInterface|false */ - public function save(EntityInterface $entity, $options = []); + public function save(EntityInterface $entity, array $options = []): EntityInterface|false; /** * Delete a single entity. @@ -157,10 +180,10 @@ public function save(EntityInterface $entity, $options = []); * based on the 'dependent' option used when defining the association. * * @param \Cake\Datasource\EntityInterface $entity The entity to remove. - * @param \ArrayAccess|array $options The options for the delete. + * @param array $options The options for the delete. * @return bool success */ - public function delete(EntityInterface $entity, $options = []): bool; + public function delete(EntityInterface $entity, array $options = []): bool; /** * This creates a new entity object. diff --git a/app/vendor/cakephp/cakephp/src/Datasource/ResultSetDecorator.php b/app/vendor/cakephp/cakephp/src/Datasource/ResultSetDecorator.php index 85d98b618..6be03e2ed 100644 --- a/app/vendor/cakephp/cakephp/src/Datasource/ResultSetDecorator.php +++ b/app/vendor/cakephp/cakephp/src/Datasource/ResultSetDecorator.php @@ -18,36 +18,16 @@ use Cake\Collection\Collection; use Cake\Core\Configure; -use Countable; /** * Generic ResultSet decorator. This will make any traversable object appear to * be a database result * - * @template T of \Cake\Datasource\EntityInterface|array + * @template T * @implements \Cake\Datasource\ResultSetInterface */ class ResultSetDecorator extends Collection implements ResultSetInterface { - /** - * Make this object countable. - * - * Part of the Countable interface. Calling this method - * will convert the underlying traversable object into an array and - * get the count of the underlying data. - * - * @return int - */ - public function count(): int - { - $iterator = $this->getInnerIterator(); - if ($iterator instanceof Countable) { - return $iterator->count(); - } - - return count($this->toArray()); - } - /** * @inheritDoc */ diff --git a/app/vendor/cakephp/cakephp/src/Datasource/ResultSetInterface.php b/app/vendor/cakephp/cakephp/src/Datasource/ResultSetInterface.php index c193fac81..35a1b37e0 100644 --- a/app/vendor/cakephp/cakephp/src/Datasource/ResultSetInterface.php +++ b/app/vendor/cakephp/cakephp/src/Datasource/ResultSetInterface.php @@ -17,14 +17,12 @@ namespace Cake\Datasource; use Cake\Collection\CollectionInterface; -use Countable; -use Serializable; /** * Describes how a collection of datasource results should look like * * @template T */ -interface ResultSetInterface extends CollectionInterface, Countable, Serializable +interface ResultSetInterface extends CollectionInterface { } diff --git a/app/vendor/cakephp/cakephp/src/Datasource/RuleInvoker.php b/app/vendor/cakephp/cakephp/src/Datasource/RuleInvoker.php index 8a80efc8e..bfd78946a 100644 --- a/app/vendor/cakephp/cakephp/src/Datasource/RuleInvoker.php +++ b/app/vendor/cakephp/cakephp/src/Datasource/RuleInvoker.php @@ -16,6 +16,8 @@ */ namespace Cake\Datasource; +use Closure; + /** * Contains logic for invoking an application rule. * @@ -32,14 +34,14 @@ class RuleInvoker * * @var string|null */ - protected $name; + protected ?string $name = null; /** * Rule options * * @var array */ - protected $options = []; + protected array $options = []; /** * Rule callable @@ -61,7 +63,7 @@ class RuleInvoker * rule $scope. * * @param callable $rule The rule to be invoked. - * @param ?string $name The name of the rule. Used in error messages. + * @param string|null $name The name of the rule. Used in error messages. * @param array $options The options for the rule. See above. */ public function __construct(callable $rule, ?string $name, array $options = []) @@ -115,20 +117,24 @@ public function __invoke(EntityInterface $entity, array $scope): bool { $rule = $this->rule; $pass = $rule($entity, $this->options + $scope); - if ($pass === true || empty($this->options['errorField'])) { - return $pass === true; + if ($pass === true) { + return true; } $message = $this->options['message'] ?? 'invalid'; if (is_string($pass)) { $message = $pass; } + if ($message instanceof Closure) { + $message = $message($entity, $this->options + $scope); + } if ($this->name) { $message = [$this->name => $message]; } else { $message = [$message]; } - $errorField = $this->options['errorField']; + + $errorField = $this->options['errorField'] ?? ($this->name ?? '_rule'); $entity->setError($errorField, $message); if ($entity instanceof InvalidPropertyInterface && isset($entity->{$errorField})) { @@ -136,7 +142,6 @@ public function __invoke(EntityInterface $entity, array $scope): bool $entity->setInvalidField($errorField, $invalidValue); } - /** @phpstan-ignore-next-line */ - return $pass === true; + return false; } } diff --git a/app/vendor/cakephp/cakephp/src/Datasource/RulesAwareTrait.php b/app/vendor/cakephp/cakephp/src/Datasource/RulesAwareTrait.php index 5c016146b..8f74ebdee 100644 --- a/app/vendor/cakephp/cakephp/src/Datasource/RulesAwareTrait.php +++ b/app/vendor/cakephp/cakephp/src/Datasource/RulesAwareTrait.php @@ -34,9 +34,9 @@ trait RulesAwareTrait /** * The domain rules to be applied to entities saved by this table * - * @var \Cake\Datasource\RulesChecker + * @var \Cake\Datasource\RulesChecker|null */ - protected $_rulesChecker; + protected ?RulesChecker $_rulesChecker = null; /** * Returns whether the passed entity complies with all the rules stored in @@ -44,11 +44,14 @@ trait RulesAwareTrait * * @param \Cake\Datasource\EntityInterface $entity The entity to check for validity. * @param string $operation The operation being run. Either 'create', 'update' or 'delete'. - * @param \ArrayObject|array|null $options The options To be passed to the rules. + * @param \ArrayObject|array|null $options The options To be passed to the rules. * @return bool */ - public function checkRules(EntityInterface $entity, string $operation = RulesChecker::CREATE, $options = null): bool - { + public function checkRules( + EntityInterface $entity, + string $operation = RulesChecker::CREATE, + ArrayObject|array|null $options = null, + ): bool { $rules = $this->rulesChecker(); $options = $options ?: new ArrayObject(); $options = is_array($options) ? new ArrayObject($options) : $options; @@ -57,7 +60,7 @@ public function checkRules(EntityInterface $entity, string $operation = RulesChe if ($hasEvents) { $event = $this->dispatchEvent( 'Model.beforeRules', - compact('entity', 'options', 'operation') + compact('entity', 'options', 'operation'), ); if ($event->isStopped()) { return $event->getResult(); @@ -69,7 +72,7 @@ public function checkRules(EntityInterface $entity, string $operation = RulesChe if ($hasEvents) { $event = $this->dispatchEvent( 'Model.afterRules', - compact('entity', 'options', 'result', 'operation') + compact('entity', 'options', 'result', 'operation'), ); if ($event->isStopped()) { @@ -95,9 +98,11 @@ public function rulesChecker(): RulesChecker if ($this->_rulesChecker !== null) { return $this->_rulesChecker; } - /** @psalm-var class-string<\Cake\Datasource\RulesChecker> $class */ + /** @var class-string<\Cake\Datasource\RulesChecker> $class */ $class = defined('static::RULES_CLASS') ? static::RULES_CLASS : RulesChecker::class; - /** @psalm-suppress ArgumentTypeCoercion */ + /** + * @phpstan-ignore-next-line + */ $this->_rulesChecker = $this->buildRules(new $class(['repository' => $this])); $this->dispatchEvent('Model.buildRules', ['rules' => $this->_rulesChecker]); diff --git a/app/vendor/cakephp/cakephp/src/Datasource/RulesChecker.php b/app/vendor/cakephp/cakephp/src/Datasource/RulesChecker.php index c523eba5a..b22146796 100644 --- a/app/vendor/cakephp/cakephp/src/Datasource/RulesChecker.php +++ b/app/vendor/cakephp/cakephp/src/Datasource/RulesChecker.php @@ -16,6 +16,7 @@ */ namespace Cake\Datasource; +use Cake\Core\Exception\CakeException; use InvalidArgumentException; /** @@ -67,42 +68,42 @@ class RulesChecker * * @var array<\Cake\Datasource\RuleInvoker> */ - protected $_rules = []; + protected array $_rules = []; /** * The list of rules to check during create operations * * @var array<\Cake\Datasource\RuleInvoker> */ - protected $_createRules = []; + protected array $_createRules = []; /** * The list of rules to check during update operations * * @var array<\Cake\Datasource\RuleInvoker> */ - protected $_updateRules = []; + protected array $_updateRules = []; /** * The list of rules to check during delete operations * * @var array<\Cake\Datasource\RuleInvoker> */ - protected $_deleteRules = []; + protected array $_deleteRules = []; /** * List of options to pass to every callable rule * * @var array */ - protected $_options = []; + protected array $_options = []; /** * Whether to use I18n functions for translating default error messages * * @var bool */ - protected $_useI18n = false; + protected bool $_useI18n = false; /** * Constructor. Takes the options to be passed to all rules. @@ -116,7 +117,7 @@ public function __construct(array $options = []) } /** - * Adds a rule that will be applied to the entity both on create and update + * Adds a rule that will be applied to the entity on create, update and delete * operations. * * ### Options @@ -133,10 +134,30 @@ public function __construct(array $options = []) * @param array $options List of extra options to pass to the rule callable as * second argument. * @return $this + * @throws \Cake\Core\Exception\CakeException If a rule with the same name already exists */ - public function add(callable $rule, $name = null, array $options = []) + public function add(callable $rule, array|string|null $name = null, array $options = []) { - $this->_rules[] = $this->_addError($rule, $name, $options); + if (is_string($name)) { + $this->checkName($name, $this->_rules); + $this->_rules[$name] = $this->_addError($rule, $name, $options); + } else { + $this->_rules[] = $this->_addError($rule, $name, $options); + } + + return $this; + } + + /** + * Removes a rule from the set. + * + * @param string $name The name of the rule to remove. + * @return $this + * @since 5.1.0 + */ + public function remove(string $name) + { + unset($this->_rules[$name]); return $this; } @@ -158,10 +179,30 @@ public function add(callable $rule, $name = null, array $options = []) * @param array $options List of extra options to pass to the rule callable as * second argument. * @return $this + * @throws \Cake\Core\Exception\CakeException If a rule with the same name already exists */ - public function addCreate(callable $rule, $name = null, array $options = []) + public function addCreate(callable $rule, array|string|null $name = null, array $options = []) { - $this->_createRules[] = $this->_addError($rule, $name, $options); + if (is_string($name)) { + $this->checkName($name, $this->_createRules); + $this->_createRules[$name] = $this->_addError($rule, $name, $options); + } else { + $this->_createRules[] = $this->_addError($rule, $name, $options); + } + + return $this; + } + + /** + * Removes a rule from the create set. + * + * @param string $name The name of the rule to remove. + * @return $this + * @since 5.1.0 + */ + public function removeCreate(string $name) + { + unset($this->_createRules[$name]); return $this; } @@ -183,10 +224,30 @@ public function addCreate(callable $rule, $name = null, array $options = []) * @param array $options List of extra options to pass to the rule callable as * second argument. * @return $this + * @throws \Cake\Core\Exception\CakeException If a rule with the same name already exists */ - public function addUpdate(callable $rule, $name = null, array $options = []) + public function addUpdate(callable $rule, array|string|null $name = null, array $options = []) { - $this->_updateRules[] = $this->_addError($rule, $name, $options); + if (is_string($name)) { + $this->checkName($name, $this->_updateRules); + $this->_updateRules[$name] = $this->_addError($rule, $name, $options); + } else { + $this->_updateRules[] = $this->_addError($rule, $name, $options); + } + + return $this; + } + + /** + * Removes a rule from the update set. + * + * @param string $name The name of the rule to remove. + * @return $this + * @since 5.1.0 + */ + public function removeUpdate(string $name) + { + unset($this->_updateRules[$name]); return $this; } @@ -208,10 +269,30 @@ public function addUpdate(callable $rule, $name = null, array $options = []) * @param array $options List of extra options to pass to the rule callable as * second argument. * @return $this + * @throws \Cake\Core\Exception\CakeException If a rule with the same name already exists + */ + public function addDelete(callable $rule, array|string|null $name = null, array $options = []) + { + if (is_string($name)) { + $this->checkName($name, $this->_deleteRules); + $this->_deleteRules[$name] = $this->_addError($rule, $name, $options); + } else { + $this->_deleteRules[] = $this->_addError($rule, $name, $options); + } + + return $this; + } + + /** + * Removes a rule from the delete set. + * + * @param string $name The name of the rule to remove. + * @return $this + * @since 5.1.0 */ - public function addDelete(callable $rule, $name = null, array $options = []) + public function removeDelete(string $name) { - $this->_deleteRules[] = $this->_addError($rule, $name, $options); + unset($this->_deleteRules[$name]); return $this; } @@ -229,19 +310,12 @@ public function addDelete(callable $rule, $name = null, array $options = []) */ public function check(EntityInterface $entity, string $mode, array $options = []): bool { - if ($mode === self::CREATE) { - return $this->checkCreate($entity, $options); - } - - if ($mode === self::UPDATE) { - return $this->checkUpdate($entity, $options); - } - - if ($mode === self::DELETE) { - return $this->checkDelete($entity, $options); - } - - throw new InvalidArgumentException('Wrong checking mode: ' . $mode); + return match ($mode) { + self::CREATE => $this->checkCreate($entity, $options), + self::UPDATE => $this->checkUpdate($entity, $options), + self::DELETE => $this->checkDelete($entity, $options), + default => throw new InvalidArgumentException('Wrong checking mode: ' . $mode), + }; } /** @@ -254,7 +328,11 @@ public function check(EntityInterface $entity, string $mode, array $options = [] */ public function checkCreate(EntityInterface $entity, array $options = []): bool { - return $this->_checkRules($entity, $options, array_merge($this->_rules, $this->_createRules)); + return $this->_checkRules( + $entity, + $options, + array_merge(array_values($this->_rules), array_values($this->_createRules)), + ); } /** @@ -267,7 +345,11 @@ public function checkCreate(EntityInterface $entity, array $options = []): bool */ public function checkUpdate(EntityInterface $entity, array $options = []): bool { - return $this->_checkRules($entity, $options, array_merge($this->_rules, $this->_updateRules)); + return $this->_checkRules( + $entity, + $options, + array_merge(array_values($this->_rules), array_values($this->_updateRules)), + ); } /** @@ -307,12 +389,12 @@ protected function _checkRules(EntityInterface $entity, array $options = [], arr * Utility method for decorating any callable so that if it returns false, the correct * property in the entity is marked as invalid. * - * @param callable|\Cake\Datasource\RuleInvoker $rule The rule to decorate + * @param callable $rule The rule to decorate * @param array|string|null $name The alias for a rule or an array of options * @param array $options The options containing the error message and field. * @return \Cake\Datasource\RuleInvoker */ - protected function _addError(callable $rule, $name = null, array $options = []): RuleInvoker + protected function _addError(callable $rule, array|string|null $name = null, array $options = []): RuleInvoker { if (is_array($name)) { $options = $name; @@ -327,4 +409,19 @@ protected function _addError(callable $rule, $name = null, array $options = []): return $rule; } + + /** + * Checks that a rule with the same name doesn't already exist + * + * @param string $name The name to check + * @param array<\Cake\Datasource\RuleInvoker> $rules The rules array to check + * @return void + * @throws \Cake\Core\Exception\CakeException + */ + protected function checkName(string $name, array $rules): void + { + if (array_key_exists($name, $rules)) { + throw new CakeException('A rule with the same name already exists'); + } + } } diff --git a/app/vendor/cakephp/cakephp/src/Datasource/SchemaInterface.php b/app/vendor/cakephp/cakephp/src/Datasource/SchemaInterface.php index 08ab8be25..660275030 100644 --- a/app/vendor/cakephp/cakephp/src/Datasource/SchemaInterface.php +++ b/app/vendor/cakephp/cakephp/src/Datasource/SchemaInterface.php @@ -56,7 +56,7 @@ public function name(): string; * @param array|string $attrs The attributes for the column or the type name. * @return $this */ - public function addColumn(string $name, $attrs); + public function addColumn(string $name, array|string $attrs); /** * Get column data in the table. @@ -100,7 +100,7 @@ public function columns(): array; public function getColumnType(string $name): ?string; /** - * Sets the type of a column. + * Sets the type of column. * * @param string $name The column to set the type of. * @param string $type The type to set the column to. @@ -110,7 +110,7 @@ public function setColumnType(string $name, string $type); /** * Returns the base type name for the provided column. - * This represent the database type a more complex class is + * This represents the database type a more complex class is * based upon. * * @param string $column The column name to get the base type from diff --git a/app/vendor/cakephp/cakephp/src/Datasource/SimplePaginator.php b/app/vendor/cakephp/cakephp/src/Datasource/SimplePaginator.php deleted file mode 100644 index 134b0bd64..000000000 --- a/app/vendor/cakephp/cakephp/src/Datasource/SimplePaginator.php +++ /dev/null @@ -1,10 +0,0 @@ -=7.4.0", - "cakephp/core": "^4.0", - "psr/log": "^1.0 || ^2.0", - "psr/simple-cache": "^1.0 || ^2.0" + "php": ">=8.1", + "cakephp/core": "5.2.*@dev", + "psr/simple-cache": "^2.0 || ^3.0" + }, + "require-dev": { + "cakephp/cache": "5.2.*@dev", + "cakephp/collection": "5.2.*@dev", + "cakephp/utility": "5.2.*@dev" + }, + "autoload": { + "psr-4": { + "Cake\\Datasource\\": "." + } }, "suggest": { "cakephp/utility": "If you decide to use EntityTrait.", "cakephp/collection": "If you decide to use ResultSetInterface.", "cakephp/cache": "If you decide to use Query caching." }, - "autoload": { - "psr-4": { - "Cake\\Datasource\\": "." + "prefer-stable": true, + "extra": { + "branch-alias": { + "dev-5.x": "5.2.x-dev" } } } diff --git a/app/vendor/cakephp/cakephp/src/Error/BaseErrorHandler.php b/app/vendor/cakephp/cakephp/src/Error/BaseErrorHandler.php deleted file mode 100644 index 349e67719..000000000 --- a/app/vendor/cakephp/cakephp/src/Error/BaseErrorHandler.php +++ /dev/null @@ -1,412 +0,0 @@ - - */ - protected $_defaultConfig = [ - 'log' => true, - 'trace' => false, - 'skipLog' => [], - 'errorLogger' => ErrorLogger::class, - ]; - - /** - * @var bool - */ - protected $_handled = false; - - /** - * Exception logger instance. - * - * @var \Cake\Error\ErrorLoggerInterface|null - */ - protected $logger; - - /** - * Display an error message in an environment specific way. - * - * Subclasses should implement this method to display the error as - * desired for the runtime they operate in. - * - * @param array $error An array of error data. - * @param bool $debug Whether the app is in debug mode. - * @return void - */ - abstract protected function _displayError(array $error, bool $debug): void; - - /** - * Display an exception in an environment specific way. - * - * Subclasses should implement this method to display an uncaught exception as - * desired for the runtime they operate in. - * - * @param \Throwable $exception The uncaught exception. - * @return void - */ - abstract protected function _displayException(Throwable $exception): void; - - /** - * Register the error and exception handlers. - * - * @return void - */ - public function register(): void - { - deprecationWarning( - 'Use of `BaseErrorHandler` and subclasses are deprecated. ' . - 'Upgrade to the new `ErrorTrap` and `ExceptionTrap` subsystem. ' . - 'See https://book.cakephp.org/4/en/appendices/4-4-migration-guide.html' - ); - - $level = $this->_config['errorLevel'] ?? -1; - error_reporting($level); - set_error_handler([$this, 'handleError'], $level); - set_exception_handler([$this, 'handleException']); - register_shutdown_function(function (): void { - if ((PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') && $this->_handled) { - return; - } - $megabytes = $this->_config['extraFatalErrorMemory'] ?? 4; - if ($megabytes > 0) { - $this->increaseMemoryLimit($megabytes * 1024); - } - $error = error_get_last(); - if (!is_array($error)) { - return; - } - $fatals = [ - E_USER_ERROR, - E_ERROR, - E_PARSE, - ]; - if (!in_array($error['type'], $fatals, true)) { - return; - } - $this->handleFatalError( - $error['type'], - $error['message'], - $error['file'], - $error['line'] - ); - }); - } - - /** - * Set as the default error handler by CakePHP. - * - * Use config/error.php to customize or replace this error handler. - * This function will use Debugger to display errors when debug mode is on. And - * will log errors to Log, when debug mode is off. - * - * You can use the 'errorLevel' option to set what type of errors will be handled. - * Stack traces for errors can be enabled with the 'trace' option. - * - * @param int $code Code of error - * @param string $description Error description - * @param string|null $file File on which error occurred - * @param int|null $line Line that triggered the error - * @param array|null $context Context - * @return bool True if error was handled - */ - public function handleError( - int $code, - string $description, - ?string $file = null, - ?int $line = null, - ?array $context = null - ): bool { - if (!(error_reporting() & $code)) { - return false; - } - $this->_handled = true; - [$error, $log] = static::mapErrorCode($code); - if ($log === LOG_ERR) { - /** @psalm-suppress PossiblyNullArgument */ - return $this->handleFatalError($code, $description, $file, $line); - } - $data = [ - 'level' => $log, - 'code' => $code, - 'error' => $error, - 'description' => $description, - 'file' => $file, - 'line' => $line, - ]; - - $debug = (bool)Configure::read('debug'); - if ($debug) { - // By default trim 3 frames off for the public and protected methods - // used by ErrorHandler instances. - $start = 3; - - // Can be used by error handlers that wrap other error handlers - // to coerce the generated stack trace to the correct point. - if (isset($context['_trace_frame_offset'])) { - $start += $context['_trace_frame_offset']; - unset($context['_trace_frame_offset']); - } - $data += [ - 'context' => $context, - 'start' => $start, - 'path' => Debugger::trimPath((string)$file), - ]; - } - $this->_displayError($data, $debug); - $this->_logError($log, $data); - - return true; - } - - /** - * Checks the passed exception type. If it is an instance of `Error` - * then, it wraps the passed object inside another Exception object - * for backwards compatibility purposes. - * - * @param \Throwable $exception The exception to handle - * @return void - * @deprecated 4.0.0 Unused method will be removed in 5.0 - */ - public function wrapAndHandleException(Throwable $exception): void - { - deprecationWarning('This method is no longer in use. Call handleException instead.'); - $this->handleException($exception); - } - - /** - * Handle uncaught exceptions. - * - * Uses a template method provided by subclasses to display errors in an - * environment appropriate way. - * - * @param \Throwable $exception Exception instance. - * @return void - * @throws \Exception When renderer class not found - * @see https://secure.php.net/manual/en/function.set-exception-handler.php - */ - public function handleException(Throwable $exception): void - { - $this->_displayException($exception); - $this->logException($exception); - $code = $exception->getCode() ?: 1; - $this->_stop((int)$code); - } - - /** - * Stop the process. - * - * Implemented in subclasses that need it. - * - * @param int $code Exit code. - * @return void - */ - protected function _stop(int $code): void - { - // Do nothing. - } - - /** - * Display/Log a fatal error. - * - * @param int $code Code of error - * @param string $description Error description - * @param string $file File on which error occurred - * @param int $line Line that triggered the error - * @return bool - */ - public function handleFatalError(int $code, string $description, string $file, int $line): bool - { - $data = [ - 'code' => $code, - 'description' => $description, - 'file' => $file, - 'line' => $line, - 'error' => 'Fatal Error', - ]; - $this->_logError(LOG_ERR, $data); - - $this->handleException(new FatalErrorException($description, 500, $file, $line)); - - return true; - } - - /** - * Increases the PHP "memory_limit" ini setting by the specified amount - * in kilobytes - * - * @param int $additionalKb Number in kilobytes - * @return void - */ - public function increaseMemoryLimit(int $additionalKb): void - { - $limit = ini_get('memory_limit'); - if ($limit === false || $limit === '' || $limit === '-1') { - return; - } - $limit = trim($limit); - $units = strtoupper(substr($limit, -1)); - $current = (int)substr($limit, 0, strlen($limit) - 1); - if ($units === 'M') { - $current *= 1024; - $units = 'K'; - } - if ($units === 'G') { - $current = $current * 1024 * 1024; - $units = 'K'; - } - - if ($units === 'K') { - ini_set('memory_limit', ceil($current + $additionalKb) . 'K'); - } - } - - /** - * Log an error. - * - * @param string|int $level The level name of the log. - * @param array $data Array of error data. - * @return bool - */ - protected function _logError($level, array $data): bool - { - $message = sprintf( - '%s (%s): %s in [%s, line %s]', - $data['error'], - $data['code'], - $data['description'], - $data['file'], - $data['line'] - ); - $context = []; - if (!empty($this->_config['trace'])) { - $context['trace'] = Debugger::trace([ - 'start' => 1, - 'format' => 'log', - ]); - $context['request'] = Router::getRequest(); - } - - return $this->getLogger()->logMessage($level, $message, $context); - } - - /** - * Log an error for the exception if applicable. - * - * @param \Throwable $exception The exception to log a message for. - * @param \Psr\Http\Message\ServerRequestInterface|null $request The current request. - * @return bool - */ - public function logException(Throwable $exception, ?ServerRequestInterface $request = null): bool - { - if (empty($this->_config['log'])) { - return false; - } - foreach ($this->_config['skipLog'] as $class) { - if ($exception instanceof $class) { - return false; - } - } - - return $this->getLogger()->log($exception, $request ?? Router::getRequest()); - } - - /** - * Get exception logger. - * - * @return \Cake\Error\ErrorLoggerInterface - */ - public function getLogger() - { - if ($this->logger === null) { - /** @var \Cake\Error\ErrorLoggerInterface $logger */ - $logger = new $this->_config['errorLogger']($this->_config); - - if (!$logger instanceof ErrorLoggerInterface) { - // Set the logger so that the next error can be logged. - $this->logger = new ErrorLogger($this->_config); - - $interface = ErrorLoggerInterface::class; - $type = getTypeName($logger); - throw new RuntimeException("Cannot create logger. `{$type}` does not implement `{$interface}`."); - } - $this->logger = $logger; - } - - return $this->logger; - } - - /** - * Map an error code into an Error word, and log location. - * - * @param int $code Error code to map - * @return array Array of error word, and log location. - */ - public static function mapErrorCode(int $code): array - { - $levelMap = [ - E_PARSE => 'error', - E_ERROR => 'error', - E_CORE_ERROR => 'error', - E_COMPILE_ERROR => 'error', - E_USER_ERROR => 'error', - E_WARNING => 'warning', - E_USER_WARNING => 'warning', - E_COMPILE_WARNING => 'warning', - E_RECOVERABLE_ERROR => 'warning', - E_NOTICE => 'notice', - E_USER_NOTICE => 'notice', - E_STRICT => 'strict', - E_DEPRECATED => 'deprecated', - E_USER_DEPRECATED => 'deprecated', - ]; - $logMap = [ - 'error' => LOG_ERR, - 'warning' => LOG_WARNING, - 'notice' => LOG_NOTICE, - 'strict' => LOG_NOTICE, - 'deprecated' => LOG_NOTICE, - ]; - - $error = $levelMap[$code]; - $log = $logMap[$error]; - - return [ucfirst($error), $log]; - } -} diff --git a/app/vendor/cakephp/cakephp/src/Error/ConsoleErrorHandler.php b/app/vendor/cakephp/cakephp/src/Error/ConsoleErrorHandler.php deleted file mode 100644 index 78353d0ca..000000000 --- a/app/vendor/cakephp/cakephp/src/Error/ConsoleErrorHandler.php +++ /dev/null @@ -1,139 +0,0 @@ - $config Config options for the error handler. - */ - public function __construct(array $config = []) - { - $config += [ - 'stderr' => new ConsoleOutput('php://stderr'), - 'log' => false, - ]; - - $this->setConfig($config); - $this->_stderr = $this->_config['stderr']; - } - - /** - * Handle errors in the console environment. Writes errors to stderr, - * and logs messages if Configure::read('debug') is false. - * - * @param \Throwable $exception Exception instance. - * @return void - * @throws \Exception When renderer class not found - * @see https://secure.php.net/manual/en/function.set-exception-handler.php - */ - public function handleException(Throwable $exception): void - { - $this->_displayException($exception); - $this->logException($exception); - - $exitCode = Command::CODE_ERROR; - if ($exception instanceof ConsoleException) { - $exitCode = $exception->getCode(); - } - $this->_stop($exitCode); - } - - /** - * Prints an exception to stderr. - * - * @param \Throwable $exception The exception to handle - * @return void - */ - protected function _displayException(Throwable $exception): void - { - $errorName = 'Exception:'; - if ($exception instanceof FatalErrorException) { - $errorName = 'Fatal Error:'; - } - - $message = sprintf( - "%s %s\nIn [%s, line %s]\n", - $errorName, - $exception->getMessage(), - $exception->getFile(), - $exception->getLine() - ); - $this->_stderr->write($message); - } - - /** - * Prints an error to stderr. - * - * Template method of BaseErrorHandler. - * - * @param array $error An array of error data. - * @param bool $debug Whether the app is in debug mode. - * @return void - */ - protected function _displayError(array $error, bool $debug): void - { - $message = sprintf( - "%s\nIn [%s, line %s]", - $error['description'], - $error['file'], - $error['line'] - ); - $message = sprintf( - "%s Error: %s\n", - $error['error'], - $message - ); - $this->_stderr->write($message); - } - - /** - * Stop the execution and set the exit code for the process. - * - * @param int $code The exit code. - * @return void - */ - protected function _stop(int $code): void - { - exit($code); - } -} - -// phpcs:disable -class_alias( - 'Cake\Error\ConsoleErrorHandler', - 'Cake\Console\ConsoleErrorHandler' -); -// phpcs:enable diff --git a/app/vendor/cakephp/cakephp/src/Error/Debug/ArrayItemNode.php b/app/vendor/cakephp/cakephp/src/Error/Debug/ArrayItemNode.php index 4051dd475..de74bb12a 100644 --- a/app/vendor/cakephp/cakephp/src/Error/Debug/ArrayItemNode.php +++ b/app/vendor/cakephp/cakephp/src/Error/Debug/ArrayItemNode.php @@ -24,12 +24,12 @@ class ArrayItemNode implements NodeInterface /** * @var \Cake\Error\Debug\NodeInterface */ - private $key; + private NodeInterface $key; /** * @var \Cake\Error\Debug\NodeInterface */ - private $value; + private NodeInterface $value; /** * Constructor @@ -48,7 +48,7 @@ public function __construct(NodeInterface $key, NodeInterface $value) * * @return \Cake\Error\Debug\NodeInterface */ - public function getValue() + public function getValue(): NodeInterface { return $this->value; } @@ -58,7 +58,7 @@ public function getValue() * * @return \Cake\Error\Debug\NodeInterface */ - public function getKey() + public function getKey(): NodeInterface { return $this->key; } diff --git a/app/vendor/cakephp/cakephp/src/Error/Debug/ArrayNode.php b/app/vendor/cakephp/cakephp/src/Error/Debug/ArrayNode.php index ccbb7274b..c7f91d2c2 100644 --- a/app/vendor/cakephp/cakephp/src/Error/Debug/ArrayNode.php +++ b/app/vendor/cakephp/cakephp/src/Error/Debug/ArrayNode.php @@ -24,7 +24,7 @@ class ArrayNode implements NodeInterface /** * @var array<\Cake\Error\Debug\ArrayItemNode> */ - private $items; + private array $items = []; /** * Constructor @@ -33,7 +33,6 @@ class ArrayNode implements NodeInterface */ public function __construct(array $items = []) { - $this->items = []; foreach ($items as $item) { $this->add($item); } diff --git a/app/vendor/cakephp/cakephp/src/Error/Debug/ClassNode.php b/app/vendor/cakephp/cakephp/src/Error/Debug/ClassNode.php index 62fa58522..09adf5f22 100644 --- a/app/vendor/cakephp/cakephp/src/Error/Debug/ClassNode.php +++ b/app/vendor/cakephp/cakephp/src/Error/Debug/ClassNode.php @@ -24,17 +24,17 @@ class ClassNode implements NodeInterface /** * @var string */ - private $class; + private string $class; /** * @var int */ - private $id; + private int $id; /** * @var array<\Cake\Error\Debug\PropertyNode> */ - private $properties = []; + private array $properties = []; /** * Constructor diff --git a/app/vendor/cakephp/cakephp/src/Error/Debug/ConsoleFormatter.php b/app/vendor/cakephp/cakephp/src/Error/Debug/ConsoleFormatter.php index 5cb343383..e738d4ec3 100644 --- a/app/vendor/cakephp/cakephp/src/Error/Debug/ConsoleFormatter.php +++ b/app/vendor/cakephp/cakephp/src/Error/Debug/ConsoleFormatter.php @@ -16,7 +16,7 @@ */ namespace Cake\Error\Debug; -use RuntimeException; +use InvalidArgumentException; use function Cake\Core\env; /** @@ -31,7 +31,7 @@ class ConsoleFormatter implements FormatterInterface * * @var array */ - protected $styles = [ + protected array $styles = [ // bold yellow 'const' => '1;33', // green @@ -67,8 +67,8 @@ public static function environmentMatches(): bool // Windows environment checks if ( DIRECTORY_SEPARATOR === '\\' && - strpos(strtolower(php_uname('v')), 'windows 10') === false && - strpos(strtolower((string)env('SHELL')), 'bash.exe') === false && + !str_contains(strtolower(php_uname('v')), 'windows 10') && + !str_contains(strtolower((string)env('SHELL')), 'bash.exe') && !(bool)env('ANSICON') && env('ConEmuANSI') !== 'ON' ) { @@ -121,20 +121,14 @@ public function dump(NodeInterface $node): string protected function export(NodeInterface $var, int $indent): string { if ($var instanceof ScalarNode) { - switch ($var->getType()) { - case 'bool': - return $this->style('const', $var->getValue() ? 'true' : 'false'); - case 'null': - return $this->style('const', 'null'); - case 'string': - return $this->style('string', "'" . (string)$var->getValue() . "'"); - case 'int': - case 'float': - return $this->style('visibility', "({$var->getType()})") . - ' ' . $this->style('number', "{$var->getValue()}"); - default: - return "({$var->getType()}) {$var->getValue()}"; - } + return match ($var->getType()) { + 'bool' => $this->style('const', $var->getValue() ? 'true' : 'false'), + 'null' => $this->style('const', 'null'), + 'string' => $this->style('string', "'" . $var->getValue() . "'"), + 'int', 'float' => $this->style('visibility', "({$var->getType()})") . + ' ' . $this->style('number', "{$var->getValue()}"), + default => "({$var->getType()}) {$var->getValue()}", + }; } if ($var instanceof ArrayNode) { return $this->exportArray($var, $indent + 1); @@ -145,7 +139,7 @@ protected function export(NodeInterface $var, int $indent): string if ($var instanceof SpecialNode) { return $this->style('special', $var->getValue()); } - throw new RuntimeException('Unknown node received ' . get_class($var)); + throw new InvalidArgumentException('Unknown node received ' . $var::class); } /** @@ -169,7 +163,7 @@ protected function exportArray(ArrayNode $var, int $indent): string } $close = $this->style('punct', ']'); - if (count($vars)) { + if ($vars !== []) { return $out . implode($this->style('punct', ','), $vars) . $end . $close; } @@ -184,7 +178,7 @@ protected function exportArray(ArrayNode $var, int $indent): string * @return string * @see \Cake\Error\Debugger::exportVar() */ - protected function exportObject($var, int $indent): string + protected function exportObject(ClassNode|ReferenceNode $var, int $indent): string { $props = []; @@ -221,7 +215,7 @@ protected function exportObject($var, int $indent): string $this->export($property->getValue(), $indent); } } - if (count($props)) { + if ($props !== []) { return $out . $break . implode($break, $props) . $end; } diff --git a/app/vendor/cakephp/cakephp/src/Error/Debug/DebugContext.php b/app/vendor/cakephp/cakephp/src/Error/Debug/DebugContext.php index c240e3feb..a5deccb33 100644 --- a/app/vendor/cakephp/cakephp/src/Error/Debug/DebugContext.php +++ b/app/vendor/cakephp/cakephp/src/Error/Debug/DebugContext.php @@ -31,17 +31,17 @@ class DebugContext /** * @var int */ - private $maxDepth; + private int $maxDepth = 0; /** * @var int */ - private $depth = 0; + private int $depth = 0; /** - * @var \SplObjectStorage + * @var \SplObjectStorage */ - private $refs; + private SplObjectStorage $refs; /** * Constructor @@ -59,7 +59,7 @@ public function __construct(int $maxDepth) * * @return static */ - public function withAddedDepth() + public function withAddedDepth(): static { $new = clone $this; $new->depth += 1; @@ -88,11 +88,11 @@ public function remainingDepth(): int */ public function getReferenceId(object $object): int { - if ($this->refs->contains($object)) { + if ($this->refs->offsetExists($object)) { return $this->refs[$object]; } $refId = $this->refs->count(); - $this->refs->attach($object, $refId); + $this->refs->offsetSet($object, $refId); return $refId; } @@ -105,6 +105,6 @@ public function getReferenceId(object $object): int */ public function hasReference(object $object): bool { - return $this->refs->contains($object); + return $this->refs->offsetExists($object); } } diff --git a/app/vendor/cakephp/cakephp/src/Error/Debug/HtmlFormatter.php b/app/vendor/cakephp/cakephp/src/Error/Debug/HtmlFormatter.php index adaf6ee29..d77d35827 100644 --- a/app/vendor/cakephp/cakephp/src/Error/Debug/HtmlFormatter.php +++ b/app/vendor/cakephp/cakephp/src/Error/Debug/HtmlFormatter.php @@ -16,7 +16,7 @@ */ namespace Cake\Error\Debug; -use RuntimeException; +use InvalidArgumentException; use function Cake\Core\h; /** @@ -29,14 +29,14 @@ class HtmlFormatter implements FormatterInterface /** * @var bool */ - protected static $outputHeader = false; + protected static bool $outputHeader = false; /** * Random id so that HTML ids are not shared between dump outputs. * * @var string */ - protected $id; + protected string $id; /** * Constructor. @@ -70,7 +70,7 @@ public function formatWrapper(string $contents, array $location): string $lineInfo = sprintf( '%s (line %s)', $location['file'], - $location['line'] + $location['line'], ); } $parts = [ @@ -95,7 +95,7 @@ protected function dumpHeader(): string ob_start(); include __DIR__ . DIRECTORY_SEPARATOR . 'dumpHeader.html'; - return ob_get_clean(); + return (string)ob_get_clean(); } /** @@ -113,7 +113,7 @@ public function dump(NodeInterface $node): string $head = $this->dumpHeader(); } - return $head . '
' . $html . '
'; + return $head . '
' . $html . '
'; } /** @@ -126,20 +126,14 @@ public function dump(NodeInterface $node): string protected function export(NodeInterface $var, int $indent): string { if ($var instanceof ScalarNode) { - switch ($var->getType()) { - case 'bool': - return $this->style('const', $var->getValue() ? 'true' : 'false'); - case 'null': - return $this->style('const', 'null'); - case 'string': - return $this->style('string', "'" . (string)$var->getValue() . "'"); - case 'int': - case 'float': - return $this->style('visibility', "({$var->getType()})") . - ' ' . $this->style('number', "{$var->getValue()}"); - default: - return "({$var->getType()}) {$var->getValue()}"; - } + return match ($var->getType()) { + 'bool' => $this->style('const', $var->getValue() ? 'true' : 'false'), + 'null' => $this->style('const', 'null'), + 'string' => $this->style('string', "'" . $var->getValue() . "'"), + 'int', 'float' => $this->style('visibility', "({$var->getType()})") . + ' ' . $this->style('number', "{$var->getValue()}"), + default => "({$var->getType()}) {$var->getValue()}", + }; } if ($var instanceof ArrayNode) { return $this->exportArray($var, $indent + 1); @@ -150,7 +144,7 @@ protected function export(NodeInterface $var, int $indent): string if ($var instanceof SpecialNode) { return $this->style('special', $var->getValue()); } - throw new RuntimeException('Unknown node received ' . get_class($var)); + throw new InvalidArgumentException('Unknown node received ' . $var::class); } /** @@ -162,9 +156,9 @@ protected function export(NodeInterface $var, int $indent): string */ protected function exportArray(ArrayNode $var, int $indent): string { - $open = '' . + $open = '' . $this->style('punct', '[') . - ''; + ''; $vars = []; $break = "\n" . str_repeat(' ', $indent); $endBreak = "\n" . str_repeat(' ', $indent - 1); @@ -172,7 +166,7 @@ protected function exportArray(ArrayNode $var, int $indent): string $arrow = $this->style('punct', ' => '); foreach ($var->getChildren() as $item) { $val = $item->getValue(); - $vars[] = $break . '' . + $vars[] = $break . '' . $this->export($item->getKey(), $indent) . $arrow . $this->export($val, $indent) . $this->style('punct', ',') . ''; @@ -194,24 +188,24 @@ protected function exportArray(ArrayNode $var, int $indent): string * @return string * @see \Cake\Error\Debugger::exportVar() */ - protected function exportObject($var, int $indent): string + protected function exportObject(ClassNode|ReferenceNode $var, int $indent): string { $objectId = "cake-db-object-{$this->id}-{$var->getId()}"; $out = sprintf( - '', - $objectId + '', + $objectId, ); $break = "\n" . str_repeat(' ', $indent); $endBreak = "\n" . str_repeat(' ', $indent - 1); if ($var instanceof ReferenceNode) { $link = sprintf( - 'id: %s', + 'id: %s', $objectId, - $var->getId() + $var->getId(), ); - return '' . + return '' . $this->style('punct', 'object(') . $this->style('class', $var->getValue()) . $this->style('punct', ') ') . @@ -225,7 +219,7 @@ protected function exportObject($var, int $indent): string $this->style('punct', ') id:') . $this->style('number', (string)$var->getId()) . $this->style('punct', ' {') . - ''; + ''; $props = []; foreach ($var->getChildren() as $property) { @@ -234,7 +228,7 @@ protected function exportObject($var, int $indent): string $name = $property->getName(); if ($visibility && $visibility !== 'public') { $props[] = $break . - '' . + '' . $this->style('visibility', $visibility) . ' ' . $this->style('property', $name) . @@ -243,7 +237,7 @@ protected function exportObject($var, int $indent): string ''; } else { $props[] = $break . - '' . + '' . $this->style('property', $name) . $arrow . $this->export($property->getValue(), $indent) . @@ -256,7 +250,7 @@ protected function exportObject($var, int $indent): string $this->style('punct', '}') . ''; - if (count($props)) { + if ($props !== []) { return $out . implode('', $props) . $end; } @@ -273,9 +267,9 @@ protected function exportObject($var, int $indent): string protected function style(string $style, string $text): string { return sprintf( - '%s', + '%s', $style, - h($text) + h($text), ); } } diff --git a/app/vendor/cakephp/cakephp/src/Error/Debug/NodeInterface.php b/app/vendor/cakephp/cakephp/src/Error/Debug/NodeInterface.php index 6a17c795e..f5329bd49 100644 --- a/app/vendor/cakephp/cakephp/src/Error/Debug/NodeInterface.php +++ b/app/vendor/cakephp/cakephp/src/Error/Debug/NodeInterface.php @@ -35,5 +35,5 @@ public function getChildren(): array; * * @return mixed */ - public function getValue(); + public function getValue(): mixed; } diff --git a/app/vendor/cakephp/cakephp/src/Error/Debug/PropertyNode.php b/app/vendor/cakephp/cakephp/src/Error/Debug/PropertyNode.php index 568bc00a0..742e91ab1 100644 --- a/app/vendor/cakephp/cakephp/src/Error/Debug/PropertyNode.php +++ b/app/vendor/cakephp/cakephp/src/Error/Debug/PropertyNode.php @@ -24,17 +24,17 @@ class PropertyNode implements NodeInterface /** * @var string */ - private $name; + private string $name; /** * @var string|null */ - private $visibility; + private ?string $visibility = null; /** * @var \Cake\Error\Debug\NodeInterface */ - private $value; + private NodeInterface $value; /** * Constructor @@ -63,7 +63,7 @@ public function getValue(): NodeInterface /** * Get the property visibility * - * @return string + * @return string|null */ public function getVisibility(): ?string { diff --git a/app/vendor/cakephp/cakephp/src/Error/Debug/ReferenceNode.php b/app/vendor/cakephp/cakephp/src/Error/Debug/ReferenceNode.php index 008428f42..427dda78d 100644 --- a/app/vendor/cakephp/cakephp/src/Error/Debug/ReferenceNode.php +++ b/app/vendor/cakephp/cakephp/src/Error/Debug/ReferenceNode.php @@ -28,12 +28,12 @@ class ReferenceNode implements NodeInterface /** * @var string */ - private $class; + private string $class; /** * @var int */ - private $id; + private int $id; /** * Constructor diff --git a/app/vendor/cakephp/cakephp/src/Error/Debug/ScalarNode.php b/app/vendor/cakephp/cakephp/src/Error/Debug/ScalarNode.php index e5c5efe42..1a05a19d6 100644 --- a/app/vendor/cakephp/cakephp/src/Error/Debug/ScalarNode.php +++ b/app/vendor/cakephp/cakephp/src/Error/Debug/ScalarNode.php @@ -24,10 +24,10 @@ class ScalarNode implements NodeInterface /** * @var string */ - private $type; + private string $type; /** - * @var string|float|int|bool|null + * @var resource|string|float|int|bool|null */ private $value; @@ -35,7 +35,7 @@ class ScalarNode implements NodeInterface * Constructor * * @param string $type The type of scalar value. - * @param string|float|int|bool|null $value The wrapped value. + * @param resource|string|float|int|bool|null $value The wrapped value. */ public function __construct(string $type, $value) { @@ -56,9 +56,9 @@ public function getType(): string /** * Get the value * - * @return string|float|int|bool|null + * @return resource|string|float|int|bool|null */ - public function getValue() + public function getValue(): mixed { return $this->value; } diff --git a/app/vendor/cakephp/cakephp/src/Error/Debug/SpecialNode.php b/app/vendor/cakephp/cakephp/src/Error/Debug/SpecialNode.php index 34b38f0b8..bc3bc07aa 100644 --- a/app/vendor/cakephp/cakephp/src/Error/Debug/SpecialNode.php +++ b/app/vendor/cakephp/cakephp/src/Error/Debug/SpecialNode.php @@ -24,7 +24,7 @@ class SpecialNode implements NodeInterface /** * @var string */ - private $value; + private string $value; /** * Constructor diff --git a/app/vendor/cakephp/cakephp/src/Error/Debug/TextFormatter.php b/app/vendor/cakephp/cakephp/src/Error/Debug/TextFormatter.php index ef60b74d1..68adae161 100644 --- a/app/vendor/cakephp/cakephp/src/Error/Debug/TextFormatter.php +++ b/app/vendor/cakephp/cakephp/src/Error/Debug/TextFormatter.php @@ -16,7 +16,7 @@ */ namespace Cake\Error\Debug; -use RuntimeException; +use InvalidArgumentException; /** * A Debugger formatter for generating unstyled plain text output. @@ -71,16 +71,12 @@ public function dump(NodeInterface $node): string protected function export(NodeInterface $var, int $indent): string { if ($var instanceof ScalarNode) { - switch ($var->getType()) { - case 'bool': - return $var->getValue() ? 'true' : 'false'; - case 'null': - return 'null'; - case 'string': - return "'" . (string)$var->getValue() . "'"; - default: - return "({$var->getType()}) {$var->getValue()}"; - } + return match ($var->getType()) { + 'bool' => $var->getValue() ? 'true' : 'false', + 'null' => 'null', + 'string' => "'" . $var->getValue() . "'", + default => "({$var->getType()}) {$var->getValue()}", + }; } if ($var instanceof ArrayNode) { return $this->exportArray($var, $indent + 1); @@ -91,7 +87,7 @@ protected function export(NodeInterface $var, int $indent): string if ($var instanceof SpecialNode) { return $var->getValue(); } - throw new RuntimeException('Unknown node received ' . get_class($var)); + throw new InvalidArgumentException('Unknown node received ' . $var::class); } /** @@ -112,7 +108,7 @@ protected function exportArray(ArrayNode $var, int $indent): string $val = $item->getValue(); $vars[] = $break . $this->export($item->getKey(), $indent) . ' => ' . $this->export($val, $indent); } - if (count($vars)) { + if ($vars !== []) { return $out . implode(',', $vars) . $end . ']'; } @@ -127,7 +123,7 @@ protected function exportArray(ArrayNode $var, int $indent): string * @return string * @see \Cake\Error\Debugger::exportVar() */ - protected function exportObject($var, int $indent): string + protected function exportObject(ClassNode|ReferenceNode $var, int $indent): string { $out = ''; $props = []; @@ -149,7 +145,7 @@ protected function exportObject($var, int $indent): string $props[] = "{$name} => " . $this->export($property->getValue(), $indent); } } - if (count($props)) { + if ($props !== []) { return $out . $break . implode($break, $props) . $end; } diff --git a/app/vendor/cakephp/cakephp/src/Error/Debug/dumpHeader.html b/app/vendor/cakephp/cakephp/src/Error/Debug/dumpHeader.html index 501b2d0ad..82595f16e 100644 --- a/app/vendor/cakephp/cakephp/src/Error/Debug/dumpHeader.html +++ b/app/vendor/cakephp/cakephp/src/Error/Debug/dumpHeader.html @@ -1,275 +1,284 @@ - - diff --git a/app/vendor/cakephp/cakephp/src/Error/Debugger.php b/app/vendor/cakephp/cakephp/src/Error/Debugger.php index 3f2583cec..a8afa9693 100644 --- a/app/vendor/cakephp/cakephp/src/Error/Debugger.php +++ b/app/vendor/cakephp/cakephp/src/Error/Debugger.php @@ -17,6 +17,7 @@ namespace Cake\Error; use Cake\Core\Configure; +use Cake\Core\Exception\CakeException; use Cake\Core\InstanceConfigTrait; use Cake\Error\Debug\ArrayItemNode; use Cake\Error\Debug\ArrayNode; @@ -31,21 +32,15 @@ use Cake\Error\Debug\ScalarNode; use Cake\Error\Debug\SpecialNode; use Cake\Error\Debug\TextFormatter; -use Cake\Error\Renderer\HtmlErrorRenderer; -use Cake\Error\Renderer\TextErrorRenderer; use Cake\Log\Log; use Cake\Utility\Hash; use Cake\Utility\Security; -use Cake\Utility\Text; use Closure; use Exception; use InvalidArgumentException; use ReflectionObject; use ReflectionProperty; -use RuntimeException; use Throwable; -use function Cake\Core\deprecationWarning; -use function Cake\Core\getTypeName; use function Cake\Core\h; use function Cake\Core\pr; @@ -55,7 +50,7 @@ * Debugger extends PHP's default error handling and gives * simpler to use more powerful interfaces. * - * @link https://book.cakephp.org/4/en/development/debugging.html#namespace-Cake\Error + * @link https://book.cakephp.org/5/en/development/debugging.html#namespace-Cake\Error */ class Debugger { @@ -66,78 +61,18 @@ class Debugger * * @var array */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'outputMask' => [], 'exportFormatter' => null, 'editor' => 'phpstorm', ]; - /** - * The current output format. - * - * @var string - */ - protected $_outputFormat = 'js'; - - /** - * Templates used when generating trace or error strings. Can be global or indexed by the format - * value used in $_outputFormat. - * - * @var array> - */ - protected $_templates = [ - 'log' => [ - // These templates are not actually used, as Debugger::log() is called instead. - 'trace' => '{:reference} - {:path}, line {:line}', - 'error' => '{:error} ({:code}): {:description} in [{:file}, line {:line}]', - ], - 'js' => [ - 'error' => '', - 'info' => '', - 'trace' => '
{:trace}
', - 'code' => '', - 'context' => '', - 'links' => [], - 'escapeContext' => true, - ], - 'html' => [ - 'trace' => '
Trace 

{:trace}

', - 'context' => '
Context 

{:context}

', - 'escapeContext' => true, - ], - 'txt' => [ - 'error' => "{:error}: {:code} :: {:description} on line {:line} of {:path}\n{:info}", - 'code' => '', - 'info' => '', - ], - 'base' => [ - 'traceLine' => '{:reference} - {:path}, line {:line}', - 'trace' => "Trace:\n{:trace}\n", - 'context' => "Context:\n{:context}\n", - ], - ]; - - /** - * Mapping for error renderers. - * - * Error renderers are replacing output formatting with - * an object based system. Having Debugger handle and render errors - * will be deprecated and the new ErrorTrap system should be used instead. - * - * @var array - */ - protected $renderers = [ - 'txt' => TextErrorRenderer::class, - // The html alias currently uses no JS and will be deprecated. - 'js' => HtmlErrorRenderer::class, - ]; - /** * A map of editors to their link templates. * * @var array */ - protected $editors = [ + protected array $editors = [ 'atom' => 'atom://core/open/file?filename={file}&line={line}', 'emacs' => 'emacs://open?url=file://{file}&line={line}', 'macvim' => 'mvim://open/?url=file://{file}&line={line}', @@ -145,6 +80,7 @@ class Debugger 'sublime' => 'subl://open?url=file://{file}&line={line}', 'textmate' => 'txmt://open?url=file://{file}&line={line}', 'vscode' => 'vscode://file/{file}:{line}', + 'vscodium' => 'vscodium://file/{file}:{line}', ]; /** @@ -152,7 +88,7 @@ class Debugger * * @var array */ - protected $_data = []; + protected array $_data = []; /** * Constructor. @@ -160,7 +96,7 @@ class Debugger public function __construct() { $docRef = ini_get('docref_root'); - if (empty($docRef) && function_exists('ini_set')) { + if (!$docRef && function_exists('ini_set')) { ini_set('docref_root', 'https://secure.php.net/'); } if (!defined('E_RECOVERABLE_ERROR')) { @@ -169,67 +105,26 @@ public function __construct() $config = array_intersect_key((array)Configure::read('Debugger'), $this->_defaultConfig); $this->setConfig($config); - - $e = '
';
-        $e .= '{:error} ({:code}): {:description} ';
-        $e .= '[{:path}, line {:line}]';
-
-        $e .= '';
-        $e .= '
'; - $this->_templates['js']['error'] = $e; - - $t = ''; - $this->_templates['js']['info'] = $t; - - $links = []; - $link = 'Code'; - $links['code'] = $link; - - $link = 'Context'; - $links['context'] = $link; - - $this->_templates['js']['links'] = $links; - - $this->_templates['js']['context'] = '
_templates['js']['context'] .= 'style="display: none;">{:context}
'; - - $this->_templates['js']['code'] = '
_templates['js']['code'] .= 'style="display: none;">{:code}
'; - - $e = '
{:error} ({:code}) : {:description} ';
-        $e .= '[{:path}, line {:line}]
'; - $this->_templates['html']['error'] = $e; - - $this->_templates['html']['context'] = '
Context ';
-        $this->_templates['html']['context'] .= '

{:context}

'; } /** * Returns a reference to the Debugger singleton object instance. * - * @param string|null $class Class name. + * @param class-string<\Cake\Error\Debugger>|null $class Class name. * @return static */ - public static function getInstance(?string $class = null) + public static function getInstance(?string $class = null): static { + /** @var array $instance */ static $instance = []; - if (!empty($class)) { - if (!$instance || strtolower($class) !== strtolower(get_class($instance[0]))) { - $instance[0] = new $class(); - } + if ($class && (!$instance || strtolower($class) !== strtolower($instance[0]::class))) { + $instance[0] = new $class(); } if (!$instance) { $instance[0] = new Debugger(); } + /** @var static */ return $instance[0]; } @@ -242,7 +137,7 @@ public static function getInstance(?string $class = null) * @return mixed Config value being read, or the object itself on write operations. * @throws \Cake\Core\Exception\CakeException When trying to set a key that is invalid. */ - public static function configInstance($key = null, $value = null, bool $merge = true) + public static function configInstance(array|string|null $key = null, mixed $value = null, bool $merge = true): mixed { if ($key === null) { return static::getInstance()->getConfig($key); @@ -270,7 +165,7 @@ public static function outputMask(): array * * ### Example * - * Debugger::setOutputMask(['password' => '[*************]'); + * Debugger::setOutputMask(['password' => '[*************]']); * * @param array $value An array where keys are replaced by their values in output. * @param bool $merge Whether to recursively merge or overwrite existing config, defaults to true. @@ -292,13 +187,9 @@ public static function setOutputMask(array $value, bool $merge = true): void * @param \Closure|string $template The string template or closure * @return void */ - public static function addEditor(string $name, $template): void + public static function addEditor(string $name, Closure|string $template): void { $instance = static::getInstance(); - if (!is_string($template) && !($template instanceof Closure)) { - $type = getTypeName($template); - throw new RuntimeException("Invalid editor type of `{$type}`. Expected string or Closure."); - } $instance->editors[$name] = $template; } @@ -313,7 +204,11 @@ public static function setEditor(string $name): void $instance = static::getInstance(); if (!isset($instance->editors[$name])) { $known = implode(', ', array_keys($instance->editors)); - throw new RuntimeException("Unknown editor `{$name}`. Known editors are {$known}"); + throw new InvalidArgumentException(sprintf( + 'Unknown editor `%s`. Known editors are `%s`.', + $name, + $known, + )); } $instance->setConfig('editor', $name); } @@ -330,7 +225,10 @@ public static function editorUrl(string $file, int $line): string $instance = static::getInstance(); $editor = $instance->getConfig('editor'); if (!isset($instance->editors[$editor])) { - throw new RuntimeException("Cannot format editor URL `{$editor}` is not a known editor."); + throw new InvalidArgumentException(sprintf( + 'Cannot format editor URL `%s` is not a known editor.', + $editor, + )); } $template = $instance->editors[$editor]; @@ -348,9 +246,9 @@ public static function editorUrl(string $file, int $line): string * @param int $maxDepth The depth to output to. Defaults to 3. * @return void * @see \Cake\Error\Debugger::exportVar() - * @link https://book.cakephp.org/4/en/development/debugging.html#outputting-values + * @link https://book.cakephp.org/5/en/development/debugging.html#outputting-values */ - public static function dump($var, int $maxDepth = 3): void + public static function dump(mixed $var, int $maxDepth = 3): void { pr(static::exportVar($var, $maxDepth)); } @@ -364,7 +262,7 @@ public static function dump($var, int $maxDepth = 3): void * @param int $maxDepth The depth to output to. Defaults to 3. * @return void */ - public static function log($var, $level = 'debug', int $maxDepth = 3): void + public static function log(mixed $var, string|int $level = 'debug', int $maxDepth = 3): void { /** @var string $source */ $source = static::trace(['start' => 1]); @@ -372,7 +270,7 @@ public static function log($var, $level = 'debug', int $maxDepth = 3): void Log::write( $level, - "\n" . $source . static::exportVarAsPlainText($var, $maxDepth) + "\n" . $source . static::exportVarAsPlainText($var, $maxDepth), ); } @@ -380,7 +278,7 @@ public static function log($var, $level = 'debug', int $maxDepth = 3): void * Get the frames from $exception that are not present in $parent * * @param \Throwable $exception The exception to get frames from. - * @param ?\Throwable $parent The parent exception to compare frames with. + * @param \Throwable|null $parent The parent exception to compare frames with. * @return array An array of frame structures. */ public static function getUniqueFrames(Throwable $exception, ?Throwable $parent): array @@ -427,16 +325,16 @@ public static function getUniqueFrames(Throwable $exception, ?Throwable $parent) * * - `depth` - The number of stack frames to return. Defaults to 999 * - `format` - The format you want the return. Defaults to the currently selected format. If - * format is 'array' or 'points' the return will be an array. + * format is 'array', 'points', or 'shortPoints' the return will be an array. * - `args` - Should arguments for functions be shown? If true, the arguments for each method call * will be displayed. * - `start` - The stack frame to start generating a trace from. Defaults to 0 * * @param array $options Format for outputting stack trace. * @return array|string Formatted stack trace. - * @link https://book.cakephp.org/4/en/development/debugging.html#generating-stack-traces + * @link https://book.cakephp.org/5/en/development/debugging.html#generating-stack-traces */ - public static function trace(array $options = []) + public static function trace(array $options = []): array|string { // Remove the frame for Debugger::trace() $backtrace = debug_backtrace(); @@ -451,8 +349,8 @@ public static function trace(array $options = []) * ### Options * * - `depth` - The number of stack frames to return. Defaults to 999 - * - `format` - The format you want the return. Defaults to the currently selected format. If - * format is 'array' or 'points' the return will be an array. + * - `format` - The format you want the return. Defaults to 'text'. If + * format is 'array', 'points', or 'shortPoints' the return will be an array. * - `args` - Should arguments for functions be shown? If true, the arguments for each method call * will be displayed. * - `start` - The stack frame to start generating a trace from. Defaults to 0 @@ -460,21 +358,22 @@ public static function trace(array $options = []) * @param \Throwable|array $backtrace Trace as array or an exception object. * @param array $options Format for outputting stack trace. * @return array|string Formatted stack trace. - * @link https://book.cakephp.org/4/en/development/debugging.html#generating-stack-traces + * @link https://book.cakephp.org/5/en/development/debugging.html#generating-stack-traces */ - public static function formatTrace($backtrace, array $options = []) + public static function formatTrace(Throwable|array $backtrace, array $options = []): array|string { if ($backtrace instanceof Throwable) { $backtrace = $backtrace->getTrace(); } - $self = Debugger::getInstance(); + $defaults = [ 'depth' => 999, - 'format' => $self->_outputFormat, + 'format' => 'text', 'args' => false, 'start' => 0, 'scope' => null, 'exclude' => ['call_user_func_array', 'trigger_error'], + 'shortPath' => false, ]; $options = Hash::merge($defaults, $options); @@ -486,8 +385,8 @@ public static function formatTrace($backtrace, array $options = []) if (isset($backtrace[$i])) { $frame = $backtrace[$i] + ['file' => '[internal]', 'line' => '??']; } - - $signature = $reference = $frame['file']; + $signature = $frame['file']; + $reference = $frame['file']; if (!empty($frame['class'])) { $signature = $frame['class'] . $frame['type'] . $frame['function']; $reference = $signature . '('; @@ -503,31 +402,35 @@ public static function formatTrace($backtrace, array $options = []) if (in_array($signature, $options['exclude'], true)) { continue; } - if ($options['format'] === 'points') { + + $format = $options['format']; + if ($format === 'shortPoints') { + $back[] = [ + 'file' => self::trimPath($frame['file']), + 'line' => $frame['line'], + 'reference' => $reference, + ]; + } elseif ($format === 'points') { $back[] = ['file' => $frame['file'], 'line' => $frame['line'], 'reference' => $reference]; - } elseif ($options['format'] === 'array') { + } elseif ($format === 'array') { if (!$options['args']) { unset($frame['args']); } $back[] = $frame; + } elseif ($format === 'text') { + $path = static::trimPath($frame['file']); + $back[] = sprintf('%s - %s, line %d', $reference, $path, $frame['line']); } else { - $tpl = $self->_templates[$options['format']]['traceLine'] ?? $self->_templates['base']['traceLine']; - if ($frame['file'] == '[main]') { - $back[] = '[main]'; - } else { - $frame['path'] = static::trimPath($frame['file']); - $frame['reference'] = $reference; - unset($frame['object'], $frame['args']); - $back[] = Text::insert($tpl, $frame, ['before' => '{:', 'after' => '}']); - } + throw new InvalidArgumentException( + "Invalid trace format of `$format` chosen. Must be one of `array`, `points` or `text`.", + ); } } - if ($options['format'] === 'array' || $options['format'] === 'points') { + if (in_array($options['format'], ['array', 'points', 'shortPoints'])) { return $back; } /** - * @psalm-suppress InvalidArgument * @phpstan-ignore-next-line */ return implode("\n", $back); @@ -542,13 +445,13 @@ public static function formatTrace($backtrace, array $options = []) */ public static function trimPath(string $path): string { - if (defined('APP') && strpos($path, APP) === 0) { + if (defined('APP') && str_starts_with($path, APP)) { return str_replace(APP, 'APP/', $path); } - if (defined('CAKE_CORE_INCLUDE_PATH') && strpos($path, CAKE_CORE_INCLUDE_PATH) === 0) { + if (defined('CAKE_CORE_INCLUDE_PATH') && str_starts_with($path, CAKE_CORE_INCLUDE_PATH)) { return str_replace(CAKE_CORE_INCLUDE_PATH, 'CORE', $path); } - if (defined('ROOT') && strpos($path, ROOT) === 0) { + if (defined('ROOT') && str_starts_with($path, ROOT)) { return str_replace(ROOT, 'ROOT', $path); } @@ -574,7 +477,7 @@ public static function trimPath(string $path): string * @param int $context Number of lines of context to extract above and below $line. * @return array Set of lines highlighted * @see https://secure.php.net/highlight_string - * @link https://book.cakephp.org/4/en/development/debugging.html#getting-an-excerpt-from-a-file + * @link https://book.cakephp.org/5/en/development/debugging.html#getting-an-excerpt-from-a-file */ public static function excerpt(string $file, int $line, int $context = 2): array { @@ -583,10 +486,10 @@ public static function excerpt(string $file, int $line, int $context = 2): array return []; } $data = file_get_contents($file); - if (empty($data)) { + if (!$data) { return $lines; } - if (strpos($data, "\n") !== false) { + if (str_contains($data, "\n")) { $data = explode("\n", $data); } $line--; @@ -618,16 +521,16 @@ public static function excerpt(string $file, int $line, int $context = 2): array protected static function _highlight(string $str): string { $added = false; - if (strpos($str, '', '<?php 
', '<?php '], '', - $highlight + $highlight, ); } @@ -656,9 +559,11 @@ public function getExportFormatter(): FormatterInterface } $instance = new $class(); if (!$instance instanceof FormatterInterface) { - throw new RuntimeException( - "The `{$class}` formatter does not implement " . FormatterInterface::class - ); + throw new CakeException(sprintf( + 'The `%s` formatter does not implement `%s`.', + $class, + FormatterInterface::class, + )); } return $instance; @@ -685,7 +590,7 @@ public function getExportFormatter(): FormatterInterface * @param int $maxDepth The depth to output to. Defaults to 3. * @return string Variable as a formatted string */ - public static function exportVar($var, int $maxDepth = 3): string + public static function exportVar(mixed $var, int $maxDepth = 3): string { $context = new DebugContext($maxDepth); $node = static::export($var, $context); @@ -700,10 +605,10 @@ public static function exportVar($var, int $maxDepth = 3): string * @param int $maxDepth The depth to output to. Defaults to 3. * @return string Variable as a string */ - public static function exportVarAsPlainText($var, int $maxDepth = 3): string + public static function exportVarAsPlainText(mixed $var, int $maxDepth = 3): string { return (new TextFormatter())->dump( - static::export($var, new DebugContext($maxDepth)) + static::export($var, new DebugContext($maxDepth)), ); } @@ -717,7 +622,7 @@ public static function exportVarAsPlainText($var, int $maxDepth = 3): string * @param int $maxDepth The depth to generate nodes to. Defaults to 3. * @return \Cake\Error\Debug\NodeInterface The root node of the tree. */ - public static function exportVarAsNodes($var, int $maxDepth = 3): NodeInterface + public static function exportVarAsNodes(mixed $var, int $maxDepth = 3): NodeInterface { return static::export($var, new DebugContext($maxDepth)); } @@ -729,27 +634,22 @@ public static function exportVarAsNodes($var, int $maxDepth = 3): NodeInterface * @param \Cake\Error\Debug\DebugContext $context Dump context * @return \Cake\Error\Debug\NodeInterface The dumped variable. */ - protected static function export($var, DebugContext $context): NodeInterface + protected static function export(mixed $var, DebugContext $context): NodeInterface { $type = static::getType($var); - switch ($type) { - case 'float': - case 'string': - case 'resource': - case 'resource (closed)': - case 'null': - return new ScalarNode($type, $var); - case 'boolean': - return new ScalarNode('bool', $var); - case 'integer': - return new ScalarNode('int', $var); - case 'array': - return static::exportArray($var, $context->withAddedDepth()); - case 'unknown': - return new SpecialNode('(unknown)'); - default: - return static::exportObject($var, $context->withAddedDepth()); + + if (str_starts_with($type, 'resource ')) { + return new ScalarNode($type, $var); } + + return match ($type) { + 'float', 'string', 'null' => new ScalarNode($type, $var), + 'bool' => new ScalarNode('bool', $var), + 'int' => new ScalarNode('int', $var), + 'array' => static::exportArray($var, $context->withAddedDepth()), + 'unknown' => new SpecialNode('(unknown)'), + default => static::exportObject($var, $context->withAddedDepth()), + }; } /** @@ -791,7 +691,7 @@ protected static function exportArray(array $var, DebugContext $context): ArrayN } else { $items[] = new ArrayItemNode( new ScalarNode('string', ''), - new SpecialNode('[maximum depth reached]') + new SpecialNode('[maximum depth reached]'), ); } @@ -811,7 +711,7 @@ protected static function exportObject(object $var, DebugContext $context): Node $isRef = $context->hasReference($var); $refNum = $context->getReferenceId($var); - $className = get_class($var); + $className = $var::class; if ($isRef) { return new ReferenceNode($className, $refNum); } @@ -837,9 +737,8 @@ protected static function exportObject(object $var, DebugContext $context): Node if (array_key_exists($key, $outputMask)) { $value = $outputMask[$key]; } - /** @psalm-suppress RedundantCast */ $node->addProperty( - new PropertyNode((string)$key, 'public', static::export($value, $context->withAddedDepth())) + new PropertyNode((string)$key, 'public', static::export($value, $context->withAddedDepth())), ); } @@ -852,8 +751,6 @@ protected static function exportObject(object $var, DebugContext $context): Node foreach ($filters as $filter => $visibility) { $reflectionProperties = $ref->getProperties($filter); foreach ($reflectionProperties as $reflectionProperty) { - $reflectionProperty->setAccessible(true); - if ( method_exists($reflectionProperty, 'isInitialized') && !$reflectionProperty->isInitialized($var) @@ -866,8 +763,8 @@ protected static function exportObject(object $var, DebugContext $context): Node new PropertyNode( $reflectionProperty->getName(), $visibility, - $value - ) + $value, + ), ); } } @@ -876,221 +773,6 @@ protected static function exportObject(object $var, DebugContext $context): Node return $node; } - /** - * Get the output format for Debugger error rendering. - * - * @return string Returns the current format when getting. - * @deprecated 4.4.0 Update your application so use ErrorTrap instead. - */ - public static function getOutputFormat(): string - { - deprecationWarning('Debugger::getOutputFormat() is deprecated.'); - - return Debugger::getInstance()->_outputFormat; - } - - /** - * Set the output format for Debugger error rendering. - * - * @param string $format The format you want errors to be output as. - * @return void - * @throws \InvalidArgumentException When choosing a format that doesn't exist. - * @deprecated 4.4.0 Update your application so use ErrorTrap instead. - */ - public static function setOutputFormat(string $format): void - { - deprecationWarning('Debugger::setOutputFormat() is deprecated.'); - $self = Debugger::getInstance(); - - if (!isset($self->_templates[$format])) { - throw new InvalidArgumentException('Invalid Debugger output format.'); - } - $self->_outputFormat = $format; - } - - /** - * Add an output format or update a format in Debugger. - * - * ``` - * Debugger::addFormat('custom', $data); - * ``` - * - * Where $data is an array of strings that use Text::insert() variable - * replacement. The template vars should be in a `{:id}` style. - * An error formatter can have the following keys: - * - * - 'error' - Used for the container for the error message. Gets the following template - * variables: `id`, `error`, `code`, `description`, `path`, `line`, `links`, `info` - * - 'info' - A combination of `code`, `context` and `trace`. Will be set with - * the contents of the other template keys. - * - 'trace' - The container for a stack trace. Gets the following template - * variables: `trace` - * - 'context' - The container element for the context variables. - * Gets the following templates: `id`, `context` - * - 'links' - An array of HTML links that are used for creating links to other resources. - * Typically this is used to create javascript links to open other sections. - * Link keys, are: `code`, `context`, `help`. See the JS output format for an - * example. - * - 'traceLine' - Used for creating lines in the stacktrace. Gets the following - * template variables: `reference`, `path`, `line` - * - * Alternatively if you want to use a custom callback to do all the formatting, you can use - * the callback key, and provide a callable: - * - * ``` - * Debugger::addFormat('custom', ['callback' => [$foo, 'outputError']]; - * ``` - * - * The callback can expect two parameters. The first is an array of all - * the error data. The second contains the formatted strings generated using - * the other template strings. Keys like `info`, `links`, `code`, `context` and `trace` - * will be present depending on the other templates in the format type. - * - * @param string $format Format to use, including 'js' for JavaScript-enhanced HTML, 'html' for - * straight HTML output, or 'txt' for unformatted text. - * @param array $strings Template strings, or a callback to be used for the output format. - * @return array The resulting format string set. - * @deprecated 4.4.0 Update your application so use ErrorTrap instead. - */ - public static function addFormat(string $format, array $strings): array - { - deprecationWarning('Debugger::addFormat() is deprecated.'); - $self = Debugger::getInstance(); - if (isset($self->_templates[$format])) { - if (isset($strings['links'])) { - $self->_templates[$format]['links'] = array_merge( - $self->_templates[$format]['links'], - $strings['links'] - ); - unset($strings['links']); - } - $self->_templates[$format] = $strings + $self->_templates[$format]; - } else { - $self->_templates[$format] = $strings; - } - unset($self->renderers[$format]); - - return $self->_templates[$format]; - } - - /** - * Add a renderer to the current instance. - * - * @param string $name The alias for the the renderer. - * @param class-string<\Cake\Error\ErrorRendererInterface> $class The classname of the renderer to use. - * @return void - * @deprecated 4.4.0 Update your application so use ErrorTrap instead. - */ - public static function addRenderer(string $name, string $class): void - { - deprecationWarning('Debugger::addRenderer() is deprecated.'); - if (!in_array(ErrorRendererInterface::class, class_implements($class))) { - throw new InvalidArgumentException( - 'Invalid renderer class. $class must implement ' . ErrorRendererInterface::class - ); - } - $self = Debugger::getInstance(); - $self->renderers[$name] = $class; - } - - /** - * Takes a processed array of data from an error and displays it in the chosen format. - * - * @param array $data Data to output. - * @return void - * @deprecated 4.4.0 Update your application so use ErrorTrap instead. - */ - public function outputError(array $data): void - { - $defaults = [ - 'level' => 0, - 'error' => 0, - 'code' => 0, - 'description' => '', - 'file' => '', - 'line' => 0, - 'context' => [], - 'start' => 2, - ]; - $data += $defaults; - - $outputFormat = $this->_outputFormat; - if (isset($this->renderers[$outputFormat])) { - /** @var array $trace */ - $trace = static::trace(['start' => $data['start'], 'format' => 'points']); - $error = new PhpError($data['code'], $data['description'], $data['file'], $data['line'], $trace); - $renderer = new $this->renderers[$outputFormat](); - echo $renderer->render($error, Configure::read('debug')); - - return; - } - - $files = static::trace(['start' => $data['start'], 'format' => 'points']); - $code = ''; - $file = null; - if (isset($files[0]['file'])) { - $file = $files[0]; - } elseif (isset($files[1]['file'])) { - $file = $files[1]; - } - if ($file) { - $code = static::excerpt($file['file'], $file['line'], 1); - } - $trace = static::trace(['start' => $data['start'], 'depth' => '20']); - $insertOpts = ['before' => '{:', 'after' => '}']; - $context = []; - $links = []; - $info = ''; - - foreach ((array)$data['context'] as $var => $value) { - $context[] = "\${$var} = " . static::exportVar($value, 3); - } - - switch ($this->_outputFormat) { - case false: - $this->_data[] = compact('context', 'trace') + $data; - - return; - case 'log': - static::log(compact('context', 'trace') + $data); - - return; - } - - $data['trace'] = $trace; - $data['id'] = 'cakeErr' . uniqid(); - $tpl = $this->_templates[$outputFormat] + $this->_templates['base']; - - if (isset($tpl['links'])) { - foreach ($tpl['links'] as $key => $val) { - $links[$key] = Text::insert($val, $data, $insertOpts); - } - } - - if (!empty($tpl['escapeContext'])) { - $data['description'] = h($data['description']); - } - - $infoData = compact('code', 'context', 'trace'); - foreach ($infoData as $key => $value) { - if (empty($value) || !isset($tpl[$key])) { - continue; - } - if (is_array($value)) { - $value = implode("\n", $value); - } - $info .= Text::insert($tpl[$key], [$key => $value] + $data, $insertOpts); - } - $links = implode(' ', $links); - - if (isset($tpl['callback']) && is_callable($tpl['callback'])) { - $tpl['callback']($data, compact('links', 'info')); - - return; - } - echo Text::insert($tpl['error'], compact('links', 'info') + $data, $insertOpts); - } - /** * Get the type of the given variable. Will return the class name * for objects. @@ -1098,13 +780,9 @@ public function outputError(array $data): void * @param mixed $var The variable to get the type of. * @return string The type of variable. */ - public static function getType($var): string + public static function getType(mixed $var): string { - $type = getTypeName($var); - - if ($type === 'NULL') { - return 'null'; - } + $type = get_debug_type($var); if ($type === 'double') { return 'float'; @@ -1129,7 +807,7 @@ public static function getType($var): string * environment conditions. * @return void */ - public static function printVar($var, array $location = [], ?bool $showHtml = null): void + public static function printVar(mixed $var, array $location = [], ?bool $showHtml = null): void { $location += ['file' => null, 'line' => null]; if ($location['file']) { @@ -1166,7 +844,7 @@ public static function printVar($var, array $location = [], ?bool $showHtml = nu public static function formatHtmlMessage(string $message): string { $message = h($message); - $message = preg_replace('/`([^`]+)`/', '$1', $message); + $message = (string)preg_replace('/`([^`]+)`/', '$0', $message); return nl2br($message); } @@ -1183,7 +861,7 @@ public static function checkSecurityKeys(): void trigger_error( 'Please change the value of `Security.salt` in `ROOT/config/app_local.php` ' . 'to a random value of at least 32 characters.', - E_USER_NOTICE + E_USER_NOTICE, ); } } diff --git a/app/vendor/cakephp/cakephp/src/Error/ErrorHandler.php b/app/vendor/cakephp/cakephp/src/Error/ErrorHandler.php deleted file mode 100644 index f69193f3f..000000000 --- a/app/vendor/cakephp/cakephp/src/Error/ErrorHandler.php +++ /dev/null @@ -1,216 +0,0 @@ - $config The options for error handling. - */ - public function __construct(array $config = []) - { - $config += [ - 'exceptionRenderer' => ExceptionRenderer::class, - ]; - - $this->setConfig($config); - } - - /** - * Display an error. - * - * Template method of BaseErrorHandler. - * - * @param array $error An array of error data. - * @param bool $debug Whether the app is in debug mode. - * @return void - */ - protected function _displayError(array $error, bool $debug): void - { - if (!$debug) { - return; - } - Debugger::getInstance()->outputError($error); - } - - /** - * Displays an exception response body. - * - * @param \Throwable $exception The exception to display. - * @return void - * @throws \Exception When the chosen exception renderer is invalid. - */ - protected function _displayException(Throwable $exception): void - { - try { - $renderer = $this->getRenderer( - $exception, - Router::getRequest() - ); - $response = $renderer->render(); - $this->_sendResponse($response); - } catch (Throwable $exception) { - $this->_logInternalError($exception); - } - } - - /** - * Get a renderer instance. - * - * @param \Throwable $exception The exception being rendered. - * @param \Psr\Http\Message\ServerRequestInterface|null $request The request. - * @return \Cake\Error\ExceptionRendererInterface The exception renderer. - * @throws \RuntimeException When the renderer class cannot be found. - */ - public function getRenderer( - Throwable $exception, - ?ServerRequestInterface $request = null - ): ExceptionRendererInterface { - $renderer = $this->_config['exceptionRenderer']; - - if (is_string($renderer)) { - /** @var class-string<\Cake\Error\ExceptionRendererInterface>|null $class */ - $class = App::className($renderer, 'Error'); - if (!$class) { - throw new RuntimeException(sprintf( - "The '%s' renderer class could not be found.", - $renderer - )); - } - - return new $class($exception, $request); - } - - /** @var callable $factory */ - $factory = $renderer; - - return $factory($exception, $request); - } - - /** - * Log internal errors. - * - * @param \Throwable $exception Exception. - * @return void - */ - protected function _logInternalError(Throwable $exception): void - { - // Disable trace for internal errors. - $this->_config['trace'] = false; - $message = sprintf( - "[%s] %s (%s:%s)\n%s", // Keeping same message format - get_class($exception), - $exception->getMessage(), - $exception->getFile(), - $exception->getLine(), - $exception->getTraceAsString() - ); - trigger_error($message, E_USER_ERROR); - } - - /** - * Method that can be easily stubbed in testing. - * - * @param \Psr\Http\Message\ResponseInterface|string $response Either the message or response object. - * @return void - */ - protected function _sendResponse($response): void - { - if (is_string($response)) { - echo $response; - - return; - } - - $emitter = new ResponseEmitter(); - $emitter->emit($response); - } -} diff --git a/app/vendor/cakephp/cakephp/src/Error/ErrorLogger.php b/app/vendor/cakephp/cakephp/src/Error/ErrorLogger.php index 4e91d8b90..b5e92b6b2 100644 --- a/app/vendor/cakephp/cakephp/src/Error/ErrorLogger.php +++ b/app/vendor/cakephp/cakephp/src/Error/ErrorLogger.php @@ -19,8 +19,11 @@ use Cake\Core\Configure; use Cake\Core\Exception\CakeException; use Cake\Core\InstanceConfigTrait; +use Cake\Http\ServerRequest; use Cake\Log\Log; use Psr\Http\Message\ServerRequestInterface; +use Psr\Log\LoggerTrait; +use Stringable; use Throwable; /** @@ -29,6 +32,7 @@ class ErrorLogger implements ErrorLoggerInterface { use InstanceConfigTrait; + use LoggerTrait; /** * Default configuration values. @@ -37,7 +41,7 @@ class ErrorLogger implements ErrorLoggerInterface * * @var array */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'trace' => false, ]; @@ -52,94 +56,73 @@ public function __construct(array $config = []) } /** - * Log an error to Cake's Log subsystem - * - * @param \Cake\Error\PhpError $error The error to log - * @param ?\Psr\Http\Message\ServerRequestInterface $request The request if in an HTTP context. - * @param bool $includeTrace Should the log message include a stacktrace - * @return void + * @inheritDoc */ - public function logError(PhpError $error, ?ServerRequestInterface $request = null, bool $includeTrace = false): void + public function log($level, Stringable|string $message, array $context = []): void { - $message = $error->getMessage(); - if ($request) { - $message .= $this->getRequestContext($request); - } - if ($includeTrace) { - $message .= "\nTrace:\n" . $error->getTraceAsString() . "\n"; - } - $logMap = [ - 'strict' => LOG_NOTICE, - 'deprecated' => LOG_NOTICE, - ]; - $level = $error->getLabel(); - $level = $logMap[$level] ?? $level; - - Log::write($level, $message); + Log::write($level, $message, $context); } /** - * Log an exception to Cake's Log subsystem - * - * @param \Throwable $exception The exception to log a message for. - * @param \Psr\Http\Message\ServerRequestInterface|null $request The current request if available. - * @param bool $includeTrace Whether or not a stack trace should be logged. - * @return void + * @inheritDoc */ - public function logException( - Throwable $exception, - ?ServerRequestInterface $request = null, - bool $includeTrace = false - ): void { - $message = $this->getMessage($exception, false, $includeTrace); + public function logError(PhpError $error, ?ServerRequestInterface $request = null, bool $includeTrace = false): void + { + $message = $this->getErrorMessage($error, $includeTrace); - if ($request !== null) { + if ($request instanceof ServerRequestInterface) { $message .= $this->getRequestContext($request); } - Log::error($message); + + $label = $error->getLabel(); + $level = match ($label) { + 'strict' => LOG_NOTICE, + 'deprecated' => LOG_DEBUG, + default => $label, + }; + + $this->log($level, $message); } /** - * @param string|int $level The logging level - * @param string $message The message to be logged. - * @param array $context Context. - * @return bool - * @deprecated 4.4.0 Use logError instead. + * Generate the message for the error + * + * @param \Cake\Error\PhpError $error The exception to log a message for. + * @param bool $includeTrace Whether to include a stack trace. + * @return string Error message */ - public function logMessage($level, string $message, array $context = []): bool + protected function getErrorMessage(PhpError $error, bool $includeTrace = false): string { - if (!empty($context['request'])) { - $message .= $this->getRequestContext($context['request']); - } - if (!empty($context['trace'])) { - $message .= "\nTrace:\n" . $context['trace'] . "\n"; + $message = sprintf( + '%s in %s on line %s', + $error->getMessage(), + $error->getFile(), + $error->getLine(), + ); + + if (!$includeTrace) { + return $message; } - $logMap = [ - 'strict' => LOG_NOTICE, - 'deprecated' => LOG_NOTICE, - ]; - $level = $logMap[$level] ?? $level; - return Log::write($level, $message); + $message .= "\nTrace:\n" . $error->getTraceAsString() . "\n"; + + return $message; } /** - * @param \Throwable $exception The exception to log a message for. - * @param \Psr\Http\Message\ServerRequestInterface|null $request The current request if available. - * @return bool - * @deprecated 4.4.0 Use logException instead. + * @inheritDoc */ - public function log(Throwable $exception, ?ServerRequestInterface $request = null): bool - { - $message = $this->getMessage($exception, false, $this->getConfig('trace')); + public function logException( + Throwable $exception, + ?ServerRequestInterface $request = null, + bool $includeTrace = false, + ): void { + $message = $this->getMessage($exception, false, $includeTrace); if ($request !== null) { $message .= $this->getRequestContext($request); } - - $message .= "\n\n"; - - return Log::error($message); + $this->error($message); } /** @@ -147,7 +130,7 @@ public function log(Throwable $exception, ?ServerRequestInterface $request = nul * * @param \Throwable $exception The exception to log a message for. * @param bool $isPrevious False for original exception, true for previous - * @param bool $includeTrace Whether or not to include a stack trace. + * @param bool $includeTrace Whether to include a stack trace. * @return string Error message */ protected function getMessage(Throwable $exception, bool $isPrevious = false, bool $includeTrace = false): string @@ -155,10 +138,10 @@ protected function getMessage(Throwable $exception, bool $isPrevious = false, bo $message = sprintf( '%s[%s] %s in %s on line %s', $isPrevious ? "\nCaused by: " : '', - get_class($exception), + $exception::class, $exception->getMessage(), $exception->getFile(), - $exception->getLine() + $exception->getLine(), ); $debug = Configure::read('debug'); @@ -170,8 +153,8 @@ protected function getMessage(Throwable $exception, bool $isPrevious = false, bo } if ($includeTrace) { - /** @var array $trace */ - $trace = Debugger::formatTrace($exception, ['format' => 'points']); + $trace = Debugger::formatTrace($exception, ['format' => 'shortPoints']); + assert(is_array($trace)); $message .= "\nStack Trace:\n"; foreach ($trace as $line) { if (is_string($line)) { @@ -205,7 +188,7 @@ public function getRequestContext(ServerRequestInterface $request): string $message .= "\nReferer URL: " . $referer; } - if (method_exists($request, 'clientIp')) { + if ($request instanceof ServerRequest) { $clientIp = $request->clientIp(); if ($clientIp && $clientIp !== '::1') { $message .= "\nClient IP: " . $clientIp; diff --git a/app/vendor/cakephp/cakephp/src/Error/ErrorLoggerInterface.php b/app/vendor/cakephp/cakephp/src/Error/ErrorLoggerInterface.php index 5a2626b24..bba6907f4 100644 --- a/app/vendor/cakephp/cakephp/src/Error/ErrorLoggerInterface.php +++ b/app/vendor/cakephp/cakephp/src/Error/ErrorLoggerInterface.php @@ -22,13 +22,7 @@ /** * Interface for error logging handlers. * - * Used by the ErrorHandlerMiddleware and global - * error handlers to log exceptions and errors. - * - * @method void logException(\Throwable $exception, ?\Psr\Http\Message\ServerRequestInterface $request = null, bool $includeTrace = false) - * Log an exception with an optional HTTP request. - * @method void logError(\Cake\Error\PhpError $error, ?\Psr\Http\Message\ServerRequestInterface $request = null, bool $includeTrace = false) - * Log an error with an optional HTTP request. + * Used by the ErrorHandlerMiddleware and global error handlers to log exceptions and errors. */ interface ErrorLoggerInterface { @@ -37,22 +31,26 @@ interface ErrorLoggerInterface * * @param \Throwable $exception The exception to log a message for. * @param \Psr\Http\Message\ServerRequestInterface|null $request The current request if available. - * @return bool - * @deprecated 4.4.0 Implement `logException` instead. + * @param bool $includeTrace Should the log message include a stacktrace. + * @return void */ - public function log( + public function logException( Throwable $exception, - ?ServerRequestInterface $request = null - ): bool; + ?ServerRequestInterface $request = null, + bool $includeTrace = false, + ): void; /** - * Log a an error message to the error logger. + * Log an error to Cake's Log subsystem * - * @param string|int $level The logging level - * @param string $message The message to be logged. - * @param array $context Context. - * @return bool - * @deprecated 4.4.0 Implement `logError` instead. + * @param \Cake\Error\PhpError $error The error to log. + * @param \Psr\Http\Message\ServerRequestInterface|null $request The request if in an HTTP context. + * @param bool $includeTrace Should the log message include a stacktrace. + * @return void */ - public function logMessage($level, string $message, array $context = []): bool; + public function logError( + PhpError $error, + ?ServerRequestInterface $request = null, + bool $includeTrace = false, + ): void; } diff --git a/app/vendor/cakephp/cakephp/src/Error/ErrorRendererInterface.php b/app/vendor/cakephp/cakephp/src/Error/ErrorRendererInterface.php index c0829126b..a180798bf 100644 --- a/app/vendor/cakephp/cakephp/src/Error/ErrorRendererInterface.php +++ b/app/vendor/cakephp/cakephp/src/Error/ErrorRendererInterface.php @@ -28,7 +28,7 @@ interface ErrorRendererInterface * Render output for the provided error. * * @param \Cake\Error\PhpError $error The error to be rendered. - * @param bool $debug Whether or not the application is in debug mode. + * @param bool $debug Whether the application is in debug mode. * @return string The output to be echoed. */ public function render(PhpError $error, bool $debug): string; diff --git a/app/vendor/cakephp/cakephp/src/Error/ErrorTrap.php b/app/vendor/cakephp/cakephp/src/Error/ErrorTrap.php index ab2f54d04..1ae8d83e2 100644 --- a/app/vendor/cakephp/cakephp/src/Error/ErrorTrap.php +++ b/app/vendor/cakephp/cakephp/src/Error/ErrorTrap.php @@ -10,7 +10,6 @@ use Cake\Event\EventDispatcherTrait; use Cake\Routing\Router; use Exception; -use function Cake\Core\deprecationWarning; /** * Entry point to CakePHP's error handling. @@ -23,6 +22,9 @@ */ class ErrorTrap { + /** + * @use \Cake\Event\EventDispatcherTrait<\Cake\Error\ErrorTrap> + */ use EventDispatcherTrait; use InstanceConfigTrait; @@ -32,14 +34,14 @@ class ErrorTrap * - `errorLevel` - int - The level of errors you are interested in capturing. * - `errorRenderer` - string - The class name of render errors with. Defaults * to choosing between Html and Console based on the SAPI. - * - `log` - boolean - Whether or not you want errors logged. + * - `log` - boolean - Whether you want errors logged. * - `logger` - string - The class name of the error logger to use. - * - `trace` - boolean - Whether or not backtraces should be included in + * - `trace` - boolean - Whether backtraces should be included in * logged errors. * * @var array */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'errorLevel' => E_ALL, 'errorRenderer' => null, 'log' => true, @@ -88,7 +90,7 @@ public function register(): void { $level = $this->_config['errorLevel'] ?? -1; error_reporting($level); - set_error_handler([$this, 'handleError'], $level); + set_error_handler($this->handleError(...), $level); } /** @@ -110,7 +112,7 @@ public function handleError( int $code, string $description, ?string $file = null, - ?int $line = null + ?int $line = null, ): bool { if (!(error_reporting() & $code)) { return false; @@ -119,8 +121,7 @@ public function handleError( throw new FatalErrorException($description, $code, $file, $line); } - /** @var array $trace */ - $trace = Debugger::trace(['start' => 1, 'format' => 'points']); + $trace = (array)Debugger::trace(['start' => 0, 'format' => 'points']); $error = new PhpError($code, $description, $file, $line, $trace); $ignoredPaths = (array)Configure::read('Error.ignoredDeprecationPaths'); @@ -138,7 +139,7 @@ public function handleError( $renderer = $this->renderer(); try { - // Log first incase rendering or event listeners fail + // Log first in case rendering or event listeners fail $this->logError($error); $event = $this->dispatchEvent('Error.beforeRender', ['error' => $error]); if ($event->isStopped()) { @@ -147,7 +148,7 @@ public function handleError( $renderer->write($event->getResult() ?: $renderer->render($error, $debug)); } catch (Exception $e) { // Fatal errors always log. - $this->logger()->logMessage('error', 'Could not render error. Got: ' . $e->getMessage()); + $this->logger()->logException($e); return false; } @@ -166,24 +167,7 @@ protected function logError(PhpError $error): void if (!$this->_config['log']) { return; } - $logger = $this->logger(); - if (method_exists($logger, 'logError')) { - $logger->logError($error, Router::getRequest(), $this->_config['trace']); - } else { - $loggerClass = get_class($logger); - deprecationWarning( - "The configured logger `{$loggerClass}` does not implement `logError()` " . - 'which will be required in future versions of CakePHP.' - ); - $context = []; - if ($this->_config['trace']) { - $context = [ - 'trace' => $error->getTraceAsString(), - 'request' => Router::getRequest(), - ]; - } - $logger->logMessage($error->getLabel(), $error->getMessage(), $context); - } + $this->logger()->logError($error, Router::getRequest(), $this->_config['trace']); } /** @@ -206,12 +190,6 @@ public function renderer(): ErrorRendererInterface */ public function logger(): ErrorLoggerInterface { - $oldConfig = $this->getConfig('errorLogger'); - if ($oldConfig !== null) { - deprecationWarning('The `errorLogger` configuration key is deprecated. Use `logger` instead.'); - $this->setConfig(['logger' => $oldConfig, 'errorLogger' => null]); - } - /** @var class-string<\Cake\Error\ErrorLoggerInterface> $class */ $class = $this->getConfig('logger', $this->_defaultConfig['logger']); diff --git a/app/vendor/cakephp/cakephp/src/Error/ExceptionRenderer.php b/app/vendor/cakephp/cakephp/src/Error/ExceptionRenderer.php deleted file mode 100644 index c4dab58a6..000000000 --- a/app/vendor/cakephp/cakephp/src/Error/ExceptionRenderer.php +++ /dev/null @@ -1,28 +0,0 @@ - + */ use EventDispatcherTrait; use InstanceConfigTrait; @@ -43,7 +46,7 @@ class ExceptionTrap * implement the `render()` method and return either a string or Http\Response. * - `log` Set to false to disable logging. * - `logger` - string - The class name of the error logger to use. - * - `trace` - boolean - Whether or not backtraces should be included in + * - `trace` - boolean - Whether backtraces should be included in * logged exceptions. * - `skipLog` - array - List of exceptions to skip for logging. Exceptions that * extend one of the listed exceptions will also not be logged. E.g.: @@ -57,7 +60,7 @@ class ExceptionTrap * * @var array */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'exceptionRenderer' => null, 'logger' => ErrorLogger::class, 'stderr' => null, @@ -75,7 +78,7 @@ class ExceptionTrap * * @var array<\Closure> */ - protected $callbacks = []; + protected array $callbacks = []; /** * The currently registered global exception handler @@ -85,14 +88,14 @@ class ExceptionTrap * * @var \Cake\Error\ExceptionTrap|null */ - protected static $registeredTrap = null; + protected static ?ExceptionTrap $registeredTrap = null; /** * Track if this trap was removed from the global handler. * * @var bool */ - protected $disabled = false; + protected bool $disabled = false; /** * Constructor @@ -111,34 +114,18 @@ public function __construct(array $options = []) * @param \Psr\Http\Message\ServerRequestInterface|null $request The request if possible. * @return \Cake\Error\ExceptionRendererInterface */ - public function renderer(Throwable $exception, $request = null) + public function renderer(Throwable $exception, ?ServerRequestInterface $request = null): ExceptionRendererInterface { - $request = $request ?? Router::getRequest(); + $request ??= Router::getRequest(); - /** @var class-string|callable $class */ - $class = $this->getConfig('exceptionRenderer'); - $deprecatedConfig = ($class === ExceptionRenderer::class && PHP_SAPI === 'cli'); - if ($deprecatedConfig) { - deprecationWarning( - 'Your application is using a deprecated `Error.exceptionRenderer`. ' . - 'You can either remove the `Error.exceptionRenderer` config key to have CakePHP choose ' . - 'one of the default exception renderers, or define a class that is not `Cake\Error\ExceptionRenderer`.' - ); - } - if (!$class || $deprecatedConfig) { - // Default to detecting the exception renderer if we're - // in a CLI context and the Web renderer is currently selected. - // This indicates old configuration or user error, in both scenarios - // it is preferrable to use the Console renderer instead. - $class = $this->chooseRenderer(); - } + /** @var callable|class-string $class */ + $class = $this->getConfig('exceptionRenderer') ?: $this->chooseRenderer(); if (is_string($class)) { - /** @psalm-suppress ArgumentTypeCoercion */ - if (!(method_exists($class, 'render') && method_exists($class, 'write'))) { + if (!is_subclass_of($class, ExceptionRendererInterface::class)) { throw new InvalidArgumentException( - "Cannot use {$class} as an `exceptionRenderer`. " . - 'It must implement render() and write() methods.' + "Cannot use `{$class}` as an `exceptionRenderer`. " . + 'It must be an instance of `Cake\Error\ExceptionRendererInterface`.', ); } @@ -157,7 +144,7 @@ public function renderer(Throwable $exception, $request = null) protected function chooseRenderer(): string { /** @var class-string<\Cake\Error\ExceptionRendererInterface> */ - return PHP_SAPI === 'cli' ? ConsoleExceptionRenderer::class : ExceptionRenderer::class; + return PHP_SAPI === 'cli' ? ConsoleExceptionRenderer::class : WebExceptionRenderer::class; } /** @@ -183,9 +170,11 @@ public function logger(): ErrorLoggerInterface */ public function register(): void { - set_exception_handler([$this, 'handleException']); - register_shutdown_function([$this, 'handleShutdown']); + set_exception_handler($this->handleException(...)); + register_shutdown_function($this->handleShutdown(...)); static::$registeredTrap = $this; + + ini_set('assert.exception', '1'); } /** @@ -201,6 +190,7 @@ public function unregister(): void if (static::$registeredTrap == $this) { $this->disabled = true; static::$registeredTrap = null; + restore_exception_handler(); } } @@ -252,7 +242,7 @@ public function handleException(Throwable $exception): void $this->logInternalError($exception); } // Use this constant as a proxy for cakephp tests. - if (PHP_SAPI == 'cli' && !env('FIXTURE_SCHEMA_METADATA')) { + if (PHP_SAPI === 'cli' && !env('FIXTURE_SCHEMA_METADATA')) { exit(1); } } @@ -289,7 +279,7 @@ public function handleShutdown(): void $error['type'], $error['message'], $error['file'], - $error['line'] + $error['line'], ); } @@ -362,24 +352,14 @@ public function logException(Throwable $exception, ?ServerRequestInterface $requ } } if ($shouldLog) { - $logger = $this->logger(); - if (method_exists($logger, 'logException')) { - $logger->logException($exception, $request, $this->_config['trace']); - } else { - $loggerClass = get_class($logger); - deprecationWarning( - "The configured logger `{$loggerClass}` should implement `logException()` " . - 'to be compatible with future versions of CakePHP.' - ); - $this->logger()->log($exception, $request); - } + $this->logger()->logException($exception, $request, $this->_config['trace']); } } /** * Trigger an error that occurred during rendering an exception. * - * By triggering an E_USER_ERROR we can end up in the default + * By triggering an E_USER_WARNING we can end up in the default * exception handling which will log the rendering failure, * and hopefully render an error page. * @@ -390,11 +370,11 @@ public function logInternalError(Throwable $exception): void { $message = sprintf( '[%s] %s (%s:%s)', // Keeping same message format - get_class($exception), + $exception::class, $exception->getMessage(), $exception->getFile(), $exception->getLine(), ); - trigger_error($message, E_USER_ERROR); + trigger_error($message, E_USER_WARNING); } } diff --git a/app/vendor/cakephp/cakephp/src/Error/FatalErrorException.php b/app/vendor/cakephp/cakephp/src/Error/FatalErrorException.php index 42f751850..fa4c754b6 100644 --- a/app/vendor/cakephp/cakephp/src/Error/FatalErrorException.php +++ b/app/vendor/cakephp/cakephp/src/Error/FatalErrorException.php @@ -36,7 +36,7 @@ public function __construct( ?int $code = null, ?string $file = null, ?int $line = null, - ?Throwable $previous = null + ?Throwable $previous = null, ) { parent::__construct($message, $code, $previous); if ($file) { diff --git a/app/vendor/cakephp/cakephp/src/Error/Middleware/ErrorHandlerMiddleware.php b/app/vendor/cakephp/cakephp/src/Error/Middleware/ErrorHandlerMiddleware.php index b39ecc3f9..ddf3ba19a 100644 --- a/app/vendor/cakephp/cakephp/src/Error/Middleware/ErrorHandlerMiddleware.php +++ b/app/vendor/cakephp/cakephp/src/Error/Middleware/ErrorHandlerMiddleware.php @@ -20,7 +20,6 @@ use Cake\Core\Configure; use Cake\Core\InstanceConfigTrait; use Cake\Core\PluginApplicationInterface; -use Cake\Error\ErrorHandler; use Cake\Error\ExceptionTrap; use Cake\Error\Renderer\WebExceptionRenderer; use Cake\Event\EventDispatcherTrait; @@ -28,15 +27,12 @@ use Cake\Http\Response; use Cake\Routing\Router; use Cake\Routing\RoutingApplicationInterface; -use InvalidArgumentException; use Laminas\Diactoros\Response\RedirectResponse; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; use Throwable; -use function Cake\Core\deprecationWarning; -use function Cake\Core\getTypeName; use function Cake\Core\triggerWarning; /** @@ -48,6 +44,10 @@ class ErrorHandlerMiddleware implements MiddlewareInterface { use InstanceConfigTrait; + + /** + * @use \Cake\Event\EventDispatcherTrait<\Cake\Error\ExceptionTrap> + */ use EventDispatcherTrait; /** @@ -62,78 +62,44 @@ class ErrorHandlerMiddleware implements MiddlewareInterface * @var array * @see \Cake\Error\ExceptionTrap */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'exceptionRenderer' => WebExceptionRenderer::class, ]; - /** - * Error handler instance. - * - * @var \Cake\Error\ErrorHandler|null - */ - protected $errorHandler = null; - /** * ExceptionTrap instance * * @var \Cake\Error\ExceptionTrap|null */ - protected $exceptionTrap = null; + protected ?ExceptionTrap $exceptionTrap = null; /** * @var \Cake\Routing\RoutingApplicationInterface|null */ - protected $app = null; + protected ?RoutingApplicationInterface $app = null; /** * Constructor * - * @param \Cake\Error\ErrorHandler|\Cake\Error\ExceptionTrap|array $errorHandler The error handler instance + * @param \Cake\Error\ExceptionTrap|array $config The error handler instance * or config array. * @param \Cake\Routing\RoutingApplicationInterface|null $app Application instance. - * @throws \InvalidArgumentException */ - public function __construct($errorHandler = [], $app = null) + public function __construct(ExceptionTrap|array $config = [], ?RoutingApplicationInterface $app = null) { - if (func_num_args() > 1) { - if (is_array($app)) { - deprecationWarning( - 'The signature of ErrorHandlerMiddleware::__construct() has changed. ' - . 'Pass the config array as 1st argument instead.' - ); + $this->app = $app; - $errorHandler = func_get_arg(1); - } else { - $this->app = $app; - } - } - - if (PHP_VERSION_ID >= 70400 && Configure::read('debug')) { + if (Configure::read('debug')) { ini_set('zend.exception_ignore_args', '0'); } - if (is_array($errorHandler)) { - $this->setConfig($errorHandler); - - return; - } - if ($errorHandler instanceof ErrorHandler) { - deprecationWarning( - 'Using an `ErrorHandler` is deprecated. You should migate to the `ExceptionTrap` sub-system instead.' - ); - $this->errorHandler = $errorHandler; + if (is_array($config)) { + $this->setConfig($config); return; } - if ($errorHandler instanceof ExceptionTrap) { - $this->exceptionTrap = $errorHandler; - return; - } - throw new InvalidArgumentException(sprintf( - '$errorHandler argument must be a config array or ExceptionTrap instance. Got `%s` instead.', - getTypeName($errorHandler) - )); + $this->exceptionTrap = $config; } /** @@ -150,7 +116,7 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface } catch (RedirectException $exception) { return $this->handleRedirect($exception); } catch (Throwable $exception) { - return $this->handleException($exception, $request); + return $this->handleException($exception, Router::getRequest() ?? $request); } } @@ -165,39 +131,29 @@ public function handleException(Throwable $exception, ServerRequestInterface $re { $this->loadRoutes(); - $response = null; - if ($this->errorHandler === null) { - $handler = $this->getExceptionTrap(); - $handler->logException($exception, $request); - - $event = $this->dispatchEvent( - 'Exception.beforeRender', - ['exception' => $exception, 'request' => $request], - $handler - ); - - $exception = $event->getData('exception'); - assert($exception instanceof Throwable); - $renderer = $handler->renderer($exception, $request); + $trap = $this->getExceptionTrap(); + $trap->logException($exception, $request); - $response = $event->getResult(); - } else { - $handler = $this->getErrorHandler(); - $handler->logException($exception, $request); + $event = $this->dispatchEvent( + 'Exception.beforeRender', + ['exception' => $exception, 'request' => $request], + $trap, + ); - $renderer = $handler->getRenderer($exception, $request); + $response = $event->getResult(); + if ($response === null) { + $renderer = $trap->renderer($event->getData('exception'), $request); } try { - if ($response === null) { - $response = $renderer->render(); + $response ??= $renderer->render(); + if (is_string($response)) { + return new Response(['body' => $response, 'status' => 500]); } - return $response instanceof ResponseInterface - ? $response - : new Response(['body' => $response, 'status' => 500]); + return $response; } catch (Throwable $internalException) { - $handler->logException($internalException, $request); + $trap->logException($internalException, $request); return $this->handleInternalError(); } @@ -214,7 +170,7 @@ public function handleRedirect(RedirectException $exception): ResponseInterface return new RedirectResponse( $exception->getMessage(), $exception->getCode(), - $exception->getHeaders() + $exception->getHeaders(), ); } @@ -231,22 +187,6 @@ protected function handleInternalError(): ResponseInterface ]); } - /** - * Get a error handler instance - * - * @return \Cake\Error\ErrorHandler The error handler. - */ - protected function getErrorHandler(): ErrorHandler - { - if ($this->errorHandler === null) { - /** @var class-string<\Cake\Error\ErrorHandler> $className */ - $className = App::className('ErrorHandler', 'Error'); - $this->errorHandler = new $className($this->getConfig()); - } - - return $this->errorHandler; - } - /** * Get a exception trap instance * @@ -287,8 +227,8 @@ protected function loadRoutes(): void } catch (Throwable $e) { triggerWarning(sprintf( "Exception loading routes when rendering an error page: \n %s - %s", - get_class($e), - $e->getMessage() + $e::class, + $e->getMessage(), )); } } diff --git a/app/vendor/cakephp/cakephp/src/Error/PhpError.php b/app/vendor/cakephp/cakephp/src/Error/PhpError.php index 0b9944edb..ff91fae12 100644 --- a/app/vendor/cakephp/cakephp/src/Error/PhpError.php +++ b/app/vendor/cakephp/cakephp/src/Error/PhpError.php @@ -24,34 +24,34 @@ class PhpError /** * @var int */ - private $code; + private int $code; /** * @var string */ - private $message; + private string $message; /** * @var string|null */ - private $file; + private ?string $file; /** * @var int|null */ - private $line; + private ?int $line; /** * Stack trace data. Each item should have a `reference`, `file` and `line` keys. * * @var array> */ - private $trace; + private array $trace; /** * @var array */ - private $levelMap = [ + private array $levelMap = [ E_PARSE => 'error', E_ERROR => 'error', E_CORE_ERROR => 'error', @@ -63,7 +63,6 @@ class PhpError E_RECOVERABLE_ERROR => 'warning', E_NOTICE => 'notice', E_USER_NOTICE => 'notice', - E_STRICT => 'strict', E_DEPRECATED => 'deprecated', E_USER_DEPRECATED => 'deprecated', ]; @@ -71,7 +70,7 @@ class PhpError /** * @var array */ - private $logMap = [ + private array $logMap = [ 'error' => LOG_ERR, 'warning' => LOG_WARNING, 'notice' => LOG_NOTICE, @@ -93,8 +92,12 @@ public function __construct( string $message, ?string $file = null, ?int $line = null, - array $trace = [] + array $trace = [], ) { + if (version_compare(PHP_VERSION, '8.4.0-dev', '<')) { + $this->levelMap[E_STRICT] = 'strict'; + } + $this->code = $code; $this->message = $message; $this->file = $file; diff --git a/app/vendor/cakephp/cakephp/src/Error/Renderer/ConsoleErrorRenderer.php b/app/vendor/cakephp/cakephp/src/Error/Renderer/ConsoleErrorRenderer.php index eb1a2efac..44f977943 100644 --- a/app/vendor/cakephp/cakephp/src/Error/Renderer/ConsoleErrorRenderer.php +++ b/app/vendor/cakephp/cakephp/src/Error/Renderer/ConsoleErrorRenderer.php @@ -30,12 +30,12 @@ class ConsoleErrorRenderer implements ErrorRendererInterface /** * @var \Cake\Console\ConsoleOutput */ - protected $output; + protected ConsoleOutput $output; /** * @var bool */ - protected $trace = false; + protected bool $trace = false; /** * Constructor. @@ -43,7 +43,7 @@ class ConsoleErrorRenderer implements ErrorRendererInterface * ### Options * * - `stderr` - The ConsoleOutput instance to use. Defaults to `php://stderr` - * - `trace` - Whether or not stacktraces should be output. + * - `trace` - Whether stacktraces should be output. * * @param array $config Error handling configuration. */ @@ -78,7 +78,7 @@ public function render(PhpError $error, bool $debug): string $error->getMessage(), $error->getLine() ?? '', $error->getFile() ?? '', - $trace + $trace, ); } } diff --git a/app/vendor/cakephp/cakephp/src/Error/Renderer/ConsoleExceptionRenderer.php b/app/vendor/cakephp/cakephp/src/Error/Renderer/ConsoleExceptionRenderer.php index cba448219..1119888d2 100644 --- a/app/vendor/cakephp/cakephp/src/Error/Renderer/ConsoleExceptionRenderer.php +++ b/app/vendor/cakephp/cakephp/src/Error/Renderer/ConsoleExceptionRenderer.php @@ -20,6 +20,8 @@ use Cake\Core\Configure; use Cake\Core\Exception\CakeException; use Cake\Error\Debugger; +use Cake\Error\ExceptionRendererInterface; +use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Throwable; @@ -27,26 +29,23 @@ * Plain text exception rendering with a stack trace. * * Useful in CI or plain text environments. - * - * @todo 5.0 Implement \Cake\Error\ExceptionRendererInterface. This implementation can't implement - * the concrete interface because the return types are not compatible. */ -class ConsoleExceptionRenderer +class ConsoleExceptionRenderer implements ExceptionRendererInterface { /** * @var \Throwable */ - private $error; + private Throwable $error; /** * @var \Cake\Console\ConsoleOutput */ - private $output; + private ConsoleOutput $output; /** * @var bool */ - private $trace; + private bool $trace; /** * Constructor. @@ -67,7 +66,7 @@ public function __construct(Throwable $error, ?ServerRequestInterface $request, * * @return \Psr\Http\Message\ResponseInterface|string */ - public function render() + public function render(): ResponseInterface|string { $exceptions = [$this->error]; $previous = $this->error->getPrevious(); @@ -77,18 +76,18 @@ public function render() } $out = []; foreach ($exceptions as $i => $error) { - $parent = $exceptions[$i - 1] ?? null; + $parent = $i > 0 ? $exceptions[$i - 1] : null; $out = array_merge($out, $this->renderException($error, $parent)); } - return join("\n", $out); + return implode("\n", $out); } /** * Render an individual exception * * @param \Throwable $exception The exception to render. - * @param ?\Throwable $parent The Exception index in the chain + * @param \Throwable|null $parent The Exception index in the chain * @return array */ protected function renderException(Throwable $exception, ?Throwable $parent): array @@ -97,10 +96,10 @@ protected function renderException(Throwable $exception, ?Throwable $parent): ar sprintf( '%s[%s] %s in %s on line %s', $parent ? 'Caused by ' : '', - get_class($exception), + $exception::class, $exception->getMessage(), $exception->getFile(), - $exception->getLine() + $exception->getLine(), ), ]; @@ -120,7 +119,7 @@ protected function renderException(Throwable $exception, ?Throwable $parent): ar $out[] = ''; $out[] = 'Stack Trace:'; $out[] = ''; - $out[] = Debugger::formatTrace($stacktrace, ['format' => 'txt']); + $out[] = Debugger::formatTrace($stacktrace, ['format' => 'text']); $out[] = ''; } @@ -130,11 +129,13 @@ protected function renderException(Throwable $exception, ?Throwable $parent): ar /** * Write output to the output stream * - * @param string $output The output to print. + * @param \Psr\Http\Message\ResponseInterface|string $output The output to print. * @return void */ - public function write($output): void + public function write(ResponseInterface|string $output): void { - $this->output->write($output); + if (is_string($output)) { + $this->output->write($output); + } } } diff --git a/app/vendor/cakephp/cakephp/src/Error/Renderer/HtmlErrorRenderer.php b/app/vendor/cakephp/cakephp/src/Error/Renderer/HtmlErrorRenderer.php index 7e49178f5..8e52b18ae 100644 --- a/app/vendor/cakephp/cakephp/src/Error/Renderer/HtmlErrorRenderer.php +++ b/app/vendor/cakephp/cakephp/src/Error/Renderer/HtmlErrorRenderer.php @@ -57,7 +57,7 @@ public function render(PhpError $error, bool $debug): string $errorMessage = sprintf( '%s (%s)', h(ucfirst($error->getLabel())), - h($error->getCode()) + h($error->getCode()), ); $toggle = $this->renderToggle($errorMessage, $id, 'trace'); $codeToggle = $this->renderToggle('Code', $id, 'code'); diff --git a/app/vendor/cakephp/cakephp/src/Error/Renderer/TextExceptionRenderer.php b/app/vendor/cakephp/cakephp/src/Error/Renderer/TextExceptionRenderer.php index 42fbda2bf..ee916de31 100644 --- a/app/vendor/cakephp/cakephp/src/Error/Renderer/TextExceptionRenderer.php +++ b/app/vendor/cakephp/cakephp/src/Error/Renderer/TextExceptionRenderer.php @@ -16,22 +16,21 @@ */ namespace Cake\Error\Renderer; +use Cake\Error\ExceptionRendererInterface; +use Psr\Http\Message\ResponseInterface; use Throwable; /** * Plain text exception rendering with a stack trace. * * Useful in CI or plain text environments. - * - * @todo 5.0 Implement \Cake\Error\ExceptionRendererInterface. This implementation can't implement - * the concrete interface because the return types are not compatible. */ -class TextExceptionRenderer +class TextExceptionRenderer implements ExceptionRendererInterface { /** * @var \Throwable */ - private $error; + protected Throwable $error; /** * Constructor. @@ -48,7 +47,7 @@ public function __construct(Throwable $error) * * @return \Psr\Http\Message\ResponseInterface|string */ - public function render() + public function render(): ResponseInterface|string { return sprintf( "%s : %s on line %s of %s\nTrace:\n%s", @@ -63,11 +62,12 @@ public function render() /** * Write output to stdout. * - * @param string $output The output to print. + * @param \Psr\Http\Message\ResponseInterface|string $output The output to print. * @return void */ - public function write($output): void + public function write(ResponseInterface|string $output): void { + assert(is_string($output)); echo $output; } } diff --git a/app/vendor/cakephp/cakephp/src/Error/Renderer/WebExceptionRenderer.php b/app/vendor/cakephp/cakephp/src/Error/Renderer/WebExceptionRenderer.php index b249f7409..9b08d88af 100644 --- a/app/vendor/cakephp/cakephp/src/Error/Renderer/WebExceptionRenderer.php +++ b/app/vendor/cakephp/cakephp/src/Error/Renderer/WebExceptionRenderer.php @@ -18,32 +18,29 @@ use Cake\Controller\Controller; use Cake\Controller\ControllerFactory; -use Cake\Controller\Exception\InvalidParameterException; -use Cake\Controller\Exception\MissingActionException; use Cake\Core\App; use Cake\Core\Configure; use Cake\Core\Container; use Cake\Core\Exception\CakeException; +use Cake\Core\Exception\HttpErrorCodeInterface; use Cake\Core\Exception\MissingPluginException; -use Cake\Datasource\Exception\RecordNotFoundException; -use Cake\Datasource\Paging\Exception\PageOutOfBoundsException; use Cake\Error\Debugger; use Cake\Error\ExceptionRendererInterface; -use Cake\Event\Event; use Cake\Http\Exception\HttpException; -use Cake\Http\Exception\MissingControllerException; use Cake\Http\Response; use Cake\Http\ResponseEmitter; use Cake\Http\ServerRequest; use Cake\Http\ServerRequestFactory; -use Cake\Routing\Exception\MissingRouteException; +use Cake\Log\Log; use Cake\Routing\Router; use Cake\Utility\Inflector; use Cake\View\Exception\MissingLayoutException; use Cake\View\Exception\MissingTemplateException; use PDOException; use Psr\Http\Message\ResponseInterface; +use ReflectionMethod; use Throwable; +use function Cake\Core\deprecationWarning; use function Cake\Core\h; use function Cake\Core\namespaceSplit; use function Cake\I18n\__d; @@ -72,28 +69,28 @@ class WebExceptionRenderer implements ExceptionRendererInterface * * @var \Throwable */ - protected $error; + protected Throwable $error; /** * Controller instance. * * @var \Cake\Controller\Controller */ - protected $controller; + protected Controller $controller; /** * Template to render for {@link \Cake\Core\Exception\CakeException} * * @var string */ - protected $template = ''; + protected string $template = ''; /** * The method corresponding to the Exception this object is for. * * @var string */ - protected $method = ''; + protected string $method = ''; /** * If set, this will be request used to create the controller that will render @@ -101,7 +98,7 @@ class WebExceptionRenderer implements ExceptionRendererInterface * * @var \Cake\Http\ServerRequest|null */ - protected $request; + protected ?ServerRequest $request; /** * Map of exceptions to http status codes. @@ -109,21 +106,11 @@ class WebExceptionRenderer implements ExceptionRendererInterface * This can be customized for users that don't want specific exceptions to throw 404 errors * or want their application exceptions to be automatically converted. * - * @var array - * @psalm-var array, int> + * @var array, int> + * @deprecated 5.2.0 Exceptions returning HTTP error codes should extend + * HttpErrorCodeInterface instead of using this array. */ - protected $exceptionHttpCodes = [ - // Controller exceptions - InvalidParameterException::class => 404, - MissingActionException::class => 404, - // Datasource exceptions - PageOutOfBoundsException::class => 404, - RecordNotFoundException::class => 404, - // Http exceptions - MissingControllerException::class => 404, - // Routing exceptions - MissingRouteException::class => 404, - ]; + protected array $exceptionHttpCodes = []; /** * Creates the controller to perform rendering on the error response. @@ -154,9 +141,7 @@ protected function _getController(): Controller $routerRequest = Router::getRequest(); // Fallback to the request in the router or make a new one from // $_SERVER - if ($request === null) { - $request = $routerRequest ?: ServerRequestFactory::fromGlobals(); - } + $request ??= $routerRequest ?: ServerRequestFactory::fromGlobals(); // If the current request doesn't have routing data, but we // found a request in the router context copy the params over @@ -164,7 +149,7 @@ protected function _getController(): Controller $request = $request->withAttribute('params', $routerRequest->getAttribute('params')); } - $errorOccured = false; + $class = ''; try { $params = $request->getAttribute('params'); $params['controller'] = 'Error'; @@ -185,28 +170,23 @@ protected function _getController(): Controller $class = App::className('Error', 'Controller', 'Controller'); } - /** @var \Cake\Controller\Controller $controller */ + assert(is_subclass_of($class, Controller::class)); $controller = new $class($request); $controller->startupProcess(); } catch (Throwable $e) { - $errorOccured = true; + Log::warning( + "Failed to construct or call startup() on the resolved controller class of `{$class}`. " . + "Using Fallback Controller instead. Error {$e->getMessage()}" . + "\nStack Trace\n: {$e->getTraceAsString()}", + 'cake.error', + ); + $controller = null; } - if (!isset($controller)) { + if ($controller === null) { return new Controller($request); } - // Retry RequestHandler, as another aspect of startupProcess() - // could have failed. Ignore any exceptions out of startup, as - // there could be userland input data parsers. - if ($errorOccured && isset($controller->RequestHandler)) { - try { - $event = new Event('Controller.startup', $controller); - $controller->RequestHandler->startup($event); - } catch (Throwable $e) { - } - } - return $controller; } @@ -228,7 +208,7 @@ protected function clearOutput(): void /** * Renders the response for the exception. * - * @return \Cake\Http\Response The response to be sent. + * @return \Psr\Http\Message\ResponseInterface The response to be sent. */ public function render(): ResponseInterface { @@ -246,12 +226,6 @@ public function render(): ResponseInterface $url = $this->controller->getRequest()->getRequestTarget(); $response = $this->controller->getResponse(); - if ($exception instanceof CakeException) { - /** @psalm-suppress DeprecatedMethod */ - foreach ((array)$exception->responseHeader() as $key => $value) { - $response = $response->withHeader($key, $value); - } - } if ($exception instanceof HttpException) { foreach ($exception->getHeaders() as $name => $value) { $response = $response->withHeader($name, $value); @@ -309,7 +283,7 @@ public function render(): ResponseInterface * @param \Psr\Http\Message\ResponseInterface|string $output The response to output. * @return void */ - public function write($output): void + public function write(ResponseInterface|string $output): void { if (is_string($output)) { echo $output; @@ -333,7 +307,7 @@ protected function _customMethod(string $method, Throwable $exception): Response $result = $this->{$method}($exception); $this->_shutdown(); if (is_string($result)) { - $result = $this->controller->getResponse()->withStringBody($result); + return $this->controller->getResponse()->withStringBody($result); } return $result; @@ -347,9 +321,9 @@ protected function _customMethod(string $method, Throwable $exception): Response */ protected function _method(Throwable $exception): string { - [, $baseClass] = namespaceSplit(get_class($exception)); + [, $baseClass] = namespaceSplit($exception::class); - if (substr($baseClass, -9) === 'Exception') { + if (str_ends_with($baseClass, 'Exception')) { $baseClass = substr($baseClass, 0, -9); } @@ -413,36 +387,73 @@ protected function _template(Throwable $exception, string $method, int $code): s */ protected function getHttpCode(Throwable $exception): int { - if ($exception instanceof HttpException) { + if ($exception instanceof HttpErrorCodeInterface) { return $exception->getCode(); } - return $this->exceptionHttpCodes[get_class($exception)] ?? 500; + if (isset($this->exceptionHttpCodes[$exception::class])) { + deprecationWarning( + '5.2.0', + 'Exceptions returning a HTTP error code should implement HttpErrorCodeInterface,' + . ' instead of using the WebExceptionRenderer::$exceptionHttpCodes property.', + ); + + return $this->exceptionHttpCodes[$exception::class]; + } + + return 500; } /** * Generate the response using the controller object. * * @param string $template The template to render. + * @param bool $skipControllerCheck Skip checking controller for existence of + * method matching the exception name. * @return \Cake\Http\Response A response object that can be sent. */ - protected function _outputMessage(string $template): Response + protected function _outputMessage(string $template, bool $skipControllerCheck = false): Response { try { - $this->controller->render($template); + $method = $this->method ?: $this->_method($this->error); + + if (!$skipControllerCheck && method_exists($this->controller, $method)) { + $this->controller->viewBuilder()->setTemplate($method); + + $reflectionMethod = new ReflectionMethod($this->controller, $method); + $result = $reflectionMethod->invoke($this->controller, $this->error); + + if ($result instanceof Response) { + $this->controller->setResponse($result); + } else { + $this->controller->render(); + } + } else { + $this->controller->render($template); + } return $this->_shutdown(); } catch (MissingTemplateException $e) { + Log::warning( + "MissingTemplateException - Failed to render error template `{$template}` . Error: {$e->getMessage()}" . + "\nStack Trace\n: {$e->getTraceAsString()}", + 'cake.error', + ); $attributes = $e->getAttributes(); if ( $e instanceof MissingLayoutException || - strpos($attributes['file'], 'error500') !== false + str_contains($attributes['file'], 'error500') ) { return $this->_outputMessageSafe('error500'); } - return $this->_outputMessage('error500'); + return $this->_outputMessage('error500', true); } catch (MissingPluginException $e) { + Log::warning( + "MissingPluginException - Failed to render error template `{$template}`. Error: {$e->getMessage()}" . + "\nStack Trace\n: {$e->getTraceAsString()}", + 'cake.error', + ); $attributes = $e->getAttributes(); if (isset($attributes['plugin']) && $attributes['plugin'] === $this->controller->getPlugin()) { $this->controller->setPlugin(null); @@ -450,9 +461,14 @@ protected function _outputMessage(string $template): Response return $this->_outputMessageSafe('error500'); } catch (Throwable $outer) { + Log::warning( + "Throwable - Failed to render error template `{$template}`. Error: {$outer->getMessage()}" . + "\nStack Trace\n: {$outer->getTraceAsString()}", + 'cake.error', + ); try { return $this->_outputMessageSafe('error500'); - } catch (Throwable $inner) { + } catch (Throwable) { throw $outer; } } @@ -469,7 +485,7 @@ protected function _outputMessageSafe(string $template): Response { $builder = $this->controller->viewBuilder(); $builder - ->setHelpers([], false) + ->setHelpers([]) ->setLayoutPath('') ->setTemplatePath('Error'); $view = $this->controller->createView('View'); diff --git a/app/vendor/cakephp/cakephp/src/Error/functions.php b/app/vendor/cakephp/cakephp/src/Error/functions.php index 6a33370a1..56bb966ec 100644 --- a/app/vendor/cakephp/cakephp/src/Error/functions.php +++ b/app/vendor/cakephp/cakephp/src/Error/functions.php @@ -29,10 +29,10 @@ * @param bool|null $showHtml If set to true, the method prints the debug data in a browser-friendly way. * @param bool $showFrom If set to true, the method prints from where the function was called. * @return mixed The same $var that was passed - * @link https://book.cakephp.org/4/en/development/debugging.html#basic-debugging - * @link https://book.cakephp.org/4/en/core-libraries/global-constants-and-functions.html#debug + * @link https://book.cakephp.org/5/en/development/debugging.html#basic-debugging + * @link https://book.cakephp.org/5/en/core-libraries/global-constants-and-functions.html#debug */ -function debug($var, $showHtml = null, $showFrom = true) +function debug(mixed $var, ?bool $showHtml = null, bool $showFrom = true): mixed { if (!Configure::read('debug')) { return $var; @@ -90,16 +90,15 @@ function stackTrace(array $options = []): void * @param mixed $var Variable to show debug information for. * @param bool|null $showHtml If set to true, the method prints the debug data in a browser-friendly way. * @return void - * @link https://book.cakephp.org/4/en/development/debugging.html#basic-debugging + * @link https://book.cakephp.org/5/en/development/debugging.html#basic-debugging */ -function dd($var, $showHtml = null): void +function dd(mixed $var, ?bool $showHtml = null): void { if (!Configure::read('debug')) { return; } $trace = Debugger::trace(['start' => 0, 'depth' => 2, 'format' => 'array']); - /** @psalm-suppress PossiblyInvalidArrayOffset */ $location = [ 'line' => $trace[0]['line'], 'file' => $trace[0]['file'], diff --git a/app/vendor/cakephp/cakephp/src/Error/functions_global.php b/app/vendor/cakephp/cakephp/src/Error/functions_global.php index 1f5ae23d8..aecc1068a 100644 --- a/app/vendor/cakephp/cakephp/src/Error/functions_global.php +++ b/app/vendor/cakephp/cakephp/src/Error/functions_global.php @@ -28,10 +28,10 @@ * @param bool|null $showHtml If set to true, the method prints the debug data in a browser-friendly way. * @param bool $showFrom If set to true, the method prints from where the function was called. * @return mixed The same $var that was passed - * @link https://book.cakephp.org/4/en/development/debugging.html#basic-debugging - * @link https://book.cakephp.org/4/en/core-libraries/global-constants-and-functions.html#debug + * @link https://book.cakephp.org/5/en/development/debugging.html#basic-debugging + * @link https://book.cakephp.org/5/en/core-libraries/global-constants-and-functions.html#debug */ - function debug($var, $showHtml = null, $showFrom = true) + function debug(mixed $var, ?bool $showHtml = null, bool $showFrom = true): mixed { if (!Configure::read('debug')) { return $var; @@ -93,16 +93,15 @@ function stackTrace(array $options = []): void * @param mixed $var Variable to show debug information for. * @param bool|null $showHtml If set to true, the method prints the debug data in a browser-friendly way. * @return void - * @link https://book.cakephp.org/4/en/development/debugging.html#basic-debugging + * @link https://book.cakephp.org/5/en/development/debugging.html#basic-debugging */ - function dd($var, $showHtml = null): void + function dd(mixed $var, ?bool $showHtml = null): void { if (!Configure::read('debug')) { return; } $trace = Debugger::trace(['start' => 0, 'depth' => 2, 'format' => 'array']); - /** @psalm-suppress PossiblyInvalidArrayOffset */ $location = [ 'line' => $trace[0]['line'], 'file' => $trace[0]['file'], @@ -128,12 +127,13 @@ function dd($var, $showHtml = null): void */ function breakpoint(): ?string { - if ((PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') && class_exists(PsyShell::class)) { + // phpcs:ignore SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly + if ((PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') && class_exists(\Psy\Shell::class)) { return 'extract(\Psy\Shell::debug(get_defined_vars(), isset($this) ? $this : null));'; } trigger_error( 'psy/psysh must be installed and you must be in a CLI environment to use the breakpoint function', - E_USER_WARNING + E_USER_WARNING, ); return null; diff --git a/app/vendor/cakephp/cakephp/src/Event/Decorator/AbstractDecorator.php b/app/vendor/cakephp/cakephp/src/Event/Decorator/AbstractDecorator.php index 0e5d59d37..1f2242fae 100644 --- a/app/vendor/cakephp/cakephp/src/Event/Decorator/AbstractDecorator.php +++ b/app/vendor/cakephp/cakephp/src/Event/Decorator/AbstractDecorator.php @@ -26,14 +26,14 @@ abstract class AbstractDecorator * * @var callable */ - protected $_callable; + protected mixed $_callable; /** * Decorator options * * @var array */ - protected $_options = []; + protected array $_options = []; /** * Constructor. @@ -53,7 +53,7 @@ public function __construct(callable $callable, array $options = []) * @link https://secure.php.net/manual/en/language.oop5.magic.php#object.invoke * @return mixed */ - public function __invoke() + public function __invoke(): mixed { return $this->_call(func_get_args()); } @@ -64,7 +64,7 @@ public function __invoke() * @param array $args Arguments for the callable. * @return mixed */ - protected function _call(array $args) + protected function _call(array $args): mixed { $callable = $this->_callable; diff --git a/app/vendor/cakephp/cakephp/src/Event/Decorator/ConditionDecorator.php b/app/vendor/cakephp/cakephp/src/Event/Decorator/ConditionDecorator.php index 92c7b9f3b..297ca1809 100644 --- a/app/vendor/cakephp/cakephp/src/Event/Decorator/ConditionDecorator.php +++ b/app/vendor/cakephp/cakephp/src/Event/Decorator/ConditionDecorator.php @@ -17,7 +17,7 @@ namespace Cake\Event\Decorator; use Cake\Event\EventInterface; -use RuntimeException; +use InvalidArgumentException; /** * Event Condition Decorator @@ -30,11 +30,10 @@ class ConditionDecorator extends AbstractDecorator /** * @inheritDoc */ - public function __invoke() + public function __invoke(mixed ...$args): mixed { - $args = func_get_args(); if (!$this->canTrigger($args[0])) { - return; + return null; } return $this->_call($args); @@ -43,7 +42,8 @@ public function __invoke() /** * Checks if the event is triggered for this listener. * - * @param \Cake\Event\EventInterface $event Event object. + * @template TSubject of object + * @param \Cake\Event\EventInterface $event Event object. * @return bool */ public function canTrigger(EventInterface $event): bool @@ -57,8 +57,9 @@ public function canTrigger(EventInterface $event): bool /** * Evaluates the filter conditions * + * @template TSubject of object * @param string $condition Condition type - * @param \Cake\Event\EventInterface $event Event object + * @param \Cake\Event\EventInterface $event Event object * @return bool */ protected function _evaluateCondition(string $condition, EventInterface $event): bool @@ -67,7 +68,7 @@ protected function _evaluateCondition(string $condition, EventInterface $event): return $condition !== 'unless'; } if (!is_callable($this->_options[$condition])) { - throw new RuntimeException(self::class . ' the `' . $condition . '` condition is not a callable!'); + throw new InvalidArgumentException(self::class . ' the `' . $condition . '` condition is not a callable!'); } return (bool)$this->_options[$condition]($event); diff --git a/app/vendor/cakephp/cakephp/src/Event/Decorator/SubjectFilterDecorator.php b/app/vendor/cakephp/cakephp/src/Event/Decorator/SubjectFilterDecorator.php index b802ed1ab..f380d9469 100644 --- a/app/vendor/cakephp/cakephp/src/Event/Decorator/SubjectFilterDecorator.php +++ b/app/vendor/cakephp/cakephp/src/Event/Decorator/SubjectFilterDecorator.php @@ -18,7 +18,6 @@ use Cake\Core\Exception\CakeException; use Cake\Event\EventInterface; -use RuntimeException; /** * Event Subject Filter Decorator @@ -34,11 +33,10 @@ class SubjectFilterDecorator extends AbstractDecorator /** * @inheritDoc */ - public function __invoke() + public function __invoke(mixed ...$args): mixed { - $args = func_get_args(); if (!$this->canTrigger($args[0])) { - return false; + return null; } return $this->_call($args); @@ -47,13 +45,14 @@ public function __invoke() /** * Checks if the event is triggered for this listener. * - * @param \Cake\Event\EventInterface $event Event object. + * @template TSubject of object + * @param \Cake\Event\EventInterface $event Event object. * @return bool */ public function canTrigger(EventInterface $event): bool { if (!isset($this->_options['allowedSubject'])) { - throw new RuntimeException(self::class . ' Missing subject filter options!'); + throw new CakeException(self::class . ' Missing subject filter options!'); } if (is_string($this->_options['allowedSubject'])) { $this->_options['allowedSubject'] = [$this->_options['allowedSubject']]; @@ -61,10 +60,10 @@ public function canTrigger(EventInterface $event): bool try { $subject = $event->getSubject(); - } catch (CakeException $e) { + } catch (CakeException) { return false; } - return in_array(get_class($subject), $this->_options['allowedSubject'], true); + return in_array($subject::class, $this->_options['allowedSubject'], true); } } diff --git a/app/vendor/cakephp/cakephp/src/Event/Event.php b/app/vendor/cakephp/cakephp/src/Event/Event.php index 86e3fd03f..85a114497 100644 --- a/app/vendor/cakephp/cakephp/src/Event/Event.php +++ b/app/vendor/cakephp/cakephp/src/Event/Event.php @@ -21,7 +21,7 @@ /** * Class Event * - * @template TSubject + * @template TSubject of object * @implements \Cake\Event\EventInterface */ class Event implements EventInterface @@ -31,22 +31,22 @@ class Event implements EventInterface * * @var string */ - protected $_name; + protected string $_name; /** * The object this event applies to (usually the same object that generates the event) * * @var object|null - * @psalm-var TSubject|null + * @phpstan-var TSubject|null */ - protected $_subject; + protected ?object $_subject = null; /** * Custom data for the method that receives the event * * @var array */ - protected $_data; + protected array $_data; /** * Property used to retain the result value of the event listeners @@ -55,14 +55,14 @@ class Event implements EventInterface * * @var mixed */ - protected $result; + protected mixed $result = null; /** * Flags an event as stopped or not, default is false * * @var bool */ - protected $_stopped = false; + protected bool $_stopped = false; /** * Constructor @@ -77,15 +77,15 @@ class Event implements EventInterface * @param string $name Name of the event * @param object|null $subject the object that this event applies to * (usually the object that is generating the event). - * @param \ArrayAccess|array|null $data any value you wish to be transported + * @param array $data any value you wish to be transported * with this event to it can be read by listeners. - * @psalm-param TSubject|null $subject + * @phpstan-param TSubject|null $subject */ - public function __construct(string $name, $subject = null, $data = null) + public function __construct(string $name, ?object $subject = null, array $data = []) { $this->_name = $name; $this->_subject = $subject; - $this->_data = (array)$data; + $this->_data = $data; } /** @@ -105,9 +105,9 @@ public function getName(): string * * @return object * @throws \Cake\Core\Exception\CakeException - * @psalm-return TSubject + * @phpstan-return TSubject */ - public function getSubject() + public function getSubject(): object { if ($this->_subject === null) { throw new CakeException('No subject set for this event'); @@ -141,7 +141,7 @@ public function isStopped(): bool * * @return mixed */ - public function getResult() + public function getResult(): mixed { return $this->result; } @@ -149,10 +149,12 @@ public function getResult() /** * Listeners can attach a result value to the event. * + * Setting the result to `false` will also stop event propagation. + * * @param mixed $value The value to set. * @return $this */ - public function setResult($value = null) + public function setResult(mixed $value = null) { $this->result = $value; @@ -160,13 +162,9 @@ public function setResult($value = null) } /** - * Access the event data/payload. - * - * @param string|null $key The data payload element to return, or null to return all data. - * @return mixed|array|null The data payload if $key is null, or the data value for the given $key. - * If the $key does not exist a null value is returned. + * @inheritDoc */ - public function getData(?string $key = null) + public function getData(?string $key = null): mixed { if ($key !== null) { return $this->_data[$key] ?? null; @@ -176,13 +174,9 @@ public function getData(?string $key = null) } /** - * Assigns a value to the data/payload of this event. - * - * @param array|string $key An array will replace all payload data, and a key will set just that array item. - * @param mixed $value The value to set. - * @return $this + * @inheritDoc */ - public function setData($key, $value = null) + public function setData(array|string $key, $value = null) { if (is_array($key)) { $this->_data = $key; diff --git a/app/vendor/cakephp/cakephp/src/Event/EventDispatcherInterface.php b/app/vendor/cakephp/cakephp/src/Event/EventDispatcherInterface.php index 7ab362695..479260fd0 100644 --- a/app/vendor/cakephp/cakephp/src/Event/EventDispatcherInterface.php +++ b/app/vendor/cakephp/cakephp/src/Event/EventDispatcherInterface.php @@ -24,6 +24,8 @@ * * The {@link \Cake\Event\EventDispatcherTrait} lets you easily implement * this interface. + * + * @template TSubject of object */ interface EventDispatcherInterface { @@ -33,13 +35,13 @@ interface EventDispatcherInterface * Returns a dispatched event. * * @param string $name Name of the event. - * @param array|null $data Any value you wish to be transported with this event to + * @param array $data Any value you wish to be transported with this event to * it can be read by listeners. - * @param object|null $subject The object that this event applies to + * @param TSubject|null $subject The object that this event applies to * ($this by default). - * @return \Cake\Event\EventInterface + * @return \Cake\Event\EventInterface */ - public function dispatchEvent(string $name, ?array $data = null, ?object $subject = null): EventInterface; + public function dispatchEvent(string $name, array $data = [], ?object $subject = null): EventInterface; /** * Sets the Cake\Event\EventManager manager instance for this object. diff --git a/app/vendor/cakephp/cakephp/src/Event/EventDispatcherTrait.php b/app/vendor/cakephp/cakephp/src/Event/EventDispatcherTrait.php index e9b4dd963..5eedc9d1f 100644 --- a/app/vendor/cakephp/cakephp/src/Event/EventDispatcherTrait.php +++ b/app/vendor/cakephp/cakephp/src/Event/EventDispatcherTrait.php @@ -18,6 +18,8 @@ /** * Implements Cake\Event\EventDispatcherInterface. + * + * @template TSubject of object */ trait EventDispatcherTrait { @@ -27,14 +29,14 @@ trait EventDispatcherTrait * * @var \Cake\Event\EventManagerInterface|null */ - protected $_eventManager; + protected ?EventManagerInterface $_eventManager = null; /** * Default class name for new event objects. * * @var string */ - protected $_eventClass = Event::class; + protected string $_eventClass = Event::class; /** * Returns the Cake\Event\EventManager manager instance for this object. @@ -46,11 +48,7 @@ trait EventDispatcherTrait */ public function getEventManager(): EventManagerInterface { - if ($this->_eventManager === null) { - $this->_eventManager = new EventManager(); - } - - return $this->_eventManager; + return $this->_eventManager ??= new EventManager(); } /** @@ -75,19 +73,17 @@ public function setEventManager(EventManagerInterface $eventManager) * Returns a dispatched event. * * @param string $name Name of the event. - * @param array|null $data Any value you wish to be transported with this event to + * @param array $data Any value you wish to be transported with this event to * it can be read by listeners. - * @param object|null $subject The object that this event applies to + * @param TSubject|null $subject The object that this event applies to * ($this by default). - * @return \Cake\Event\EventInterface + * @return \Cake\Event\EventInterface */ - public function dispatchEvent(string $name, ?array $data = null, ?object $subject = null): EventInterface + public function dispatchEvent(string $name, array $data = [], ?object $subject = null): EventInterface { - if ($subject === null) { - $subject = $this; - } + $subject ??= $this; - /** @var \Cake\Event\EventInterface $event */ + /** @var \Cake\Event\EventInterface $event Coerce for psalm/phpstan */ $event = new $this->_eventClass($name, $subject, $data); $this->getEventManager()->dispatch($event); diff --git a/app/vendor/cakephp/cakephp/src/Event/EventInterface.php b/app/vendor/cakephp/cakephp/src/Event/EventInterface.php index aca9956d6..97673d78c 100644 --- a/app/vendor/cakephp/cakephp/src/Event/EventInterface.php +++ b/app/vendor/cakephp/cakephp/src/Event/EventInterface.php @@ -21,7 +21,7 @@ * payload. The name can be any string that uniquely identifies the event across the application, while the subject * represents the object that the event applies to. * - * @template TSubject + * @template TSubject of object */ interface EventInterface { @@ -36,9 +36,9 @@ public function getName(): string; * Returns the subject of this event. * * @return object - * @psalm-return TSubject + * @phpstan-return TSubject */ - public function getSubject(); + public function getSubject(): object; /** * Stops the event from being used anymore. @@ -59,7 +59,7 @@ public function isStopped(): bool; * * @return mixed */ - public function getResult(); + public function getResult(): mixed; /** * Listeners can attach a result value to the event. @@ -67,16 +67,16 @@ public function getResult(); * @param mixed $value The value to set. * @return $this */ - public function setResult($value = null); + public function setResult(mixed $value = null); /** * Accesses the event data/payload. * * @param string|null $key The data payload element to return, or null to return all data. - * @return mixed|array|null The data payload if $key is null, or the data value for the given $key. + * @return mixed The data payload if $key is null, or the data value for the given $key. * If the $key does not exist a null value is returned. */ - public function getData(?string $key = null); + public function getData(?string $key = null): mixed; /** * Assigns a value to the data/payload of this event. @@ -85,5 +85,5 @@ public function getData(?string $key = null); * @param mixed $value The value to set. * @return $this */ - public function setData($key, $value = null); + public function setData(array|string $key, mixed $value = null); } diff --git a/app/vendor/cakephp/cakephp/src/Event/EventList.php b/app/vendor/cakephp/cakephp/src/Event/EventList.php index 00be78cd2..6e22cfffd 100644 --- a/app/vendor/cakephp/cakephp/src/Event/EventList.php +++ b/app/vendor/cakephp/cakephp/src/Event/EventList.php @@ -29,9 +29,9 @@ class EventList implements ArrayAccess, Countable /** * Events list * - * @var array<\Cake\Event\EventInterface> + * @var array<\Cake\Event\EventInterface> */ - protected $_events = []; + protected array $_events = []; /** * Empties the list of dispatched events. @@ -46,7 +46,7 @@ public function flush(): void /** * Adds an event to the list when event listing is enabled. * - * @param \Cake\Event\EventInterface $event An event to the list of dispatched events. + * @param \Cake\Event\EventInterface $event An event to the list of dispatched events. * @return void */ public function add(EventInterface $event): void @@ -61,7 +61,7 @@ public function add(EventInterface $event): void * @param mixed $offset An offset to check for. * @return bool True on success or false on failure. */ - public function offsetExists($offset): bool + public function offsetExists(mixed $offset): bool { return isset($this->_events[$offset]); } @@ -71,16 +71,15 @@ public function offsetExists($offset): bool * * @link https://secure.php.net/manual/en/arrayaccess.offsetget.php * @param mixed $offset The offset to retrieve. - * @return \Cake\Event\EventInterface|null + * @return \Cake\Event\EventInterface|null */ - #[\ReturnTypeWillChange] - public function offsetGet($offset) + public function offsetGet(mixed $offset): ?EventInterface { - if ($this->offsetExists($offset)) { - return $this->_events[$offset]; + if (!$this->offsetExists($offset)) { + return null; } - return null; + return $this->_events[$offset]; } /** @@ -91,7 +90,7 @@ public function offsetGet($offset) * @param mixed $value The value to set. * @return void */ - public function offsetSet($offset, $value): void + public function offsetSet(mixed $offset, mixed $value): void { $this->_events[$offset] = $value; } @@ -103,7 +102,7 @@ public function offsetSet($offset, $value): void * @param mixed $offset The offset to unset. * @return void */ - public function offsetUnset($offset): void + public function offsetUnset(mixed $offset): void { unset($this->_events[$offset]); } diff --git a/app/vendor/cakephp/cakephp/src/Event/EventManager.php b/app/vendor/cakephp/cakephp/src/Event/EventManager.php index 278261d56..88b16d9d1 100644 --- a/app/vendor/cakephp/cakephp/src/Event/EventManager.php +++ b/app/vendor/cakephp/cakephp/src/Event/EventManager.php @@ -17,6 +17,10 @@ namespace Cake\Event; use Cake\Core\Exception\CakeException; +use Closure; +use InvalidArgumentException; +use ReflectionFunction; +use function Cake\Core\deprecationWarning; /** * The event manager is responsible for keeping track of event listeners, passing the correct @@ -31,42 +35,42 @@ class EventManager implements EventManagerInterface * * @var int */ - public static $defaultPriority = 10; + public static int $defaultPriority = 10; /** * The globally available instance, used for dispatching events attached from any scope * * @var \Cake\Event\EventManager|null */ - protected static $_generalManager; + protected static ?EventManager $_generalManager = null; /** * List of listener callbacks associated to * * @var array */ - protected $_listeners = []; + protected array $_listeners = []; /** * Internal flag to distinguish a common manager from the singleton * * @var bool */ - protected $_isGlobal = false; + protected bool $_isGlobal = false; /** * The event list object. * * @var \Cake\Event\EventList|null */ - protected $_eventList; + protected ?EventList $_eventList = null; /** * Enables automatic adding of events to the event list object if it is present. * * @var bool */ - protected $_trackEvents = false; + protected bool $_trackEvents = false; /** * Returns the globally available instance of a Cake\Event\EventManager @@ -79,15 +83,16 @@ class EventManager implements EventManagerInterface * @param \Cake\Event\EventManager|null $manager Event manager instance. * @return \Cake\Event\EventManager The global event manager */ - public static function instance(?EventManager $manager = null) + public static function instance(?EventManager $manager = null): EventManager { + if ($manager === null && static::$_generalManager) { + return static::$_generalManager; + } + if ($manager instanceof EventManager) { static::$_generalManager = $manager; } - if (empty(static::$_generalManager)) { - static::$_generalManager = new static(); - } - + static::$_generalManager ??= new static(); static::$_generalManager->_isGlobal = true; return static::$_generalManager; @@ -96,26 +101,36 @@ public static function instance(?EventManager $manager = null) /** * @inheritDoc */ - public function on($eventKey, $options = [], ?callable $callable = null) - { + public function on( + EventListenerInterface|string $eventKey, + callable|array $options = [], + ?callable $callable = null, + ) { if ($eventKey instanceof EventListenerInterface) { $this->_attachSubscriber($eventKey); return $this; } - $argCount = func_num_args(); - if ($argCount === 2) { + if ($callable === null && !is_callable($options)) { + throw new InvalidArgumentException( + 'Second argument of `EventManager::on()` must be a callable if `$callable` is null.', + ); + } + + if ($callable === null) { + /** @var callable $options */ $this->_listeners[$eventKey][static::$defaultPriority][] = [ - 'callable' => $options, + 'callable' => $options(...), ]; return $this; } + /** @var array $options */ $priority = $options['priority'] ?? static::$defaultPriority; $this->_listeners[$eventKey][$priority][] = [ - 'callable' => $callable, + 'callable' => $callable(...), ]; return $this; @@ -130,52 +145,20 @@ public function on($eventKey, $options = [], ?callable $callable = null) */ protected function _attachSubscriber(EventListenerInterface $subscriber): void { - foreach ($subscriber->implementedEvents() as $eventKey => $function) { - $options = []; - $method = $function; - if (is_array($function) && isset($function['callable'])) { - [$method, $options] = $this->_extractCallable($function, $subscriber); - } elseif (is_array($function) && is_numeric(key($function))) { - foreach ($function as $f) { - [$method, $options] = $this->_extractCallable($f, $subscriber); - $this->on($eventKey, $options, $method); - } - continue; - } - if (is_string($method)) { - $method = [$subscriber, $function]; + foreach ($subscriber->implementedEvents() as $eventKey => $handlers) { + foreach ($this->normalizeHandlers($subscriber, $handlers) as $handler) { + $this->on($eventKey, $handler['settings'], $handler['callable']); } - $this->on($eventKey, $options, $method); } } - /** - * Auxiliary function to extract and return a PHP callback type out of the callable definition - * from the return value of the `implementedEvents()` method on a {@link \Cake\Event\EventListenerInterface} - * - * @param array $function the array taken from a handler definition for an event - * @param \Cake\Event\EventListenerInterface $object The handler object - * @return array - */ - protected function _extractCallable(array $function, EventListenerInterface $object): array - { - /** @var callable $method */ - $method = $function['callable']; - $options = $function; - unset($options['callable']); - if (is_string($method)) { - /** @var callable $method */ - $method = [$object, $method]; - } - - return [$method, $options]; - } - /** * @inheritDoc */ - public function off($eventKey, $callable = null) - { + public function off( + EventListenerInterface|callable|string $eventKey, + EventListenerInterface|callable|null $callable = null, + ) { if ($eventKey instanceof EventListenerInterface) { $this->_detachSubscriber($eventKey); @@ -183,13 +166,6 @@ public function off($eventKey, $callable = null) } if (!is_string($eventKey)) { - if (!is_callable($eventKey)) { - throw new CakeException( - 'First argument of EventManager::off() must be ' . - ' string or EventListenerInterface instance or callable.' - ); - } - foreach (array_keys($this->_listeners) as $name) { $this->off($name, $eventKey); } @@ -213,9 +189,10 @@ public function off($eventKey, $callable = null) return $this; } + $callable = $callable(...); foreach ($this->_listeners[$eventKey] as $priority => $callables) { foreach ($callables as $k => $callback) { - if ($callback['callable'] === $callable) { + if ($callback['callable'] == $callable) { unset($this->_listeners[$eventKey][$priority][$k]); break; } @@ -235,31 +212,83 @@ public function off($eventKey, $callable = null) protected function _detachSubscriber(EventListenerInterface $subscriber, ?string $eventKey = null): void { $events = $subscriber->implementedEvents(); - if (!empty($eventKey) && empty($events[$eventKey])) { + if ($eventKey && empty($events[$eventKey])) { return; } - if (!empty($eventKey)) { + if ($eventKey) { $events = [$eventKey => $events[$eventKey]]; } - foreach ($events as $key => $function) { - if (is_array($function)) { - if (is_numeric(key($function))) { - foreach ($function as $handler) { - $handler = $handler['callable'] ?? $handler; - $this->off($key, [$subscriber, $handler]); - } - continue; - } - $function = $function['callable']; + foreach ($events as $key => $handlers) { + foreach ($this->normalizeHandlers($subscriber, $handlers) as $handler) { + $this->off($key, $handler['callable']); + } + } + } + + /** + * Builds an array of normalized handlers. + * + * A normalized handler is an array with these keys: + * + * - `callable` - The event handler closure + * - `settings` - The event handler settings + * + * @param \Cake\Event\EventListenerInterface $subscriber Event subscriber + * @param callable|array|string $handlers Event handlers + * @return array + */ + protected function normalizeHandlers( + EventListenerInterface $subscriber, + callable|array|string $handlers, + ): array { + // Check if an array of handlers not single handler config array + if (is_array($handlers) && !isset($handlers['callable'])) { + foreach ($handlers as &$handler) { + $handler = $this->normalizeHandler($subscriber, $handler); } - $this->off($key, [$subscriber, $function]); + + return $handlers; + } + + return [$this->normalizeHandler($subscriber, $handlers)]; + } + + /** + * Builds a single normalized handler. + * + * A normalized handler is an array with these keys: + * + * - `callable` - The event handler closure + * - `settings` - The event handler settings + * + * @param \Cake\Event\EventListenerInterface $subscriber Event subscriber + * @param callable|array|string $handler Event handler + * @return array + */ + protected function normalizeHandler( + EventListenerInterface $subscriber, + callable|array|string $handler, + ): array { + $callable = $handler; + $settings = []; + + if (is_array($handler)) { + $callable = $handler['callable']; + $settings = $handler; + unset($settings['callable']); + } + + if (is_string($callable)) { + $callable = $subscriber->$callable(...); } + + return ['callable' => $callable, 'settings' => $settings]; } /** * @inheritDoc */ - public function dispatch($event): EventInterface + public function dispatch(EventInterface|string $event): EventInterface { if (is_string($event)) { $event = new Event($event); @@ -275,7 +304,7 @@ public function dispatch($event): EventInterface static::instance()->addEventToList($event); } - if (empty($listeners)) { + if (!$listeners) { return $event; } @@ -283,13 +312,8 @@ public function dispatch($event): EventInterface if ($event->isStopped()) { break; } - $result = $this->_callListener($listener['callable'], $event); - if ($result === false) { - $event->stopPropagation(); - } - if ($result !== null) { - $event->setResult($result); - } + + $this->_callListener($listener['callable'], $event); } return $event; @@ -298,15 +322,42 @@ public function dispatch($event): EventInterface /** * Calls a listener. * + * @template TSubject of object * @param callable $listener The listener to trigger. - * @param \Cake\Event\EventInterface $event Event instance. - * @return mixed The result of the $listener function. + * @param \Cake\Event\EventInterface $event Event instance. + * @return void */ - protected function _callListener(callable $listener, EventInterface $event) + protected function _callListener(callable $listener, EventInterface $event): void { - $data = (array)$event->getData(); + $result = $listener($event, ...array_values($event->getData())); - return $listener($event, ...array_values($data)); + if ($result !== null) { + try { + $class = get_class($event->getSubject()); + } catch (CakeException) { + $class = 'unknown subject'; + } + + if ($listener instanceof Closure) { + $ref = new ReflectionFunction($listener); + $closureClass = $ref->getClosureScopeClass(); + $closureMethod = $ref->getName(); + if ($closureClass && $closureClass->name && $closureMethod) { + $class = $closureClass->name . '::' . $closureMethod . '()'; + } + } + + deprecationWarning( + '5.2.0', + 'Returning a value from event listeners is deprecated. ' . + 'Use `$event->setResult()` instead in `' . $event->getName() . '` of `' . $class . '`', + ); + $event->setResult($result); + } + + if ($event->getResult() === false) { + $event->stopPropagation(); + } } /** @@ -317,10 +368,8 @@ public function listeners(string $eventKey): array $localListeners = []; if (!$this->_isGlobal) { $localListeners = $this->prioritisedListeners($eventKey); - $localListeners = empty($localListeners) ? [] : $localListeners; } $globalListeners = static::instance()->prioritisedListeners($eventKey); - $globalListeners = empty($globalListeners) ? [] : $globalListeners; $priorities = array_merge(array_keys($globalListeners), array_keys($localListeners)); $priorities = array_unique($priorities); @@ -367,8 +416,8 @@ public function matchingListeners(string $eventKeyPattern): array return array_intersect_key( $this->_listeners, array_flip( - preg_grep($matchPattern, array_keys($this->_listeners), 0) - ) + preg_grep($matchPattern, array_keys($this->_listeners), 0) ?: [], + ), ); } @@ -385,14 +434,13 @@ public function getEventList(): ?EventList /** * Adds an event to the list if the event list object is present. * - * @param \Cake\Event\EventInterface $event An event to add to the list. + * @template TSubject of object + * @param \Cake\Event\EventInterface $event An event to add to the list. * @return $this */ public function addEventToList(EventInterface $event) { - if ($this->_eventList) { - $this->_eventList->add($event); - } + $this->_eventList?->add($event); return $this; } @@ -467,11 +515,13 @@ public function __debugInfo(): array if ($this->_eventList) { $count = count($this->_eventList); for ($i = 0; $i < $count; $i++) { + assert(!empty($this->_eventList[$i]), 'Given event item not present'); + $event = $this->_eventList[$i]; try { $subject = $event->getSubject(); - $properties['_dispatchedEvents'][] = $event->getName() . ' with subject ' . get_class($subject); - } catch (CakeException $e) { + $properties['_dispatchedEvents'][] = $event->getName() . ' with subject ' . $subject::class; + } catch (CakeException) { $properties['_dispatchedEvents'][] = $event->getName() . ' with no subject'; } } diff --git a/app/vendor/cakephp/cakephp/src/Event/EventManagerInterface.php b/app/vendor/cakephp/cakephp/src/Event/EventManagerInterface.php index 492319b0f..96db2703d 100644 --- a/app/vendor/cakephp/cakephp/src/Event/EventManagerInterface.php +++ b/app/vendor/cakephp/cakephp/src/Event/EventManagerInterface.php @@ -57,7 +57,11 @@ interface EventManagerInterface * @throws \InvalidArgumentException When event key is missing or callable is not an * instance of Cake\Event\EventListenerInterface. */ - public function on($eventKey, $options = [], ?callable $callable = null); + public function on( + EventListenerInterface|string $eventKey, + callable|array $options = [], + ?callable $callable = null, + ); /** * Remove a listener from the active listeners. @@ -91,16 +95,20 @@ public function on($eventKey, $options = [], ?callable $callable = null); * @param \Cake\Event\EventListenerInterface|callable|null $callable The callback you want to detach. * @return $this */ - public function off($eventKey, $callable = null); + public function off( + EventListenerInterface|callable|string $eventKey, + EventListenerInterface|callable|null $callable = null, + ); /** * Dispatches a new event to all configured listeners * - * @param \Cake\Event\EventInterface|string $event The event key name or instance of EventInterface. - * @return \Cake\Event\EventInterface + * @template TSubject of object + * @param \Cake\Event\EventInterface|string $event The event key name or instance of EventInterface. + * @return \Cake\Event\EventInterface * @triggers $event */ - public function dispatch($event): EventInterface; + public function dispatch(EventInterface|string $event): EventInterface; /** * Returns a list of all listeners for an eventKey in the order they should be called diff --git a/app/vendor/cakephp/cakephp/src/Event/README.md b/app/vendor/cakephp/cakephp/src/Event/README.md index 2b511957a..7339afd4f 100644 --- a/app/vendor/cakephp/cakephp/src/Event/README.md +++ b/app/vendor/cakephp/cakephp/src/Event/README.md @@ -48,4 +48,4 @@ in separate objects that focus on those concerns. ## Documentation -Please make sure you check the [official documentation](https://book.cakephp.org/4/en/core-libraries/events.html) +Please make sure you check the [official documentation](https://book.cakephp.org/5/en/core-libraries/events.html) diff --git a/app/vendor/cakephp/cakephp/src/Event/composer.json b/app/vendor/cakephp/cakephp/src/Event/composer.json index 7e7ed7474..600b926c8 100644 --- a/app/vendor/cakephp/cakephp/src/Event/composer.json +++ b/app/vendor/cakephp/cakephp/src/Event/composer.json @@ -23,12 +23,18 @@ "source": "https://github.com/cakephp/event" }, "require": { - "php": ">=7.4.0", - "cakephp/core": "^4.0" + "php": ">=8.1", + "cakephp/core": "5.2.*@dev" }, "autoload": { "psr-4": { "Cake\\Event\\": "." } + }, + "prefer-stable": true, + "extra": { + "branch-alias": { + "dev-5.x": "5.2.x-dev" + } } } diff --git a/app/vendor/cakephp/cakephp/src/Filesystem/File.php b/app/vendor/cakephp/cakephp/src/Filesystem/File.php deleted file mode 100644 index 708a2583b..000000000 --- a/app/vendor/cakephp/cakephp/src/Filesystem/File.php +++ /dev/null @@ -1,655 +0,0 @@ - - * https://book.cakephp.org/4/en/core-libraries/file-folder.html#Cake\Filesystem\File::$info - */ - public $info = []; - - /** - * Holds the file handler resource if the file is opened - * - * @var resource|null - * https://book.cakephp.org/4/en/core-libraries/file-folder.html#Cake\Filesystem\File::$handle - */ - public $handle; - - /** - * Enable locking for file reading and writing - * - * @var bool|null - * https://book.cakephp.org/4/en/core-libraries/file-folder.html#Cake\Filesystem\File::$lock - */ - public $lock; - - /** - * Path property - * - * Current file's absolute path - * - * @var string|null - * https://book.cakephp.org/4/en/core-libraries/file-folder.html#Cake\Filesystem\File::$path - */ - public $path; - - /** - * Constructor - * - * @param string $path Path to file - * @param bool $create Create file if it does not exist (if true) - * @param int $mode Mode to apply to the folder holding the file - * @link https://book.cakephp.org/4/en/core-libraries/file-folder.html#file-api - */ - public function __construct(string $path, bool $create = false, int $mode = 0755) - { - $splInfo = new SplFileInfo($path); - $this->Folder = new Folder($splInfo->getPath(), $create, $mode); - if (!is_dir($path)) { - $this->name = ltrim($splInfo->getFilename(), '/\\'); - } - $this->pwd(); - $create && !$this->exists() && $this->safe($path) && $this->create(); - } - - /** - * Closes the current file if it is opened - */ - public function __destruct() - { - $this->close(); - } - - /** - * Creates the file. - * - * @return bool Success - */ - public function create(): bool - { - $dir = $this->Folder->pwd(); - - if (is_dir($dir) && is_writable($dir) && !$this->exists() && touch($this->path)) { - return true; - } - - return false; - } - - /** - * Opens the current file with a given $mode - * - * @param string $mode A valid 'fopen' mode string (r|w|a ...) - * @param bool $force If true then the file will be re-opened even if its already opened, otherwise it won't - * @return bool True on success, false on failure - */ - public function open(string $mode = 'r', bool $force = false): bool - { - if (!$force && is_resource($this->handle)) { - return true; - } - if ($this->exists() === false && $this->create() === false) { - return false; - } - - $this->handle = fopen($this->path, $mode); - - return is_resource($this->handle); - } - - /** - * Return the contents of this file as a string. - * - * @param string|false $bytes where to start - * @param string $mode A `fread` compatible mode. - * @param bool $force If true then the file will be re-opened even if its already opened, otherwise it won't - * @return string|false String on success, false on failure - */ - public function read($bytes = false, string $mode = 'rb', bool $force = false) - { - if ($bytes === false && $this->lock === null) { - return file_get_contents($this->path); - } - if ($this->open($mode, $force) === false) { - return false; - } - if ($this->lock !== null && flock($this->handle, LOCK_SH) === false) { - return false; - } - if (is_int($bytes)) { - return fread($this->handle, $bytes); - } - - $data = ''; - while (!feof($this->handle)) { - $data .= fgets($this->handle, 4096); - } - - if ($this->lock !== null) { - flock($this->handle, LOCK_UN); - } - if ($bytes === false) { - $this->close(); - } - - return trim($data); - } - - /** - * Sets or gets the offset for the currently opened file. - * - * @param int|false $offset The $offset in bytes to seek. If set to false then the current offset is returned. - * @param int $seek PHP Constant SEEK_SET | SEEK_CUR | SEEK_END determining what the $offset is relative to - * @return int|bool True on success, false on failure (set mode), false on failure - * or integer offset on success (get mode). - */ - public function offset($offset = false, int $seek = SEEK_SET) - { - if ($offset === false) { - if (is_resource($this->handle)) { - return ftell($this->handle); - } - } elseif ($this->open() === true) { - return fseek($this->handle, $offset, $seek) === 0; - } - - return false; - } - - /** - * Prepares an ASCII string for writing. Converts line endings to the - * correct terminator for the current platform. If Windows, "\r\n" will be used, - * all other platforms will use "\n" - * - * @param string $data Data to prepare for writing. - * @param bool $forceWindows If true forces Windows new line string. - * @return string The with converted line endings. - */ - public static function prepare(string $data, bool $forceWindows = false): string - { - $lineBreak = "\n"; - if (DIRECTORY_SEPARATOR === '\\' || $forceWindows === true) { - $lineBreak = "\r\n"; - } - - return strtr($data, ["\r\n" => $lineBreak, "\n" => $lineBreak, "\r" => $lineBreak]); - } - - /** - * Write given data to this file. - * - * @param string $data Data to write to this File. - * @param string $mode Mode of writing. {@link https://secure.php.net/fwrite See fwrite()}. - * @param bool $force Force the file to open - * @return bool Success - */ - public function write(string $data, string $mode = 'w', bool $force = false): bool - { - $success = false; - if ($this->open($mode, $force) === true) { - if ($this->lock !== null && flock($this->handle, LOCK_EX) === false) { - return false; - } - - if (fwrite($this->handle, $data) !== false) { - $success = true; - } - if ($this->lock !== null) { - flock($this->handle, LOCK_UN); - } - } - - return $success; - } - - /** - * Append given data string to this file. - * - * @param string $data Data to write - * @param bool $force Force the file to open - * @return bool Success - */ - public function append(string $data, bool $force = false): bool - { - return $this->write($data, 'a', $force); - } - - /** - * Closes the current file if it is opened. - * - * @return bool True if closing was successful or file was already closed, otherwise false - */ - public function close(): bool - { - if (!is_resource($this->handle)) { - return true; - } - - return fclose($this->handle); - } - - /** - * Deletes the file. - * - * @return bool Success - */ - public function delete(): bool - { - $this->close(); - $this->handle = null; - if ($this->exists()) { - return unlink($this->path); - } - - return false; - } - - /** - * Returns the file info as an array with the following keys: - * - * - dirname - * - basename - * - extension - * - filename - * - filesize - * - mime - * - * @return array File information. - */ - public function info(): array - { - if (!$this->info) { - $this->info = pathinfo($this->path); - } - - $this->info['filename'] = $this->info['filename'] ?? $this->name(); - $this->info['filesize'] = $this->info['filesize'] ?? $this->size(); - $this->info['mime'] = $this->info['mime'] ?? $this->mime(); - - return $this->info; - } - - /** - * Returns the file extension. - * - * @return string|false The file extension, false if extension cannot be extracted. - */ - public function ext() - { - if (!$this->info) { - $this->info(); - } - - return $this->info['extension'] ?? false; - } - - /** - * Returns the file name without extension. - * - * @return string|false The file name without extension, false if name cannot be extracted. - */ - public function name() - { - if (!$this->info) { - $this->info(); - } - if (isset($this->info['extension'])) { - return static::_basename($this->name, '.' . $this->info['extension']); - } - if ($this->name) { - return $this->name; - } - - return false; - } - - /** - * Returns the file basename. simulate the php basename() for multibyte (mb_basename). - * - * @param string $path Path to file - * @param string|null $ext The name of the extension - * @return string the file basename. - */ - protected static function _basename(string $path, ?string $ext = null): string - { - // check for multibyte string and use basename() if not found - if (mb_strlen($path) === strlen($path)) { - return $ext === null ? basename($path) : basename($path, $ext); - } - - $splInfo = new SplFileInfo($path); - $name = ltrim($splInfo->getFilename(), '/\\'); - - if ($ext === null || $ext === '') { - return $name; - } - $ext = preg_quote($ext); - $new = preg_replace("/({$ext})$/u", '', $name); - - // basename of '/etc/.d' is '.d' not '' - return $new === '' ? $name : $new; - } - - /** - * Makes file name safe for saving - * - * @param string|null $name The name of the file to make safe if different from $this->name - * @param string|null $ext The name of the extension to make safe if different from $this->ext - * @return string The extension of the file - */ - public function safe(?string $name = null, ?string $ext = null): string - { - if (!$name) { - $name = (string)$this->name; - } - if (!$ext) { - $ext = (string)$this->ext(); - } - - return preg_replace("/(?:[^\w\.-]+)/", '_', static::_basename($name, $ext)); - } - - /** - * Get md5 Checksum of file with previous check of Filesize - * - * @param int|true $maxsize in MB or true to force - * @return string|false md5 Checksum {@link https://secure.php.net/md5_file See md5_file()}, - * or false in case of an error. - */ - public function md5($maxsize = 5) - { - if ($maxsize === true) { - return md5_file($this->path); - } - - $size = $this->size(); - if ($size && $size < $maxsize * 1024 * 1024) { - return md5_file($this->path); - } - - return false; - } - - /** - * Returns the full path of the file. - * - * @return string|null Full path to the file, or null on failure - */ - public function pwd() - { - if ($this->path === null) { - $dir = $this->Folder->pwd(); - if ($dir && is_dir($dir)) { - $this->path = $this->Folder->slashTerm($dir) . $this->name; - } - } - - return $this->path; - } - - /** - * Returns true if the file exists. - * - * @return bool True if it exists, false otherwise - */ - public function exists(): bool - { - $this->clearStatCache(); - - return $this->path && file_exists($this->path) && is_file($this->path); - } - - /** - * Returns the "chmod" (permissions) of the file. - * - * @return string|false Permissions for the file, or false in case of an error - */ - public function perms() - { - if ($this->exists()) { - return decoct(fileperms($this->path) & 0777); - } - - return false; - } - - /** - * Returns the file size - * - * @return int|false Size of the file in bytes, or false in case of an error - */ - public function size() - { - if ($this->exists()) { - return filesize($this->path); - } - - return false; - } - - /** - * Returns true if the file is writable. - * - * @return bool True if it's writable, false otherwise - */ - public function writable(): bool - { - return is_writable($this->path); - } - - /** - * Returns true if the File is executable. - * - * @return bool True if it's executable, false otherwise - */ - public function executable(): bool - { - return is_executable($this->path); - } - - /** - * Returns true if the file is readable. - * - * @return bool True if file is readable, false otherwise - */ - public function readable(): bool - { - return is_readable($this->path); - } - - /** - * Returns the file's owner. - * - * @return int|false The file owner, or bool in case of an error - */ - public function owner() - { - if ($this->exists()) { - return fileowner($this->path); - } - - return false; - } - - /** - * Returns the file's group. - * - * @return int|false The file group, or false in case of an error - */ - public function group() - { - if ($this->exists()) { - return filegroup($this->path); - } - - return false; - } - - /** - * Returns last access time. - * - * @return int|false Timestamp of last access time, or false in case of an error - */ - public function lastAccess() - { - if ($this->exists()) { - return fileatime($this->path); - } - - return false; - } - - /** - * Returns last modified time. - * - * @return int|false Timestamp of last modification, or false in case of an error - */ - public function lastChange() - { - if ($this->exists()) { - return filemtime($this->path); - } - - return false; - } - - /** - * Returns the current folder. - * - * @return \Cake\Filesystem\Folder Current folder - */ - public function folder(): Folder - { - return $this->Folder; - } - - /** - * Copy the File to $dest - * - * @param string $dest Absolute path to copy the file to. - * @param bool $overwrite Overwrite $dest if exists - * @return bool Success - */ - public function copy(string $dest, bool $overwrite = true): bool - { - if (!$this->exists() || is_file($dest) && !$overwrite) { - return false; - } - - return copy($this->path, $dest); - } - - /** - * Gets the mime type of the file. Uses the finfo extension if - * it's available, otherwise falls back to mime_content_type(). - * - * @return string|false The mimetype of the file, or false if reading fails. - */ - public function mime() - { - if (!$this->exists()) { - return false; - } - if (class_exists('finfo')) { - $finfo = new finfo(FILEINFO_MIME); - $type = $finfo->file($this->pwd()); - if (!$type) { - return false; - } - [$type] = explode(';', $type); - - return $type; - } - if (function_exists('mime_content_type')) { - return mime_content_type($this->pwd()); - } - - return false; - } - - /** - * Clear PHP's internal stat cache - * - * @param bool $all Clear all cache or not. Passing false will clear - * the stat cache for the current path only. - * @return void - */ - public function clearStatCache($all = false): void - { - if ($all === false && $this->path) { - clearstatcache(true, $this->path); - } - - clearstatcache(); - } - - /** - * Searches for a given text and replaces the text if found. - * - * @param array|string $search Text(s) to search for. - * @param array|string $replace Text(s) to replace with. - * @return bool Success - */ - public function replaceText($search, $replace): bool - { - if (!$this->open('r+')) { - return false; - } - - if ($this->lock !== null && flock($this->handle, LOCK_EX) === false) { - return false; - } - - $replaced = $this->write(str_replace($search, $replace, $this->read()), 'w', true); - - if ($this->lock !== null) { - flock($this->handle, LOCK_UN); - } - $this->close(); - - return $replaced; - } -} diff --git a/app/vendor/cakephp/cakephp/src/Filesystem/Folder.php b/app/vendor/cakephp/cakephp/src/Filesystem/Folder.php deleted file mode 100644 index 70ac122e7..000000000 --- a/app/vendor/cakephp/cakephp/src/Filesystem/Folder.php +++ /dev/null @@ -1,987 +0,0 @@ - - */ - protected $_fsorts = [ - self::SORT_NAME => 'getPathname', - self::SORT_TIME => 'getCTime', - ]; - - /** - * Holds messages from last method. - * - * @var array - */ - protected $_messages = []; - - /** - * Holds errors from last method. - * - * @var array - */ - protected $_errors = []; - - /** - * Holds array of complete directory paths. - * - * @var array - */ - protected $_directories; - - /** - * Holds array of complete file paths. - * - * @var array - */ - protected $_files; - - /** - * Constructor. - * - * @param string|null $path Path to folder - * @param bool $create Create folder if not found - * @param int|null $mode Mode (CHMOD) to apply to created folder, false to ignore - */ - public function __construct(?string $path = null, bool $create = false, ?int $mode = null) - { - if (empty($path)) { - $path = TMP; - } - if ($mode) { - $this->mode = $mode; - } - - if (!file_exists($path) && $create === true) { - $this->create($path, $this->mode); - } - if (!Folder::isAbsolute($path)) { - $path = realpath($path); - } - if (!empty($path)) { - $this->cd($path); - } - } - - /** - * Return current path. - * - * @return string|null Current path - */ - public function pwd(): ?string - { - return $this->path; - } - - /** - * Change directory to $path. - * - * @param string $path Path to the directory to change to - * @return string|false The new path. Returns false on failure - */ - public function cd(string $path) - { - $path = $this->realpath($path); - if ($path !== false && is_dir($path)) { - return $this->path = $path; - } - - return false; - } - - /** - * Returns an array of the contents of the current directory. - * The returned array holds two arrays: One of directories and one of files. - * - * @param string|bool $sort Whether you want the results sorted, set this and the sort property - * to false to get unsorted results. - * @param array|bool $exceptions Either an array or boolean true will not grab dot files - * @param bool $fullPath True returns the full path - * @return array Contents of current directory as an array, an empty array on failure - */ - public function read($sort = self::SORT_NAME, $exceptions = false, bool $fullPath = false): array - { - $dirs = $files = []; - - if (!$this->pwd()) { - return [$dirs, $files]; - } - if (is_array($exceptions)) { - $exceptions = array_flip($exceptions); - } - $skipHidden = isset($exceptions['.']) || $exceptions === true; - - try { - $iterator = new DirectoryIterator($this->path); - } catch (Exception $e) { - return [$dirs, $files]; - } - - if (!is_bool($sort) && isset($this->_fsorts[$sort])) { - $methodName = $this->_fsorts[$sort]; - } else { - $methodName = $this->_fsorts[self::SORT_NAME]; - } - - foreach ($iterator as $item) { - if ($item->isDot()) { - continue; - } - $name = $item->getFilename(); - if ($skipHidden && $name[0] === '.' || isset($exceptions[$name])) { - continue; - } - if ($fullPath) { - $name = $item->getPathname(); - } - - if ($item->isDir()) { - $dirs[$item->{$methodName}()][] = $name; - } else { - $files[$item->{$methodName}()][] = $name; - } - } - - if ($sort || $this->sort) { - ksort($dirs); - ksort($files); - } - - if ($dirs) { - $dirs = array_merge(...array_values($dirs)); - } - - if ($files) { - $files = array_merge(...array_values($files)); - } - - return [$dirs, $files]; - } - - /** - * Returns an array of all matching files in current directory. - * - * @param string $regexpPattern Preg_match pattern (Defaults to: .*) - * @param string|bool $sort Whether results should be sorted. - * @return array Files that match given pattern - */ - public function find(string $regexpPattern = '.*', $sort = false): array - { - [, $files] = $this->read($sort); - - return array_values(preg_grep('/^' . $regexpPattern . '$/i', $files)); - } - - /** - * Returns an array of all matching files in and below current directory. - * - * @param string $pattern Preg_match pattern (Defaults to: .*) - * @param string|bool $sort Whether results should be sorted. - * @return array Files matching $pattern - */ - public function findRecursive(string $pattern = '.*', $sort = false): array - { - if (!$this->pwd()) { - return []; - } - $startsOn = $this->path; - $out = $this->_findRecursive($pattern, $sort); - $this->cd($startsOn); - - return $out; - } - - /** - * Private helper function for findRecursive. - * - * @param string $pattern Pattern to match against - * @param bool $sort Whether results should be sorted. - * @return array Files matching pattern - */ - protected function _findRecursive(string $pattern, bool $sort = false): array - { - [$dirs, $files] = $this->read($sort); - $found = []; - - foreach ($files as $file) { - if (preg_match('/^' . $pattern . '$/i', $file)) { - $found[] = Folder::addPathElement($this->path, $file); - } - } - $start = $this->path; - - foreach ($dirs as $dir) { - $this->cd(Folder::addPathElement($start, $dir)); - $found = array_merge($found, $this->findRecursive($pattern, $sort)); - } - - return $found; - } - - /** - * Returns true if given $path is a Windows path. - * - * @param string $path Path to check - * @return bool true if windows path, false otherwise - */ - public static function isWindowsPath(string $path): bool - { - return preg_match('/^[A-Z]:\\\\/i', $path) || substr($path, 0, 2) === '\\\\'; - } - - /** - * Returns true if given $path is an absolute path. - * - * @param string $path Path to check - * @return bool true if path is absolute. - */ - public static function isAbsolute(string $path): bool - { - if (empty($path)) { - return false; - } - - return $path[0] === '/' || - preg_match('/^[A-Z]:\\\\/i', $path) || - substr($path, 0, 2) === '\\\\' || - self::isRegisteredStreamWrapper($path); - } - - /** - * Returns true if given $path is a registered stream wrapper. - * - * @param string $path Path to check - * @return bool True if path is registered stream wrapper. - */ - public static function isRegisteredStreamWrapper(string $path): bool - { - return preg_match('/^[^:\/]+?(?=:\/\/)/', $path, $matches) && - in_array($matches[0], stream_get_wrappers(), true); - } - - /** - * Returns a correct set of slashes for given $path. (\\ for Windows paths and / for other paths.) - * - * @param string $path Path to transform - * @return string Path with the correct set of slashes ("\\" or "/") - */ - public static function normalizeFullPath(string $path): string - { - $to = Folder::correctSlashFor($path); - $from = ($to === '/' ? '\\' : '/'); - - return str_replace($from, $to, $path); - } - - /** - * Returns a correct set of slashes for given $path. (\\ for Windows paths and / for other paths.) - * - * @param string $path Path to check - * @return string Set of slashes ("\\" or "/") - */ - public static function correctSlashFor(string $path): string - { - return Folder::isWindowsPath($path) ? '\\' : '/'; - } - - /** - * Returns $path with added terminating slash (corrected for Windows or other OS). - * - * @param string $path Path to check - * @return string Path with ending slash - */ - public static function slashTerm(string $path): string - { - if (Folder::isSlashTerm($path)) { - return $path; - } - - return $path . Folder::correctSlashFor($path); - } - - /** - * Returns $path with $element added, with correct slash in-between. - * - * @param string $path Path - * @param array|string $element Element to add at end of path - * @return string Combined path - */ - public static function addPathElement(string $path, $element): string - { - $element = (array)$element; - array_unshift($element, rtrim($path, DIRECTORY_SEPARATOR)); - - return implode(DIRECTORY_SEPARATOR, $element); - } - - /** - * Returns true if the Folder is in the given path. - * - * @param string $path The absolute path to check that the current `pwd()` resides within. - * @param bool $reverse Reverse the search, check if the given `$path` resides within the current `pwd()`. - * @return bool - * @throws \InvalidArgumentException When the given `$path` argument is not an absolute path. - */ - public function inPath(string $path, bool $reverse = false): bool - { - if (!Folder::isAbsolute($path)) { - throw new InvalidArgumentException('The $path argument is expected to be an absolute path.'); - } - - $dir = Folder::slashTerm($path); - $current = Folder::slashTerm($this->pwd()); - - if (!$reverse) { - $return = preg_match('/^' . preg_quote($dir, '/') . '(.*)/', $current); - } else { - $return = preg_match('/^' . preg_quote($current, '/') . '(.*)/', $dir); - } - - return (bool)$return; - } - - /** - * Change the mode on a directory structure recursively. This includes changing the mode on files as well. - * - * @param string $path The path to chmod. - * @param int|null $mode Octal value, e.g. 0755. - * @param bool $recursive Chmod recursively, set to false to only change the current directory. - * @param array $exceptions Array of files, directories to skip. - * @return bool Success. - */ - public function chmod(string $path, ?int $mode = null, bool $recursive = true, array $exceptions = []): bool - { - if (!$mode) { - $mode = $this->mode; - } - - if ($recursive === false && is_dir($path)) { - // phpcs:disable - if (@chmod($path, intval($mode, 8))) { - // phpcs:enable - $this->_messages[] = sprintf('%s changed to %s', $path, $mode); - - return true; - } - - $this->_errors[] = sprintf('%s NOT changed to %s', $path, $mode); - - return false; - } - - if (is_dir($path)) { - $paths = $this->tree($path); - - foreach ($paths as $type) { - foreach ($type as $fullpath) { - $check = explode(DIRECTORY_SEPARATOR, $fullpath); - $count = count($check); - - if (in_array($check[$count - 1], $exceptions, true)) { - continue; - } - - // phpcs:disable - if (@chmod($fullpath, intval($mode, 8))) { - // phpcs:enable - $this->_messages[] = sprintf('%s changed to %s', $fullpath, $mode); - } else { - $this->_errors[] = sprintf('%s NOT changed to %s', $fullpath, $mode); - } - } - } - - if (empty($this->_errors)) { - return true; - } - } - - return false; - } - - /** - * Returns an array of subdirectories for the provided or current path. - * - * @param string|null $path The directory path to get subdirectories for. - * @param bool $fullPath Whether to return the full path or only the directory name. - * @return array Array of subdirectories for the provided or current path. - */ - public function subdirectories(?string $path = null, bool $fullPath = true): array - { - if (!$path) { - $path = $this->path; - } - $subdirectories = []; - - try { - $iterator = new DirectoryIterator($path); - } catch (Exception $e) { - return []; - } - - foreach ($iterator as $item) { - if (!$item->isDir() || $item->isDot()) { - continue; - } - $subdirectories[] = $fullPath ? $item->getRealPath() : $item->getFilename(); - } - - return $subdirectories; - } - - /** - * Returns an array of nested directories and files in each directory - * - * @param string|null $path the directory path to build the tree from - * @param array|bool $exceptions Either an array of files/folder to exclude - * or boolean true to not grab dot files/folders - * @param string|null $type either 'file' or 'dir'. Null returns both files and directories - * @return array Array of nested directories and files in each directory - */ - public function tree(?string $path = null, $exceptions = false, ?string $type = null): array - { - if (!$path) { - $path = $this->path; - } - $files = []; - $directories = [$path]; - - if (is_array($exceptions)) { - $exceptions = array_flip($exceptions); - } - $skipHidden = false; - if ($exceptions === true) { - $skipHidden = true; - } elseif (isset($exceptions['.'])) { - $skipHidden = true; - unset($exceptions['.']); - } - - try { - $directory = new RecursiveDirectoryIterator( - $path, - RecursiveDirectoryIterator::KEY_AS_PATHNAME | RecursiveDirectoryIterator::CURRENT_AS_SELF - ); - $iterator = new RecursiveIteratorIterator($directory, RecursiveIteratorIterator::SELF_FIRST); - } catch (Exception $e) { - unset($directory, $iterator); - - if ($type === null) { - return [[], []]; - } - - return []; - } - - /** - * @var string $itemPath - * @var \RecursiveDirectoryIterator $fsIterator - */ - foreach ($iterator as $itemPath => $fsIterator) { - if ($skipHidden) { - $subPathName = $fsIterator->getSubPathname(); - if ($subPathName[0] === '.' || strpos($subPathName, DIRECTORY_SEPARATOR . '.') !== false) { - unset($fsIterator); - continue; - } - } - /** @var \FilesystemIterator $item */ - $item = $fsIterator->current(); - if (!empty($exceptions) && isset($exceptions[$item->getFilename()])) { - unset($fsIterator, $item); - continue; - } - - if ($item->isFile()) { - $files[] = $itemPath; - } elseif ($item->isDir() && !$item->isDot()) { - $directories[] = $itemPath; - } - - // inner iterators need to be unset too in order for locks on parents to be released - unset($fsIterator, $item); - } - - // unsetting iterators helps releasing possible locks in certain environments, - // which could otherwise make `rmdir()` fail - unset($directory, $iterator); - - if ($type === null) { - return [$directories, $files]; - } - if ($type === 'dir') { - return $directories; - } - - return $files; - } - - /** - * Create a directory structure recursively. - * - * Can be used to create deep path structures like `/foo/bar/baz/shoe/horn` - * - * @param string $pathname The directory structure to create. Either an absolute or relative - * path. If the path is relative and exists in the process' cwd it will not be created. - * Otherwise, relative paths will be prefixed with the current pwd(). - * @param int|null $mode octal value 0755 - * @return bool Returns TRUE on success, FALSE on failure - */ - public function create(string $pathname, ?int $mode = null): bool - { - if (is_dir($pathname) || empty($pathname)) { - return true; - } - - if (!self::isAbsolute($pathname)) { - $pathname = self::addPathElement($this->pwd(), $pathname); - } - - if (!$mode) { - $mode = $this->mode; - } - - if (is_file($pathname)) { - $this->_errors[] = sprintf('%s is a file', $pathname); - - return false; - } - $pathname = rtrim($pathname, DIRECTORY_SEPARATOR); - $nextPathname = substr($pathname, 0, strrpos($pathname, DIRECTORY_SEPARATOR)); - - if ($this->create($nextPathname, $mode)) { - if (!file_exists($pathname)) { - $old = umask(0); - if (mkdir($pathname, $mode, true)) { - $this->_messages[] = sprintf('%s created', $pathname); - umask($old); - - return true; - } - $this->_errors[] = sprintf('%s NOT created', $pathname); - umask($old); - - return false; - } - } - - return false; - } - - /** - * Returns the size in bytes of this Folder and its contents. - * - * @return int size in bytes of current folder - */ - public function dirsize(): int - { - $size = 0; - $directory = Folder::slashTerm($this->path); - $stack = [$directory]; - $count = count($stack); - for ($i = 0, $j = $count; $i < $j; $i++) { - if (is_file($stack[$i])) { - $size += filesize($stack[$i]); - } elseif (is_dir($stack[$i])) { - $dir = dir($stack[$i]); - if ($dir) { - while (($entry = $dir->read()) !== false) { - if ($entry === '.' || $entry === '..') { - continue; - } - $add = $stack[$i] . $entry; - - if (is_dir($stack[$i] . $entry)) { - $add = Folder::slashTerm($add); - } - $stack[] = $add; - } - $dir->close(); - } - } - $j = count($stack); - } - - return $size; - } - - /** - * Recursively Remove directories if the system allows. - * - * @param string|null $path Path of directory to delete - * @return bool Success - */ - public function delete(?string $path = null): bool - { - if (!$path) { - $path = $this->pwd(); - } - if (!$path) { - return false; - } - $path = Folder::slashTerm($path); - if (is_dir($path)) { - try { - $directory = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::CURRENT_AS_SELF); - $iterator = new RecursiveIteratorIterator($directory, RecursiveIteratorIterator::CHILD_FIRST); - } catch (Exception $e) { - unset($directory, $iterator); - - return false; - } - - foreach ($iterator as $item) { - $filePath = $item->getPathname(); - if ($item->isFile() || $item->isLink()) { - // phpcs:disable - if (@unlink($filePath)) { - // phpcs:enable - $this->_messages[] = sprintf('%s removed', $filePath); - } else { - $this->_errors[] = sprintf('%s NOT removed', $filePath); - } - } elseif ($item->isDir() && !$item->isDot()) { - // phpcs:disable - if (@rmdir($filePath)) { - // phpcs:enable - $this->_messages[] = sprintf('%s removed', $filePath); - } else { - $this->_errors[] = sprintf('%s NOT removed', $filePath); - - unset($directory, $iterator, $item); - - return false; - } - } - - // inner iterators need to be unset too in order for locks on parents to be released - unset($item); - } - - // unsetting iterators helps releasing possible locks in certain environments, - // which could otherwise make `rmdir()` fail - unset($directory, $iterator); - - $path = rtrim($path, DIRECTORY_SEPARATOR); - // phpcs:disable - if (@rmdir($path)) { - // phpcs:enable - $this->_messages[] = sprintf('%s removed', $path); - } else { - $this->_errors[] = sprintf('%s NOT removed', $path); - - return false; - } - } - - return true; - } - - /** - * Recursive directory copy. - * - * ### Options - * - * - `from` The directory to copy from, this will cause a cd() to occur, changing the results of `pwd()`. - * - `mode` The mode to copy the files/directories with as integer, e.g. `0770`. - * - `skip` Files/directories to skip. - * - `scheme` Folder::MERGE, Folder::OVERWRITE, Folder::SKIP - * - `recursive` Whether to copy recursively or not (default: true - recursive) - * - * @param string $to The directory to copy to. - * @param array $options Array of options (see above). - * @return bool Success. - */ - public function copy(string $to, array $options = []): bool - { - if (!$this->pwd()) { - return false; - } - $options += [ - 'from' => $this->path, - 'mode' => $this->mode, - 'skip' => [], - 'scheme' => Folder::MERGE, - 'recursive' => true, - ]; - - $fromDir = $options['from']; - $toDir = $to; - $mode = $options['mode']; - - if (!$this->cd($fromDir)) { - $this->_errors[] = sprintf('%s not found', $fromDir); - - return false; - } - - if (!is_dir($toDir)) { - $this->create($toDir, $mode); - } - - if (!is_writable($toDir)) { - $this->_errors[] = sprintf('%s not writable', $toDir); - - return false; - } - - $exceptions = array_merge(['.', '..', '.svn'], $options['skip']); - // phpcs:disable - if ($handle = @opendir($fromDir)) { - // phpcs:enable - while (($item = readdir($handle)) !== false) { - $to = Folder::addPathElement($toDir, $item); - if (($options['scheme'] !== Folder::SKIP || !is_dir($to)) && !in_array($item, $exceptions, true)) { - $from = Folder::addPathElement($fromDir, $item); - if (is_file($from) && (!is_file($to) || $options['scheme'] !== Folder::SKIP)) { - if (copy($from, $to)) { - chmod($to, intval($mode, 8)); - touch($to, filemtime($from)); - $this->_messages[] = sprintf('%s copied to %s', $from, $to); - } else { - $this->_errors[] = sprintf('%s NOT copied to %s', $from, $to); - } - } - - if (is_dir($from) && file_exists($to) && $options['scheme'] === Folder::OVERWRITE) { - $this->delete($to); - } - - if (is_dir($from) && $options['recursive'] === false) { - continue; - } - - if (is_dir($from) && !file_exists($to)) { - $old = umask(0); - if (mkdir($to, $mode, true)) { - umask($old); - $old = umask(0); - chmod($to, $mode); - umask($old); - $this->_messages[] = sprintf('%s created', $to); - $options = ['from' => $from] + $options; - $this->copy($to, $options); - } else { - $this->_errors[] = sprintf('%s not created', $to); - } - } elseif (is_dir($from) && $options['scheme'] === Folder::MERGE) { - $options = ['from' => $from] + $options; - $this->copy($to, $options); - } - } - } - closedir($handle); - } else { - return false; - } - - return empty($this->_errors); - } - - /** - * Recursive directory move. - * - * ### Options - * - * - `from` The directory to copy from, this will cause a cd() to occur, changing the results of `pwd()`. - * - `mode` The mode to copy the files/directories with as integer, e.g. `0770`. - * - `skip` Files/directories to skip. - * - `scheme` Folder::MERGE, Folder::OVERWRITE, Folder::SKIP - * - `recursive` Whether to copy recursively or not (default: true - recursive) - * - * @param string $to The directory to move to. - * @param array $options Array of options (see above). - * @return bool Success - */ - public function move(string $to, array $options = []): bool - { - $options += ['from' => $this->path, 'mode' => $this->mode, 'skip' => [], 'recursive' => true]; - - if ($this->copy($to, $options) && $this->delete($options['from'])) { - return (bool)$this->cd($to); - } - - return false; - } - - /** - * get messages from latest method - * - * @param bool $reset Reset message stack after reading - * @return array - */ - public function messages(bool $reset = true): array - { - $messages = $this->_messages; - if ($reset) { - $this->_messages = []; - } - - return $messages; - } - - /** - * get error from latest method - * - * @param bool $reset Reset error stack after reading - * @return array - */ - public function errors(bool $reset = true): array - { - $errors = $this->_errors; - if ($reset) { - $this->_errors = []; - } - - return $errors; - } - - /** - * Get the real path (taking ".." and such into account) - * - * @param string $path Path to resolve - * @return string|false The resolved path - */ - public function realpath($path) - { - if (strpos($path, '..') === false) { - if (!Folder::isAbsolute($path)) { - $path = Folder::addPathElement($this->path, $path); - } - - return $path; - } - $path = str_replace('/', DIRECTORY_SEPARATOR, trim($path)); - $parts = explode(DIRECTORY_SEPARATOR, $path); - $newparts = []; - $newpath = ''; - if ($path[0] === DIRECTORY_SEPARATOR) { - $newpath = DIRECTORY_SEPARATOR; - } - - while (($part = array_shift($parts)) !== null) { - if ($part === '.' || $part === '') { - continue; - } - if ($part === '..') { - if (!empty($newparts)) { - array_pop($newparts); - continue; - } - - return false; - } - $newparts[] = $part; - } - $newpath .= implode(DIRECTORY_SEPARATOR, $newparts); - - return Folder::slashTerm($newpath); - } - - /** - * Returns true if given $path ends in a slash (i.e. is slash-terminated). - * - * @param string $path Path to check - * @return bool true if path ends with slash, false otherwise - */ - public static function isSlashTerm(string $path): bool - { - $lastChar = $path[strlen($path) - 1]; - - return $lastChar === '/' || $lastChar === '\\'; - } -} diff --git a/app/vendor/cakephp/cakephp/src/Filesystem/LICENSE.txt b/app/vendor/cakephp/cakephp/src/Filesystem/LICENSE.txt deleted file mode 100644 index b938c9e8e..000000000 --- a/app/vendor/cakephp/cakephp/src/Filesystem/LICENSE.txt +++ /dev/null @@ -1,22 +0,0 @@ -The MIT License (MIT) - -CakePHP(tm) : The Rapid Development PHP Framework (https://cakephp.org) -Copyright (c) 2005-2020, Cake Software Foundation, Inc. (https://cakefoundation.org) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/app/vendor/cakephp/cakephp/src/Filesystem/README.md b/app/vendor/cakephp/cakephp/src/Filesystem/README.md deleted file mode 100644 index ce2fa5c15..000000000 --- a/app/vendor/cakephp/cakephp/src/Filesystem/README.md +++ /dev/null @@ -1,37 +0,0 @@ -[![Total Downloads](https://img.shields.io/packagist/dt/cakephp/filesystem.svg?style=flat-square)](https://packagist.org/packages/cakephp/filesystem) -[![License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](LICENSE.txt) - -# This package has been deprecated. - -## CakePHP Filesystem Library - -The Folder and File utilities are convenience classes to help you read from and write/append to files; list files within a folder and other common directory related tasks. - -## Basic Usage - -Create a folder instance and search for all the `.php` files within it: - -```php -use Cake\Filesystem\Folder; - -$dir = new Folder('/path/to/folder'); -$files = $dir->find('.*\.php'); -``` - -Now you can loop through the files and read from or write/append to the contents or simply delete the file: - -```php -foreach ($files as $file) { - $file = new File($dir->pwd() . DIRECTORY_SEPARATOR . $file); - $contents = $file->read(); - // $file->write('I am overwriting the contents of this file'); - // $file->append('I am adding to the bottom of this file.'); - // $file->delete(); // I am deleting this file - $file->close(); // Be sure to close the file when you're done -} -``` - -## Documentation - -Please make sure you check the [official -documentation](https://book.cakephp.org/4/en/core-libraries/file-folder.html) diff --git a/app/vendor/cakephp/cakephp/src/Filesystem/composer.json b/app/vendor/cakephp/cakephp/src/Filesystem/composer.json deleted file mode 100644 index 82688b7c1..000000000 --- a/app/vendor/cakephp/cakephp/src/Filesystem/composer.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "cakephp/filesystem", - "description": "CakePHP filesystem convenience classes to help you work with files and folders.", - "type": "library", - "keywords": [ - "cakephp", - "filesystem", - "files", - "folders" - ], - "homepage": "https://cakephp.org", - "license": "MIT", - "authors": [ - { - "name": "CakePHP Community", - "homepage": "https://github.com/cakephp/filesystem/graphs/contributors" - } - ], - "support": { - "issues": "https://github.com/cakephp/cakephp/issues", - "forum": "https://stackoverflow.com/tags/cakephp", - "irc": "irc://irc.freenode.org/cakephp", - "source": "https://github.com/cakephp/filesystem" - }, - "require": { - "php": ">=7.4.0", - "cakephp/core": "^4.0" - }, - "autoload": { - "psr-4": { - "Cake\\Filesystem\\": "." - } - } -} diff --git a/app/vendor/cakephp/cakephp/src/Form/Form.php b/app/vendor/cakephp/cakephp/src/Form/Form.php index 29f8b198b..ff492c193 100644 --- a/app/vendor/cakephp/cakephp/src/Form/Form.php +++ b/app/vendor/cakephp/cakephp/src/Form/Form.php @@ -23,7 +23,6 @@ use Cake\Utility\Hash; use Cake\Validation\ValidatorAwareInterface; use Cake\Validation\ValidatorAwareTrait; -use function Cake\Core\deprecationWarning; /** * Form abstraction used to create forms not tied to ORM backed models, @@ -38,9 +37,14 @@ * fields, validation and primary action respectively. * * Forms are conventionally placed in the `App\Form` namespace. + * + * @implements \Cake\Event\EventDispatcherInterface<\Cake\Form\Form> */ class Form implements EventListenerInterface, EventDispatcherInterface, ValidatorAwareInterface { + /** + * @use \Cake\Event\EventDispatcherTrait<\Cake\Form\Form> + */ use EventDispatcherTrait; use ValidatorAwareTrait; @@ -69,30 +73,30 @@ class Form implements EventListenerInterface, EventDispatcherInterface, Validato * Schema class. * * @var string - * @psalm-var class-string<\Cake\Form\Schema> + * @phpstan-var class-string<\Cake\Form\Schema> */ - protected $_schemaClass = Schema::class; + protected string $_schemaClass = Schema::class; /** * The schema used by this form. * * @var \Cake\Form\Schema|null */ - protected $_schema; + protected ?Schema $_schema = null; /** * The errors if any * * @var array */ - protected $_errors = []; + protected array $_errors = []; /** * Form's data. * * @var array */ - protected $_data = []; + protected array $_data = []; /** * Constructor @@ -107,14 +111,6 @@ public function __construct(?EventManager $eventManager = null) } $this->getEventManager()->on($this); - - if (method_exists($this, '_buildValidator')) { - deprecationWarning( - static::class . ' implements `_buildValidator` which is no longer used. ' . - 'You should implement `buildValidator(Validator $validator, string $name): void` ' . - 'or `validationDefault(Validator $validator): Validator` instead.' - ); - } } /** @@ -163,34 +159,11 @@ public function setSchema(Schema $schema) */ public function getSchema(): Schema { - if ($this->_schema === null) { - $this->_schema = $this->_buildSchema(new $this->_schemaClass()); - } + $this->_schema ??= $this->_buildSchema(new $this->_schemaClass()); return $this->_schema; } - /** - * Get/Set the schema for this form. - * - * This method will call `_buildSchema()` when the schema - * is first built. This hook method lets you configure the - * schema or load a pre-defined one. - * - * @deprecated 4.1.0 Use {@link setSchema()}/{@link getSchema()} instead. - * @param \Cake\Form\Schema|null $schema The schema to set, or null. - * @return \Cake\Form\Schema the schema instance. - */ - public function schema(?Schema $schema = null): Schema - { - deprecationWarning('Form::schema() is deprecated. Use setSchema() and getSchema() instead.'); - if ($schema !== null) { - $this->setSchema($schema); - } - - return $this->getSchema(); - } - /** * A hook method intended to be implemented by subclasses. * @@ -219,7 +192,7 @@ public function validate(array $data, ?string $validator = null): bool $this->_errors = $this->getValidator($validator ?: static::DEFAULT_VALIDATOR) ->validate($data); - return count($this->_errors) === 0; + return $this->_errors === []; } /** @@ -297,7 +270,7 @@ public function execute(array $data, array $options = []): bool $validator = $options['validate'] === true ? static::DEFAULT_VALIDATOR : $options['validate']; - return $this->validate($data, $validator) ? $this->_execute($data) : false; + return $this->validate($data, $validator) && $this->_execute($data); } /** @@ -320,7 +293,7 @@ protected function _execute(array $data): bool * all fields. * @return mixed */ - public function getData(?string $field = null) + public function getData(?string $field = null): mixed { if ($field === null) { return $this->_data; @@ -337,14 +310,14 @@ public function getData(?string $field = null) * @param mixed $value Value to set for var * @return $this */ - public function set($name, $value = null) + public function set(array|string $name, mixed $value = null) { $write = $name; if (!is_array($name)) { $write = [$name => $value]; } - /** @psalm-suppress PossiblyInvalidIterator */ + /** @var array $write */ foreach ($write as $key => $val) { $this->_data = Hash::insert($this->_data, $key, $val); } diff --git a/app/vendor/cakephp/cakephp/src/Form/FormProtector.php b/app/vendor/cakephp/cakephp/src/Form/FormProtector.php index 757cfebab..77f7a6038 100644 --- a/app/vendor/cakephp/cakephp/src/Form/FormProtector.php +++ b/app/vendor/cakephp/cakephp/src/Form/FormProtector.php @@ -37,21 +37,21 @@ class FormProtector * * @var array */ - protected $fields = []; + protected array $fields = []; /** * Unlocked fields. * * @var array */ - protected $unlockedFields = []; + protected array $unlockedFields = []; /** * Error message providing detail for failed validation. * * @var string|null */ - protected $debugMessage; + protected ?string $debugMessage = null; /** * Validate submitted form data. @@ -61,12 +61,12 @@ class FormProtector * @param string $sessionId Session id for hash generation. * @return bool */ - public function validate($formData, string $url, string $sessionId): bool + public function validate(mixed $formData, string $url, string $sessionId): bool { $this->debugMessage = null; $extractedToken = $this->extractToken($formData); - if (empty($extractedToken)) { + if (!$extractedToken) { return false; } @@ -75,7 +75,7 @@ public function validate($formData, string $url, string $sessionId): bool $hashParts['fields'], $hashParts['unlockedFields'], $url, - $sessionId + $sessionId, ); if (hash_equals($generatedToken, $extractedToken)) { @@ -114,13 +114,13 @@ public function __construct(array $data = []) * @param mixed $value Field value, if value should not be tampered with. * @return $this */ - public function addField($field, bool $lock = true, $value = null) + public function addField(array|string $field, bool $lock = true, mixed $value = null) { if (is_string($field)) { $field = $this->getFieldNameArray($field); } - if (empty($field)) { + if (!$field) { return $this; } @@ -132,7 +132,7 @@ public function addField($field, bool $lock = true, $value = null) } $field = implode('.', $field); - $field = preg_replace('/(\.\d+)+$/', '', $field); + $field = (string)preg_replace('/(\.\d+)+$/', '', $field); if ($lock) { if (!in_array($field, $this->fields, true)) { @@ -164,11 +164,11 @@ public function addField($field, bool $lock = true, $value = null) */ protected function getFieldNameArray(string $name): array { - if (empty($name) && $name !== '0') { + if ($name === '') { return []; } - if (strpos($name, '[') === false) { + if (!str_contains($name, '[')) { return Hash::filter(explode('.', $name)); } $parts = explode('[', $name); @@ -187,7 +187,7 @@ protected function getFieldNameArray(string $name): array * @param string $name The dot separated name for the field. * @return $this */ - public function unlockField($name) + public function unlockField(string $name) { if (!in_array($name, $this->unlockedFields, true)) { $this->unlockedFields[] = $name; @@ -218,7 +218,7 @@ public function getError(): ?string * @param mixed $formData Data to validate. * @return string|null Fields token on success, null on failure. */ - protected function extractToken($formData): ?string + protected function extractToken(mixed $formData): ?string { if (!is_array($formData)) { $this->debugMessage = 'Request data is not an array.'; @@ -259,7 +259,7 @@ protected function extractToken($formData): ?string } $token = urldecode($formData['_Token']['fields']); - if (strpos($token, ':')) { + if (str_contains($token, ':')) { [$token, ] = explode(':', $token, 2); } @@ -270,8 +270,8 @@ protected function extractToken($formData): ?string * Return hash parts for the token generation * * @param array $formData Form data. - * @return array - * @psalm-return array{fields: array, unlockedFields: array} + * @return array Contains 'fields' and 'unlockedFields' keys. Additional keys allowed. + * @phpstan-return array{fields: array, unlockedFields: array, ...} */ protected function extractHashParts(array $formData): array { @@ -296,7 +296,7 @@ protected function extractFields(array $formData): array $token = urldecode($formData['_Token']['fields']); $unlocked = urldecode($formData['_Token']['unlocked']); - if (strpos($token, ':')) { + if (str_contains($token, ':')) { [, $locked] = explode(':', $token, 2); } unset($formData['_Token']); @@ -306,7 +306,8 @@ protected function extractFields(array $formData): array $fields = Hash::flatten($formData); $fieldList = array_keys($fields); - $multi = $lockedFields = []; + $multi = []; + $lockedFields = []; $isUnlocked = false; foreach ($fieldList as $i => $key) { @@ -317,28 +318,27 @@ protected function extractFields(array $formData): array $fieldList[$i] = (string)$key; } } - if (!empty($multi)) { + if ($multi) { $fieldList += array_unique($multi); } $unlockedFields = array_unique( array_merge( $this->unlockedFields, - $unlocked - ) + $unlocked, + ), ); + /** @var string $key */ foreach ($fieldList as $i => $key) { $isLocked = in_array($key, $locked, true); - if (!empty($unlockedFields)) { - foreach ($unlockedFields as $off) { - $off = explode('.', $off); - $field = array_values(array_intersect(explode('.', $key), $off)); - $isUnlocked = ($field === $off); - if ($isUnlocked) { - break; - } + foreach ($unlockedFields as $off) { + $off = explode('.', $off); + $field = array_values(array_intersect(explode('.', $key), $off)); + $isUnlocked = ($field === $off); + if ($isUnlocked) { + break; } } @@ -365,7 +365,7 @@ protected function extractFields(array $formData): array protected function sortedUnlockedFields(array $formData): array { $unlocked = urldecode($formData['_Token']['unlocked']); - if (empty($unlocked)) { + if (!$unlocked) { return []; } @@ -379,9 +379,9 @@ protected function sortedUnlockedFields(array $formData): array * Generate the token data. * * @param string $url Form URL. - * @param string $sessionId Session Id. - * @return array The token data. - * @psalm-return array{fields: string, unlocked: string, debug: string} + * @param string $sessionId Session ID. + * @return array The token data. Contains 'fields', 'unlocked', and 'debug' keys. Additional keys allowed. + * @phpstan-return array{fields: string, unlocked: string, debug: string, ...} */ public function buildTokenData(string $url = '', string $sessionId = ''): array { @@ -390,9 +390,14 @@ public function buildTokenData(string $url = '', string $sessionId = ''): array $locked = []; foreach ($fields as $key => $value) { - if (is_numeric($value)) { + if ($value === true) { + $value = '1'; + } elseif ($value === false) { + $value = '0'; + } elseif (is_numeric($value)) { $value = (string)$value; } + if (!is_int($key)) { $locked[$key] = $value; unset($fields[$key]); @@ -410,7 +415,7 @@ public function buildTokenData(string $url = '', string $sessionId = ''): array return [ 'fields' => urlencode($fields . ':' . $locked), 'unlocked' => urlencode(implode('|', $unlockedFields)), - 'debug' => urlencode(json_encode([ + 'debug' => urlencode((string)json_encode([ $url, $this->fields, $this->unlockedFields, @@ -427,7 +432,7 @@ public function buildTokenData(string $url = '', string $sessionId = ''): array * @param string $sessionId Session Id. * @return string */ - protected function generateHash(array $fields, array $unlockedFields, string $url, string $sessionId) + protected function generateHash(array $fields, array $unlockedFields, string $url, string $sessionId): string { $hashParts = [ $url, @@ -469,7 +474,7 @@ protected function debugTokenNotMatching(array $formData, array $hashParts): str $expectedFields, 'Unexpected field `%s` in POST data', 'Tampered field `%s` in POST data (expected value `%s` but found `%s`)', - 'Missing field `%s` in POST data' + 'Missing field `%s` in POST data', ); $expectedUnlockedFields = Hash::get($expectedParts, 2); $dataUnlockedFields = Hash::get($hashParts, 'unlockedFields') ?: []; @@ -478,7 +483,7 @@ protected function debugTokenNotMatching(array $formData, array $hashParts): str $expectedUnlockedFields, 'Unexpected unlocked field `%s` in POST data', '', - 'Missing unlocked field: `%s`' + 'Missing unlocked field: `%s`', ); $messages = array_merge($messages, $fieldsMessages, $unlockFieldsMessages); @@ -502,7 +507,7 @@ protected function debugCheckFields( array $expectedFields = [], string $intKeyMessage = '', string $stringKeyMessage = '', - string $missingMessage = '' + string $missingMessage = '', ): array { $messages = $this->matchExistingFields($dataFields, $expectedFields, $intKeyMessage, $stringKeyMessage); $expectedFieldsMessage = $this->debugExpectedFields($expectedFields, $missingMessage); @@ -528,7 +533,7 @@ protected function matchExistingFields( array $dataFields, array &$expectedFields, string $intKeyMessage, - string $stringKeyMessage + string $stringKeyMessage, ): array { $messages = []; foreach ($dataFields as $key => $value) { @@ -559,7 +564,7 @@ protected function matchExistingFields( */ protected function debugExpectedFields(array $expectedFields = [], string $missingMessage = ''): ?string { - if (count($expectedFields) === 0) { + if ($expectedFields === []) { return null; } diff --git a/app/vendor/cakephp/cakephp/src/Form/README.md b/app/vendor/cakephp/cakephp/src/Form/README.md index e7594cbc6..618c43a10 100644 --- a/app/vendor/cakephp/cakephp/src/Form/README.md +++ b/app/vendor/cakephp/cakephp/src/Form/README.md @@ -60,4 +60,4 @@ $errors = $contact->getErrors(); ## Documentation -Please make sure you check the [official documentation](https://book.cakephp.org/4/en/core-libraries/form.html) +Please make sure you check the [official documentation](https://book.cakephp.org/5/en/core-libraries/form.html) diff --git a/app/vendor/cakephp/cakephp/src/Form/Schema.php b/app/vendor/cakephp/cakephp/src/Form/Schema.php index ae8f7501b..02a40e6e0 100644 --- a/app/vendor/cakephp/cakephp/src/Form/Schema.php +++ b/app/vendor/cakephp/cakephp/src/Form/Schema.php @@ -26,14 +26,14 @@ class Schema * * @var array> */ - protected $_fields = []; + protected array $_fields = []; /** * The default values for fields. * * @var array */ - protected $_fieldDefaults = [ + protected array $_fieldDefaults = [ 'type' => null, 'length' => null, 'precision' => null, @@ -63,7 +63,7 @@ public function addFields(array $fields) * as a string. * @return $this */ - public function addField(string $name, $attrs) + public function addField(string $name, array|string $attrs) { if (is_string($attrs)) { $attrs = ['type' => $attrs]; diff --git a/app/vendor/cakephp/cakephp/src/Form/composer.json b/app/vendor/cakephp/cakephp/src/Form/composer.json index 016644ca5..9458465d8 100644 --- a/app/vendor/cakephp/cakephp/src/Form/composer.json +++ b/app/vendor/cakephp/cakephp/src/Form/composer.json @@ -21,13 +21,19 @@ "source": "https://github.com/cakephp/form" }, "require": { - "php": ">=7.4.0", - "cakephp/event": "^4.0", - "cakephp/validation": "^4.0" + "php": ">=8.1", + "cakephp/event": "5.2.*@dev", + "cakephp/validation":"5.2.*@dev" }, "autoload": { "psr-4": { "Cake\\Form\\": "." } + }, + "prefer-stable": true, + "extra": { + "branch-alias": { + "dev-5.x": "5.2.x-dev" + } } } diff --git a/app/vendor/cakephp/cakephp/src/Http/BaseApplication.php b/app/vendor/cakephp/cakephp/src/Http/BaseApplication.php index 3f2dbaf84..589b89c85 100644 --- a/app/vendor/cakephp/cakephp/src/Http/BaseApplication.php +++ b/app/vendor/cakephp/cakephp/src/Http/BaseApplication.php @@ -18,17 +18,19 @@ namespace Cake\Http; use Cake\Console\CommandCollection; -use Cake\Controller\ComponentRegistry; use Cake\Controller\ControllerFactory; use Cake\Core\ConsoleApplicationInterface; use Cake\Core\Container; use Cake\Core\ContainerApplicationInterface; use Cake\Core\ContainerInterface; +use Cake\Core\EventAwareApplicationInterface; use Cake\Core\Exception\MissingPluginException; use Cake\Core\HttpApplicationInterface; use Cake\Core\Plugin; use Cake\Core\PluginApplicationInterface; use Cake\Core\PluginCollection; +use Cake\Core\PluginInterface; +use Cake\Event\EventDispatcherInterface; use Cake\Event\EventDispatcherTrait; use Cake\Event\EventManager; use Cake\Event\EventManagerInterface; @@ -49,41 +51,50 @@ * The application class is responsible for bootstrapping the application, * and ensuring that middleware is attached. It is also invoked as the last piece * of middleware, and delegates request/response handling to the correct controller. + * + * @template TSubject of \Cake\Http\BaseApplication + * @implements \Cake\Event\EventDispatcherInterface + * @implements \Cake\Core\PluginApplicationInterface */ abstract class BaseApplication implements ConsoleApplicationInterface, ContainerApplicationInterface, + EventAwareApplicationInterface, + EventDispatcherInterface, HttpApplicationInterface, PluginApplicationInterface, RoutingApplicationInterface { + /** + * @use \Cake\Event\EventDispatcherTrait + */ use EventDispatcherTrait; /** * @var string Contains the path of the config directory */ - protected $configDir; + protected string $configDir; /** * Plugin Collection * * @var \Cake\Core\PluginCollection */ - protected $plugins; + protected PluginCollection $plugins; /** * Controller factory * * @var \Cake\Http\ControllerFactoryInterface|null */ - protected $controllerFactory; + protected ?ControllerFactoryInterface $controllerFactory = null; /** * Container * * @var \Cake\Core\ContainerInterface|null */ - protected $container; + protected ?ContainerInterface $container = null; /** * Constructor @@ -95,12 +106,13 @@ abstract class BaseApplication implements public function __construct( string $configDir, ?EventManagerInterface $eventManager = null, - ?ControllerFactoryInterface $controllerFactory = null + ?ControllerFactoryInterface $controllerFactory = null, ) { $this->configDir = rtrim($configDir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; - $this->plugins = Plugin::getCollection(); + $this->plugins = new PluginCollection(); $this->_eventManager = $eventManager ?: EventManager::instance(); $this->controllerFactory = $controllerFactory; + Plugin::setCollection($this->plugins); } /** @@ -145,11 +157,11 @@ public function addPlugin($name, array $config = []) * @param array $config The configuration data for the plugin if using a string for $name * @return $this */ - public function addOptionalPlugin($name, array $config = []) + public function addOptionalPlugin(PluginInterface|string $name, array $config = []) { try { $this->addPlugin($name, $config); - } catch (MissingPluginException $e) { + } catch (MissingPluginException) { // Do not halt if the plugin is missing } @@ -172,6 +184,12 @@ public function getPlugins(): PluginCollection public function bootstrap(): void { require_once $this->configDir . 'bootstrap.php'; + + // phpcs:ignore + $plugins = @include $this->configDir . 'plugins.php'; + if (is_array($plugins)) { + $this->plugins->addFromConfig($plugins); + } } /** @@ -241,6 +259,19 @@ public function pluginConsole(CommandCollection $commands): CommandCollection return $commands; } + /** + * @param \Cake\Event\EventManagerInterface $eventManager The global event manager to register listeners on + * @return \Cake\Event\EventManagerInterface + */ + public function pluginEvents(EventManagerInterface $eventManager): EventManagerInterface + { + foreach ($this->plugins->with('events') as $plugin) { + $eventManager = $plugin->events($eventManager); + } + + return $eventManager; + } + /** * Get the dependency injection container for the application. * @@ -251,11 +282,7 @@ public function pluginConsole(CommandCollection $commands): CommandCollection */ public function getContainer(): ContainerInterface { - if ($this->container === null) { - $this->container = $this->buildContainer(); - } - - return $this->container; + return $this->container ??= $this->buildContainer(); } /** @@ -292,6 +319,17 @@ public function services(ContainerInterface $container): void { } + /** + * Register application events. + * + * @param \Cake\Event\EventManagerInterface $eventManager The global event manager to register listeners on + * @return \Cake\Event\EventManagerInterface + */ + public function events(EventManagerInterface $eventManager): EventManagerInterface + { + return $eventManager; + } + /** * Invoke the application. * @@ -303,25 +341,24 @@ public function services(ContainerInterface $container): void * @return \Psr\Http\Message\ResponseInterface */ public function handle( - ServerRequestInterface $request + ServerRequestInterface $request, ): ResponseInterface { $container = $this->getContainer(); $container->add(ServerRequest::class, $request); $container->add(ContainerInterface::class, $container); - if ($this->controllerFactory === null) { - $this->controllerFactory = new ControllerFactory($container); - } + $eventManager = $this->events($this->getEventManager()); + $this->setEventManager($this->pluginEvents($eventManager)); + + $this->controllerFactory ??= new ControllerFactory($container); if (Router::getRequest() !== $request) { + assert($request instanceof ServerRequest); Router::setRequest($request); } $controller = $this->controllerFactory->create($request); - // This is needed for auto-wiring. Should be removed in 5.x - $container->add(ComponentRegistry::class, $controller->components()); - return $this->controllerFactory->invoke($controller); } } diff --git a/app/vendor/cakephp/cakephp/src/Http/Client.php b/app/vendor/cakephp/cakephp/src/Http/Client.php index e2dbd16ab..2fc653448 100644 --- a/app/vendor/cakephp/cakephp/src/Http/Client.php +++ b/app/vendor/cakephp/cakephp/src/Http/Client.php @@ -18,10 +18,13 @@ use Cake\Core\App; use Cake\Core\Exception\CakeException; use Cake\Core\InstanceConfigTrait; +use Cake\Event\EventDispatcherInterface; +use Cake\Event\EventDispatcherTrait; use Cake\Http\Client\Adapter\Curl; use Cake\Http\Client\Adapter\Mock as MockAdapter; use Cake\Http\Client\Adapter\Stream; use Cake\Http\Client\AdapterInterface; +use Cake\Http\Client\ClientEvent; use Cake\Http\Client\Request; use Cake\Http\Client\Response; use Cake\Http\Cookie\CookieCollection; @@ -100,9 +103,15 @@ * a proxy if you need to use one. The type sub option can be used to * specify which authentication strategy you want to use. * CakePHP comes with built-in support for basic authentication. + * + * @implements \Cake\Event\EventDispatcherInterface<\Cake\Http\Client> */ -class Client implements ClientInterface +class Client implements EventDispatcherInterface, ClientInterface { + /** + * @use \Cake\Event\EventDispatcherTrait<\Cake\Http\Client> + */ + use EventDispatcherTrait; use InstanceConfigTrait; /** @@ -110,7 +119,7 @@ class Client implements ClientInterface * * @var array */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'auth' => null, 'adapter' => null, 'host' => null, @@ -134,21 +143,21 @@ class Client implements ClientInterface * * @var \Cake\Http\Cookie\CookieCollection */ - protected $_cookies; + protected CookieCollection $_cookies; /** * Mock adapter for stubbing requests in tests. * * @var \Cake\Http\Client\Adapter\Mock|null */ - protected static $_mockAdapter; + protected static ?MockAdapter $_mockAdapter = null; /** * Adapter for sending requests. * * @var \Cake\Http\Client\AdapterInterface */ - protected $_adapter; + protected AdapterInterface $_adapter; /** * Create a new HTTP Client. @@ -181,10 +190,10 @@ class Client implements ClientInterface * to use. Short class names are resolved to the `Http\Client\Auth` namespace. * * @param array $config Config options for scoped clients. - * @throws \InvalidArgumentException */ public function __construct(array $config = []) { + $this->_eventClass = ClientEvent::class; $this->setConfig($config); $adapter = $this->_config['adapter']; @@ -202,9 +211,6 @@ public function __construct(array $config = []) $adapter = new $adapter(); } - if (!$adapter instanceof AdapterInterface) { - throw new InvalidArgumentException('Adapter must be an instance of Cake\Http\Client\AdapterInterface'); - } $this->_adapter = $adapter; if (!empty($this->_config['cookieJar'])) { @@ -225,12 +231,15 @@ public function __construct(array $config = []) * @return static * @throws \InvalidArgumentException */ - public static function createFromUrl(string $url) + public static function createFromUrl(string $url): static { $parts = parse_url($url); if ($parts === false) { - throw new InvalidArgumentException('String ' . $url . ' did not parse'); + throw new InvalidArgumentException(sprintf( + 'String `%s` did not parse.', + $url, + )); } $config = array_intersect_key($parts, ['scheme' => '', 'port' => '', 'host' => '', 'path' => '']); @@ -287,7 +296,7 @@ public function addCookie(CookieInterface $cookie) * @param array $options Additional options for the request. * @return \Cake\Http\Client\Response */ - public function get(string $url, $data = [], array $options = []): Response + public function get(string $url, array|string $data = [], array $options = []): Response { $options = $this->_mergeOptions($options); $body = null; @@ -301,7 +310,7 @@ public function get(string $url, $data = [], array $options = []): Response Request::METHOD_GET, $url, $body, - $options + $options, ); } @@ -313,7 +322,7 @@ public function get(string $url, $data = [], array $options = []): Response * @param array $options Additional options for the request. * @return \Cake\Http\Client\Response */ - public function post(string $url, $data = [], array $options = []): Response + public function post(string $url, mixed $data = [], array $options = []): Response { $options = $this->_mergeOptions($options); $url = $this->buildUrl($url, [], $options); @@ -329,7 +338,7 @@ public function post(string $url, $data = [], array $options = []): Response * @param array $options Additional options for the request. * @return \Cake\Http\Client\Response */ - public function put(string $url, $data = [], array $options = []): Response + public function put(string $url, mixed $data = [], array $options = []): Response { $options = $this->_mergeOptions($options); $url = $this->buildUrl($url, [], $options); @@ -345,7 +354,7 @@ public function put(string $url, $data = [], array $options = []): Response * @param array $options Additional options for the request. * @return \Cake\Http\Client\Response */ - public function patch(string $url, $data = [], array $options = []): Response + public function patch(string $url, mixed $data = [], array $options = []): Response { $options = $this->_mergeOptions($options); $url = $this->buildUrl($url, [], $options); @@ -361,7 +370,7 @@ public function patch(string $url, $data = [], array $options = []): Response * @param array $options Additional options for the request. * @return \Cake\Http\Client\Response */ - public function options(string $url, $data = [], array $options = []): Response + public function options(string $url, mixed $data = [], array $options = []): Response { $options = $this->_mergeOptions($options); $url = $this->buildUrl($url, [], $options); @@ -377,7 +386,7 @@ public function options(string $url, $data = [], array $options = []): Response * @param array $options Additional options for the request. * @return \Cake\Http\Client\Response */ - public function trace(string $url, $data = [], array $options = []): Response + public function trace(string $url, mixed $data = [], array $options = []): Response { $options = $this->_mergeOptions($options); $url = $this->buildUrl($url, [], $options); @@ -393,7 +402,7 @@ public function trace(string $url, $data = [], array $options = []): Response * @param array $options Additional options for the request. * @return \Cake\Http\Client\Response */ - public function delete(string $url, $data = [], array $options = []): Response + public function delete(string $url, mixed $data = [], array $options = []): Response { $options = $this->_mergeOptions($options); $url = $this->buildUrl($url, [], $options); @@ -426,13 +435,13 @@ public function head(string $url, array $data = [], array $options = []): Respon * @param array $options The options to use. Contains auth, proxy, etc. * @return \Cake\Http\Client\Response */ - protected function _doRequest(string $method, string $url, $data, $options): Response + protected function _doRequest(string $method, string $url, mixed $data, array $options): Response { $request = $this->_createRequest( $method, $url, $data, - $options + $options, ); return $this->send($request, $options); @@ -480,7 +489,33 @@ public function send(RequestInterface $request, array $options = []): Response } do { - $response = $this->_sendRequest($request, $options); + /** @var \Cake\Http\Client\ClientEvent $event */ + $event = $this->dispatchEvent( + 'HttpClient.beforeSend', + ['request' => $request, 'adapterOptions' => $options, 'redirects' => $redirects], + ); + + $request = $event->getRequest(); + $response = $event->getResult(); + $requestSent = false; + if ($response === null) { + $requestSent = true; + $response = $this->_sendRequest($request, $event->getAdapterOptions()); + } + + /** @var \Cake\Http\Client\ClientEvent $event */ + $event = $this->dispatchEvent( + 'HttpClient.afterSend', + [ + 'request' => $request, + 'adapterOptions' => $options, + 'redirects' => $redirects, + 'requestSent' => $requestSent, + 'response' => $response, + ], + ); + $response = $event->getResult(); + assert($response instanceof Response); $handleRedirect = $response->isRedirect() && $redirects-- > 0; if ($handleRedirect) { @@ -549,16 +584,18 @@ public static function addMockResponse(string $method, string $url, Response $re */ protected function _sendRequest(RequestInterface $request, array $options): Response { + $responses = []; if (static::$_mockAdapter) { $responses = static::$_mockAdapter->send($request, $options); } - if (empty($responses)) { + if (!$responses) { $responses = $this->_adapter->send($request, $options); } foreach ($responses as $response) { $this->_cookies = $this->_cookies->addFromResponse($response, $request); } + /** @var \Cake\Http\Client\Response */ return array_pop($responses); } @@ -570,9 +607,9 @@ protected function _sendRequest(RequestInterface $request, array $options): Resp * @param array $options The config options stored with Client::config() * @return string A complete url with scheme, port, host, and path. */ - public function buildUrl(string $url, $query = [], array $options = []): string + public function buildUrl(string $url, array|string $query = [], array $options = []): string { - if (empty($options) && empty($query)) { + if (!$options && !$query) { return $url; } $defaults = [ @@ -585,12 +622,12 @@ public function buildUrl(string $url, $query = [], array $options = []): string $options += $defaults; if ($query) { - $q = strpos($url, '?') === false ? '?' : '&'; + $q = str_contains($url, '?') ? '&' : '?'; $url .= $q; $url .= is_string($query) ? $query : http_build_query($query, '', '&', PHP_QUERY_RFC3986); } - if ($options['protocolRelative'] && preg_match('#^//#', $url)) { + if ($options['protocolRelative'] && str_starts_with($url, '//')) { $url = $options['scheme'] . ':' . $url; } if (preg_match('#^https?://#', $url)) { @@ -622,7 +659,7 @@ public function buildUrl(string $url, $query = [], array $options = []): string * @param array $options The options to use. Contains auth, proxy, etc. * @return \Cake\Http\Client\Request */ - protected function _createRequest(string $method, string $url, $data, $options): Request + protected function _createRequest(string $method, string $url, mixed $data, array $options): Request { /** @var array $headers */ $headers = (array)($options['headers'] ?? []); @@ -642,7 +679,7 @@ protected function _createRequest(string $method, string $url, $data, $options): $request = $this->_addAuthentication($request, $options); } if (isset($options['proxy'])) { - $request = $this->_addProxy($request, $options); + return $this->_addProxy($request, $options); } return $request; @@ -656,11 +693,11 @@ protected function _createRequest(string $method, string $url, $data, $options): * @param string $type short type alias or full mimetype. * @return array Headers to set on the request. * @throws \Cake\Core\Exception\CakeException When an unknown type alias is used. - * @psalm-return array + * @phpstan-return array */ protected function _typeHeaders(string $type): array { - if (strpos($type, '/') !== false) { + if (str_contains($type, '/')) { return [ 'Accept' => $type, 'Content-Type' => $type, @@ -671,7 +708,10 @@ protected function _typeHeaders(string $type): array 'xml' => 'application/xml', ]; if (!isset($typeMap[$type])) { - throw new CakeException("Unknown type alias '$type'."); + throw new CakeException(sprintf( + 'Unknown type alias `%s`.', + $type, + )); } return [ @@ -729,7 +769,7 @@ protected function _addProxy(Request $request, array $options): Request * @return object Authentication strategy instance. * @throws \Cake\Core\Exception\CakeException when an invalid strategy is chosen. */ - protected function _createAuth(array $auth, array $options) + protected function _createAuth(array $auth, array $options): object { if (empty($auth['type'])) { $auth['type'] = 'basic'; @@ -738,7 +778,7 @@ protected function _createAuth(array $auth, array $options) $class = App::className($name, 'Http/Client/Auth'); if (!$class) { throw new CakeException( - sprintf('Invalid authentication type %s', $name) + sprintf('Invalid authentication type `%s`.', $name), ); } diff --git a/app/vendor/cakephp/cakephp/src/Http/Client/Adapter/Curl.php b/app/vendor/cakephp/cakephp/src/Http/Client/Adapter/Curl.php index fba54c485..97437cfed 100644 --- a/app/vendor/cakephp/cakephp/src/Http/Client/Adapter/Curl.php +++ b/app/vendor/cakephp/cakephp/src/Http/Client/Adapter/Curl.php @@ -23,6 +23,7 @@ use Cake\Http\Client\Response; use Cake\Http\Exception\HttpException; use Composer\CaBundle\CaBundle; +use CurlHandle; use Psr\Http\Message\RequestInterface; /** @@ -48,12 +49,11 @@ public function send(RequestInterface $request, array $options): array $options = $this->buildOptions($request, $options); curl_setopt_array($ch, $options); - /** @var string|false $body */ $body = $this->exec($ch); + assert($body !== true); if ($body === false) { $errorCode = curl_errno($ch); $error = curl_error($ch); - curl_close($ch); $message = "cURL Error ({$errorCode}) {$error}"; $errorNumbers = [ @@ -68,7 +68,6 @@ public function send(RequestInterface $request, array $options): array } $responses = $this->createResponse($ch, $body); - curl_close($ch); return $responses; } @@ -118,7 +117,7 @@ public function buildOptions(RequestInterface $request, array $options): array $out[CURLOPT_POSTFIELDS] = $body->getContents(); // GET requests with bodies require custom request to be used. if ($out[CURLOPT_POSTFIELDS] !== '' && isset($out[CURLOPT_HTTPGET])) { - $out[CURLOPT_CUSTOMREQUEST] = 'get'; + $out[CURLOPT_CUSTOMREQUEST] = 'GET'; } if ($out[CURLOPT_POSTFIELDS] === '') { unset($out[CURLOPT_POSTFIELDS]); @@ -169,36 +168,28 @@ public function buildOptions(RequestInterface $request, array $options): array */ protected function getProtocolVersion(RequestInterface $request): int { - switch ($request->getProtocolVersion()) { - case '1.0': - return CURL_HTTP_VERSION_1_0; - case '1.1': - return CURL_HTTP_VERSION_1_1; - case '2': - case '2.0': - if (defined('CURL_HTTP_VERSION_2TLS')) { - return CURL_HTTP_VERSION_2TLS; - } - if (defined('CURL_HTTP_VERSION_2_0')) { - return CURL_HTTP_VERSION_2_0; - } - throw new HttpException('libcurl 7.33 or greater required for HTTP/2 support'); - } - - return CURL_HTTP_VERSION_NONE; + return match ($request->getProtocolVersion()) { + '1.0' => CURL_HTTP_VERSION_1_0, + '1.1' => CURL_HTTP_VERSION_1_1, + '2', '2.0' => defined('CURL_HTTP_VERSION_2TLS') + ? CURL_HTTP_VERSION_2TLS + : (defined('CURL_HTTP_VERSION_2_0') + ? CURL_HTTP_VERSION_2_0 + : throw new HttpException('libcurl 7.33 or greater required for HTTP/2 support') + ), + default => CURL_HTTP_VERSION_NONE, + }; } /** * Convert the raw curl response into an Http\Client\Response * - * @param resource|\CurlHandle $handle Curl handle + * @param \CurlHandle $handle Curl handle * @param string $responseData string The response data from curl_exec * @return array<\Cake\Http\Client\Response> - * @psalm-suppress UndefinedDocblockClass */ - protected function createResponse($handle, $responseData): array + protected function createResponse(CurlHandle $handle, string $responseData): array { - /** @psalm-suppress PossiblyInvalidArgument */ $headerSize = curl_getinfo($handle, CURLINFO_HEADER_SIZE); $headers = trim(substr($responseData, 0, $headerSize)); $body = substr($responseData, $headerSize); @@ -210,13 +201,11 @@ protected function createResponse($handle, $responseData): array /** * Execute the curl handle. * - * @param resource|\CurlHandle $ch Curl Resource handle + * @param \CurlHandle $ch Curl Resource handle * @return string|bool - * @psalm-suppress UndefinedDocblockClass */ - protected function exec($ch) + protected function exec(CurlHandle $ch): string|bool { - /** @psalm-suppress PossiblyInvalidArgument */ return curl_exec($ch); } } diff --git a/app/vendor/cakephp/cakephp/src/Http/Client/Adapter/Mock.php b/app/vendor/cakephp/cakephp/src/Http/Client/Adapter/Mock.php index 4ec86236d..99391049d 100644 --- a/app/vendor/cakephp/cakephp/src/Http/Client/Adapter/Mock.php +++ b/app/vendor/cakephp/cakephp/src/Http/Client/Adapter/Mock.php @@ -21,7 +21,6 @@ use Closure; use InvalidArgumentException; use Psr\Http\Message\RequestInterface; -use function Cake\Core\getTypeName; /** * Implements sending requests to an array of stubbed responses @@ -38,7 +37,7 @@ class Mock implements AdapterInterface * * @var array */ - protected $responses = []; + protected array $responses = []; /** * Add a mocked response. @@ -55,8 +54,11 @@ class Mock implements AdapterInterface public function addResponse(RequestInterface $request, Response $response, array $options): void { if (isset($options['match']) && !($options['match'] instanceof Closure)) { - $type = getTypeName($options['match']); - throw new InvalidArgumentException("The `match` option must be a `Closure`. Got `{$type}`."); + $type = get_debug_type($options['match']); + throw new InvalidArgumentException(sprintf( + 'The `match` option must be a `Closure`. Got `%s`.', + $type, + )); } $this->responses[] = [ 'request' => $request, @@ -70,7 +72,7 @@ public function addResponse(RequestInterface $request, Response $response, array * * @param \Psr\Http\Message\RequestInterface $request The request to match * @param array $options Unused. - * @return \Cake\Http\Client\Response[] The matched response or an empty array for no matches. + * @return array<\Cake\Http\Client\Response> The matched response or an empty array for no matches. */ public function send(RequestInterface $request, array $options): array { @@ -79,14 +81,16 @@ public function send(RequestInterface $request, array $options): array $requestUri = (string)$request->getUri(); foreach ($this->responses as $index => $mock) { - if ($method !== $mock['request']->getMethod()) { + /** @var \Psr\Http\Message\RequestInterface $request */ + $request = $mock['request']; + if ($method !== $request->getMethod()) { continue; } if (!$this->urlMatches($requestUri, $mock['request'])) { continue; } if (isset($mock['options']['match'])) { - $match = $mock['options']['match']($request); + $match = $mock['options']['match']($request, $options); if (!is_bool($match)) { throw new InvalidArgumentException('Match callback must return a boolean value.'); } @@ -127,7 +131,7 @@ protected function urlMatches(string $requestUri, RequestInterface $mock): bool if ($starPosition === strlen($mockUri) - 4) { $mockUri = substr($mockUri, 0, $starPosition); - return strpos($requestUri, $mockUri) === 0; + return str_starts_with($requestUri, $mockUri); } return false; diff --git a/app/vendor/cakephp/cakephp/src/Http/Client/Adapter/Stream.php b/app/vendor/cakephp/cakephp/src/Http/Client/Adapter/Stream.php index fcddd89d0..79139c829 100644 --- a/app/vendor/cakephp/cakephp/src/Http/Client/Adapter/Stream.php +++ b/app/vendor/cakephp/cakephp/src/Http/Client/Adapter/Stream.php @@ -43,14 +43,14 @@ class Stream implements AdapterInterface * * @var array */ - protected $_contextOptions = []; + protected array $_contextOptions = []; /** * Array of options/content for the SSL stream context. * * @var array */ - protected $_sslContextOptions = []; + protected array $_sslContextOptions = []; /** * The stream resource. @@ -64,7 +64,7 @@ class Stream implements AdapterInterface * * @var array */ - protected $_connectionErrors = []; + protected array $_connectionErrors = []; /** * @inheritDoc @@ -88,13 +88,14 @@ public function send(RequestInterface $request, array $options): array * Creates one or many response objects based on the number * of redirects that occurred. * - * @param array $headers The list of headers from the request(s) + * @param array $headers The list of headers from the request(s) * @param string $content The response content. * @return array<\Cake\Http\Client\Response> The list of responses from the request(s) */ public function createResponses(array $headers, string $content): array { - $indexes = $responses = []; + $indexes = []; + $responses = []; foreach ($headers as $i => $header) { if (strtoupper(substr($header, 0, 5)) === 'HTTP/') { $indexes[] = $i; @@ -102,9 +103,7 @@ public function createResponses(array $headers, string $content): array } $last = count($indexes) - 1; foreach ($indexes as $i => $start) { - /** @psalm-suppress InvalidOperand */ $end = isset($indexes[$i + 1]) ? $indexes[$i + 1] - $start : null; - /** @psalm-suppress PossiblyInvalidArgument */ $headerSlice = array_slice($headers, $start, $end); $body = $i === $last ? $content : ''; $responses[] = $this->_buildResponse($headerSlice, $body); @@ -243,6 +242,7 @@ protected function _send(RequestInterface $request): array { $deadline = false; if (isset($this->_contextOptions['timeout']) && $this->_contextOptions['timeout'] > 0) { + /** @var int $deadline */ $deadline = time() + $this->_contextOptions['timeout']; } @@ -251,7 +251,8 @@ protected function _send(RequestInterface $request): array $content = ''; $timedOut = false; - /** @psalm-suppress PossiblyNullArgument */ + assert($this->_stream !== null, 'HTTP stream failed to open'); + while (!feof($this->_stream)) { if ($deadline !== false) { stream_set_timeout($this->_stream, max($deadline - time(), 1)); @@ -265,9 +266,8 @@ protected function _send(RequestInterface $request): array break; } } - /** @psalm-suppress PossiblyNullArgument */ + $meta = stream_get_meta_data($this->_stream); - /** @psalm-suppress InvalidPropertyAssignmentValue */ fclose($this->_stream); if ($timedOut) { @@ -285,7 +285,7 @@ protected function _send(RequestInterface $request): array /** * Build a response object * - * @param array $headers Unparsed headers. + * @param array $headers Unparsed headers. * @param string $body The response body. * @return \Cake\Http\Client\Response */ @@ -314,7 +314,11 @@ protected function _open(string $url, RequestInterface $request): void return true; }); try { - $this->_stream = fopen($url, 'rb', false, $this->_context); + $stream = fopen($url, 'rb', false, $this->_context); + if ($stream === false) { + $stream = null; + } + $this->_stream = $stream; } finally { restore_error_handler(); } diff --git a/app/vendor/cakephp/cakephp/src/Http/Client/Auth/Basic.php b/app/vendor/cakephp/cakephp/src/Http/Client/Auth/Basic.php index 0df928b33..9afb82e49 100644 --- a/app/vendor/cakephp/cakephp/src/Http/Client/Auth/Basic.php +++ b/app/vendor/cakephp/cakephp/src/Http/Client/Auth/Basic.php @@ -37,7 +37,6 @@ public function authentication(Request $request, array $credentials): Request { if (isset($credentials['username'], $credentials['password'])) { $value = $this->_generateHeader($credentials['username'], $credentials['password']); - /** @var \Cake\Http\Client\Request $request */ $request = $request->withHeader('Authorization', $value); } @@ -56,7 +55,6 @@ public function proxyAuthentication(Request $request, array $credentials): Reque { if (isset($credentials['username'], $credentials['password'])) { $value = $this->_generateHeader($credentials['username'], $credentials['password']); - /** @var \Cake\Http\Client\Request $request */ $request = $request->withHeader('Proxy-Authorization', $value); } diff --git a/app/vendor/cakephp/cakephp/src/Http/Client/Auth/Digest.php b/app/vendor/cakephp/cakephp/src/Http/Client/Auth/Digest.php index 41e8903c3..101bfe24f 100644 --- a/app/vendor/cakephp/cakephp/src/Http/Client/Auth/Digest.php +++ b/app/vendor/cakephp/cakephp/src/Http/Client/Auth/Digest.php @@ -19,6 +19,7 @@ use Cake\Http\Client\Request; use Cake\Http\HeaderUtility; use Cake\Utility\Hash; +use InvalidArgumentException; /** * Digest authentication adapter for Cake\Http\Client @@ -60,28 +61,28 @@ class Digest * * @var \Cake\Http\Client */ - protected $_client; + protected Client $_client; /** * Algorithm * * @var string */ - protected $algorithm; + protected string $algorithm; /** * Hash type * * @var string */ - protected $hashType; + protected string $hashType; /** * Is Sess algorithm * * @var bool */ - protected $isSessAlgorithm; + protected bool $isSessAlgorithm = false; /** * Constructor @@ -104,11 +105,11 @@ protected function setAlgorithm(array $credentials): void { $algorithm = $credentials['algorithm'] ?? self::ALGO_MD5; if (!isset(self::HASH_ALGORITHMS[$algorithm])) { - throw new \InvalidArgumentException('Invalid Algorithm. Valid ones are: ' . + throw new InvalidArgumentException('Invalid Algorithm. Valid ones are: ' . implode(',', array_keys(self::HASH_ALGORITHMS))); } $this->algorithm = $algorithm; - $this->isSessAlgorithm = strpos($this->algorithm, '-sess') !== false; + $this->isSessAlgorithm = str_contains($this->algorithm, '-sess'); $this->hashType = Hash::get(self::HASH_ALGORITHMS, $this->algorithm); } @@ -154,7 +155,7 @@ protected function _getServerInfo(Request $request, array $credentials): array $response = $this->_client->get( (string)$request->getUri(), [], - ['auth' => ['type' => null]] + ['auth' => ['type' => null]], ); $header = $response->getHeader('WWW-Authenticate'); @@ -207,7 +208,7 @@ protected function _generateHeader(Request $request, array $credentials): string $response = hash($this->hashType, $ha1 . ':' . $credentials['nonce'] . ':' . $ha2); } else { if (!in_array($credentials['qop'], [self::QOP_AUTH, self::QOP_AUTH_INT])) { - throw new \InvalidArgumentException('Invalid QOP parameter. Valid types are: ' . + throw new InvalidArgumentException('Invalid QOP parameter. Valid types are: ' . implode(',', [self::QOP_AUTH, self::QOP_AUTH_INT])); } if ($credentials['qop'] === self::QOP_AUTH_INT) { @@ -220,7 +221,7 @@ protected function _generateHeader(Request $request, array $credentials): string $response = hash( $this->hashType, $ha1 . ':' . $credentials['nonce'] . ':' . $nc . ':' . - $credentials['cnonce'] . ':' . $credentials['qop'] . ':' . $ha2 + $credentials['cnonce'] . ':' . $credentials['qop'] . ':' . $ha2, ); } diff --git a/app/vendor/cakephp/cakephp/src/Http/Client/Auth/Oauth.php b/app/vendor/cakephp/cakephp/src/Http/Client/Auth/Oauth.php index f6edf2785..d578534b9 100644 --- a/app/vendor/cakephp/cakephp/src/Http/Client/Auth/Oauth.php +++ b/app/vendor/cakephp/cakephp/src/Http/Client/Auth/Oauth.php @@ -19,7 +19,6 @@ use Cake\Http\Client\Request; use Cake\Utility\Security; use Psr\Http\Message\UriInterface; -use RuntimeException; /** * Oauth 1 authentication strategy for Cake\Http\Client @@ -57,7 +56,7 @@ public function authentication(Request $request, array $credentials): Request $hasKeys = isset( $credentials['consumerSecret'], $credentials['token'], - $credentials['tokenSecret'] + $credentials['tokenSecret'], ); if (!$hasKeys) { return $request; @@ -76,7 +75,7 @@ public function authentication(Request $request, array $credentials): Request $hasKeys = isset( $credentials['consumerSecret'], $credentials['token'], - $credentials['tokenSecret'] + $credentials['tokenSecret'], ); if (!$hasKeys) { return $request; @@ -85,7 +84,7 @@ public function authentication(Request $request, array $credentials): Request break; default: - throw new CakeException(sprintf('Unknown Oauth signature method %s', $credentials['method'])); + throw new CakeException(sprintf('Unknown Oauth signature method `%s`.', $credentials['method'])); } return $request->withHeader('Authorization', $value); @@ -153,11 +152,11 @@ protected function _hmacSha1(Request $request, array $credentials): string $values['oauth_realm'] = $credentials['realm']; } $key = [$credentials['consumerSecret'], $credentials['tokenSecret']]; - $key = array_map([$this, '_encode'], $key); + $key = array_map($this->_encode(...), $key); $key = implode('&', $key); $values['oauth_signature'] = base64_encode( - hash_hmac('sha1', $baseString, $key, true) + hash_hmac('sha1', $baseString, $key, true), ); return $this->_buildAuth($values); @@ -171,12 +170,11 @@ protected function _hmacSha1(Request $request, array $credentials): string * @param \Cake\Http\Client\Request $request The request object. * @param array $credentials Authentication credentials. * @return string - * @throws \RuntimeException */ protected function _rsaSha1(Request $request, array $credentials): string { if (!function_exists('openssl_pkey_get_private')) { - throw new RuntimeException('RSA-SHA1 signature method requires the OpenSSL extension.'); + throw new CakeException('RSA-SHA1 signature method requires the OpenSSL extension.'); } $nonce = $credentials['nonce'] ?? bin2hex(Security::randomBytes(16)); @@ -219,17 +217,16 @@ protected function _rsaSha1(Request $request, array $credentials): string rewind($resource); $credentials['privateKeyPassphrase'] = $passphrase; } + /** @var \OpenSSLAsymmetricKey|false $privateKey */ $privateKey = openssl_pkey_get_private($credentials['privateKey'], $credentials['privateKeyPassphrase']); $this->checkSslError(); + assert($privateKey !== false); + $signature = ''; openssl_sign($baseString, $signature, $privateKey); $this->checkSslError(); - if (PHP_MAJOR_VERSION < 8) { - openssl_free_key($privateKey); - } - $values['oauth_signature'] = base64_encode($signature); return $this->_buildAuth($values); @@ -255,7 +252,7 @@ public function baseString(Request $request, array $oauthValues): string $this->_normalizedUrl($request->getUri()), $this->_normalizedParams($request, $oauthValues), ]; - $parts = array_map([$this, '_encode'], $parts); + $parts = array_map($this->_encode(...), $parts); return implode('&', $parts); } @@ -373,9 +370,10 @@ protected function _encode(string $value): string } /** - * Check for SSL errors and raise if one is encountered. + * Check for SSL errors and throw an exception if found. * * @return void + * @throws \Cake\Core\Exception\CakeException When an error is found */ protected function checkSslError(): void { @@ -385,7 +383,7 @@ protected function checkSslError(): void } if (strlen($error) > 0) { - throw new RuntimeException('openssl error: ' . $error); + throw new CakeException('openssl error: ' . $error); } } } diff --git a/app/vendor/cakephp/cakephp/src/Http/Client/ClientEvent.php b/app/vendor/cakephp/cakephp/src/Http/Client/ClientEvent.php new file mode 100644 index 000000000..741b0fed5 --- /dev/null +++ b/app/vendor/cakephp/cakephp/src/Http/Client/ClientEvent.php @@ -0,0 +1,120 @@ + + */ +class ClientEvent extends Event +{ + /** + * Constructor + * + * @param string $name Name of the event + * @param \Cake\Http\Client $subject The Http Client instance this event applies to. + * @param array $data Any value you wish to be transported + * with this event to it can be read by listeners. + */ + public function __construct(string $name, Client $subject, array $data = []) + { + if (isset($data['response'])) { + $this->result = $data['response']; + unset($data['response']); + } + + parent::__construct($name, $subject, $data); + } + + /** + * The result value of the event listeners + * + * @return \Cake\Http\Client\Response|null + */ + public function getResult(): ?Response + { + return $this->result; + } + + /** + * Listeners can attach a result value to the event. + * + * @param mixed $value The value to set. + * @return $this + */ + public function setResult(mixed $value = null) + { + if ($value !== null && !$value instanceof Response) { + throw new InvalidArgumentException( + 'The result for Http Client events must be a `Cake\Http\Client\Response` instance.', + ); + } + + return parent::setResult($value); + } + + /** + * Set request instance. + * + * @param \Psr\Http\Message\RequestInterface $request + * @return $this + */ + public function setRequest(RequestInterface $request) + { + $this->_data['request'] = $request; + + return $this; + } + + /** + * Get the request instance. + * + * @return \Psr\Http\Message\RequestInterface + */ + public function getRequest(): RequestInterface + { + return $this->_data['request']; + } + + /** + * Set the adapter options. + * + * @return $this + */ + public function setAdapterOptions(array $options = []) + { + $this->_data['adapterOptions'] = $options; + + return $this; + } + + /** + * Get the adapter options. + * + * @return array + */ + public function getAdapterOptions(): array + { + return $this->_data['adapterOptions']; + } +} diff --git a/app/vendor/cakephp/cakephp/src/Http/Client/Exception/MissingResponseException.php b/app/vendor/cakephp/cakephp/src/Http/Client/Exception/MissingResponseException.php index 8f80cfdbc..7f780b66c 100644 --- a/app/vendor/cakephp/cakephp/src/Http/Client/Exception/MissingResponseException.php +++ b/app/vendor/cakephp/cakephp/src/Http/Client/Exception/MissingResponseException.php @@ -25,5 +25,5 @@ class MissingResponseException extends CakeException /** * @var string */ - protected $_messageTemplate = 'Unable to find a mocked response for `%s` to `%s`.'; + protected string $_messageTemplate = 'Unable to find a mocked response for `%s` to `%s`.'; } diff --git a/app/vendor/cakephp/cakephp/src/Http/Client/Exception/NetworkException.php b/app/vendor/cakephp/cakephp/src/Http/Client/Exception/NetworkException.php index bf0efd2d7..6f689000b 100644 --- a/app/vendor/cakephp/cakephp/src/Http/Client/Exception/NetworkException.php +++ b/app/vendor/cakephp/cakephp/src/Http/Client/Exception/NetworkException.php @@ -32,12 +32,12 @@ class NetworkException extends RuntimeException implements NetworkExceptionInter /** * @var \Psr\Http\Message\RequestInterface */ - protected $request; + protected RequestInterface $request; /** * Constructor. * - * @param string $message Exeception message. + * @param string $message Exception message. * @param \Psr\Http\Message\RequestInterface $request Request instance. * @param \Throwable|null $previous Previous Exception */ diff --git a/app/vendor/cakephp/cakephp/src/Http/Client/Exception/RequestException.php b/app/vendor/cakephp/cakephp/src/Http/Client/Exception/RequestException.php index ab8b06874..f57ae5071 100644 --- a/app/vendor/cakephp/cakephp/src/Http/Client/Exception/RequestException.php +++ b/app/vendor/cakephp/cakephp/src/Http/Client/Exception/RequestException.php @@ -33,12 +33,12 @@ class RequestException extends RuntimeException implements RequestExceptionInter /** * @var \Psr\Http\Message\RequestInterface */ - protected $request; + protected RequestInterface $request; /** * Constructor. * - * @param string $message Exeception message. + * @param string $message Exception message. * @param \Psr\Http\Message\RequestInterface $request Request instance. * @param \Throwable|null $previous Previous Exception */ diff --git a/app/vendor/cakephp/cakephp/src/Http/Client/FormData.php b/app/vendor/cakephp/cakephp/src/Http/Client/FormData.php index 329702195..e8597b9c8 100644 --- a/app/vendor/cakephp/cakephp/src/Http/Client/FormData.php +++ b/app/vendor/cakephp/cakephp/src/Http/Client/FormData.php @@ -18,6 +18,7 @@ use Countable; use finfo; use Psr\Http\Message\UploadedFileInterface; +use Stringable; /** * Provides an interface for building @@ -26,35 +27,35 @@ * Used by Http\Client to upload POST/PUT data * and files. */ -class FormData implements Countable +class FormData implements Countable, Stringable { /** * Boundary marker. * * @var string */ - protected $_boundary; + protected string $_boundary = ''; /** * Whether this formdata object has attached files. * * @var bool */ - protected $_hasFile = false; + protected bool $_hasFile = false; /** * Whether this formdata object has a complex part. * * @var bool */ - protected $_hasComplexPart = false; + protected bool $_hasComplexPart = false; /** * The parts in the form data. * * @var array<\Cake\Http\Client\FormDataPart> */ - protected $_parts = []; + protected array $_parts = []; /** * Get the boundary marker @@ -66,7 +67,7 @@ public function boundary(): string if ($this->_boundary) { return $this->_boundary; } - $this->_boundary = md5(uniqid((string)time())); + $this->_boundary = hash('xxh128', uniqid((string)time())); return $this->_boundary; } @@ -97,7 +98,7 @@ public function newPart(string $name, string $value): FormDataPart * @param mixed $value The value for the part. * @return $this */ - public function add($name, $value = null) + public function add(FormDataPart|string $name, mixed $value = null) { if (is_string($name)) { if (is_array($value)) { @@ -137,11 +138,11 @@ public function addMany(array $data) * or a file handle. * * @param string $name The name to use. - * @param string|resource|\Psr\Http\Message\UploadedFileInterface $value Either a string filename, or a filehandle, + * @param \Psr\Http\Message\UploadedFileInterface|resource|string $value Either a string filename, or a filehandle, * or a UploadedFileInterface instance. * @return \Cake\Http\Client\FormDataPart */ - public function addFile(string $name, $value): FormDataPart + public function addFile(string $name, mixed $value): FormDataPart { $this->_hasFile = true; @@ -152,19 +153,29 @@ public function addFile(string $name, $value): FormDataPart $contentType = $value->getClientMediaType(); $filename = $value->getClientFilename(); } elseif (is_resource($value)) { - $content = stream_get_contents($value); + $content = (string)stream_get_contents($value); if (stream_is_local($value)) { $finfo = new finfo(FILEINFO_MIME); $metadata = stream_get_meta_data($value); - $contentType = $finfo->file($metadata['uri']); - $filename = basename($metadata['uri']); + $uri = $metadata['uri'] ?? ''; + $contentType = (string)$finfo->file($uri); + $filename = basename($uri); } } else { + assert( + is_string($value), + sprintf( + '`$value` must be a string, a resource or an instance of `Psr\Http\Message\UploadedFileInterface`.' + . ' `%s` given.', + get_debug_type($value), + ), + ); + $finfo = new finfo(FILEINFO_MIME); $value = substr($value, 1); $filename = basename($value); - $content = file_get_contents($value); - $contentType = $finfo->file($value); + $content = (string)file_get_contents($value); + $contentType = (string)$finfo->file($value); } $part = $this->newPart($name, $content); $part->type($contentType); @@ -183,7 +194,7 @@ public function addFile(string $name, $value): FormDataPart * @param mixed $value The value to add. * @return void */ - public function addRecursive(string $name, $value): void + public function addRecursive(string $name, mixed $value): void { foreach ($value as $key => $value) { $key = $name . '[' . $key . ']'; @@ -255,11 +266,11 @@ public function __toString(): string $boundary = $this->boundary(); $out = ''; foreach ($this->_parts as $part) { - $out .= "--$boundary\r\n"; + $out .= "--{$boundary}\r\n"; $out .= (string)$part; $out .= "\r\n"; } - $out .= "--$boundary--\r\n"; + $out .= "--{$boundary}--\r\n"; return $out; } diff --git a/app/vendor/cakephp/cakephp/src/Http/Client/FormDataPart.php b/app/vendor/cakephp/cakephp/src/Http/Client/FormDataPart.php index 002c9ca6f..561b9f945 100644 --- a/app/vendor/cakephp/cakephp/src/Http/Client/FormDataPart.php +++ b/app/vendor/cakephp/cakephp/src/Http/Client/FormDataPart.php @@ -16,6 +16,7 @@ namespace Cake\Http\Client; use Cake\Utility\Text; +use Stringable; /** * Contains the data and behavior for a single @@ -26,63 +27,35 @@ * * @internal */ -class FormDataPart +class FormDataPart implements Stringable { - /** - * Name of the value. - * - * @var string - */ - protected $_name; - - /** - * Value to send. - * - * @var string - */ - protected $_value; - /** * Content type to use * * @var string|null */ - protected $_type; - - /** - * Disposition to send - * - * @var string - */ - protected $_disposition; + protected ?string $type = null; /** * Filename to send if using files. * * @var string|null */ - protected $_filename; + protected ?string $filename = null; /** * The encoding used in this part. * * @var string|null */ - protected $_transferEncoding; + protected ?string $transferEncoding = null; /** * The contentId for the part * * @var string|null */ - protected $_contentId; - - /** - * The charset attribute for the Content-Disposition header fields - * - * @var string|null - */ - protected $_charset; + protected ?string $contentId = null; /** * Constructor @@ -92,12 +65,12 @@ class FormDataPart * @param string $disposition The type of disposition to use, defaults to form-data. * @param string|null $charset The charset of the data. */ - public function __construct(string $name, string $value, string $disposition = 'form-data', ?string $charset = null) - { - $this->_name = $name; - $this->_value = $value; - $this->_disposition = $disposition; - $this->_charset = $charset; + public function __construct( + protected string $name, + protected string $value, + protected string $disposition = 'form-data', + protected ?string $charset = null, + ) { } /** @@ -112,10 +85,10 @@ public function __construct(string $name, string $value, string $disposition = ' public function disposition(?string $disposition = null): string { if ($disposition === null) { - return $this->_disposition; + return $this->disposition; } - return $this->_disposition = $disposition; + return $this->disposition = $disposition; } /** @@ -127,10 +100,10 @@ public function disposition(?string $disposition = null): string public function contentId(?string $id = null): ?string { if ($id === null) { - return $this->_contentId; + return $this->contentId; } - return $this->_contentId = $id; + return $this->contentId = $id; } /** @@ -145,10 +118,10 @@ public function contentId(?string $id = null): ?string public function filename(?string $filename = null): ?string { if ($filename === null) { - return $this->_filename; + return $this->filename; } - return $this->_filename = $filename; + return $this->filename = $filename; } /** @@ -160,10 +133,10 @@ public function filename(?string $filename = null): ?string public function type(?string $type): ?string { if ($type === null) { - return $this->_type; + return $this->type; } - return $this->_type = $type; + return $this->type = $type; } /** @@ -177,10 +150,10 @@ public function type(?string $type): ?string public function transferEncoding(?string $type): ?string { if ($type === null) { - return $this->_transferEncoding; + return $this->transferEncoding; } - return $this->_transferEncoding = $type; + return $this->transferEncoding = $type; } /** @@ -190,7 +163,7 @@ public function transferEncoding(?string $type): ?string */ public function name(): string { - return $this->_name; + return $this->name; } /** @@ -200,7 +173,7 @@ public function name(): string */ public function value(): string { - return $this->_value; + return $this->value; } /** @@ -213,27 +186,27 @@ public function value(): string public function __toString(): string { $out = ''; - if ($this->_disposition) { - $out .= 'Content-Disposition: ' . $this->_disposition; - if ($this->_name) { - $out .= '; ' . $this->_headerParameterToString('name', $this->_name); + if ($this->disposition) { + $out .= 'Content-Disposition: ' . $this->disposition; + if ($this->name) { + $out .= '; ' . $this->_headerParameterToString('name', $this->name); } - if ($this->_filename) { - $out .= '; ' . $this->_headerParameterToString('filename', $this->_filename); + if ($this->filename) { + $out .= '; ' . $this->_headerParameterToString('filename', $this->filename); } $out .= "\r\n"; } - if ($this->_type) { - $out .= 'Content-Type: ' . $this->_type . "\r\n"; + if ($this->type) { + $out .= 'Content-Type: ' . $this->type . "\r\n"; } - if ($this->_transferEncoding) { - $out .= 'Content-Transfer-Encoding: ' . $this->_transferEncoding . "\r\n"; + if ($this->transferEncoding) { + $out .= 'Content-Transfer-Encoding: ' . $this->transferEncoding . "\r\n"; } - if ($this->_contentId) { - $out .= 'Content-ID: <' . $this->_contentId . ">\r\n"; + if ($this->contentId) { + $out .= 'Content-ID: <' . $this->contentId . ">\r\n"; } $out .= "\r\n"; - $out .= $this->_value; + $out .= $this->value; return $out; } @@ -252,8 +225,8 @@ protected function _headerParameterToString(string $name, string $value): string { $transliterated = Text::transliterate(str_replace('"', '', $value)); $return = sprintf('%s="%s"', $name, $transliterated); - if ($this->_charset !== null && $value !== $transliterated) { - $return .= sprintf("; %s*=%s''%s", $name, strtolower($this->_charset), rawurlencode($value)); + if ($this->charset !== null && $value !== $transliterated) { + $return .= sprintf("; %s*=%s''%s", $name, strtolower($this->charset), rawurlencode($value)); } return $return; diff --git a/app/vendor/cakephp/cakephp/src/Http/Client/Message.php b/app/vendor/cakephp/cakephp/src/Http/Client/Message.php index 59994e03a..30beaa23e 100644 --- a/app/vendor/cakephp/cakephp/src/Http/Client/Message.php +++ b/app/vendor/cakephp/cakephp/src/Http/Client/Message.php @@ -154,7 +154,7 @@ class Message * * @var array */ - protected $_cookies = []; + protected array $_cookies = []; /** * Get all cookies diff --git a/app/vendor/cakephp/cakephp/src/Http/Client/Request.php b/app/vendor/cakephp/cakephp/src/Http/Client/Request.php index 752eba4ab..0e3ef10e2 100644 --- a/app/vendor/cakephp/cakephp/src/Http/Client/Request.php +++ b/app/vendor/cakephp/cakephp/src/Http/Client/Request.php @@ -15,9 +15,11 @@ */ namespace Cake\Http\Client; +use Cake\Utility\Xml; use Laminas\Diactoros\RequestTrait; use Laminas\Diactoros\Stream; use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\UriInterface; /** * Implements methods for HTTP requests. @@ -35,13 +37,17 @@ class Request extends Message implements RequestInterface * Provides backwards compatible defaults for some properties. * * @phpstan-param array $headers - * @param string $url The request URL + * @param \Psr\Http\Message\UriInterface|string $url The request URL * @param string $method The HTTP method to use. * @param array $headers The HTTP headers to set. * @param array|string|null $data The request body to use. */ - public function __construct(string $url = '', string $method = self::METHOD_GET, array $headers = [], $data = null) - { + public function __construct( + UriInterface|string $url = '', + string $method = self::METHOD_GET, + array $headers = [], + array|string|null $data = null, + ) { $this->setMethod($method); $this->uri = $this->createUri($url); $headers += [ @@ -49,8 +55,7 @@ public function __construct(string $url = '', string $method = self::METHOD_GET, 'User-Agent' => ini_get('user_agent') ?: 'CakePHP', ]; $this->addHeaders($headers); - - if ($data === null) { + if ($data === null || $data === '' || $data === []) { $this->stream = new Stream('php://memory', 'rw'); } else { $this->setContent($data); @@ -82,15 +87,25 @@ protected function addHeaders(array $headers): void * @param array|string $content The body for the request. * @return $this */ - protected function setContent($content) + protected function setContent(array|string $content) { if (is_array($content)) { - $formData = new FormData(); - $formData->addMany($content); - /** @phpstan-var array $headers */ - $headers = ['Content-Type' => $formData->contentType()]; - $this->addHeaders($headers); - $content = (string)$formData; + $contentType = $this->getHeaderLine('content-type'); + + if (str_contains($contentType, 'application/json')) { + $content = json_encode($content, JSON_THROW_ON_ERROR); + } elseif (str_contains($contentType, 'application/xml')) { + /** @phpstan-ignore-next-line */ + $content = (string)Xml::fromArray($content); + } else { + $formData = new FormData(); + $formData->addMany($content); + + /** @phpstan-var array $headers */ + $headers = ['Content-Type' => $formData->contentType()]; + $this->addHeaders($headers); + $content = (string)$formData; + } } $stream = new Stream('php://memory', 'rw'); diff --git a/app/vendor/cakephp/cakephp/src/Http/Client/Response.php b/app/vendor/cakephp/cakephp/src/Http/Client/Response.php index 13df4d3d1..22adf538c 100644 --- a/app/vendor/cakephp/cakephp/src/Http/Client/Response.php +++ b/app/vendor/cakephp/cakephp/src/Http/Client/Response.php @@ -15,11 +15,11 @@ */ namespace Cake\Http\Client; +use Cake\Core\Exception\CakeException; use Cake\Http\Cookie\CookieCollection; use Laminas\Diactoros\MessageTrait; use Laminas\Diactoros\Stream; use Psr\Http\Message\ResponseInterface; -use RuntimeException; use SimpleXMLElement; /** @@ -89,40 +89,40 @@ class Response extends Message implements ResponseInterface * * @var int */ - protected $code; + protected int $code = 0; /** * Cookie Collection instance * - * @var \Cake\Http\Cookie\CookieCollection + * @var \Cake\Http\Cookie\CookieCollection|null */ - protected $cookies; + protected ?CookieCollection $cookies = null; /** * The reason phrase for the status code * * @var string */ - protected $reasonPhrase; + protected string $reasonPhrase; /** * Cached decoded XML data. * - * @var \SimpleXMLElement + * @var \SimpleXMLElement|null */ - protected $_xml; + protected ?SimpleXMLElement $_xml = null; /** * Cached decoded JSON data. * * @var mixed */ - protected $_json; + protected mixed $_json = null; /** * Constructor * - * @param array $headers Unparsed headers. + * @param array $headers Unparsed headers. * @param string $body The response body. */ public function __construct(array $headers = [], string $body = '') @@ -145,51 +145,51 @@ public function __construct(array $headers = [], string $body = '') * * @param string $body Gzip encoded body. * @return string - * @throws \RuntimeException When attempting to decode gzip content without gzinflate. + * @throws \Cake\Core\Exception\CakeException When attempting to decode gzip content without gzinflate. */ protected function _decodeGzipBody(string $body): string { if (!function_exists('gzinflate')) { - throw new RuntimeException('Cannot decompress gzip response body without gzinflate()'); + throw new CakeException('Cannot decompress gzip response body without gzinflate()'); } $offset = 0; // Look for gzip 'signature' - if (substr($body, 0, 2) === "\x1f\x8b") { + if (str_starts_with($body, "\x1f\x8b")) { $offset = 2; } // Check the format byte if (substr($body, $offset, 1) === "\x08") { - return gzinflate(substr($body, $offset + 8)); + return (string)gzinflate(substr($body, $offset + 8)); } - throw new RuntimeException('Invalid gzip response'); + throw new CakeException('Invalid gzip response'); } /** * Parses headers if necessary. * - * - Decodes the status code and reasonphrase. - * - Parses and normalizes header names + values. + * - Decodes the status code and reason phrase. + * - Parses and normalizes header names and values. * - * @param array $headers Headers to parse. + * @param array $headers Headers to parse. * @return void */ protected function _parseHeaders(array $headers): void { foreach ($headers as $value) { - if (substr($value, 0, 5) === 'HTTP/') { + if (str_starts_with($value, 'HTTP/')) { preg_match('/HTTP\/([\d.]+) ([0-9]+)(.*)/i', $value, $matches); $this->protocol = $matches[1]; $this->code = (int)$matches[2]; $this->reasonPhrase = trim($matches[3]); continue; } - if (strpos($value, ':') === false) { + if (!str_contains($value, ':')) { continue; } [$name, $value] = explode(':', $value, 2); $value = trim($value); - /** @phpstan-var non-empty-string $name */ + /** @var non-empty-string $name */ $name = trim($name); $normalized = strtolower($name); @@ -259,7 +259,7 @@ public function getStatusCode(): int * @param string $reasonPhrase The status reason phrase. * @return static A copy of the current object with an updated status code. */ - public function withStatus($code, $reasonPhrase = '') + public function withStatus(int $code, string $reasonPhrase = ''): static { $new = clone $this; $new->code = $code; @@ -317,9 +317,7 @@ public function getCookies(): array */ public function getCookieCollection(): CookieCollection { - $this->buildCookieCollection(); - - return $this->cookies; + return $this->buildCookieCollection(); } /** @@ -328,15 +326,15 @@ public function getCookieCollection(): CookieCollection * @param string $name The name of the cookie value. * @return array|string|null Either the cookie's value or null when the cookie is undefined. */ - public function getCookie(string $name) + public function getCookie(string $name): array|string|null { - $this->buildCookieCollection(); + $cookies = $this->buildCookieCollection(); - if (!$this->cookies->has($name)) { + if (!$cookies->has($name)) { return null; } - return $this->cookies->get($name)->getValue(); + return $cookies->get($name)->getValue(); } /** @@ -347,26 +345,25 @@ public function getCookie(string $name) */ public function getCookieData(string $name): ?array { - $this->buildCookieCollection(); + $cookies = $this->buildCookieCollection(); - if (!$this->cookies->has($name)) { + if (!$cookies->has($name)) { return null; } - return $this->cookies->get($name)->toArray(); + return $cookies->get($name)->toArray(); } /** * Lazily build the CookieCollection and cookie objects from the response header * - * @return void + * @return \Cake\Http\Cookie\CookieCollection */ - protected function buildCookieCollection(): void + protected function buildCookieCollection(): CookieCollection { - if ($this->cookies !== null) { - return; - } - $this->cookies = CookieCollection::createFromHeader($this->getHeader('Set-Cookie')); + $this->cookies ??= CookieCollection::createFromHeader($this->getHeader('Set-Cookie')); + + return $this->cookies; } /** @@ -376,12 +373,8 @@ protected function buildCookieCollection(): void */ protected function _getCookies(): array { - $this->buildCookieCollection(); - $out = []; - /** @var array<\Cake\Http\Cookie\Cookie> $cookies */ - $cookies = $this->cookies; - foreach ($cookies as $cookie) { + foreach ($this->buildCookieCollection() as $cookie) { $out[$cookie->getName()] = $cookie->toArray(); } @@ -403,7 +396,7 @@ public function getStringBody(): string * * @return mixed */ - public function getJson() + public function getJson(): mixed { return $this->_getJson(); } @@ -413,7 +406,7 @@ public function getJson() * * @return mixed */ - protected function _getJson() + protected function _getJson(): mixed { if ($this->_json) { return $this->_json; @@ -444,13 +437,13 @@ protected function _getXml(): ?SimpleXMLElement } libxml_use_internal_errors(); $data = simplexml_load_string($this->_getBody()); - if ($data) { - $this->_xml = $data; - - return $this->_xml; + if (!$data) { + return null; } - return null; + $this->_xml = $data; + + return $this->_xml; } /** diff --git a/app/vendor/cakephp/cakephp/src/Http/ContentTypeNegotiation.php b/app/vendor/cakephp/cakephp/src/Http/ContentTypeNegotiation.php index 9db0c5530..1811a7893 100644 --- a/app/vendor/cakephp/cakephp/src/Http/ContentTypeNegotiation.php +++ b/app/vendor/cakephp/cakephp/src/Http/ContentTypeNegotiation.php @@ -6,7 +6,7 @@ use Psr\Http\Message\RequestInterface; /** - * Negotiates the prefered content type from what the application + * Negotiates the preferred content type from what the application * provides and what the request has in its Accept header. */ class ContentTypeNegotiation @@ -65,16 +65,16 @@ protected function parseQualifiers(string $header): array * * @param \Psr\Http\Message\RequestInterface $request The request to use. * @param array $choices The supported content type choices. - * @return string|null The prefered type or null if there is no match with choices or if the + * @return string|null The preferred type or null if there is no match with choices or if the * request had no Accept header. */ public function preferredType(RequestInterface $request, array $choices = []): ?string { $parsed = $this->parseAccept($request); - if (empty($parsed)) { + if (!$parsed) { return null; } - if (empty($choices)) { + if (!$choices) { $preferred = array_shift($parsed); return $preferred[0]; diff --git a/app/vendor/cakephp/cakephp/src/Http/ControllerFactory.php b/app/vendor/cakephp/cakephp/src/Http/ControllerFactory.php deleted file mode 100644 index d5af2a37e..000000000 --- a/app/vendor/cakephp/cakephp/src/Http/ControllerFactory.php +++ /dev/null @@ -1,10 +0,0 @@ - * @see \Cake\Http\Cookie\Cookie::setDefaults() */ - protected static $defaults = [ + protected static array $defaults = [ 'expires' => null, 'path' => '/', 'domain' => '', @@ -138,23 +138,23 @@ class Cookie implements CookieInterface * * @link https://php.net/manual/en/function.setcookie.php * @param string $name Cookie name - * @param array|string $value Value of the cookie - * @param \DateTime|\DateTimeImmutable|null $expiresAt Expiration time and date + * @param array|string|float|int|bool $value Value of the cookie + * @param \DateTimeInterface|null $expiresAt Expiration time and date * @param string|null $path Path * @param string|null $domain Domain * @param bool|null $secure Is secure * @param bool|null $httpOnly HTTP Only - * @param string|null $sameSite Samesite + * @param \Cake\Http\Cookie\SameSiteEnum|string|null $sameSite Samesite */ public function __construct( string $name, - $value = '', + array|string|float|int|bool $value = '', ?DateTimeInterface $expiresAt = null, ?string $path = null, ?string $domain = null, ?bool $secure = null, ?bool $httpOnly = null, - ?string $sameSite = null + SameSiteEnum|string|null $sameSite = null, ) { $this->validateName($name); $this->name = $name; @@ -165,14 +165,13 @@ public function __construct( $this->httpOnly = $httpOnly ?? static::$defaults['httponly']; $this->path = $path ?? static::$defaults['path']; $this->secure = $secure ?? static::$defaults['secure']; - if ($sameSite === null) { - $this->sameSite = static::$defaults['samesite']; - } else { - $this->validateSameSiteValue($sameSite); - $this->sameSite = $sameSite; - } + $this->sameSite = static::resolveSameSiteEnum($sameSite ?? static::$defaults['samesite']); if ($expiresAt) { + if ($expiresAt instanceof DateTime) { + $expiresAt = clone $expiresAt; + } + /** @var \DateTimeImmutable|\DateTime $expiresAt */ $expiresAt = $expiresAt->setTimezone(new DateTimeZone('GMT')); } else { $expiresAt = static::$defaults['expires']; @@ -186,7 +185,7 @@ public function __construct( * Valid option keys are: * * - `expires`: Can be a UNIX timestamp or `strtotime()` compatible string or `DateTimeInterface` instance or `null`. - * - `path`: A path string. Defauts to `'/'`. + * - `path`: A path string. Defaults to `'/'`. * - `domain`: Domain name string. Defaults to `''`. * - `httponly`: Boolean. Defaults to `false`. * - `secure`: Boolean. Defaults to `false`. @@ -202,7 +201,7 @@ public static function setDefaults(array $options): void $options['expires'] = static::dateTimeInstance($options['expires']); } if (isset($options['samesite'])) { - static::validateSameSiteValue($options['samesite']); + $options['samesite'] = static::resolveSameSiteEnum($options['samesite']); } static::$defaults = $options + static::$defaults; @@ -212,12 +211,12 @@ public static function setDefaults(array $options): void * Factory method to create Cookie instances. * * @param string $name Cookie name - * @param array|string $value Value of the cookie + * @param array|string|float|int|bool $value Value of the cookie * @param array $options Cookies options. * @return static * @see \Cake\Cookie\Cookie::setDefaults() */ - public static function create(string $name, $value, array $options = []) + public static function create(string $name, array|string|float|int|bool $value, array $options = []): static { $options += static::$defaults; $options['expires'] = static::dateTimeInstance($options['expires']); @@ -230,43 +229,38 @@ public static function create(string $name, $value, array $options = []) $options['domain'], $options['secure'], $options['httponly'], - $options['samesite'] + $options['samesite'], ); } /** * Converts non null expiry value into DateTimeInterface instance. * - * @param mixed $expires Expiry value. - * @return \DateTime|\DateTimeImmutable|null + * @param \DateTimeInterface|string|int|null $expires Expiry value. + * @return \DateTimeInterface|null */ - protected static function dateTimeInstance($expires): ?DateTimeInterface + protected static function dateTimeInstance(DateTimeInterface|string|int|null $expires): ?DateTimeInterface { if ($expires === null) { return null; } if ($expires instanceof DateTimeInterface) { - /** @psalm-suppress UndefinedInterfaceMethod */ + /** + * @phpstan-ignore-next-line + */ return $expires->setTimezone(new DateTimeZone('GMT')); } - if (!is_string($expires) && !is_int($expires)) { - throw new InvalidArgumentException(sprintf( - 'Invalid type `%s` for expires. Expected an string, integer or DateTime object.', - getTypeName($expires) - )); - } - if (!is_numeric($expires)) { $expires = strtotime($expires) ?: null; } if ($expires !== null) { - $expires = new DateTimeImmutable('@' . (string)$expires); + return new DateTimeImmutable('@' . $expires); } - return $expires; + return null; } /** @@ -277,16 +271,16 @@ protected static function dateTimeInstance($expires): ?DateTimeInterface * @return static * @see \Cake\Http\Cookie\Cookie::setDefaults() */ - public static function createFromHeaderString(string $cookie, array $defaults = []) + public static function createFromHeaderString(string $cookie, array $defaults = []): static { - if (strpos($cookie, '";"') !== false) { + if (str_contains($cookie, '";"')) { $cookie = str_replace('";"', '{__cookie_replace__}', $cookie); $parts = str_replace('{__cookie_replace__}', '";"', explode(';', $cookie)); } else { - $parts = preg_split('/\;[ \t]*/', $cookie); + $parts = preg_split('/\;[ \t]*/', $cookie) ?: []; } - $nameValue = explode('=', array_shift($parts), 2); + $nameValue = explode('=', (string)array_shift($parts), 2); $name = array_shift($nameValue); $value = array_shift($nameValue) ?? ''; @@ -296,7 +290,7 @@ public static function createFromHeaderString(string $cookie, array $defaults = ] + $defaults; foreach ($parts as $part) { - if (strpos($part, '=') !== false) { + if (str_contains($part, '=')) { [$key, $value] = explode('=', $part); } else { $key = $part; @@ -312,22 +306,26 @@ public static function createFromHeaderString(string $cookie, array $defaults = unset($data['max-age']); } + // Ignore invalid value when parsing headers + // https://tools.ietf.org/html/draft-west-first-party-cookies-07#section-4.1 if (isset($data['samesite'])) { - // Ignore invalid value when parsing headers - // https://tools.ietf.org/html/draft-west-first-party-cookies-07#section-4.1 - if (!in_array($data['samesite'], CookieInterface::SAMESITE_VALUES, true)) { + try { + $data['samesite'] = static::resolveSameSiteEnum($data['samesite']); + } catch (ValueError) { unset($data['samesite']); } } - $name = (string)$data['name']; - $value = (string)$data['value']; + $name = $data['name']; + $value = $data['value']; + assert(is_string($name) && is_string($value)); unset($data['name'], $data['value']); + /** @phpstan-ignore-next-line */ return Cookie::create( $name, $value, - $data + $data, ); } @@ -340,11 +338,13 @@ public function toHeaderValue(): string { $value = $this->value; if ($this->isExpanded) { - /** @psalm-suppress PossiblyInvalidArgument */ - $value = $this->_flatten($this->value); + assert(is_array($value), '$value is not an array'); + + $value = $this->_flatten($value); } + $headerValue = []; - /** @psalm-suppress PossiblyInvalidArgument */ + /** @var string $value */ $headerValue[] = sprintf('%s=%s', $this->name, rawurlencode($value)); if ($this->expiresAt) { @@ -357,7 +357,7 @@ public function toHeaderValue(): string $headerValue[] = sprintf('domain=%s', $this->domain); } if ($this->sameSite) { - $headerValue[] = sprintf('samesite=%s', $this->sameSite); + $headerValue[] = sprintf('samesite=%s', $this->sameSite->value); } if ($this->secure) { $headerValue[] = 'secure'; @@ -372,7 +372,7 @@ public function toHeaderValue(): string /** * @inheritDoc */ - public function withName(string $name) + public function withName(string $name): static { $this->validateName($name); $new = clone $this; @@ -409,11 +409,11 @@ protected function validateName(string $name): void { if (preg_match("/[=,;\t\r\n\013\014]/", $name)) { throw new InvalidArgumentException( - sprintf('The cookie name `%s` contains invalid characters.', $name) + sprintf('The cookie name `%s` contains invalid characters.', $name), ); } - if (empty($name)) { + if (!$name) { throw new InvalidArgumentException('The cookie name cannot be empty.'); } } @@ -421,43 +421,31 @@ protected function validateName(string $name): void /** * @inheritDoc */ - public function getValue() + public function getValue(): array|string { return $this->value; } - /** - * Gets the cookie value as a string. - * - * This will collapse any complex data in the cookie with json_encode() - * - * @return mixed - * @deprecated 4.0.0 Use {@link getScalarValue()} instead. - */ - public function getStringValue() - { - deprecationWarning('Cookie::getStringValue() is deprecated. Use getScalarValue() instead.'); - - return $this->getScalarValue(); - } - /** * @inheritDoc */ - public function getScalarValue() + public function getScalarValue(): string { if ($this->isExpanded) { - /** @psalm-suppress PossiblyInvalidArgument */ + assert(is_array($this->value), '$value is not an array'); + return $this->_flatten($this->value); } + assert(is_string($this->value), '$value is not a string'); + return $this->value; } /** * @inheritDoc */ - public function withValue($value) + public function withValue(array|string|float|int|bool $value): static { $new = clone $this; $new->_setValue($value); @@ -468,19 +456,19 @@ public function withValue($value) /** * Setter for the value attribute. * - * @param array|string $value The value to store. + * @param array|string|float|int|bool $value The value to store. * @return void */ - protected function _setValue($value): void + protected function _setValue(array|string|float|int|bool $value): void { $this->isExpanded = is_array($value); - $this->value = $value; + $this->value = is_array($value) ? $value : (string)$value; } /** * @inheritDoc */ - public function withPath(string $path) + public function withPath(string $path): static { $new = clone $this; $new->path = $path; @@ -499,7 +487,7 @@ public function getPath(): string /** * @inheritDoc */ - public function withDomain(string $domain) + public function withDomain(string $domain): static { $new = clone $this; $new->domain = $domain; @@ -526,7 +514,7 @@ public function isSecure(): bool /** * @inheritDoc */ - public function withSecure(bool $secure) + public function withSecure(bool $secure): static { $new = clone $this; $new->secure = $secure; @@ -537,7 +525,7 @@ public function withSecure(bool $secure) /** * @inheritDoc */ - public function withHttpOnly(bool $httpOnly) + public function withHttpOnly(bool $httpOnly): static { $new = clone $this; $new->httpOnly = $httpOnly; @@ -556,8 +544,12 @@ public function isHttpOnly(): bool /** * @inheritDoc */ - public function withExpiry($dateTime) + public function withExpiry(DateTimeInterface $dateTime): static { + if ($dateTime instanceof DateTime) { + $dateTime = clone $dateTime; + } + $new = clone $this; $new->expiresAt = $dateTime->setTimezone(new DateTimeZone('GMT')); @@ -567,7 +559,7 @@ public function withExpiry($dateTime) /** * @inheritDoc */ - public function getExpiry() + public function getExpiry(): ?DateTimeInterface { return $this->expiresAt; } @@ -599,9 +591,13 @@ public function getFormattedExpires(): string /** * @inheritDoc */ - public function isExpired($time = null): bool + public function isExpired(?DateTimeInterface $time = null): bool { $time = $time ?: new DateTimeImmutable('now', new DateTimeZone('UTC')); + if ($time instanceof DateTime) { + $time = clone $time; + } + if (!$this->expiresAt) { return false; } @@ -612,7 +608,7 @@ public function isExpired($time = null): bool /** * @inheritDoc */ - public function withNeverExpire() + public function withNeverExpire(): static { $new = clone $this; $new->expiresAt = new DateTimeImmutable('2038-01-01'); @@ -623,7 +619,7 @@ public function withNeverExpire() /** * @inheritDoc */ - public function withExpired() + public function withExpired(): static { $new = clone $this; $new->expiresAt = new DateTimeImmutable('@1'); @@ -634,7 +630,7 @@ public function withExpired() /** * @inheritDoc */ - public function getSameSite(): ?string + public function getSameSite(): ?SameSiteEnum { return $this->sameSite; } @@ -642,32 +638,27 @@ public function getSameSite(): ?string /** * @inheritDoc */ - public function withSameSite(?string $sameSite) + public function withSameSite(SameSiteEnum|string|null $sameSite): static { - if ($sameSite !== null) { - $this->validateSameSiteValue($sameSite); - } - $new = clone $this; - $new->sameSite = $sameSite; + $new->sameSite = static::resolveSameSiteEnum($sameSite); return $new; } /** - * Check that value passed for SameSite is valid. + * Create SameSiteEnum instance. * - * @param string $sameSite SameSite value - * @return void - * @throws \InvalidArgumentException + * @param \Cake\Http\Cookie\SameSiteEnum|string|null $sameSite SameSite value + * @return \Cake\Http\Cookie\SameSiteEnum|null */ - protected static function validateSameSiteValue(string $sameSite) + protected static function resolveSameSiteEnum(SameSiteEnum|string|null $sameSite): ?SameSiteEnum { - if (!in_array($sameSite, CookieInterface::SAMESITE_VALUES, true)) { - throw new InvalidArgumentException( - 'Samesite value must be either of: ' . implode(', ', CookieInterface::SAMESITE_VALUES) - ); - } + return match (true) { + $sameSite === null => $sameSite, + $sameSite instanceof SameSiteEnum => $sameSite, + default => SameSiteEnum::from(ucfirst(strtolower($sameSite))), + }; } /** @@ -682,11 +673,12 @@ protected static function validateSameSiteValue(string $sameSite) public function check(string $path): bool { if ($this->isExpanded === false) { - /** @psalm-suppress PossiblyInvalidArgument */ + assert(is_string($this->value), '$value is not a string'); $this->value = $this->_expand($this->value); } - /** @psalm-suppress PossiblyInvalidArgument */ + assert(is_array($this->value), '$value is not an array'); + return Hash::check($this->value, $path); } @@ -697,15 +689,15 @@ public function check(string $path): bool * @param mixed $value Value to write * @return static */ - public function withAddedValue(string $path, $value) + public function withAddedValue(string $path, mixed $value): static { $new = clone $this; if ($new->isExpanded === false) { - /** @psalm-suppress PossiblyInvalidArgument */ + assert(is_string($new->value), '$value is not a string'); $new->value = $new->_expand($new->value); } - /** @psalm-suppress PossiblyInvalidArgument */ + assert(is_array($new->value), '$value is not an array'); $new->value = Hash::insert($new->value, $path, $value); return $new; @@ -717,15 +709,16 @@ public function withAddedValue(string $path, $value) * @param string $path Path to remove * @return static */ - public function withoutAddedValue(string $path) + public function withoutAddedValue(string $path): static { $new = clone $this; if ($new->isExpanded === false) { - /** @psalm-suppress PossiblyInvalidArgument */ + assert(is_string($new->value), '$value is not a string'); $new->value = $new->_expand($new->value); } - /** @psalm-suppress PossiblyInvalidArgument */ + assert(is_array($new->value), '$value is not an array'); + $new->value = Hash::remove($new->value, $path); return $new; @@ -740,10 +733,11 @@ public function withoutAddedValue(string $path) * @param string|null $path Path to read the data from * @return mixed */ - public function read(?string $path = null) + public function read(?string $path = null): mixed { if ($this->isExpanded === false) { - /** @psalm-suppress PossiblyInvalidArgument */ + assert(is_string($this->value), '$value is not a string'); + $this->value = $this->_expand($this->value); } @@ -751,7 +745,8 @@ public function read(?string $path = null) return $this->value; } - /** @psalm-suppress PossiblyInvalidArgument */ + assert(is_array($this->value), '$value is not an array'); + return Hash::get($this->value, $path); } @@ -779,7 +774,7 @@ public function getOptions(): array ]; if ($this->sameSite !== null) { - $options['samesite'] = $this->sameSite; + $options['samesite'] = $this->sameSite->value; } return $options; @@ -804,7 +799,7 @@ public function toArray(): array */ protected function _flatten(array $array): string { - return json_encode($array); + return json_encode($array, JSON_THROW_ON_ERROR); } /** @@ -814,14 +809,12 @@ protected function _flatten(array $array): string * @param string $string A string containing JSON encoded data, or a bare string. * @return array|string Map of key and values */ - protected function _expand(string $string) + protected function _expand(string $string): array|string { $this->isExpanded = true; $first = substr($string, 0, 1); if ($first === '{' || $first === '[') { - $ret = json_decode($string, true); - - return $ret ?? $string; + return json_decode($string, true) ?? $string; } $array = []; diff --git a/app/vendor/cakephp/cakephp/src/Http/Cookie/CookieCollection.php b/app/vendor/cakephp/cakephp/src/Http/Cookie/CookieCollection.php index 966b57e9e..82abdef2b 100644 --- a/app/vendor/cakephp/cakephp/src/Http/Cookie/CookieCollection.php +++ b/app/vendor/cakephp/cakephp/src/Http/Cookie/CookieCollection.php @@ -27,7 +27,6 @@ use Psr\Http\Message\ServerRequestInterface; use Traversable; use TypeError; -use function Cake\Core\getTypeName; use function Cake\Core\triggerWarning; /** @@ -43,9 +42,9 @@ class CookieCollection implements IteratorAggregate, Countable /** * Cookie objects * - * @var array<\Cake\Http\Cookie\CookieInterface> + * @var array */ - protected $cookies = []; + protected array $cookies = []; /** * Constructor @@ -67,13 +66,13 @@ public function __construct(array $cookies = []) * @param array $defaults The defaults attributes. * @return static */ - public static function createFromHeader(array $header, array $defaults = []) + public static function createFromHeader(array $header, array $defaults = []): static { $cookies = []; foreach ($header as $value) { try { $cookies[] = Cookie::createFromHeaderString($value, $defaults); - } catch (Exception | TypeError $e) { + } catch (Exception | TypeError) { // Don't blow up on invalid cookies } } @@ -87,7 +86,7 @@ public static function createFromHeader(array $header, array $defaults = []) * @param \Psr\Http\Message\ServerRequestInterface $request The request to extract cookie data from * @return static */ - public static function createFromServerRequest(ServerRequestInterface $request) + public static function createFromServerRequest(ServerRequestInterface $request): static { $data = $request->getCookieParams(); $cookies = []; @@ -118,7 +117,7 @@ public function count(): int * @param \Cake\Http\Cookie\CookieInterface $cookie Cookie instance to add. * @return static */ - public function add(CookieInterface $cookie) + public function add(CookieInterface $cookie): static { $new = clone $this; $new->cookies[$cookie->getId()] = $cookie; @@ -135,19 +134,18 @@ public function add(CookieInterface $cookie) */ public function get(string $name): CookieInterface { - $key = mb_strtolower($name); - foreach ($this->cookies as $cookie) { - if (mb_strtolower($cookie->getName()) === $key) { - return $cookie; - } + $cookie = $this->__get($name); + + if ($cookie === null) { + throw new InvalidArgumentException( + sprintf( + 'Cookie `%s` not found. Use `has()` to check first for existence.', + $name, + ), + ); } - throw new InvalidArgumentException( - sprintf( - 'Cookie %s not found. Use has() to check first for existence.', - $name - ) - ); + return $cookie; } /** @@ -157,15 +155,37 @@ public function get(string $name): CookieInterface * @return bool True if the cookie exists, otherwise false. */ public function has(string $name): bool + { + return $this->__get($name) !== null; + } + + /** + * Get the first cookie by name if cookie with provided name exists + * + * @param string $name The name of the cookie. + * @return \Cake\Http\Cookie\CookieInterface|null + */ + public function __get(string $name): ?CookieInterface { $key = mb_strtolower($name); foreach ($this->cookies as $cookie) { if (mb_strtolower($cookie->getName()) === $key) { - return true; + return $cookie; } } - return false; + return null; + } + + /** + * Check if a cookie with the given name exists + * + * @param string $name The cookie name to check. + * @return bool True if the cookie exists, otherwise false. + */ + public function __isset(string $name): bool + { + return $this->__get($name) !== null; } /** @@ -176,7 +196,7 @@ public function has(string $name): bool * @param string $name The name of the cookie to remove. * @return static */ - public function remove(string $name) + public function remove(string $name): static { $new = clone $this; $key = mb_strtolower($name); @@ -204,9 +224,9 @@ protected function checkCookies(array $cookies): void sprintf( 'Expected `%s[]` as $cookies but instead got `%s` at index %d', static::class, - getTypeName($cookie), - $index - ) + get_debug_type($cookie), + $index, + ), ); } } @@ -240,7 +260,7 @@ public function addToRequest(RequestInterface $request, array $extraCookies = [] $cookies = $this->findMatchingCookies( $uri->getScheme(), $uri->getHost(), - $uri->getPath() ?: '/' + $uri->getPath() ?: '/', ); $cookies = $extraCookies + $cookies; $cookiePairs = []; @@ -250,13 +270,13 @@ public function addToRequest(RequestInterface $request, array $extraCookies = [] if ($size > 4096) { triggerWarning(sprintf( 'The cookie `%s` exceeds the recommended maximum cookie length of 4096 bytes.', - $key + $key, )); } $cookiePairs[] = $cookie; } - if (empty($cookiePairs)) { + if (!$cookiePairs) { return $request; } @@ -279,11 +299,11 @@ protected function findMatchingCookies(string $scheme, string $host, string $pat if ($scheme === 'http' && $cookie->isSecure()) { continue; } - if (strpos($path, $cookie->getPath()) !== 0) { + if (!str_starts_with($path, $cookie->getPath())) { continue; } $domain = $cookie->getDomain(); - $leadingDot = substr($domain, 0, 1) === '.'; + $leadingDot = str_starts_with($domain, '.'); if ($leadingDot) { $domain = ltrim($domain, '.'); } @@ -310,7 +330,7 @@ protected function findMatchingCookies(string $scheme, string $host, string $pat * @param \Psr\Http\Message\RequestInterface $request Request to get cookie context from. * @return static */ - public function addFromResponse(ResponseInterface $response, RequestInterface $request) + public function addFromResponse(ResponseInterface $response, RequestInterface $request): static { $uri = $request->getUri(); $host = $uri->getHost(); @@ -318,7 +338,7 @@ public function addFromResponse(ResponseInterface $response, RequestInterface $r $cookies = static::createFromHeader( $response->getHeader('Set-Cookie'), - ['domain' => $host, 'path' => $path] + ['domain' => $host, 'path' => $path], ); $new = clone $this; foreach ($cookies as $cookie) { @@ -345,7 +365,7 @@ protected function removeExpiredCookies(string $host, string $path): void if (!$cookie->isExpired($time)) { continue; } - $pathMatches = strpos($path, $cookie->getPath()) === 0; + $pathMatches = str_starts_with($path, $cookie->getPath()); $hostMatches = preg_match($hostPattern, $cookie->getDomain()); if ($pathMatches && $hostMatches) { unset($this->cookies[$i]); diff --git a/app/vendor/cakephp/cakephp/src/Http/Cookie/CookieInterface.php b/app/vendor/cakephp/cakephp/src/Http/Cookie/CookieInterface.php index 2befd1e11..c32ff0fc4 100644 --- a/app/vendor/cakephp/cakephp/src/Http/Cookie/CookieInterface.php +++ b/app/vendor/cakephp/cakephp/src/Http/Cookie/CookieInterface.php @@ -15,6 +15,8 @@ */ namespace Cake\Http\Cookie; +use DateTimeInterface; + /** * Cookie Interface */ @@ -65,7 +67,7 @@ interface CookieInterface * @param string $name Name of the cookie * @return static */ - public function withName(string $name); + public function withName(string $name): static; /** * Gets the cookie name @@ -79,24 +81,24 @@ public function getName(): string; * * @return array|string */ - public function getValue(); + public function getValue(): array|string; /** * Gets the cookie value as scalar. * * This will collapse any complex data in the cookie with json_encode() * - * @return mixed + * @return string */ - public function getScalarValue(); + public function getScalarValue(): string; /** * Create a cookie with an updated value. * - * @param array|string $value Value of the cookie to set + * @param array|string|float|int|bool $value Value of the cookie to set * @return static */ - public function withValue($value); + public function withValue(array|string|float|int|bool $value): static; /** * Get the id for a cookie @@ -120,7 +122,7 @@ public function getPath(): string; * @param string $path Sets the path * @return static */ - public function withPath(string $path); + public function withPath(string $path): static; /** * Get the domain attribute. @@ -135,14 +137,14 @@ public function getDomain(): string; * @param string $domain Domain to set * @return static */ - public function withDomain(string $domain); + public function withDomain(string $domain): static; /** * Get the current expiry time * - * @return \DateTime|\DateTimeImmutable|null Timestamp of expiry or null + * @return \DateTimeInterface|null Timestamp of expiry or null */ - public function getExpiry(); + public function getExpiry(): ?DateTimeInterface; /** * Get the timestamp from the expiration time @@ -161,17 +163,17 @@ public function getFormattedExpires(): string; /** * Create a cookie with an updated expiration date * - * @param \DateTime|\DateTimeImmutable $dateTime Date time object + * @param \DateTimeInterface $dateTime Date time object * @return static */ - public function withExpiry($dateTime); + public function withExpiry(DateTimeInterface $dateTime): static; /** * Create a new cookie that will virtually never expire. * * @return static */ - public function withNeverExpire(); + public function withNeverExpire(): static; /** * Create a new cookie that will expire/delete the cookie from the browser. @@ -180,17 +182,17 @@ public function withNeverExpire(); * * @return static */ - public function withExpired(); + public function withExpired(): static; /** * Check if a cookie is expired when compared to $time * * Cookies without an expiration date always return false. * - * @param \DateTime|\DateTimeImmutable $time The time to test against. Defaults to 'now' in UTC. + * @param \DateTimeInterface|null $time The time to test against. Defaults to 'now' in UTC. * @return bool */ - public function isExpired($time = null): bool; + public function isExpired(?DateTimeInterface $time = null): bool; /** * Check if the cookie is HTTP only @@ -205,7 +207,7 @@ public function isHttpOnly(): bool; * @param bool $httpOnly HTTP Only * @return static */ - public function withHttpOnly(bool $httpOnly); + public function withHttpOnly(bool $httpOnly): static; /** * Check if the cookie is secure @@ -220,23 +222,22 @@ public function isSecure(): bool; * @param bool $secure Secure attribute value * @return static */ - public function withSecure(bool $secure); + public function withSecure(bool $secure): static; /** * Get the SameSite attribute. * - * @return string|null + * @return \Cake\Http\Cookie\SameSiteEnum|null */ - public function getSameSite(): ?string; + public function getSameSite(): ?SameSiteEnum; /** * Create a cookie with an updated SameSite option. * - * @param string|null $sameSite Value for to set for Samesite option. - * One of CookieInterface::SAMESITE_* constants. + * @param \Cake\Http\Cookie\SameSiteEnum|string|null $sameSite Value for to set for Samesite option. * @return static */ - public function withSameSite(?string $sameSite); + public function withSameSite(SameSiteEnum|string|null $sameSite): static; /** * Get cookie options diff --git a/app/vendor/cakephp/cakephp/src/Http/Cookie/SameSiteEnum.php b/app/vendor/cakephp/cakephp/src/Http/Cookie/SameSiteEnum.php new file mode 100644 index 000000000..79bc59e05 --- /dev/null +++ b/app/vendor/cakephp/cakephp/src/Http/Cookie/SameSiteEnum.php @@ -0,0 +1,23 @@ + */ - protected $_headers = []; + protected array $_headers = []; /** * Constructor. * - * @param \Psr\Http\Message\MessageInterface $response The response object to add headers onto. + * @param \Psr\Http\Message\ResponseInterface $response The response object to add headers onto. * @param string $origin The request's Origin header. * @param bool $isSsl Whether the request was over SSL. */ - public function __construct(MessageInterface $response, string $origin, bool $isSsl = false) + public function __construct(ResponseInterface $response, string $origin, bool $isSsl = false) { $this->_origin = $origin; $this->_isSsl = $isSsl; @@ -80,9 +80,9 @@ public function __construct(MessageInterface $response, string $origin, bool $is * If the builder has no Origin, or if there are no allowed domains, * or if the allowed domains do not match the Origin header no headers will be applied. * - * @return \Psr\Http\Message\MessageInterface A new instance of the response with new headers. + * @return \Psr\Http\Message\ResponseInterface A new instance of the response with new headers. */ - public function build(): MessageInterface + public function build(): ResponseInterface { $response = $this->_response; if (empty($this->_origin)) { @@ -107,7 +107,7 @@ public function build(): MessageInterface * @param array|string $domains The allowed domains * @return $this */ - public function allowOrigin($domains) + public function allowOrigin(array|string $domains) { $allowed = $this->_normalizeDomains((array)$domains); foreach ($allowed as $domain) { @@ -126,7 +126,7 @@ public function allowOrigin($domains) * Normalize the origin to regular expressions and put in an array format * * @param array $domains Domain names to normalize. - * @return array + * @return array> */ protected function _normalizeDomains(array $domains): array { @@ -136,9 +136,9 @@ protected function _normalizeDomains(array $domains): array $result[] = ['preg' => '@.@', 'original' => '*']; continue; } - - $original = $preg = $domain; - if (strpos($domain, '://') === false) { + $original = $domain; + $preg = $domain; + if (!str_contains($domain, '://')) { $preg = ($this->_isSsl ? 'https://' : 'http://') . $domain; } $preg = '@^' . str_replace('\*', '.*', preg_quote($preg, '@')) . '$@'; @@ -205,7 +205,7 @@ public function exposeHeaders(array $headers) * @param string|int $age The max-age for OPTIONS requests in seconds * @return $this */ - public function maxAge($age) + public function maxAge(string|int $age) { $this->_headers['Access-Control-Max-Age'] = $age; diff --git a/app/vendor/cakephp/cakephp/src/Http/Exception/BadRequestException.php b/app/vendor/cakephp/cakephp/src/Http/Exception/BadRequestException.php index 8277a2629..8f86982e8 100644 --- a/app/vendor/cakephp/cakephp/src/Http/Exception/BadRequestException.php +++ b/app/vendor/cakephp/cakephp/src/Http/Exception/BadRequestException.php @@ -24,7 +24,7 @@ class BadRequestException extends HttpException /** * @inheritDoc */ - protected $_defaultCode = 400; + protected int $_defaultCode = 400; /** * Constructor @@ -35,7 +35,7 @@ class BadRequestException extends HttpException */ public function __construct(?string $message = null, ?int $code = null, ?Throwable $previous = null) { - if (empty($message)) { + if (!$message) { $message = 'Bad Request'; } parent::__construct($message, $code, $previous); diff --git a/app/vendor/cakephp/cakephp/src/Http/Exception/ConflictException.php b/app/vendor/cakephp/cakephp/src/Http/Exception/ConflictException.php index af7197af6..fd29a3484 100644 --- a/app/vendor/cakephp/cakephp/src/Http/Exception/ConflictException.php +++ b/app/vendor/cakephp/cakephp/src/Http/Exception/ConflictException.php @@ -24,7 +24,7 @@ class ConflictException extends HttpException /** * @inheritDoc */ - protected $_defaultCode = 409; + protected int $_defaultCode = 409; /** * Constructor @@ -35,7 +35,7 @@ class ConflictException extends HttpException */ public function __construct(?string $message = null, ?int $code = null, ?Throwable $previous = null) { - if (empty($message)) { + if (!$message) { $message = 'Conflict'; } parent::__construct($message, $code, $previous); diff --git a/app/vendor/cakephp/cakephp/src/Http/Exception/ForbiddenException.php b/app/vendor/cakephp/cakephp/src/Http/Exception/ForbiddenException.php index 0b901604e..ddb2761f9 100644 --- a/app/vendor/cakephp/cakephp/src/Http/Exception/ForbiddenException.php +++ b/app/vendor/cakephp/cakephp/src/Http/Exception/ForbiddenException.php @@ -24,7 +24,7 @@ class ForbiddenException extends HttpException /** * @inheritDoc */ - protected $_defaultCode = 403; + protected int $_defaultCode = 403; /** * Constructor @@ -35,7 +35,7 @@ class ForbiddenException extends HttpException */ public function __construct(?string $message = null, ?int $code = null, ?Throwable $previous = null) { - if (empty($message)) { + if (!$message) { $message = 'Forbidden'; } parent::__construct($message, $code, $previous); diff --git a/app/vendor/cakephp/cakephp/src/Http/Exception/GoneException.php b/app/vendor/cakephp/cakephp/src/Http/Exception/GoneException.php index f6c547fa8..63ed61c6f 100644 --- a/app/vendor/cakephp/cakephp/src/Http/Exception/GoneException.php +++ b/app/vendor/cakephp/cakephp/src/Http/Exception/GoneException.php @@ -24,7 +24,7 @@ class GoneException extends HttpException /** * @inheritDoc */ - protected $_defaultCode = 410; + protected int $_defaultCode = 410; /** * Constructor @@ -35,7 +35,7 @@ class GoneException extends HttpException */ public function __construct(?string $message = null, ?int $code = null, ?Throwable $previous = null) { - if (empty($message)) { + if (!$message) { $message = 'Gone'; } parent::__construct($message, $code, $previous); diff --git a/app/vendor/cakephp/cakephp/src/Http/Exception/HttpException.php b/app/vendor/cakephp/cakephp/src/Http/Exception/HttpException.php index e39ea89d2..e91e5f67c 100644 --- a/app/vendor/cakephp/cakephp/src/Http/Exception/HttpException.php +++ b/app/vendor/cakephp/cakephp/src/Http/Exception/HttpException.php @@ -15,6 +15,7 @@ namespace Cake\Http\Exception; use Cake\Core\Exception\CakeException; +use Cake\Core\Exception\HttpErrorCodeInterface; /** * Parent class for all the HTTP related exceptions in CakePHP. @@ -24,17 +25,17 @@ * You may also use this as a meaningful bridge to {@link \Cake\Core\Exception\CakeException}, e.g.: * throw new \Cake\Network\Exception\HttpException('HTTP Version Not Supported', 505); */ -class HttpException extends CakeException +class HttpException extends CakeException implements HttpErrorCodeInterface { /** * @inheritDoc */ - protected $_defaultCode = 500; + protected int $_defaultCode = 500; /** * @var array */ - protected $headers = []; + protected array $headers = []; /** * Set a single HTTP response header. @@ -43,7 +44,7 @@ class HttpException extends CakeException * @param array|string|null $value Header value * @return void */ - public function setHeader(string $header, $value = null): void + public function setHeader(string $header, array|string|null $value = null): void { $this->headers[$header] = $value; } diff --git a/app/vendor/cakephp/cakephp/src/Http/Exception/InternalErrorException.php b/app/vendor/cakephp/cakephp/src/Http/Exception/InternalErrorException.php index efff1e06b..43d2816ec 100644 --- a/app/vendor/cakephp/cakephp/src/Http/Exception/InternalErrorException.php +++ b/app/vendor/cakephp/cakephp/src/Http/Exception/InternalErrorException.php @@ -30,7 +30,7 @@ class InternalErrorException extends HttpException */ public function __construct(?string $message = null, ?int $code = null, ?Throwable $previous = null) { - if (empty($message)) { + if (!$message) { $message = 'Internal Server Error'; } parent::__construct($message, $code, $previous); diff --git a/app/vendor/cakephp/cakephp/src/Http/Exception/InvalidCsrfTokenException.php b/app/vendor/cakephp/cakephp/src/Http/Exception/InvalidCsrfTokenException.php index 52849665a..5409a3974 100644 --- a/app/vendor/cakephp/cakephp/src/Http/Exception/InvalidCsrfTokenException.php +++ b/app/vendor/cakephp/cakephp/src/Http/Exception/InvalidCsrfTokenException.php @@ -24,7 +24,7 @@ class InvalidCsrfTokenException extends HttpException /** * @inheritDoc */ - protected $_defaultCode = 403; + protected int $_defaultCode = 403; /** * Constructor @@ -35,7 +35,7 @@ class InvalidCsrfTokenException extends HttpException */ public function __construct(?string $message = null, ?int $code = null, ?Throwable $previous = null) { - if (empty($message)) { + if (!$message) { $message = 'Invalid CSRF Token'; } parent::__construct($message, $code, $previous); diff --git a/app/vendor/cakephp/cakephp/src/Http/Exception/MethodNotAllowedException.php b/app/vendor/cakephp/cakephp/src/Http/Exception/MethodNotAllowedException.php index 4ac07dee2..e6ed508c3 100644 --- a/app/vendor/cakephp/cakephp/src/Http/Exception/MethodNotAllowedException.php +++ b/app/vendor/cakephp/cakephp/src/Http/Exception/MethodNotAllowedException.php @@ -24,7 +24,7 @@ class MethodNotAllowedException extends HttpException /** * @inheritDoc */ - protected $_defaultCode = 405; + protected int $_defaultCode = 405; /** * Constructor @@ -35,7 +35,7 @@ class MethodNotAllowedException extends HttpException */ public function __construct(?string $message = null, ?int $code = null, ?Throwable $previous = null) { - if (empty($message)) { + if (!$message) { $message = 'Method Not Allowed'; } parent::__construct($message, $code, $previous); diff --git a/app/vendor/cakephp/cakephp/src/Http/Exception/MissingControllerException.php b/app/vendor/cakephp/cakephp/src/Http/Exception/MissingControllerException.php index 63152bd05..ccf8c2da8 100644 --- a/app/vendor/cakephp/cakephp/src/Http/Exception/MissingControllerException.php +++ b/app/vendor/cakephp/cakephp/src/Http/Exception/MissingControllerException.php @@ -15,27 +15,21 @@ namespace Cake\Http\Exception; use Cake\Core\Exception\CakeException; +use Cake\Core\Exception\HttpErrorCodeInterface; /** * Missing Controller exception - used when a controller * cannot be found. */ -class MissingControllerException extends CakeException +class MissingControllerException extends CakeException implements HttpErrorCodeInterface { /** * @inheritDoc */ - protected $_defaultCode = 404; + protected int $_defaultCode = 404; /** * @inheritDoc */ - protected $_messageTemplate = 'Controller class %s could not be found.'; + protected string $_messageTemplate = 'Controller class `%s` could not be found.'; } - -// phpcs:disable -class_alias( - 'Cake\Http\Exception\MissingControllerException', - 'Cake\Routing\Exception\MissingControllerException' -); -// phpcs:enable diff --git a/app/vendor/cakephp/cakephp/src/Http/Exception/NotAcceptableException.php b/app/vendor/cakephp/cakephp/src/Http/Exception/NotAcceptableException.php index 0aad59e76..bda278f6d 100644 --- a/app/vendor/cakephp/cakephp/src/Http/Exception/NotAcceptableException.php +++ b/app/vendor/cakephp/cakephp/src/Http/Exception/NotAcceptableException.php @@ -24,7 +24,7 @@ class NotAcceptableException extends HttpException /** * @inheritDoc */ - protected $_defaultCode = 406; + protected int $_defaultCode = 406; /** * Constructor @@ -35,7 +35,7 @@ class NotAcceptableException extends HttpException */ public function __construct(?string $message = null, ?int $code = null, ?Throwable $previous = null) { - if (empty($message)) { + if (!$message) { $message = 'Not Acceptable'; } parent::__construct($message, $code, $previous); diff --git a/app/vendor/cakephp/cakephp/src/Http/Exception/NotFoundException.php b/app/vendor/cakephp/cakephp/src/Http/Exception/NotFoundException.php index 28c5c122f..5940b0324 100644 --- a/app/vendor/cakephp/cakephp/src/Http/Exception/NotFoundException.php +++ b/app/vendor/cakephp/cakephp/src/Http/Exception/NotFoundException.php @@ -24,7 +24,7 @@ class NotFoundException extends HttpException /** * @inheritDoc */ - protected $_defaultCode = 404; + protected int $_defaultCode = 404; /** * Constructor @@ -35,7 +35,7 @@ class NotFoundException extends HttpException */ public function __construct(?string $message = null, ?int $code = null, ?Throwable $previous = null) { - if (empty($message)) { + if (!$message) { $message = 'Not Found'; } parent::__construct($message, $code, $previous); diff --git a/app/vendor/cakephp/cakephp/src/Http/Exception/NotImplementedException.php b/app/vendor/cakephp/cakephp/src/Http/Exception/NotImplementedException.php index 6ba67425a..348bf5a67 100644 --- a/app/vendor/cakephp/cakephp/src/Http/Exception/NotImplementedException.php +++ b/app/vendor/cakephp/cakephp/src/Http/Exception/NotImplementedException.php @@ -22,10 +22,10 @@ class NotImplementedException extends HttpException /** * @inheritDoc */ - protected $_messageTemplate = '%s is not implemented.'; + protected string $_messageTemplate = '%s is not implemented.'; /** * @inheritDoc */ - protected $_defaultCode = 501; + protected int $_defaultCode = 501; } diff --git a/app/vendor/cakephp/cakephp/src/Http/Exception/RedirectException.php b/app/vendor/cakephp/cakephp/src/Http/Exception/RedirectException.php index ee74d3d59..b651789d8 100644 --- a/app/vendor/cakephp/cakephp/src/Http/Exception/RedirectException.php +++ b/app/vendor/cakephp/cakephp/src/Http/Exception/RedirectException.php @@ -16,8 +16,6 @@ */ namespace Cake\Http\Exception; -use function Cake\Core\deprecationWarning; - /** * An exception subclass used by routing and application code to * trigger a redirect. @@ -29,7 +27,7 @@ * ``` * * Additional headers can also be provided in the constructor, or - * using the addHeaders() method. + * using the setHeaders() method. */ class RedirectException extends HttpException { @@ -48,39 +46,4 @@ public function __construct(string $target, int $code = 302, array $headers = [] $this->setHeader($key, (array)$value); } } - - /** - * Add headers to be included in the response generated from this exception - * - * @param array $headers An array of `header => value` to append to the exception. - * If a header already exists, the new values will be appended to the existing ones. - * @return $this - * @deprecated 4.2.0 Use `setHeaders()` instead. - */ - public function addHeaders(array $headers) - { - deprecationWarning('RedirectException::addHeaders() is deprecated, use setHeaders() instead.'); - - foreach ($headers as $key => $value) { - $this->headers[$key][] = $value; - } - - return $this; - } - - /** - * Remove a header from the exception. - * - * @param string $key The header to remove. - * @return $this - * @deprecated 4.2.0 Use `setHeaders()` instead. - */ - public function removeHeader(string $key) - { - deprecationWarning('RedirectException::removeHeader() is deprecated, use setHeaders() instead.'); - - unset($this->headers[$key]); - - return $this; - } } diff --git a/app/vendor/cakephp/cakephp/src/Http/Exception/ServiceUnavailableException.php b/app/vendor/cakephp/cakephp/src/Http/Exception/ServiceUnavailableException.php index 374849b8e..0287fc96f 100644 --- a/app/vendor/cakephp/cakephp/src/Http/Exception/ServiceUnavailableException.php +++ b/app/vendor/cakephp/cakephp/src/Http/Exception/ServiceUnavailableException.php @@ -24,7 +24,7 @@ class ServiceUnavailableException extends HttpException /** * @inheritDoc */ - protected $_defaultCode = 503; + protected int $_defaultCode = 503; /** * Constructor @@ -35,7 +35,7 @@ class ServiceUnavailableException extends HttpException */ public function __construct(?string $message = null, ?int $code = null, ?Throwable $previous = null) { - if (empty($message)) { + if (!$message) { $message = 'Service Unavailable'; } parent::__construct($message, $code, $previous); diff --git a/app/vendor/cakephp/cakephp/src/Http/Exception/UnauthorizedException.php b/app/vendor/cakephp/cakephp/src/Http/Exception/UnauthorizedException.php index a01e01659..43111625c 100644 --- a/app/vendor/cakephp/cakephp/src/Http/Exception/UnauthorizedException.php +++ b/app/vendor/cakephp/cakephp/src/Http/Exception/UnauthorizedException.php @@ -24,7 +24,7 @@ class UnauthorizedException extends HttpException /** * @inheritDoc */ - protected $_defaultCode = 401; + protected int $_defaultCode = 401; /** * Constructor @@ -35,7 +35,7 @@ class UnauthorizedException extends HttpException */ public function __construct(?string $message = null, ?int $code = null, ?Throwable $previous = null) { - if (empty($message)) { + if (!$message) { $message = 'Unauthorized'; } parent::__construct($message, $code, $previous); diff --git a/app/vendor/cakephp/cakephp/src/Http/Exception/UnavailableForLegalReasonsException.php b/app/vendor/cakephp/cakephp/src/Http/Exception/UnavailableForLegalReasonsException.php index 7fbd7ce47..f973e0e0c 100644 --- a/app/vendor/cakephp/cakephp/src/Http/Exception/UnavailableForLegalReasonsException.php +++ b/app/vendor/cakephp/cakephp/src/Http/Exception/UnavailableForLegalReasonsException.php @@ -24,7 +24,7 @@ class UnavailableForLegalReasonsException extends HttpException /** * @inheritDoc */ - protected $_defaultCode = 451; + protected int $_defaultCode = 451; /** * Constructor @@ -35,7 +35,7 @@ class UnavailableForLegalReasonsException extends HttpException */ public function __construct(?string $message = null, ?int $code = null, ?Throwable $previous = null) { - if (empty($message)) { + if (!$message) { $message = 'Unavailable For Legal Reasons'; } parent::__construct($message, $code, $previous); diff --git a/app/vendor/cakephp/cakephp/src/Http/FlashMessage.php b/app/vendor/cakephp/cakephp/src/Http/FlashMessage.php index 529f602e0..d8bd9296d 100644 --- a/app/vendor/cakephp/cakephp/src/Http/FlashMessage.php +++ b/app/vendor/cakephp/cakephp/src/Http/FlashMessage.php @@ -33,7 +33,7 @@ class FlashMessage * * @var array */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'key' => 'flash', 'element' => 'default', 'plugin' => null, @@ -45,7 +45,7 @@ class FlashMessage /** * @var \Cake\Http\Session */ - protected $session; + protected Session $session; /** * Constructor @@ -81,7 +81,7 @@ public function __construct(Session $session, array $config = []) * @return void * @see FlashMessage::$_defaultConfig For default values for the options. */ - public function set($message, array $options = []): void + public function set(string $message, array $options = []): void { $options += (array)$this->getConfig(); @@ -139,8 +139,8 @@ public function set($message, array $options = []): void */ public function setExceptionMessage(Throwable $exception, array $options = []): void { - $options['element'] = $options['element'] ?? 'error'; - $options['params']['code'] = $options['params']['code'] ?? $exception->getCode(); + $options['element'] ??= 'error'; + $options['params']['code'] ??= $exception->getCode(); $message = $exception->getMessage(); $this->set($message, $options); diff --git a/app/vendor/cakephp/cakephp/src/Http/HeaderUtility.php b/app/vendor/cakephp/cakephp/src/Http/HeaderUtility.php index 3242179c1..c7919cbe9 100644 --- a/app/vendor/cakephp/cakephp/src/Http/HeaderUtility.php +++ b/app/vendor/cakephp/cakephp/src/Http/HeaderUtility.php @@ -34,27 +34,34 @@ protected static function parseLinkItem(string $value): array { preg_match('/<(.*)>[; ]?[; ]?(.*)?/i', $value, $matches); + if ($matches === []) { + return []; + } + $url = $matches[1]; $parsedParams = ['link' => $url]; $params = $matches[2]; - if ($params) { - $explodedParams = explode(';', $params); - foreach ($explodedParams as $param) { - $explodedParam = explode('=', $param); - $trimedKey = trim($explodedParam[0]); - $trimedValue = trim($explodedParam[1], '"'); - if ($trimedKey === 'title*') { - // See https://www.rfc-editor.org/rfc/rfc8187#section-3.2.3 - preg_match('/(.*)\'(.*)\'(.*)/i', $trimedValue, $matches); - $trimedValue = [ - 'language' => $matches[2], - 'encoding' => $matches[1], - 'value' => urldecode($matches[3]), - ]; - } - $parsedParams[$trimedKey] = $trimedValue; + if (!$params) { + return $parsedParams; + } + + $explodedParams = explode(';', $params); + foreach ($explodedParams as $param) { + $explodedParam = explode('=', $param); + $trimmedKey = trim($explodedParam[0]); + $trimmedValue = trim($explodedParam[1], '"'); + if ($trimmedKey === 'title*') { + // See https://www.rfc-editor.org/rfc/rfc8187#section-3.2.3 + preg_match("/(.*)'(.*)'(.*)/i", $trimmedValue, $matches); + assert(!empty($matches[1]) && !empty($matches[2]) && !empty($matches[3])); + $trimmedValue = [ + 'language' => $matches[2], + 'encoding' => $matches[1], + 'value' => urldecode($matches[3]), + ]; } + $parsedParams[$trimmedKey] = $trimmedValue; } return $parsedParams; @@ -112,11 +119,12 @@ public static function parseWwwAuthenticate(string $value): array '@(\w+)=(?:(?:")([^"]+)"|([^\s,$]+))@', $value, $matches, - PREG_SET_ORDER + PREG_SET_ORDER, ); $return = []; foreach ($matches as $match) { + /** @phpstan-ignore-next-line */ $return[$match[1]] = $match[3] ?? $match[2]; } diff --git a/app/vendor/cakephp/cakephp/src/Http/Middleware/BodyParserMiddleware.php b/app/vendor/cakephp/cakephp/src/Http/Middleware/BodyParserMiddleware.php index 8432bc610..ff2954bed 100644 --- a/app/vendor/cakephp/cakephp/src/Http/Middleware/BodyParserMiddleware.php +++ b/app/vendor/cakephp/cakephp/src/Http/Middleware/BodyParserMiddleware.php @@ -38,14 +38,14 @@ class BodyParserMiddleware implements MiddlewareInterface * * @var array<\Closure> */ - protected $parsers = []; + protected array $parsers = []; /** * The HTTP methods to parse data on. * * @var array */ - protected $methods = ['PUT', 'POST', 'PATCH', 'DELETE']; + protected array $methods = ['PUT', 'POST', 'PATCH', 'DELETE']; /** * Constructor @@ -65,13 +65,13 @@ public function __construct(array $options = []) if ($options['json']) { $this->addParser( ['application/json', 'text/json'], - Closure::fromCallable([$this, 'decodeJson']) + $this->decodeJson(...), ); } if ($options['xml']) { $this->addParser( ['application/xml', 'text/xml'], - Closure::fromCallable([$this, 'decodeXml']) + $this->decodeXml(...), ); } if ($options['methods']) { @@ -178,17 +178,17 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface * @param string $body The request body to decode * @return array|null */ - protected function decodeJson(string $body) + protected function decodeJson(string $body): ?array { if ($body === '') { return []; } $decoded = json_decode($body, true); - if (json_last_error() === JSON_ERROR_NONE) { - return (array)$decoded; + if (json_last_error() !== JSON_ERROR_NONE) { + return null; } - return null; + return (array)$decoded; } /** @@ -202,12 +202,14 @@ protected function decodeXml(string $body): array try { $xml = Xml::build($body, ['return' => 'domdocument', 'readFile' => false]); // We might not get child nodes if there are nested inline entities. - if ((int)$xml->childNodes->length > 0) { + /** @var \DOMNodeList $domNodeList */ + $domNodeList = $xml->childNodes; + if ((int)$domNodeList->length > 0) { return Xml::toArray($xml); } return []; - } catch (XmlException $e) { + } catch (XmlException) { return []; } } diff --git a/app/vendor/cakephp/cakephp/src/Http/Middleware/ClosureDecoratorMiddleware.php b/app/vendor/cakephp/cakephp/src/Http/Middleware/ClosureDecoratorMiddleware.php index c85640716..9d024b61d 100644 --- a/app/vendor/cakephp/cakephp/src/Http/Middleware/ClosureDecoratorMiddleware.php +++ b/app/vendor/cakephp/cakephp/src/Http/Middleware/ClosureDecoratorMiddleware.php @@ -43,7 +43,7 @@ class ClosureDecoratorMiddleware implements MiddlewareInterface * * @var \Closure */ - protected $callable; + protected Closure $callable; /** * Constructor @@ -66,15 +66,15 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface { return ($this->callable)( $request, - $handler + $handler, ); } /** * @internal - * @return callable + * @return \Closure */ - public function getCallable(): callable + public function getCallable(): Closure { return $this->callable; } diff --git a/app/vendor/cakephp/cakephp/src/Http/Middleware/CspMiddleware.php b/app/vendor/cakephp/cakephp/src/Http/Middleware/CspMiddleware.php index 91d44d302..0a8748ee2 100644 --- a/app/vendor/cakephp/cakephp/src/Http/Middleware/CspMiddleware.php +++ b/app/vendor/cakephp/cakephp/src/Http/Middleware/CspMiddleware.php @@ -17,13 +17,13 @@ namespace Cake\Http\Middleware; +use Cake\Core\Exception\CakeException; use Cake\Core\InstanceConfigTrait; use ParagonIE\CSPBuilder\CSPBuilder; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; -use RuntimeException; /** * Content Security Policy Middleware @@ -42,14 +42,14 @@ class CspMiddleware implements MiddlewareInterface * * @var \ParagonIE\CSPBuilder\CSPBuilder $csp CSP Builder or config array */ - protected $csp; + protected CSPBuilder $csp; /** * Configuration options. * * @var array */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'scriptNonce' => false, 'styleNonce' => false, ]; @@ -59,12 +59,11 @@ class CspMiddleware implements MiddlewareInterface * * @param \ParagonIE\CSPBuilder\CSPBuilder|array $csp CSP object or config array * @param array $config Configuration options. - * @throws \RuntimeException */ - public function __construct($csp, array $config = []) + public function __construct(CSPBuilder|array $csp, array $config = []) { if (!class_exists(CSPBuilder::class)) { - throw new RuntimeException('You must install paragonie/csp-builder to use CspMiddleware'); + throw new CakeException('You must install paragonie/csp-builder to use CspMiddleware'); } $this->setConfig($config); diff --git a/app/vendor/cakephp/cakephp/src/Http/Middleware/CsrfProtectionMiddleware.php b/app/vendor/cakephp/cakephp/src/Http/Middleware/CsrfProtectionMiddleware.php index a10d7b53e..db00008da 100644 --- a/app/vendor/cakephp/cakephp/src/Http/Middleware/CsrfProtectionMiddleware.php +++ b/app/vendor/cakephp/cakephp/src/Http/Middleware/CsrfProtectionMiddleware.php @@ -17,6 +17,7 @@ namespace Cake\Http\Middleware; use ArrayAccess; +use Cake\Core\Exception\CakeException; use Cake\Http\Cookie\Cookie; use Cake\Http\Cookie\CookieInterface; use Cake\Http\Exception\InvalidCsrfTokenException; @@ -28,8 +29,6 @@ use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; -use RuntimeException; -use function Cake\Core\deprecationWarning; use function Cake\I18n\__d; /** @@ -66,7 +65,7 @@ class CsrfProtectionMiddleware implements MiddlewareInterface * * @var array */ - protected $_config = [ + protected array $_config = [ 'cookieName' => 'csrfToken', 'expiry' => 0, 'secure' => false, @@ -109,11 +108,6 @@ class CsrfProtectionMiddleware implements MiddlewareInterface */ public function __construct(array $config = []) { - if (array_key_exists('httpOnly', $config)) { - $config['httponly'] = $config['httpOnly']; - deprecationWarning('Option `httpOnly` is deprecated. Use lowercased `httponly` instead.'); - } - $this->_config = $config + $this->_config; } @@ -140,11 +134,11 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface return $handler->handle($request); } if ($request->getAttribute('csrfToken')) { - throw new RuntimeException( + throw new CakeException( 'A CSRF token is already set in the request.' . "\n" . 'Ensure you do not have the CSRF middleware applied more than once. ' . - 'Check both your `Application::middleware()` method and `config/routes.php`.' + 'Check both your `Application::middleware()` method and `config/routes.php`.', ); } @@ -154,7 +148,7 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface if (is_string($cookieData) && $cookieData !== '') { try { $request = $request->withAttribute('csrfToken', $this->saltToken($cookieData)); - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { $cookieData = null; } } @@ -162,7 +156,6 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface if ($method === 'GET' && $cookieData === null) { $token = $this->createToken(); $request = $request->withAttribute('csrfToken', $this->saltToken($token)); - /** @var mixed $response */ $response = $handler->handle($request); return $this->_addTokenCookie($token, $request, $response); @@ -176,24 +169,6 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface return $handler->handle($request); } - /** - * Set callback for allowing to skip token check for particular request. - * - * The callback will receive request instance as argument and must return - * `true` if you want to skip token check for the current request. - * - * @deprecated 4.1.0 Use skipCheckCallback instead. - * @param callable $callback A callable. - * @return $this - */ - public function whitelistCallback(callable $callback) - { - deprecationWarning('`whitelistCallback()` is deprecated. Use `skipCheckCallback()` instead.'); - $this->skipCheckCallback = $callback; - - return $this; - } - /** * Set callback for allowing to skip token check for particular request. * @@ -227,19 +202,6 @@ protected function _unsetTokenField(ServerRequestInterface $request): ServerRequ return $request; } - /** - * Create a new token to be used for CSRF protection - * - * @return string - * @deprecated 4.0.6 Use {@link createToken()} instead. - */ - protected function _createToken(): string - { - deprecationWarning('_createToken() is deprecated. Use createToken() instead.'); - - return $this->createToken(); - } - /** * Test if the token predates salted tokens. * @@ -367,7 +329,7 @@ protected function _verifyToken(string $token): bool protected function _addTokenCookie( string $token, ServerRequestInterface $request, - ResponseInterface $response + ResponseInterface $response, ): ResponseInterface { $cookie = $this->_createCookie($token, $request); if ($response instanceof Response) { @@ -418,7 +380,7 @@ protected function _validateToken(ServerRequestInterface $request): void throw new InvalidCsrfTokenException(__d( 'cake', - 'CSRF token from either the request body or request headers did not match or is missing.' + 'CSRF token from either the request body or request headers did not match or is missing.', )); } @@ -440,7 +402,7 @@ protected function _createCookie(string $value, ServerRequestInterface $request) 'secure' => $this->_config['secure'], 'httponly' => $this->_config['httponly'], 'samesite' => $this->_config['samesite'], - ] + ], ); } } diff --git a/app/vendor/cakephp/cakephp/src/Http/Middleware/DoublePassDecoratorMiddleware.php b/app/vendor/cakephp/cakephp/src/Http/Middleware/DoublePassDecoratorMiddleware.php deleted file mode 100644 index 96eec01cb..000000000 --- a/app/vendor/cakephp/cakephp/src/Http/Middleware/DoublePassDecoratorMiddleware.php +++ /dev/null @@ -1,96 +0,0 @@ -callable = $callable; - } - - /** - * Run the internal double pass callable to process an incoming server request. - * - * @param \Psr\Http\Message\ServerRequestInterface $request Request instance. - * @param \Psr\Http\Server\RequestHandlerInterface $handler Request handler instance. - * @return \Psr\Http\Message\ResponseInterface - */ - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface - { - return ($this->callable)( - $request, - new Response(), - function ($request, $res) use ($handler) { - return $handler->handle($request); - } - ); - } - - /** - * @internal - * @return callable - */ - public function getCallable(): callable - { - return $this->callable; - } -} diff --git a/app/vendor/cakephp/cakephp/src/Http/Middleware/EncryptedCookieMiddleware.php b/app/vendor/cakephp/cakephp/src/Http/Middleware/EncryptedCookieMiddleware.php index c40e349ea..7cb24291a 100644 --- a/app/vendor/cakephp/cakephp/src/Http/Middleware/EncryptedCookieMiddleware.php +++ b/app/vendor/cakephp/cakephp/src/Http/Middleware/EncryptedCookieMiddleware.php @@ -47,21 +47,21 @@ class EncryptedCookieMiddleware implements MiddlewareInterface * * @var array */ - protected $cookieNames; + protected array $cookieNames; /** * Encryption key to use. * * @var string */ - protected $key; + protected string $key; /** * Encryption type. * * @var string */ - protected $cipherType; + protected string $cipherType; /** * Constructor @@ -95,7 +95,7 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface $response = $this->encodeSetCookieHeader($response); } if ($response instanceof Response) { - $response = $this->encodeCookies($response); + return $this->encodeCookies($response); } return $response; @@ -139,9 +139,7 @@ protected function decodeCookies(ServerRequestInterface $request): ServerRequest */ protected function encodeCookies(Response $response): Response { - /** @var array<\Cake\Http\Cookie\CookieInterface> $cookies */ - $cookies = $response->getCookieCollection(); - foreach ($cookies as $cookie) { + foreach ($response->getCookieCollection() as $cookie) { if (in_array($cookie->getName(), $this->cookieNames, true)) { $value = $this->_encrypt($cookie->getValue(), $this->cipherType); $response = $response->withCookie($cookie->withValue($value)); @@ -159,7 +157,6 @@ protected function encodeCookies(Response $response): Response */ protected function encodeSetCookieHeader(ResponseInterface $response): ResponseInterface { - /** @var array<\Cake\Http\Cookie\CookieInterface> $cookies */ $cookies = CookieCollection::createFromHeader($response->getHeader('Set-Cookie')); $header = []; foreach ($cookies as $cookie) { diff --git a/app/vendor/cakephp/cakephp/src/Http/Middleware/HttpsEnforcerMiddleware.php b/app/vendor/cakephp/cakephp/src/Http/Middleware/HttpsEnforcerMiddleware.php index 81458e858..93c78582b 100644 --- a/app/vendor/cakephp/cakephp/src/Http/Middleware/HttpsEnforcerMiddleware.php +++ b/app/vendor/cakephp/cakephp/src/Http/Middleware/HttpsEnforcerMiddleware.php @@ -46,11 +46,11 @@ class HttpsEnforcerMiddleware implements MiddlewareInterface * * - 'maxAge' - `max-age` directive value in seconds. * - 'includeSubDomains' - Whether to include `includeSubDomains` directive. Defaults to `false`. - * - 'preload' - Whether to include 'preload' directive. Defauls to `false`. + * - 'preload' - Whether to include 'preload' directive. Defaults to `false`. * * @var array */ - protected $config = [ + protected array $config = [ 'redirect' => true, 'statusCode' => 301, 'headers' => [], @@ -94,7 +94,7 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface ) { $response = $handler->handle($request); if ($this->config['hsts']) { - $response = $this->addHsts($response); + return $this->addHsts($response); } return $response; @@ -110,12 +110,12 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface return new RedirectResponse( $uri, $this->config['statusCode'], - $this->config['headers'] + $this->config['headers'], ); } throw new BadRequestException( - 'Requests to this URL must be made with HTTPS.' + 'Requests to this URL must be made with HTTPS.', ); } diff --git a/app/vendor/cakephp/cakephp/src/Http/Middleware/SecurityHeadersMiddleware.php b/app/vendor/cakephp/cakephp/src/Http/Middleware/SecurityHeadersMiddleware.php index eeb05e14b..d2a2e7591 100644 --- a/app/vendor/cakephp/cakephp/src/Http/Middleware/SecurityHeadersMiddleware.php +++ b/app/vendor/cakephp/cakephp/src/Http/Middleware/SecurityHeadersMiddleware.php @@ -25,7 +25,7 @@ /** * Handles common security headers in a convenient way * - * @link https://book.cakephp.org/4/en/controllers/middleware.html#security-header-middleware + * @link https://book.cakephp.org/5/en/controllers/middleware.html#security-header-middleware */ class SecurityHeadersMiddleware implements MiddlewareInterface { @@ -100,7 +100,7 @@ class SecurityHeadersMiddleware implements MiddlewareInterface * * @var array */ - protected $headers = []; + protected array $headers = []; /** * X-Content-Type-Options @@ -172,7 +172,7 @@ public function setXFrameOptions(string $option = self::SAMEORIGIN, ?string $url $this->checkValues($option, [self::DENY, self::SAMEORIGIN, self::ALLOW_FROM]); if ($option === self::ALLOW_FROM) { - if (empty($url)) { + if (!$url) { throw new InvalidArgumentException('The 2nd arg $url can not be empty when `allow-from` is used'); } $option .= ' ' . $url; @@ -184,9 +184,11 @@ public function setXFrameOptions(string $option = self::SAMEORIGIN, ?string $url } /** - * X-XSS-Protection + * X-XSS-Protection. It's a non standard feature and outdated. For modern browsers + * use a strong Content-Security-Policy that disables the use of inline JavaScript + * via 'unsafe-inline' option. * - * @link https://blogs.msdn.microsoft.com/ieinternals/2011/01/31/controlling-the-xss-filter + * @link https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection * @param string $mode Mode value. Available Values: '1', '0', 'block' * @return $this */ @@ -205,7 +207,7 @@ public function setXssProtection(string $mode = self::XSS_BLOCK) /** * X-Permitted-Cross-Domain-Policies * - * @link https://www.adobe.com/devnet/adobe-media-server/articles/cross-domain-xml-for-streaming.html + * @link https://web.archive.org/web/20170607190356/https://www.adobe.com/devnet/adobe-media-server/articles/cross-domain-xml-for-streaming.html * @param string $policy Policy value. Available Values: 'all', 'none', 'master-only', 'by-content-type', * 'by-ftp-filename' * @return $this @@ -224,6 +226,22 @@ public function setCrossDomainPolicy(string $policy = self::ALL) return $this; } + /** + * Permissions Policy + * + * @link https://developer.mozilla.org/en-US/docs/Web/HTTP/Permissions_Policy + * @link https://www.w3.org/TR/permissions-policy/ + * @param string $policy Policy value. + * @return $this + * @since 5.1.0 + */ + public function setPermissionsPolicy(string $policy) + { + $this->headers['permissions-policy'] = $policy; + + return $this; + } + /** * Convenience method to check if a value is in the list of allowed args * @@ -235,10 +253,11 @@ public function setCrossDomainPolicy(string $policy = self::ALL) protected function checkValues(string $value, array $allowed): void { if (!in_array($value, $allowed, true)) { + array_walk($allowed, fn(&$x) => $x = "`{$x}`"); throw new InvalidArgumentException(sprintf( - 'Invalid arg `%s`, use one of these: %s', + 'Invalid arg `%s`, use one of these: %s.', $value, - implode(', ', $allowed) + implode(', ', $allowed), )); } } diff --git a/app/vendor/cakephp/cakephp/src/Http/Middleware/SessionCsrfProtectionMiddleware.php b/app/vendor/cakephp/cakephp/src/Http/Middleware/SessionCsrfProtectionMiddleware.php index 82b2279e7..49eeac9c2 100644 --- a/app/vendor/cakephp/cakephp/src/Http/Middleware/SessionCsrfProtectionMiddleware.php +++ b/app/vendor/cakephp/cakephp/src/Http/Middleware/SessionCsrfProtectionMiddleware.php @@ -17,6 +17,7 @@ namespace Cake\Http\Middleware; use ArrayAccess; +use Cake\Core\Exception\CakeException; use Cake\Http\Exception\InvalidCsrfTokenException; use Cake\Http\ServerRequest; use Cake\Http\Session; @@ -26,7 +27,6 @@ use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; -use RuntimeException; use function Cake\I18n\__d; /** @@ -58,7 +58,7 @@ class SessionCsrfProtectionMiddleware implements MiddlewareInterface * * @var array */ - protected $_config = [ + protected array $_config = [ 'key' => 'csrfToken', 'field' => '_csrfToken', ]; @@ -111,8 +111,8 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface } $session = $request->getAttribute('session'); - if (!$session || !($session instanceof Session)) { - throw new RuntimeException('You must have a `session` attribute to use session based CSRF tokens'); + if (!($session instanceof Session)) { + throw new CakeException('You must have a `session` attribute to use session based CSRF tokens'); } $token = $session->read($this->_config['key']); @@ -266,7 +266,7 @@ protected function validateToken(ServerRequestInterface $request, Session $sessi throw new InvalidCsrfTokenException(__d( 'cake', - 'CSRF token from either the request body or request headers did not match or is missing.' + 'CSRF token from either the request body or request headers did not match or is missing.', )); } diff --git a/app/vendor/cakephp/cakephp/src/Http/MiddlewareApplication.php b/app/vendor/cakephp/cakephp/src/Http/MiddlewareApplication.php index 89cf261a5..5605125f7 100644 --- a/app/vendor/cakephp/cakephp/src/Http/MiddlewareApplication.php +++ b/app/vendor/cakephp/cakephp/src/Http/MiddlewareApplication.php @@ -49,7 +49,7 @@ abstract public function middleware(MiddlewareQueue $middlewareQueue): Middlewar * @return \Psr\Http\Message\ResponseInterface */ public function handle( - ServerRequestInterface $request + ServerRequestInterface $request, ): ResponseInterface { return new Response(['body' => 'Not found', 'status' => 404]); } diff --git a/app/vendor/cakephp/cakephp/src/Http/MiddlewareQueue.php b/app/vendor/cakephp/cakephp/src/Http/MiddlewareQueue.php index 6079d8cda..e565fed18 100644 --- a/app/vendor/cakephp/cakephp/src/Http/MiddlewareQueue.php +++ b/app/vendor/cakephp/cakephp/src/Http/MiddlewareQueue.php @@ -19,14 +19,12 @@ use Cake\Core\App; use Cake\Core\ContainerInterface; use Cake\Http\Middleware\ClosureDecoratorMiddleware; -use Cake\Http\Middleware\DoublePassDecoratorMiddleware; use Closure; use Countable; +use InvalidArgumentException; use LogicException; use OutOfBoundsException; use Psr\Http\Server\MiddlewareInterface; -use ReflectionFunction; -use RuntimeException; use SeekableIterator; /** @@ -42,25 +40,25 @@ class MiddlewareQueue implements Countable, SeekableIterator * * @var int */ - protected $position = 0; + protected int $position = 0; /** * The queue of middlewares. * * @var array */ - protected $queue = []; + protected array $queue = []; /** * @var \Cake\Core\ContainerInterface|null */ - protected $container; + protected ?ContainerInterface $container; /** * Constructor * * @param array $middleware The list of middleware to append. - * @param \Cake\Core\ContainerInterface $container Container instance. + * @param \Cake\Core\ContainerInterface|null $container Container instance. */ public function __construct(array $middleware = [], ?ContainerInterface $container = null) { @@ -73,22 +71,21 @@ public function __construct(array $middleware = [], ?ContainerInterface $contain * * @param \Psr\Http\Server\MiddlewareInterface|\Closure|string $middleware The middleware to resolve. * @return \Psr\Http\Server\MiddlewareInterface - * @throws \RuntimeException If Middleware not found. + * @throws \InvalidArgumentException If Middleware not found. */ - protected function resolve($middleware): MiddlewareInterface + protected function resolve(MiddlewareInterface|Closure|string $middleware): MiddlewareInterface { if (is_string($middleware)) { if ($this->container && $this->container->has($middleware)) { $middleware = $this->container->get($middleware); } else { + /** @var class-string<\Psr\Http\Server\MiddlewareInterface>|null $className */ $className = App::className($middleware, 'Middleware', 'Middleware'); if ($className === null) { - throw new RuntimeException( - sprintf( - 'Middleware "%s" was not found.', - $middleware - ) - ); + throw new InvalidArgumentException(sprintf( + 'Middleware `%s` was not found.', + $middleware, + )); } $middleware = new $className(); } @@ -98,15 +95,6 @@ protected function resolve($middleware): MiddlewareInterface return $middleware; } - if (!$middleware instanceof Closure) { - return new DoublePassDecoratorMiddleware($middleware); - } - - $info = new ReflectionFunction($middleware); - if ($info->getNumberOfParameters() > 2) { - return new DoublePassDecoratorMiddleware($middleware); - } - return new ClosureDecoratorMiddleware($middleware); } @@ -116,7 +104,7 @@ protected function resolve($middleware): MiddlewareInterface * @param \Psr\Http\Server\MiddlewareInterface|\Closure|array|string $middleware The middleware(s) to append. * @return $this */ - public function add($middleware) + public function add(MiddlewareInterface|Closure|array|string $middleware) { if (is_array($middleware)) { $this->queue = array_merge($this->queue, $middleware); @@ -135,7 +123,7 @@ public function add($middleware) * @return $this * @see MiddlewareQueue::add() */ - public function push($middleware) + public function push(MiddlewareInterface|Closure|array|string $middleware) { return $this->add($middleware); } @@ -146,7 +134,7 @@ public function push($middleware) * @param \Psr\Http\Server\MiddlewareInterface|\Closure|array|string $middleware The middleware(s) to prepend. * @return $this */ - public function prepend($middleware) + public function prepend(MiddlewareInterface|Closure|array|string $middleware) { if (is_array($middleware)) { $this->queue = array_merge($middleware, $this->queue); @@ -168,7 +156,7 @@ public function prepend($middleware) * @param \Psr\Http\Server\MiddlewareInterface|\Closure|string $middleware The middleware to insert. * @return $this */ - public function insertAt(int $index, $middleware) + public function insertAt(int $index, MiddlewareInterface|Closure|string $middleware) { array_splice($this->queue, $index, 0, [$middleware]); @@ -186,12 +174,11 @@ public function insertAt(int $index, $middleware) * @return $this * @throws \LogicException If middleware to insert before is not found. */ - public function insertBefore(string $class, $middleware) + public function insertBefore(string $class, MiddlewareInterface|Closure|string $middleware) { $found = false; $i = 0; foreach ($this->queue as $i => $object) { - /** @psalm-suppress ArgumentTypeCoercion */ if ( ( is_string($object) @@ -206,7 +193,7 @@ public function insertBefore(string $class, $middleware) if ($found) { return $this->insertAt($i, $middleware); } - throw new LogicException(sprintf("No middleware matching '%s' could be found.", $class)); + throw new LogicException(sprintf('No middleware matching `%s` could be found.', $class)); } /** @@ -220,12 +207,11 @@ public function insertBefore(string $class, $middleware) * @param \Psr\Http\Server\MiddlewareInterface|\Closure|string $middleware The middleware to insert. * @return $this */ - public function insertAfter(string $class, $middleware) + public function insertAfter(string $class, MiddlewareInterface|Closure|string $middleware) { $found = false; $i = 0; foreach ($this->queue as $i => $object) { - /** @psalm-suppress ArgumentTypeCoercion */ if ( ( is_string($object) @@ -263,10 +249,10 @@ public function count(): int * @return void * @see \SeekableIterator::seek() */ - public function seek($position): void + public function seek(int $position): void { if (!isset($this->queue[$position])) { - throw new OutOfBoundsException("Invalid seek position ($position)"); + throw new OutOfBoundsException(sprintf('Invalid seek position (%s).', $position)); } $this->position = $position; @@ -292,7 +278,7 @@ public function rewind(): void public function current(): MiddlewareInterface { if (!isset($this->queue[$this->position])) { - throw new OutOfBoundsException("Invalid current position ($this->position)"); + throw new OutOfBoundsException(sprintf('Invalid current position (%s).', $this->position)); } if ($this->queue[$this->position] instanceof MiddlewareInterface) { diff --git a/app/vendor/cakephp/cakephp/src/Http/MimeType.php b/app/vendor/cakephp/cakephp/src/Http/MimeType.php new file mode 100644 index 000000000..6c0592221 --- /dev/null +++ b/app/vendor/cakephp/cakephp/src/Http/MimeType.php @@ -0,0 +1,385 @@ +> + */ + protected static array $mimeTypes = [ + 'html' => ['text/html', '*/*'], + 'json' => ['application/json'], + 'xml' => ['application/xml', 'text/xml'], + 'xhtml' => ['application/xhtml+xml', 'application/xhtml', 'text/xhtml'], + 'webp' => ['image/webp'], + 'rss' => ['application/rss+xml'], + 'ai' => ['application/postscript'], + 'bcpio' => ['application/x-bcpio'], + 'bin' => ['application/octet-stream'], + 'ccad' => ['application/clariscad'], + 'cdf' => ['application/x-netcdf'], + 'class' => ['application/octet-stream'], + 'cpio' => ['application/x-cpio'], + 'cpt' => ['application/mac-compactpro'], + 'csh' => ['application/x-csh'], + 'csv' => ['text/csv', 'application/vnd.ms-excel'], + 'dcr' => ['application/x-director'], + 'dir' => ['application/x-director'], + 'dms' => ['application/octet-stream'], + 'doc' => ['application/msword'], + 'docx' => ['application/vnd.openxmlformats-officedocument.wordprocessingml.document'], + 'drw' => ['application/drafting'], + 'dvi' => ['application/x-dvi'], + 'dwg' => ['application/acad'], + 'dxf' => ['application/dxf'], + 'dxr' => ['application/x-director'], + 'eot' => ['application/vnd.ms-fontobject'], + 'eps' => ['application/postscript'], + 'exe' => ['application/octet-stream'], + 'ez' => ['application/andrew-inset'], + 'flv' => ['video/x-flv'], + 'gtar' => ['application/x-gtar'], + 'gz' => ['application/x-gzip'], + 'bz2' => ['application/x-bzip'], + '7z' => ['application/x-7z-compressed'], + 'hal' => ['application/hal+xml', 'application/vnd.hal+xml'], + 'haljson' => ['application/hal+json', 'application/vnd.hal+json'], + 'halxml' => ['application/hal+xml', 'application/vnd.hal+xml'], + 'hdf' => ['application/x-hdf'], + 'hqx' => ['application/mac-binhex40'], + 'ico' => ['image/x-icon'], + 'ips' => ['application/x-ipscript'], + 'ipx' => ['application/x-ipix'], + 'js' => ['application/javascript'], + 'cjs' => ['application/javascript'], + 'mjs' => ['application/javascript'], + 'jsonapi' => ['application/vnd.api+json'], + 'latex' => ['application/x-latex'], + 'jsonld' => ['application/ld+json'], + 'kml' => ['application/vnd.google-earth.kml+xml'], + 'kmz' => ['application/vnd.google-earth.kmz'], + 'lha' => ['application/octet-stream'], + 'lsp' => ['application/x-lisp'], + 'lzh' => ['application/octet-stream'], + 'man' => ['application/x-troff-man'], + 'me' => ['application/x-troff-me'], + 'mif' => ['application/vnd.mif'], + 'ms' => ['application/x-troff-ms'], + 'nc' => ['application/x-netcdf'], + 'oda' => ['application/oda'], + 'otf' => ['font/otf'], + 'pdf' => ['application/pdf'], + 'pgn' => ['application/x-chess-pgn'], + 'pot' => ['application/vnd.ms-powerpoint'], + 'pps' => ['application/vnd.ms-powerpoint'], + 'ppt' => ['application/vnd.ms-powerpoint'], + 'pptx' => ['application/vnd.openxmlformats-officedocument.presentationml.presentation'], + 'ppz' => ['application/vnd.ms-powerpoint'], + 'pre' => ['application/x-freelance'], + 'prt' => ['application/pro_eng'], + 'ps' => ['application/postscript'], + 'roff' => ['application/x-troff'], + 'scm' => ['application/x-lotusscreencam'], + 'set' => ['application/set'], + 'sh' => ['application/x-sh'], + 'shar' => ['application/x-shar'], + 'sit' => ['application/x-stuffit'], + 'skd' => ['application/x-koan'], + 'skm' => ['application/x-koan'], + 'skp' => ['application/x-koan'], + 'skt' => ['application/x-koan'], + 'smi' => ['application/smil'], + 'smil' => ['application/smil'], + 'sol' => ['application/solids'], + 'spl' => ['application/x-futuresplash'], + 'src' => ['application/x-wais-source'], + 'step' => ['application/STEP'], + 'stl' => ['application/SLA'], + 'stp' => ['application/STEP'], + 'sv4cpio' => ['application/x-sv4cpio'], + 'sv4crc' => ['application/x-sv4crc'], + 'svg' => ['image/svg+xml'], + 'svgz' => ['image/svg+xml'], + 'swf' => ['application/x-shockwave-flash'], + 't' => ['application/x-troff'], + 'tar' => ['application/x-tar'], + 'tcl' => ['application/x-tcl'], + 'tex' => ['application/x-tex'], + 'texi' => ['application/x-texinfo'], + 'texinfo' => ['application/x-texinfo'], + 'tr' => ['application/x-troff'], + 'tsp' => ['application/dsptype'], + 'ttc' => ['font/ttf'], + 'ttf' => ['font/ttf'], + 'unv' => ['application/i-deas'], + 'ustar' => ['application/x-ustar'], + 'vcd' => ['application/x-cdlink'], + 'vda' => ['application/vda'], + 'xlc' => ['application/vnd.ms-excel'], + 'xll' => ['application/vnd.ms-excel'], + 'xlm' => ['application/vnd.ms-excel'], + 'xls' => ['application/vnd.ms-excel'], + 'xlsx' => ['application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'], + 'xlw' => ['application/vnd.ms-excel'], + 'zip' => ['application/zip'], + 'aif' => ['audio/x-aiff'], + 'aifc' => ['audio/x-aiff'], + 'aiff' => ['audio/x-aiff'], + 'au' => ['audio/basic'], + 'kar' => ['audio/midi'], + 'mid' => ['audio/midi'], + 'midi' => ['audio/midi'], + 'mp2' => ['audio/mpeg'], + 'mp3' => ['audio/mpeg'], + 'mpga' => ['audio/mpeg'], + 'ogg' => ['audio/ogg'], + 'oga' => ['audio/ogg'], + 'spx' => ['audio/ogg'], + 'ra' => ['audio/x-realaudio'], + 'ram' => ['audio/x-pn-realaudio'], + 'rm' => ['audio/x-pn-realaudio'], + 'rpm' => ['audio/x-pn-realaudio-plugin'], + 'snd' => ['audio/basic'], + 'tsi' => ['audio/TSP-audio'], + 'wav' => ['audio/x-wav'], + 'aac' => ['audio/aac'], + 'asc' => ['text/plain'], + 'c' => ['text/plain'], + 'cc' => ['text/plain'], + 'css' => ['text/css'], + 'etx' => ['text/x-setext'], + 'f' => ['text/plain'], + 'f90' => ['text/plain'], + 'h' => ['text/plain'], + 'hh' => ['text/plain'], + 'htm' => ['text/html', '*/*'], + 'ics' => ['text/calendar'], + 'm' => ['text/plain'], + 'rtf' => ['text/rtf'], + 'rtx' => ['text/richtext'], + 'sgm' => ['text/sgml'], + 'sgml' => ['text/sgml'], + 'tsv' => ['text/tab-separated-values'], + 'tpl' => ['text/template'], + 'txt' => ['text/plain'], + 'text' => ['text/plain'], + 'avi' => ['video/x-msvideo'], + 'fli' => ['video/x-fli'], + 'mov' => ['video/quicktime'], + 'movie' => ['video/x-sgi-movie'], + 'mpe' => ['video/mpeg'], + 'mpeg' => ['video/mpeg'], + 'mpg' => ['video/mpeg'], + 'qt' => ['video/quicktime'], + 'viv' => ['video/vnd.vivo'], + 'vivo' => ['video/vnd.vivo'], + 'ogv' => ['video/ogg'], + 'webm' => ['video/webm'], + 'mp4' => ['video/mp4'], + 'm4v' => ['video/mp4'], + 'f4v' => ['video/mp4'], + 'f4p' => ['video/mp4'], + 'm4a' => ['audio/mp4'], + 'f4a' => ['audio/mp4'], + 'f4b' => ['audio/mp4'], + 'gif' => ['image/gif'], + 'ief' => ['image/ief'], + 'jpg' => ['image/jpeg'], + 'jpeg' => ['image/jpeg'], + 'jpe' => ['image/jpeg'], + 'pbm' => ['image/x-portable-bitmap'], + 'pgm' => ['image/x-portable-graymap'], + 'png' => ['image/png'], + 'pnm' => ['image/x-portable-anymap'], + 'ppm' => ['image/x-portable-pixmap'], + 'ras' => ['image/cmu-raster'], + 'rgb' => ['image/x-rgb'], + 'tif' => ['image/tiff'], + 'tiff' => ['image/tiff'], + 'xbm' => ['image/x-xbitmap'], + 'xpm' => ['image/x-xpixmap'], + 'xwd' => ['image/x-xwindowdump'], + 'psd' => [ + 'application/photoshop', + 'application/psd', + 'image/psd', + 'image/x-photoshop', + 'image/photoshop', + 'zz-application/zz-winassoc-psd', + ], + 'ice' => ['x-conference/x-cooltalk'], + 'iges' => ['model/iges'], + 'igs' => ['model/iges'], + 'mesh' => ['model/mesh'], + 'msh' => ['model/mesh'], + 'silo' => ['model/mesh'], + 'vrml' => ['model/vrml'], + 'wrl' => ['model/vrml'], + 'mime' => ['www/mime'], + 'pdb' => ['chemical/x-pdb'], + 'xyz' => ['chemical/x-pdb'], + 'javascript' => ['application/javascript'], + 'form' => ['application/x-www-form-urlencoded'], + 'file' => ['multipart/form-data'], + 'xhtml-mobile' => ['application/vnd.wap.xhtml+xml'], + 'atom' => ['application/atom+xml'], + 'amf' => ['application/x-amf'], + 'wap' => ['text/vnd.wap.wml', 'text/vnd.wap.wmlscript', 'image/vnd.wap.wbmp'], + 'wml' => ['text/vnd.wap.wml'], + 'wmlscript' => ['text/vnd.wap.wmlscript'], + 'wbmp' => ['image/vnd.wap.wbmp'], + 'woff' => ['application/x-font-woff'], + 'appcache' => ['text/cache-manifest'], + 'manifest' => ['text/cache-manifest'], + 'htc' => ['text/x-component'], + 'rdf' => ['application/xml'], + 'crx' => ['application/x-chrome-extension'], + 'oex' => ['application/x-opera-extension'], + 'xpi' => ['application/x-xpinstall'], + 'safariextz' => ['application/octet-stream'], + 'webapp' => ['application/x-web-app-manifest+json'], + 'vcf' => ['text/x-vcard'], + 'vtt' => ['text/vtt'], + 'mkv' => ['video/x-matroska'], + 'pkpass' => ['application/vnd.apple.pkpass'], + 'ajax' => ['text/html'], + 'bmp' => ['image/bmp'], + ]; + + /** + * Get the MIME types associated with a given file extension. + * + * @param string $ext The file extension to look up. + * @return array|null An array of MIME types if found, or null if no MIME types are associated with the extension. + */ + public static function getMimeTypes(string $ext): ?array + { + return static::$mimeTypes[$ext] ?? null; + } + + /** + * Get the MIME type based on the file extension. + * + * @param string $ext The file extension. + * @param string|null $default The default MIME type to return if the extension is not found. Defaults to null. + * @return string|null The MIME type corresponding to the file extension, or the default MIME type if not found. + */ + public static function getMimeType(string $ext, ?string $default = null): ?string + { + return isset(static::$mimeTypes[$ext]) ? static::$mimeTypes[$ext][0] : null; + } + + /** + * Add new mime types for a given file extension. + * + * If the file extension already exists, the new mime types will be merged with the existing ones. + * + * @param string $ext The file extension to associate with the mime types. + * @param array|string $mimeTypes The mime types to associate with the file extension. + * @return void + */ + public static function addMimeTypes(string $ext, array|string $mimeTypes): void + { + if (isset(static::$mimeTypes[$ext])) { + static::$mimeTypes[$ext] = array_merge(static::$mimeTypes[$ext], (array)$mimeTypes); + + return; + } + + static::$mimeTypes[$ext] = (array)$mimeTypes; + } + + /** + * Set MIME types for a given file extension. + * + * This will overwrite any existing MIME types for the file extension. + * + * @param string $ext The file extension. + * @param array|string $mimeTypes The MIME types to associate with the file extension. + * @return void + */ + public static function setMimeTypes(string $ext, array|string $mimeTypes): void + { + static::$mimeTypes[$ext] = (array)$mimeTypes; + } + + /** + * Get the file extension associated with a given MIME type. + * + * @param string $mimeType The MIME type for which to get the file extension. + * @return string|null The file extension associated with the MIME type, or null if no association is found. + */ + public static function getExtension(string $mimeType): ?string + { + foreach (static::$mimeTypes as $ext => $types) { + if (in_array($mimeType, $types, true)) { + return $ext; + } + } + + return null; + } + + /** + * Get the MIME type for a given file path. + * + * If the MIME type is not mapped to an extension then it will attempt to determine the MIME type of the file using + * the fileinfo extension. + * + * @param string $path The file path for which to get the MIME type. + * @param string $default The default MIME type to return if the MIME type cannot be determined. + * @return string The MIME type of the file, or the default MIME type if it cannot be determined. + */ + public static function getMimeTypeForFile(string $path, string $default = 'application/octet-stream'): string + { + $ext = strtolower(pathinfo($path, PATHINFO_EXTENSION)); + if (isset(static::$mimeTypes[$ext])) { + return static::$mimeTypes[$ext][0]; + } + + $finfo = new finfo(FILEINFO_MIME); + $mimeType = $finfo->file($path); + + return $mimeType === false ? $default : $mimeType; + } +} diff --git a/app/vendor/cakephp/cakephp/src/Http/README.md b/app/vendor/cakephp/cakephp/src/Http/README.md index f305fdd5c..8d6bc777d 100644 --- a/app/vendor/cakephp/cakephp/src/Http/README.md +++ b/app/vendor/cakephp/cakephp/src/Http/README.md @@ -4,8 +4,8 @@ # CakePHP Http Library This library provides a PSR-15 Http middleware server, PSR-7 Request and -Response objects, and a PSR-18 Http Client. Together these classes let you -handle incoming server requests and send outgoing HTTP requests. +Response objects, PSR-17 HTTP Factories, and PSR-18 Http Client. Together these +classes let you handle incoming server requests and send outgoing HTTP requests. ## Using the Http Client @@ -28,7 +28,7 @@ $response = $http->get('http://example.com/search', ['q' => 'widget'], [ ]); ``` -To learn more read the [Http Client documentation](https://book.cakephp.org/4/en/core-libraries/httpclient.html). +To learn more read the [Http Client documentation](https://book.cakephp.org/5/en/core-libraries/httpclient.html). ## Using the Http Server @@ -109,4 +109,4 @@ php -S localhost:8765 -t ./webroot ./webroot/index.php ``` For more information on middleware, [consult the -documentation](https://book.cakephp.org/4/en/controllers/middleware.html) +documentation](https://book.cakephp.org/5/en/controllers/middleware.html) diff --git a/app/vendor/cakephp/cakephp/src/Http/RequestFactory.php b/app/vendor/cakephp/cakephp/src/Http/RequestFactory.php new file mode 100644 index 000000000..6ded4d7e1 --- /dev/null +++ b/app/vendor/cakephp/cakephp/src/Http/RequestFactory.php @@ -0,0 +1,40 @@ + */ - protected $_statusCodes = [ + protected array $_statusCodes = [ 100 => 'Continue', 101 => 'Switching Protocols', 102 => 'Processing', @@ -102,7 +102,7 @@ class Response implements ResponseInterface 415 => 'Unsupported Media Type', 416 => 'Requested range not satisfiable', 417 => 'Expectation Failed', - 418 => 'I\'m a teapot', + 418 => "I'm a teapot", 421 => 'Misdirected Request', 422 => 'Unprocessable Entity', 423 => 'Locked', @@ -129,276 +129,33 @@ class Response implements ResponseInterface 599 => 'Network Connect Timeout Error', ]; - /** - * Holds type key to mime type mappings for known mime types. - * - * @var array - */ - protected $_mimeTypes = [ - 'html' => ['text/html', '*/*'], - 'json' => 'application/json', - 'xml' => ['application/xml', 'text/xml'], - 'xhtml' => ['application/xhtml+xml', 'application/xhtml', 'text/xhtml'], - 'webp' => 'image/webp', - 'rss' => 'application/rss+xml', - 'ai' => 'application/postscript', - 'bcpio' => 'application/x-bcpio', - 'bin' => 'application/octet-stream', - 'ccad' => 'application/clariscad', - 'cdf' => 'application/x-netcdf', - 'class' => 'application/octet-stream', - 'cpio' => 'application/x-cpio', - 'cpt' => 'application/mac-compactpro', - 'csh' => 'application/x-csh', - 'csv' => ['text/csv', 'application/vnd.ms-excel'], - 'dcr' => 'application/x-director', - 'dir' => 'application/x-director', - 'dms' => 'application/octet-stream', - 'doc' => 'application/msword', - 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', - 'drw' => 'application/drafting', - 'dvi' => 'application/x-dvi', - 'dwg' => 'application/acad', - 'dxf' => 'application/dxf', - 'dxr' => 'application/x-director', - 'eot' => 'application/vnd.ms-fontobject', - 'eps' => 'application/postscript', - 'exe' => 'application/octet-stream', - 'ez' => 'application/andrew-inset', - 'flv' => 'video/x-flv', - 'gtar' => 'application/x-gtar', - 'gz' => 'application/x-gzip', - 'bz2' => 'application/x-bzip', - '7z' => 'application/x-7z-compressed', - 'hal' => ['application/hal+xml', 'application/vnd.hal+xml'], - 'haljson' => ['application/hal+json', 'application/vnd.hal+json'], - 'halxml' => ['application/hal+xml', 'application/vnd.hal+xml'], - 'hdf' => 'application/x-hdf', - 'hqx' => 'application/mac-binhex40', - 'ico' => 'image/x-icon', - 'ips' => 'application/x-ipscript', - 'ipx' => 'application/x-ipix', - 'js' => 'application/javascript', - 'jsonapi' => 'application/vnd.api+json', - 'latex' => 'application/x-latex', - 'jsonld' => 'application/ld+json', - 'kml' => 'application/vnd.google-earth.kml+xml', - 'kmz' => 'application/vnd.google-earth.kmz', - 'lha' => 'application/octet-stream', - 'lsp' => 'application/x-lisp', - 'lzh' => 'application/octet-stream', - 'man' => 'application/x-troff-man', - 'me' => 'application/x-troff-me', - 'mif' => 'application/vnd.mif', - 'ms' => 'application/x-troff-ms', - 'nc' => 'application/x-netcdf', - 'oda' => 'application/oda', - 'otf' => 'font/otf', - 'pdf' => 'application/pdf', - 'pgn' => 'application/x-chess-pgn', - 'pot' => 'application/vnd.ms-powerpoint', - 'pps' => 'application/vnd.ms-powerpoint', - 'ppt' => 'application/vnd.ms-powerpoint', - 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', - 'ppz' => 'application/vnd.ms-powerpoint', - 'pre' => 'application/x-freelance', - 'prt' => 'application/pro_eng', - 'ps' => 'application/postscript', - 'roff' => 'application/x-troff', - 'scm' => 'application/x-lotusscreencam', - 'set' => 'application/set', - 'sh' => 'application/x-sh', - 'shar' => 'application/x-shar', - 'sit' => 'application/x-stuffit', - 'skd' => 'application/x-koan', - 'skm' => 'application/x-koan', - 'skp' => 'application/x-koan', - 'skt' => 'application/x-koan', - 'smi' => 'application/smil', - 'smil' => 'application/smil', - 'sol' => 'application/solids', - 'spl' => 'application/x-futuresplash', - 'src' => 'application/x-wais-source', - 'step' => 'application/STEP', - 'stl' => 'application/SLA', - 'stp' => 'application/STEP', - 'sv4cpio' => 'application/x-sv4cpio', - 'sv4crc' => 'application/x-sv4crc', - 'svg' => 'image/svg+xml', - 'svgz' => 'image/svg+xml', - 'swf' => 'application/x-shockwave-flash', - 't' => 'application/x-troff', - 'tar' => 'application/x-tar', - 'tcl' => 'application/x-tcl', - 'tex' => 'application/x-tex', - 'texi' => 'application/x-texinfo', - 'texinfo' => 'application/x-texinfo', - 'tr' => 'application/x-troff', - 'tsp' => 'application/dsptype', - 'ttc' => 'font/ttf', - 'ttf' => 'font/ttf', - 'unv' => 'application/i-deas', - 'ustar' => 'application/x-ustar', - 'vcd' => 'application/x-cdlink', - 'vda' => 'application/vda', - 'xlc' => 'application/vnd.ms-excel', - 'xll' => 'application/vnd.ms-excel', - 'xlm' => 'application/vnd.ms-excel', - 'xls' => 'application/vnd.ms-excel', - 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', - 'xlw' => 'application/vnd.ms-excel', - 'zip' => 'application/zip', - 'aif' => 'audio/x-aiff', - 'aifc' => 'audio/x-aiff', - 'aiff' => 'audio/x-aiff', - 'au' => 'audio/basic', - 'kar' => 'audio/midi', - 'mid' => 'audio/midi', - 'midi' => 'audio/midi', - 'mp2' => 'audio/mpeg', - 'mp3' => 'audio/mpeg', - 'mpga' => 'audio/mpeg', - 'ogg' => 'audio/ogg', - 'oga' => 'audio/ogg', - 'spx' => 'audio/ogg', - 'ra' => 'audio/x-realaudio', - 'ram' => 'audio/x-pn-realaudio', - 'rm' => 'audio/x-pn-realaudio', - 'rpm' => 'audio/x-pn-realaudio-plugin', - 'snd' => 'audio/basic', - 'tsi' => 'audio/TSP-audio', - 'wav' => 'audio/x-wav', - 'aac' => 'audio/aac', - 'asc' => 'text/plain', - 'c' => 'text/plain', - 'cc' => 'text/plain', - 'css' => 'text/css', - 'etx' => 'text/x-setext', - 'f' => 'text/plain', - 'f90' => 'text/plain', - 'h' => 'text/plain', - 'hh' => 'text/plain', - 'htm' => ['text/html', '*/*'], - 'ics' => 'text/calendar', - 'm' => 'text/plain', - 'rtf' => 'text/rtf', - 'rtx' => 'text/richtext', - 'sgm' => 'text/sgml', - 'sgml' => 'text/sgml', - 'tsv' => 'text/tab-separated-values', - 'tpl' => 'text/template', - 'txt' => 'text/plain', - 'text' => 'text/plain', - 'avi' => 'video/x-msvideo', - 'fli' => 'video/x-fli', - 'mov' => 'video/quicktime', - 'movie' => 'video/x-sgi-movie', - 'mpe' => 'video/mpeg', - 'mpeg' => 'video/mpeg', - 'mpg' => 'video/mpeg', - 'qt' => 'video/quicktime', - 'viv' => 'video/vnd.vivo', - 'vivo' => 'video/vnd.vivo', - 'ogv' => 'video/ogg', - 'webm' => 'video/webm', - 'mp4' => 'video/mp4', - 'm4v' => 'video/mp4', - 'f4v' => 'video/mp4', - 'f4p' => 'video/mp4', - 'm4a' => 'audio/mp4', - 'f4a' => 'audio/mp4', - 'f4b' => 'audio/mp4', - 'gif' => 'image/gif', - 'ief' => 'image/ief', - 'jpg' => 'image/jpeg', - 'jpeg' => 'image/jpeg', - 'jpe' => 'image/jpeg', - 'pbm' => 'image/x-portable-bitmap', - 'pgm' => 'image/x-portable-graymap', - 'png' => 'image/png', - 'pnm' => 'image/x-portable-anymap', - 'ppm' => 'image/x-portable-pixmap', - 'ras' => 'image/cmu-raster', - 'rgb' => 'image/x-rgb', - 'tif' => 'image/tiff', - 'tiff' => 'image/tiff', - 'xbm' => 'image/x-xbitmap', - 'xpm' => 'image/x-xpixmap', - 'xwd' => 'image/x-xwindowdump', - 'psd' => [ - 'application/photoshop', - 'application/psd', - 'image/psd', - 'image/x-photoshop', - 'image/photoshop', - 'zz-application/zz-winassoc-psd', - ], - 'ice' => 'x-conference/x-cooltalk', - 'iges' => 'model/iges', - 'igs' => 'model/iges', - 'mesh' => 'model/mesh', - 'msh' => 'model/mesh', - 'silo' => 'model/mesh', - 'vrml' => 'model/vrml', - 'wrl' => 'model/vrml', - 'mime' => 'www/mime', - 'pdb' => 'chemical/x-pdb', - 'xyz' => 'chemical/x-pdb', - 'javascript' => 'application/javascript', - 'form' => 'application/x-www-form-urlencoded', - 'file' => 'multipart/form-data', - 'xhtml-mobile' => 'application/vnd.wap.xhtml+xml', - 'atom' => 'application/atom+xml', - 'amf' => 'application/x-amf', - 'wap' => ['text/vnd.wap.wml', 'text/vnd.wap.wmlscript', 'image/vnd.wap.wbmp'], - 'wml' => 'text/vnd.wap.wml', - 'wmlscript' => 'text/vnd.wap.wmlscript', - 'wbmp' => 'image/vnd.wap.wbmp', - 'woff' => 'application/x-font-woff', - 'appcache' => 'text/cache-manifest', - 'manifest' => 'text/cache-manifest', - 'htc' => 'text/x-component', - 'rdf' => 'application/xml', - 'crx' => 'application/x-chrome-extension', - 'oex' => 'application/x-opera-extension', - 'xpi' => 'application/x-xpinstall', - 'safariextz' => 'application/octet-stream', - 'webapp' => 'application/x-web-app-manifest+json', - 'vcf' => 'text/x-vcard', - 'vtt' => 'text/vtt', - 'mkv' => 'video/x-matroska', - 'pkpass' => 'application/vnd.apple.pkpass', - 'ajax' => 'text/html', - 'bmp' => 'image/bmp', - ]; - /** * Status code to send to the client * * @var int */ - protected $_status = 200; + protected int $_status = 200; /** * File object for file to be read out as response * * @var \SplFileInfo|null */ - protected $_file; + protected ?SplFileInfo $_file = null; /** * File range. Used for requesting ranges of files. * * @var array */ - protected $_fileRange = []; + protected array $_fileRange = []; /** * The charset the response body is encoded with * * @var string */ - protected $_charset = 'UTF-8'; + protected string $_charset = 'UTF-8'; /** * Holds all the cache directives that will be converted @@ -406,28 +163,28 @@ class Response implements ResponseInterface * * @var array */ - protected $_cacheDirectives = []; + protected array $_cacheDirectives = []; /** * Collection of cookies to send to the client * * @var \Cake\Http\Cookie\CookieCollection */ - protected $_cookies; + protected CookieCollection $_cookies; /** * Reason Phrase * * @var string */ - protected $_reasonPhrase = 'OK'; + protected string $_reasonPhrase = 'OK'; /** * Stream mode options. * * @var string */ - protected $_streamMode = 'wb+'; + protected string $_streamMode = 'wb+'; /** * Stream target or resource object. @@ -509,14 +266,14 @@ protected function _setContentType(string $type): void if ( $this->_charset && ( - strpos($type, 'text/') === 0 || + str_starts_with($type, 'text/') || in_array($type, $allowed, true) ) ) { $charset = true; } - if ($charset && strpos($type, ';') === false) { + if ($charset && !str_contains($type, ';')) { $this->_setHeader('Content-Type', "{$type}; charset={$this->_charset}"); } else { $this->_setHeader('Content-Type', $type); @@ -532,7 +289,7 @@ protected function _setContentType(string $type): void * @param string $url The location to redirect to. * @return static A new response with the Location header set. */ - public function withLocation(string $url) + public function withLocation(string $url): static { $new = $this->withHeader('Location', $url); if ($new->_status === 200) { @@ -616,7 +373,7 @@ public function getStatusCode(): int * @return static * @throws \InvalidArgumentException For invalid status code arguments. */ - public function withStatus($code, $reasonPhrase = '') + public function withStatus(int $code, string $reasonPhrase = ''): static { $new = clone $this; $new->_setStatus($code, $reasonPhrase); @@ -637,7 +394,7 @@ protected function _setStatus(int $code, string $reasonPhrase = ''): void if ($code < static::STATUS_CODE_MIN || $code > static::STATUS_CODE_MAX) { throw new InvalidArgumentException(sprintf( 'Invalid status code: %s. Use a valid HTTP status code in range 1xx - 5xx.', - $code + $code, )); } @@ -682,9 +439,9 @@ public function getReasonPhrase(): string * @param array|string $mimeType Definition of the mime type. * @return void */ - public function setTypeMap(string $type, $mimeType): void + public function setTypeMap(string $type, array|string $mimeType): void { - $this->_mimeTypes[$type] = $mimeType; + MimeType::setMimeTypes($type, $mimeType); } /** @@ -695,7 +452,7 @@ public function setTypeMap(string $type, $mimeType): void public function getType(): string { $header = $this->getHeaderLine('Content-Type'); - if (strpos($header, ';') !== false) { + if (str_contains($header, ';')) { return explode(';', $header)[0]; } @@ -711,7 +468,7 @@ public function getType(): string * @param string $contentType Either a file extension which will be mapped to a mime-type or a concrete mime-type. * @return static */ - public function withType(string $contentType) + public function withType(string $contentType): static { $mappedType = $this->resolveType($contentType); $new = clone $this; @@ -729,15 +486,16 @@ public function withType(string $contentType) */ protected function resolveType(string $contentType): string { - $mapped = $this->getMimeType($contentType); - if ($mapped) { - return is_array($mapped) ? current($mapped) : $mapped; + if (str_contains($contentType, '/')) { + return $contentType; } - if (strpos($contentType, '/') === false) { - throw new InvalidArgumentException(sprintf('"%s" is an invalid content type.', $contentType)); + + $mimeType = MimeType::getMimeType($contentType); + if ($mimeType === null) { + throw new InvalidArgumentException(sprintf('`%s` is an invalid content type.', $contentType)); } - return $contentType; + return $mimeType; } /** @@ -748,9 +506,15 @@ protected function resolveType(string $contentType): string * @param string $alias the content type alias to map * @return array|string|false String mapped mime type or false if $alias is not mapped */ - public function getMimeType(string $alias) + public function getMimeType(string $alias): array|string|false { - return $this->_mimeTypes[$alias] ?? false; + $mimeTypes = MimeType::getMimeTypes($alias); + + if ($mimeTypes === null) { + return false; + } + + return count($mimeTypes) === 1 ? $mimeTypes[0] : $mimeTypes; } /** @@ -761,19 +525,13 @@ public function getMimeType(string $alias) * @param array|string $ctype Either a string content type to map, or an array of types. * @return array|string|null Aliases for the types provided. */ - public function mapType($ctype) + public function mapType(array|string $ctype): array|string|null { if (is_array($ctype)) { - return array_map([$this, 'mapType'], $ctype); - } - - foreach ($this->_mimeTypes as $alias => $types) { - if (in_array($ctype, (array)$types, true)) { - return $alias; - } + return array_map($this->mapType(...), $ctype); } - return null; + return MimeType::getExtension($ctype); } /** @@ -792,7 +550,7 @@ public function getCharset(): string * @param string $charset Character set string. * @return static */ - public function withCharset(string $charset) + public function withCharset(string $charset): static { $new = clone $this; $new->_charset = $charset; @@ -806,10 +564,10 @@ public function withCharset(string $charset) * * @return static */ - public function withDisabledCache() + public function withDisabledCache(): static { return $this->withHeader('Expires', 'Mon, 26 Jul 1997 05:00:00 GMT') - ->withHeader('Last-Modified', gmdate(DATE_RFC7231)) + ->withHeader('Last-Modified', gmdate(CAKE_DATE_RFC7231)) ->withHeader('Cache-Control', 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0'); } @@ -820,18 +578,18 @@ public function withDisabledCache() * @param string|int $time a valid time for cache expiry * @return static */ - public function withCache($since, $time = '+1 day') + public function withCache(string|int $since, string|int $time = '+1 day'): static { if (!is_int($time)) { $time = strtotime($time); if ($time === false) { throw new InvalidArgumentException( - 'Invalid time parameter. Ensure your time value can be parsed by strtotime' + 'Invalid time parameter. Ensure your time value can be parsed by strtotime', ); } } - return $this->withHeader('Date', gmdate(DATE_RFC7231, time())) + return $this->withHeader('Date', gmdate(CAKE_DATE_RFC7231, time())) ->withModified($since) ->withExpires($time) ->withSharable(true) @@ -846,7 +604,7 @@ public function withCache($since, $time = '+1 day') * @param int|null $time time in seconds after which the response should no longer be considered fresh. * @return static */ - public function withSharable(bool $public, ?int $time = null) + public function withSharable(bool $public, ?int $time = null): static { $new = clone $this; unset($new->_cacheDirectives['private'], $new->_cacheDirectives['public']); @@ -871,7 +629,7 @@ public function withSharable(bool $public, ?int $time = null) * @param int $seconds The number of seconds for shared max-age * @return static */ - public function withSharedMaxAge(int $seconds) + public function withSharedMaxAge(int $seconds): static { $new = clone $this; $new->_cacheDirectives['s-maxage'] = $seconds; @@ -889,7 +647,7 @@ public function withSharedMaxAge(int $seconds) * @param int $seconds The seconds a cached response can be considered valid * @return static */ - public function withMaxAge(int $seconds) + public function withMaxAge(int $seconds): static { $new = clone $this; $new->_cacheDirectives['max-age'] = $seconds; @@ -909,7 +667,7 @@ public function withMaxAge(int $seconds) * @param bool $enable If boolean sets or unsets the directive. * @return static */ - public function withMustRevalidate(bool $enable) + public function withMustRevalidate(bool $enable): static { $new = clone $this; if ($enable) { @@ -955,11 +713,11 @@ protected function _setCacheControl(): void * @param \DateTimeInterface|string|int|null $time Valid time string or \DateTime instance. * @return static */ - public function withExpires($time) + public function withExpires(DateTimeInterface|string|int|null $time): static { $date = $this->_getUTCDate($time); - return $this->withHeader('Expires', $date->format(DATE_RFC7231)); + return $this->withHeader('Expires', $date->format(CAKE_DATE_RFC7231)); } /** @@ -978,44 +736,11 @@ public function withExpires($time) * @param \DateTimeInterface|string|int $time Valid time string or \DateTime instance. * @return static */ - public function withModified($time) + public function withModified(DateTimeInterface|string|int $time): static { $date = $this->_getUTCDate($time); - return $this->withHeader('Last-Modified', $date->format(DATE_RFC7231)); - } - - /** - * Sets the response as Not Modified by removing any body contents - * setting the status code to "304 Not Modified" and removing all - * conflicting headers - * - * *Warning* This method mutates the response in-place and should be avoided. - * - * @deprecated 4.4.0 Use `withNotModified()` instead. - * @return void - */ - public function notModified(): void - { - deprecationWarning( - 'The `notModified()` method is deprecated. ' . - 'Use `withNotModified() instead, and remember immutability of with* methods.' - ); - $this->_createStream(); - $this->_setStatus(304); - - $remove = [ - 'Allow', - 'Content-Encoding', - 'Content-Language', - 'Content-Length', - 'Content-MD5', - 'Content-Type', - 'Last-Modified', - ]; - foreach ($remove as $header) { - $this->_clearHeader($header); - } + return $this->withHeader('Last-Modified', $date->format(CAKE_DATE_RFC7231)); } /** @@ -1027,7 +752,7 @@ public function notModified(): void * * @return static */ - public function withNotModified() + public function withNotModified(): static { $new = $this->withStatus(304); $new->_createStream(); @@ -1058,7 +783,7 @@ public function withNotModified() * containing the list for variances. * @return static */ - public function withVary($cacheVariances) + public function withVary(array|string $cacheVariances): static { return $this->withHeader('Vary', (array)$cacheVariances); } @@ -1084,7 +809,7 @@ public function withVary($cacheVariances) * other with the same hash or not. Defaults to false * @return static */ - public function withEtag(string $hash, bool $weak = false) + public function withEtag(string $hash, bool $weak = false): static { $hash = sprintf('%s"%s"', $weak ? 'W/' : '', $hash); @@ -1098,7 +823,7 @@ public function withEtag(string $hash, bool $weak = false) * @param \DateTimeInterface|string|int|null $time Valid time string or \DateTimeInterface instance. * @return \DateTimeInterface */ - protected function _getUTCDate($time = null): DateTimeInterface + protected function _getUTCDate(DateTimeInterface|string|int|null $time = null): DateTimeInterface { if ($time instanceof DateTimeInterface) { $result = clone $time; @@ -1108,7 +833,9 @@ protected function _getUTCDate($time = null): DateTimeInterface $result = new DateTime($time ?? 'now'); } - /** @psalm-suppress UndefinedInterfaceMethod */ + /** + * @phpstan-ignore-next-line + */ return $result->setTimezone(new DateTimeZone('UTC')); } @@ -1120,11 +847,10 @@ protected function _getUTCDate($time = null): DateTimeInterface */ public function compress(): bool { - $compressionEnabled = ini_get('zlib.output_compression') !== '1' && + return ini_get('zlib.output_compression') !== '1' && extension_loaded('zlib') && - (strpos((string)env('HTTP_ACCEPT_ENCODING'), 'gzip') !== false); - - return $compressionEnabled && ob_start('ob_gzhandler'); + str_contains((string)env('HTTP_ACCEPT_ENCODING'), 'gzip') && + ob_start('ob_gzhandler'); } /** @@ -1134,7 +860,7 @@ public function compress(): bool */ public function outputCompressed(): bool { - return strpos((string)env('HTTP_ACCEPT_ENCODING'), 'gzip') !== false + return str_contains((string)env('HTTP_ACCEPT_ENCODING'), 'gzip') && (ini_get('zlib.output_compression') === '1' || in_array('ob_gzhandler', ob_list_handlers(), true)); } @@ -1144,7 +870,7 @@ public function outputCompressed(): bool * @param string $filename The name of the file as the browser will download the response * @return static */ - public function withDownload(string $filename) + public function withDownload(string $filename): static { return $this->withHeader('Content-Disposition', 'attachment; filename="' . $filename . '"'); } @@ -1155,7 +881,7 @@ public function withDownload(string $filename) * @param string|int $bytes Number of bytes * @return static */ - public function withLength($bytes) + public function withLength(string|int $bytes): static { return $this->withHeader('Content-Length', (string)$bytes); } @@ -1182,7 +908,7 @@ public function withLength($bytes) * @return static * @since 3.6.0 */ - public function withAddedLink(string $url, array $options = []) + public function withAddedLink(string $url, array $options = []): static { $params = []; foreach ($options as $key => $option) { @@ -1211,7 +937,7 @@ public function withAddedLink(string $url, array $options = []) */ public function isNotModified(ServerRequest $request): bool { - $etags = preg_split('/\s*,\s*/', $request->getHeaderLine('If-None-Match'), 0, PREG_SPLIT_NO_EMPTY); + $etags = preg_split('/\s*,\s*/', $request->getHeaderLine('If-None-Match'), 0, PREG_SPLIT_NO_EMPTY) ?: []; $responseTag = $this->getHeaderLine('Etag'); $etagMatches = null; if ($responseTag) { @@ -1230,37 +956,6 @@ public function isNotModified(ServerRequest $request): bool return $etagMatches !== false && $timeMatches !== false; } - /** - * Checks whether a response has not been modified according to the 'If-None-Match' - * (Etags) and 'If-Modified-Since' (last modification date) request - * headers. If the response is detected to be not modified, it - * is marked as so accordingly so the client can be informed of that. - * - * In order to mark a response as not modified, you need to set at least - * the Last-Modified etag response header before calling this method. Otherwise - * a comparison will not be possible. - * - * *Warning* This method mutates the response in-place and should be avoided. - * - * @param \Cake\Http\ServerRequest $request Request object - * @return bool Whether the response was marked as not modified or not. - * @deprecated 4.4.0 Use `isNotModified()` and `withNotModified()` instead. - */ - public function checkNotModified(ServerRequest $request): bool - { - deprecationWarning( - 'The `checkNotModified()` method is deprecated. ' . - 'Use `isNotModified() instead and `withNoModified()` instead.' - ); - if ($this->isNotModified($request)) { - $this->notModified(); - - return true; - } - - return false; - } - /** * String conversion. Fetches the response body as a string. * Does *not* send headers. @@ -1288,7 +983,7 @@ public function __toString(): string * @param \Cake\Http\Cookie\CookieInterface $cookie cookie object * @return static */ - public function withCookie(CookieInterface $cookie) + public function withCookie(CookieInterface $cookie): static { $new = clone $this; $new->_cookies = $new->_cookies->add($cookie); @@ -1309,7 +1004,7 @@ public function withCookie(CookieInterface $cookie) * @param \Cake\Http\Cookie\CookieInterface $cookie cookie object * @return static */ - public function withExpiredCookie(CookieInterface $cookie) + public function withExpiredCookie(CookieInterface $cookie): static { $cookie = $cookie->withExpired(); @@ -1347,9 +1042,7 @@ public function getCookie(string $name): ?array public function getCookies(): array { $out = []; - /** @var array<\Cake\Http\Cookie\Cookie> $cookies */ - $cookies = $this->_cookies; - foreach ($cookies as $cookie) { + foreach ($this->_cookies as $cookie) { $out[$cookie->getName()] = $cookie->toArray(); } @@ -1372,7 +1065,7 @@ public function getCookieCollection(): CookieCollection * @param \Cake\Http\Cookie\CookieCollection $cookieCollection Cookie collection to set. * @return static */ - public function withCookieCollection(CookieCollection $cookieCollection) + public function withCookieCollection(CookieCollection $cookieCollection): static { $new = clone $this; $new->_cookies = $cookieCollection; @@ -1414,7 +1107,7 @@ public function cors(ServerRequest $request): CorsBuilder * @return static * @throws \Cake\Http\Exception\NotFoundException */ - public function withFile(string $path, array $options = []) + public function withFile(string $path, array $options = []): static { $file = $this->validateFile($path); $options += [ @@ -1422,30 +1115,19 @@ public function withFile(string $path, array $options = []) 'download' => null, ]; - $extension = strtolower($file->getExtension()); - $mapped = $this->getMimeType($extension); - if ((!$extension || !$mapped) && $options['download'] === null) { + $extension = $file->getExtension(); + $mapped = MimeType::getMimeTypeForFile($file->getRealPath()); + if ($extension === '' && $options['download'] === null) { $options['download'] = true; } $new = clone $this; if ($mapped) { - $new = $new->withType($extension); + $new = $new->withType($mapped); } $fileSize = $file->getSize(); if ($options['download']) { - $agent = (string)env('HTTP_USER_AGENT'); - - if ($agent && preg_match('%Opera([/ ])([0-9].[0-9]{1,2})%', $agent)) { - $contentType = 'application/octet-stream'; - } elseif ($agent && preg_match('/MSIE ([0-9].[0-9]{1,2})/', $agent)) { - $contentType = 'application/force-download'; - } - - if (isset($contentType)) { - $new = $new->withType($contentType); - } $name = $options['name'] ?: $file->getFileName(); $new = $new->withDownload($name) ->withHeader('Content-Transfer-Encoding', 'binary'); @@ -1470,7 +1152,7 @@ public function withFile(string $path, array $options = []) * @param string|null $string The string to be sent * @return static */ - public function withStringBody(?string $string) + public function withStringBody(?string $string): static { $new = clone $this; $new->_createStream(); @@ -1488,7 +1170,7 @@ public function withStringBody(?string $string) */ protected function validateFile(string $path): SplFileInfo { - if (strpos($path, '../') !== false || strpos($path, '..\\') !== false) { + if (str_contains($path, '../') || str_contains($path, '..\\')) { throw new NotFoundException(__d('cake', 'The requested file contains `..` and will not be read.')); } @@ -1551,8 +1233,7 @@ protected function _fileRange(SplFileInfo $file, string $httpRange): void return; } - /** @psalm-suppress PossiblyInvalidOperand */ - $this->_setHeader('Content-Length', (string)($end - $start + 1)); + $this->_setHeader('Content-Length', (string)((int)$end - (int)$start + 1)); $this->_setHeader('Content-Range', 'bytes ' . $start . '-' . $end . '/' . $fileSize); $this->_setStatus(206); /** diff --git a/app/vendor/cakephp/cakephp/src/Http/ResponseEmitter.php b/app/vendor/cakephp/cakephp/src/Http/ResponseEmitter.php index 0cbb23c6f..e2381b0e3 100644 --- a/app/vendor/cakephp/cakephp/src/Http/ResponseEmitter.php +++ b/app/vendor/cakephp/cakephp/src/Http/ResponseEmitter.php @@ -21,27 +21,21 @@ namespace Cake\Http; use Cake\Http\Cookie\Cookie; +use Cake\Http\Cookie\CookieInterface; use Laminas\Diactoros\RelativeStream; -use Laminas\HttpHandlerRunner\Emitter\EmitterInterface; use Psr\Http\Message\ResponseInterface; /** * Emits a Response to the PHP Server API. - * - * This emitter offers a few changes from the emitters offered by - * diactoros: - * - * - It logs headers sent using CakePHP's logging tools. - * - Cookies are emitted using setcookie() to not conflict with ext/session */ -class ResponseEmitter implements EmitterInterface +class ResponseEmitter { /** * Maximum output buffering size for each iteration. * * @var int */ - protected $maxBufferLength; + protected int $maxBufferLength; /** * Constructor @@ -67,7 +61,7 @@ public function emit(ResponseInterface $response): bool $file = ''; $line = 0; if (headers_sent($file, $line)) { - $message = "Unable to emit headers. Headers sent in file=$file line=$line"; + $message = "Unable to emit headers. Headers sent in file={$file} line={$line}"; trigger_error($message, E_USER_WARNING); } @@ -137,6 +131,7 @@ protected function emitBodyRange(array $range, ResponseInterface $response): voi $body = new RelativeStream($body, $first); $body->rewind(); $pos = 0; + /** @var int $length */ $length = $last - $first + 1; while (!$body->eof() && $pos < $length) { if ($pos + $this->maxBufferLength > $length) { @@ -165,7 +160,7 @@ protected function emitStatusLine(ResponseInterface $response): void 'HTTP/%s %d%s', $response->getProtocolVersion(), $response->getStatusCode(), - ($reasonPhrase ? ' ' . $reasonPhrase : '') + ($reasonPhrase ? ' ' . $reasonPhrase : ''), )); } @@ -183,7 +178,7 @@ protected function emitStatusLine(ResponseInterface $response): void protected function emitHeaders(ResponseInterface $response): void { $cookies = []; - if (method_exists($response, 'getCookieCollection')) { + if ($response instanceof Response) { $cookies = iterator_to_array($response->getCookieCollection()); } @@ -197,7 +192,7 @@ protected function emitHeaders(ResponseInterface $response): void header(sprintf( '%s: %s', $name, - $value + $value, ), $first); $first = false; } @@ -225,33 +220,13 @@ protected function emitCookies(array $cookies): void * @param \Cake\Http\Cookie\CookieInterface|string $cookie Cookie. * @return bool */ - protected function setCookie($cookie): bool + protected function setCookie(CookieInterface|string $cookie): bool { if (is_string($cookie)) { $cookie = Cookie::createFromHeaderString($cookie, ['path' => '']); } - if (PHP_VERSION_ID >= 70300) { - return setcookie($cookie->getName(), $cookie->getScalarValue(), $cookie->getOptions()); - } - - $path = $cookie->getPath(); - $sameSite = $cookie->getSameSite(); - if ($sameSite !== null) { - // Temporary hack for PHP 7.2 to set "SameSite" attribute - // https://stackoverflow.com/questions/39750906/php-setcookie-samesite-strict - $path .= '; samesite=' . $sameSite; - } - - return setcookie( - $cookie->getName(), - $cookie->getScalarValue(), - $cookie->getExpiresTimestamp() ?: 0, - $path, - $cookie->getDomain(), - $cookie->isSecure(), - $cookie->isHttpOnly() - ); + return setcookie($cookie->getName(), $cookie->getScalarValue(), $cookie->getOptions()); } /** @@ -263,9 +238,7 @@ protected function setCookie($cookie): bool */ protected function flush(?int $maxBufferLevel = null): void { - if ($maxBufferLevel === null) { - $maxBufferLevel = ob_get_level(); - } + $maxBufferLevel ??= ob_get_level(); while (ob_get_level() > $maxBufferLevel) { ob_end_flush(); @@ -280,7 +253,7 @@ protected function flush(?int $maxBufferLevel = null): void * @return array|false [unit, first, last, length]; returns false if no * content range or an invalid content range is provided */ - protected function parseContentRange(string $header) + protected function parseContentRange(string $header): array|false { if (preg_match('/(?P[\w]+)\s+(?P\d+)-(?P\d+)\/(?P\d+|\*)/', $header, $matches)) { return [ diff --git a/app/vendor/cakephp/cakephp/src/Http/ResponseFactory.php b/app/vendor/cakephp/cakephp/src/Http/ResponseFactory.php new file mode 100644 index 000000000..a05900b67 --- /dev/null +++ b/app/vendor/cakephp/cakephp/src/Http/ResponseFactory.php @@ -0,0 +1,39 @@ +withStatus($code, $reasonPhrase); + } +} diff --git a/app/vendor/cakephp/cakephp/src/Http/Runner.php b/app/vendor/cakephp/cakephp/src/Http/Runner.php index b6ea82fd0..72d25cf0c 100644 --- a/app/vendor/cakephp/cakephp/src/Http/Runner.php +++ b/app/vendor/cakephp/cakephp/src/Http/Runner.php @@ -33,14 +33,14 @@ class Runner implements RequestHandlerInterface * * @var \Cake\Http\MiddlewareQueue */ - protected $queue; + protected MiddlewareQueue $queue; /** * Fallback handler to use if middleware queue does not generate response. * * @var \Psr\Http\Server\RequestHandlerInterface|null */ - protected $fallbackHandler; + protected ?RequestHandlerInterface $fallbackHandler = null; /** * @param \Cake\Http\MiddlewareQueue $queue The middleware queue @@ -51,19 +51,12 @@ class Runner implements RequestHandlerInterface public function run( MiddlewareQueue $queue, ServerRequestInterface $request, - ?RequestHandlerInterface $fallbackHandler = null + ?RequestHandlerInterface $fallbackHandler = null, ): ResponseInterface { $this->queue = $queue; $this->queue->rewind(); $this->fallbackHandler = $fallbackHandler; - if ( - $fallbackHandler instanceof RoutingApplicationInterface && - $request instanceof ServerRequest - ) { - Router::setRequest($request); - } - return $this->handle($request); } @@ -75,6 +68,13 @@ public function run( */ public function handle(ServerRequestInterface $request): ResponseInterface { + if ( + $this->fallbackHandler instanceof RoutingApplicationInterface && + $request instanceof ServerRequest + ) { + Router::setRequest($request); + } + if ($this->queue->valid()) { $middleware = $this->queue->current(); $this->queue->next(); diff --git a/app/vendor/cakephp/cakephp/src/Http/Server.php b/app/vendor/cakephp/cakephp/src/Http/Server.php index ea9cf5973..c95ee4f21 100644 --- a/app/vendor/cakephp/cakephp/src/Http/Server.php +++ b/app/vendor/cakephp/cakephp/src/Http/Server.php @@ -23,27 +23,32 @@ use Cake\Event\EventDispatcherTrait; use Cake\Event\EventManager; use Cake\Event\EventManagerInterface; +use Cake\Routing\Router; use InvalidArgumentException; -use Laminas\HttpHandlerRunner\Emitter\EmitterInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; /** * Runs an application invoking all the PSR7 middleware and the registered application. + * + * @implements \Cake\Event\EventDispatcherInterface<\Cake\Core\HttpApplicationInterface> */ class Server implements EventDispatcherInterface { + /** + * @use \Cake\Event\EventDispatcherTrait<\Cake\Core\HttpApplicationInterface> + */ use EventDispatcherTrait; /** * @var \Cake\Core\HttpApplicationInterface */ - protected $app; + protected HttpApplicationInterface $app; /** * @var \Cake\Http\Runner */ - protected $runner; + protected Runner $runner; /** * Constructor @@ -75,7 +80,7 @@ public function __construct(HttpApplicationInterface $app, ?Runner $runner = nul */ public function run( ?ServerRequestInterface $request = null, - ?MiddlewareQueue $middlewareQueue = null + ?MiddlewareQueue $middlewareQueue = null, ): ResponseInterface { $this->bootstrap(); @@ -124,17 +129,35 @@ protected function bootstrap(): void /** * Emit the response using the PHP SAPI. * + * After the response has been emitted, the `Server.terminate` event will be triggered. + * + * The `Server.terminate` event can be used to do potentially heavy tasks after the + * response is sent to the client. Only the PHP FPM server API is able to send a + * response to the client while the server's PHP process still performs some tasks. + * For other environments the event will be triggered before the response is flushed + * to the client and will have no benefit. + * * @param \Psr\Http\Message\ResponseInterface $response The response to emit - * @param \Laminas\HttpHandlerRunner\Emitter\EmitterInterface|null $emitter The emitter to use. + * @param \Cake\Http\ResponseEmitter|null $emitter The emitter to use. * When null, a SAPI Stream Emitter will be used. * @return void */ - public function emit(ResponseInterface $response, ?EmitterInterface $emitter = null): void + public function emit(ResponseInterface $response, ?ResponseEmitter $emitter = null): void { - if (!$emitter) { - $emitter = new ResponseEmitter(); - } + $emitter ??= new ResponseEmitter(); $emitter->emit($response); + + $request = null; + if ($this->app instanceof ContainerApplicationInterface) { + $container = $this->app->getContainer(); + if ($container->has(ServerRequest::class)) { + $request = $container->get(ServerRequest::class); + } + } + if (!$request) { + $request = Router::getRequest(); + } + $this->dispatchEvent('Server.terminate', compact('request', 'response')); } /** diff --git a/app/vendor/cakephp/cakephp/src/Http/ServerRequest.php b/app/vendor/cakephp/cakephp/src/Http/ServerRequest.php index cd63b5375..7ea82e4ad 100644 --- a/app/vendor/cakephp/cakephp/src/Http/ServerRequest.php +++ b/app/vendor/cakephp/cakephp/src/Http/ServerRequest.php @@ -22,15 +22,14 @@ use Cake\Http\Cookie\CookieCollection; use Cake\Http\Exception\MethodNotAllowedException; use Cake\Utility\Hash; +use Closure; use InvalidArgumentException; -use Laminas\Diactoros\PhpInputStream; use Laminas\Diactoros\Stream; use Laminas\Diactoros\UploadedFile; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\StreamInterface; use Psr\Http\Message\UploadedFileInterface; use Psr\Http\Message\UriInterface; -use function Cake\Core\deprecationWarning; use function Cake\Core\env; /** @@ -44,7 +43,7 @@ class ServerRequest implements ServerRequestInterface * * @var array */ - protected $params = [ + protected array $params = [ 'plugin' => null, 'controller' => null, 'action' => null, @@ -59,42 +58,42 @@ class ServerRequest implements ServerRequestInterface * * @var object|array|null */ - protected $data = []; + protected object|array|null $data = []; /** * Array of query string arguments * * @var array */ - protected $query = []; + protected array $query = []; /** * Array of cookie data. * * @var array */ - protected $cookies = []; + protected array $cookies = []; /** * Array of environment data. * * @var array */ - protected $_environment = []; + protected array $_environment = []; /** * Base URL path. * * @var string */ - protected $base; + protected string $base; /** * webroot path segment for the request. * * @var string */ - protected $webroot = '/'; + protected string $webroot = '/'; /** * Whether to trust HTTP_X headers set by most load balancers. @@ -103,14 +102,14 @@ class ServerRequest implements ServerRequestInterface * * @var bool */ - public $trustProxy = false; + public bool $trustProxy = false; /** * Trusted proxies list * * @var array */ - protected $trustedProxies = []; + protected array $trustedProxies = []; /** * The built in detectors used with `is()` can be modified with `addDetector()`. @@ -118,9 +117,9 @@ class ServerRequest implements ServerRequestInterface * There are several ways to specify a detector, see \Cake\Http\ServerRequest::addDetector() for the * various formats and ways to define detectors. * - * @var array + * @var array<\Closure|array> */ - protected static $_detectors = [ + protected static array $_detectors = [ 'get' => ['env' => 'REQUEST_METHOD', 'value' => 'GET'], 'post' => ['env' => 'REQUEST_METHOD', 'value' => 'POST'], 'put' => ['env' => 'REQUEST_METHOD', 'value' => 'PUT'], @@ -128,7 +127,6 @@ class ServerRequest implements ServerRequestInterface 'delete' => ['env' => 'REQUEST_METHOD', 'value' => 'DELETE'], 'head' => ['env' => 'REQUEST_METHOD', 'value' => 'HEAD'], 'options' => ['env' => 'REQUEST_METHOD', 'value' => 'OPTIONS'], - 'ssl' => ['env' => 'HTTPS', 'options' => [1, 'on']], 'https' => ['env' => 'HTTPS', 'options' => [1, 'on']], 'ajax' => ['env' => 'HTTP_X_REQUESTED_WITH', 'value' => 'XMLHttpRequest'], 'json' => ['accept' => ['application/json'], 'param' => '_ext', 'value' => 'json'], @@ -145,70 +143,70 @@ class ServerRequest implements ServerRequestInterface * * @var array */ - protected $_detectorCache = []; + protected array $_detectorCache = []; /** * Request body stream. Contains php://input unless `input` constructor option is used. * * @var \Psr\Http\Message\StreamInterface */ - protected $stream; + protected StreamInterface $stream; /** * Uri instance * * @var \Psr\Http\Message\UriInterface */ - protected $uri; + protected UriInterface $uri; /** * Instance of a Session object relative to this request * * @var \Cake\Http\Session */ - protected $session; + protected Session $session; /** * Instance of a FlashMessage object relative to this request * * @var \Cake\Http\FlashMessage */ - protected $flash; + protected FlashMessage $flash; /** * Store the additional attributes attached to the request. * * @var array */ - protected $attributes = []; + protected array $attributes = []; /** * A list of properties that emulated by the PSR7 attribute methods. * * @var array */ - protected $emulatedAttributes = ['session', 'flash', 'webroot', 'base', 'params', 'here']; + protected array $emulatedAttributes = ['session', 'flash', 'webroot', 'base', 'params', 'here']; /** * Array of Psr\Http\Message\UploadedFileInterface objects. * * @var array */ - protected $uploadedFiles = []; + protected array $uploadedFiles = []; /** * The HTTP protocol version used. * * @var string|null */ - protected $protocol; + protected ?string $protocol = null; /** * The request target if overridden * * @var string|null */ - protected $requestTarget; + protected ?string $requestTarget = null; /** * Create a new request object. @@ -280,7 +278,7 @@ protected function _setConfig(array $config): void if ($config['url'] !== '') { $config = $this->processUrlOption($config); } - $uri = ServerRequestFactory::createUri($config['environment']); + ['uri' => $uri] = UriFactory::marshalUriAndBaseFromSapi($config['environment']); } $this->_environment = $config['environment']; @@ -294,11 +292,19 @@ protected function _setConfig(array $config): void $stream->write($config['input']); $stream->rewind(); } else { - $stream = new PhpInputStream(); + $stream = new Stream('php://input'); } $this->stream = $stream; - $this->data = $config['post']; + $post = $config['post']; + if (!(is_array($post) || is_object($post) || $post === null)) { + throw new InvalidArgumentException(sprintf( + '`post` key must be an array, object or null.' + . ' Got `%s` instead.', + get_debug_type($post), + )); + } + $this->data = $post; $this->uploadedFiles = $config['files']; $this->query = $config['query']; $this->params = $config['params']; @@ -316,11 +322,11 @@ protected function _setConfig(array $config): void */ protected function processUrlOption(array $config): array { - if ($config['url'][0] !== '/') { + if (!str_starts_with($config['url'], '/')) { $config['url'] = '/' . $config['url']; } - if (strpos($config['url'], '?') !== false) { + if (str_contains($config['url'], '?')) { [$config['url'], $config['environment']['QUERY_STRING']] = explode('?', $config['url']); parse_str($config['environment']['QUERY_STRING'], $queryArgs); @@ -339,12 +345,7 @@ protected function processUrlOption(array $config): array */ public function contentType(): ?string { - $type = $this->getEnv('CONTENT_TYPE'); - if ($type) { - return $type; - } - - return $this->getEnv('HTTP_CONTENT_TYPE'); + return $this->getEnv('CONTENT_TYPE') ?: $this->getEnv('HTTP_CONTENT_TYPE'); } /** @@ -376,7 +377,7 @@ public function clientIp(): string { if ($this->trustProxy && $this->getEnv('HTTP_X_FORWARDED_FOR')) { $addresses = array_map('trim', explode(',', (string)$this->getEnv('HTTP_X_FORWARDED_FOR'))); - $trusted = (count($this->trustedProxies) > 0); + $trusted = $this->trustedProxies !== []; $n = count($addresses); if ($trusted) { @@ -437,24 +438,27 @@ public function referer(bool $local = true): ?string $ref = $this->getEnv('HTTP_REFERER'); $base = Configure::read('App.fullBaseUrl') . $this->webroot; - if (!empty($ref) && !empty($base)) { - if ($local && strpos($ref, $base) === 0) { - $ref = substr($ref, strlen($base)); - if ($ref === '' || strpos($ref, '//') === 0) { - $ref = '/'; - } - if ($ref[0] !== '/') { - $ref = '/' . $ref; - } + if (!$ref || !$base) { + return null; + } - return $ref; + if ($local && str_starts_with($ref, $base)) { + $ref = substr($ref, strlen($base)); + if ($ref === '' || str_starts_with($ref, '//')) { + $ref = '/'; } - if (!$local) { - return $ref; + if (!str_starts_with($ref, '/')) { + return '/' . $ref; } + + return $ref; + } + + if ($local) { + return null; } - return null; + return $ref; } /** @@ -465,16 +469,16 @@ public function referer(bool $local = true): ?string * @return bool * @throws \BadMethodCallException when an invalid method is called. */ - public function __call(string $name, array $params) + public function __call(string $name, array $params): bool { - if (strpos($name, 'is') === 0) { + if (str_starts_with($name, 'is')) { $type = strtolower(substr($name, 2)); array_unshift($params, $type); return $this->is(...$params); } - throw new BadMethodCallException(sprintf('Method "%s()" does not exist', $name)); + throw new BadMethodCallException(sprintf('Method `%s()` does not exist.', $name)); } /** @@ -490,7 +494,7 @@ public function __call(string $name, array $params) * @return bool Whether the request is the type you are checking. * @throws \InvalidArgumentException If no detector has been set for the provided type. */ - public function is($type, ...$args): bool + public function is(array|string $type, mixed ...$args): bool { if (is_array($type)) { foreach ($type as $_type) { @@ -504,13 +508,13 @@ public function is($type, ...$args): bool $type = strtolower($type); if (!isset(static::$_detectors[$type])) { - throw new InvalidArgumentException("No detector set for type `{$type}`"); + throw new InvalidArgumentException(sprintf('No detector set for type `%s`.', $type)); } if ($args) { return $this->_is($type, $args); } - return $this->_detectorCache[$type] = $this->_detectorCache[$type] ?? $this->_is($type, $args); + return $this->_detectorCache[$type] ??= $this->_is($type, $args); } /** @@ -532,11 +536,8 @@ public function clearDetectorCache(): void */ protected function _is(string $type, array $args): bool { - if ($type === 'ssl') { - deprecationWarning('The `ssl` detector is deprecated. Use `https` instead.'); - } $detect = static::$_detectors[$type]; - if (is_callable($detect)) { + if ($detect instanceof Closure) { array_unshift($args, $this); return $detect(...$args); @@ -598,7 +599,7 @@ protected function _headerDetector(array $detect): bool foreach ($detect['header'] as $header => $value) { $header = $this->getEnv('http_' . $header); if ($header !== null) { - if (!is_string($value) && !is_bool($value) && is_callable($value)) { + if ($value instanceof Closure) { return $value($header); } @@ -621,10 +622,10 @@ protected function _paramDetector(array $detect): bool if (isset($detect['value'])) { $value = $detect['value']; - return isset($this->params[$key]) ? $this->params[$key] == $value : false; + return isset($this->params[$key]) && $this->params[$key] == $value; } if (isset($detect['options'])) { - return isset($this->params[$key]) ? in_array($this->params[$key], $detect['options']) : false; + return isset($this->params[$key]) && in_array($this->params[$key], $detect['options']); } return false; @@ -683,8 +684,8 @@ public function isAll(array $types): bool * * ### Callback comparison * - * Callback detectors allow you to provide a callable to handle the check. - * The callback will receive the request object as its only parameter. + * Callback detectors allow you to provide a closure to handle the check. + * The closure will receive the request object as its only parameter. * * ``` * addDetector('custom', function ($request) { //Return a boolean }); @@ -720,7 +721,7 @@ public function isAll(array $types): bool * Allows for one or more headers to be compared. * * ``` - * addDetector('fancy', ['header' => ['X-Fancy' => 1]); + * addDetector('fancy', ['header' => ['X-Fancy' => 1]]); * ``` * * The `param`, `env` and comparison types allow the following @@ -750,20 +751,22 @@ public function isAll(array $types): bool * `addDetector('extension', ['param' => '_ext', 'options' => ['pdf', 'csv']]` * * @param string $name The name of the detector. - * @param callable|array $detector A callable or options array for the detector definition. + * @param \Closure|array $detector A Closure or options array for the detector definition. * @return void */ - public static function addDetector(string $name, $detector): void + public static function addDetector(string $name, Closure|array $detector): void { $name = strtolower($name); - if (is_callable($detector)) { + if ($detector instanceof Closure) { static::$_detectors[$name] = $detector; return; } + if (isset(static::$_detectors[$name], $detector['options'])) { - /** @psalm-suppress PossiblyInvalidArgument */ - $detector = Hash::merge(static::$_detectors[$name], $detector); + /** @var array $data */ + $data = static::$_detectors[$name]; + $detector = Hash::merge($data, $detector); } static::$_detectors[$name] = $detector; } @@ -778,7 +781,7 @@ protected function normalizeHeaderName(string $name): string { $name = str_replace('-', '_', strtoupper($name)); if (!in_array($name, ['CONTENT_LENGTH', 'CONTENT_TYPE'], true)) { - $name = 'HTTP_' . $name; + return 'HTTP_' . $name; } return $name; @@ -793,7 +796,7 @@ protected function normalizeHeaderName(string $name): string * While header names are not case-sensitive, getHeaders() will normalize * the headers. * - * @return array An associative array of headers and their values. + * @return array> An associative array of headers and their values. * @link https://www.php-fig.org/psr/psr-7/ This method is part of the PSR-7 server request interface. */ public function getHeaders(): array @@ -801,10 +804,10 @@ public function getHeaders(): array $headers = []; foreach ($this->_environment as $key => $value) { $name = null; - if (strpos($key, 'HTTP_') === 0) { + if (str_starts_with($key, 'HTTP_')) { $name = substr($key, 5); } - if (strpos($key, 'CONTENT_') === 0) { + if (str_starts_with($key, 'CONTENT_')) { $name = $key; } if ($name !== null) { @@ -824,7 +827,7 @@ public function getHeaders(): array * @return bool Whether the header is defined. * @link https://www.php-fig.org/psr/psr-7/ This method is part of the PSR-7 server request interface. */ - public function hasHeader($name): bool + public function hasHeader(string $name): bool { $name = $this->normalizeHeaderName($name); @@ -835,14 +838,14 @@ public function hasHeader($name): bool * Get a single header from the request. * * Return the header value as an array. If the header - * is not present an empty array will be returned. + * is not present, an empty array will be returned. * * @param string $name The header you want to get (case-insensitive) - * @return array An associative array of headers and their values. + * @return array An associative array of headers and their values. * If the header doesn't exist, an empty array will be returned. * @link https://www.php-fig.org/psr/psr-7/ This method is part of the PSR-7 server request interface. */ - public function getHeader($name): array + public function getHeader(string $name): array { $name = $this->normalizeHeaderName($name); if (isset($this->_environment[$name])) { @@ -859,7 +862,7 @@ public function getHeader($name): array * @return string Header values collapsed into a comma separated string. * @link https://www.php-fig.org/psr/psr-7/ This method is part of the PSR-7 server request interface. */ - public function getHeaderLine($name): string + public function getHeaderLine(string $name): string { $value = $this->getHeader($name); @@ -873,8 +876,9 @@ public function getHeaderLine($name): string * @param array|string $value The header value * @return static * @link https://www.php-fig.org/psr/psr-7/ This method is part of the PSR-7 server request interface. + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint */ - public function withHeader($name, $value) + public function withHeader(string $name, $value): static { $new = clone $this; $name = $this->normalizeHeaderName($name); @@ -893,8 +897,9 @@ public function withHeader($name, $value) * @param array|string $value The header value * @return static * @link https://www.php-fig.org/psr/psr-7/ This method is part of the PSR-7 server request interface. + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint */ - public function withAddedHeader($name, $value) + public function withAddedHeader(string $name, $value): static { $new = clone $this; $name = $this->normalizeHeaderName($name); @@ -915,7 +920,7 @@ public function withAddedHeader($name, $value) * @return static * @link https://www.php-fig.org/psr/psr-7/ This method is part of the PSR-7 server request interface. */ - public function withoutHeader($name) + public function withoutHeader(string $name): static { $new = clone $this; $name = $this->normalizeHeaderName($name); @@ -929,11 +934,11 @@ public function withoutHeader($name) * There are a few ways to specify a method. * * - If your client supports it you can use native HTTP methods. - * - You can set the HTTP-X-Method-Override header. + * - You can set the X-Http-Method-Override header. * - You can submit an input with the name `_method` * * Any of these 3 approaches can be used to set the HTTP method used - * by CakePHP internally, and will effect the result of this method. + * by CakePHP internally, and will affect the result of this method. * * @return string The name of the HTTP method used. * @link https://www.php-fig.org/psr/psr-7/ This method is part of the PSR-7 server request interface. @@ -950,17 +955,14 @@ public function getMethod(): string * @return static A new instance with the updated method. * @link https://www.php-fig.org/psr/psr-7/ This method is part of the PSR-7 server request interface. */ - public function withMethod($method) + public function withMethod(string $method): static { $new = clone $this; - if ( - !is_string($method) || - !preg_match('/^[!#$%&\'*+.^_`\|~0-9a-z-]+$/i', $method) - ) { + if (!preg_match('/^[!#$%&\'*+.^_`\|~0-9a-z-]+$/i', $method)) { throw new InvalidArgumentException(sprintf( - 'Unsupported HTTP method "%s" provided', - $method + 'Unsupported HTTP method `%s` provided.', + $method, )); } $new->_environment['REQUEST_METHOD'] = $method; @@ -1001,7 +1003,7 @@ public function getQueryParams(): array * @return static A new instance with the updated query string data. * @link https://www.php-fig.org/psr/psr-7/ This method is part of the PSR-7 server request interface. */ - public function withQueryParams(array $query) + public function withQueryParams(array $query): static { $new = clone $this; $new->query = $query; @@ -1042,12 +1044,12 @@ public function port(): ?string * * e.g. 'http', or 'https' * - * @return string|null The scheme used for the request. + * @return string The scheme used for the request. */ - public function scheme(): ?string + public function scheme(): string { if ($this->trustProxy && $this->getEnv('HTTP_X_FORWARDED_PROTO')) { - return $this->getEnv('HTTP_X_FORWARDED_PROTO'); + return (string)$this->getEnv('HTTP_X_FORWARDED_PROTO'); } return $this->getEnv('HTTPS') ? 'https' : 'http'; @@ -1063,7 +1065,7 @@ public function scheme(): ?string public function domain(int $tldLength = 1): string { $host = $this->host(); - if (empty($host)) { + if (!$host) { return ''; } @@ -1083,7 +1085,7 @@ public function domain(int $tldLength = 1): string public function subdomains(int $tldLength = 1): array { $host = $this->host(); - if (empty($host)) { + if (!$host) { return []; } @@ -1115,7 +1117,7 @@ public function subdomains(int $tldLength = 1): array * @return array|bool Either an array of all the types the client accepts or a boolean if they accept the * provided type. */ - public function accepts(?string $type = null) + public function accepts(?string $type = null): array|bool { $content = new ContentTypeNegotiation(); if ($type) { @@ -1130,21 +1132,6 @@ public function accepts(?string $type = null) return $accept; } - /** - * Parse the HTTP_ACCEPT header and return a sorted array with content types - * as the keys, and pref values as the values. - * - * Generally you want to use {@link \Cake\Http\ServerRequest::accepts()} to get a simple list - * of the accepted content types. - * - * @return array An array of `prefValue => [content/types]` - * @deprecated 4.4.0 Use `accepts()` or `ContentTypeNegotiation` class instead. - */ - public function parseAccept(): array - { - return (new ContentTypeNegotiation())->parseAccept($this); - } - /** * Get the languages accepted by the client, or check if a specific language is accepted. * @@ -1159,7 +1146,7 @@ public function parseAccept(): array * @param string|null $language The language to test. * @return array|bool If a $language is provided, a boolean. Otherwise, the array of accepted languages. */ - public function acceptLanguage(?string $language = null) + public function acceptLanguage(?string $language = null): array|bool { $content = new ContentTypeNegotiation(); if ($language !== null) { @@ -1183,10 +1170,10 @@ public function acceptLanguage(?string $language = null) * * @param string|null $name The name or dotted path to the query param or null to read all. * @param mixed $default The default value if the named parameter is not set, and $name is not null. - * @return array|string|null Query data. + * @return mixed Query data. * @see ServerRequest::getQueryParams() */ - public function getQuery(?string $name = null, $default = null) + public function getQuery(?string $name = null, mixed $default = null): mixed { if ($name === null) { return $this->query; @@ -1227,64 +1214,18 @@ public function getQuery(?string $name = null, $default = null) * @param mixed $default The default data. * @return mixed The value being read. */ - public function getData(?string $name = null, $default = null) + public function getData(?string $name = null, mixed $default = null): mixed { if ($name === null) { return $this->data; } - if (!is_array($this->data) && $name) { + if (!is_array($this->data)) { return $default; } - /** @psalm-suppress PossiblyNullArgument */ return Hash::get($this->data, $name, $default); } - /** - * Read data from `php://input`. Useful when interacting with XML or JSON - * request body content. - * - * Getting input with a decoding function: - * - * ``` - * $this->request->input('json_decode'); - * ``` - * - * Getting input using a decoding function, and additional params: - * - * ``` - * $this->request->input('Xml::build', ['return' => 'DOMDocument']); - * ``` - * - * Any additional parameters are applied to the callback in the order they are given. - * - * @deprecated 4.1.0 Use `(string)$request->getBody()` to get the raw PHP input - * as string; use `BodyParserMiddleware` to parse the request body so that it's - * available as array/object through `$request->getParsedBody()`. - * @param callable|null $callback A decoding callback that will convert the string data to another - * representation. Leave empty to access the raw input data. You can also - * supply additional parameters for the decoding callback using var args, see above. - * @param mixed ...$args The additional arguments - * @return mixed The decoded/processed request data. - */ - public function input(?callable $callback = null, ...$args) - { - deprecationWarning( - 'Use `(string)$request->getBody()` to get the raw PHP input as string; ' - . 'use `BodyParserMiddleware` to parse the request body so that it\'s available as array/object ' - . 'through $request->getParsedBody()' - ); - $this->stream->rewind(); - $input = $this->stream->getContents(); - if ($callback) { - array_unshift($args, $input); - - return $callback(...$args); - } - - return $input; - } - /** * Read cookie data from the request's cookie data. * @@ -1292,7 +1233,7 @@ public function input(?callable $callback = null, ...$args) * @param array|string|null $default The default value if the cookie is not set. * @return array|string|null Either the cookie value, or null if the value doesn't exist. */ - public function getCookie(string $key, $default = null) + public function getCookie(string $key, array|string|null $default = null): array|string|null { return Hash::get($this->cookies, $key, $default); } @@ -1324,7 +1265,7 @@ public function getCookieCollection(): CookieCollection * @param \Cake\Http\Cookie\CookieCollection $cookies The cookie collection * @return static */ - public function withCookieCollection(CookieCollection $cookies) + public function withCookieCollection(CookieCollection $cookies): static { $new = clone $this; $values = []; @@ -1352,7 +1293,7 @@ public function getCookieParams(): array * @param array $cookies The new cookie data to use. * @return static */ - public function withCookieParams(array $cookies) + public function withCookieParams(array $cookies): static { $new = clone $this; $new->cookies = $cookies; @@ -1371,7 +1312,7 @@ public function withCookieParams(array $cookies) * @return object|array|null The deserialized body parameters, if any. * These will typically be an array. */ - public function getParsedBody() + public function getParsedBody(): object|array|null { return $this->data; } @@ -1382,8 +1323,9 @@ public function getParsedBody() * @param object|array|null $data The deserialized body data. This will * typically be in an array or object. * @return static + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint */ - public function withParsedBody($data) + public function withParsedBody($data): static { $new = clone $this; $new->data = $data; @@ -1422,10 +1364,10 @@ public function getProtocolVersion(): string * @param string $version HTTP protocol version * @return static */ - public function withProtocolVersion($version) + public function withProtocolVersion(string $version): static { if (!preg_match('/^(1\.[01]|2)$/', $version)) { - throw new InvalidArgumentException("Unsupported protocol version '{$version}' provided"); + throw new InvalidArgumentException(sprintf('Unsupported protocol version `%s` provided.', $version)); } $new = clone $this; $new->protocol = $version; @@ -1449,7 +1391,15 @@ public function getEnv(string $key, ?string $default = null): ?string $this->_environment[$key] = env($key); } - return $this->_environment[$key] !== null ? (string)$this->_environment[$key] : $default; + if ($this->_environment[$key] === null) { + return $default; + } + + if (is_array($this->_environment[$key])) { + return implode(', ', $this->_environment[$key]); + } + + return (string)$this->_environment[$key]; } /** @@ -1462,7 +1412,7 @@ public function getEnv(string $key, ?string $default = null): ?string * @param string $value Value to set * @return static */ - public function withEnv(string $key, string $value) + public function withEnv(string $key, string $value): static { $new = clone $this; $new->_environment[$key] = $value; @@ -1488,7 +1438,7 @@ public function withEnv(string $key, string $value) * @return true * @throws \Cake\Http\Exception\MethodNotAllowedException */ - public function allowMethod($methods): bool + public function allowMethod(array|string $methods): bool { $methods = (array)$methods; foreach ($methods as $method) { @@ -1508,13 +1458,13 @@ public function allowMethod($methods): bool * Returns an updated request object. This method returns * a *new* request object and does not mutate the request in-place. * - * Use `withParsedBody()` if you need to replace the all request data. + * Use `withParsedBody()` if you need to replace all the request data. * * @param string $name The dot separated path to insert $value at. * @param mixed $value The value to insert into the request data. * @return static */ - public function withData(string $name, $value) + public function withData(string $name, mixed $value): static { $copy = clone $this; @@ -1534,7 +1484,7 @@ public function withData(string $name, $value) * @param string $name The dot separated path to remove. * @return static */ - public function withoutData(string $name) + public function withoutData(string $name): static { $copy = clone $this; @@ -1555,7 +1505,7 @@ public function withoutData(string $name) * @param mixed $value The value to insert into the the request parameters. * @return static */ - public function withParam(string $name, $value) + public function withParam(string $name, mixed $value): static { $copy = clone $this; $copy->params = Hash::insert($copy->params, $name, $value); @@ -1570,7 +1520,7 @@ public function withParam(string $name, $value) * @param mixed $default The default value if `$name` is not set. Default `null`. * @return mixed */ - public function getParam(string $name, $default = null) + public function getParam(string $name, mixed $default = null): mixed { return Hash::get($this->params, $name, $default); } @@ -1582,7 +1532,7 @@ public function getParam(string $name, $default = null) * @param mixed $value The value of the attribute. * @return static */ - public function withAttribute($name, $value) + public function withAttribute(string $name, mixed $value): static { $new = clone $this; if (in_array($name, $this->emulatedAttributes, true)) { @@ -1601,12 +1551,12 @@ public function withAttribute($name, $value) * @return static * @throws \InvalidArgumentException */ - public function withoutAttribute($name) + public function withoutAttribute(string $name): static { $new = clone $this; if (in_array($name, $this->emulatedAttributes, true)) { throw new InvalidArgumentException( - "You cannot unset '$name'. It is a required CakePHP attribute." + "You cannot unset '{$name}'. It is a required CakePHP attribute.", ); } unset($new->attributes[$name]); @@ -1618,10 +1568,10 @@ public function withoutAttribute($name) * Read an attribute from the request, or get the default * * @param string $name The attribute name. - * @param mixed|null $default The default value if the attribute has not been set. + * @param mixed $default The default value if the attribute has not been set. * @return mixed */ - public function getAttribute($name, $default = null) + public function getAttribute(string $name, mixed $default = null): mixed { if (in_array($name, $this->emulatedAttributes, true)) { if ($name === 'here') { @@ -1690,7 +1640,7 @@ public function getUploadedFiles(): array * @return static * @throws \InvalidArgumentException when $files contains an invalid object. */ - public function withUploadedFiles(array $uploadedFiles) + public function withUploadedFiles(array $uploadedFiles): static { $this->validateUploadedFiles($uploadedFiles, ''); $new = clone $this; @@ -1716,7 +1666,7 @@ protected function validateUploadedFiles(array $uploadedFiles, string $path): vo } if (!$file instanceof UploadedFileInterface) { - throw new InvalidArgumentException("Invalid file at '{$path}{$key}'"); + throw new InvalidArgumentException(sprintf('Invalid file at `%s%s`.', $path, $key)); } } } @@ -1737,7 +1687,7 @@ public function getBody(): StreamInterface * @param \Psr\Http\Message\StreamInterface $body The new request body * @return static */ - public function withBody(StreamInterface $body) + public function withBody(StreamInterface $body): static { $new = clone $this; $new->stream = $body; @@ -1766,7 +1716,7 @@ public function getUri(): UriInterface * @param bool $preserveHost Whether the host should be retained. * @return static */ - public function withUri(UriInterface $uri, $preserveHost = false) + public function withUri(UriInterface $uri, bool $preserveHost = false): static { $new = clone $this; $new->uri = $uri; @@ -1800,7 +1750,7 @@ public function withUri(UriInterface $uri, $preserveHost = false) * @param string $requestTarget The request target. * @return static */ - public function withRequestTarget($requestTarget) + public function withRequestTarget(string $requestTarget): static { $new = clone $this; $new->requestTarget = $requestTarget; @@ -1829,8 +1779,8 @@ public function getRequestTarget(): string $target .= '?' . $this->uri->getQuery(); } - if (empty($target)) { - $target = '/'; + if (!$target) { + return '/'; } return $target; diff --git a/app/vendor/cakephp/cakephp/src/Http/ServerRequestFactory.php b/app/vendor/cakephp/cakephp/src/Http/ServerRequestFactory.php index 836c3c5b0..47df6fb03 100644 --- a/app/vendor/cakephp/cakephp/src/Http/ServerRequestFactory.php +++ b/app/vendor/cakephp/cakephp/src/Http/ServerRequestFactory.php @@ -17,13 +17,9 @@ namespace Cake\Http; use Cake\Core\Configure; -use Cake\Http\Uri as CakeUri; use Cake\Utility\Hash; use Psr\Http\Message\ServerRequestFactoryInterface; use Psr\Http\Message\ServerRequestInterface; -use Psr\Http\Message\UriInterface; -use function Laminas\Diactoros\marshalHeadersFromSapi; -use function Laminas\Diactoros\marshalUriFromSapi; use function Laminas\Diactoros\normalizeServer; use function Laminas\Diactoros\normalizeUploadedFiles; @@ -34,7 +30,7 @@ * attributes. Furthermore the Uri's path is corrected to only contain the * 'virtual' path for the request. */ -abstract class ServerRequestFactory implements ServerRequestFactoryInterface +class ServerRequestFactory implements ServerRequestFactoryInterface { /** * Create a request from the supplied superglobal values. @@ -55,21 +51,10 @@ public static function fromGlobals( ?array $query = null, ?array $parsedBody = null, ?array $cookies = null, - ?array $files = null + ?array $files = null, ): ServerRequest { - $server = normalizeServer($server ?: $_SERVER); - $uri = static::createUri($server); - - $webroot = ''; - $base = ''; - if ($uri instanceof CakeUri) { - // Unwrap our shim for base and webroot. - // For 5.x we should change the interface on createUri() to return a - // tuple of [$uri, $base, $webroot] and remove the wrapper. - $webroot = $uri->getWebroot(); - $base = $uri->getBase(); - $uri->getUri(); - } + $server = normalizeServer($server ?? $_SERVER); + ['uri' => $uri, 'base' => $base, 'webroot' => $webroot] = UriFactory::marshalUriAndBaseFromSapi($server); $sessionConfig = (array)Configure::read('Session') + [ 'defaults' => 'php', @@ -80,8 +65,8 @@ public static function fromGlobals( $request = new ServerRequest([ 'environment' => $server, 'uri' => $uri, - 'cookies' => $cookies ?: $_COOKIE, - 'query' => $query ?: $_GET, + 'cookies' => $cookies ?? $_COOKIE, + 'query' => $query ?? $_GET, 'webroot' => $webroot, 'base' => $base, 'session' => $session, @@ -91,7 +76,7 @@ public static function fromGlobals( $request = static::marshalBodyAndRequestMethod($parsedBody ?? $_POST, $request); // This is required as `ServerRequest::scheme()` ignores the value of // `HTTP_X_FORWARDED_PROTO` unless `trustProxy` is enabled, while the - // `Uri` instance intially created always takes values of `HTTP_X_FORWARDED_PROTO` + // `Uri` instance initially created always takes values of `HTTP_X_FORWARDED_PROTO` // into account. $uri = $request->getUri()->withScheme($request->scheme()); $request = $request->withUri($uri, true); @@ -118,7 +103,7 @@ protected static function marshalBodyAndRequestMethod(array $parsedBody, ServerR if ( in_array($method, ['PUT', 'DELETE', 'PATCH'], true) && - strpos((string)$request->contentType(), 'application/x-www-form-urlencoded') === 0 + str_starts_with((string)$request->contentType(), 'application/x-www-form-urlencoded') ) { $data = (string)$request->getBody(); parse_str($data, $parsedBody); @@ -162,26 +147,7 @@ protected static function marshalFiles(array $files, ServerRequest $request): Se return $request; } - if (Configure::read('App.uploadedFilesAsObjects', true)) { - $parsedBody = Hash::merge($parsedBody, $files); - } else { - // Make a flat map that can be inserted into body for BC. - $fileMap = Hash::flatten($files); - foreach ($fileMap as $key => $file) { - $error = $file->getError(); - $tmpName = ''; - if ($error === UPLOAD_ERR_OK) { - $tmpName = $file->getStream()->getMetadata('uri'); - } - $parsedBody = Hash::insert($parsedBody, (string)$key, [ - 'tmp_name' => $tmpName, - 'error' => $error, - 'name' => $file->getClientFilename(), - 'type' => $file->getClientMediaType(), - 'size' => $file->getSize(), - ]); - } - } + $parsedBody = Hash::merge($parsedBody, $files); return $request->withParsedBody($parsedBody); } @@ -200,159 +166,18 @@ protected static function marshalFiles(array $files, ServerRequest $request): Se * @param array $serverParams Array of SAPI parameters with which to seed * the generated request instance. * @return \Psr\Http\Message\ServerRequestInterface + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint */ public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface { $serverParams['REQUEST_METHOD'] = $method; $options = ['environment' => $serverParams]; - if ($uri instanceof UriInterface) { - $options['uri'] = $uri; - } else { - $options['url'] = $uri; + if (is_string($uri)) { + $uri = (new UriFactory())->createUri($uri); } + $options['uri'] = $uri; return new ServerRequest($options); } - - /** - * Create a new Uri instance from the provided server data. - * - * @param array $server Array of server data to build the Uri from. - * $_SERVER will be added into the $server parameter. - * @return \Psr\Http\Message\UriInterface New instance. - */ - public static function createUri(array $server = []): UriInterface - { - $server += $_SERVER; - $server = normalizeServer($server); - $headers = marshalHeadersFromSapi($server); - - return static::marshalUriFromSapi($server, $headers); - } - - /** - * Build a UriInterface object. - * - * Add in some CakePHP specific logic/properties that help - * preserve backwards compatibility. - * - * @param array $server The server parameters. - * @param array $headers The normalized headers - * @return \Cake\Http\Uri A constructed Uri - */ - protected static function marshalUriFromSapi(array $server, array $headers): UriInterface - { - /** @psalm-suppress DeprecatedFunction */ - $uri = marshalUriFromSapi($server, $headers); - [$base, $webroot] = static::getBase($uri, $server); - - $uri = static::updatePath($base, $uri); - - if (!$uri->getHost()) { - $uri = $uri->withHost('localhost'); - } - - return new CakeUri($uri, $base, $webroot); - } - - /** - * Updates the request URI to remove the base directory. - * - * @param string $base The base path to remove. - * @param \Psr\Http\Message\UriInterface $uri The uri to update. - * @return \Psr\Http\Message\UriInterface The modified Uri instance. - */ - protected static function updatePath(string $base, UriInterface $uri): UriInterface - { - $path = $uri->getPath(); - if ($base !== '' && strpos($path, $base) === 0) { - $path = substr($path, strlen($base)); - } - if ($path === '/index.php' && $uri->getQuery()) { - $path = $uri->getQuery(); - } - if (empty($path) || $path === '/' || $path === '//' || $path === '/index.php') { - $path = '/'; - } - // Check for $webroot/index.php at the start and end of the path. - $search = ''; - if ($path[0] === '/') { - $search .= '/'; - } - $search .= (Configure::read('App.webroot') ?: 'webroot') . '/index.php'; - if (strpos($path, $search) === 0) { - $path = substr($path, strlen($search)); - } elseif (substr($path, -strlen($search)) === $search) { - $path = '/'; - } - if (!$path) { - $path = '/'; - } - - return $uri->withPath($path); - } - - /** - * Calculate the base directory and webroot directory. - * - * @param \Psr\Http\Message\UriInterface $uri The Uri instance. - * @param array $server The SERVER data to use. - * @return array An array containing the [baseDir, webroot] - */ - protected static function getBase(UriInterface $uri, array $server): array - { - $config = (array)Configure::read('App') + [ - 'base' => null, - 'webroot' => null, - 'baseUrl' => null, - ]; - $base = $config['base']; - $baseUrl = $config['baseUrl']; - $webroot = $config['webroot']; - - if ($base !== false && $base !== null) { - return [$base, $base . '/']; - } - - if (!$baseUrl) { - $base = dirname(Hash::get($server, 'PHP_SELF')); - // Clean up additional / which cause following code to fail.. - $base = preg_replace('#/+#', '/', $base); - - $indexPos = strpos($base, '/index.php'); - if ($indexPos !== false) { - $base = substr($base, 0, $indexPos); - } - if ($webroot === basename($base)) { - $base = dirname($base); - } - - if ($base === DIRECTORY_SEPARATOR || $base === '.') { - $base = ''; - } - $base = implode('/', array_map('rawurlencode', explode('/', $base))); - - return [$base, $base . '/']; - } - - $file = '/' . basename($baseUrl); - $base = dirname($baseUrl); - - if ($base === DIRECTORY_SEPARATOR || $base === '.') { - $base = ''; - } - $webrootDir = $base . '/'; - - $docRoot = Hash::get($server, 'DOCUMENT_ROOT'); - $docRootContainsWebroot = strpos($docRoot, $webroot); - - if (!empty($base) || !$docRootContainsWebroot) { - if (strpos($webrootDir, '/' . $webroot . '/') === false) { - $webrootDir .= $webroot . '/'; - } - } - - return [$base . $file, $webrootDir]; - } } diff --git a/app/vendor/cakephp/cakephp/src/Http/Session.php b/app/vendor/cakephp/cakephp/src/Http/Session.php index c3cf9ae07..cdaec4093 100644 --- a/app/vendor/cakephp/cakephp/src/Http/Session.php +++ b/app/vendor/cakephp/cakephp/src/Http/Session.php @@ -21,14 +21,14 @@ use Cake\Error\Debugger; use Cake\Utility\Hash; use InvalidArgumentException; -use RuntimeException; use SessionHandlerInterface; use function Cake\Core\env; +use const PHP_SESSION_ACTIVE; /** * This class is a wrapper for the native PHP session functions. It provides - * several defaults for the most common session configuration - * via external handlers and helps with using session in CLI without any warnings. + * several presets for the most common session configuration + * via external handlers and helps with using sessions in CLI without any warnings. * * Sessions can be created from the defaults using `Session::create()` or you can get * an instance of a new session by just instantiating this class and passing the complete @@ -43,37 +43,37 @@ class Session /** * The Session handler instance used as an engine for persisting the session data. * - * @var \SessionHandlerInterface + * @var \SessionHandlerInterface|null */ - protected $_engine; + protected ?SessionHandlerInterface $_engine = null; /** * Indicates whether the sessions has already started * * @var bool */ - protected $_started; + protected bool $_started = false; /** * The time in seconds the session will be valid for * * @var int */ - protected $_lifetime; + protected int $_lifetime = 0; /** * Whether this session is running under a CLI environment * * @var bool */ - protected $_isCLI = false; + protected bool $_isCLI = false; /** * Info about where the headers were sent. * * @var array{filename: string, line: int}|null */ - protected $headerSentInfo = null; + protected ?array $headerSentInfo = null; /** * Returns a new instance of a session after building a configuration bundle for it. @@ -96,19 +96,17 @@ class Session * - defaults: either 'php', 'database', 'cache' or 'cake' as explained above. * - handler: An array containing the handler configuration * - ini: A list of php.ini directives to set before the session starts. - * - timeout: The time in minutes the session should stay active + * - timeout: The 'idle timeout' in minutes. If not request is received for `timeout` + * minutes the session will be regenerated. * * @param array $sessionConfig Session config. * @return static * @see \Cake\Http\Session::__construct() */ - public static function create(array $sessionConfig = []) + public static function create(array $sessionConfig = []): static { if (isset($sessionConfig['defaults'])) { - $defaults = static::_defaultConfig($sessionConfig['defaults']); - if ($defaults) { - $sessionConfig = Hash::merge($defaults, $sessionConfig); - } + $sessionConfig = Hash::merge(static::_defaultConfig($sessionConfig['defaults']), $sessionConfig); } if ( @@ -138,14 +136,14 @@ public static function create(array $sessionConfig = []) } /** - * Get one of the prebaked default session configurations. + * Get one of the pre-baked default session configurations. * * @param string $name Config name. - * @return array|false + * @return array + * @throws \Cake\Core\Exception\CakeException When an invalid name is used. */ - protected static function _defaultConfig(string $name) + protected static function _defaultConfig(string $name): array { - $tmp = defined('TMP') ? TMP : sys_get_temp_dir() . DIRECTORY_SEPARATOR; $defaults = [ 'php' => [ 'ini' => [ @@ -157,7 +155,8 @@ protected static function _defaultConfig(string $name) 'session.use_trans_sid' => 0, 'session.serialize_handler' => 'php', 'session.use_cookies' => 1, - 'session.save_path' => $tmp . 'sessions', + 'session.save_path' => (defined('TMP') ? TMP : sys_get_temp_dir() . DIRECTORY_SEPARATOR) + . 'sessions', 'session.save_handler' => 'files', ], ], @@ -183,18 +182,19 @@ protected static function _defaultConfig(string $name) ], ]; - if (isset($defaults[$name])) { - if ( - PHP_VERSION_ID >= 70300 - && ($name !== 'php' || empty(ini_get('session.cookie_samesite'))) - ) { - $defaults['php']['ini']['session.cookie_samesite'] = 'Lax'; - } + if (!isset($defaults[$name])) { + throw new CakeException(sprintf( + 'Invalid session defaults name `%s`. Valid values are: %s.', + $name, + implode(', ', array_keys($defaults)), + )); + } - return $defaults[$name]; + if ($name !== 'php' || empty(ini_get('session.cookie_samesite'))) { + $defaults['php']['ini']['session.cookie_samesite'] = 'Lax'; } - return false; + return $defaults[$name]; } /** @@ -202,7 +202,8 @@ protected static function _defaultConfig(string $name) * * ### Configuration: * - * - timeout: The time in minutes the session should be valid for. + * - timeout: The time in minutes that a session can be idle and remain valid. + * If set to 0, no server side timeout will be applied. * - cookiePath: The url path for which session cookie is set. Maps to the * `session.cookie_path` php.ini config. Defaults to base path of app. * - ini: A list of php.ini directives to change before the session start. @@ -222,9 +223,11 @@ public function __construct(array $config = []) 'handler' => [], ]; - if ($config['timeout']) { - $config['ini']['session.gc_maxlifetime'] = 60 * $config['timeout']; + $lifetime = (int)ini_get('session.gc_maxlifetime'); + if ($config['timeout'] !== null) { + $lifetime = (int)$config['timeout'] * 60; } + $this->configureSessionLifetime($lifetime); if ($config['cookie']) { $config['ini']['session.name'] = $config['cookie']; @@ -243,7 +246,6 @@ public function __construct(array $config = []) $this->engine($class, $config['handler']); } - $this->_lifetime = (int)ini_get('session.gc_maxlifetime'); $this->_isCLI = (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg'); session_register_shutdown(); } @@ -265,8 +267,10 @@ public function __construct(array $config = []) * @return \SessionHandlerInterface|null * @throws \InvalidArgumentException */ - public function engine($class = null, array $options = []): ?SessionHandlerInterface - { + public function engine( + SessionHandlerInterface|string|null $class = null, + array $options = [], + ): ?SessionHandlerInterface { if ($class === null) { return $this->_engine; } @@ -278,7 +282,7 @@ public function engine($class = null, array $options = []): ?SessionHandlerInter $className = App::className($class, 'Http/Session'); if ($className === null) { throw new InvalidArgumentException( - sprintf('The class "%s" does not exist and cannot be used as a session engine', $class) + sprintf('The class `%s` does not exist and cannot be used as a session engine', $class), ); } @@ -293,7 +297,7 @@ public function engine($class = null, array $options = []): ?SessionHandlerInter */ protected function setEngine(SessionHandlerInterface $handler): SessionHandlerInterface { - if (!headers_sent() && session_status() !== \PHP_SESSION_ACTIVE) { + if (!headers_sent() && session_status() !== PHP_SESSION_ACTIVE) { session_set_save_handler($handler, false); } @@ -312,18 +316,18 @@ protected function setEngine(SessionHandlerInterface $handler): SessionHandlerIn * * @param array $options Ini options to set. * @return void - * @throws \RuntimeException if any directive could not be set + * @throws \Cake\Core\Exception\CakeException if any directive could not be set */ public function options(array $options): void { - if (session_status() === \PHP_SESSION_ACTIVE || headers_sent()) { + if (session_status() === PHP_SESSION_ACTIVE || headers_sent()) { return; } foreach ($options as $setting => $value) { if (ini_set($setting, (string)$value) === false) { - throw new RuntimeException( - sprintf('Unable to configure the session, setting %s failed.', $setting) + throw new CakeException( + sprintf('Unable to configure the session, setting %s failed.', $setting), ); } } @@ -333,7 +337,7 @@ public function options(array $options): void * Starts the Session. * * @return bool True if session was started - * @throws \RuntimeException if the session was already started + * @throws \Cake\Core\Exception\CakeException if the session was already started */ public function start(): bool { @@ -348,11 +352,11 @@ public function start(): bool return $this->_started = true; } - if (session_status() === \PHP_SESSION_ACTIVE) { - throw new RuntimeException('Session was already started'); + if (session_status() === PHP_SESSION_ACTIVE) { + throw new CakeException('Session was already started'); } - - $filename = $line = null; + $filename = null; + $line = null; if (ini_get('session.use_cookies') && headers_sent($filename, $line)) { $this->headerSentInfo = ['filename' => $filename, 'line' => $line]; @@ -360,7 +364,7 @@ public function start(): bool } if (!session_start()) { - throw new RuntimeException('Could not start the session'); + throw new CakeException('Could not start the session'); } $this->_started = true; @@ -392,7 +396,7 @@ public function close(): bool } if (!session_write_close()) { - throw new RuntimeException('Could not close the session'); + throw new CakeException('Could not close the session'); } $this->_started = false; @@ -407,7 +411,7 @@ public function close(): bool */ public function started(): bool { - return $this->_started || session_status() === \PHP_SESSION_ACTIVE; + return $this->_started || session_status() === PHP_SESSION_ACTIVE; } /** @@ -441,7 +445,7 @@ public function check(?string $name = null): bool * @return mixed|null The value of the session variable, or default value if a session * is not available, can't be started, or provided $name is not found in the session. */ - public function read(?string $name = null, $default = null) + public function read(?string $name = null, mixed $default = null): mixed { if ($this->_hasSession() && !$this->started()) { $this->start(); @@ -462,13 +466,13 @@ public function read(?string $name = null, $default = null) * Returns given session variable, or throws Exception if not found. * * @param string $name The name of the session variable (or a path as sent to Hash.extract) - * @throws \RuntimeException + * @throws \Cake\Core\Exception\CakeException * @return mixed|null */ - public function readOrFail(string $name) + public function readOrFail(string $name): mixed { if (!$this->check($name)) { - throw new RuntimeException(sprintf('Expected session key "%s" not found.', $name)); + throw new CakeException(sprintf('Expected session key `%s` not found.', $name)); } return $this->read($name); @@ -481,14 +485,13 @@ public function readOrFail(string $name) * @return mixed|null The value of the session variable, null if session not available, * session not started, or provided name not found in the session. */ - public function consume(string $name) + public function consume(string $name): mixed { - if (empty($name)) { + if (!$name) { return null; } $value = $this->read($name); if ($value !== null) { - /** @psalm-suppress InvalidScalarArgument */ $this->_overwrite($_SESSION, Hash::remove($_SESSION, $name)); } @@ -502,7 +505,7 @@ public function consume(string $name) * @param mixed $value Value to write * @return void */ - public function write($name, $value = null): void + public function write(array|string $name, mixed $value = null): void { $started = $this->started() || $this->start(); if (!$started) { @@ -511,7 +514,7 @@ public function write($name, $value = null): void $message .= sprintf( ', headers already sent in file `%s` on line `%s`', Debugger::trimPath($this->headerSentInfo['filename']), - $this->headerSentInfo['line'] + $this->headerSentInfo['line'], ); } @@ -531,18 +534,18 @@ public function write($name, $value = null): void } /** - * Returns the session id. + * Returns the session ID. * Calling this method will not auto start the session. You might have to manually * assert a started session. * - * Passing an id into it, you can also replace the session id if the session + * Passing an ID into it, you can also replace the session ID if the session * has not already been started. * Note that depending on the session handler, not all characters are allowed - * within the session id. For example, the file session handler only allows + * within the session ID. For example, the file session handler only allows * characters in the range a-z A-Z 0-9 , (comma) and - (minus). * - * @param string|null $id Id to replace the current session id - * @return string Session id + * @param string|null $id ID to replace the current session ID. + * @return string Session ID */ public function id(?string $id = null): string { @@ -550,7 +553,7 @@ public function id(?string $id = null): string session_id($id); } - return session_id(); + return (string)session_id(); } /** @@ -562,7 +565,6 @@ public function id(?string $id = null): string public function delete(string $name): void { if ($this->check($name)) { - /** @psalm-suppress InvalidScalarArgument */ $this->_overwrite($_SESSION, Hash::remove($_SESSION, $name)); } } @@ -598,7 +600,7 @@ public function destroy(): void $this->start(); } - if (!$this->_isCLI && session_status() === \PHP_SESSION_ACTIVE) { + if (!$this->_isCLI && session_status() === PHP_SESSION_ACTIVE) { session_destroy(); } @@ -648,14 +650,12 @@ public function renew(): void $this->start(); $params = session_get_cookie_params(); + unset($params['lifetime']); + $params['expires'] = time() - 42000; setcookie( - session_name(), + (string)session_name(), '', - time() - 42000, - $params['path'], - $params['domain'], - $params['secure'], - $params['httponly'] + $params, ); if (session_id() !== '') { @@ -683,4 +683,39 @@ protected function _timedOut(): bool return $result; } + + /** + * Set the session timeout period. + * + * If set to `0`, no server side timeout will be applied. + * + * @param int $lifetime in seconds + * @return void + * @throws \Cake\Core\Exception\CakeException + */ + public function setSessionLifetime(int $lifetime): void + { + if ($this->started()) { + throw new CakeException("Can't modify session lifetime after session has already been started."); + } + + $this->configureSessionLifetime($lifetime); + } + + /** + * Configure session lifetime + * + * @param int $lifetime + * @return void + */ + protected function configureSessionLifetime(int $lifetime): void + { + if ($lifetime !== 0) { + $this->options([ + 'session.gc_maxlifetime' => $lifetime, + ]); + } + + $this->_lifetime = $lifetime; + } } diff --git a/app/vendor/cakephp/cakephp/src/Http/Session/CacheSession.php b/app/vendor/cakephp/cakephp/src/Http/Session/CacheSession.php index 2959521c5..045ad8e50 100644 --- a/app/vendor/cakephp/cakephp/src/Http/Session/CacheSession.php +++ b/app/vendor/cakephp/cakephp/src/Http/Session/CacheSession.php @@ -34,7 +34,7 @@ class CacheSession implements SessionHandlerInterface * * @var array */ - protected $_options = []; + protected array $_options = []; /** * Constructor. @@ -59,7 +59,7 @@ public function __construct(array $config = []) * @param string $name The session name. * @return bool Success */ - public function open($path, $name): bool + public function open(string $path, string $name): bool { return true; } @@ -80,16 +80,9 @@ public function close(): bool * @param string $id ID that uniquely identifies session in cache. * @return string|false Session data or false if it does not exist. */ - #[\ReturnTypeWillChange] - public function read($id) + public function read(string $id): string|false { - $value = Cache::read($id, $this->_options['config']); - - if ($value === null) { - return ''; - } - - return $value; + return Cache::read($id, $this->_options['config']) ?? ''; } /** @@ -99,7 +92,7 @@ public function read($id) * @param string $data The data to be saved. * @return bool True for successful write, false otherwise. */ - public function write($id, $data): bool + public function write(string $id, string $data): bool { if (!$id) { return false; @@ -114,7 +107,7 @@ public function write($id, $data): bool * @param string $id ID that uniquely identifies session in cache. * @return bool Always true. */ - public function destroy($id): bool + public function destroy(string $id): bool { Cache::delete($id, $this->_options['config']); @@ -124,11 +117,10 @@ public function destroy($id): bool /** * No-op method. Always returns 0 since cache engine don't have garbage collection. * - * @param int $maxlifetime Sessions that have not updated for the last maxlifetime seconds will be removed. + * @param int $max_lifetime Sessions that have not updated for the last maxlifetime seconds will be removed. * @return int|false */ - #[\ReturnTypeWillChange] - public function gc($maxlifetime) + public function gc(int $max_lifetime): int|false { return 0; } diff --git a/app/vendor/cakephp/cakephp/src/Http/Session/DatabaseSession.php b/app/vendor/cakephp/cakephp/src/Http/Session/DatabaseSession.php index 76a39a634..d0b51ad31 100644 --- a/app/vendor/cakephp/cakephp/src/Http/Session/DatabaseSession.php +++ b/app/vendor/cakephp/cakephp/src/Http/Session/DatabaseSession.php @@ -19,6 +19,7 @@ namespace Cake\Http\Session; use Cake\ORM\Locator\LocatorAwareTrait; +use Cake\ORM\Table; use SessionHandlerInterface; /** @@ -33,14 +34,14 @@ class DatabaseSession implements SessionHandlerInterface * * @var \Cake\ORM\Table */ - protected $_table; + protected Table $_table; /** * Number of seconds to mark the session as expired * * @var int */ - protected $_timeout; + protected int $_timeout; /** * Constructor. Looks at Session configuration information and @@ -88,7 +89,7 @@ public function setTimeout(int $timeout) * @param string $name The session name. * @return bool Success */ - public function open($path, $name): bool + public function open(string $path, string $name): bool { return true; } @@ -109,11 +110,10 @@ public function close(): bool * @param string $id ID that uniquely identifies session in database. * @return string|false Session data or false if it does not exist. */ - #[\ReturnTypeWillChange] - public function read($id) + public function read(string $id): string|false { - /** @var string $pkField */ $pkField = $this->_table->getPrimaryKey(); + assert(is_string($pkField)); $result = $this->_table ->find('all') ->select(['data']) @@ -121,7 +121,7 @@ public function read($id) ->disableHydration() ->first(); - if (empty($result)) { + if (!$result) { return ''; } @@ -145,7 +145,7 @@ public function read($id) * @param string $data The data to be saved. * @return bool True for successful write, false otherwise. */ - public function write($id, $data): bool + public function write(string $id, string $data): bool { if (!$id) { return false; @@ -168,7 +168,7 @@ public function write($id, $data): bool * @param string $id ID that uniquely identifies session in database. * @return bool True for successful delete, false otherwise. */ - public function destroy($id): bool + public function destroy(string $id): bool { /** @var string $pkField */ $pkField = $this->_table->getPrimaryKey(); @@ -180,11 +180,10 @@ public function destroy($id): bool /** * Helper function called on gc for database sessions. * - * @param int $maxlifetime Sessions that have not updated for the last maxlifetime seconds will be removed. + * @param int $max_lifetime Sessions that have not updated for the last maxlifetime seconds will be removed. * @return int|false The number of deleted sessions on success, or false on failure. */ - #[\ReturnTypeWillChange] - public function gc($maxlifetime) + public function gc(int $max_lifetime): int|false { return $this->_table->deleteAll(['expires <' => time()]); } diff --git a/app/vendor/cakephp/cakephp/src/Http/StreamFactory.php b/app/vendor/cakephp/cakephp/src/Http/StreamFactory.php new file mode 100644 index 000000000..54fac023e --- /dev/null +++ b/app/vendor/cakephp/cakephp/src/Http/StreamFactory.php @@ -0,0 +1,79 @@ +createStreamFromResource($resource); + } + + /** + * Create a stream from an existing file. + * + * The file MUST be opened using the given mode, which may be any mode + * supported by the `fopen` function. + * + * The `$filename` MAY be any string supported by `fopen()`. + * + * @param string $filename The filename or stream URI to use as basis of stream. + * @param string $mode The mode with which to open the underlying filename/stream. + * @throws \RuntimeException If the file cannot be opened. + * @throws \InvalidArgumentException If the mode is invalid. + */ + public function createStreamFromFile(string $filename, string $mode = 'r'): StreamInterface + { + if (!is_readable($filename)) { + throw new RuntimeException(sprintf('Cannot read file `%s`', $filename)); + } + + return new Stream($filename, $mode); + } + + /** + * Create a new stream from an existing resource. + * + * The stream MUST be readable and may be writable. + * + * @param resource $resource The PHP resource to use as the basis for the stream. + */ + public function createStreamFromResource($resource): StreamInterface + { + return new Stream($resource); + } +} diff --git a/app/vendor/cakephp/cakephp/src/Http/TestSuite/HttpClientTrait.php b/app/vendor/cakephp/cakephp/src/Http/TestSuite/HttpClientTrait.php index d4281d2d0..7c2d101ed 100644 --- a/app/vendor/cakephp/cakephp/src/Http/TestSuite/HttpClientTrait.php +++ b/app/vendor/cakephp/cakephp/src/Http/TestSuite/HttpClientTrait.php @@ -18,6 +18,7 @@ use Cake\Http\Client; use Cake\Http\Client\Response; +use PHPUnit\Framework\Attributes\After; /** * Define mock responses and have mocks automatically cleared. @@ -27,9 +28,9 @@ trait HttpClientTrait /** * Resets mocked responses * - * @after * @return void */ + #[After] public function cleanupMockResponses(): void { Client::clearMockResponses(); @@ -118,7 +119,7 @@ public function mockClientDelete(string $url, Response $response, array $options // phpcs:disable class_alias( - 'Cake\Http\TestSuite\HttpClientTrait', + 'Cake\Http\TestSuite\HttpClientTrait', 'Cake\TestSuite\HttpClientTrait' ); // phpcs:enable diff --git a/app/vendor/cakephp/cakephp/src/Http/UploadedFileFactory.php b/app/vendor/cakephp/cakephp/src/Http/UploadedFileFactory.php new file mode 100644 index 000000000..907e4bdba --- /dev/null +++ b/app/vendor/cakephp/cakephp/src/Http/UploadedFileFactory.php @@ -0,0 +1,56 @@ +getSize() ?? 0; + + return new UploadedFile($stream, $size, $error, $clientFilename, $clientMediaType); + } +} diff --git a/app/vendor/cakephp/cakephp/src/Http/Uri.php b/app/vendor/cakephp/cakephp/src/Http/Uri.php deleted file mode 100644 index f929836d9..000000000 --- a/app/vendor/cakephp/cakephp/src/Http/Uri.php +++ /dev/null @@ -1,255 +0,0 @@ -uri = $uri; - $this->base = $base; - $this->webroot = $webroot; - } - - /** - * Backwards compatibility shim for previously dynamic properties. - * - * @param string $name The attribute to read. - * @return mixed - */ - public function __get(string $name) - { - if ($name === 'base' || $name === 'webroot') { - return $this->{$name}; - } - throw new UnexpectedValueException("Undefined property via __get('{$name}')"); - } - - /** - * Get the decorated URI - * - * @return \Psr\Http\Message\UriInterface - */ - public function getUri(): UriInterface - { - return $this->uri; - } - - /** - * Get the application base path. - * - * @return string - */ - public function getBase(): string - { - return $this->base; - } - - /** - * Get the application webroot path. - * - * @return string - */ - public function getWebroot(): string - { - return $this->webroot; - } - - /** - * @inheritDoc - */ - public function getScheme() - { - return $this->uri->getScheme(); - } - - /** - * @inheritDoc - */ - public function getAuthority() - { - return $this->uri->getAuthority(); - } - - /** - * @inheritDoc - */ - public function getUserInfo() - { - return $this->uri->getUserInfo(); - } - - /** - * @inheritDoc - */ - public function getHost() - { - return $this->uri->getHost(); - } - - /** - * @inheritDoc - */ - public function getPort() - { - return $this->uri->getPort(); - } - - /** - * @inheritDoc - */ - public function getPath() - { - return $this->uri->getPath(); - } - - /** - * @inheritDoc - */ - public function getQuery() - { - return $this->uri->getQuery(); - } - - /** - * @inheritDoc - */ - public function getFragment() - { - return $this->uri->getFragment(); - } - - /** - * @inheritDoc - */ - public function withScheme($scheme) - { - $new = clone $this; - $new->uri = $this->uri->withScheme($scheme); - - return $new; - } - - /** - * @inheritDoc - */ - public function withUserInfo($user, $password = null) - { - $new = clone $this; - $new->uri = $this->uri->withUserInfo($user, $password); - - return $new; - } - - /** - * @inheritDoc - */ - public function withHost($host) - { - $new = clone $this; - $new->uri = $this->uri->withHost($host); - - return $new; - } - - /** - * @inheritDoc - */ - public function withPort($port) - { - $new = clone $this; - $new->uri = $this->uri->withPort($port); - - return $new; - } - - /** - * @inheritDoc - */ - public function withPath($path) - { - $new = clone $this; - $new->uri = $this->uri->withPath($path); - - return $new; - } - - /** - * @inheritDoc - */ - public function withQuery($query) - { - $new = clone $this; - $new->uri = $this->uri->withQuery($query); - - return $new; - } - - /** - * @inheritDoc - */ - public function withFragment($fragment) - { - $new = clone $this; - $new->uri = $this->uri->withFragment($fragment); - - return $new; - } - - /** - * @inheritDoc - */ - public function __toString() - { - return $this->uri->__toString(); - } -} diff --git a/app/vendor/cakephp/cakephp/src/Http/UriFactory.php b/app/vendor/cakephp/cakephp/src/Http/UriFactory.php new file mode 100644 index 000000000..64d011e29 --- /dev/null +++ b/app/vendor/cakephp/cakephp/src/Http/UriFactory.php @@ -0,0 +1,182 @@ + $base, 'webroot' => $webroot] = static::getBase($uri, $server); + + $uri = static::updatePath($base, $uri); + + if (!$uri->getHost()) { + $uri = $uri->withHost('localhost'); + } + + return ['uri' => $uri, 'base' => $base, 'webroot' => $webroot]; + } + + /** + * Updates the request URI to remove the base directory. + * + * @param string $base The base path to remove. + * @param \Psr\Http\Message\UriInterface $uri The uri to update. + * @return \Psr\Http\Message\UriInterface + */ + protected static function updatePath(string $base, UriInterface $uri): UriInterface + { + $path = $uri->getPath(); + if ($base !== '' && str_starts_with($path, $base)) { + $path = substr($path, strlen($base)); + } + + // App.baseUrl is meant to be set only when URL rewriting is not used. + if (!Configure::read('App.baseUrl')) { + if ($path === '' || $path === '//') { + $path = '/'; + } + + return $uri->withPath($path); + } + + if ($path === '/index.php' && $uri->getQuery()) { + $path = $uri->getQuery(); + } + if ($path === '' || $path === '//' || $path === '/index.php') { + $path = '/'; + } + + // Check for $webroot/index.php at the start and end of the path. + $search = ''; + if (str_starts_with($path, '/')) { + $search .= '/'; + } + $search .= (Configure::read('App.webroot') ?: 'webroot') . '/index.php'; + if (str_starts_with($path, $search)) { + $path = substr($path, strlen($search)); + } elseif (str_ends_with($path, $search)) { + $path = '/'; + } + if (!$path) { + $path = '/'; + } + + return $uri->withPath($path); + } + + /** + * Calculate the base directory and webroot directory. + * + * @param \Psr\Http\Message\UriInterface $uri The Uri instance. + * @param array $server The SERVER data to use. + * @return array An array containing the base and webroot paths. + * @phpstan-return array{base: string, webroot: string} + */ + protected static function getBase(UriInterface $uri, array $server): array + { + $config = (array)Configure::read('App') + [ + 'base' => null, + 'webroot' => null, + 'baseUrl' => null, + ]; + $base = $config['base']; + $baseUrl = $config['baseUrl']; + $webroot = (string)$config['webroot']; + + if ($base !== false && $base !== null) { + return ['base' => $base, 'webroot' => $base . '/']; + } + + if (!$baseUrl) { + $phpSelf = $server['PHP_SELF'] ?? null; + if ($phpSelf === null) { + return ['base' => '', 'webroot' => '/']; + } + + $base = dirname($server['PHP_SELF'] ?? DIRECTORY_SEPARATOR); + // Clean up additional / which cause following code to fail.. + $base = (string)preg_replace('#/+#', '/', $base); + + $indexPos = strpos($base, '/index.php'); + if ($indexPos !== false) { + $base = substr($base, 0, $indexPos); + } + if ($webroot === basename($base)) { + $base = dirname($base); + } + + if ($base === DIRECTORY_SEPARATOR || $base === '.') { + $base = ''; + } + $base = implode('/', array_map('rawurlencode', explode('/', $base))); + + return ['base' => $base, 'webroot' => $base . '/']; + } + + $file = '/' . basename($baseUrl); + $base = dirname($baseUrl); + + if ($base === DIRECTORY_SEPARATOR || $base === '.') { + $base = ''; + } + $webrootDir = $base . '/'; + + $docRoot = $server['DOCUMENT_ROOT'] ?? ''; + if ( + ($base || !str_contains($docRoot, $webroot)) + && !str_contains($webrootDir, '/' . $webroot . '/') + ) { + $webrootDir .= $webroot . '/'; + } + + return ['base' => $base . $file, 'webroot' => $webrootDir]; + } +} diff --git a/app/vendor/cakephp/cakephp/src/Http/composer.json b/app/vendor/cakephp/cakephp/src/Http/composer.json index ee0f86a2c..2b58c6084 100644 --- a/app/vendor/cakephp/cakephp/src/Http/composer.json +++ b/app/vendor/cakephp/cakephp/src/Http/composer.json @@ -1,12 +1,14 @@ { "name": "cakephp/http", - "description": "CakePHP HTTP client and PSR7/15 middleware libraries", + "description": "CakePHP HTTP client and PSR-7, PSR-15, PSR-17, PSR-18 compliant libraries", "type": "library", "keywords": [ "cakephp", "http", - "psr7", - "psr15" + "PSR-7", + "PSR-15", + "PSR-17", + "PSR-18" ], "homepage": "https://cakephp.org", "license": "MIT", @@ -23,29 +25,46 @@ "source": "https://github.com/cakephp/http" }, "require": { - "php": ">=7.4.0", - "cakephp/core": "^4.0", - "cakephp/event": "^4.0", - "cakephp/utility": "^4.0", - "composer/ca-bundle": "^1.2", - "psr/http-client": "^1.0", - "psr/http-server-handler": "^1.0", - "psr/http-server-middleware": "^1.0", - "laminas/laminas-diactoros": "^2.1", - "laminas/laminas-httphandlerrunner": "^1.0" + "php": ">=8.1", + "cakephp/core": "5.2.*@dev", + "cakephp/event": "5.2.*@dev", + "cakephp/utility": "5.2.*@dev", + "composer/ca-bundle": "^1.5", + "psr/http-client": "^1.0.2", + "psr/http-factory": "^1.1", + "psr/http-message": "^1.1 || ^2.0", + "psr/http-server-handler": "^1.0.2", + "psr/http-server-middleware": "^1.0.2", + "laminas/laminas-diactoros": "^3.3", + "laminas/laminas-httphandlerrunner": "^2.6" + }, + "require-dev": { + "cakephp/cache": "5.2.*@dev", + "cakephp/console": "5.2.*@dev", + "cakephp/orm": "5.2.*@dev", + "cakephp/i18n": "5.2.*@dev", + "paragonie/csp-builder": "^3.0" + }, + "autoload": { + "psr-4": { + "Cake\\Http\\": "." + } }, "provide": { "psr/http-client-implementation": "^1.0", - "psr/http-server-implementation": "^1.0", + "psr/http-factory-implementation": "^1.1", + "psr/http-server-handler-implementation": "^1.0", "psr/http-server-middleware-implementation": "^1.0" }, "suggest": { "cakephp/cache": "To use cache session storage", - "cakephp/orm": "To use database session storage" + "cakephp/orm": "To use database session storage", + "paragonie/csp-builder": "To use CspMiddleware" }, - "autoload": { - "psr-4": { - "Cake\\Http\\": "." + "prefer-stable": true, + "extra": { + "branch-alias": { + "dev-5.x": "5.2.x-dev" } } } diff --git a/app/vendor/cakephp/cakephp/src/I18n/ChainMessagesLoader.php b/app/vendor/cakephp/cakephp/src/I18n/ChainMessagesLoader.php index e48c8c3b8..9720c9c78 100644 --- a/app/vendor/cakephp/cakephp/src/I18n/ChainMessagesLoader.php +++ b/app/vendor/cakephp/cakephp/src/I18n/ChainMessagesLoader.php @@ -16,7 +16,7 @@ */ namespace Cake\I18n; -use RuntimeException; +use Cake\Core\Exception\CakeException; /** * Wraps multiple message loaders calling them one after another until @@ -29,7 +29,7 @@ class ChainMessagesLoader * * @var array */ - protected $_loaders = []; + protected array $_loaders = []; /** * Receives a list of callable functions or objects that will be executed @@ -47,15 +47,15 @@ public function __construct(array $loaders) * the chain. * * @return \Cake\I18n\Package - * @throws \RuntimeException if any of the loaders in the chain is not a valid callable + * @throws \Cake\Core\Exception\CakeException if any of the loaders in the chain is not a valid callable */ public function __invoke(): Package { foreach ($this->_loaders as $k => $loader) { if (!is_callable($loader)) { - throw new RuntimeException(sprintf( - 'Loader "%s" in the chain is not a valid callable', - $k + throw new CakeException(sprintf( + 'Loader `%s` in the chain is not a valid callable.', + $k, )); } @@ -65,9 +65,9 @@ public function __invoke(): Package } if (!($package instanceof Package)) { - throw new RuntimeException(sprintf( - 'Loader "%s" in the chain did not return a valid Package object', - $k + throw new CakeException(sprintf( + 'Loader `%s` in the chain did not return a valid Package object.', + $k, )); } diff --git a/app/vendor/cakephp/cakephp/src/I18n/Date.php b/app/vendor/cakephp/cakephp/src/I18n/Date.php index 9678bfdeb..856c2435a 100644 --- a/app/vendor/cakephp/cakephp/src/I18n/Date.php +++ b/app/vendor/cakephp/cakephp/src/I18n/Date.php @@ -16,37 +16,37 @@ */ namespace Cake\I18n; -use Cake\Chronos\MutableDate; +use Cake\Chronos\ChronosDate; +use Cake\Chronos\DifferenceFormatterInterface; +use Closure; use IntlDateFormatter; -use function Cake\Core\deprecationWarning; +use InvalidArgumentException; +use JsonSerializable; +use Stringable; /** * Extends the Date class provided by Chronos. * - * Adds handy methods and locale-aware formatting helpers + * Adds handy methods and locale-aware formatting helpers. * - * @deprecated 4.3.0 Use the immutable alternative `FrozenDate` instead. + * @phpstan-immutable */ -class Date extends MutableDate implements I18nDateTimeInterface +class Date extends ChronosDate implements JsonSerializable, Stringable { use DateFormatTrait; /** * The format to use when formatting a time using `Cake\I18n\Date::i18nFormat()` - * and `__toString`. This format is also used by `parseDateTime()`. + * and `__toString`. * * The format should be either the formatting constants from IntlDateFormatter as * described in (https://secure.php.net/manual/en/class.intldateformatter.php) or a pattern * as specified in (https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/classSimpleDateFormat.html#details) * - * It is possible to provide an array of 2 constants. In this case, the first position - * will be used for formatting the date part of the object and the second position - * will be used to format the time part. - * - * @var array|string|int - * @see \Cake\I18n\DateFormatTrait::i18nFormat() + * @var string|int + * @see \Cake\I18n\Date::i18nFormat() */ - protected static $_toStringFormat = [IntlDateFormatter::SHORT, IntlDateFormatter::NONE]; + protected static string|int $_toStringFormat = IntlDateFormatter::SHORT; /** * The format to use when converting this object to JSON. @@ -55,23 +55,19 @@ class Date extends MutableDate implements I18nDateTimeInterface * described in (https://secure.php.net/manual/en/class.intldateformatter.php) or a pattern * as specified in (https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/classSimpleDateFormat.html#details) * - * It is possible to provide an array of 2 constants. In this case, the first position - * will be used for formatting the date part of the object and the second position - * will be used to format the time part. - * - * @var \Closure|array|string|int - * @see \Cake\I18n\Time::i18nFormat() + * @var \Closure|string|int + * @see \Cake\I18n\Date::i18nFormat() */ - protected static $_jsonEncodeFormat = 'yyyy-MM-dd'; + protected static Closure|string|int $_jsonEncodeFormat = 'yyyy-MM-dd'; /** * The format to use when formatting a time using `Cake\I18n\Date::timeAgoInWords()` * and the difference is more than `Cake\I18n\Date::$wordEnd` * - * @var array|string|int - * @see \Cake\I18n\DateFormatTrait::parseDate() + * @var string|int + * @see \Cake\I18n\Date::parseDate() */ - public static $wordFormat = [IntlDateFormatter::SHORT, IntlDateFormatter::NONE]; + public static string|int $wordFormat = IntlDateFormatter::SHORT; /** * The format to use when formatting a time using `Cake\I18n\Date::nice()` @@ -80,23 +76,19 @@ class Date extends MutableDate implements I18nDateTimeInterface * described in (https://secure.php.net/manual/en/class.intldateformatter.php) or a pattern * as specified in (https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/classSimpleDateFormat.html#details) * - * It is possible to provide an array of 2 constants. In this case, the first position - * will be used for formatting the date part of the object and the second position - * will be used to format the time part. - * - * @var array|string|int - * @see \Cake\I18n\DateFormatTrait::nice() + * @var string|int + * @see \Cake\I18n\Date::nice() */ - public static $niceFormat = [IntlDateFormatter::MEDIUM, IntlDateFormatter::NONE]; + public static string|int $niceFormat = IntlDateFormatter::MEDIUM; /** * The format to use when formatting a time using `Date::timeAgoInWords()` * and the difference is less than `Date::$wordEnd` * - * @var array + * @var array * @see \Cake\I18n\Date::timeAgoInWords() */ - public static $wordAccuracy = [ + public static array $wordAccuracy = [ 'year' => 'day', 'month' => 'day', 'week' => 'day', @@ -112,34 +104,164 @@ class Date extends MutableDate implements I18nDateTimeInterface * @var string * @see \Cake\I18n\Date::timeAgoInWords() */ - public static $wordEnd = '+1 month'; + public static string $wordEnd = '+1 month'; /** - * Create a new FrozenDate instance. + * Sets the default format used when type converting instances of this type to string * - * You can specify the timezone for the $time parameter. This timezone will - * not be used in any future modifications to the Date instance. + * The format should be either the formatting constants from IntlDateFormatter as + * described in (https://secure.php.net/manual/en/class.intldateformatter.php) or a pattern + * as specified in (https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/classSimpleDateFormat.html#details) + * + * @param string|int $format Format. + * @return void + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint + */ + public static function setToStringFormat($format): void + { + static::$_toStringFormat = $format; + } + + /** + * Sets the default format used when converting this object to JSON * - * The `$timezone` parameter is ignored if `$time` is a DateTimeInterface - * instance. + * The format should be either the formatting constants from IntlDateFormatter as + * described in (https://secure.php.net/manual/en/class.intldateformatter.php) or a pattern + * as specified in (http://www.icu-project.org/apiref/icu4c/classSimpleDateFormat.html#details) * - * Date instances lack time components, however due to limitations in PHP's - * internal Datetime object the time will always be set to 00:00:00, and the - * timezone will always be the server local time. Normalizing the timezone allows for - * subtraction/addition to have deterministic results. + * Alternatively, the format can provide a callback. In this case, the callback + * can receive this object and return a formatted string. * - * @param \DateTime|\DateTimeImmutable|string|int|null $time Fixed or relative time - * @param \DateTimeZone|string|null $tz The timezone in which the date is taken. - * Ignored if `$time` is a DateTimeInterface instance. + * @see \Cake\I18n\Date::i18nFormat() + * @param \Closure|string|int $format Format. + * @return void */ - public function __construct($time = 'now', $tz = null) + public static function setJsonEncodeFormat(Closure|string|int $format): void { - deprecationWarning( - 'The `Date` class has been deprecated. Use the immutable alternative `FrozenDate` instead', - 0 - ); + static::$_jsonEncodeFormat = $format; + } - parent::__construct($time, $tz); + /** + * Returns a new Date object after parsing the provided $date string based on + * the passed or configured format. This method is locale dependent, + * Any string that is passed to this function will be interpreted as a locale + * dependent string. + * + * When no $format is provided, the `wordFormat` format will be used. + * + * If it was impossible to parse the provided time, null will be returned. + * + * Example: + * + * ``` + * $time = Date::parseDate('10/13/2013'); + * $time = Date::parseDate('13 Oct, 2013', 'dd MMM, y'); + * $time = Date::parseDate('13 Oct, 2013', IntlDateFormatter::SHORT); + * ``` + * + * @param string $date The date string to parse. + * @param string|int|null $format Any format accepted by IntlDateFormatter. + * @return static|null + */ + public static function parseDate(string $date, string|int|null $format = null): ?static + { + $format ??= static::$wordFormat; + if (is_int($format)) { + $format = [$format, IntlDateFormatter::NONE]; + } + + return static::_parseDateTime($date, $format); + } + + /** + * Get the difference formatter instance. + * + * @param \Cake\Chronos\DifferenceFormatterInterface|null $formatter Difference formatter + * @return \Cake\I18n\RelativeTimeFormatter + */ + public static function diffFormatter(?DifferenceFormatterInterface $formatter = null): RelativeTimeFormatter + { + if ($formatter) { + if (!$formatter instanceof RelativeTimeFormatter) { + throw new InvalidArgumentException('Formatter for I18n must extend RelativeTimeFormatter.'); + } + + return static::$diffFormatter = $formatter; + } + + /** @var \Cake\I18n\RelativeTimeFormatter $formatter */ + $formatter = static::$diffFormatter ??= new RelativeTimeFormatter(); + + return $formatter; + } + + /** + * Returns a formatted string for this time object using the preferred format and + * language for the specified locale. + * + * It is possible to specify the desired format for the string to be displayed. + * You can either pass `IntlDateFormatter` constants as the first argument of this + * function, or pass a full ICU date formatting string as specified in the following + * resource: https://unicode-org.github.io/icu/userguide/format_parse/datetime/#datetime-format-syntax. + * + * ### Examples + * + * ``` + * $date = new Date('2014-04-20'); + * $date->i18nFormat(); // outputs '4/20/14' for the en-US locale + * $date->i18nFormat(\IntlDateFormatter::FULL); // Use the full date format + * $date->i18nFormat('yyyy-MM-dd'); // outputs '2014-04-20' + * ``` + * + * You can control the default format used through `Date::setToStringFormat()`. + * + * You can read about the available IntlDateFormatter constants at + * https://secure.php.net/manual/en/class.intldateformatter.php + * + * Should you need to use a different locale for displaying this time object, + * pass a locale string as the third parameter to this function. + * + * ### Examples + * + * ``` + * $date = new Date('2014-04-20'); + * $time->i18nFormat(null, 'de-DE'); + * $time->i18nFormat(\IntlDateFormatter::FULL, 'de-DE'); + * ``` + * + * You can control the default locale used through `Date::setDefaultLocale()`. + * If empty, the default will be taken from the `intl.default_locale` ini config. + * + * @param string|int|null $format Format string. + * @param string|null $locale The locale name in which the date should be displayed (e.g. pt-BR) + * @return string|int Formatted and translated date string + */ + public function i18nFormat( + string|int|null $format = null, + ?string $locale = null, + ): string|int { + if ($format === DateTime::UNIX_TIMESTAMP_FORMAT) { + throw new InvalidArgumentException('UNIT_TIMESTAMP_FORMAT is not supported for Date.'); + } + + $format ??= static::$_toStringFormat; + $format = is_int($format) ? [$format, IntlDateFormatter::NONE] : $format; + $locale = $locale ?: DateTime::getDefaultLocale(); + + return $this->_formatObject($this->native, $format, $locale); + } + + /** + * Returns a nicely formatted date string for this object. + * + * The format to be used is stored in the static property `Date::$niceFormat`. + * + * @param string|null $locale The locale name in which the date should be displayed (e.g. pt-BR) + * @return string Formatted date string + */ + public function nice(?string $locale = null): string + { + return (string)$this->i18nFormat(static::$niceFormat, $locale); } /** @@ -149,7 +271,7 @@ public function __construct($time = 'now', $tz = null) * ### Options: * * - `from` => another Date object representing the "now" date - * - `format` => a fall back format if the relative time is longer than the duration specified by end + * - `format` => a fallback format if the relative time is longer than the duration specified by end * - `accuracy` => Specifies how accurate the date should be described (array) * - year => The format if years > 0 (default "day") * - month => The format if months > 0 (default "day") @@ -178,7 +300,32 @@ public function __construct($time = 'now', $tz = null) */ public function timeAgoInWords(array $options = []): string { - /** @psalm-suppress UndefinedInterfaceMethod */ - return static::getDiffFormatter()->dateAgoInWords($this, $options); + return static::diffFormatter()->dateAgoInWords($this, $options); + } + + /** + * Returns a string that should be serialized when converting this object to JSON + * + * @return string|int + */ + public function jsonSerialize(): mixed + { + if (static::$_jsonEncodeFormat instanceof Closure) { + return call_user_func(static::$_jsonEncodeFormat, $this); + } + + return $this->i18nFormat(static::$_jsonEncodeFormat); + } + + /** + * @inheritDoc + */ + public function __toString(): string + { + return (string)$this->i18nFormat(); } } + +// phpcs:disable +class_alias('Cake\I18n\Date', 'Cake\I18n\FrozenDate'); +// phpcs:enable diff --git a/app/vendor/cakephp/cakephp/src/I18n/DateFormatTrait.php b/app/vendor/cakephp/cakephp/src/I18n/DateFormatTrait.php index dca02329c..1d05f532a 100644 --- a/app/vendor/cakephp/cakephp/src/I18n/DateFormatTrait.php +++ b/app/vendor/cakephp/cakephp/src/I18n/DateFormatTrait.php @@ -16,14 +16,11 @@ */ namespace Cake\I18n; -use Cake\Chronos\ChronosInterface; -use Cake\Chronos\DifferenceFormatterInterface; use Cake\Core\Exception\CakeException; -use Closure; -use DateTime; +use DateTimeImmutable; +use DateTimeInterface; use DateTimeZone; use IntlDateFormatter; -use RuntimeException; /** * Trait for date formatting methods shared by both Time & Date. @@ -32,203 +29,43 @@ */ trait DateFormatTrait { - /** - * The default locale to be used for displaying formatted date strings. - * - * Use static::setDefaultLocale() and static::getDefaultLocale() instead. - * - * @var string|null - */ - protected static $defaultLocale; - - /** - * Whether lenient parsing is enabled for IntlDateFormatter. - * - * Defaults to true which is the default for IntlDateFormatter. - * - * @var bool - */ - protected static $lenientParsing = true; - /** * In-memory cache of date formatters * - * @var array<\IntlDateFormatter> - */ - protected static $_formatters = []; - - /** - * Gets the default locale. - * - * @return string|null The default locale string to be used or null. - */ - public static function getDefaultLocale(): ?string - { - return static::$defaultLocale; - } - - /** - * Sets the default locale. - * - * Set to null to use IntlDateFormatter default. - * - * @param string|null $locale The default locale string to be used. - * @return void - */ - public static function setDefaultLocale(?string $locale = null): void - { - static::$defaultLocale = $locale; - } - - /** - * Gets whether locale format parsing is set to lenient. - * - * @return bool - */ - public static function lenientParsingEnabled(): bool - { - return static::$lenientParsing; - } - - /** - * Enables lenient parsing for locale formats. - * - * @return void - */ - public static function enableLenientParsing(): void - { - static::$lenientParsing = true; - } - - /** - * Enables lenient parsing for locale formats. - * - * @return void - */ - public static function disableLenientParsing(): void - { - static::$lenientParsing = false; - } - - /** - * Returns a nicely formatted date string for this object. - * - * The format to be used is stored in the static property `Time::niceFormat`. - * - * @param \DateTimeZone|string|null $timezone Timezone string or DateTimeZone object - * in which the date will be displayed. The timezone stored for this object will not - * be changed. - * @param string|null $locale The locale name in which the date should be displayed (e.g. pt-BR) - * @return string Formatted date string - */ - public function nice($timezone = null, $locale = null): string - { - return (string)$this->i18nFormat(static::$niceFormat, $timezone, $locale); - } - - /** - * Returns a formatted string for this time object using the preferred format and - * language for the specified locale. - * - * It is possible to specify the desired format for the string to be displayed. - * You can either pass `IntlDateFormatter` constants as the first argument of this - * function, or pass a full ICU date formatting string as specified in the following - * resource: https://unicode-org.github.io/icu/userguide/format_parse/datetime/#datetime-format-syntax. - * - * Additional to `IntlDateFormatter` constants and date formatting string you can use - * Time::UNIX_TIMESTAMP_FORMAT to get a unix timestamp - * - * ### Examples - * - * ``` - * $time = new Time('2014-04-20 22:10'); - * $time->i18nFormat(); // outputs '4/20/14, 10:10 PM' for the en-US locale - * $time->i18nFormat(\IntlDateFormatter::FULL); // Use the full date and time format - * $time->i18nFormat([\IntlDateFormatter::FULL, \IntlDateFormatter::SHORT]); // Use full date but short time format - * $time->i18nFormat('yyyy-MM-dd HH:mm:ss'); // outputs '2014-04-20 22:10' - * $time->i18nFormat(Time::UNIX_TIMESTAMP_FORMAT); // outputs '1398031800' - * ``` - * - * You can control the default format used through `Time::setToStringFormat()`. - * - * You can read about the available IntlDateFormatter constants at - * https://secure.php.net/manual/en/class.intldateformatter.php - * - * If you need to display the date in a different timezone than the one being used for - * this Time object without altering its internal state, you can pass a timezone - * string or object as the second parameter. - * - * Finally, should you need to use a different locale for displaying this time object, - * pass a locale string as the third parameter to this function. - * - * ### Examples - * - * ``` - * $time = new Time('2014-04-20 22:10'); - * $time->i18nFormat(null, null, 'de-DE'); - * $time->i18nFormat(\IntlDateFormatter::FULL, 'Europe/Berlin', 'de-DE'); - * ``` - * - * You can control the default locale used through `Time::setDefaultLocale()`. - * If empty, the default will be taken from the `intl.default_locale` ini config. - * - * @param array|string|int|null $format Format string. - * @param \DateTimeZone|string|null $timezone Timezone string or DateTimeZone object - * in which the date will be displayed. The timezone stored for this object will not - * be changed. - * @param string|null $locale The locale name in which the date should be displayed (e.g. pt-BR) - * @return string|int Formatted and translated date string + * @var array */ - public function i18nFormat($format = null, $timezone = null, $locale = null) - { - if ($format === Time::UNIX_TIMESTAMP_FORMAT) { - return $this->getTimestamp(); - } - - $time = $this; - - if ($timezone) { - // Handle the immutable and mutable object cases. - $time = clone $this; - $time = $time->setTimezone($timezone); - } - - $format = $format ?? static::$_toStringFormat; - $locale = $locale ?: static::$defaultLocale; - - return $this->_formatObject($time, $format, $locale); - } + protected static array $formatters = []; /** * Returns a translated and localized date string. * Implements what IntlDateFormatter::formatObject() is in PHP 5.5+ * - * @param \DateTime|\DateTimeImmutable $date Date. - * @param array|string|int $format Format. + * @param \DateTimeInterface $date Date. + * @param array|string $format Format. * @param string|null $locale The locale name in which the date should be displayed. * @return string */ - protected function _formatObject($date, $format, ?string $locale): string - { + protected function _formatObject( + DateTimeInterface $date, + array|string $format, + ?string $locale, + ): string { $pattern = ''; if (is_array($format)) { [$dateFormat, $timeFormat] = $format; - } elseif (is_int($format)) { - $dateFormat = $timeFormat = $format; } else { - $dateFormat = $timeFormat = IntlDateFormatter::FULL; + $dateFormat = IntlDateFormatter::FULL; + $timeFormat = IntlDateFormatter::FULL; $pattern = $format; } - if ($locale === null) { - $locale = I18n::getLocale(); - } + $locale ??= I18n::getLocale(); if ( preg_match( '/@calendar=(japanese|buddhist|chinese|persian|indian|islamic|hebrew|coptic|ethiopic)/', - $locale + $locale, ) ) { $calendar = IntlDateFormatter::TRADITIONAL; @@ -239,76 +76,33 @@ protected function _formatObject($date, $format, ?string $locale): string $timezone = $date->getTimezone()->getName(); $key = "{$locale}.{$dateFormat}.{$timeFormat}.{$timezone}.{$calendar}.{$pattern}"; - if (!isset(static::$_formatters[$key])) { + if (!isset(static::$formatters[$key])) { if ($timezone === '+00:00' || $timezone === 'Z') { $timezone = 'UTC'; - } elseif ($timezone[0] === '+' || $timezone[0] === '-') { + } elseif (str_starts_with($timezone, '+') || str_starts_with($timezone, '-')) { $timezone = 'GMT' . $timezone; } + $formatter = datefmt_create( $locale, $dateFormat, $timeFormat, $timezone, $calendar, - $pattern + $pattern, ); - if (empty($formatter)) { - throw new RuntimeException( + + if (!$formatter) { + throw new CakeException( 'Your version of icu does not support creating a date formatter for ' . - "`$key`. You should try to upgrade libicu and the intl extension." + "`{$key}`. You should try to upgrade libicu and the intl extension.", ); } - static::$_formatters[$key] = $formatter; - } - - return static::$_formatters[$key]->format($date); - } - - /** - * @inheritDoc - */ - public function __toString(): string - { - return (string)$this->i18nFormat(); - } - - /** - * Resets the format used to the default when converting an instance of this type to - * a string - * - * @return void - */ - public static function resetToStringFormat(): void - { - static::setToStringFormat([IntlDateFormatter::SHORT, IntlDateFormatter::SHORT]); - } - /** - * Sets the default format used when type converting instances of this type to string - * - * The format should be either the formatting constants from IntlDateFormatter as - * described in (https://secure.php.net/manual/en/class.intldateformatter.php) or a pattern - * as specified in (https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/classSimpleDateFormat.html#details) - * - * It is possible to provide an array of 2 constants. In this case, the first position - * will be used for formatting the date part of the object and the second position - * will be used to format the time part. - * - * @param array|string|int $format Format. - * @return void - */ - public static function setToStringFormat($format): void - { - static::$_toStringFormat = $format; - } + static::$formatters[$key] = $formatter; + } - /** - * @inheritDoc - */ - public static function setJsonEncodeFormat($format): void - { - static::$_jsonEncodeFormat = $format; + return (string)static::$formatters[$key]->format($date); } /** @@ -317,8 +111,6 @@ public static function setJsonEncodeFormat($format): void * Any string that is passed to this function will be interpreted as a locale * dependent string. * - * When no $format is provided, the `toString` format will be used. - * * Unlike DateTime, the time zone of the returned instance is always converted * to `$tz` (default time zone if null) even if the `$time` string specified a * time zone. This is a limitation of IntlDateFormatter. @@ -334,196 +126,51 @@ public static function setJsonEncodeFormat($format): void * ``` * * @param string $time The time string to parse. - * @param array|string|int|null $format Any format accepted by IntlDateFormatter. + * @param array|string $format Any format accepted by IntlDateFormatter. * @param \DateTimeZone|string|null $tz The timezone for the instance * @return static|null */ - public static function parseDateTime(string $time, $format = null, $tz = null) - { - $format = $format ?? static::$_toStringFormat; + protected static function _parseDateTime( + string $time, + array|string $format, + DateTimeZone|string|null $tz = null, + ): ?static { $pattern = ''; if (is_array($format)) { [$dateFormat, $timeFormat] = $format; - } elseif (is_int($format)) { - $dateFormat = $timeFormat = $format; } else { - $dateFormat = $timeFormat = IntlDateFormatter::FULL; + $dateFormat = IntlDateFormatter::FULL; + $timeFormat = IntlDateFormatter::FULL; $pattern = $format; } - $locale = static::$defaultLocale ?? I18n::getLocale(); + $locale = DateTime::getDefaultLocale() ?? I18n::getLocale(); $formatter = datefmt_create( $locale, $dateFormat, $timeFormat, $tz, null, - $pattern + $pattern, ); if (!$formatter) { throw new CakeException('Unable to create IntlDateFormatter instance'); } - $formatter->setLenient(static::$lenientParsing); + $formatter->setLenient(DateTime::lenientParsingEnabled()); $time = $formatter->parse($time); - if ($time !== false) { - $dateTime = new DateTime('@' . $time); - - if (!($tz instanceof DateTimeZone)) { - $tz = new DateTimeZone($tz ?? date_default_timezone_get()); - } - $dateTime->setTimezone($tz); - - return new static($dateTime); + if ($time === false) { + return null; } - return null; - } + $dateTime = new DateTimeImmutable('@' . $time); - /** - * Returns a new Time object after parsing the provided $date string based on - * the passed or configured date time format. This method is locale dependent, - * Any string that is passed to this function will be interpreted as a locale - * dependent string. - * - * When no $format is provided, the `wordFormat` format will be used. - * - * If it was impossible to parse the provided time, null will be returned. - * - * Example: - * - * ``` - * $time = Time::parseDate('10/13/2013'); - * $time = Time::parseDate('13 Oct, 2013', 'dd MMM, y'); - * $time = Time::parseDate('13 Oct, 2013', IntlDateFormatter::SHORT); - * ``` - * - * @param string $date The date string to parse. - * @param array|string|int|null $format Any format accepted by IntlDateFormatter. - * @return static|null - */ - public static function parseDate(string $date, $format = null) - { - if (is_int($format)) { - $format = [$format, IntlDateFormatter::NONE]; + if (!($tz instanceof DateTimeZone)) { + $tz = new DateTimeZone($tz ?? date_default_timezone_get()); } - $format = $format ?: static::$wordFormat; + $dateTime = $dateTime->setTimezone($tz); - return static::parseDateTime($date, $format); - } - - /** - * Returns a new Time object after parsing the provided $time string based on - * the passed or configured date time format. This method is locale dependent, - * Any string that is passed to this function will be interpreted as a locale - * dependent string. - * - * When no $format is provided, the IntlDateFormatter::SHORT format will be used. - * - * If it was impossible to parse the provided time, null will be returned. - * - * Example: - * - * ``` - * $time = Time::parseTime('11:23pm'); - * ``` - * - * @param string $time The time string to parse. - * @param string|int|null $format Any format accepted by IntlDateFormatter. - * @return static|null - */ - public static function parseTime(string $time, $format = null) - { - if (is_int($format)) { - $format = [IntlDateFormatter::NONE, $format]; - } - $format = $format ?: [IntlDateFormatter::NONE, IntlDateFormatter::SHORT]; - - return static::parseDateTime($time, $format); - } - - /** - * Returns a string that should be serialized when converting this object to JSON - * - * @return string|int - */ - #[\ReturnTypeWillChange] - public function jsonSerialize() - { - if (static::$_jsonEncodeFormat instanceof Closure) { - return call_user_func(static::$_jsonEncodeFormat, $this); - } - - return $this->i18nFormat(static::$_jsonEncodeFormat); - } - - /** - * Get the difference formatter instance. - * - * @return \Cake\Chronos\DifferenceFormatterInterface - */ - public static function getDiffFormatter(): DifferenceFormatterInterface - { - // Use the static property defined in chronos. - if (static::$diffFormatter === null) { - static::$diffFormatter = new RelativeTimeFormatter(); - } - - return static::$diffFormatter; - } - - /** - * Set the difference formatter instance. - * - * @param \Cake\Chronos\DifferenceFormatterInterface $formatter The formatter instance when setting. - * @return void - */ - public static function setDiffFormatter(DifferenceFormatterInterface $formatter): void - { - static::$diffFormatter = $formatter; - } - - /** - * Get the difference in a human readable format. - * - * When comparing a value in the past to default now: - * 1 hour ago - * 5 months ago - * - * When comparing a value in the future to default now: - * 1 hour from now - * 5 months from now - * - * When comparing a value in the past to another value: - * 1 hour before - * 5 months before - * - * When comparing a value in the future to another value: - * 1 hour after - * 5 months after - * - * @param \Cake\Chronos\ChronosInterface|null $other The datetime to compare with. - * @param bool $absolute removes time difference modifiers ago, after, etc - * @return string - */ - public function diffForHumans(?ChronosInterface $other = null, bool $absolute = false): string - { - return static::getDiffFormatter()->diffForHumans($this, $other, $absolute); - } - - /** - * Returns the data that should be displayed when debugging this object - * - * @return array - */ - public function __debugInfo(): array - { - /** @psalm-suppress PossiblyNullReference */ - return [ - 'time' => $this->format('Y-m-d H:i:s.uP'), - 'timezone' => $this->getTimezone()->getName(), - 'fixedNowTime' => static::hasTestNow() ? static::getTestNow()->format('Y-m-d\TH:i:s.uP') : false, - ]; + return new static($dateTime); } } diff --git a/app/vendor/cakephp/cakephp/src/I18n/DateTime.php b/app/vendor/cakephp/cakephp/src/I18n/DateTime.php new file mode 100644 index 000000000..b21e02d4b --- /dev/null +++ b/app/vendor/cakephp/cakephp/src/I18n/DateTime.php @@ -0,0 +1,611 @@ +|string|int + * @see \Cake\I18n\DateTime::i18nFormat() + */ + protected static array|string|int $_toStringFormat = [IntlDateFormatter::SHORT, IntlDateFormatter::SHORT]; + + /** + * The format to use when converting this object to JSON. + * + * The format should be either the formatting constants from IntlDateFormatter as + * described in (https://secure.php.net/manual/en/class.intldateformatter.php) or a pattern + * as specified in (https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/classSimpleDateFormat.html#details) + * + * It is possible to provide an array of 2 constants. In this case, the first position + * will be used for formatting the date part of the object and the second position + * will be used to format the time part. + * + * @var \Closure|array|string|int + * @see \Cake\I18n\DateTime::i18nFormat() + */ + protected static Closure|array|string|int $_jsonEncodeFormat = "yyyy-MM-dd'T'HH':'mm':'ssxxx"; + + /** + * The format to use when formatting a time using `Cake\I18n\DateTime::nice()` + * + * The format should be either the formatting constants from IntlDateFormatter as + * described in (https://secure.php.net/manual/en/class.intldateformatter.php) or a pattern + * as specified in (https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/classSimpleDateFormat.html#details) + * + * It is possible to provide an array of 2 constants. In this case, the first position + * will be used for formatting the date part of the object and the second position + * will be used to format the time part. + * + * @var array|string|int + * @see \Cake\I18n\DateTime::nice() + */ + public static array|string|int $niceFormat = [IntlDateFormatter::MEDIUM, IntlDateFormatter::SHORT]; + + /** + * The format to use when formatting a time using `Cake\I18n\DateTime::timeAgoInWords()` + * and the difference is more than `Cake\I18n\DateTime::$wordEnd` + * + * @var array|string|int + * @see \Cake\I18n\DateTime::timeAgoInWords() + */ + public static array|string|int $wordFormat = [IntlDateFormatter::SHORT, IntlDateFormatter::NONE]; + + /** + * The format to use when formatting a time using `DateTime::timeAgoInWords()` + * and the difference is less than `DateTime::$wordEnd` + * + * @var array + * @see \Cake\I18n\DateTime::timeAgoInWords() + */ + public static array $wordAccuracy = [ + 'year' => 'day', + 'month' => 'day', + 'week' => 'day', + 'day' => 'hour', + 'hour' => 'minute', + 'minute' => 'minute', + 'second' => 'second', + ]; + + /** + * The end of relative time telling + * + * @var string + * @see \Cake\I18n\DateTime::timeAgoInWords() + */ + public static string $wordEnd = '+1 month'; + + /** + * serialise the value as a Unix Timestamp + * + * @var string + */ + public const UNIX_TIMESTAMP_FORMAT = 'unixTimestampFormat'; + + /** + * Gets the default locale. + * + * @return string|null The default locale string to be used or null. + */ + public static function getDefaultLocale(): ?string + { + return static::$defaultLocale; + } + + /** + * Sets the default locale. + * + * Set to null to use IntlDateFormatter default. + * + * @param string|null $locale The default locale string to be used. + * @return void + */ + public static function setDefaultLocale(?string $locale = null): void + { + static::$defaultLocale = $locale; + } + + /** + * Gets whether locale format parsing is set to lenient. + * + * @return bool + */ + public static function lenientParsingEnabled(): bool + { + return static::$lenientParsing; + } + + /** + * Enables lenient parsing for locale formats. + * + * @return void + */ + public static function enableLenientParsing(): void + { + static::$lenientParsing = true; + } + + /** + * Enables lenient parsing for locale formats. + * + * @return void + */ + public static function disableLenientParsing(): void + { + static::$lenientParsing = false; + } + + /** + * Sets the default format used when type converting instances of this type to string + * + * The format should be either the formatting constants from IntlDateFormatter as + * described in (https://secure.php.net/manual/en/class.intldateformatter.php) or a pattern + * as specified in (https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/classSimpleDateFormat.html#details) + * + * It is possible to provide an array of 2 constants. In this case, the first position + * will be used for formatting the date part of the object and the second position + * will be used to format the time part. + * + * @param array|string|int $format Format. + * @return void + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint + */ + public static function setToStringFormat($format): void + { + static::$_toStringFormat = $format; + } + + /** + * Resets the format used to the default when converting an instance of this type to + * a string + * + * @return void + */ + public static function resetToStringFormat(): void + { + static::setToStringFormat([IntlDateFormatter::SHORT, IntlDateFormatter::SHORT]); + } + + /** + * Sets the default format used when converting this object to JSON + * + * The format should be either the formatting constants from IntlDateFormatter as + * described in (https://secure.php.net/manual/en/class.intldateformatter.php) or a pattern + * as specified in (http://www.icu-project.org/apiref/icu4c/classSimpleDateFormat.html#details) + * + * It is possible to provide an array of 2 constants. In this case, the first position + * will be used for formatting the date part of the object and the second position + * will be used to format the time part. + * + * Alternatively, the format can provide a callback. In this case, the callback + * can receive this datetime object and return a formatted string. + * + * @see \Cake\I18n\DateTime::i18nFormat() + * @param \Closure|array|string|int $format Format. + * @return void + */ + public static function setJsonEncodeFormat(Closure|array|string|int $format): void + { + static::$_jsonEncodeFormat = $format; + } + + /** + * Returns a new Time object after parsing the provided time string based on + * the passed or configured date time format. This method is locale dependent, + * Any string that is passed to this function will be interpreted as a locale + * dependent string. + * + * When no $format is provided, the `toString` format will be used. + * + * Unlike DateTime, the time zone of the returned instance is always converted + * to `$tz` (default time zone if null) even if the `$time` string specified a + * time zone. This is a limitation of IntlDateFormatter. + * + * If it was impossible to parse the provided time, null will be returned. + * + * Example: + * + * ``` + * $time = DateTime::parseDateTime('10/13/2013 12:54am'); + * $time = DateTime::parseDateTime('13 Oct, 2013 13:54', 'dd MMM, y H:mm'); + * $time = DateTime::parseDateTime('10/10/2015', [IntlDateFormatter::SHORT, IntlDateFormatter::NONE]); + * ``` + * + * @param string $time The time string to parse. + * @param array|string|int|null $format Any format accepted by IntlDateFormatter. + * @param \DateTimeZone|string|null $tz The timezone for the instance + * @return static|null + */ + public static function parseDateTime( + string $time, + array|string|int|null $format = null, + DateTimeZone|string|null $tz = null, + ): ?static { + $format ??= static::$_toStringFormat; + $format = is_int($format) ? [$format, $format] : $format; + + return static::_parseDateTime($time, $format, $tz); + } + + /** + * Returns a new Time object after parsing the provided $date string based on + * the passed or configured date time format. This method is locale dependent, + * Any string that is passed to this function will be interpreted as a locale + * dependent string. + * + * When no $format is provided, the `wordFormat` format will be used. + * + * If it was impossible to parse the provided time, null will be returned. + * + * Example: + * + * ``` + * $time = DateTime::parseDate('10/13/2013'); + * $time = DateTime::parseDate('13 Oct, 2013', 'dd MMM, y'); + * $time = DateTime::parseDate('13 Oct, 2013', IntlDateFormatter::SHORT); + * ``` + * + * @param string $date The date string to parse. + * @param array|string|int|null $format Any format accepted by IntlDateFormatter. + * @return static|null + */ + public static function parseDate(string $date, array|string|int|null $format = null): ?static + { + $format ??= static::$wordFormat; + if (is_int($format)) { + $format = [$format, IntlDateFormatter::NONE]; + } + + return static::parseDateTime($date, $format); + } + + /** + * Returns a new Time object after parsing the provided $time string based on + * the passed or configured date time format. This method is locale dependent, + * Any string that is passed to this function will be interpreted as a locale + * dependent string. + * + * When no $format is provided, the IntlDateFormatter::SHORT format will be used. + * + * If it was impossible to parse the provided time, null will be returned. + * + * Example: + * + * ``` + * $time = DateTime::parseTime('11:23pm'); + * ``` + * + * @param string $time The time string to parse. + * @param array|string|int|null $format Any format accepted by IntlDateFormatter. + * @return static|null + */ + public static function parseTime(string $time, array|string|int|null $format = null): ?static + { + if (is_int($format)) { + $format = [IntlDateFormatter::NONE, $format]; + } + $format = $format ?: [IntlDateFormatter::NONE, IntlDateFormatter::SHORT]; + + return static::parseDateTime($time, $format); + } + + /** + * Get the difference formatter instance. + * + * @param \Cake\Chronos\DifferenceFormatterInterface|null $formatter Difference formatter + * @return \Cake\I18n\RelativeTimeFormatter + */ + public static function diffFormatter(?DifferenceFormatterInterface $formatter = null): RelativeTimeFormatter + { + if ($formatter) { + if (!$formatter instanceof RelativeTimeFormatter) { + throw new InvalidArgumentException('Formatter for I18n must extend RelativeTimeFormatter.'); + } + + return static::$diffFormatter = $formatter; + } + + /** @var \Cake\I18n\RelativeTimeFormatter $formatter */ + $formatter = static::$diffFormatter ??= new RelativeTimeFormatter(); + + return $formatter; + } + + /** + * Returns a formatted string for this time object using the preferred format and + * language for the specified locale. + * + * It is possible to specify the desired format for the string to be displayed. + * You can either pass `IntlDateFormatter` constants as the first argument of this + * function, or pass a full ICU date formatting string as specified in the following + * resource: https://unicode-org.github.io/icu/userguide/format_parse/datetime/#datetime-format-syntax. + * + * Additional to `IntlDateFormatter` constants and date formatting string you can use + * DateTime::UNIX_TIMESTAMP_FORMAT to get a unix timestamp + * + * ### Examples + * + * ``` + * $time = new DateTime('2014-04-20 22:10'); + * $time->i18nFormat(); // outputs '4/20/14, 10:10 PM' for the en-US locale + * $time->i18nFormat(\IntlDateFormatter::FULL); // Use the full date and time format + * $time->i18nFormat([\IntlDateFormatter::FULL, \IntlDateFormatter::SHORT]); // Use full date but short time format + * $time->i18nFormat('yyyy-MM-dd HH:mm:ss'); // outputs '2014-04-20 22:10' + * $time->i18nFormat(DateTime::UNIX_TIMESTAMP_FORMAT); // outputs '1398031800' + * ``` + * + * You can control the default format used through `DateTime::setToStringFormat()`. + * + * You can read about the available IntlDateFormatter constants at + * https://secure.php.net/manual/en/class.intldateformatter.php + * + * If you need to display the date in a different timezone than the one being used for + * this Time object without altering its internal state, you can pass a timezone + * string or object as the second parameter. + * + * Finally, should you need to use a different locale for displaying this time object, + * pass a locale string as the third parameter to this function. + * + * ### Examples + * + * ``` + * $time = new Time('2014-04-20 22:10'); + * $time->i18nFormat(null, null, 'de-DE'); + * $time->i18nFormat(\IntlDateFormatter::FULL, 'Europe/Berlin', 'de-DE'); + * ``` + * + * You can control the default locale used through `DateTime::setDefaultLocale()`. + * If empty, the default will be taken from the `intl.default_locale` ini config. + * + * @param array|string|int|null $format Format string. + * @param \DateTimeZone|string|null $timezone Timezone string or DateTimeZone object + * in which the date will be displayed. The timezone stored for this object will not + * be changed. + * @param string|null $locale The locale name in which the date should be displayed (e.g. pt-BR) + * @return string|int Formatted and translated date string + */ + public function i18nFormat( + array|string|int|null $format = null, + DateTimeZone|string|null $timezone = null, + ?string $locale = null, + ): string|int { + if ($format === DateTime::UNIX_TIMESTAMP_FORMAT) { + return $this->getTimestamp(); + } + + $time = $this; + + if ($timezone) { + $time = $time->setTimezone($timezone); + } + + $format ??= static::$_toStringFormat; + $format = is_int($format) ? [$format, $format] : $format; + $locale = $locale ?: DateTime::getDefaultLocale(); + + return $this->_formatObject($time, $format, $locale); + } + + /** + * Returns a nicely formatted date string for this object. + * + * The format to be used is stored in the static property `DateTime::$niceFormat`. + * + * @param \DateTimeZone|string|null $timezone Timezone string or DateTimeZone object + * in which the date will be displayed. The timezone stored for this object will not + * be changed. + * @param string|null $locale The locale name in which the date should be displayed (e.g. pt-BR) + * @return string Formatted date string + */ + public function nice(DateTimeZone|string|null $timezone = null, ?string $locale = null): string + { + return (string)$this->i18nFormat(static::$niceFormat, $timezone, $locale); + } + + /** + * Returns either a relative or a formatted absolute date depending + * on the difference between the current time and this object. + * + * ### Options: + * + * - `from` => another Time object representing the "now" time + * - `format` => a fallback format if the relative time is longer than the duration specified by end + * - `accuracy` => Specifies how accurate the date should be described (array) + * - year => The format if years > 0 (default "day") + * - month => The format if months > 0 (default "day") + * - week => The format if weeks > 0 (default "day") + * - day => The format if weeks > 0 (default "hour") + * - hour => The format if hours > 0 (default "minute") + * - minute => The format if minutes > 0 (default "minute") + * - second => The format if seconds > 0 (default "second") + * - `end` => The end of relative time telling + * - `relativeString` => The printf compatible string when outputting relative time + * - `absoluteString` => The printf compatible string when outputting absolute time + * - `timezone` => The user timezone the timestamp should be formatted in. + * + * Relative dates look something like this: + * + * - 3 weeks, 4 days ago + * - 15 seconds ago + * + * Default date formatting is d/M/YY e.g: on 18/2/09. Formatting is done internally using + * `i18nFormat`, see the method for the valid formatting strings + * + * The returned string includes 'ago' or 'on' and assumes you'll properly add a word + * like 'Posted ' before the function output. + * + * NOTE: If the difference is one week or more, the lowest level of accuracy is day + * + * @param array $options Array of options. + * @return string Relative time string. + */ + public function timeAgoInWords(array $options = []): string + { + return static::diffFormatter()->timeAgoInWords($this, $options); + } + + /** + * Get list of timezone identifiers + * + * @param string|int|null $filter A regex to filter identifier + * Or one of DateTimeZone class constants + * @param string|null $country A two-letter ISO 3166-1 compatible country code. + * This option is only used when $filter is set to DateTimeZone::PER_COUNTRY + * @param array|bool $options If true (default value) groups the identifiers list by primary region. + * Otherwise, an array containing `group`, `abbr`, `before`, and `after` + * keys. Setting `group` and `abbr` to true will group results and append + * timezone abbreviation in the display value. Set `before` and `after` + * to customize the abbreviation wrapper. + * @return array List of timezone identifiers + * @since 2.2 + */ + public static function listTimezones( + string|int|null $filter = null, + ?string $country = null, + array|bool $options = [], + ): array { + if (is_bool($options)) { + $options = [ + 'group' => $options, + ]; + } + $defaults = [ + 'group' => true, + 'abbr' => false, + 'before' => ' - ', + 'after' => null, + ]; + $options += $defaults; + $group = $options['group']; + + $regex = null; + if (is_string($filter)) { + $regex = $filter; + $filter = null; + } + $filter ??= DateTimeZone::ALL; + $identifiers = DateTimeZone::listIdentifiers($filter, (string)$country) ?: []; + + if ($regex) { + foreach ($identifiers as $key => $tz) { + if (!preg_match($regex, $tz)) { + unset($identifiers[$key]); + } + } + } + + if ($group) { + $groupedIdentifiers = []; + $now = time(); + $before = $options['before']; + $after = $options['after']; + foreach ($identifiers as $tz) { + $abbr = ''; + if ($options['abbr']) { + $dateTimeZone = new DateTimeZone($tz); + $trans = $dateTimeZone->getTransitions($now, $now); + $abbr = isset($trans[0]['abbr']) ? + $before . $trans[0]['abbr'] . $after : + ''; + } + $item = explode('/', $tz, 2); + if (isset($item[1])) { + $groupedIdentifiers[$item[0]][$tz] = $item[1] . $abbr; + } else { + $groupedIdentifiers[$item[0]] = [$tz => $item[0] . $abbr]; + } + } + + return $groupedIdentifiers; + } + + return array_combine($identifiers, $identifiers); + } + + /** + * Returns a string that should be serialized when converting this object to JSON + * + * @return string|int + */ + public function jsonSerialize(): mixed + { + if (static::$_jsonEncodeFormat instanceof Closure) { + return call_user_func(static::$_jsonEncodeFormat, $this); + } + + return $this->i18nFormat(static::$_jsonEncodeFormat); + } + + /** + * @inheritDoc + */ + public function __toString(): string + { + return (string)$this->i18nFormat(); + } +} + +// phpcs:disable +class_alias('Cake\I18n\DateTime', 'Cake\I18n\FrozenTime'); +// phpcs:enable diff --git a/app/vendor/cakephp/cakephp/src/I18n/FormatterLocator.php b/app/vendor/cakephp/cakephp/src/I18n/FormatterLocator.php index d1d1fa0be..0398cd097 100644 --- a/app/vendor/cakephp/cakephp/src/I18n/FormatterLocator.php +++ b/app/vendor/cakephp/cakephp/src/I18n/FormatterLocator.php @@ -31,7 +31,7 @@ class FormatterLocator * * @var array> */ - protected $registry = []; + protected array $registry = []; /** * Tracks whether a registry entry has been converted from a @@ -39,7 +39,7 @@ class FormatterLocator * * @var array */ - protected $converted = []; + protected array $converted = []; /** * Constructor. @@ -77,7 +77,7 @@ public function set(string $name, string $className): void public function get(string $name): FormatterInterface { if (!isset($this->registry[$name])) { - throw new I18nException("Formatter named `{$name}` has not been registered"); + throw new I18nException(sprintf('Formatter named `%s` has not been registered.', $name)); } if (!$this->converted[$name]) { diff --git a/app/vendor/cakephp/cakephp/src/I18n/FrozenDate.php b/app/vendor/cakephp/cakephp/src/I18n/FrozenDate.php index 84d11f1cd..bec29c82f 100644 --- a/app/vendor/cakephp/cakephp/src/I18n/FrozenDate.php +++ b/app/vendor/cakephp/cakephp/src/I18n/FrozenDate.php @@ -1,178 +1,9 @@ |string|int - * @see \Cake\I18n\DateFormatTrait::i18nFormat() - */ - protected static $_toStringFormat = [IntlDateFormatter::SHORT, -1]; - - /** - * The format to use when converting this object to JSON. - * - * The format should be either the formatting constants from IntlDateFormatter as - * described in (https://secure.php.net/manual/en/class.intldateformatter.php) or a pattern - * as specified in (https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/classSimpleDateFormat.html#details) - * - * It is possible to provide an array of 2 constants. In this case, the first position - * will be used for formatting the date part of the object and the second position - * will be used to format the time part. - * - * @var \Closure|array|string|int - * @see \Cake\I18n\Time::i18nFormat() - */ - protected static $_jsonEncodeFormat = 'yyyy-MM-dd'; - - /** - * The format to use when formatting a time using `Cake\I18n\Date::timeAgoInWords()` - * and the difference is more than `Cake\I18n\Date::$wordEnd` - * - * @var array|string|int - * @see \Cake\I18n\DateFormatTrait::parseDate() - */ - public static $wordFormat = [IntlDateFormatter::SHORT, IntlDateFormatter::NONE]; - - /** - * The format to use when formatting a time using `Cake\I18n\Date::nice()` - * - * The format should be either the formatting constants from IntlDateFormatter as - * described in (https://secure.php.net/manual/en/class.intldateformatter.php) or a pattern - * as specified in (https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/classSimpleDateFormat.html#details) - * - * It is possible to provide an array of 2 constants. In this case, the first position - * will be used for formatting the date part of the object and the second position - * will be used to format the time part. - * - * @var array|string|int - * @see \Cake\I18n\DateFormatTrait::nice() - */ - public static $niceFormat = [IntlDateFormatter::MEDIUM, IntlDateFormatter::NONE]; - - /** - * The format to use when formatting a time using `Date::timeAgoInWords()` - * and the difference is less than `Date::$wordEnd` - * - * @var array - * @see \Cake\I18n\Date::timeAgoInWords() - */ - public static $wordAccuracy = [ - 'year' => 'day', - 'month' => 'day', - 'week' => 'day', - 'day' => 'day', - 'hour' => 'day', - 'minute' => 'day', - 'second' => 'day', - ]; - - /** - * The end of relative time telling - * - * @var string - * @see \Cake\I18n\Date::timeAgoInWords() - */ - public static $wordEnd = '+1 month'; - - /** - * Create a new Date instance. - * - * You can specify the timezone for the $time parameter. This timezone will - * not be used in any future modifications to the Date instance. - * - * The `$timezone` parameter is ignored if `$time` is a DateTimeInterface - * instance. - * - * Date instances lack time components, however due to limitations in PHP's - * internal Datetime object the time will always be set to 00:00:00, and the - * timezone will always be the server local time. Normalizing the timezone allows for - * subtraction/addition to have deterministic results. - * - * @param \DateTime|\DateTimeImmutable|string|int|null $time Fixed or relative time - * @param \DateTimeZone|string|null $tz The timezone in which the date is taken. - * Ignored if `$time` is a DateTimeInterface instance. - */ - public function __construct($time = 'now', $tz = null) - { - parent::__construct($time, $tz); - } - - /** - * Returns either a relative or a formatted absolute date depending - * on the difference between the current date and this object. - * - * ### Options: - * - * - `from` => another Date object representing the "now" date - * - `format` => a fall back format if the relative time is longer than the duration specified by end - * - `accuracy` => Specifies how accurate the date should be described (array) - * - year => The format if years > 0 (default "day") - * - month => The format if months > 0 (default "day") - * - week => The format if weeks > 0 (default "day") - * - day => The format if weeks > 0 (default "day") - * - `end` => The end of relative date telling - * - `relativeString` => The printf compatible string when outputting relative date - * - `absoluteString` => The printf compatible string when outputting absolute date - * - `timezone` => The user timezone the timestamp should be formatted in. - * - * Relative dates look something like this: - * - * - 3 weeks, 4 days ago - * - 1 day ago - * - * Default date formatting is d/M/YY e.g: on 18/2/09. Formatting is done internally using - * `i18nFormat`, see the method for the valid formatting strings. - * - * The returned string includes 'ago' or 'on' and assumes you'll properly add a word - * like 'Posted ' before the function output. - * - * NOTE: If the difference is one week or more, the lowest level of accuracy is day. - * - * @param array $options Array of options. - * @return string Relative time string. - */ - public function timeAgoInWords(array $options = []): string - { - /** @psalm-suppress UndefinedInterfaceMethod */ - return static::getDiffFormatter()->dateAgoInWords($this, $options); - } -} +class_exists(Date::class); diff --git a/app/vendor/cakephp/cakephp/src/I18n/FrozenTime.php b/app/vendor/cakephp/cakephp/src/I18n/FrozenTime.php index f1e6d6215..710511352 100644 --- a/app/vendor/cakephp/cakephp/src/I18n/FrozenTime.php +++ b/app/vendor/cakephp/cakephp/src/I18n/FrozenTime.php @@ -1,263 +1,9 @@ |string|int - * @see \Cake\I18n\FrozenTime::i18nFormat() - */ - protected static $_toStringFormat = [IntlDateFormatter::SHORT, IntlDateFormatter::SHORT]; - - /** - * The format to use when converting this object to JSON. - * - * The format should be either the formatting constants from IntlDateFormatter as - * described in (https://secure.php.net/manual/en/class.intldateformatter.php) or a pattern - * as specified in (https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/classSimpleDateFormat.html#details) - * - * It is possible to provide an array of 2 constants. In this case, the first position - * will be used for formatting the date part of the object and the second position - * will be used to format the time part. - * - * @var \Closure|array|string|int - * @see \Cake\I18n\Time::i18nFormat() - */ - protected static $_jsonEncodeFormat = "yyyy-MM-dd'T'HH':'mm':'ssxxx"; - - /** - * The format to use when formatting a time using `Cake\I18n\FrozenTime::nice()` - * - * The format should be either the formatting constants from IntlDateFormatter as - * described in (https://secure.php.net/manual/en/class.intldateformatter.php) or a pattern - * as specified in (https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/classSimpleDateFormat.html#details) - * - * It is possible to provide an array of 2 constants. In this case, the first position - * will be used for formatting the date part of the object and the second position - * will be used to format the time part. - * - * @var array|string|int - * @see \Cake\I18n\FrozenTime::nice() - */ - public static $niceFormat = [IntlDateFormatter::MEDIUM, IntlDateFormatter::SHORT]; - - /** - * The format to use when formatting a time using `Cake\I18n\FrozenTime::timeAgoInWords()` - * and the difference is more than `Cake\I18n\FrozenTime::$wordEnd` - * - * @var array|string|int - * @see \Cake\I18n\FrozenTime::timeAgoInWords() - */ - public static $wordFormat = [IntlDateFormatter::SHORT, IntlDateFormatter::NONE]; - - /** - * The format to use when formatting a time using `Time::timeAgoInWords()` - * and the difference is less than `Time::$wordEnd` - * - * @var array - * @see \Cake\I18n\FrozenTime::timeAgoInWords() - */ - public static $wordAccuracy = [ - 'year' => 'day', - 'month' => 'day', - 'week' => 'day', - 'day' => 'hour', - 'hour' => 'minute', - 'minute' => 'minute', - 'second' => 'second', - ]; - - /** - * The end of relative time telling - * - * @var string - * @see \Cake\I18n\FrozenTime::timeAgoInWords() - */ - public static $wordEnd = '+1 month'; - - /** - * serialise the value as a Unix Timestamp - * - * @var string - */ - public const UNIX_TIMESTAMP_FORMAT = 'unixTimestampFormat'; - - /** - * Create a new immutable time instance. - * - * @param \DateTimeInterface|string|int|null $time Fixed or relative time - * @param \DateTimeZone|string|null $tz The timezone for the instance - */ - public function __construct($time = null, $tz = null) - { - if ($time instanceof DateTimeInterface) { - $tz = $time->getTimezone(); - $time = $time->format('Y-m-d H:i:s.u'); - } - - if (is_numeric($time)) { - $time = '@' . $time; - } - - parent::__construct($time, $tz); - } - - /** - * Returns either a relative or a formatted absolute date depending - * on the difference between the current time and this object. - * - * ### Options: - * - * - `from` => another Time object representing the "now" time - * - `format` => a fall back format if the relative time is longer than the duration specified by end - * - `accuracy` => Specifies how accurate the date should be described (array) - * - year => The format if years > 0 (default "day") - * - month => The format if months > 0 (default "day") - * - week => The format if weeks > 0 (default "day") - * - day => The format if weeks > 0 (default "hour") - * - hour => The format if hours > 0 (default "minute") - * - minute => The format if minutes > 0 (default "minute") - * - second => The format if seconds > 0 (default "second") - * - `end` => The end of relative time telling - * - `relativeString` => The printf compatible string when outputting relative time - * - `absoluteString` => The printf compatible string when outputting absolute time - * - `timezone` => The user timezone the timestamp should be formatted in. - * - * Relative dates look something like this: - * - * - 3 weeks, 4 days ago - * - 15 seconds ago - * - * Default date formatting is d/M/YY e.g: on 18/2/09. Formatting is done internally using - * `i18nFormat`, see the method for the valid formatting strings - * - * The returned string includes 'ago' or 'on' and assumes you'll properly add a word - * like 'Posted ' before the function output. - * - * NOTE: If the difference is one week or more, the lowest level of accuracy is day - * - * @param array $options Array of options. - * @return string Relative time string. - */ - public function timeAgoInWords(array $options = []): string - { - /** @psalm-suppress UndefinedInterfaceMethod */ - return static::getDiffFormatter()->timeAgoInWords($this, $options); - } - - /** - * Get list of timezone identifiers - * - * @param string|int|null $filter A regex to filter identifier - * Or one of DateTimeZone class constants - * @param string|null $country A two-letter ISO 3166-1 compatible country code. - * This option is only used when $filter is set to DateTimeZone::PER_COUNTRY - * @param array|bool $options If true (default value) groups the identifiers list by primary region. - * Otherwise, an array containing `group`, `abbr`, `before`, and `after` - * keys. Setting `group` and `abbr` to true will group results and append - * timezone abbreviation in the display value. Set `before` and `after` - * to customize the abbreviation wrapper. - * @return array List of timezone identifiers - * @since 2.2 - */ - public static function listTimezones($filter = null, ?string $country = null, $options = []): array - { - if (is_bool($options)) { - $options = [ - 'group' => $options, - ]; - } - $defaults = [ - 'group' => true, - 'abbr' => false, - 'before' => ' - ', - 'after' => null, - ]; - $options += $defaults; - $group = $options['group']; - - $regex = null; - if (is_string($filter)) { - $regex = $filter; - $filter = null; - } - if ($filter === null) { - $filter = DateTimeZone::ALL; - } - $identifiers = DateTimeZone::listIdentifiers($filter, (string)$country) ?: []; - - if ($regex) { - foreach ($identifiers as $key => $tz) { - if (!preg_match($regex, $tz)) { - unset($identifiers[$key]); - } - } - } - - if ($group) { - $groupedIdentifiers = []; - $now = time(); - $before = $options['before']; - $after = $options['after']; - foreach ($identifiers as $tz) { - $abbr = ''; - if ($options['abbr']) { - $dateTimeZone = new DateTimeZone($tz); - $trans = $dateTimeZone->getTransitions($now, $now); - $abbr = isset($trans[0]['abbr']) ? - $before . $trans[0]['abbr'] . $after : - ''; - } - $item = explode('/', $tz, 2); - if (isset($item[1])) { - $groupedIdentifiers[$item[0]][$tz] = $item[1] . $abbr; - } else { - $groupedIdentifiers[$item[0]] = [$tz => $item[0] . $abbr]; - } - } - - return $groupedIdentifiers; - } - - return array_combine($identifiers, $identifiers); - } -} +class_exists(DateTime::class); diff --git a/app/vendor/cakephp/cakephp/src/I18n/I18n.php b/app/vendor/cakephp/cakephp/src/I18n/I18n.php index 1cf3a559c..03d5c92f9 100644 --- a/app/vendor/cakephp/cakephp/src/I18n/I18n.php +++ b/app/vendor/cakephp/cakephp/src/I18n/I18n.php @@ -17,10 +17,12 @@ namespace Cake\I18n; use Cake\Cache\Cache; +use Cake\Cache\Exception\InvalidArgumentException; use Cake\I18n\Exception\I18nException; use Cake\I18n\Formatter\IcuFormatter; use Cake\I18n\Formatter\SprintfFormatter; use Locale; +use function Cake\Core\deprecationWarning; /** * I18n handles translation of Text and time format strings. @@ -39,14 +41,14 @@ class I18n * * @var \Cake\I18n\TranslatorRegistry|null */ - protected static $_collection; + protected static ?TranslatorRegistry $_collection = null; /** * The environment default locale * * @var string|null */ - protected static $_defaultLocale; + protected static ?string $_defaultLocale = null; /** * Returns the translators collection instance. It can be used @@ -67,11 +69,20 @@ public static function translators(): TranslatorRegistry 'default' => IcuFormatter::class, 'sprintf' => SprintfFormatter::class, ]), - static::getLocale() + static::getLocale(), ); if (class_exists(Cache::class)) { - static::$_collection->setCacher(Cache::pool('_cake_core_')); + try { + $pool = Cache::pool('_cake_translations_'); + } catch (InvalidArgumentException) { + $pool = Cache::pool('_cake_core_'); + deprecationWarning( + '5.1.0', + 'Cache config `_cake_core_` is deprecated. Use `_cake_translations_` instead', + ); + } + static::$_collection->setCacher($pool); } return static::$_collection; @@ -140,6 +151,7 @@ public static function getTranslator(string $name = 'default', ?string $locale = { $translators = static::translators(); + $currentLocale = null; if ($locale) { $currentLocale = $translators->getLocale(); $translators->setLocale($locale); @@ -148,12 +160,12 @@ public static function getTranslator(string $name = 'default', ?string $locale = $translator = $translators->get($name); if ($translator === null) { throw new I18nException(sprintf( - 'Translator for domain "%s" could not be found.', - $name + 'Translator for domain `%s` could not be found.', + $name, )); } - if (isset($currentLocale)) { + if ($currentLocale !== null) { $translators->setLocale($currentLocale); } @@ -253,11 +265,7 @@ public static function getLocale(): string */ public static function getDefaultLocale(): string { - if (static::$_defaultLocale === null) { - static::$_defaultLocale = Locale::getDefault() ?: static::DEFAULT_LOCALE; - } - - return static::$_defaultLocale; + return static::$_defaultLocale ??= Locale::getDefault() ?: static::DEFAULT_LOCALE; } /** diff --git a/app/vendor/cakephp/cakephp/src/I18n/I18nDateTimeInterface.php b/app/vendor/cakephp/cakephp/src/I18n/I18nDateTimeInterface.php deleted file mode 100644 index 52a5fc153..000000000 --- a/app/vendor/cakephp/cakephp/src/I18n/I18nDateTimeInterface.php +++ /dev/null @@ -1,236 +0,0 @@ -i18nFormat(); // outputs '4/20/14, 10:10 PM' for the en-US locale - * $time->i18nFormat(\IntlDateFormatter::FULL); // Use the full date and time format - * $time->i18nFormat([\IntlDateFormatter::FULL, \IntlDateFormatter::SHORT]); // Use full date but short time format - * $time->i18nFormat('yyyy-MM-dd HH:mm:ss'); // outputs '2014-04-20 22:10' - * $time->i18nFormat(Time::UNIX_TIMESTAMP_FORMAT); // outputs '1398031800' - * ``` - * - * If you wish to control the default format to be used for this method, you can alter - * the value of the static `Time::$defaultLocale` variable and set it to one of the - * possible formats accepted by this function. - * - * You can read about the available IntlDateFormatter constants at - * https://secure.php.net/manual/en/class.intldateformatter.php - * - * If you need to display the date in a different timezone than the one being used for - * this Time object without altering its internal state, you can pass a timezone - * string or object as the second parameter. - * - * Finally, should you need to use a different locale for displaying this time object, - * pass a locale string as the third parameter to this function. - * - * ### Examples - * - * ``` - * $time = new Time('2014-04-20 22:10'); - * $time->i18nFormat(null, null, 'de-DE'); - * $time->i18nFormat(\IntlDateFormatter::FULL, 'Europe/Berlin', 'de-DE'); - * ``` - * - * You can control the default locale to be used by setting the static variable - * `Time::$defaultLocale` to a valid locale string. If empty, the default will be - * taken from the `intl.default_locale` ini config. - * - * @param string|int|null $format Format string. - * @param \DateTimeZone|string|null $timezone Timezone string or DateTimeZone object - * in which the date will be displayed. The timezone stored for this object will not - * be changed. - * @param string|null $locale The locale name in which the date should be displayed (e.g. pt-BR) - * @return string|int Formatted and translated date string - */ - public function i18nFormat($format = null, $timezone = null, $locale = null); - - /** - * Resets the format used to the default when converting an instance of this type to - * a string - * - * @return void - */ - public static function resetToStringFormat(): void; - - /** - * Sets the default format used when type converting instances of this type to string - * - * @param array|string|int $format Format. - * @return void - */ - public static function setToStringFormat($format): void; - - /** - * Sets the default format used when converting this object to JSON - * - * The format should be either the formatting constants from IntlDateFormatter as - * described in (https://secure.php.net/manual/en/class.intldateformatter.php) or a pattern - * as specified in (https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/classSimpleDateFormat.html#details) - * - * It is possible to provide an array of 2 constants. In this case, the first position - * will be used for formatting the date part of the object and the second position - * will be used to format the time part. - * - * Alternatively, the format can provide a callback. In this case, the callback - * can receive this datetime object and return a formatted string. - * - * @see \Cake\I18n\Time::i18nFormat() - * @param \Closure|array|string|int $format Format. - * @return void - */ - public static function setJsonEncodeFormat($format): void; - - /** - * Returns a new Time object after parsing the provided time string based on - * the passed or configured date time format. This method is locale dependent, - * Any string that is passed to this function will be interpreted as a locale - * dependent string. - * - * When no $format is provided, the `toString` format will be used. - * - * If it was impossible to parse the provided time, null will be returned. - * - * Example: - * - * ``` - * $time = Time::parseDateTime('10/13/2013 12:54am'); - * $time = Time::parseDateTime('13 Oct, 2013 13:54', 'dd MMM, y H:mm'); - * $time = Time::parseDateTime('10/10/2015', [IntlDateFormatter::SHORT, -1]); - * ``` - * - * @param string $time The time string to parse. - * @param array|string|null $format Any format accepted by IntlDateFormatter. - * @param \DateTimeZone|string|null $tz The timezone for the instance - * @return static|null - * @throws \InvalidArgumentException If $format is a single int instead of array of constants - */ - public static function parseDateTime(string $time, $format = null, $tz = null); - - /** - * Returns a new Time object after parsing the provided $date string based on - * the passed or configured date time format. This method is locale dependent, - * Any string that is passed to this function will be interpreted as a locale - * dependent string. - * - * When no $format is provided, the `wordFormat` format will be used. - * - * If it was impossible to parse the provided time, null will be returned. - * - * Example: - * - * ``` - * $time = Time::parseDate('10/13/2013'); - * $time = Time::parseDate('13 Oct, 2013', 'dd MMM, y'); - * $time = Time::parseDate('13 Oct, 2013', IntlDateFormatter::SHORT); - * ``` - * - * @param string $date The date string to parse. - * @param array|string|int|null $format Any format accepted by IntlDateFormatter. - * @return static|null - */ - public static function parseDate(string $date, $format = null); - - /** - * Returns a new Time object after parsing the provided $time string based on - * the passed or configured date time format. This method is locale dependent, - * Any string that is passed to this function will be interpreted as a locale - * dependent string. - * - * When no $format is provided, the IntlDateFormatter::SHORT format will be used. - * - * If it was impossible to parse the provided time, null will be returned. - * - * Example: - * - * ``` - * $time = Time::parseTime('11:23pm'); - * ``` - * - * @param string $time The time string to parse. - * @param string|int|null $format Any format accepted by IntlDateFormatter. - * @return static|null - */ - public static function parseTime(string $time, $format = null); - - /** - * Get the difference formatter instance. - * - * @return \Cake\Chronos\DifferenceFormatterInterface The formatter instance. - */ - public static function getDiffFormatter(): DifferenceFormatterInterface; - - /** - * Set the difference formatter instance. - * - * @param \Cake\Chronos\DifferenceFormatterInterface $formatter The formatter instance when setting. - * @return void - */ - public static function setDiffFormatter(DifferenceFormatterInterface $formatter): void; -} diff --git a/app/vendor/cakephp/cakephp/src/I18n/MessagesFileLoader.php b/app/vendor/cakephp/cakephp/src/I18n/MessagesFileLoader.php index d47e86923..8a57871f7 100644 --- a/app/vendor/cakephp/cakephp/src/I18n/MessagesFileLoader.php +++ b/app/vendor/cakephp/cakephp/src/I18n/MessagesFileLoader.php @@ -17,10 +17,10 @@ namespace Cake\I18n; use Cake\Core\App; +use Cake\Core\Exception\CakeException; use Cake\Core\Plugin; use Cake\Utility\Inflector; use Locale; -use RuntimeException; use function Cake\Core\pluginSplit; /** @@ -36,34 +36,34 @@ class MessagesFileLoader * * @var string */ - protected $_name; + protected string $_name; /** * The package (domain) plugin * * @var string|null */ - protected $_plugin; + protected ?string $_plugin = null; /** * The locale to load for the given package. * * @var string */ - protected $_locale; + protected string $_locale; /** * The extension name. * * @var string */ - protected $_extension; + protected string $_extension; /** * Creates a translation file loader. The file to be loaded corresponds to * the following rules: * - * - The locale is a folder under the `Locale` directory, a fallback will be + * - The locale is a folder under the `resources/locales/` directory, a fallback will be * used if the folder is not found. * - The $name corresponds to the file name to load * - If there is a loaded plugin with the underscored version of $name, the @@ -90,6 +90,8 @@ class MessagesFileLoader * ``` * $loader = new MessagesFileLoader('my_plugin', 'fr_FR', 'mo'); * $package = $loader(); + * + * Vendor prefixed plugins are expected to use `my_prefix_my_plugin` syntax. * ``` * * @param string $name The name (domain) of the translations package. @@ -117,40 +119,27 @@ public function __construct(string $name, string $locale, string $extension = 'p * package containing the messages loaded from the file. * * @return \Cake\I18n\Package|false - * @throws \RuntimeException if no file parser class could be found for the specified + * @throws \Cake\Core\Exception\CakeException if no file parser class could be found for the specified * file extension. */ - public function __invoke() + public function __invoke(): Package|false { $folders = $this->translationsFolders(); - $ext = $this->_extension; - $file = false; - - $fileName = $this->_name; - $pos = strpos($fileName, '/'); - if ($pos !== false) { - $fileName = substr($fileName, $pos + 1); - } - foreach ($folders as $folder) { - $path = $folder . $fileName . ".$ext"; - if (is_file($path)) { - $file = $path; - break; - } - } - + $file = $this->translationFile($folders, $this->_name, $this->_extension); if (!$file) { return false; } - $name = ucfirst($ext); + $name = ucfirst($this->_extension); $class = App::className($name, 'I18n\Parser', 'FileParser'); if (!$class) { - throw new RuntimeException(sprintf('Could not find class %s', "{$name}FileParser")); + throw new CakeException(sprintf('Could not find class `%s`.', "{$name}FileParser")); } - $messages = (new $class())->parse($file); + /** @var \Cake\I18n\Parser\MoFileParser|\Cake\I18n\Parser\PoFileParser $object */ + $object = new $class(); + $messages = $object->parse($file); $package = new Package('default'); $package->setMessages($messages); @@ -168,33 +157,57 @@ public function translationsFolders(): array $locale = Locale::parseLocale($this->_locale) + ['region' => null]; $folders = [ - implode('_', [$locale['language'], $locale['region']]), $locale['language'], + // gettext compatible paths, see https://www.php.net/manual/en/function.gettext.php + $locale['language'] . DIRECTORY_SEPARATOR . 'LC_MESSAGES', ]; + if ($locale['region']) { + $languageRegion = implode('_', [$locale['language'], $locale['region']]); + $folders[] = $languageRegion; + // gettext compatible paths, see https://www.php.net/manual/en/function.gettext.php + $folders[] = $languageRegion . DIRECTORY_SEPARATOR . 'LC_MESSAGES'; + } $searchPaths = []; $localePaths = App::path('locales'); - if (empty($localePaths) && defined('APP')) { - $localePaths[] = ROOT . 'resources' . DIRECTORY_SEPARATOR . 'locales' . DIRECTORY_SEPARATOR; + if (!$localePaths && defined('ROOT')) { + $localePaths[] = ROOT . DIRECTORY_SEPARATOR + . 'resources' . DIRECTORY_SEPARATOR + . 'locales' . DIRECTORY_SEPARATOR; + } + if ($this->_plugin && Plugin::isLoaded($this->_plugin)) { + $localePaths[] = App::path('locales', $this->_plugin)[0]; } foreach ($localePaths as $path) { foreach ($folders as $folder) { $searchPaths[] = $path . $folder . DIRECTORY_SEPARATOR; - // gettext compatible paths, see https://www.php.net/manual/en/function.gettext.php - $searchPaths[] = $path . $folder . DIRECTORY_SEPARATOR . 'LC_MESSAGES' . DIRECTORY_SEPARATOR; } } - if ($this->_plugin && Plugin::isLoaded($this->_plugin)) { - $basePath = App::path('locales', $this->_plugin)[0]; - foreach ($folders as $folder) { - $searchPaths[] = $basePath . $folder . DIRECTORY_SEPARATOR; - // gettext compatible paths, see https://www.php.net/manual/en/function.gettext.php - $searchPaths[] = $basePath . $folder . DIRECTORY_SEPARATOR . 'LC_MESSAGES' . DIRECTORY_SEPARATOR; + return $searchPaths; + } + + /** + * @param array $folders Folders + * @param string $name File name + * @param string $ext File extension + * @return string|null File if found + */ + protected function translationFile(array $folders, string $name, string $ext): ?string + { + $file = null; + + $name = str_replace('/', '_', $name); + + foreach ($folders as $folder) { + $path = "{$folder}{$name}.{$ext}"; + if (is_file($path)) { + $file = $path; + break; } } - return $searchPaths; + return $file; } } diff --git a/app/vendor/cakephp/cakephp/src/I18n/Middleware/LocaleSelectorMiddleware.php b/app/vendor/cakephp/cakephp/src/I18n/Middleware/LocaleSelectorMiddleware.php index 6d32035c2..d9726698c 100644 --- a/app/vendor/cakephp/cakephp/src/I18n/Middleware/LocaleSelectorMiddleware.php +++ b/app/vendor/cakephp/cakephp/src/I18n/Middleware/LocaleSelectorMiddleware.php @@ -34,7 +34,7 @@ class LocaleSelectorMiddleware implements MiddlewareInterface * * @var array */ - protected $locales = []; + protected array $locales = []; /** * Constructor. @@ -63,7 +63,7 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface if ($this->locales !== ['*']) { $locale = Locale::lookup($this->locales, $locale, true); } - if ($locale || $this->locales === ['*']) { + if ($locale) { I18n::setLocale($locale); } diff --git a/app/vendor/cakephp/cakephp/src/I18n/Number.php b/app/vendor/cakephp/cakephp/src/I18n/Number.php index 6c12f715a..1cee7adb0 100644 --- a/app/vendor/cakephp/cakephp/src/I18n/Number.php +++ b/app/vendor/cakephp/cakephp/src/I18n/Number.php @@ -17,14 +17,13 @@ namespace Cake\I18n; use NumberFormatter; -use function Cake\Core\deprecationWarning; /** * Number helper library. * * Methods to make numbers more readable. * - * @link https://book.cakephp.org/4/en/core-libraries/number.html + * @link https://book.cakephp.org/5/en/core-libraries/number.html */ class Number { @@ -49,35 +48,26 @@ class Number */ public const FORMAT_CURRENCY_ACCOUNTING = 'currency_accounting'; - /** - * ICU Constant for accounting format; not yet widely supported by INTL library. - * This will be able to go away once CakePHP minimum PHP requirement is 7.4.1 or higher. - * See UNUM_CURRENCY_ACCOUNTING in https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/unum_8h.html - * - * @var int - */ - public const CURRENCY_ACCOUNTING = 12; - /** * A list of number formatters indexed by locale and type * * @var array> */ - protected static $_formatters = []; + protected static array $_formatters = []; /** * Default currency used by Number::currency() * * @var string|null */ - protected static $_defaultCurrency; + protected static ?string $_defaultCurrency = null; /** * Default currency format used by Number::currency() * * @var string|null */ - protected static $_defaultCurrencyFormat; + protected static ?string $_defaultCurrencyFormat = null; /** * Formats a number with a level of precision. @@ -90,13 +80,13 @@ class Number * @param int $precision The precision of the returned number. * @param array $options Additional options * @return string Formatted float. - * @link https://book.cakephp.org/4/en/core-libraries/number.html#formatting-floating-point-numbers + * @link https://book.cakephp.org/5/en/core-libraries/number.html#formatting-floating-point-numbers */ - public static function precision($value, int $precision = 3, array $options = []): string + public static function precision(string|float|int $value, int $precision = 3, array $options = []): string { $formatter = static::formatter(['precision' => $precision, 'places' => $precision] + $options); - return $formatter->format((float)$value); + return (string)$formatter->format((float)$value); } /** @@ -104,24 +94,20 @@ public static function precision($value, int $precision = 3, array $options = [] * * @param string|float|int $size Size in bytes * @return string Human readable size - * @link https://book.cakephp.org/4/en/core-libraries/number.html#interacting-with-human-readable-values + * @link https://book.cakephp.org/5/en/core-libraries/number.html#interacting-with-human-readable-values */ - public static function toReadableSize($size): string + public static function toReadableSize(string|float|int $size): string { $size = (int)$size; - switch (true) { - case $size < 1024: - return __dn('cake', '{0,number,integer} Byte', '{0,number,integer} Bytes', $size, $size); - case round($size / 1024) < 1024: - return __d('cake', '{0,number,#,###.##} KB', $size / 1024); - case round($size / 1024 / 1024, 2) < 1024: - return __d('cake', '{0,number,#,###.##} MB', $size / 1024 / 1024); - case round($size / 1024 / 1024 / 1024, 2) < 1024: - return __d('cake', '{0,number,#,###.##} GB', $size / 1024 / 1024 / 1024); - default: - return __d('cake', '{0,number,#,###.##} TB', $size / 1024 / 1024 / 1024 / 1024); - } + return match (true) { + $size < 1024 => __dn('cake', '{0,number,integer} Byte', '{0,number,integer} Bytes', $size, $size), + round($size / 1024) < 1024 => __d('cake', '{0,number,#,###.##} KB', $size / 1024), + round($size / 1024 / 1024, 2) < 1024 => __d('cake', '{0,number,#,###.##} MB', $size / 1024 / 1024), + round($size / 1024 / 1024 / 1024, 2) < 1024 => + __d('cake', '{0,number,#,###.##} GB', $size / 1024 / 1024 / 1024), + default => __d('cake', '{0,number,#,###.##} TB', $size / 1024 / 1024 / 1024 / 1024), + }; } /** @@ -136,9 +122,9 @@ public static function toReadableSize($size): string * @param int $precision The precision of the returned number * @param array $options Options * @return string Percentage string - * @link https://book.cakephp.org/4/en/core-libraries/number.html#formatting-percentages + * @link https://book.cakephp.org/5/en/core-libraries/number.html#formatting-percentages */ - public static function toPercentage($value, int $precision = 2, array $options = []): string + public static function toPercentage(string|float|int $value, int $precision = 2, array $options = []): string { $options += ['multiply' => false, 'type' => NumberFormatter::PERCENT]; if (!$options['multiply']) { @@ -160,11 +146,11 @@ public static function toPercentage($value, int $precision = 2, array $options = * - `before` - The string to place before whole numbers, e.g. '[' * - `after` - The string to place after decimal numbers, e.g. ']' * - * @param string|int|float $value A floating point number. + * @param string|float|int $value A floating point number. * @param array $options An array with options. * @return string Formatted number */ - public static function format($value, array $options = []): string + public static function format(string|float|int $value, array $options = []): string { $formatter = static::formatter($options); $options += ['before' => '', 'after' => '']; @@ -203,11 +189,11 @@ public static function parseFloat(string $value, array $options = []): float * - `before` - The string to place before whole numbers, e.g. '[' * - `after` - The string to place after decimal numbers, e.g. ']' * - * @param string|float $value A floating point number + * @param string|float|int $value A floating point number * @param array $options Options list. * @return string formatted delta */ - public static function formatDelta($value, array $options = []): string + public static function formatDelta(string|float|int $value, array $options = []): string { $options += ['places' => 0]; $value = number_format((float)$value, $options['places'], '.', ''); @@ -237,12 +223,12 @@ public static function formatDelta($value, array $options = []): string * - `useIntlCode` - Whether to replace the currency symbol with the international * currency code. * - * @param string|float $value Value to format. + * @param string|float|int $value Value to format. * @param string|null $currency International currency name such as 'USD', 'EUR', 'JPY', 'CAD' * @param array $options Options list. * @return string Number formatted as a currency. */ - public static function currency($value, ?string $currency = null, array $options = []): string + public static function currency(string|float|int $value, ?string $currency = null, array $options = []): string { $value = (float)$value; $currency = $currency ?: static::getDefaultCurrency(); @@ -255,6 +241,7 @@ public static function currency($value, ?string $currency = null, array $options $abs = abs($value); if (!empty($options['fractionSymbol']) && $abs > 0 && $abs < 1) { $value *= 100; + /** @var string $pos */ $pos = $options['fractionPosition'] ?? 'after'; return static::format($value, ['precision' => 0, $pos => $options['fractionSymbol']]); @@ -267,37 +254,6 @@ public static function currency($value, ?string $currency = null, array $options return $before . $value . $after; } - /** - * Getter/setter for default currency. This behavior is *deprecated* and will be - * removed in future versions of CakePHP. - * - * @deprecated 3.9.0 Use {@link getDefaultCurrency()} and {@link setDefaultCurrency()} instead. - * @param string|false|null $currency Default currency string to be used by {@link currency()} - * if $currency argument is not provided. If boolean false is passed, it will clear the - * currently stored value - * @return string|null Currency - */ - public static function defaultCurrency($currency = null): ?string - { - deprecationWarning( - 'Number::defaultCurrency() is deprecated. ' . - 'Use Number::setDefaultCurrency()/getDefaultCurrency() instead.' - ); - - if ($currency === false) { - static::setDefaultCurrency(null); - - // This doesn't seem like a useful result to return, but it's what the old version did. - // Retaining it for backward compatibility. - return null; - } - if ($currency !== null) { - static::setDefaultCurrency($currency); - } - - return static::getDefaultCurrency(); - } - /** * Getter for default currency * @@ -334,11 +290,7 @@ public static function setDefaultCurrency(?string $currency = null): void */ public static function getDefaultCurrencyFormat(): string { - if (static::$_defaultCurrencyFormat === null) { - static::$_defaultCurrencyFormat = static::FORMAT_CURRENCY; - } - - return static::$_defaultCurrencyFormat; + return static::$_defaultCurrencyFormat ??= static::FORMAT_CURRENCY; } /** @@ -349,7 +301,7 @@ public static function getDefaultCurrencyFormat(): string * currently stored value * @return void */ - public static function setDefaultCurrencyFormat($currencyFormat = null): void + public static function setDefaultCurrencyFormat(?string $currencyFormat = null): void { static::$_defaultCurrencyFormat = $currencyFormat; } @@ -378,6 +330,7 @@ public static function setDefaultCurrencyFormat($currencyFormat = null): void */ public static function formatter(array $options = []): NumberFormatter { + /** @var string $locale */ $locale = $options['locale'] ?? ini_get('intl.default_locale'); if (!$locale) { @@ -386,15 +339,11 @@ public static function formatter(array $options = []): NumberFormatter $type = NumberFormatter::DECIMAL; if (!empty($options['type'])) { - $type = $options['type']; + $type = (int)$options['type']; if ($options['type'] === static::FORMAT_CURRENCY) { $type = NumberFormatter::CURRENCY; } elseif ($options['type'] === static::FORMAT_CURRENCY_ACCOUNTING) { - if (defined('NumberFormatter::CURRENCY_ACCOUNTING')) { - $type = NumberFormatter::CURRENCY_ACCOUNTING; - } else { - $type = static::CURRENCY_ACCOUNTING; - } + $type = NumberFormatter::CURRENCY_ACCOUNTING; } } @@ -404,21 +353,6 @@ public static function formatter(array $options = []): NumberFormatter /** @var \NumberFormatter $formatter */ $formatter = static::$_formatters[$locale][$type]; - - // PHP 8.0.0 - 8.0.6 throws an exception when cloning NumberFormatter after a failed parse - if (version_compare(PHP_VERSION, '8.0.6', '>') || version_compare(PHP_VERSION, '8.0.0', '<')) { - $options = array_intersect_key($options, [ - 'places' => null, - 'precision' => null, - 'roundingMode' => null, - 'pattern' => null, - 'useIntlCode' => null, - ]); - if (empty($options)) { - return $formatter; - } - } - $formatter = clone $formatter; return static::_setAttributes($formatter, $options); @@ -436,7 +370,7 @@ public static function config(string $locale, int $type = NumberFormatter::DECIM { static::$_formatters[$locale][$type] = static::_setAttributes( new NumberFormatter($locale, $type), - $options + $options, ); } @@ -491,8 +425,8 @@ protected static function _setAttributes(NumberFormatter $formatter, array $opti * @param array $options An array with options. * @return string */ - public static function ordinal($value, array $options = []): string + public static function ordinal(float|int $value, array $options = []): string { - return static::formatter(['type' => NumberFormatter::ORDINAL] + $options)->format($value); + return (string)static::formatter(['type' => NumberFormatter::ORDINAL] + $options)->format($value); } } diff --git a/app/vendor/cakephp/cakephp/src/I18n/Package.php b/app/vendor/cakephp/cakephp/src/I18n/Package.php index 487f2a821..f9209c2e2 100644 --- a/app/vendor/cakephp/cakephp/src/I18n/Package.php +++ b/app/vendor/cakephp/cakephp/src/I18n/Package.php @@ -27,7 +27,7 @@ class Package * * @var array */ - protected $messages = []; + protected array $messages = []; /** * The name of a fallback package to use when a message key does not @@ -35,14 +35,14 @@ class Package * * @var string|null */ - protected $fallback; + protected ?string $fallback = null; /** * The name of the formatter to use when formatting translated messages. * * @var string */ - protected $formatter; + protected string $formatter; /** * Constructor. @@ -54,7 +54,7 @@ class Package public function __construct( string $formatter = 'default', ?string $fallback = null, - array $messages = [] + array $messages = [], ) { $this->formatter = $formatter; $this->fallback = $fallback; @@ -79,7 +79,7 @@ public function setMessages(array $messages): void * @param array|string $message the actual message * @return void */ - public function addMessage(string $key, $message): void + public function addMessage(string $key, array|string $message): void { $this->messages[$key] = $message; } @@ -111,7 +111,7 @@ public function getMessages(): array * @param string $key the key of the message to return * @return array|string|false The message translation, or false if not found. */ - public function getMessage(string $key) + public function getMessage(string $key): array|string|false { return $this->messages[$key] ?? false; } diff --git a/app/vendor/cakephp/cakephp/src/I18n/PackageLocator.php b/app/vendor/cakephp/cakephp/src/I18n/PackageLocator.php index e4d4296a6..7d26d5a2e 100644 --- a/app/vendor/cakephp/cakephp/src/I18n/PackageLocator.php +++ b/app/vendor/cakephp/cakephp/src/I18n/PackageLocator.php @@ -35,7 +35,7 @@ class PackageLocator * * @var array> */ - protected $registry = []; + protected array $registry = []; /** * Tracks whether a registry entry has been converted from a @@ -43,7 +43,7 @@ class PackageLocator * * @var array> */ - protected $converted = []; + protected array $converted = []; /** * Constructor. @@ -68,7 +68,7 @@ public function __construct(array $registry = []) * @param \Cake\I18n\Package|callable $spec A callable that returns a package or Package instance. * @return void */ - public function set(string $name, string $locale, $spec): void + public function set(string $name, string $locale, Package|callable $spec): void { $this->registry[$name][$locale] = $spec; $this->converted[$name][$locale] = $spec instanceof Package; @@ -84,12 +84,12 @@ public function set(string $name, string $locale, $spec): void public function get(string $name, string $locale): Package { if (!isset($this->registry[$name][$locale])) { - throw new I18nException("Package '$name' with locale '$locale' is not registered."); + throw new I18nException(sprintf('Package `%s` with locale `%s` is not registered.', $name, $locale)); } if (!$this->converted[$name][$locale]) { - /** @var callable $func */ $func = $this->registry[$name][$locale]; + assert(is_callable($func)); $this->registry[$name][$locale] = $func(); $this->converted[$name][$locale] = true; } diff --git a/app/vendor/cakephp/cakephp/src/I18n/Parser/MoFileParser.php b/app/vendor/cakephp/cakephp/src/I18n/Parser/MoFileParser.php index 40696b3e5..52d228ddd 100644 --- a/app/vendor/cakephp/cakephp/src/I18n/Parser/MoFileParser.php +++ b/app/vendor/cakephp/cakephp/src/I18n/Parser/MoFileParser.php @@ -16,7 +16,7 @@ */ namespace Cake\I18n\Parser; -use RuntimeException; +use Cake\Core\Exception\CakeException; /** * Parses file in MO format @@ -55,18 +55,22 @@ class MoFileParser * * @param string $file The file to be parsed. * @return array List of messages extracted from the file - * @throws \RuntimeException If stream content has an invalid format. + * @throws \Cake\Core\Exception\CakeException If stream content has an invalid format. */ - public function parse($file): array + public function parse(string $file): array { $stream = fopen($file, 'rb'); + if ($stream === false) { + throw new CakeException(sprintf('Cannot open resource `%s`', $file)); + } $stat = fstat($stream); - if ($stat['size'] < self::MO_HEADER_SIZE) { - throw new RuntimeException('Invalid format for MO translations file'); + if ($stat === false || $stat['size'] < self::MO_HEADER_SIZE) { + throw new CakeException('Invalid format for MO translations file'); } - $magic = unpack('V1', fread($stream, 4)); + /** @var array $magic */ + $magic = unpack('V1', (string)fread($stream, 4)); $magic = hexdec(substr(dechex(current($magic)), -8)); if ($magic === self::MO_LITTLE_ENDIAN_MAGIC) { @@ -74,7 +78,7 @@ public function parse($file): array } elseif ($magic === self::MO_BIG_ENDIAN_MAGIC) { $isBigEndian = true; } else { - throw new RuntimeException('Invalid format for MO translations file'); + throw new CakeException('Invalid format for MO translations file'); } // offset formatRevision @@ -103,23 +107,27 @@ public function parse($file): array } fseek($stream, $offset); - $singularId = fread($stream, $length); + $singularId = (string)fread($stream, $length); - if (strpos($singularId, "\x04") !== false) { + if (str_contains($singularId, "\x04")) { [$context, $singularId] = explode("\x04", $singularId); } - if (strpos($singularId, "\000") !== false) { + if (str_contains($singularId, "\000")) { [$singularId, $pluralId] = explode("\000", $singularId); } fseek($stream, $offsetTranslated + $i * 8); $length = $this->_readLong($stream, $isBigEndian); + if ($length < 1) { + throw new CakeException('Length must be > 0'); + } + $offset = $this->_readLong($stream, $isBigEndian); fseek($stream, $offset); - $translated = fread($stream, $length); + $translated = (string)fread($stream, $length); - if ($pluralId !== null || strpos($translated, "\000") !== false) { + if ($pluralId !== null || str_contains($translated, "\000")) { $translated = explode("\000", $translated); $plurals = $pluralId !== null ? $translated : null; $translated = $translated[0]; @@ -146,15 +154,16 @@ public function parse($file): array } /** - * Reads an unsigned long from stream respecting endianess. + * Reads an unsigned long from stream respecting endianness. * * @param resource $stream The File being read. * @param bool $isBigEndian Whether the current platform is Big Endian * @return int */ - protected function _readLong($stream, $isBigEndian): int + protected function _readLong($stream, bool $isBigEndian): int { - $result = unpack($isBigEndian ? 'N1' : 'V1', fread($stream, 4)); + /** @var array $result */ + $result = unpack($isBigEndian ? 'N1' : 'V1', (string)fread($stream, 4)); $result = current($result); return (int)substr((string)$result, -8); diff --git a/app/vendor/cakephp/cakephp/src/I18n/Parser/PoFileParser.php b/app/vendor/cakephp/cakephp/src/I18n/Parser/PoFileParser.php index e4aa46270..9f9f33bd1 100644 --- a/app/vendor/cakephp/cakephp/src/I18n/Parser/PoFileParser.php +++ b/app/vendor/cakephp/cakephp/src/I18n/Parser/PoFileParser.php @@ -16,6 +16,7 @@ */ namespace Cake\I18n\Parser; +use Cake\Core\Exception\CakeException; use Cake\I18n\Translator; /** @@ -73,6 +74,9 @@ class PoFileParser public function parse(string $resource): array { $stream = fopen($resource, 'rb'); + if ($stream === false) { + throw new CakeException(sprintf('Cannot open resource `%s`', $resource)); + } $defaults = [ 'ids' => [], @@ -81,6 +85,7 @@ public function parse(string $resource): array $messages = []; $item = $defaults; + /** @var array $stage */ $stage = []; while ($line = fgets($stream)) { @@ -91,43 +96,37 @@ public function parse(string $resource): array $this->_addMessage($messages, $item); $item = $defaults; $stage = []; - } elseif (substr($line, 0, 7) === 'msgid "') { + } elseif (str_starts_with($line, 'msgid "')) { // We start a new msg so save previous $this->_addMessage($messages, $item); $item['ids']['singular'] = substr($line, 7, -1); $stage = ['ids', 'singular']; - } elseif (substr($line, 0, 8) === 'msgstr "') { + } elseif (str_starts_with($line, 'msgstr "')) { $item['translated'] = substr($line, 8, -1); $stage = ['translated']; - } elseif (substr($line, 0, 9) === 'msgctxt "') { + } elseif (str_starts_with($line, 'msgctxt "')) { $item['context'] = substr($line, 9, -1); $stage = ['context']; } elseif ($line[0] === '"') { switch (count($stage)) { case 2: - /** - * @psalm-suppress PossiblyUndefinedArrayOffset - * @psalm-suppress InvalidArrayOffset - * @psalm-suppress PossiblyNullArrayAccess - */ + assert(isset($stage[0])); + assert(isset($stage[1])); $item[$stage[0]][$stage[1]] .= substr($line, 1, -1); break; case 1: - /** - * @psalm-suppress PossiblyUndefinedArrayOffset - * @psalm-suppress PossiblyInvalidOperand - * @psalm-suppress PossiblyNullOperand - */ + assert(isset($stage[0])); $item[$stage[0]] .= substr($line, 1, -1); break; } - } elseif (substr($line, 0, 14) === 'msgid_plural "') { + } elseif (str_starts_with($line, 'msgid_plural "')) { $item['ids']['plural'] = substr($line, 14, -1); $stage = ['ids', 'plural']; - } elseif (substr($line, 0, 7) === 'msgstr[') { - /** @var int $size */ + } elseif (str_starts_with($line, 'msgstr[')) { $size = strpos($line, ']'); + assert(is_int($size)); + $row = (int)substr($line, 7, 1); $item['translated'][$row] = substr($line, $size + 3, -1); $stage = ['translated', $row]; diff --git a/app/vendor/cakephp/cakephp/src/I18n/PluralRules.php b/app/vendor/cakephp/cakephp/src/I18n/PluralRules.php index 8de03d694..114f530ec 100644 --- a/app/vendor/cakephp/cakephp/src/I18n/PluralRules.php +++ b/app/vendor/cakephp/cakephp/src/I18n/PluralRules.php @@ -17,6 +17,7 @@ namespace Cake\I18n; use Cake\Core\Exception\CakeException; +use InvalidArgumentException; use Locale; /** @@ -33,7 +34,7 @@ class PluralRules * * @var array */ - protected static $_rulesMap = [ + protected static array $_rulesMap = [ 'af' => 1, 'am' => 2, 'ar' => 13, @@ -53,14 +54,14 @@ class PluralRules 'el' => 1, 'en' => 1, 'eo' => 1, - 'es' => 1, + 'es' => 17, 'et' => 1, 'eu' => 1, 'fa' => 1, 'fi' => 1, 'fil' => 2, 'fo' => 1, - 'fr' => 2, + 'fr' => 16, 'fur' => 1, 'fy' => 1, 'ga' => 5, @@ -74,7 +75,7 @@ class PluralRules 'hu' => 1, 'id' => 0, 'is' => 15, - 'it' => 1, + 'it' => 17, 'ja' => 0, 'jv' => 0, 'ka' => 0, @@ -106,8 +107,8 @@ class PluralRules 'pap' => 1, 'pl' => 11, 'ps' => 1, - 'pt_BR' => 2, - 'pt' => 1, + 'pt_PT' => 17, + 'pt' => 16, 'ro' => 12, 'ru' => 3, 'sk' => 4, @@ -138,13 +139,16 @@ class PluralRules * @param string $locale The locale to get the rule calculated for. * @param int $n The number to apply the rules to. * @return int The plural rule number that should be used. - * @link http://localization-guide.readthedocs.org/en/latest/l10n/pluralforms.html - * @link https://developer.mozilla.org/en-US/docs/Mozilla/Localization/Localization_and_Plurals#List_of_Plural_Rules + * @link https://php-gettext.github.io/Languages/#47 */ - public static function calculate(string $locale, $n): int + public static function calculate(string $locale, int $n): int { $locale = Locale::canonicalize($locale); + if ($locale === null) { + throw new InvalidArgumentException('Invalid locale provided'); + } + if (!isset(static::$_rulesMap[$locale])) { $locale = explode('_', $locale)[0]; } @@ -153,56 +157,41 @@ public static function calculate(string $locale, $n): int return 0; } - switch (static::$_rulesMap[$locale]) { - case 0: - return 0; - case 1: - return $n === 1 ? 0 : 1; - case 2: - return $n > 1 ? 1 : 0; - case 3: - return $n % 10 === 1 && $n % 100 !== 11 ? 0 : - (($n % 10 >= 2 && $n % 10 <= 4) && ($n % 100 < 10 || $n % 100 >= 20) ? 1 : 2); - case 4: - return $n === 1 ? 0 : - ($n >= 2 && $n <= 4 ? 1 : 2); - case 5: - return $n === 1 ? 0 : - ($n === 2 ? 1 : ($n < 7 ? 2 : ($n < 11 ? 3 : 4))); - case 6: - return $n % 10 === 1 && $n % 100 !== 11 ? 0 : - ($n % 10 >= 2 && ($n % 100 < 10 || $n % 100 >= 20) ? 1 : 2); - case 7: - return $n % 100 === 1 ? 1 : - ($n % 100 === 2 ? 2 : ($n % 100 === 3 || $n % 100 === 4 ? 3 : 0)); - case 8: - return $n % 10 === 1 ? 0 : ($n % 10 === 2 ? 1 : 2); - case 9: - return $n === 1 ? 0 : + return match (static::$_rulesMap[$locale]) { + 0 => 0, + 1 => $n === 1 ? 0 : 1, + 2 => $n > 1 ? 1 : 0, + 3 => $n % 10 === 1 && $n % 100 !== 11 ? 0 : + (($n % 10 >= 2 && $n % 10 <= 4) && ($n % 100 < 10 || $n % 100 >= 20) ? 1 : 2), + 4 => $n === 1 ? 0 : + ($n >= 2 && $n <= 4 ? 1 : 2), + 5 => $n === 1 ? 0 : + ($n === 2 ? 1 : ($n < 7 ? 2 : ($n < 11 ? 3 : 4))), + 6 => $n % 10 === 1 && $n % 100 !== 11 ? 0 : + ($n % 10 >= 2 && ($n % 100 < 10 || $n % 100 >= 20) ? 1 : 2), + 7 => $n % 100 === 1 ? 1 : + ($n % 100 === 2 ? 2 : ($n % 100 === 3 || $n % 100 === 4 ? 3 : 0)), + 8 => $n % 10 === 1 ? 0 : ($n % 10 === 2 ? 1 : 2), + 9 => $n === 1 ? 0 : ($n === 0 || ($n % 100 > 0 && $n % 100 <= 10) ? 1 : - ($n % 100 > 10 && $n % 100 < 20 ? 2 : 3)); - case 10: - return $n % 10 === 1 && $n % 100 !== 11 ? 0 : ($n !== 0 ? 1 : 2); - case 11: - return $n === 1 ? 0 : - ($n % 10 >= 2 && $n % 10 <= 4 && ($n % 100 < 10 || $n % 100 >= 20) ? 1 : 2); - case 12: - return $n === 1 ? 0 : - ($n === 0 || $n % 100 > 0 && $n % 100 < 20 ? 1 : 2); - case 13: - return $n === 0 ? 0 : + ($n % 100 > 10 && $n % 100 < 20 ? 2 : 3)), + 10 => $n % 10 === 1 && $n % 100 !== 11 ? 0 : ($n !== 0 ? 1 : 2), + 11 => $n === 1 ? 0 : + ($n % 10 >= 2 && $n % 10 <= 4 && ($n % 100 < 10 || $n % 100 >= 20) ? 1 : 2), + 12 => $n === 1 ? 0 : + ($n === 0 || $n % 100 > 0 && $n % 100 < 20 ? 1 : 2), + 13 => $n === 0 ? 0 : ($n === 1 ? 1 : ($n === 2 ? 2 : ($n % 100 >= 3 && $n % 100 <= 10 ? 3 : - ($n % 100 >= 11 ? 4 : 5)))); - case 14: - return $n === 1 ? 0 : + ($n % 100 >= 11 ? 4 : 5)))), + 14 => $n === 1 ? 0 : ($n === 2 ? 1 : - ($n !== 8 && $n !== 11 ? 2 : 3)); - case 15: - return $n % 10 !== 1 || $n % 100 === 11 ? 1 : 0; - } - - throw new CakeException('Unable to find plural rule number.'); + ($n !== 8 && $n !== 11 ? 2 : 3)), + 15 => $n % 10 !== 1 || $n % 100 === 11 ? 1 : 0, + 16 => $n === 0 || $n === 1 ? 0 : ($n % 1000000 === 0 ? 1 : 2), + 17 => $n === 1 ? 0 : ($n !== 0 && $n % 1000000 === 0 ? 1 : 2), + default => throw new CakeException('Unable to find plural rule number.'), + }; } } diff --git a/app/vendor/cakephp/cakephp/src/I18n/README.md b/app/vendor/cakephp/cakephp/src/I18n/README.md index e7724b8fe..41a9dd1a4 100644 --- a/app/vendor/cakephp/cakephp/src/I18n/README.md +++ b/app/vendor/cakephp/cakephp/src/I18n/README.md @@ -31,7 +31,7 @@ use Cake\Core\Configure; Configure::write('App.paths.locales', ['/path/with/trailing/slash/']); ``` -Please refer to the [CakePHP Manual](https://book.cakephp.org/4/en/core-libraries/internationalization-and-localization.html#language-files) for details +Please refer to the [CakePHP Manual](https://book.cakephp.org/5/en/core-libraries/internationalization-and-localization.html#language-files) for details about expected folder structure and file naming. ### Translating a Message @@ -92,12 +92,12 @@ echo Number::currency(123456.7890, 'EUR'); ## Documentation Please make sure you check the [official I18n -documentation](https://book.cakephp.org/4/en/core-libraries/internationalization-and-localization.html). +documentation](https://book.cakephp.org/5/en/core-libraries/internationalization-and-localization.html). The [documentation for the Time -class](https://book.cakephp.org/4/en/core-libraries/time.html) contains +class](https://book.cakephp.org/5/en/core-libraries/time.html) contains instructions on how to configure and output time strings for selected locales. The [documentation for the Number -class](https://book.cakephp.org/4/en/core-libraries/number.html) shows how to +class](https://book.cakephp.org/5/en/core-libraries/number.html) shows how to use the `Number` class for displaying numbers in specific locales. diff --git a/app/vendor/cakephp/cakephp/src/I18n/RelativeTimeFormatter.php b/app/vendor/cakephp/cakephp/src/I18n/RelativeTimeFormatter.php index 7615507f5..391cda743 100644 --- a/app/vendor/cakephp/cakephp/src/I18n/RelativeTimeFormatter.php +++ b/app/vendor/cakephp/cakephp/src/I18n/RelativeTimeFormatter.php @@ -16,8 +16,9 @@ */ namespace Cake\I18n; -use Cake\Chronos\ChronosInterface; +use Cake\Chronos\ChronosDate; use Cake\Chronos\DifferenceFormatterInterface; +use DateTimeInterface; /** * Helper class for formatting relative dates & times. @@ -29,23 +30,31 @@ class RelativeTimeFormatter implements DifferenceFormatterInterface /** * Get the difference in a human readable format. * - * @param \Cake\Chronos\ChronosInterface $date The datetime to start with. - * @param \Cake\Chronos\ChronosInterface|null $other The datetime to compare against. + * @param \Cake\Chronos\ChronosDate|\DateTimeInterface $first The datetime to start with. + * @param \Cake\Chronos\ChronosDate|\DateTimeInterface|null $second The datetime to compare against. * @param bool $absolute Removes time difference modifiers ago, after, etc. * @return string The difference between the two days in a human readable format. - * @see \Cake\Chronos\ChronosInterface::diffForHumans + * @see \Cake\Chronos\Chronos::diffForHumans */ public function diffForHumans( - ChronosInterface $date, - ?ChronosInterface $other = null, - bool $absolute = false + ChronosDate|DateTimeInterface $first, + ChronosDate|DateTimeInterface|null $second = null, + bool $absolute = false, ): string { - $isNow = $other === null; - if ($isNow) { - $other = $date->now($date->getTimezone()); + $isNow = $second === null; + if ($second === null) { + if ($first instanceof ChronosDate) { + $second = Date::now(); + } else { + $second = DateTime::now($first->getTimezone()); + } } - /** @psalm-suppress PossiblyNullArgument */ - $diffInterval = $date->diff($other); + assert( + ($first instanceof ChronosDate && $second instanceof ChronosDate) || + ($first instanceof DateTimeInterface && $second instanceof DateTimeInterface), + ); + + $diffInterval = $first->diff($second); switch (true) { case $diffInterval->y > 0: @@ -58,8 +67,8 @@ public function diffForHumans( break; case $diffInterval->d > 0: $count = $diffInterval->d; - if ($count >= I18nDateTimeInterface::DAYS_PER_WEEK) { - $count = (int)($count / I18nDateTimeInterface::DAYS_PER_WEEK); + if ($count >= DateTime::DAYS_PER_WEEK) { + $count = (int)($count / DateTime::DAYS_PER_WEEK); $message = __dn('cake', '{0} week', '{0} weeks', $count, $count); } else { $message = __dn('cake', '{0} day', '{0} days', $count, $count); @@ -92,20 +101,22 @@ public function diffForHumans( /** * Format a into a relative timestring. * - * @param \Cake\I18n\I18nDateTimeInterface $time The time instance to format. + * @param \Cake\I18n\DateTime|\Cake\I18n\Date $time The time instance to format. * @param array $options Array of options. * @return string Relative time string. * @see \Cake\I18n\Time::timeAgoInWords() */ - public function timeAgoInWords(I18nDateTimeInterface $time, array $options = []): string + public function timeAgoInWords(DateTime|Date $time, array $options = []): string { - $options = $this->_options($options, FrozenTime::class); + $options = $this->_options($options, DateTime::class); if ($options['timezone']) { $time = $time->setTimezone($options['timezone']); } - $now = $options['from']->format('U'); - $inSeconds = $time->format('U'); + /** @var \Cake\Chronos\Chronos $from */ + $from = $options['from']; + $now = (int)$from->format('U'); + $inSeconds = (int)$time->format('U'); $backwards = ($inSeconds > $now); $futureTime = $now; @@ -120,7 +131,7 @@ public function timeAgoInWords(I18nDateTimeInterface $time, array $options = []) return __d('cake', 'just now', 'just now'); } - if ($diff > abs($now - (new FrozenTime($options['end']))->format('U'))) { + if ($diff > abs($now - (int)(new DateTime($options['end']))->format('U'))) { return sprintf($options['absoluteString'], $time->i18nFormat($options['format'])); } @@ -192,7 +203,7 @@ public function timeAgoInWords(I18nDateTimeInterface $time, array $options = []) * @param array $options An array of options. * @return array An array of values. */ - protected function _diffData($futureTime, $pastTime, bool $backwards, $options): array + protected function _diffData(string|int $futureTime, string|int $pastTime, bool $backwards, array $options): array { $futureTime = (int)$futureTime; $pastTime = (int)$pastTime; @@ -219,7 +230,11 @@ protected function _diffData($futureTime, $pastTime, bool $backwards, $options): $past['m'], $past['Y'], ] = explode('/', date('H/i/s/d/m/Y', $pastTime)); - $weeks = $days = $hours = $minutes = $seconds = 0; + $weeks = 0; + $days = 0; + $hours = 0; + $minutes = 0; + $seconds = 0; $years = (int)$future['Y'] - (int)$past['Y']; $months = (int)$future['m'] + (12 * $years) - (int)$past['m']; @@ -236,7 +251,7 @@ protected function _diffData($futureTime, $pastTime, bool $backwards, $options): $days = (int)$future['d'] - (int)$past['d']; } else { $daysInPastMonth = (int)date('t', $pastTime); - $daysInFutureMonth = (int)date('t', mktime(0, 0, 0, (int)$future['m'] - 1, 1, (int)$future['Y'])); + $daysInFutureMonth = (int)date('t', (int)mktime(0, 0, 0, (int)$future['m'] - 1, 1, (int)$future['Y'])); if (!$backwards) { $days = $daysInPastMonth - (int)$past['d'] + (int)$future['d']; @@ -264,7 +279,9 @@ protected function _diffData($futureTime, $pastTime, bool $backwards, $options): $days -= $weeks * 7; } } else { - $years = $months = $weeks = 0; + $years = 0; + $months = 0; + $weeks = 0; $days = floor($diff / 86400); $diff -= $days * 86400; @@ -295,7 +312,7 @@ protected function _diffData($futureTime, $pastTime, bool $backwards, $options): $fNum = str_replace( ['year', 'month', 'week', 'day', 'hour', 'minute', 'second'], ['1', '2', '3', '4', '5', '6', '7'], - $fWord + $fWord, ); return [ @@ -314,20 +331,22 @@ protected function _diffData($futureTime, $pastTime, bool $backwards, $options): /** * Format a into a relative date string. * - * @param \Cake\I18n\I18nDateTimeInterface $date The date to format. + * @param \Cake\I18n\DateTime|\Cake\I18n\Date $date The date to format. * @param array $options Array of options. * @return string Relative date string. * @see \Cake\I18n\Date::timeAgoInWords() */ - public function dateAgoInWords(I18nDateTimeInterface $date, array $options = []): string + public function dateAgoInWords(DateTime|Date $date, array $options = []): string { - $options = $this->_options($options, FrozenDate::class); - if ($options['timezone']) { + $options = $this->_options($options, Date::class); + if ($date instanceof DateTime && $options['timezone']) { $date = $date->setTimezone($options['timezone']); } - $now = $options['from']->format('U'); - $inSeconds = $date->format('U'); + /** @var \Cake\Chronos\Chronos $from */ + $from = $options['from']; + $now = (int)$from->format('U'); + $inSeconds = (int)$date->format('U'); $backwards = ($inSeconds > $now); $futureTime = $now; @@ -342,7 +361,7 @@ public function dateAgoInWords(I18nDateTimeInterface $date, array $options = []) return __d('cake', 'today'); } - if ($diff > abs($now - (new FrozenDate($options['end']))->format('U'))) { + if ($diff > abs($now - (int)(new Date($options['end']))->format('U'))) { return sprintf($options['absoluteString'], $date->i18nFormat($options['format'])); } @@ -396,7 +415,7 @@ public function dateAgoInWords(I18nDateTimeInterface $date, array $options = []) * @param array $options The options provided by the user. * @param string $class The class name to use for defaults. * @return array Options with defaults applied. - * @psalm-param class-string<\Cake\I18n\FrozenDate>|class-string<\Cake\I18n\FrozenTime> $class + * @phpstan-param class-string<\Cake\I18n\Date>|class-string<\Cake\I18n\DateTime> $class */ protected function _options(array $options, string $class): array { diff --git a/app/vendor/cakephp/cakephp/src/I18n/Time.php b/app/vendor/cakephp/cakephp/src/I18n/Time.php index 9209418cc..79def737a 100644 --- a/app/vendor/cakephp/cakephp/src/I18n/Time.php +++ b/app/vendor/cakephp/cakephp/src/I18n/Time.php @@ -11,43 +11,41 @@ * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://cakephp.org CakePHP(tm) Project - * @since 3.0.0 + * @since 5.0.0 * @license https://opensource.org/licenses/mit-license.php MIT License */ namespace Cake\I18n; -use Cake\Chronos\MutableDateTime; -use DateTimeInterface; -use DateTimeZone; +use Cake\Chronos\ChronosTime; +use Closure; use IntlDateFormatter; -use function Cake\Core\deprecationWarning; +use InvalidArgumentException; +use JsonSerializable; +use Stringable; /** - * Extends the built-in DateTime class to provide handy methods and locale-aware - * formatting helpers + * Extends time class provided by Chronos. * - * @deprecated 4.3.0 Use the immutable alternative `FrozenTime` instead. + * Adds handy methods and locale-aware formatting helpers. + * + * @phpstan-immutable */ -class Time extends MutableDateTime implements I18nDateTimeInterface +class Time extends ChronosTime implements JsonSerializable, Stringable { use DateFormatTrait; /** * The format to use when formatting a time using `Cake\I18n\Time::i18nFormat()` - * and `__toString`. This format is also used by `parseDateTime()`. + * and `__toString`. * * The format should be either the formatting constants from IntlDateFormatter as * described in (https://secure.php.net/manual/en/class.intldateformatter.php) or a pattern * as specified in (https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/classSimpleDateFormat.html#details) * - * It is possible to provide an array of 2 constants. In this case, the first position - * will be used for formatting the date part of the object and the second position - * will be used to format the time part. - * - * @var array|string|int + * @var string|int * @see \Cake\I18n\Time::i18nFormat() */ - protected static $_toStringFormat = [IntlDateFormatter::SHORT, IntlDateFormatter::SHORT]; + protected static string|int $_toStringFormat = IntlDateFormatter::SHORT; /** * The format to use when converting this object to JSON. @@ -56,14 +54,10 @@ class Time extends MutableDateTime implements I18nDateTimeInterface * described in (https://secure.php.net/manual/en/class.intldateformatter.php) or a pattern * as specified in (https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/classSimpleDateFormat.html#details) * - * It is possible to provide an array of 2 constants. In this case, the first position - * will be used for formatting the date part of the object and the second position - * will be used to format the time part. - * - * @var \Closure|array|string|int - * @see \Cake\I18n\Time::i18nFormat() + * @var \Closure|string|int + * @see \Cake\I18n\Date::i18nFormat() */ - protected static $_jsonEncodeFormat = "yyyy-MM-dd'T'HH':'mm':'ssxxx"; + protected static Closure|string|int $_jsonEncodeFormat = "HH':'mm':'ss"; /** * The format to use when formatting a time using `Cake\I18n\Time::nice()` @@ -72,280 +66,175 @@ class Time extends MutableDateTime implements I18nDateTimeInterface * described in (https://secure.php.net/manual/en/class.intldateformatter.php) or a pattern * as specified in (https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/classSimpleDateFormat.html#details) * - * It is possible to provide an array of 2 constants. In this case, the first position - * will be used for formatting the date part of the object and the second position - * will be used to format the time part. - * - * @var array|string|int + * @var string|int * @see \Cake\I18n\Time::nice() */ - public static $niceFormat = [IntlDateFormatter::MEDIUM, IntlDateFormatter::SHORT]; - - /** - * The format to use when formatting a time using `Cake\I18n\Time::timeAgoInWords()` - * and the difference is more than `Cake\I18n\Time::$wordEnd` - * - * @var array|string|int - * @see \Cake\I18n\Time::timeAgoInWords() - */ - public static $wordFormat = [IntlDateFormatter::SHORT, IntlDateFormatter::NONE]; - - /** - * The format to use when formatting a time using `Time::timeAgoInWords()` - * and the difference is less than `Time::$wordEnd` - * - * @var array - * @see \Cake\I18n\Time::timeAgoInWords() - */ - public static $wordAccuracy = [ - 'year' => 'day', - 'month' => 'day', - 'week' => 'day', - 'day' => 'hour', - 'hour' => 'minute', - 'minute' => 'minute', - 'second' => 'second', - ]; - - /** - * The end of relative time telling - * - * @var string - * @see \Cake\I18n\Time::timeAgoInWords() - */ - public static $wordEnd = '+1 month'; + public static string|int $niceFormat = IntlDateFormatter::MEDIUM; /** - * serialise the value as a Unix Timestamp + * Sets the default format used when type converting instances of this type to string * - * @var string - */ - public const UNIX_TIMESTAMP_FORMAT = 'unixTimestampFormat'; - - /** - * Create a new mutable time instance. + * The format should be either the formatting constants from IntlDateFormatter as + * described in (https://secure.php.net/manual/en/class.intldateformatter.php) or a pattern + * as specified in (https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/classSimpleDateFormat.html#details) * - * @param \DateTimeInterface|string|int|null $time Fixed or relative time - * @param \DateTimeZone|string|null $tz The timezone for the instance + * @param string|int $format Format. + * @return void + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint */ - public function __construct($time = null, $tz = null) + public static function setToStringFormat($format): void { - deprecationWarning( - 'The `Time` class has been deprecated. Use the immutable alternative `FrozenTime` instead', - 0 - ); - - if ($time instanceof DateTimeInterface) { - $tz = $time->getTimezone(); - $time = $time->format('Y-m-d H:i:s.u'); - } - - if (is_numeric($time)) { - $time = '@' . $time; - } - parent::__construct($time, $tz); + static::$_toStringFormat = $format; } /** - * Returns a nicely formatted date string for this object. + * Resets the format used to the default when converting an instance of this type to + * a string * - * The format to be used is stored in the static property `Time::niceFormat`. - * - * @param \DateTimeZone|string|null $timezone Timezone string or DateTimeZone object - * in which the date will be displayed. The timezone stored for this object will not - * be changed. - * @param string|null $locale The locale name in which the date should be displayed (e.g. pt-BR) - * @return string Formatted date string + * @return void */ - public function nice($timezone = null, $locale = null): string + public static function resetToStringFormat(): void { - return (string)$this->i18nFormat(static::$niceFormat, $timezone, $locale); + static::setToStringFormat(IntlDateFormatter::SHORT); } /** - * Returns true if this object represents a date within the current week + * Sets the default format used when converting this object to JSON * - * @return bool - */ - public function isThisWeek(): bool - { - return static::now($this->getTimezone())->format('W o') === $this->format('W o'); - } - - /** - * Returns true if this object represents a date within the current month + * The format should be either the formatting constants from IntlDateFormatter as + * described in (https://secure.php.net/manual/en/class.intldateformatter.php) or a pattern + * as specified in (http://www.icu-project.org/apiref/icu4c/classSimpleDateFormat.html#details) * - * @return bool - */ - public function isThisMonth(): bool - { - return static::now($this->getTimezone())->format('m Y') === $this->format('m Y'); - } - - /** - * Returns true if this object represents a date within the current year + * Alternatively, the format can provide a callback. In this case, the callback + * can receive this object and return a formatted string. * - * @return bool + * @see \Cake\I18n\Time::i18nFormat() + * @param \Closure|string|int $format Format. + * @return void */ - public function isThisYear(): bool + public static function setJsonEncodeFormat(Closure|string|int $format): void { - return static::now($this->getTimezone())->format('Y') === $this->format('Y'); + static::$_jsonEncodeFormat = $format; } /** - * Returns the quarter + * Returns a new Time object after parsing the provided $time string based on + * the passed or configured date time format. This method is locale dependent, + * Any string passed to this function will be interpreted as a locale + * dependent string. + * + * When no $format is provided, the IntlDateFormatter::SHORT format will be used. * - * @param bool $range Range. - * @return array|int 1, 2, 3, or 4 quarter of year, or array if $range true + * If it was impossible to parse the provided time, null will be returned. + * + * Example: + * + * ``` + * $time = Time::parseTime('11:23pm'); + * ``` + * + * @param string $time The time string to parse. + * @param string|int|null $format Any format accepted by IntlDateFormatter. + * @return static|null */ - public function toQuarter(bool $range = false) + public static function parseTime(string $time, string|int|null $format = null): ?static { - $quarter = (int)ceil((int)$this->format('m') / 3); - if ($range === false) { - return $quarter; - } - - $year = $this->format('Y'); - switch ($quarter) { - case 1: - return [$year . '-01-01', $year . '-03-31']; - case 2: - return [$year . '-04-01', $year . '-06-30']; - case 3: - return [$year . '-07-01', $year . '-09-30']; + $format ??= [IntlDateFormatter::NONE, IntlDateFormatter::SHORT]; + if (is_int($format)) { + $format = [IntlDateFormatter::NONE, $format]; } - // 4th quarter - return [$year . '-10-01', $year . '-12-31']; + return static::_parseDateTime($time, $format); } /** - * Returns a UNIX timestamp. + * Returns a formatted string for this time object using the preferred format and + * language for the specified locale. * - * @return string UNIX timestamp - */ - public function toUnixString(): string - { - return $this->format('U'); - } - - /** - * Returns either a relative or a formatted absolute date depending - * on the difference between the current time and this object. + * It is possible to specify the desired format for the string to be displayed. + * You can either pass `IntlDateFormatter` constants as the first argument of this + * function, or pass a full ICU date formatting string as specified in the following + * resource: https://unicode-org.github.io/icu/userguide/format_parse/datetime/#datetime-format-syntax. * - * ### Options: + * ### Examples * - * - `from` => another Time object representing the "now" time - * - `format` => a fall back format if the relative time is longer than the duration specified by end - * - `accuracy` => Specifies how accurate the date should be described (array) - * - year => The format if years > 0 (default "day") - * - month => The format if months > 0 (default "day") - * - week => The format if weeks > 0 (default "day") - * - day => The format if weeks > 0 (default "hour") - * - hour => The format if hours > 0 (default "minute") - * - minute => The format if minutes > 0 (default "minute") - * - second => The format if seconds > 0 (default "second") - * - `end` => The end of relative time telling - * - `relativeString` => The `printf` compatible string when outputting relative time - * - `absoluteString` => The `printf` compatible string when outputting absolute time - * - `timezone` => The user timezone the timestamp should be formatted in. + * ``` + * $time = new Time('23:10:10'); + * $time->i18nFormat(); + * $time->i18nFormat(\IntlDateFormatter::FULL); + * $time->i18nFormat("HH':'mm':'ss"); + * ``` * - * Relative dates look something like this: + * You can control the default format used through `Time::setToStringFormat()`. * - * - 3 weeks, 4 days ago - * - 15 seconds ago + * You can read about the available IntlDateFormatter constants at + * https://secure.php.net/manual/en/class.intldateformatter.php * - * Default date formatting is d/M/YY e.g: on 18/2/09. Formatting is done internally using - * `i18nFormat`, see the method for the valid formatting strings + * Should you need to use a different locale for displaying this time object, + * pass a locale string as the third parameter to this function. * - * The returned string includes 'ago' or 'on' and assumes you'll properly add a word - * like 'Posted ' before the function output. + * ### Examples * - * NOTE: If the difference is one week or more, the lowest level of accuracy is day + * ``` + * $time = new Time('2014-04-20'); + * $time->i18nFormat('de-DE'); + * $time->i18nFormat(\IntlDateFormatter::FULL, 'de-DE'); + * ``` * - * @param array $options Array of options. - * @return string Relative time string. + * You can control the default locale used through `DateTime::setDefaultLocale()`. + * If empty, the default will be taken from the `intl.default_locale` ini config. + * + * @param string|int|null $format Format string. + * @param string|null $locale The locale name in which the time should be displayed (e.g. pt-BR) + * @return string|int Formatted and translated time string */ - public function timeAgoInWords(array $options = []): string - { - /** @psalm-suppress UndefinedInterfaceMethod */ - return static::getDiffFormatter()->timeAgoInWords($this, $options); + public function i18nFormat( + string|int|null $format = null, + ?string $locale = null, + ): string|int { + if ($format === DateTime::UNIX_TIMESTAMP_FORMAT) { + throw new InvalidArgumentException('UNIT_TIMESTAMP_FORMAT is not supported for Time.'); + } + + $format ??= static::$_toStringFormat; + $format = is_int($format) ? [IntlDateFormatter::NONE, $format] : $format; + $locale = $locale ?: DateTime::getDefaultLocale(); + + return $this->_formatObject($this->toNative(), $format, $locale); } /** - * Get list of timezone identifiers + * Returns a nicely formatted date string for this object. + * + * The format to be used is stored in the static property `Time::$niceFormat`. * - * @param string|int|null $filter A regex to filter identifier - * Or one of DateTimeZone class constants - * @param string|null $country A two-letter ISO 3166-1 compatible country code. - * This option is only used when $filter is set to DateTimeZone::PER_COUNTRY - * @param array|bool $options If true (default value) groups the identifiers list by primary region. - * Otherwise, an array containing `group`, `abbr`, `before`, and `after` - * keys. Setting `group` and `abbr` to true will group results and append - * timezone abbreviation in the display value. Set `before` and `after` - * to customize the abbreviation wrapper. - * @return array List of timezone identifiers - * @since 2.2 + * @param string|null $locale The locale name in which the date should be displayed (e.g. pt-BR) + * @return string Formatted date string */ - public static function listTimezones($filter = null, ?string $country = null, $options = []): array + public function nice(?string $locale = null): string { - if (is_bool($options)) { - $options = [ - 'group' => $options, - ]; - } - $defaults = [ - 'group' => true, - 'abbr' => false, - 'before' => ' - ', - 'after' => null, - ]; - $options += $defaults; - $group = $options['group']; - - $regex = null; - if (is_string($filter)) { - $regex = $filter; - $filter = null; - } - if ($filter === null) { - $filter = DateTimeZone::ALL; - } - $identifiers = DateTimeZone::listIdentifiers($filter, (string)$country) ?: []; + return (string)$this->i18nFormat(static::$niceFormat, $locale); + } - if ($regex) { - foreach ($identifiers as $key => $tz) { - if (!preg_match($regex, $tz)) { - unset($identifiers[$key]); - } - } + /** + * Returns a string that should be serialized when converting this object to JSON + * + * @return string|int + */ + public function jsonSerialize(): mixed + { + if (static::$_jsonEncodeFormat instanceof Closure) { + return call_user_func(static::$_jsonEncodeFormat, $this); } - if ($group) { - $groupedIdentifiers = []; - $now = time(); - $before = $options['before']; - $after = $options['after']; - foreach ($identifiers as $tz) { - $abbr = ''; - if ($options['abbr']) { - $dateTimeZone = new DateTimeZone($tz); - $trans = $dateTimeZone->getTransitions($now, $now); - $abbr = isset($trans[0]['abbr']) ? - $before . $trans[0]['abbr'] . $after : - ''; - } - $item = explode('/', $tz, 2); - if (isset($item[1])) { - $groupedIdentifiers[$item[0]][$tz] = $item[1] . $abbr; - } else { - $groupedIdentifiers[$item[0]] = [$tz => $item[0] . $abbr]; - } - } - - return $groupedIdentifiers; - } + return $this->i18nFormat(static::$_jsonEncodeFormat); + } - return array_combine($identifiers, $identifiers); + /** + * @inheritDoc + */ + public function __toString(): string + { + return (string)$this->i18nFormat(); } } diff --git a/app/vendor/cakephp/cakephp/src/I18n/Translator.php b/app/vendor/cakephp/cakephp/src/I18n/Translator.php index f657ac81a..f1e6bda1f 100644 --- a/app/vendor/cakephp/cakephp/src/I18n/Translator.php +++ b/app/vendor/cakephp/cakephp/src/I18n/Translator.php @@ -33,28 +33,28 @@ class Translator * * @var \Cake\I18n\Translator|null */ - protected $fallback; + protected ?Translator $fallback = null; /** * The formatter to use when translating messages. * * @var \Cake\I18n\FormatterInterface */ - protected $formatter; + protected FormatterInterface $formatter; /** * The locale being used for translations. * * @var string */ - protected $locale; + protected string $locale; /** * The Package containing keys and translations. * * @var \Cake\I18n\Package */ - protected $package; + protected Package $package; /** * Constructor @@ -68,7 +68,7 @@ public function __construct( string $locale, Package $package, FormatterInterface $formatter, - ?Translator $fallback = null + ?Translator $fallback = null, ) { $this->locale = $locale; $this->package = $package; @@ -82,7 +82,7 @@ public function __construct( * @param string $key The message key. * @return mixed The message translation string, or false if not found. */ - protected function getMessage(string $key) + protected function getMessage(string $key): mixed { $message = $this->package->getMessage($key); if ($message) { @@ -134,7 +134,7 @@ public function translate(string $key, array $tokensValues = []): string unset($tokensValues['_context']); } - if (empty($tokensValues)) { + if (!$tokensValues) { // Fallback for plurals that were using the singular key if (is_array($message)) { return array_values($message + [''])[0]; @@ -177,7 +177,7 @@ public function translate(string $key, array $tokensValues = []): string * @param array $vars The variables containing the `_context` key. * @return array|string */ - protected function resolveContext(string $key, array $message, array $vars) + protected function resolveContext(string $key, array $message, array $vars): array|string { $context = $vars['_context'] ?? null; diff --git a/app/vendor/cakephp/cakephp/src/I18n/TranslatorRegistry.php b/app/vendor/cakephp/cakephp/src/I18n/TranslatorRegistry.php index 0ba3cee7b..bf3f7a819 100644 --- a/app/vendor/cakephp/cakephp/src/I18n/TranslatorRegistry.php +++ b/app/vendor/cakephp/cakephp/src/I18n/TranslatorRegistry.php @@ -16,6 +16,9 @@ */ namespace Cake\I18n; +use Cake\Cache\CacheEngineInterface; +use Psr\SimpleCache\CacheInterface; + /** * Constructs and stores instances of translators that can be * retrieved by name and locale. @@ -34,28 +37,28 @@ class TranslatorRegistry * * @var array> */ - protected $registry = []; + protected array $registry = []; /** * The current locale code. * * @var string */ - protected $locale; + protected string $locale; /** * A package locator. * * @var \Cake\I18n\PackageLocator */ - protected $packages; + protected PackageLocator $packages; /** * A formatter locator. * * @var \Cake\I18n\FormatterLocator */ - protected $formatters; + protected FormatterLocator $formatters; /** * A list of loader functions indexed by domain name. Loaders are @@ -65,7 +68,7 @@ class TranslatorRegistry * * @var array */ - protected $_loaders = []; + protected array $_loaders = []; /** * The name of the default formatter to use for newly created @@ -73,14 +76,14 @@ class TranslatorRegistry * * @var string */ - protected $_defaultFormatter = 'default'; + protected string $_defaultFormatter = 'default'; /** * Use fallback-domain for translation loaders. * * @var bool */ - protected $_useFallback = true; + protected bool $_useFallback = true; /** * A CacheEngine object that is used to remember translator across @@ -100,7 +103,7 @@ class TranslatorRegistry public function __construct( PackageLocator $packages, FormatterLocator $formatters, - string $locale + string $locale, ) { $this->packages = $packages; $this->formatters = $formatters; @@ -168,7 +171,7 @@ public function getFormatters(): FormatterLocator * @param \Psr\SimpleCache\CacheInterface&\Cake\Cache\CacheEngineInterface $cacher The cacher instance. * @return void */ - public function setCacher($cacher): void + public function setCacher(CacheInterface&CacheEngineInterface $cacher): void { $this->_cacher = $cacher; } @@ -185,9 +188,7 @@ public function setCacher($cacher): void */ public function get(string $name, ?string $locale = null): ?Translator { - if ($locale === null) { - $locale = $this->getLocale(); - } + $locale ??= $this->getLocale(); if (isset($this->registry[$name][$locale])) { return $this->registry[$name][$locale]; @@ -200,13 +201,10 @@ public function get(string $name, ?string $locale = null): ?Translator // Cache keys cannot contain / if they go to file engine. $keyName = str_replace('/', '.', $name); $key = "translations.{$keyName}.{$locale}"; + /** @var \Cake\I18n\Translator|null $translator */ $translator = $this->_cacher->get($key); - // PHP <8.1 does not correctly garbage collect strings created - // by unserialized arrays. - gc_collect_cycles(); - - if (!$translator || !$translator->getPackage()) { + if (!$translator) { $translator = $this->_getTranslator($name, $locale); $this->_cacher->set($key, $translator); } @@ -234,6 +232,11 @@ protected function _getTranslator(string $name, string $locale): Translator $package = $this->_loaders[static::FALLBACK_LOADER]($name, $locale); } + // Support __invoke() wrapper classes + if (!$package instanceof Package && is_callable($package)) { + $package = $package(); + } + $package = $this->setFallbackPackage($name, $package); $this->packages->set($name, $locale, $package); diff --git a/app/vendor/cakephp/cakephp/src/I18n/composer.json b/app/vendor/cakephp/cakephp/src/I18n/composer.json index 5b75e5983..19a27250d 100644 --- a/app/vendor/cakephp/cakephp/src/I18n/composer.json +++ b/app/vendor/cakephp/cakephp/src/I18n/composer.json @@ -28,13 +28,10 @@ "source": "https://github.com/cakephp/i18n" }, "require": { - "php": ">=7.4.0", + "php": ">=8.1", "ext-intl": "*", - "cakephp/core": "^4.0", - "cakephp/chronos": "^2.0.0" - }, - "suggest": { - "cakephp/cache": "Require this if you want automatic caching of translators" + "cakephp/core": "5.2.*@dev", + "cakephp/chronos": "^3.1" }, "autoload": { "psr-4": { @@ -43,5 +40,14 @@ "files": [ "functions.php" ] + }, + "suggest": { + "cakephp/cache": "Require this if you want automatic caching of translators" + }, + "prefer-stable": true, + "extra": { + "branch-alias": { + "dev-5.x": "5.2.x-dev" + } } } diff --git a/app/vendor/cakephp/cakephp/src/I18n/functions.php b/app/vendor/cakephp/cakephp/src/I18n/functions.php index 3295049ad..b971655d3 100644 --- a/app/vendor/cakephp/cakephp/src/I18n/functions.php +++ b/app/vendor/cakephp/cakephp/src/I18n/functions.php @@ -14,13 +14,11 @@ * @since 3.0.0 * @license https://opensource.org/licenses/mit-license.php MIT License */ +// phpcs:disable PSR1.Files.SideEffects namespace Cake\I18n; -// phpcs:disable PSR1.Files.SideEffects -// Backwards compatibility alias for custom translation messages loaders which return a Package instance. -if (!class_exists('Aura\Intl\Package')) { - class_alias('Cake\I18n\Package', 'Aura\Intl\Package'); -} +use DateTimeInterface; +use Throwable; /** * Returns a translated string if one is found; Otherwise, the submitted message. @@ -28,9 +26,9 @@ class_alias('Cake\I18n\Package', 'Aura\Intl\Package'); * @param string $singular Text to translate. * @param mixed ...$args Array with arguments or multiple arguments in function. * @return string The translated text. - * @link https://book.cakephp.org/4/en/core-libraries/global-constants-and-functions.html#__ + * @link https://book.cakephp.org/5/en/core-libraries/global-constants-and-functions.html#__ */ -function __(string $singular, ...$args): string +function __(string $singular, mixed ...$args): string { if (!$singular) { return ''; @@ -51,9 +49,9 @@ function __(string $singular, ...$args): string * @param int $count Count. * @param mixed ...$args Array with arguments or multiple arguments in function. * @return string Plural form of translated string. - * @link https://book.cakephp.org/4/en/core-libraries/global-constants-and-functions.html#__n + * @link https://book.cakephp.org/5/en/core-libraries/global-constants-and-functions.html#__n */ -function __n(string $singular, string $plural, int $count, ...$args): string +function __n(string $singular, string $plural, int $count, mixed ...$args): string { if (!$singular) { return ''; @@ -64,7 +62,7 @@ function __n(string $singular, string $plural, int $count, ...$args): string return I18n::getTranslator()->translate( $plural, - ['_count' => $count, '_singular' => $singular] + $args + ['_count' => $count, '_singular' => $singular] + $args, ); } @@ -75,9 +73,9 @@ function __n(string $singular, string $plural, int $count, ...$args): string * @param string $msg String to translate. * @param mixed ...$args Array with arguments or multiple arguments in function. * @return string Translated string. - * @link https://book.cakephp.org/4/en/core-libraries/global-constants-and-functions.html#__d + * @link https://book.cakephp.org/5/en/core-libraries/global-constants-and-functions.html#__d */ -function __d(string $domain, string $msg, ...$args): string +function __d(string $domain, string $msg, mixed ...$args): string { if (!$msg) { return ''; @@ -100,9 +98,9 @@ function __d(string $domain, string $msg, ...$args): string * @param int $count Count. * @param mixed ...$args Array with arguments or multiple arguments in function. * @return string Plural form of translated string. - * @link https://book.cakephp.org/4/en/core-libraries/global-constants-and-functions.html#__dn + * @link https://book.cakephp.org/5/en/core-libraries/global-constants-and-functions.html#__dn */ -function __dn(string $domain, string $singular, string $plural, int $count, ...$args): string +function __dn(string $domain, string $singular, string $plural, int $count, mixed ...$args): string { if (!$singular) { return ''; @@ -113,7 +111,7 @@ function __dn(string $domain, string $singular, string $plural, int $count, ...$ return I18n::getTranslator($domain)->translate( $plural, - ['_count' => $count, '_singular' => $singular] + $args + ['_count' => $count, '_singular' => $singular] + $args, ); } @@ -126,9 +124,9 @@ function __dn(string $domain, string $singular, string $plural, int $count, ...$ * @param string $singular Text to translate. * @param mixed ...$args Array with arguments or multiple arguments in function. * @return string Translated string. - * @link https://book.cakephp.org/4/en/core-libraries/global-constants-and-functions.html#__x + * @link https://book.cakephp.org/5/en/core-libraries/global-constants-and-functions.html#__x */ -function __x(string $context, string $singular, ...$args): string +function __x(string $context, string $singular, mixed ...$args): string { if (!$singular) { return ''; @@ -152,9 +150,9 @@ function __x(string $context, string $singular, ...$args): string * @param int $count Count. * @param mixed ...$args Array with arguments or multiple arguments in function. * @return string Plural form of translated string. - * @link https://book.cakephp.org/4/en/core-libraries/global-constants-and-functions.html#__xn + * @link https://book.cakephp.org/5/en/core-libraries/global-constants-and-functions.html#__xn */ -function __xn(string $context, string $singular, string $plural, int $count, ...$args): string +function __xn(string $context, string $singular, string $plural, int $count, mixed ...$args): string { if (!$singular) { return ''; @@ -165,7 +163,7 @@ function __xn(string $context, string $singular, string $plural, int $count, ... return I18n::getTranslator()->translate( $plural, - ['_count' => $count, '_singular' => $singular, '_context' => $context] + $args + ['_count' => $count, '_singular' => $singular, '_context' => $context] + $args, ); } @@ -179,9 +177,9 @@ function __xn(string $context, string $singular, string $plural, int $count, ... * @param string $msg String to translate. * @param mixed ...$args Array with arguments or multiple arguments in function. * @return string Translated string. - * @link https://book.cakephp.org/4/en/core-libraries/global-constants-and-functions.html#__dx + * @link https://book.cakephp.org/5/en/core-libraries/global-constants-and-functions.html#__dx */ -function __dx(string $domain, string $context, string $msg, ...$args): string +function __dx(string $domain, string $context, string $msg, mixed ...$args): string { if (!$msg) { return ''; @@ -192,7 +190,7 @@ function __dx(string $domain, string $context, string $msg, ...$args): string return I18n::getTranslator($domain)->translate( $msg, - ['_context' => $context] + $args + ['_context' => $context] + $args, ); } @@ -209,10 +207,16 @@ function __dx(string $domain, string $context, string $msg, ...$args): string * @param int $count Count. * @param mixed ...$args Array with arguments or multiple arguments in function. * @return string Plural form of translated string. - * @link https://book.cakephp.org/4/en/core-libraries/global-constants-and-functions.html#__dxn + * @link https://book.cakephp.org/5/en/core-libraries/global-constants-and-functions.html#__dxn */ -function __dxn(string $domain, string $context, string $singular, string $plural, int $count, ...$args): string -{ +function __dxn( + string $domain, + string $context, + string $singular, + string $plural, + int $count, + mixed ...$args, +): string { if (!$singular) { return ''; } @@ -222,13 +226,97 @@ function __dxn(string $domain, string $context, string $singular, string $plural return I18n::getTranslator($domain)->translate( $plural, - ['_count' => $count, '_singular' => $singular, '_context' => $context] + $args + ['_count' => $count, '_singular' => $singular, '_context' => $context] + $args, ); } /** - * Include global functions. + * Converts a value to a DateTime object. + * + * integer - value is treated as a Unix timestamp + * float - value is treated as a Unix timestamp with microseconds + * string - value is treated as an Atom-formatted timestamp, unless otherwise specified + * Other values returns as null. + * + * @param mixed $value The value to convert to DateTime. + * @param string $format The datetime format the value is in. Defaults to Atom (ex: 1970-01-01T12:00:00+00:00) format. + * @return \Cake\I18n\DateTime|null Returns a DateTime object if parsing is successful, or NULL otherwise. + * @since 5.1.0 + */ +function toDateTime(mixed $value, string $format = DateTimeInterface::ATOM): ?DateTime +{ + if ($value instanceof DateTime) { + return $value; + } + + if ( + $value instanceof DateTimeInterface || + $value instanceof Date + ) { + return DateTime::parse($value); + } + + if (is_numeric($value)) { + try { + return DateTime::createFromTimestamp((float)$value); + } catch (Throwable) { + return null; + } + } + + if (is_string($value)) { + try { + return DateTime::createFromFormat($format, $value); + } catch (Throwable) { + return null; + } + } + + return null; +} + +/** + * Converts a value to a Date object. + * + * integer - value is treated as a Unix timestamp + * float - value is treated as a Unix timestamp with microseconds + * string - value is treated as a I18N short formatted date, unless otherwise specified + * Other values returns as null. + * + * @param mixed $value The value to convert to Date. + * @param string $format The date format the value is in. Defaults to Short (ex: 1970-01-01) format. + * @return \Cake\I18n\Date|null Returns a Date object if parsing is successful, or NULL otherwise. + * @since 5.1.0 */ -if (!getenv('CAKE_DISABLE_GLOBAL_FUNCS')) { - include 'functions_global.php'; +function toDate(mixed $value, string $format = 'Y-m-d'): ?Date +{ + if ($value instanceof Date) { + return $value; + } + + if ($value instanceof DateTimeInterface) { + return Date::parse($value); + } + + if (is_numeric($value)) { + try { + $datetime = DateTime::createFromTimestamp((float)$value); + + return Date::create($datetime->year, $datetime->month, $datetime->day); + } catch (Throwable) { + return null; + } + } + + if (is_string($value)) { + try { + $datetime = DateTime::createFromFormat($format, $value); + + return Date::parse($datetime); + } catch (Throwable) { + return null; + } + } + + return null; } diff --git a/app/vendor/cakephp/cakephp/src/I18n/functions_global.php b/app/vendor/cakephp/cakephp/src/I18n/functions_global.php index 94e48d381..4bd2c1734 100644 --- a/app/vendor/cakephp/cakephp/src/I18n/functions_global.php +++ b/app/vendor/cakephp/cakephp/src/I18n/functions_global.php @@ -14,7 +14,10 @@ * @since 3.0.0 * @license https://opensource.org/licenses/mit-license.php MIT License */ +// phpcs:disable PSR1.Files.SideEffects +use Cake\I18n\Date; +use Cake\I18n\DateTime; use function Cake\I18n\__ as cake__; use function Cake\I18n\__d as cake__d; use function Cake\I18n\__dn as cake__dn; @@ -23,6 +26,8 @@ use function Cake\I18n\__n as cake__n; use function Cake\I18n\__x as cake__x; use function Cake\I18n\__xn as cake__xn; +use function Cake\I18n\toDate as cakeToDate; +use function Cake\I18n\toDateTime as cakeToDateTime; if (!function_exists('__')) { /** @@ -31,9 +36,9 @@ * @param string $singular Text to translate. * @param mixed ...$args Array with arguments or multiple arguments in function. * @return string The translated text. - * @link https://book.cakephp.org/4/en/core-libraries/global-constants-and-functions.html#__ + * @link https://book.cakephp.org/5/en/core-libraries/global-constants-and-functions.html#__ */ - function __(string $singular, ...$args): string + function __(string $singular, mixed ...$args): string { return cake__($singular, ...$args); } @@ -49,9 +54,9 @@ function __(string $singular, ...$args): string * @param int $count Count. * @param mixed ...$args Array with arguments or multiple arguments in function. * @return string Plural form of translated string. - * @link https://book.cakephp.org/4/en/core-libraries/global-constants-and-functions.html#__n + * @link https://book.cakephp.org/5/en/core-libraries/global-constants-and-functions.html#__n */ - function __n(string $singular, string $plural, int $count, ...$args): string + function __n(string $singular, string $plural, int $count, mixed ...$args): string { return cake__n($singular, $plural, $count, ...$args); } @@ -65,9 +70,9 @@ function __n(string $singular, string $plural, int $count, ...$args): string * @param string $msg String to translate. * @param mixed ...$args Array with arguments or multiple arguments in function. * @return string Translated string. - * @link https://book.cakephp.org/4/en/core-libraries/global-constants-and-functions.html#__d + * @link https://book.cakephp.org/5/en/core-libraries/global-constants-and-functions.html#__d */ - function __d(string $domain, string $msg, ...$args): string + function __d(string $domain, string $msg, mixed ...$args): string { return cake__d($domain, $msg, ...$args); } @@ -85,9 +90,9 @@ function __d(string $domain, string $msg, ...$args): string * @param int $count Count. * @param mixed ...$args Array with arguments or multiple arguments in function. * @return string Plural form of translated string. - * @link https://book.cakephp.org/4/en/core-libraries/global-constants-and-functions.html#__dn + * @link https://book.cakephp.org/5/en/core-libraries/global-constants-and-functions.html#__dn */ - function __dn(string $domain, string $singular, string $plural, int $count, ...$args): string + function __dn(string $domain, string $singular, string $plural, int $count, mixed ...$args): string { return cake__dn($domain, $singular, $plural, $count, ...$args); } @@ -103,9 +108,9 @@ function __dn(string $domain, string $singular, string $plural, int $count, ...$ * @param string $singular Text to translate. * @param mixed ...$args Array with arguments or multiple arguments in function. * @return string Translated string. - * @link https://book.cakephp.org/4/en/core-libraries/global-constants-and-functions.html#__x + * @link https://book.cakephp.org/5/en/core-libraries/global-constants-and-functions.html#__x */ - function __x(string $context, string $singular, ...$args): string + function __x(string $context, string $singular, mixed ...$args): string { return cake__x($context, $singular, ...$args); } @@ -124,9 +129,9 @@ function __x(string $context, string $singular, ...$args): string * @param int $count Count. * @param mixed ...$args Array with arguments or multiple arguments in function. * @return string Plural form of translated string. - * @link https://book.cakephp.org/4/en/core-libraries/global-constants-and-functions.html#__xn + * @link https://book.cakephp.org/5/en/core-libraries/global-constants-and-functions.html#__xn */ - function __xn(string $context, string $singular, string $plural, int $count, ...$args): string + function __xn(string $context, string $singular, string $plural, int $count, mixed ...$args): string { return cake__xn($context, $singular, $plural, $count, ...$args); } @@ -143,9 +148,9 @@ function __xn(string $context, string $singular, string $plural, int $count, ... * @param string $msg String to translate. * @param mixed ...$args Array with arguments or multiple arguments in function. * @return string Translated string. - * @link https://book.cakephp.org/4/en/core-libraries/global-constants-and-functions.html#__dx + * @link https://book.cakephp.org/5/en/core-libraries/global-constants-and-functions.html#__dx */ - function __dx(string $domain, string $context, string $msg, ...$args): string + function __dx(string $domain, string $context, string $msg, mixed ...$args): string { return cake__dx($domain, $context, $msg, ...$args); } @@ -165,10 +170,56 @@ function __dx(string $domain, string $context, string $msg, ...$args): string * @param int $count Count. * @param mixed ...$args Array with arguments or multiple arguments in function. * @return string Plural form of translated string. - * @link https://book.cakephp.org/4/en/core-libraries/global-constants-and-functions.html#__dxn + * @link https://book.cakephp.org/5/en/core-libraries/global-constants-and-functions.html#__dxn */ - function __dxn(string $domain, string $context, string $singular, string $plural, int $count, ...$args): string - { + function __dxn( + string $domain, + string $context, + string $singular, + string $plural, + int $count, + mixed ...$args, + ): string { return cake__dxn($domain, $context, $singular, $plural, $count, ...$args); } } + +if (!function_exists('toDateTime')) { + /** + * Converts a value to a DateTime object. + * + * integer - value is treated as a Unix timestamp + * float - value is treated as a Unix timestamp with microseconds + * string - value is treated as an Atom-formatted timestamp, unless otherwise specified + * Other values returns as null. + * + * @param mixed $value The value to convert to DateTime. + * @param string $format The datetime format the value is in. Defaults to Atom (ex: 1970-01-01T12:00:00+00:00) format. + * @return \Cake\I18n\DateTime|null Returns a DateTime object if parsing is successful, or NULL otherwise. + * @since 5.1.1 + */ + function toDateTime(mixed $value, string $format = DateTimeInterface::ATOM): ?DateTime + { + return cakeToDateTime($value, $format); + } +} + +if (!function_exists('toDate')) { + /** + * Converts a value to a Date object. + * + * integer - value is treated as a Unix timestamp + * float - value is treated as a Unix timestamp with microseconds + * string - value is treated as a I18N short formatted date, unless otherwise specified + * Other values returns as null. + * + * @param mixed $value The value to convert to Date. + * @param string $format The date format the value is in. Defaults to Short (ex: 1970-01-01) format. + * @return \Cake\I18n\Date|null Returns a Date object if parsing is successful, or NULL otherwise. + * @since 5.1.1 + */ + function toDate(mixed $value, string $format = 'Y-m-d'): ?Date + { + return cakeToDate($value, $format); + } +} diff --git a/app/vendor/cakephp/cakephp/src/Log/Engine/ArrayLog.php b/app/vendor/cakephp/cakephp/src/Log/Engine/ArrayLog.php index 1083f5ded..ece7799e5 100644 --- a/app/vendor/cakephp/cakephp/src/Log/Engine/ArrayLog.php +++ b/app/vendor/cakephp/cakephp/src/Log/Engine/ArrayLog.php @@ -17,6 +17,7 @@ namespace Cake\Log\Engine; use Cake\Log\Formatter\DefaultFormatter; +use Stringable; /** * Array logger. @@ -32,7 +33,7 @@ class ArrayLog extends BaseLog * * @var array */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'levels' => [], 'scopes' => [], 'formatter' => [ @@ -46,20 +47,21 @@ class ArrayLog extends BaseLog * * @var array */ - protected $content = []; + protected array $content = []; /** * Implements writing to the internal storage. * * @param mixed $level The severity level of log you are making. - * @param string $message The message you want to log. + * @param \Stringable|string $message The message you want to log. * @param array $context Additional information about the logged message * @return void success of write. * @see \Cake\Log\Log::$_levels + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint */ - public function log($level, $message, array $context = []) + public function log($level, Stringable|string $message, array $context = []): void { - $message = $this->_format($message, $context); + $message = $this->interpolate($message, $context); $this->content[] = $this->formatter->format($level, $message, $context); } diff --git a/app/vendor/cakephp/cakephp/src/Log/Engine/BaseLog.php b/app/vendor/cakephp/cakephp/src/Log/Engine/BaseLog.php index 5fff46cdc..19594c88f 100644 --- a/app/vendor/cakephp/cakephp/src/Log/Engine/BaseLog.php +++ b/app/vendor/cakephp/cakephp/src/Log/Engine/BaseLog.php @@ -20,11 +20,11 @@ use Cake\Core\InstanceConfigTrait; use Cake\Log\Formatter\AbstractFormatter; use Cake\Log\Formatter\DefaultFormatter; -use InvalidArgumentException; use JsonSerializable; use Psr\Log\AbstractLogger; use Serializable; -use function Cake\Core\getTypeName; +use Stringable; +use function Cake\Core\deprecationWarning; /** * Base log engine class. @@ -38,7 +38,7 @@ abstract class BaseLog extends AbstractLogger * * @var array */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'levels' => [], 'scopes' => [], 'formatter' => DefaultFormatter::class, @@ -47,7 +47,7 @@ abstract class BaseLog extends AbstractLogger /** * @var \Cake\Log\Formatter\AbstractFormatter */ - protected $formatter; + protected AbstractFormatter $formatter; /** * __construct method @@ -58,38 +58,35 @@ public function __construct(array $config = []) { $this->setConfig($config); - if (!is_array($this->_config['scopes']) && $this->_config['scopes'] !== false) { + // Backwards compatibility shim as we can't deprecate using false because of how 4.x merges configuration. + if ($this->_config['scopes'] === false) { + deprecationWarning('5.0.0', 'Using `false` to disable logging scopes is deprecated. Use `null` instead.'); + $this->_config['scopes'] = null; + } + if ($this->_config['scopes'] !== null) { $this->_config['scopes'] = (array)$this->_config['scopes']; } - if (!is_array($this->_config['levels'])) { - $this->_config['levels'] = (array)$this->_config['levels']; - } + $this->_config['levels'] = (array)$this->_config['levels']; if (!empty($this->_config['types']) && empty($this->_config['levels'])) { $this->_config['levels'] = (array)$this->_config['types']; } + /** @var \Cake\Log\Formatter\AbstractFormatter|array|class-string<\Cake\Log\Formatter\AbstractFormatter> $formatter */ $formatter = $this->_config['formatter'] ?? DefaultFormatter::class; if (!is_object($formatter)) { if (is_array($formatter)) { + /** @var class-string<\Cake\Log\Formatter\AbstractFormatter> $class */ $class = $formatter['className']; $options = $formatter; } else { $class = $formatter; $options = []; } - /** @var class-string<\Cake\Log\Formatter\AbstractFormatter> $class */ $formatter = new $class($options); } - if (!$formatter instanceof AbstractFormatter) { - throw new InvalidArgumentException(sprintf( - 'Formatter must extend `%s`, got `%s` instead', - AbstractFormatter::class, - get_class($formatter) - )); - } $this->formatter = $formatter; } @@ -106,53 +103,40 @@ public function levels(): array /** * Get the scopes this logger is interested in. * - * @return array|false + * @return array|null */ - public function scopes() + public function scopes(): ?array { return $this->_config['scopes']; } - /** - * Formats the message to be logged. - * - * The context can optionally be used by log engines to interpolate variables - * or add additional info to the logged message. - * - * @param string $message The message to be formatted. - * @param array $context Additional logging information for the message. - * @return string - * @deprecated 4.3.0 Call `interpolate()` directly from your log engine and format the message in a formatter. - */ - protected function _format(string $message, array $context = []): string - { - return $this->interpolate($message, $context); - } - /** * Replaces placeholders in message string with context values. * - * @param string $message Formatted string + * @param \Stringable|string $message Formatted message. * @param array $context Context for placeholder values. * @return string */ - protected function interpolate(string $message, array $context = []): string + protected function interpolate(Stringable|string $message, array $context = []): string { - if (strpos($message, '{') === false && strpos($message, '}') === false) { + $message = (string)$message; + + if (!str_contains($message, '{') && !str_contains($message, '}')) { return $message; } - preg_match_all( - '/(?getArrayCopy(), JSON_UNESCAPED_UNICODE); + $replacements['{' . $key . '}'] = json_encode($value->getArrayCopy(), $jsonFlags); continue; } @@ -184,30 +168,29 @@ protected function interpolate(string $message, array $context = []): string if (is_object($value)) { if (method_exists($value, 'toArray')) { - $replacements['{' . $key . '}'] = json_encode($value->toArray(), JSON_UNESCAPED_UNICODE); + $replacements['{' . $key . '}'] = json_encode($value->toArray(), $jsonFlags); continue; } - if (method_exists($value, '__serialize')) { + if ($value instanceof Serializable) { $replacements['{' . $key . '}'] = serialize($value); continue; } - if (method_exists($value, '__toString')) { + if ($value instanceof Stringable) { $replacements['{' . $key . '}'] = (string)$value; continue; } if (method_exists($value, '__debugInfo')) { - $replacements['{' . $key . '}'] = json_encode($value->__debugInfo(), JSON_UNESCAPED_UNICODE); + $replacements['{' . $key . '}'] = json_encode($value->__debugInfo(), $jsonFlags); continue; } } - $replacements['{' . $key . '}'] = sprintf('[unhandled value of type %s]', getTypeName($value)); + $replacements['{' . $key . '}'] = sprintf('[unhandled value of type %s]', get_debug_type($value)); } - /** @psalm-suppress InvalidArgument */ return str_replace(array_keys($replacements), $replacements, $message); } } diff --git a/app/vendor/cakephp/cakephp/src/Log/Engine/ConsoleLog.php b/app/vendor/cakephp/cakephp/src/Log/Engine/ConsoleLog.php index 1e432a010..73db8a721 100644 --- a/app/vendor/cakephp/cakephp/src/Log/Engine/ConsoleLog.php +++ b/app/vendor/cakephp/cakephp/src/Log/Engine/ConsoleLog.php @@ -19,7 +19,7 @@ use Cake\Console\ConsoleOutput; use Cake\Log\Formatter\DefaultFormatter; use InvalidArgumentException; -use function Cake\Core\deprecationWarning; +use Stringable; /** * Console logging. Writes logs to console output. @@ -31,7 +31,7 @@ class ConsoleLog extends BaseLog * * @var array */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'stream' => 'php://stderr', 'levels' => null, 'scopes' => [], @@ -47,7 +47,7 @@ class ConsoleLog extends BaseLog * * @var \Cake\Console\ConsoleOutput */ - protected $_output; + protected ConsoleOutput $_output; /** * Constructs a new Console Logger. @@ -79,25 +79,21 @@ public function __construct(array $config = []) if (isset($config['outputAs'])) { $this->_output->setOutputAs($config['outputAs']); } - - if (isset($this->_config['dateFormat'])) { - deprecationWarning('`dateFormat` option should now be set in the formatter options.', 0); - $this->formatter->setConfig('dateFormat', $this->_config['dateFormat']); - } } /** * Implements writing to console. * * @param mixed $level The severity level of log you are making. - * @param string $message The message you want to log. + * @param \Stringable|string $message The message you want to log. * @param array $context Additional information about the logged message * @return void success of write. * @see \Cake\Log\Log::$_levels + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint */ - public function log($level, $message, array $context = []) + public function log($level, Stringable|string $message, array $context = []): void { - $message = $this->_format($message, $context); + $message = $this->interpolate($message, $context); $this->_output->write($this->formatter->format($level, $message, $context)); } } diff --git a/app/vendor/cakephp/cakephp/src/Log/Engine/FileLog.php b/app/vendor/cakephp/cakephp/src/Log/Engine/FileLog.php index 66dbb5bd9..d87fc270d 100644 --- a/app/vendor/cakephp/cakephp/src/Log/Engine/FileLog.php +++ b/app/vendor/cakephp/cakephp/src/Log/Engine/FileLog.php @@ -18,7 +18,7 @@ use Cake\Log\Formatter\DefaultFormatter; use Cake\Utility\Text; -use function Cake\Core\deprecationWarning; +use Stringable; /** * File Storage stream for Logging. Writes logs to different files @@ -38,15 +38,14 @@ class FileLog extends BaseLog * to filename and new log file is created. Can be integer bytes value or * human readable string values like '10MB', '100KB' etc. * - `rotate` Log files are rotated specified times before being removed. - * If value is 0, old versions are removed rather then rotated. + * If value is 0, old versions are removed rather than rotated. * - `mask` A mask is applied when log files are created. Left empty no chmod * is made. * - `dirMask` The mask used for created folders. - * - `dateFormat` PHP date() format. * * @var array */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'path' => null, 'file' => null, 'types' => null, @@ -66,21 +65,21 @@ class FileLog extends BaseLog * * @var string */ - protected $_path; + protected string $_path; /** * The name of the file to save logs into. * * @var string|null */ - protected $_file; + protected ?string $_file = null; /** * Max file size, used for log file rotation. * * @var int|null */ - protected $_size; + protected ?int $_size = null; /** * Sets protected properties based on config provided @@ -98,7 +97,7 @@ public function __construct(array $config = []) if (!empty($this->_config['file'])) { $this->_file = $this->_config['file']; - if (substr($this->_file, -4) !== '.log') { + if (!str_ends_with($this->_file, '.log')) { $this->_file .= '.log'; } } @@ -110,25 +109,21 @@ public function __construct(array $config = []) $this->_size = Text::parseFileSize($this->_config['size']); } } - - if (isset($this->_config['dateFormat'])) { - deprecationWarning('`dateFormat` option should now be set in the formatter options.', 0); - $this->formatter->setConfig('dateFormat', $this->_config['dateFormat']); - } } /** * Implements writing to log files. * * @param mixed $level The severity level of the message being written. - * @param string $message The message you want to log. + * @param \Stringable|string $message The message you want to log. * @param array $context Additional information about the logged message * @return void * @see \Cake\Log\Log::$_levels + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint */ - public function log($level, $message, array $context = []): void + public function log($level, Stringable|string $message, array $context = []): void { - $message = $this->_format($message, $context); + $message = $this->interpolate($message, $context); $message = $this->formatter->format($level, $message, $context); $filename = $this->_getFilename($level); @@ -151,8 +146,8 @@ public function log($level, $message, array $context = []): void if (!$selfError && !$exists && !chmod($pathname, (int)$mask)) { $selfError = true; trigger_error(vsprintf( - 'Could not apply permission mask "%s" on log file "%s"', - [$mask, $pathname] + 'Could not apply permission mask `%s` on log file `%s`', + [$mask, $pathname], ), E_USER_WARNING); $selfError = false; } @@ -212,7 +207,7 @@ protected function _rotateFile(string $filename): ?bool if ($files) { $filesToDelete = count($files) - $rotate; while ($filesToDelete > 0) { - unlink(array_shift($files)); + unlink((string)array_shift($files)); $filesToDelete--; } } diff --git a/app/vendor/cakephp/cakephp/src/Log/Engine/SyslogLog.php b/app/vendor/cakephp/cakephp/src/Log/Engine/SyslogLog.php index 5f8c6d342..543a2d51a 100644 --- a/app/vendor/cakephp/cakephp/src/Log/Engine/SyslogLog.php +++ b/app/vendor/cakephp/cakephp/src/Log/Engine/SyslogLog.php @@ -17,8 +17,7 @@ namespace Cake\Log\Engine; use Cake\Log\Formatter\DefaultFormatter; -use Cake\Log\Formatter\LegacySyslogFormatter; -use function Cake\Core\deprecationWarning; +use Stringable; /** * Syslog stream for Logging. Writes logs to the system logger @@ -28,7 +27,7 @@ class SyslogLog extends BaseLog /** * Default config for this class * - * By default messages are formatted as: + * By default, messages are formatted as: * level: message * * To override the log format (e.g. to add your own info) define the format key when configuring @@ -43,16 +42,16 @@ class SyslogLog extends BaseLog * ### Example: * * ``` - * Log::config('error', ] + * Log::config('error', [ * 'engine' => 'Syslog', * 'levels' => ['emergency', 'alert', 'critical', 'error'], - * 'prefix' => 'Web Server 01' + * 'prefix' => 'Web Server 01', * ]); * ``` * * @var array */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'levels' => [], 'scopes' => [], 'flag' => LOG_ODELAY, @@ -69,7 +68,7 @@ class SyslogLog extends BaseLog * * @var array */ - protected $_levelMap = [ + protected array $_levelMap = [ 'emergency' => LOG_EMERG, 'alert' => LOG_ALERT, 'critical' => LOG_CRIT, @@ -85,27 +84,7 @@ class SyslogLog extends BaseLog * * @var bool */ - protected $_open = false; - - /** - * @inheritDoc - */ - public function __construct(array $config = []) - { - if (isset($config['format'])) { - deprecationWarning( - '`format` option is now deprecated in favor of custom formatters. ' . - 'Switching to `LegacySyslogFormatter`.', - 0 - ); - /** @psalm-suppress DeprecatedClass */ - $config['formatter'] = [ - 'className' => LegacySyslogFormatter::class, - 'format' => $config['format'], - ]; - } - parent::__construct($config); - } + protected bool $_open = false; /** * Writes a message to syslog @@ -114,12 +93,13 @@ public function __construct(array $config = []) * log messages, pass all messages through the format defined in the configuration * * @param mixed $level The severity level of log you are making. - * @param string $message The message you want to log. + * @param \Stringable|string $message The message you want to log. * @param array $context Additional information about the logged message * @return void * @see \Cake\Log\Log::$_levels + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint */ - public function log($level, $message, array $context = []): void + public function log($level, Stringable|string $message, array $context = []): void { if (!$this->_open) { $config = $this->_config; @@ -132,7 +112,7 @@ public function log($level, $message, array $context = []): void $priority = $this->_levelMap[$level]; } - $lines = explode("\n", $this->_format($message, $context)); + $lines = explode("\n", $this->interpolate($message, $context)); foreach ($lines as $line) { $this->_write($priority, $this->formatter->format($level, $line, $context)); } @@ -154,7 +134,7 @@ protected function _open(string $ident, int $options, int $facility): void /** * Extracts the call to syslog() in order to run unit tests on it. This function - * will perform the actual write in the system logger + * will perform the actual write operation in the system logger * * @param int $priority Message priority. * @param string $message Message to log. diff --git a/app/vendor/cakephp/cakephp/src/Log/Formatter/AbstractFormatter.php b/app/vendor/cakephp/cakephp/src/Log/Formatter/AbstractFormatter.php index 8e34d8074..5629d7800 100644 --- a/app/vendor/cakephp/cakephp/src/Log/Formatter/AbstractFormatter.php +++ b/app/vendor/cakephp/cakephp/src/Log/Formatter/AbstractFormatter.php @@ -27,7 +27,7 @@ abstract class AbstractFormatter * * @var array */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ ]; /** @@ -43,8 +43,8 @@ public function __construct(array $config = []) * * @param mixed $level Logging level * @param string $message Message string - * @param array $context Mesage context + * @param array $context Message context * @return string Formatted message */ - abstract public function format($level, string $message, array $context = []): string; + abstract public function format(mixed $level, string $message, array $context = []): string; } diff --git a/app/vendor/cakephp/cakephp/src/Log/Formatter/DefaultFormatter.php b/app/vendor/cakephp/cakephp/src/Log/Formatter/DefaultFormatter.php index b4fc4d4a1..75bc044f1 100644 --- a/app/vendor/cakephp/cakephp/src/Log/Formatter/DefaultFormatter.php +++ b/app/vendor/cakephp/cakephp/src/Log/Formatter/DefaultFormatter.php @@ -25,20 +25,12 @@ class DefaultFormatter extends AbstractFormatter * * @var array */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'dateFormat' => 'Y-m-d H:i:s', 'includeTags' => false, 'includeDate' => true, ]; - /** - * @param array $config Formatter config - */ - public function __construct(array $config = []) - { - $this->setConfig($config); - } - /** * @inheritDoc */ @@ -50,7 +42,7 @@ public function format($level, string $message, array $context = []): string $message = sprintf('%s: %s', $level, $message); } if ($this->_config['includeTags']) { - $message = sprintf('<%s>%s', $level, $message, $level); + return sprintf('<%s>%s', $level, $message, $level); } return $message; diff --git a/app/vendor/cakephp/cakephp/src/Log/Formatter/JsonFormatter.php b/app/vendor/cakephp/cakephp/src/Log/Formatter/JsonFormatter.php index 430df4933..55e322ec7 100644 --- a/app/vendor/cakephp/cakephp/src/Log/Formatter/JsonFormatter.php +++ b/app/vendor/cakephp/cakephp/src/Log/Formatter/JsonFormatter.php @@ -23,27 +23,19 @@ class JsonFormatter extends AbstractFormatter * * @var array */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'dateFormat' => DATE_ATOM, 'flags' => JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES, 'appendNewline' => true, ]; - /** - * @param array $config Formatter config - */ - public function __construct(array $config = []) - { - $this->setConfig($config); - } - /** * @inheritDoc */ public function format($level, string $message, array $context = []): string { $log = ['date' => date($this->_config['dateFormat']), 'level' => (string)$level, 'message' => $message]; - $json = json_encode($log, $this->_config['flags']); + $json = json_encode($log, JSON_THROW_ON_ERROR | $this->_config['flags']); return $this->_config['appendNewline'] ? $json . "\n" : $json; } diff --git a/app/vendor/cakephp/cakephp/src/Log/Formatter/LegacySyslogFormatter.php b/app/vendor/cakephp/cakephp/src/Log/Formatter/LegacySyslogFormatter.php deleted file mode 100644 index 7c03cc6b6..000000000 --- a/app/vendor/cakephp/cakephp/src/Log/Formatter/LegacySyslogFormatter.php +++ /dev/null @@ -1,48 +0,0 @@ - - */ - protected $_defaultConfig = [ - 'format' => '%s: %s', - ]; - - /** - * @param array $config Formatter config - */ - public function __construct(array $config = []) - { - $this->setConfig($config); - } - - /** - * @inheritDoc - */ - public function format($level, string $message, array $context = []): string - { - return sprintf($this->getConfig('format'), $level, $message); - } -} diff --git a/app/vendor/cakephp/cakephp/src/Log/Log.php b/app/vendor/cakephp/cakephp/src/Log/Log.php index 930187820..67ddd4665 100644 --- a/app/vendor/cakephp/cakephp/src/Log/Log.php +++ b/app/vendor/cakephp/cakephp/src/Log/Log.php @@ -17,8 +17,10 @@ use Cake\Core\StaticConfigTrait; use Cake\Log\Engine\BaseLog; +use Closure; use InvalidArgumentException; use Psr\Log\LoggerInterface; +use Stringable; /** * Logs messages to configured Log adapters. One or more adapters @@ -114,9 +116,9 @@ class Log * An array mapping url schemes to fully qualified Log engine class names * * @var array - * @psalm-var array + * @phpstan-var array */ - protected static $_dsnClassMap = [ + protected static array $_dsnClassMap = [ 'console' => Engine\ConsoleLog::class, 'file' => Engine\FileLog::class, 'syslog' => Engine\SyslogLog::class, @@ -127,21 +129,21 @@ class Log * * @var bool */ - protected static $_dirtyConfig = false; + protected static bool $_dirtyConfig = false; /** * LogEngineRegistry class * * @var \Cake\Log\LogEngineRegistry */ - protected static $_registry; + protected static LogEngineRegistry $_registry; /** * Handled log levels * * @var array */ - protected static $_levels = [ + protected static array $_levels = [ 'emergency', 'alert', 'critical', @@ -158,7 +160,7 @@ class Log * * @var array */ - protected static $_levelMap = [ + protected static array $_levelMap = [ 'emergency' => LOG_EMERG, 'alert' => LOG_ALERT, 'critical' => LOG_CRIT, @@ -170,38 +172,28 @@ class Log ]; /** - * Initializes registry and configurations + * Creates registry if doesn't exist and creates all defined logging + * adapters if config isn't loaded. * - * @return void + * @return \Cake\Log\LogEngineRegistry */ - protected static function _init(): void + protected static function getRegistry(): LogEngineRegistry { - /** @psalm-suppress RedundantPropertyInitializationCheck */ - if (!isset(static::$_registry)) { - static::$_registry = new LogEngineRegistry(); - } + static::$_registry ??= new LogEngineRegistry(); + if (static::$_dirtyConfig) { - static::_loadConfig(); + foreach (static::$_config as $name => $properties) { + if (isset($properties['engine'])) { + $properties['className'] = $properties['engine']; + } + if (!static::$_registry->has((string)$name)) { + static::$_registry->load((string)$name, $properties); + } + } } static::$_dirtyConfig = false; - } - /** - * Load the defined configuration and create all the defined logging - * adapters. - * - * @return void - */ - protected static function _loadConfig(): void - { - foreach (static::$_config as $name => $properties) { - if (isset($properties['engine'])) { - $properties['className'] = $properties['engine']; - } - if (!static::$_registry->has((string)$name)) { - static::$_registry->load((string)$name, $properties); - } - } + return static::$_registry; } /** @@ -216,7 +208,6 @@ protected static function _loadConfig(): void */ public static function reset(): void { - /** @psalm-suppress RedundantPropertyInitializationCheck */ if (isset(static::$_registry)) { static::$_registry->reset(); } @@ -273,11 +264,11 @@ public static function levels(): array * ``` * * @param array|string $key The name of the logger config, or an array of multiple configs. - * @param array|\Closure|null $config An array of name => config data for adapter. + * @param \Psr\Log\LoggerInterface|\Closure|array|null $config An array of name => config data for adapter. * @return void * @throws \BadMethodCallException When trying to modify an existing config. */ - public static function setConfig($key, $config = null): void + public static function setConfig(array|string $key, LoggerInterface|Closure|array|null $config = null): void { static::_setConfig($key, $config); static::$_dirtyConfig = true; @@ -291,12 +282,12 @@ public static function setConfig($key, $config = null): void */ public static function engine(string $name): ?LoggerInterface { - static::_init(); - if (static::$_registry->{$name}) { - return static::$_registry->{$name}; + $registry = static::getRegistry(); + if (!$registry->{$name}) { + return null; } - return null; + return $registry->{$name}; } /** @@ -344,7 +335,7 @@ public static function engine(string $name): ?LoggerInterface * * @param string|int $level The severity level of the message being written. * The value must be an integer or string matching a known level. - * @param string $message Message content to log + * @param \Stringable|string $message Message content to log * @param array|string $context Additional data to be used for logging the message. * The special `scope` key can be passed to be used for further filtering of the * log engines to be used. If a string or a numerically index array is passed, it @@ -353,15 +344,13 @@ public static function engine(string $name): ?LoggerInterface * @return bool Success * @throws \InvalidArgumentException If invalid level is passed. */ - public static function write($level, string $message, $context = []): bool + public static function write(string|int $level, Stringable|string $message, array|string $context = []): bool { - static::_init(); if (is_int($level) && in_array($level, static::$_levelMap, true)) { $level = array_search($level, static::$_levelMap, true); } if (!in_array($level, static::$_levels, true)) { - /** @psalm-suppress PossiblyFalseArgument */ throw new InvalidArgumentException(sprintf('Invalid log level `%s`', $level)); } @@ -372,20 +361,20 @@ public static function write($level, string $message, $context = []): bool } $context += ['scope' => []]; - foreach (static::$_registry->loaded() as $streamName) { - $logger = static::$_registry->{$streamName}; - $levels = $scopes = null; + $registry = static::getRegistry(); + foreach ($registry->loaded() as $streamName) { + /** @var \Psr\Log\LoggerInterface $logger */ + $logger = $registry->{$streamName}; + $levels = null; + $scopes = null; if ($logger instanceof BaseLog) { $levels = $logger->levels(); $scopes = $logger->scopes(); } - if ($scopes === null) { - $scopes = []; - } $correctLevel = empty($levels) || in_array($level, $levels, true); - $inScope = $scopes === false && empty($context['scope']) || $scopes === [] || + $inScope = $scopes === null && empty($context['scope']) || $scopes === [] || is_array($scopes) && array_intersect((array)$context['scope'], $scopes); if ($correctLevel && $inScope) { @@ -400,7 +389,7 @@ public static function write($level, string $message, $context = []): bool /** * Convenience method to log emergency messages * - * @param string $message log message + * @param \Stringable|string $message log message * @param array|string $context Additional data to be used for logging the message. * The special `scope` key can be passed to be used for further filtering of the * log engines to be used. If a string or a numerically index array is passed, it @@ -408,7 +397,7 @@ public static function write($level, string $message, $context = []): bool * See {@link \Cake\Log\Log::setConfig()} for more information on logging scopes. * @return bool Success */ - public static function emergency(string $message, $context = []): bool + public static function emergency(Stringable|string $message, array|string $context = []): bool { return static::write(__FUNCTION__, $message, $context); } @@ -416,7 +405,7 @@ public static function emergency(string $message, $context = []): bool /** * Convenience method to log alert messages * - * @param string $message log message + * @param \Stringable|string $message log message * @param array|string $context Additional data to be used for logging the message. * The special `scope` key can be passed to be used for further filtering of the * log engines to be used. If a string or a numerically index array is passed, it @@ -424,7 +413,7 @@ public static function emergency(string $message, $context = []): bool * See {@link \Cake\Log\Log::setConfig()} for more information on logging scopes. * @return bool Success */ - public static function alert(string $message, $context = []): bool + public static function alert(Stringable|string $message, array|string $context = []): bool { return static::write(__FUNCTION__, $message, $context); } @@ -432,7 +421,7 @@ public static function alert(string $message, $context = []): bool /** * Convenience method to log critical messages * - * @param string $message log message + * @param \Stringable|string $message log message * @param array|string $context Additional data to be used for logging the message. * The special `scope` key can be passed to be used for further filtering of the * log engines to be used. If a string or a numerically index array is passed, it @@ -440,7 +429,7 @@ public static function alert(string $message, $context = []): bool * See {@link \Cake\Log\Log::setConfig()} for more information on logging scopes. * @return bool Success */ - public static function critical(string $message, $context = []): bool + public static function critical(Stringable|string $message, array|string $context = []): bool { return static::write(__FUNCTION__, $message, $context); } @@ -448,7 +437,7 @@ public static function critical(string $message, $context = []): bool /** * Convenience method to log error messages * - * @param string $message log message + * @param \Stringable|string $message log message * @param array|string $context Additional data to be used for logging the message. * The special `scope` key can be passed to be used for further filtering of the * log engines to be used. If a string or a numerically index array is passed, it @@ -456,7 +445,7 @@ public static function critical(string $message, $context = []): bool * See {@link \Cake\Log\Log::setConfig()} for more information on logging scopes. * @return bool Success */ - public static function error(string $message, $context = []): bool + public static function error(Stringable|string $message, array|string $context = []): bool { return static::write(__FUNCTION__, $message, $context); } @@ -464,7 +453,7 @@ public static function error(string $message, $context = []): bool /** * Convenience method to log warning messages * - * @param string $message log message + * @param \Stringable|string $message log message * @param array|string $context Additional data to be used for logging the message. * The special `scope` key can be passed to be used for further filtering of the * log engines to be used. If a string or a numerically index array is passed, it @@ -472,7 +461,7 @@ public static function error(string $message, $context = []): bool * See {@link \Cake\Log\Log::setConfig()} for more information on logging scopes. * @return bool Success */ - public static function warning(string $message, $context = []): bool + public static function warning(Stringable|string $message, array|string $context = []): bool { return static::write(__FUNCTION__, $message, $context); } @@ -480,7 +469,7 @@ public static function warning(string $message, $context = []): bool /** * Convenience method to log notice messages * - * @param string $message log message + * @param \Stringable|string $message log message * @param array|string $context Additional data to be used for logging the message. * The special `scope` key can be passed to be used for further filtering of the * log engines to be used. If a string or a numerically index array is passed, it @@ -488,7 +477,7 @@ public static function warning(string $message, $context = []): bool * See {@link \Cake\Log\Log::setConfig()} for more information on logging scopes. * @return bool Success */ - public static function notice(string $message, $context = []): bool + public static function notice(Stringable|string $message, array|string $context = []): bool { return static::write(__FUNCTION__, $message, $context); } @@ -496,7 +485,7 @@ public static function notice(string $message, $context = []): bool /** * Convenience method to log debug messages * - * @param string $message log message + * @param \Stringable|string $message log message * @param array|string $context Additional data to be used for logging the message. * The special `scope` key can be passed to be used for further filtering of the * log engines to be used. If a string or a numerically index array is passed, it @@ -504,7 +493,7 @@ public static function notice(string $message, $context = []): bool * See {@link \Cake\Log\Log::setConfig()} for more information on logging scopes. * @return bool Success */ - public static function debug(string $message, $context = []): bool + public static function debug(Stringable|string $message, array|string $context = []): bool { return static::write(__FUNCTION__, $message, $context); } @@ -512,7 +501,7 @@ public static function debug(string $message, $context = []): bool /** * Convenience method to log info messages * - * @param string $message log message + * @param \Stringable|string $message log message * @param array|string $context Additional data to be used for logging the message. * The special `scope` key can be passed to be used for further filtering of the * log engines to be used. If a string or a numerically indexed array is passed, it @@ -520,7 +509,7 @@ public static function debug(string $message, $context = []): bool * See {@link \Cake\Log\Log::setConfig()} for more information on logging scopes. * @return bool Success */ - public static function info(string $message, $context = []): bool + public static function info(Stringable|string $message, array|string $context = []): bool { return static::write(__FUNCTION__, $message, $context); } diff --git a/app/vendor/cakephp/cakephp/src/Log/LogEngineRegistry.php b/app/vendor/cakephp/cakephp/src/Log/LogEngineRegistry.php index 535ade03f..570badf42 100644 --- a/app/vendor/cakephp/cakephp/src/Log/LogEngineRegistry.php +++ b/app/vendor/cakephp/cakephp/src/Log/LogEngineRegistry.php @@ -17,10 +17,9 @@ namespace Cake\Log; use Cake\Core\App; +use Cake\Core\Exception\CakeException; use Cake\Core\ObjectRegistry; use Psr\Log\LoggerInterface; -use RuntimeException; -use function Cake\Core\getTypeName; /** * Registry of loaded log engines @@ -35,11 +34,11 @@ class LogEngineRegistry extends ObjectRegistry * Part of the template method for Cake\Core\ObjectRegistry::load() * * @param string $class Partial classname to resolve. - * @return string|null Either the correct class name or null. - * @psalm-return class-string|null + * @return class-string<\Psr\Log\LoggerInterface>|null Either the correct class name or null. */ protected function _resolveClassName(string $class): ?string { + /** @var class-string<\Psr\Log\LoggerInterface>|null */ return App::className($class, 'Log/Engine', 'Log'); } @@ -51,11 +50,11 @@ protected function _resolveClassName(string $class): ?string * @param string $class The classname that is missing. * @param string|null $plugin The plugin the logger is missing in. * @return void - * @throws \RuntimeException + * @throws \Cake\Core\Exception\CakeException */ protected function _throwMissingClassError(string $class, ?string $plugin): void { - throw new RuntimeException(sprintf('Could not load class %s', $class)); + throw new CakeException(sprintf('Could not load class `%s`.', $class)); } /** @@ -63,35 +62,23 @@ protected function _throwMissingClassError(string $class, ?string $plugin): void * * Part of the template method for Cake\Core\ObjectRegistry::load() * - * @param \Psr\Log\LoggerInterface|string $class The classname or object to make. + * @param \Psr\Log\LoggerInterface|callable|class-string<\Psr\Log\LoggerInterface> $class The classname or object to make. * @param string $alias The alias of the object. * @param array $config An array of settings to use for the logger. * @return \Psr\Log\LoggerInterface The constructed logger class. - * @throws \RuntimeException when an object doesn't implement the correct interface. */ - protected function _create($class, string $alias, array $config): LoggerInterface + protected function _create(callable|object|string $class, string $alias, array $config): LoggerInterface { - if (is_callable($class)) { - $class = $class($alias); - } - - if (is_object($class)) { - $instance = $class; + if (is_string($class)) { + /** @var class-string<\Psr\Log\LoggerInterface> $class */ + return new $class($config); } - if (!isset($instance)) { - $instance = new $class($config); - } - - if ($instance instanceof LoggerInterface) { - return $instance; + if (is_callable($class)) { + return $class($alias); } - throw new RuntimeException(sprintf( - 'Loggers must implement %s. Found `%s` instance instead.', - LoggerInterface::class, - getTypeName($instance) - )); + return $class; } /** diff --git a/app/vendor/cakephp/cakephp/src/Log/LogTrait.php b/app/vendor/cakephp/cakephp/src/Log/LogTrait.php index 129af8823..6843f3300 100644 --- a/app/vendor/cakephp/cakephp/src/Log/LogTrait.php +++ b/app/vendor/cakephp/cakephp/src/Log/LogTrait.php @@ -16,6 +16,7 @@ namespace Cake\Log; use Psr\Log\LogLevel; +use Stringable; /** * A trait providing an object short-cut method @@ -27,13 +28,16 @@ trait LogTrait * Convenience method to write a message to Log. See Log::write() * for more information on writing to logs. * - * @param string $message Log message. + * @param \Stringable|string $message Log message. * @param string|int $level Error level. * @param array|string $context Additional log data relevant to this message. * @return bool Success of log write. */ - public function log(string $message, $level = LogLevel::ERROR, $context = []): bool - { + public function log( + Stringable|string $message, + string|int $level = LogLevel::ERROR, + array|string $context = [], + ): bool { return Log::write($level, $message, $context); } } diff --git a/app/vendor/cakephp/cakephp/src/Log/README.md b/app/vendor/cakephp/cakephp/src/Log/README.md index d43cb9e55..df549302a 100644 --- a/app/vendor/cakephp/cakephp/src/Log/README.md +++ b/app/vendor/cakephp/cakephp/src/Log/README.md @@ -78,4 +78,4 @@ Log::warning('this gets written only to payments.log', ['scope' => ['payments']] ## Documentation -Please make sure you check the [official documentation](https://book.cakephp.org/4/en/core-libraries/logging.html) +Please make sure you check the [official documentation](https://book.cakephp.org/5/en/core-libraries/logging.html) diff --git a/app/vendor/cakephp/cakephp/src/Log/composer.json b/app/vendor/cakephp/cakephp/src/Log/composer.json index 78f0d0eee..593de4ff1 100644 --- a/app/vendor/cakephp/cakephp/src/Log/composer.json +++ b/app/vendor/cakephp/cakephp/src/Log/composer.json @@ -23,16 +23,22 @@ "source": "https://github.com/cakephp/log" }, "require": { - "php": ">=7.4.0", - "cakephp/core": "^4.0", - "psr/log": "^1.0 || ^2.0" - }, - "provide": { - "psr/log-implementation": "^1.0 || ^2.0" + "php": ">=8.1", + "cakephp/core": "5.2.*@dev", + "psr/log": "^3.0" }, "autoload": { "psr-4": { "Cake\\Log\\": "." } + }, + "provide": { + "psr/log-implementation": "^3.0" + }, + "prefer-stable": true, + "extra": { + "branch-alias": { + "dev-5.x": "5.2.x-dev" + } } } diff --git a/app/vendor/cakephp/cakephp/src/Mailer/AbstractTransport.php b/app/vendor/cakephp/cakephp/src/Mailer/AbstractTransport.php index d469441f5..4964d1c10 100644 --- a/app/vendor/cakephp/cakephp/src/Mailer/AbstractTransport.php +++ b/app/vendor/cakephp/cakephp/src/Mailer/AbstractTransport.php @@ -31,14 +31,14 @@ abstract class AbstractTransport * * @var array */ - protected $_defaultConfig = []; + protected array $_defaultConfig = []; /** * Send mail * * @param \Cake\Mailer\Message $message Email message. - * @return array - * @psalm-return array{headers: string, message: string} + * @return array Contains 'headers' and 'message' keys. Additional keys allowed. + * @phpstan-return array{headers: string, message: string, ...} */ abstract public function send(Message $message): array; @@ -68,7 +68,7 @@ protected function checkRecipient(Message $message): void ) { throw new CakeException( 'You must specify at least one recipient.' - . ' Use one of `setTo`, `setCc` or `setBcc` to define a recipient.' + . ' Use one of `setTo`, `setCc` or `setBcc` to define a recipient.', ); } } diff --git a/app/vendor/cakephp/cakephp/src/Mailer/Email.php b/app/vendor/cakephp/cakephp/src/Mailer/Email.php deleted file mode 100644 index bc2358b13..000000000 --- a/app/vendor/cakephp/cakephp/src/Mailer/Email.php +++ /dev/null @@ -1,645 +0,0 @@ - - */ - protected $_profile = []; - - /** - * Message class name. - * - * @var string - * @psalm-var class-string<\Cake\Mailer\Message> - */ - protected $messageClass = Message::class; - - /** - * Message instance. - * - * @var \Cake\Mailer\Message - */ - protected $message; - - /** - * Constructor - * - * @param array|string|null $config Array of configs, or string to load configs from app.php - */ - public function __construct($config = null) - { - $this->message = new $this->messageClass(); - - if ($config === null) { - $config = Mailer::getConfig('default'); - } - - if ($config) { - $this->setProfile($config); - } - } - - /** - * Clone Renderer instance when email object is cloned. - * - * @return void - */ - public function __clone() - { - if ($this->renderer) { - $this->renderer = clone $this->renderer; - } - - if ($this->message !== null) { - $this->message = clone $this->message; - } - } - - /** - * Magic method to forward method class to Email instance. - * - * @param string $method Method name. - * @param array $args Method arguments - * @return $this|mixed - */ - public function __call(string $method, array $args) - { - $result = $this->message->$method(...$args); - - if (strpos($method, 'get') === 0) { - return $result; - } - - $getters = ['message']; - if (in_array($method, $getters, true)) { - return $result; - } - - return $this; - } - - /** - * Get message instance. - * - * @return \Cake\Mailer\Message - */ - public function getMessage(): Message - { - return $this->message; - } - - /** - * Sets view class for render. - * - * @param string $viewClass View class name. - * @return $this - */ - public function setViewRenderer(string $viewClass) - { - $this->getRenderer()->viewBuilder()->setClassName($viewClass); - - return $this; - } - - /** - * Gets view class for render. - * - * @return string - * @psalm-suppress InvalidNullableReturnType - */ - public function getViewRenderer(): string - { - /** @psalm-suppress NullableReturnStatement */ - return $this->getRenderer()->viewBuilder()->getClassName(); - } - - /** - * Sets variables to be set on render. - * - * @param array $viewVars Variables to set for view. - * @return $this - */ - public function setViewVars(array $viewVars) - { - $this->getRenderer()->viewBuilder()->setVars($viewVars); - - return $this; - } - - /** - * Gets variables to be set on render. - * - * @return array - */ - public function getViewVars(): array - { - return $this->getRenderer()->viewBuilder()->getVars(); - } - - /** - * Sets the transport. - * - * When setting the transport you can either use the name - * of a configured transport or supply a constructed transport. - * - * @param \Cake\Mailer\AbstractTransport|string $name Either the name of a configured - * transport, or a transport instance. - * @return $this - * @throws \LogicException When the chosen transport lacks a send method. - * @throws \InvalidArgumentException When $name is neither a string nor an object. - */ - public function setTransport($name) - { - if (is_string($name)) { - $transport = TransportFactory::get($name); - } elseif (is_object($name)) { - $transport = $name; - } else { - throw new InvalidArgumentException(sprintf( - 'The value passed for the "$name" argument must be either a string, or an object, %s given.', - gettype($name) - )); - } - if (!method_exists($transport, 'send')) { - throw new LogicException(sprintf('The "%s" do not have send method.', get_class($transport))); - } - - $this->_transport = $transport; - - return $this; - } - - /** - * Gets the transport. - * - * @return \Cake\Mailer\AbstractTransport|null - */ - public function getTransport(): ?AbstractTransport - { - return $this->_transport; - } - - /** - * Get generated message (used by transport classes) - * - * @param string|null $type Use MESSAGE_* constants or null to return the full message as array - * @return array|string String if type is given, array if type is null - */ - public function message(?string $type = null) - { - if ($type === null) { - return $this->message->getBody(); - } - - $method = 'getBody' . ucfirst($type); - - return $this->message->$method(); - } - - /** - * Sets the configuration profile to use for this instance. - * - * @param array|string $config String with configuration name, or - * an array with config. - * @return $this - */ - public function setProfile($config) - { - if (is_string($config)) { - $name = $config; - $config = Mailer::getConfig($name); - if (empty($config)) { - throw new InvalidArgumentException(sprintf('Unknown email configuration "%s".', $name)); - } - unset($name); - } - - $this->_profile = $config + $this->_profile; - - $simpleMethods = [ - 'transport', - ]; - foreach ($simpleMethods as $method) { - if (isset($config[$method])) { - $this->{'set' . ucfirst($method)}($config[$method]); - unset($config[$method]); - } - } - - $viewBuilderMethods = [ - 'template', 'layout', 'theme', - ]; - foreach ($viewBuilderMethods as $method) { - if (array_key_exists($method, $config)) { - $this->getRenderer()->viewBuilder()->{'set' . ucfirst($method)}($config[$method]); - unset($config[$method]); - } - } - - if (array_key_exists('helpers', $config)) { - $this->getRenderer()->viewBuilder()->setHelpers($config['helpers'], false); - unset($config['helpers']); - } - if (array_key_exists('viewRenderer', $config)) { - $this->getRenderer()->viewBuilder()->setClassName($config['viewRenderer']); - unset($config['viewRenderer']); - } - if (array_key_exists('viewVars', $config)) { - $this->getRenderer()->viewBuilder()->setVars($config['viewVars']); - unset($config['viewVars']); - } - - $this->message->setConfig($config); - - return $this; - } - - /** - * Gets the configuration profile to use for this instance. - * - * @return array - */ - public function getProfile(): array - { - return $this->_profile; - } - - /** - * Send an email using the specified content, template and layout - * - * @param array|string|null $content String with message or array with messages - * @return array - * @throws \BadMethodCallException - * @psalm-return array{headers: string, message: string} - */ - public function send($content = null): array - { - if (is_array($content)) { - $content = implode("\n", $content) . "\n"; - } - - $this->render($content); - - $transport = $this->getTransport(); - if (!$transport) { - $msg = 'Cannot send email, transport was not defined. Did you call transport() or define ' . - ' a transport in the set profile?'; - throw new BadMethodCallException($msg); - } - $contents = $transport->send($this->message); - $this->_logDelivery($contents); - - return $contents; - } - - /** - * Render email. - * - * @param array|string|null $content Content array or string - * @return void - */ - public function render($content = null): void - { - if (is_array($content)) { - $content = implode("\n", $content) . "\n"; - } - - $this->message->setBody( - $this->getRenderer()->render( - (string)$content, - $this->message->getBodyTypes() - ) - ); - } - - /** - * Get view builder. - * - * @return \Cake\View\ViewBuilder - */ - public function viewBuilder(): ViewBuilder - { - return $this->getRenderer()->viewBuilder(); - } - - /** - * Get email renderer. - * - * @return \Cake\Mailer\Renderer - */ - public function getRenderer(): Renderer - { - if ($this->renderer === null) { - $this->renderer = new Renderer(); - } - - return $this->renderer; - } - - /** - * Set email renderer. - * - * @param \Cake\Mailer\Renderer $renderer Render instance. - * @return $this - */ - public function setRenderer(Renderer $renderer) - { - $this->renderer = $renderer; - - return $this; - } - - /** - * Log the email message delivery. - * - * @param array $contents The content with 'headers' and 'message' keys. - * @return void - */ - protected function _logDelivery(array $contents): void - { - if (empty($this->_profile['log'])) { - return; - } - $config = [ - 'level' => 'debug', - 'scope' => 'email', - ]; - if ($this->_profile['log'] !== true) { - if (!is_array($this->_profile['log'])) { - $this->_profile['log'] = ['level' => $this->_profile['log']]; - } - $config = $this->_profile['log'] + $config; - } - Log::write( - $config['level'], - PHP_EOL . $this->flatten($contents['headers']) . PHP_EOL . PHP_EOL . $this->flatten($contents['message']), - $config['scope'] - ); - } - - /** - * Converts given value to string - * - * @param array|string $value The value to convert - * @return string - */ - protected function flatten($value): string - { - return is_array($value) ? implode(';', $value) : $value; - } - - /** - * Static method to fast create an instance of \Cake\Mailer\Email - * - * @param array|string|null $to Address to send ({@see \Cake\Mailer\Email::setTo()}). - * If null, will try to use 'to' from transport config - * @param string|null $subject String of subject or null to use 'subject' from transport config - * @param array|string|null $message String with message or array with variables to be used in render - * @param array|string $config String to use Email delivery profile from app.php or array with configs - * @param bool $send Send the email or just return the instance pre-configured - * @return \Cake\Mailer\Email - * @throws \InvalidArgumentException - */ - public static function deliver( - $to = null, - ?string $subject = null, - $message = null, - $config = 'default', - bool $send = true - ) { - if (is_array($config) && !isset($config['transport'])) { - $config['transport'] = 'default'; - } - - $instance = new static($config); - if ($to !== null) { - $instance->getMessage()->setTo($to); - } - if ($subject !== null) { - $instance->getMessage()->setSubject($subject); - } - if (is_array($message)) { - $instance->setViewVars($message); - $message = null; - } elseif ($message === null) { - $config = $instance->getProfile(); - if (array_key_exists('message', $config)) { - $message = $config['message']; - } - } - - if ($send === true) { - $instance->send($message); - } - - return $instance; - } - - /** - * Reset all the internal variables to be able to send out a new email. - * - * @return $this - */ - public function reset() - { - $this->message->reset(); - if ($this->renderer !== null) { - $this->renderer->reset(); - } - $this->_transport = null; - $this->_profile = []; - - return $this; - } - - /** - * Serializes the email object to a value that can be natively serialized and re-used - * to clone this email instance. - * - * @return array Serializable array of configuration properties. - * @throws \Exception When a view var object can not be properly serialized. - */ - public function jsonSerialize(): array - { - $array = $this->message->jsonSerialize(); - $array['viewConfig'] = $this->getRenderer()->viewBuilder()->jsonSerialize(); - - return $array; - } - - /** - * Configures an email instance object from serialized config. - * - * @param array $config Email configuration array. - * @return $this - */ - public function createFromArray(array $config) - { - if (isset($config['viewConfig'])) { - $this->getRenderer()->viewBuilder()->createFromArray($config['viewConfig']); - unset($config['viewConfig']); - } - - if ($this->message === null) { - $this->message = new $this->messageClass(); - } - $this->message->createFromArray($config); - - return $this; - } - - /** - * Serializes the Email object. - * - * @return string - */ - public function serialize(): string - { - $array = $this->__serialize(); - - return serialize($array); - } - - /** - * Magic method used for serializing the Email object. - * - * @return array - */ - public function __serialize(): array - { - $array = $this->jsonSerialize(); - array_walk_recursive($array, function (&$item, $key): void { - if ($item instanceof SimpleXMLElement) { - $item = json_decode(json_encode((array)$item), true); - } - }); - - /** @psalm-var array */ - return $array; - } - - /** - * Unserializes the Email object. - * - * @param string $data Serialized string. - * @return void - */ - public function unserialize($data): void - { - $this->createFromArray(unserialize($data)); - } - - /** - * Magic method used to rebuild the Email object. - * - * @param array $data Data array. - * @return void - */ - public function __unserialize(array $data): void - { - $this->createFromArray($data); - } - - /** - * Proxy all static method calls (for methods provided by StaticConfigTrait) to Mailer. - * - * @param string $name Method name. - * @param array $arguments Method argument. - * @return mixed - */ - public static function __callStatic($name, $arguments) - { - return [Mailer::class, $name](...$arguments); - } -} diff --git a/app/vendor/cakephp/cakephp/src/Mailer/Exception/MissingActionException.php b/app/vendor/cakephp/cakephp/src/Mailer/Exception/MissingActionException.php index eafd5be0a..2bfb2f75d 100644 --- a/app/vendor/cakephp/cakephp/src/Mailer/Exception/MissingActionException.php +++ b/app/vendor/cakephp/cakephp/src/Mailer/Exception/MissingActionException.php @@ -24,5 +24,5 @@ class MissingActionException extends CakeException /** * @inheritDoc */ - protected $_messageTemplate = 'Mail %s::%s() could not be found, or is not accessible.'; + protected string $_messageTemplate = 'Mail %s::%s() could not be found, or is not accessible.'; } diff --git a/app/vendor/cakephp/cakephp/src/Mailer/Exception/MissingMailerException.php b/app/vendor/cakephp/cakephp/src/Mailer/Exception/MissingMailerException.php index 4b3efcee3..76ab7069e 100644 --- a/app/vendor/cakephp/cakephp/src/Mailer/Exception/MissingMailerException.php +++ b/app/vendor/cakephp/cakephp/src/Mailer/Exception/MissingMailerException.php @@ -26,5 +26,5 @@ class MissingMailerException extends CakeException /** * @inheritDoc */ - protected $_messageTemplate = 'Mailer class "%s" could not be found.'; + protected string $_messageTemplate = 'Mailer class `%s` could not be found.'; } diff --git a/app/vendor/cakephp/cakephp/src/Mailer/Mailer.php b/app/vendor/cakephp/cakephp/src/Mailer/Mailer.php index 1cd89c23f..5dc38ddc2 100644 --- a/app/vendor/cakephp/cakephp/src/Mailer/Mailer.php +++ b/app/vendor/cakephp/cakephp/src/Mailer/Mailer.php @@ -15,9 +15,7 @@ namespace Cake\Mailer; use BadMethodCallException; -use Cake\Core\Exception\CakeException; use Cake\Core\StaticConfigTrait; -use Cake\Datasource\ModelAwareTrait; use Cake\Event\EventListenerInterface; use Cake\Log\Log; use Cake\Mailer\Exception\MissingActionException; @@ -120,7 +118,7 @@ * @method $this setHeaders(array $headers) Sets headers for the message. {@see \Cake\Mailer\Message::setHeaders()} * @method $this addHeaders(array $headers) Add header for the message. {@see \Cake\Mailer\Message::addHeaders()} * @method $this getHeaders(array $include = []) Get list of headers. {@see \Cake\Mailer\Message::getHeaders()} - * @method $this setEmailFormat($format) Sets email format. {@see \Cake\Mailer\Message::getHeaders()} + * @method $this setEmailFormat($format) Sets email format. {@see \Cake\Mailer\Message::setEmailFormat()} * @method string getEmailFormat() Gets email format. {@see \Cake\Mailer\Message::getEmailFormat()} * @method $this setMessageId($message) Sets message ID. {@see \Cake\Mailer\Message::setMessageId()} * @method string|bool getMessageId() Gets message ID. {@see \Cake\Mailer\Message::getMessageId()} @@ -132,10 +130,8 @@ * @method array|string getBody(?string $type = null) Get generated message body as array. * {@see \Cake\Mailer\Message::getBody()} */ -#[\AllowDynamicProperties] class Mailer implements EventListenerInterface { - use ModelAwareTrait; use LocatorAwareTrait; use StaticConfigTrait; @@ -144,36 +140,36 @@ class Mailer implements EventListenerInterface * * @var string */ - public static $name; + public static string $name; /** * The transport instance to use for sending mail. * * @var \Cake\Mailer\AbstractTransport|null */ - protected $transport; + protected ?AbstractTransport $transport = null; /** * Message class name. * * @var string - * @psalm-var class-string<\Cake\Mailer\Message> + * @phpstan-var class-string<\Cake\Mailer\Message> */ - protected $messageClass = Message::class; + protected string $messageClass = Message::class; /** * Message instance. * * @var \Cake\Mailer\Message */ - protected $message; + protected Message $message; /** * Email Renderer * * @var \Cake\Mailer\Renderer|null */ - protected $renderer; + protected ?Renderer $renderer = null; /** * Hold message, renderer and transport instance for restoring after running @@ -181,7 +177,7 @@ class Mailer implements EventListenerInterface * * @var array */ - protected $clonedInstances = [ + protected array $clonedInstances = [ 'message' => null, 'renderer' => null, 'transport' => null, @@ -191,31 +187,25 @@ class Mailer implements EventListenerInterface * Mailer driver class map. * * @var array - * @psalm-var array + * @phpstan-var array */ - protected static $_dsnClassMap = []; + protected static array $_dsnClassMap = []; /** * @var array|null */ - protected $logConfig = null; + protected ?array $logConfig = null; /** * Constructor * * @param array|string|null $config Array of configs, or string to load configs from app.php */ - public function __construct($config = null) + public function __construct(array|string|null $config = null) { $this->message = new $this->messageClass(); - if ($this->defaultTable !== null) { - $this->modelClass = $this->defaultTable; - } - - if ($config === null) { - $config = static::getConfig('default'); - } + $config ??= static::getConfig('default'); if ($config) { $this->setProfile($config); @@ -239,11 +229,7 @@ public function viewBuilder(): ViewBuilder */ public function getRenderer(): Renderer { - if ($this->renderer === null) { - $this->renderer = new Renderer(); - } - - return $this->renderer; + return $this->renderer ??= new Renderer(); } /** @@ -274,9 +260,14 @@ public function getMessage(): Message * * @param \Cake\Mailer\Message $message Message instance. * @return $this + * @deprecated 5.1.0 Configure the mailer according to the documentation instead of manually setting the Message instance. */ public function setMessage(Message $message) { + deprecationWarning( + '5.1.0', + 'Setting the message instance is deprecated. Configure the mailer according to the documentation instead.', + ); $this->message = $message; return $this; @@ -292,28 +283,13 @@ public function setMessage(Message $message) public function __call(string $method, array $args) { $result = $this->message->$method(...$args); - if (strpos($method, 'get') === 0) { + if (str_starts_with($method, 'get')) { return $result; } return $this; } - /** - * Sets email view vars. - * - * @param array|string $key Variable name or hash of view variables. - * @param mixed $value View variable value. - * @return $this - * @deprecated 4.0.0 Use {@link Mailer::setViewVars()} instead. - */ - public function set($key, $value = null) - { - deprecationWarning('Mailer::set() is deprecated. Use setViewVars() instead.'); - - return $this->setViewVars($key, $value); - } - /** * Sets email view vars. * @@ -321,7 +297,7 @@ public function set($key, $value = null) * @param mixed $value View variable value. * @return $this */ - public function setViewVars($key, $value = null) + public function setViewVars(array|string $key, mixed $value = null) { $this->getRenderer()->set($key, $value); @@ -331,14 +307,17 @@ public function setViewVars($key, $value = null) /** * Sends email. * + * If an `$action` is specified the internal state of the mailer will be + * backed up and restored after the action is run. + * * @param string|null $action The name of the mailer action to trigger. * If no action is specified then all other method arguments will be ignored. * @param array $args Arguments to pass to the triggered mailer action. * @param array $headers Headers to set. - * @return array + * @return array Contains 'headers' and 'message' keys. Additional keys allowed. + * @phpstan-return array{headers: string, message: string, ...} * @throws \Cake\Mailer\Exception\MissingActionException * @throws \BadMethodCallException - * @psalm-return array{headers: string, message: string} */ public function send(?string $action = null, array $args = [], array $headers = []): array { @@ -353,11 +332,7 @@ public function send(?string $action = null, array $args = [], array $headers = ]); } - $this->clonedInstances['message'] = clone $this->message; - $this->clonedInstances['renderer'] = clone $this->getRenderer(); - if ($this->transport !== null) { - $this->clonedInstances['transport'] = clone $this->transport; - } + $this->backup(); $this->getMessage()->setHeaders($headers); if (!$this->viewBuilder()->getTemplate()) { @@ -385,7 +360,7 @@ public function render(string $content = '') { $content = $this->getRenderer()->render( $content, - $this->message->getBodyTypes() + $this->message->getBodyTypes(), ); $this->message->setBody($content); @@ -397,10 +372,10 @@ public function render(string $content = '') * Render content and send email using configured transport. * * @param string $content Content. - * @return array - * @psalm-return array{headers: string, message: string} + * @return array Contains 'headers' and 'message' keys. Additional keys allowed. + * @phpstan-return array{headers: string, message: string, ...} */ - public function deliver(string $content = '') + public function deliver(string $content = ''): array { $this->render($content); @@ -417,13 +392,13 @@ public function deliver(string $content = '') * an array with config. * @return $this */ - public function setProfile($config) + public function setProfile(array|string $config) { if (is_string($config)) { $name = $config; $config = static::getConfig($name); - if (empty($config)) { - throw new InvalidArgumentException(sprintf('Unknown email configuration "%s".', $name)); + if (!$config) { + throw new InvalidArgumentException(sprintf('Unknown email configuration `%s`.', $name)); } unset($name); } @@ -449,7 +424,7 @@ public function setProfile($config) } if (array_key_exists('helpers', $config)) { - $this->viewBuilder()->setHelpers($config['helpers'], false); + $this->viewBuilder()->setHelpers($config['helpers']); unset($config['helpers']); } if (array_key_exists('viewRenderer', $config)) { @@ -486,26 +461,15 @@ public function setProfile($config) * transport, or a transport instance. * @return $this * @throws \LogicException When the chosen transport lacks a send method. - * @throws \InvalidArgumentException When $name is neither a string nor an object. */ - public function setTransport($name) + public function setTransport(AbstractTransport|string $name) { if (is_string($name)) { - $transport = TransportFactory::get($name); - } elseif (is_object($name)) { - $transport = $name; - if (!$transport instanceof AbstractTransport) { - throw new CakeException('Transport class must extend Cake\Mailer\AbstractTransport'); - } + $this->transport = TransportFactory::get($name); } else { - throw new InvalidArgumentException(sprintf( - 'The value passed for the "$name" argument must be either a string, or an object, %s given.', - gettype($name) - )); + $this->transport = $name; } - $this->transport = $transport; - return $this; } @@ -519,13 +483,29 @@ public function getTransport(): AbstractTransport if ($this->transport === null) { throw new BadMethodCallException( 'Transport was not defined. ' - . 'You must set on using setTransport() or set `transport` option in your mailer profile.' + . 'You must set on using setTransport() or set `transport` option in your mailer profile.', ); } return $this->transport; } + /** + * Backup message, renderer, transport instances before an action is run. + * + * @return void + */ + protected function backup(): void + { + $this->clonedInstances['message'] = clone $this->message; + if ($this->renderer !== null) { + $this->clonedInstances['renderer'] = clone $this->renderer; + } + if ($this->transport !== null) { + $this->clonedInstances['transport'] = clone $this->transport; + } + } + /** * Restore message, renderer, transport instances to state before an action was run. * @@ -535,7 +515,11 @@ protected function restore() { foreach (array_keys($this->clonedInstances) as $key) { if ($this->clonedInstances[$key] === null) { - $this->{$key} = null; + if ($key === 'message') { + $this->message->reset(); + } else { + $this->{$key} = null; + } } else { $this->{$key} = clone $this->clonedInstances[$key]; $this->clonedInstances[$key] = null; @@ -567,20 +551,20 @@ public function reset() /** * Log the email message delivery. * - * @param array $contents The content with 'headers' and 'message' keys. + * @param array $contents The content with 'headers' and 'message' keys. + * @phpstan-param array{headers: string, message: string, ...} $contents * @return void - * @psalm-param array{headers: string, message: string} $contents */ protected function logDelivery(array $contents): void { - if (empty($this->logConfig)) { + if (!$this->logConfig) { return; } Log::write( $this->logConfig['level'], PHP_EOL . $this->flatten($contents['headers']) . PHP_EOL . PHP_EOL . $this->flatten($contents['message']), - $this->logConfig['scope'] + $this->logConfig['scope'], ); } @@ -590,11 +574,11 @@ protected function logDelivery(array $contents): void * @param array|string|true $log Log config. * @return void */ - protected function setLogConfig($log) + protected function setLogConfig(array|string|bool $log): void { $config = [ 'level' => 'debug', - 'scope' => 'email', + 'scope' => ['cake.mailer', 'email'], ]; if ($log !== true) { if (!is_array($log)) { @@ -612,7 +596,7 @@ protected function setLogConfig($log) * @param array|string $value The value to convert * @return string */ - protected function flatten($value): string + protected function flatten(array|string $value): string { return is_array($value) ? implode(';', $value) : $value; } diff --git a/app/vendor/cakephp/cakephp/src/Mailer/MailerAwareTrait.php b/app/vendor/cakephp/cakephp/src/Mailer/MailerAwareTrait.php index dee44ada9..1b7611ff1 100644 --- a/app/vendor/cakephp/cakephp/src/Mailer/MailerAwareTrait.php +++ b/app/vendor/cakephp/cakephp/src/Mailer/MailerAwareTrait.php @@ -36,7 +36,7 @@ trait MailerAwareTrait * @return \Cake\Mailer\Mailer * @throws \Cake\Mailer\Exception\MissingMailerException if undefined mailer class. */ - protected function getMailer(string $name, $config = null): Mailer + protected function getMailer(string $name, array|string|null $config = null): Mailer { $className = App::className($name, 'Mailer', 'Mailer'); if ($className === null) { diff --git a/app/vendor/cakephp/cakephp/src/Mailer/Message.php b/app/vendor/cakephp/cakephp/src/Mailer/Message.php index 78ae98a62..9e02bb3e4 100644 --- a/app/vendor/cakephp/cakephp/src/Mailer/Message.php +++ b/app/vendor/cakephp/cakephp/src/Mailer/Message.php @@ -17,7 +17,6 @@ namespace Cake\Mailer; use Cake\Core\Configure; -use Cake\Core\Exception\CakeException; use Cake\Http\Client\FormDataPart; use Cake\Utility\Hash; use Cake\Utility\Security; @@ -26,7 +25,7 @@ use InvalidArgumentException; use JsonSerializable; use Psr\Http\Message\UploadedFileInterface; -use Serializable; +use RuntimeException; use SimpleXMLElement; use function Cake\Core\env; @@ -36,7 +35,7 @@ * This class is used for sending Internet Message Format based * on the standard outlined in https://www.rfc-editor.org/rfc/rfc2822.txt */ -class Message implements JsonSerializable, Serializable +class Message implements JsonSerializable { /** * Line length - no should more - RFC 2822 - 2.1.1 @@ -85,35 +84,35 @@ class Message implements JsonSerializable, Serializable * * @var array */ - protected $to = []; + protected array $to = []; /** * The mail which the email is sent from * * @var array */ - protected $from = []; + protected array $from = []; /** * The sender email * * @var array */ - protected $sender = []; + protected array $sender = []; /** * List of email(s) that the recipient will reply to * * @var array */ - protected $replyTo = []; + protected array $replyTo = []; /** * The read receipt email * * @var array */ - protected $readReceipt = []; + protected array $readReceipt = []; /** * The mail that will be used in case of any errors like @@ -123,7 +122,7 @@ class Message implements JsonSerializable, Serializable * * @var array */ - protected $returnPath = []; + protected array $returnPath = []; /** * Carbon Copy @@ -133,7 +132,7 @@ class Message implements JsonSerializable, Serializable * * @var array */ - protected $cc = []; + protected array $cc = []; /** * Blind Carbon Copy @@ -143,14 +142,14 @@ class Message implements JsonSerializable, Serializable * * @var array */ - protected $bcc = []; + protected array $bcc = []; /** * Message ID * * @var string|bool */ - protected $messageId = true; + protected string|bool $messageId = true; /** * Domain for messageId generation. @@ -158,14 +157,14 @@ class Message implements JsonSerializable, Serializable * * @var string */ - protected $domain = ''; + protected string $domain = ''; /** * The subject of the email * * @var string */ - protected $subject = ''; + protected string $subject = ''; /** * Associative array of a user defined headers @@ -173,49 +172,49 @@ class Message implements JsonSerializable, Serializable * * @var array */ - protected $headers = []; + protected array $headers = []; /** * Text message * * @var string */ - protected $textMessage = ''; + protected string $textMessage = ''; /** * Html message * * @var string */ - protected $htmlMessage = ''; + protected string $htmlMessage = ''; /** * Final message to send * * @var array */ - protected $message = []; + protected array $message = []; /** * Available formats to be sent. * * @var array */ - protected $emailFormatAvailable = [self::MESSAGE_TEXT, self::MESSAGE_HTML, self::MESSAGE_BOTH]; + protected array $emailFormatAvailable = [self::MESSAGE_TEXT, self::MESSAGE_HTML, self::MESSAGE_BOTH]; /** * What format should the email be sent in * * @var string */ - protected $emailFormat = self::MESSAGE_TEXT; + protected string $emailFormat = self::MESSAGE_TEXT; /** * Charset the email body is sent in * * @var string */ - protected $charset = 'utf-8'; + protected string $charset = 'utf-8'; /** * Charset the email header is sent in @@ -223,7 +222,7 @@ class Message implements JsonSerializable, Serializable * * @var string|null */ - protected $headerCharset; + protected ?string $headerCharset = null; /** * The email transfer encoding used. @@ -231,14 +230,14 @@ class Message implements JsonSerializable, Serializable * * @var string|null */ - protected $transferEncoding; + protected ?string $transferEncoding = null; /** * Available encoding to be set for transfer. * * @var array */ - protected $transferEncodingAvailable = [ + protected array $transferEncodingAvailable = [ '7bit', '8bit', 'base64', @@ -251,7 +250,7 @@ class Message implements JsonSerializable, Serializable * * @var string|null */ - protected $appCharset; + protected ?string $appCharset = null; /** * List of files that should be attached to the email. @@ -260,35 +259,35 @@ class Message implements JsonSerializable, Serializable * * @var array */ - protected $attachments = []; + protected array $attachments = []; /** * If set, boundary to use for multipart mime messages * * @var string|null */ - protected $boundary; + protected ?string $boundary = null; /** * Contains the optional priority of the email. * * @var int|null */ - protected $priority; + protected ?int $priority = null; /** * 8Bit character sets * * @var array */ - protected $charset8bit = ['UTF-8', 'SHIFT_JIS']; + protected array $charset8bit = ['UTF-8', 'SHIFT_JIS']; /** * Define Content-Type charset name * * @var array */ - protected $contentTypeCharset = [ + protected array $contentTypeCharset = [ 'ISO-2022-JP-MS' => 'ISO-2022-JP', ]; @@ -296,18 +295,18 @@ class Message implements JsonSerializable, Serializable * Regex for email validation * * If null, filter_var() will be used. Use the emailPattern() method - * to set a custom pattern.' + * to set a custom pattern. * * @var string|null */ - protected $emailPattern = self::EMAIL_PATTERN; + protected ?string $emailPattern = self::EMAIL_PATTERN; /** * Properties that could be serialized * * @var array */ - protected $serializableProperties = [ + protected array $serializableProperties = [ 'to', 'from', 'sender', 'replyTo', 'cc', 'bcc', 'subject', 'returnPath', 'readReceipt', 'emailFormat', 'emailPattern', 'domain', 'attachments', 'messageId', 'headers', 'appCharset', 'charset', 'headerCharset', @@ -325,8 +324,8 @@ public function __construct(?array $config = null) if ($this->appCharset !== null) { $this->charset = $this->appCharset; } - $this->domain = preg_replace('/\:\d+$/', '', (string)env('HTTP_HOST')); - if (empty($this->domain)) { + $this->domain = (string)preg_replace('/\:\d+$/', '', (string)env('HTTP_HOST')); + if (!$this->domain) { $this->domain = php_uname('n'); } @@ -344,7 +343,7 @@ public function __construct(?array $config = null) * @return $this * @throws \InvalidArgumentException */ - public function setFrom($email, ?string $name = null) + public function setFrom(array|string $email, ?string $name = null) { return $this->setEmailSingle('from', $email, $name, 'From requires only 1 email address.'); } @@ -369,7 +368,7 @@ public function getFrom(): array * @throws \InvalidArgumentException * @link https://tools.ietf.org/html/rfc2822.html#section-3.6.2 */ - public function setSender($email, ?string $name = null) + public function setSender(array|string $email, ?string $name = null) { return $this->setEmailSingle('sender', $email, $name, 'Sender requires only 1 email address.'); } @@ -394,7 +393,7 @@ public function getSender(): array * @return $this * @throws \InvalidArgumentException */ - public function setReplyTo($email, ?string $name = null) + public function setReplyTo(array|string $email, ?string $name = null) { return $this->setEmail('replyTo', $email, $name); } @@ -417,7 +416,7 @@ public function getReplyTo(): array * @param string|null $name Name * @return $this */ - public function addReplyTo($email, ?string $name = null) + public function addReplyTo(array|string $email, ?string $name = null) { return $this->addEmail('replyTo', $email, $name); } @@ -431,13 +430,13 @@ public function addReplyTo($email, ?string $name = null) * @return $this * @throws \InvalidArgumentException */ - public function setReadReceipt($email, ?string $name = null) + public function setReadReceipt(array|string $email, ?string $name = null) { return $this->setEmailSingle( 'readReceipt', $email, $name, - 'Disposition-Notification-To requires only 1 email address.' + 'Disposition-Notification-To requires only 1 email address.', ); } @@ -460,7 +459,7 @@ public function getReadReceipt(): array * @return $this * @throws \InvalidArgumentException */ - public function setReturnPath($email, ?string $name = null) + public function setReturnPath(array|string $email, ?string $name = null) { return $this->setEmailSingle('returnPath', $email, $name, 'Return-Path requires only 1 email address.'); } @@ -483,7 +482,7 @@ public function getReturnPath(): array * @param string|null $name Name * @return $this */ - public function setTo($email, ?string $name = null) + public function setTo(array|string $email, ?string $name = null) { return $this->setEmail('to', $email, $name); } @@ -506,7 +505,7 @@ public function getTo(): array * @param string|null $name Name * @return $this */ - public function addTo($email, ?string $name = null) + public function addTo(array|string $email, ?string $name = null) { return $this->addEmail('to', $email, $name); } @@ -519,7 +518,7 @@ public function addTo($email, ?string $name = null) * @param string|null $name Name * @return $this */ - public function setCc($email, ?string $name = null) + public function setCc(array|string $email, ?string $name = null) { return $this->setEmail('cc', $email, $name); } @@ -542,7 +541,7 @@ public function getCc(): array * @param string|null $name Name * @return $this */ - public function addCc($email, ?string $name = null) + public function addCc(array|string $email, ?string $name = null) { return $this->addEmail('cc', $email, $name); } @@ -555,7 +554,7 @@ public function addCc($email, ?string $name = null) * @param string|null $name Name * @return $this */ - public function setBcc($email, ?string $name = null) + public function setBcc(array|string $email, ?string $name = null) { return $this->setEmail('bcc', $email, $name); } @@ -578,7 +577,7 @@ public function getBcc(): array * @param string|null $name Name * @return $this */ - public function addBcc($email, ?string $name = null) + public function addBcc(array|string $email, ?string $name = null) { return $this->addEmail('bcc', $email, $name); } @@ -644,8 +643,8 @@ public function setTransferEncoding(?string $encoding) throw new InvalidArgumentException( sprintf( 'Transfer encoding not available. Can be : %s.', - implode(', ', $this->transferEncodingAvailable) - ) + implode(', ', $this->transferEncodingAvailable), + ), ); } } @@ -699,7 +698,7 @@ public function getEmailPattern(): ?string * @return $this * @throws \InvalidArgumentException */ - protected function setEmail(string $varName, $email, ?string $name) + protected function setEmail(string $varName, array|string $email, ?string $name) { if (!is_array($email)) { $this->validateEmail($email, $varName); @@ -740,9 +739,9 @@ protected function validateEmail(string $email, string $context): void $context = ltrim($context, '_'); if ($email === '') { - throw new InvalidArgumentException(sprintf('The email set for "%s" is empty.', $context)); + throw new InvalidArgumentException(sprintf('The email set for `%s` is empty.', $context)); } - throw new InvalidArgumentException(sprintf('Invalid email set for "%s". You passed "%s".', $context, $email)); + throw new InvalidArgumentException(sprintf('Invalid email set for `%s`. You passed `%s`.', $context, $email)); } /** @@ -756,7 +755,7 @@ protected function validateEmail(string $email, string $context): void * @return $this * @throws \InvalidArgumentException */ - protected function setEmailSingle(string $varName, $email, ?string $name, string $throwMessage) + protected function setEmailSingle(string $varName, array|string $email, ?string $name, string $throwMessage) { if ($email === []) { $this->{$varName} = $email; @@ -784,13 +783,11 @@ protected function setEmailSingle(string $varName, $email, ?string $name, string * @return $this * @throws \InvalidArgumentException */ - protected function addEmail(string $varName, $email, ?string $name) + protected function addEmail(string $varName, array|string $email, ?string $name) { if (!is_array($email)) { $this->validateEmail($email, $varName); - if ($name === null) { - $name = $email; - } + $name ??= $email; $this->{$varName}[$email] = $name; return $this; @@ -881,8 +878,8 @@ public function addHeaders(array $headers) * - `bcc` * - `subject` * - * @param array $include List of headers. - * @return array + * @param array $include List of headers. + * @return array */ public function getHeaders(array $include = []): array { @@ -896,7 +893,7 @@ public function getHeaders(array $include = []): array 'from', 'sender', 'replyTo', 'readReceipt', 'returnPath', 'to', 'cc', 'bcc', 'subject', ], - false + false, ); $include += $defaults; @@ -950,9 +947,9 @@ public function getHeaders(array $include = []): array $headers['MIME-Version'] = '1.0'; if ($this->attachments) { - $headers['Content-Type'] = 'multipart/mixed; boundary="' . (string)$this->boundary . '"'; + $headers['Content-Type'] = 'multipart/mixed; boundary="' . $this->boundary . '"'; } elseif ($this->emailFormat === static::MESSAGE_BOTH) { - $headers['Content-Type'] = 'multipart/alternative; boundary="' . (string)$this->boundary . '"'; + $headers['Content-Type'] = 'multipart/alternative; boundary="' . $this->boundary . '"'; } elseif ($this->emailFormat === static::MESSAGE_TEXT) { $headers['Content-Type'] = 'text/plain; charset=' . $this->getContentTypeCharset(); } elseif ($this->emailFormat === static::MESSAGE_HTML) { @@ -982,7 +979,7 @@ public function getHeadersString(array $include = [], string $eol = "\r\n", ?Clo $headers = []; foreach ($lines as $key => $value) { - if (empty($value) && $value !== '0') { + if ($value === '') { continue; } @@ -1073,14 +1070,14 @@ public function getBodyTypes(): array * @return $this * @throws \InvalidArgumentException */ - public function setMessageId($message) + public function setMessageId(string|bool $message) { if (is_bool($message)) { $this->messageId = $message; } else { if (!preg_match('/^\<.+@.+\>$/', $message)) { throw new InvalidArgumentException( - 'Invalid format to Message-ID. The text should be something like ""' + 'Invalid format to Message-ID. The text should be something like ""', ); } $this->messageId = $message; @@ -1094,7 +1091,7 @@ public function setMessageId($message) * * @return string|bool */ - public function getMessageId() + public function getMessageId(): string|bool { return $this->messageId; } @@ -1129,13 +1126,7 @@ public function getDomain(): string * * Attachments can be defined in a few forms depending on how much control you need: * - * Attach a single file: - * - * ``` - * $this->setAttachments('path/to/file'); - * ``` - * - * Attach a file with a different filename: + * Attach a file: * * ``` * $this->setAttachments(['custom_name.txt' => 'path/to/file.txt']); @@ -1191,14 +1182,14 @@ public function setAttachments(array $attachments) } elseif ($fileInfo['file'] instanceof UploadedFileInterface) { $fileInfo['mimetype'] = $fileInfo['file']->getClientMediaType(); if (is_int($name)) { - /** @var string $name */ $name = $fileInfo['file']->getClientFilename(); + assert(is_string($name)); } } elseif (is_string($fileInfo['file'])) { $fileName = $fileInfo['file']; $fileInfo['file'] = realpath($fileInfo['file']); if ($fileInfo['file'] === false || !file_exists($fileInfo['file'])) { - throw new InvalidArgumentException(sprintf('File not found: "%s"', $fileName)); + throw new InvalidArgumentException(sprintf('File not found: `%s`', $fileName)); } if (is_int($name)) { $name = basename($fileInfo['file']); @@ -1206,7 +1197,7 @@ public function setAttachments(array $attachments) } else { throw new InvalidArgumentException(sprintf( 'File must be a filepath or UploadedFileInterface instance. Found `%s` instead.', - gettype($fileInfo['file']) + gettype($fileInfo['file']), )); } if ( @@ -1259,9 +1250,9 @@ public function addAttachments(array $attachments) * * @return array */ - public function getBody() + public function getBody(): array { - if (empty($this->message)) { + if (!$this->message) { $this->message = $this->generateMessage(); } @@ -1296,7 +1287,7 @@ protected function createBoundary(): void $this->emailFormat === static::MESSAGE_BOTH ) ) { - $this->boundary = md5(Security::randomBytes(16)); + $this->boundary = hash('xxh128', Security::randomBytes(16)); } } @@ -1311,20 +1302,21 @@ protected function generateMessage(): array $msg = []; $contentIds = array_filter((array)Hash::extract($this->attachments, '{s}.contentId')); - $hasInlineAttachments = count($contentIds) > 0; - $hasAttachments = !empty($this->attachments); + $hasInlineAttachments = $contentIds !== []; + $hasAttachments = $this->attachments !== []; $hasMultipleTypes = $this->emailFormat === static::MESSAGE_BOTH; $multiPart = ($hasAttachments || $hasMultipleTypes); - /** @var string $boundary */ - $boundary = $this->boundary; - $relBoundary = $textBoundary = $boundary; + $boundary = $this->boundary ?? ''; + $relBoundary = $boundary; + $textBoundary = $boundary; if ($hasInlineAttachments) { $msg[] = '--' . $boundary; $msg[] = 'Content-Type: multipart/related; boundary="rel-' . $boundary . '"'; $msg[] = ''; - $relBoundary = $textBoundary = 'rel-' . $boundary; + $relBoundary = 'rel-' . $boundary; + $textBoundary = 'rel-' . $boundary; } if ($hasMultipleTypes && $hasAttachments) { @@ -1400,10 +1392,7 @@ protected function generateMessage(): array */ protected function attachFiles(?string $boundary = null): array { - if ($boundary === null) { - /** @var string $boundary */ - $boundary = $this->boundary; - } + $boundary ??= $this->boundary; $msg = []; foreach ($this->attachments as $filename => $fileInfo) { @@ -1440,10 +1429,7 @@ protected function attachFiles(?string $boundary = null): array */ protected function attachInlineFiles(?string $boundary = null): array { - if ($boundary === null) { - /** @var string $boundary */ - $boundary = $this->boundary; - } + $boundary ??= $this->boundary; $msg = []; foreach ($this->getAttachments() as $filename => $fileInfo) { @@ -1526,8 +1512,8 @@ public function setBody(array $content) foreach ($content as $type => $text) { if (!in_array($type, $this->emailFormatAvailable, true)) { throw new InvalidArgumentException(sprintf( - 'Invalid message type: "%s". Valid types are: "text", "html".', - $type + 'Invalid message type: `%s`. Valid types are: `text`, `html`.', + $type, )); } @@ -1578,7 +1564,7 @@ public function setBodyHtml(string $content) * * @return string */ - public function getBodyText() + public function getBodyText(): string { return $this->textMessage; } @@ -1588,7 +1574,7 @@ public function getBodyText() * * @return string */ - public function getBodyHtml() + public function getBodyHtml(): string { return $this->htmlMessage; } @@ -1608,10 +1594,20 @@ protected function encodeString(string $text, string $charset): string } if ($this->appCharset === null) { - return mb_convert_encoding($text, $charset); + $encoded = mb_convert_encoding($text, $charset); + if ($encoded === false) { + throw new RuntimeException('mb_convert_encoding failed.'); + } + + return $encoded; } - return mb_convert_encoding($text, $charset, $this->appCharset); + $encoded = mb_convert_encoding($text, $charset, $this->appCharset); + if ($encoded === false) { + throw new RuntimeException('mb_convert_encoding failed.'); + } + + return $encoded; } /** @@ -1632,7 +1628,7 @@ protected function wrap(?string $message = null, int $wrapLength = self::LINE_LE $cut = ($wrapLength === static::LINE_LENGTH_MUST); foreach ($lines as $line) { - if (empty($line) && $line !== '0') { + if ($line === '') { $formatted[] = ''; continue; } @@ -1643,13 +1639,14 @@ protected function wrap(?string $message = null, int $wrapLength = self::LINE_LE if (!preg_match('/<[a-z]+.*>/i', $line)) { $formatted = array_merge( $formatted, - explode("\n", Text::wordWrap($line, $wrapLength, "\n", $cut)) + explode("\n", Text::wordWrap($line, $wrapLength, "\n", $cut)), ); continue; } $tagOpen = false; - $tmpLine = $tag = ''; + $tmpLine = ''; + $tag = ''; $tmpLineLength = 0; for ($i = 0, $count = strlen($line); $i < $count; $i++) { $char = $line[$i]; @@ -1664,7 +1661,7 @@ protected function wrap(?string $message = null, int $wrapLength = self::LINE_LE if ($tmpLineLength > 0) { $formatted = array_merge( $formatted, - explode("\n", Text::wordWrap(trim($tmpLine), $wrapLength, "\n", $cut)) + explode("\n", Text::wordWrap(trim($tmpLine), $wrapLength, "\n", $cut)), ); $tmpLine = ''; $tmpLineLength = 0; @@ -1714,7 +1711,7 @@ protected function wrap(?string $message = null, int $wrapLength = self::LINE_LE } } } - if (!empty($tmpLine)) { + if ($tmpLine) { $formatted[] = $tmpLine; } } @@ -1767,7 +1764,6 @@ protected function encodeForHeader(string $text): string return $text; } - /** @var string $restore */ $restore = mb_internal_encoding(); mb_internal_encoding($this->appCharset); $return = mb_encode_mimeheader($text, $this->getHeaderCharset(), 'B'); @@ -1788,7 +1784,6 @@ protected function decodeForHeader(string $text): string return $text; } - /** @var string $restore */ $restore = mb_internal_encoding(); mb_internal_encoding($this->appCharset); $return = mb_decode_mimeheader($text); @@ -1804,7 +1799,7 @@ protected function decodeForHeader(string $text): string * or UploadedFileInterface instance. * @return string File contents in base64 encoding */ - protected function readFile($file): string + protected function readFile(UploadedFileInterface|string $file): string { if (is_string($file)) { $content = (string)file_get_contents($file); @@ -1894,18 +1889,6 @@ public function createFromArray(array $config) return $this; } - /** - * Serializes the Email object. - * - * @return string - */ - public function serialize(): string - { - $array = $this->__serialize(); - - return serialize($array); - } - /** * Magic method used for serializing the Message object. * @@ -1916,30 +1899,14 @@ public function __serialize(): array $array = $this->jsonSerialize(); array_walk_recursive($array, function (&$item, $key): void { if ($item instanceof SimpleXMLElement) { - $item = json_decode(json_encode((array)$item), true); + $item = json_decode((string)json_encode((array)$item), true); } }); - /** @psalm-var array */ + /** @var array */ return $array; } - /** - * Unserializes the Message object. - * - * @param string $data Serialized string. - * @return void - */ - public function unserialize($data) - { - $array = unserialize($data); - if (!is_array($array)) { - throw new CakeException('Unable to unserialize message.'); - } - - $this->createFromArray($array); - } - /** * Magic method used to rebuild the Message object. * diff --git a/app/vendor/cakephp/cakephp/src/Mailer/Renderer.php b/app/vendor/cakephp/cakephp/src/Mailer/Renderer.php index 32cef1f86..dbb7e41da 100644 --- a/app/vendor/cakephp/cakephp/src/Mailer/Renderer.php +++ b/app/vendor/cakephp/cakephp/src/Mailer/Renderer.php @@ -49,16 +49,16 @@ public function __construct() * of the specified content types for the email. * * @param string $content The content. - * @param array $types Content types to render. Valid array values are Message::MESSAGE_HTML, Message::MESSAGE_TEXT. + * @param array $types Content types to render. Valid array values are {@link Message::MESSAGE_HTML}, {@link Message::MESSAGE_TEXT}. * @return array The rendered content with "html" and/or "text" keys. - * @psalm-param array<\Cake\Mailer\Message::MESSAGE_HTML|\Cake\Mailer\Message::MESSAGE_TEXT> $types - * @psalm-return array{html?: string, text?: string} + * @phpstan-param array<\Cake\Mailer\Message::MESSAGE_HTML|\Cake\Mailer\Message::MESSAGE_TEXT> $types + * @phpstan-return array{html?: string, text?: string} */ public function render(string $content, array $types = []): array { $rendered = []; $template = $this->viewBuilder()->getTemplate(); - if (empty($template)) { + if (!$template) { foreach ($types as $type) { $rendered[$type] = $content; } @@ -102,7 +102,7 @@ public function reset() $this->viewBuilder() ->setClassName(View::class) ->setLayout('default') - ->setHelpers(['Html'], false); + ->setHelpers(['Html']); return $this; } diff --git a/app/vendor/cakephp/cakephp/src/Mailer/Transport/DebugTransport.php b/app/vendor/cakephp/cakephp/src/Mailer/Transport/DebugTransport.php index 52671bd77..bfd33d459 100644 --- a/app/vendor/cakephp/cakephp/src/Mailer/Transport/DebugTransport.php +++ b/app/vendor/cakephp/cakephp/src/Mailer/Transport/DebugTransport.php @@ -33,7 +33,7 @@ class DebugTransport extends AbstractTransport public function send(Message $message): array { $headers = $message->getHeadersString( - ['from', 'sender', 'replyTo', 'readReceipt', 'returnPath', 'to', 'cc', 'subject'] + ['from', 'sender', 'replyTo', 'readReceipt', 'returnPath', 'to', 'cc', 'subject'], ); $message = implode("\r\n", $message->getBody()); diff --git a/app/vendor/cakephp/cakephp/src/Mailer/Transport/MailTransport.php b/app/vendor/cakephp/cakephp/src/Mailer/Transport/MailTransport.php index 3442bd921..ff244c16c 100644 --- a/app/vendor/cakephp/cakephp/src/Mailer/Transport/MailTransport.php +++ b/app/vendor/cakephp/cakephp/src/Mailer/Transport/MailTransport.php @@ -41,7 +41,7 @@ public function send(Message $message): array $to = $message->getHeaders(['to'])['To']; $to = str_replace("\r\n", '', $to); - $eol = $this->getConfig('eol', version_compare(PHP_VERSION, '8.0', '>=') ? "\r\n" : "\n"); + $eol = $this->getConfig('eol', "\r\n"); $headers = $message->getHeadersString( [ 'from', @@ -55,7 +55,7 @@ public function send(Message $message): array $eol, function ($val) { return str_replace("\r\n", '', $val); - } + }, ); $message = $message->getBodyString($eol); @@ -85,7 +85,7 @@ protected function _mail( string $subject, string $message, string $headers = '', - string $params = '' + string $params = '', ): void { // phpcs:disable if (!@mail($to, $subject, $message, $headers, $params)) { diff --git a/app/vendor/cakephp/cakephp/src/Mailer/Transport/SmtpTransport.php b/app/vendor/cakephp/cakephp/src/Mailer/Transport/SmtpTransport.php index 428316b47..866dbd847 100644 --- a/app/vendor/cakephp/cakephp/src/Mailer/Transport/SmtpTransport.php +++ b/app/vendor/cakephp/cakephp/src/Mailer/Transport/SmtpTransport.php @@ -22,7 +22,6 @@ use Cake\Network\Exception\SocketException; use Cake\Network\Socket; use Exception; -use RuntimeException; use function Cake\Core\env; /** @@ -45,7 +44,7 @@ class SmtpTransport extends AbstractTransport * * @var array */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'host' => 'localhost', 'port' => 25, 'timeout' => 30, @@ -60,30 +59,30 @@ class SmtpTransport extends AbstractTransport /** * Socket to SMTP server * - * @var \Cake\Network\Socket|null + * @var \Cake\Network\Socket */ - protected $_socket; + protected Socket $_socket; /** * Content of email to return * * @var array */ - protected $_content = []; + protected array $_content = []; /** * The response of the last sent SMTP command. * * @var array */ - protected $_lastResponse = []; + protected array $_lastResponse = []; /** * Authentication type. * * @var string|null */ - protected $authType = null; + protected ?string $authType = null; /** * Destructor @@ -95,7 +94,7 @@ public function __destruct() { try { $this->disconnect(); - } catch (Exception $e) { + } catch (Exception) { // avoid fatal error on script termination } } @@ -109,7 +108,7 @@ public function __destruct() */ public function __wakeup(): void { - $this->_socket = null; + unset($this->_socket); } /** @@ -135,7 +134,7 @@ public function connect(): void */ public function connected(): bool { - return $this->_socket !== null && $this->_socket->isConnected(); + return isset($this->_socket) && $this->_socket->isConnected(); } /** @@ -189,7 +188,8 @@ public function getLastResponse(): array * Send mail * * @param \Cake\Mailer\Message $message Message instance - * @return array{headers: string, message: string} + * @return array Contains 'headers' and 'message' keys. Additional keys allowed. + * @phpstan-return array{headers: string, message: string, ...} * @throws \Cake\Network\Exception\SocketException */ public function send(Message $message): array @@ -210,6 +210,7 @@ public function send(Message $message): array $this->_disconnect(); } + /** @var array{headers: string, message: string} */ return $this->_content; } @@ -244,7 +245,7 @@ protected function _parseAuthType(): void if ($authType !== null) { if (!in_array($authType, self::SUPPORTED_AUTH_TYPES)) { throw new CakeException( - 'Unsupported auth type. Available types are: ' . implode(', ', self::SUPPORTED_AUTH_TYPES) + 'Unsupported auth type. Available types are: ' . implode(', ', self::SUPPORTED_AUTH_TYPES), ); } @@ -259,7 +260,7 @@ protected function _parseAuthType(): void $auth = ''; foreach ($this->_lastResponse as $line) { - if (strlen($line['message']) === 0 || substr($line['message'], 0, 5) === 'AUTH ') { + if ($line['message'] === '' || str_starts_with($line['message'], 'AUTH ')) { $auth = $line['message']; break; } @@ -270,7 +271,7 @@ protected function _parseAuthType(): void } foreach (self::SUPPORTED_AUTH_TYPES as $type) { - if (strpos($auth, $type) !== false) { + if (str_contains($auth, $type)) { $this->authType = $type; return; @@ -289,7 +290,7 @@ protected function _parseAuthType(): void protected function _connect(): void { $this->_generateSocket(); - if (!$this->_socket()->connect()) { + if (!$this->_socket->connect()) { throw new SocketException('Unable to connect to SMTP server.'); } $this->_smtpSend(null, '220'); @@ -303,9 +304,8 @@ protected function _connect(): void } $host = $config['client']; } else { - /** @var string $httpHost */ $httpHost = env('HTTP_HOST'); - if ($httpHost) { + if (is_string($httpHost) && strlen($httpHost)) { [$host] = explode(':', $httpHost); } } @@ -314,7 +314,7 @@ protected function _connect(): void $this->_smtpSend("EHLO {$host}", '250'); if ($config['tls']) { $this->_smtpSend('STARTTLS', '220'); - $this->_socket()->enableCrypto('tls'); + $this->_socket->enableCrypto('tls'); $this->_smtpSend("EHLO {$host}", '250'); } } catch (SocketException $e) { @@ -322,7 +322,7 @@ protected function _connect(): void throw new SocketException( 'SMTP server did not accept the connection or trying to connect to non TLS SMTP server using TLS.', null, - $e + $e, ); } try { @@ -385,9 +385,9 @@ protected function _authPlain(string $username, string $password): ?string return $this->_smtpSend( sprintf( 'AUTH PLAIN %s', - base64_encode(chr(0) . $username . chr(0) . $password) + base64_encode(chr(0) . $username . chr(0) . $password), ), - '235|504|534|535' + '235|504|534|535', ); } @@ -416,7 +416,7 @@ protected function _authLogin(string $username, string $password): void throw new SocketException('SMTP authentication method not allowed, check if SMTP server requires TLS.'); } else { throw new SocketException( - 'AUTH command not recognized or not implemented, SMTP server may not require authentication.' + 'AUTH command not recognized or not implemented, SMTP server may not require authentication.', ); } } @@ -435,7 +435,7 @@ protected function _authXoauth2(string $username, string $token): void $authString = base64_encode(sprintf( "user=%s\1auth=Bearer %s\1\1", $username, - $token + $token, )); $this->_smtpSend('AUTH XOAUTH2 ' . $authString, '235'); @@ -472,8 +472,8 @@ protected function _prepareRcptCmd(string $message): string protected function _prepareFromAddress(Message $message): array { $from = $message->getReturnPath(); - if (empty($from)) { - $from = $message->getFrom(); + if (!$from) { + return $message->getFrom(); } return $from; @@ -505,7 +505,7 @@ protected function _prepareMessage(Message $message): string $lines = $message->getBody(); $messages = []; foreach ($lines as $line) { - if (!empty($line) && ($line[0] === '.')) { + if (str_starts_with($line, '.')) { $messages[] = '.' . $line; } else { $messages[] = $line; @@ -525,7 +525,7 @@ protected function _prepareMessage(Message $message): string protected function _sendRcpt(Message $message): void { $from = $this->_prepareFromAddress($message); - $this->_smtpSend($this->_prepareFromCmd(key($from))); + $this->_smtpSend($this->_prepareFromCmd((string)key($from))); $messages = $this->_prepareRecipientAddresses($message); foreach ($messages as $mail) { @@ -569,7 +569,7 @@ protected function _sendData(Message $message): void protected function _disconnect(): void { $this->_smtpSend('QUIT', false); - $this->_socket()->disconnect(); + $this->_socket->disconnect(); $this->authType = null; } @@ -592,12 +592,12 @@ protected function _generateSocket(): void * @return string|null The matched code, or null if nothing matched * @throws \Cake\Network\Exception\SocketException */ - protected function _smtpSend(?string $data, $checkCode = '250'): ?string + protected function _smtpSend(?string $data, string|false $checkCode = '250'): ?string { $this->_lastResponse = []; if ($data !== null) { - $this->_socket()->write($data . "\r\n"); + $this->_socket->write($data . "\r\n"); } $timeout = $this->_config['timeout']; @@ -605,15 +605,15 @@ protected function _smtpSend(?string $data, $checkCode = '250'): ?string while ($checkCode !== false) { $response = ''; $startTime = time(); - while (substr($response, -2) !== "\r\n" && (time() - $startTime < $timeout)) { - $bytes = $this->_socket()->read(); + while (!str_ends_with($response, "\r\n") && (time() - $startTime < $timeout)) { + $bytes = $this->_socket->read(); if ($bytes === null) { break; } $response .= $bytes; } // Catch empty or malformed responses. - if (substr($response, -2) !== "\r\n") { + if (!str_ends_with($response, "\r\n")) { // Use response message or assume operation timed out. throw new SocketException($response ?: 'SMTP timeout.'); } @@ -634,19 +634,4 @@ protected function _smtpSend(?string $data, $checkCode = '250'): ?string return null; } - - /** - * Get socket instance. - * - * @return \Cake\Network\Socket - * @throws \RuntimeException If socket is not set. - */ - protected function _socket(): Socket - { - if ($this->_socket === null) { - throw new RuntimeException('Socket is null, but must be set.'); - } - - return $this->_socket; - } } diff --git a/app/vendor/cakephp/cakephp/src/Mailer/TransportFactory.php b/app/vendor/cakephp/cakephp/src/Mailer/TransportFactory.php index e59e9f13f..8bec1c39d 100644 --- a/app/vendor/cakephp/cakephp/src/Mailer/TransportFactory.php +++ b/app/vendor/cakephp/cakephp/src/Mailer/TransportFactory.php @@ -29,17 +29,17 @@ class TransportFactory /** * Transport Registry used for creating and using transport instances. * - * @var \Cake\Mailer\TransportRegistry|null + * @var \Cake\Mailer\TransportRegistry */ - protected static $_registry; + protected static TransportRegistry $_registry; /** * An array mapping url schemes to fully qualified Transport class names * * @var array - * @psalm-var array + * @phpstan-var array */ - protected static $_dsnClassMap = [ + protected static array $_dsnClassMap = [ 'debug' => Transport\DebugTransport::class, 'mail' => Transport\MailTransport::class, 'smtp' => Transport\SmtpTransport::class, @@ -52,11 +52,7 @@ class TransportFactory */ public static function getRegistry(): TransportRegistry { - if (static::$_registry === null) { - static::$_registry = new TransportRegistry(); - } - - return static::$_registry; + return static::$_registry ??= new TransportRegistry(); } /** @@ -73,9 +69,9 @@ public static function setRegistry(TransportRegistry $registry): void } /** - * Finds and builds the instance of the required tranport class. + * Finds and builds the instance of the required transport class. * - * @param string $name Name of the config array that needs a tranport instance built + * @param string $name Name of the config array that needs a transport instance built * @return void * @throws \InvalidArgumentException When a tranport cannot be created. */ @@ -83,17 +79,16 @@ protected static function _buildTransport(string $name): void { if (!isset(static::$_config[$name])) { throw new InvalidArgumentException( - sprintf('The "%s" transport configuration does not exist', $name) + sprintf('The `%s` transport configuration does not exist', $name), ); } if (is_array(static::$_config[$name]) && empty(static::$_config[$name]['className'])) { throw new InvalidArgumentException( - sprintf('Transport config "%s" is invalid, the required `className` option is missing', $name) + sprintf('Transport config `%s` is invalid, the required `className` option is missing', $name), ); } - /** @phpstan-ignore-next-line */ static::getRegistry()->load($name, static::$_config[$name]); } diff --git a/app/vendor/cakephp/cakephp/src/Mailer/TransportRegistry.php b/app/vendor/cakephp/cakephp/src/Mailer/TransportRegistry.php index e6477115c..00f7c2826 100644 --- a/app/vendor/cakephp/cakephp/src/Mailer/TransportRegistry.php +++ b/app/vendor/cakephp/cakephp/src/Mailer/TransportRegistry.php @@ -19,7 +19,6 @@ use BadMethodCallException; use Cake\Core\App; use Cake\Core\ObjectRegistry; -use RuntimeException; /** * An object registry for mailer transports. @@ -29,16 +28,16 @@ class TransportRegistry extends ObjectRegistry { /** - * Resolve a mailer tranport classname. + * Resolve a mailer transport classname. * * Part of the template method for Cake\Core\ObjectRegistry::load() * * @param string $class Partial classname to resolve or transport instance. - * @return string|null Either the correct classname or null. - * @psalm-return class-string|null + * @return class-string<\Cake\Mailer\AbstractTransport>|null Either the correct classname or null. */ protected function _resolveClassName(string $class): ?string { + /** @var class-string<\Cake\Mailer\AbstractTransport>|null */ return App::className($class, 'Mailer/Transport', 'Transport'); } @@ -54,7 +53,7 @@ protected function _resolveClassName(string $class): ?string */ protected function _throwMissingClassError(string $class, ?string $plugin): void { - throw new BadMethodCallException(sprintf('Mailer transport %s is not available.', $class)); + throw new BadMethodCallException(sprintf('Mailer transport `%s` is not available.', $class)); } /** @@ -62,27 +61,18 @@ protected function _throwMissingClassError(string $class, ?string $plugin): void * * Part of the template method for Cake\Core\ObjectRegistry::load() * - * @param \Cake\Mailer\AbstractTransport|string $class The classname or object to make. + * @param \Cake\Mailer\AbstractTransport|class-string<\Cake\Mailer\AbstractTransport> $class The classname or object to make. * @param string $alias The alias of the object. * @param array $config An array of settings to use for the cache engine. * @return \Cake\Mailer\AbstractTransport The constructed transport class. - * @throws \RuntimeException when an object doesn't implement the correct interface. */ - protected function _create($class, string $alias, array $config): AbstractTransport + protected function _create(object|string $class, string $alias, array $config): AbstractTransport { if (is_object($class)) { - $instance = $class; - } else { - $instance = new $class($config); + return $class; } - if ($instance instanceof AbstractTransport) { - return $instance; - } - - throw new RuntimeException( - 'Mailer transports must use Cake\Mailer\AbstractTransport as a base class.' - ); + return new $class($config); } /** diff --git a/app/vendor/cakephp/cakephp/src/Network/Socket.php b/app/vendor/cakephp/cakephp/src/Network/Socket.php index bfcdbc14f..d29a03270 100644 --- a/app/vendor/cakephp/cakephp/src/Network/Socket.php +++ b/app/vendor/cakephp/cakephp/src/Network/Socket.php @@ -23,7 +23,6 @@ use Composer\CaBundle\CaBundle; use Exception; use InvalidArgumentException; -use function Cake\Core\deprecationWarning; /** * CakePHP network socket connection class. @@ -39,7 +38,7 @@ class Socket * * @var array */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'persistent' => false, 'host' => 'localhost', 'protocol' => 'tcp', @@ -59,28 +58,28 @@ class Socket * * @var bool */ - protected $connected = false; + protected bool $connected = false; /** * This variable contains an array with the last error number (num) and string (str) * * @var array */ - protected $lastError = []; + protected array $lastError = []; /** * True if the socket stream is encrypted after a {@link \Cake\Network\Socket::enableCrypto()} call * * @var bool */ - protected $encrypted = false; + protected bool $encrypted = false; /** * Contains all the encryption methods available * * @var array */ - protected $_encryptMethods = [ + protected array $_encryptMethods = [ 'sslv23_client' => STREAM_CRYPTO_METHOD_SSLv23_CLIENT, 'tls_client' => STREAM_CRYPTO_METHOD_TLS_CLIENT, 'tlsv10_client' => STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT, @@ -99,7 +98,7 @@ class Socket * * @var array */ - protected $_connectionErrors = []; + protected array $_connectionErrors = []; /** * Constructor. @@ -124,8 +123,7 @@ public function connect(): bool $this->disconnect(); } - $hasProtocol = strpos($this->_config['host'], '://') !== false; - if ($hasProtocol) { + if (str_contains($this->_config['host'], '://')) { [$this->_config['protocol'], $this->_config['host']] = explode('://', $this->_config['host']); } $scheme = null; @@ -145,8 +143,10 @@ public function connect(): bool $connectAs |= STREAM_CLIENT_PERSISTENT; } - /** @psalm-suppress InvalidArgument */ - set_error_handler([$this, '_connectionErrorHandler']); + /** + * @phpstan-ignore-next-line + */ + set_error_handler($this->_connectionErrorHandler(...)); $remoteSocketTarget = $scheme . $this->_config['host']; $port = (int)$this->_config['port']; if ($port > 0) { @@ -161,7 +161,7 @@ public function connect(): bool $errStr, (int)$this->_config['timeout'], $connectAs, - $context + $context, ); restore_error_handler(); @@ -177,7 +177,8 @@ public function connect(): bool $this->connected = is_resource($this->connection); if ($this->connected) { - /** @psalm-suppress PossiblyNullArgument */ + assert($this->connection !== null); + stream_set_timeout($this->connection, (int)$this->_config['timeout']); } @@ -201,26 +202,32 @@ public function isConnected(): bool * @param int $errNum error number * @param string $errStr error string * @param int $timeout timeout - * @param int $connectAs flags + * @param int<0, 7> $connectAs flags * @param resource $context context * @return resource|null */ - protected function _getStreamSocketClient($remoteSocketTarget, &$errNum, &$errStr, $timeout, $connectAs, $context) - { + protected function _getStreamSocketClient( + string $remoteSocketTarget, + int &$errNum, + string &$errStr, + int $timeout, + int $connectAs, + $context, + ) { $resource = stream_socket_client( $remoteSocketTarget, $errNum, $errStr, $timeout, $connectAs, - $context + $context, ); - if ($resource) { - return $resource; + if (!$resource) { + return null; } - return null; + return $resource; } /** @@ -232,7 +239,7 @@ protected function _getStreamSocketClient($remoteSocketTarget, &$errNum, &$errSt protected function _setSslContext(string $host): void { foreach ($this->_config as $key => $value) { - if (substr($key, 0, 4) !== 'ssl_') { + if (!str_starts_with($key, 'ssl_')) { continue; } $contextKey = substr($key, 4); @@ -260,7 +267,7 @@ protected function _setSslContext(string $host): void * socket_stream_client() does not populate errNum, or $errStr when there are * connection errors, as in the case of SSL verification failure. * - * Instead we need to handle those errors manually. + * Instead, we need to handle those errors manually. * * @param int $code Code number. * @param string $message Message. @@ -274,7 +281,7 @@ protected function _connectionErrorHandler(int $code, string $message): void /** * Get the connection context. * - * @return array|null Null when there is no connection, an array when there is. + * @return array|null Null when there is no connection, an array when there is. */ public function context(): ?array { @@ -293,10 +300,10 @@ public function context(): ?array public function host(): string { if (Validation::ip($this->_config['host'])) { - return gethostbyaddr($this->_config['host']); + return (string)gethostbyaddr($this->_config['host']); } - return gethostbyaddr($this->address()); + return (string)gethostbyaddr($this->address()); } /** @@ -316,7 +323,7 @@ public function address(): string /** * Get all IP addresses associated with the current connection. * - * @return array IP addresses + * @return array IP addresses */ public function addresses(): array { @@ -324,7 +331,7 @@ public function addresses(): array return [$this->_config['host']]; } - return gethostbynamel($this->_config['host']); + return gethostbynamel($this->_config['host']) ?: []; } /** @@ -334,11 +341,11 @@ public function addresses(): array */ public function lastError(): ?string { - if (!empty($this->lastError)) { - return $this->lastError['num'] . ': ' . $this->lastError['str']; + if (!$this->lastError) { + return null; } - return null; + return $this->lastError['num'] . ': ' . $this->lastError['str']; } /** @@ -367,7 +374,8 @@ public function write(string $data): int $totalBytes = strlen($data); $written = 0; while ($written < $totalBytes) { - /** @psalm-suppress PossiblyNullArgument */ + assert($this->connection !== null); + $rv = fwrite($this->connection, substr($data, $written)); if ($rv === false || $rv === 0) { return $written; @@ -387,24 +395,28 @@ public function write(string $data): int */ public function read(int $length = 1024): ?string { + if ($length < 1) { + throw new InvalidArgumentException('Length must be greater than `0`'); + } + if (!$this->connected && !$this->connect()) { return null; } - /** @psalm-suppress PossiblyNullArgument */ - if (!feof($this->connection)) { - $buffer = fread($this->connection, $length); - $info = stream_get_meta_data($this->connection); - if ($info['timed_out']) { - $this->setLastError(E_WARNING, 'Connection timed out'); + assert($this->connection !== null); + if (feof($this->connection)) { + return null; + } - return null; - } + $buffer = fread($this->connection, $length); + $info = stream_get_meta_data($this->connection); + if ($info['timed_out']) { + $this->setLastError(E_WARNING, 'Connection timed out'); - return $buffer; + return null; } - return null; + return $buffer === false ? null : $buffer; } /** @@ -419,7 +431,6 @@ public function disconnect(): bool return true; } - /** @psalm-suppress InvalidPropertyAssignmentValue */ $this->connected = !fclose($this->connection); if (!$this->connected) { @@ -445,9 +456,9 @@ public function __destruct() */ public function reset(?array $state = null): void { - if (empty($state)) { + if (!$state) { static $initialState = []; - if (empty($initialState)) { + if (!$initialState) { $initialState = get_class_vars(self::class); } $state = $initialState; @@ -513,57 +524,4 @@ public function isEncrypted(): bool { return $this->encrypted; } - - /** - * Temporary magic method to allow accessing protected properties. - * - * Will be removed in 5.0. - * - * @param string $name Property name. - * @return mixed - */ - public function __get($name) - { - switch ($name) { - case 'connected': - deprecationWarning('The property `$connected` is deprecated, use `isConnected()` instead.'); - - return $this->connected; - - case 'encrypted': - deprecationWarning('The property `$encrypted` is deprecated, use `isEncrypted()` instead.'); - - return $this->encrypted; - - case 'lastError': - deprecationWarning('The property `$lastError` is deprecated, use `lastError()` instead.'); - - return $this->lastError; - - case 'connection': - deprecationWarning('The property `$connection` is deprecated.'); - - return $this->connection; - - case 'description': - deprecationWarning('The CakePHP team would love to know your use case for this property.'); - - return 'Remote DataSource Network Socket Interface'; - } - - $trace = debug_backtrace(); - $parts = explode('\\', static::class); - trigger_error( - sprintf( - 'Undefined property: %s::$%s in %s on line %s', - array_pop($parts), - $name, - $trace[0]['file'], - $trace[0]['line'] - ), - E_USER_NOTICE - ); - - return null; - } } diff --git a/app/vendor/cakephp/cakephp/src/ORM/Association.php b/app/vendor/cakephp/cakephp/src/ORM/Association.php index 8601283b7..bc0f9bb9b 100644 --- a/app/vendor/cakephp/cakephp/src/ORM/Association.php +++ b/app/vendor/cakephp/cakephp/src/ORM/Association.php @@ -16,20 +16,20 @@ */ namespace Cake\ORM; -use Cake\Collection\Collection; use Cake\Collection\CollectionInterface; use Cake\Core\App; use Cake\Core\ConventionsTrait; +use Cake\Database\Exception\DatabaseException; use Cake\Database\Expression\IdentifierExpression; +use Cake\Database\Expression\QueryExpression; +use Cake\Database\ExpressionInterface; use Cake\Datasource\EntityInterface; -use Cake\Datasource\ResultSetDecorator; use Cake\Datasource\ResultSetInterface; use Cake\ORM\Locator\LocatorAwareTrait; +use Cake\ORM\Query\SelectQuery; use Cake\Utility\Inflector; use Closure; use InvalidArgumentException; -use RuntimeException; -use function Cake\Core\deprecationWarning; use function Cake\Core\pluginSplit; /** @@ -98,28 +98,28 @@ abstract class Association * * @var string */ - protected $_name; + protected string $_name; /** * The class name of the target table object * * @var string */ - protected $_className; + protected string $_className; /** * The field name in the owning side table that is used to match with the foreignKey * - * @var array|string|null + * @var array|string */ - protected $_bindingKey; + protected array|string $_bindingKey; /** * The name of the field representing the foreign key to the table to load * - * @var array|string + * @var array|string|false */ - protected $_foreignKey; + protected array|string|false $_foreignKey; /** * A list of conditions to be always included when fetching records from @@ -127,7 +127,7 @@ abstract class Association * * @var \Closure|array */ - protected $_conditions = []; + protected Closure|array $_conditions = []; /** * Whether the records on the target table are dependent on the source table, @@ -136,35 +136,35 @@ abstract class Association * * @var bool */ - protected $_dependent = false; + protected bool $_dependent = false; /** * Whether cascaded deletes should also fire callbacks. * * @var bool */ - protected $_cascadeCallbacks = false; + protected bool $_cascadeCallbacks = false; /** * Source table instance * * @var \Cake\ORM\Table */ - protected $_sourceTable; + protected Table $_sourceTable; /** * Target table instance * * @var \Cake\ORM\Table */ - protected $_targetTable; + protected Table $_targetTable; /** * The type of join to be used when adding the association to a query * * @var string */ - protected $_joinType = Query::JOIN_TYPE_LEFT; + protected string $_joinType = SelectQuery::JOIN_TYPE_LEFT; /** * The property name that should be filled with data from the target table @@ -172,7 +172,7 @@ abstract class Association * * @var string */ - protected $_propertyName; + protected string $_propertyName; /** * The strategy name to be used to fetch associated records. Some association @@ -180,7 +180,7 @@ abstract class Association * * @var string */ - protected $_strategy = self::STRATEGY_JOIN; + protected string $_strategy = self::STRATEGY_JOIN; /** * The default finder name to use for fetching rows from the target table @@ -188,14 +188,14 @@ abstract class Association * * @var array|string */ - protected $_finder = 'all'; + protected array|string $_finder = 'all'; /** * Valid strategies for this association. Subclasses can narrow this down. * * @var array */ - protected $_validStrategies = [ + protected array $_validStrategies = [ self::STRATEGY_JOIN, self::STRATEGY_SELECT, self::STRATEGY_SUBQUERY, @@ -230,9 +230,7 @@ public function __construct(string $alias, array $options = []) } } - if (empty($this->_className)) { - $this->_className = $alias; - } + $this->_className ??= $alias; [, $name] = pluginSplit($alias); $this->_name = $name; @@ -244,38 +242,6 @@ public function __construct(string $alias, array $options = []) } } - /** - * Sets the name for this association, usually the alias - * assigned to the target associated table - * - * @param string $name Name to be assigned - * @return $this - * @deprecated 4.3.0 Changing the association name after object creation is - * no longer supported. The name should only be set through the constructor. - */ - public function setName(string $name) - { - deprecationWarning( - 'Changing the association name after object creation is no longer supported.' - . ' The name should only be set through the constructor' - ); - - if ($this->_targetTable !== null) { - $alias = $this->_targetTable->getAlias(); - if ($alias !== $name) { - throw new InvalidArgumentException(sprintf( - 'Association name "%s" does not match target table alias "%s".', - $name, - $alias - )); - } - } - - $this->_name = $name; - - return $this; - } - /** * Gets the name for this association, usually the alias * assigned to the target associated table @@ -321,13 +287,13 @@ public function getCascadeCallbacks(): bool public function setClassName(string $className) { if ( - $this->_targetTable !== null && + isset($this->_targetTable) && get_class($this->_targetTable) !== App::className($className, 'Model/Table', 'Table') ) { throw new InvalidArgumentException(sprintf( - 'The class name "%s" doesn\'t match the target table class name of "%s".', + "The class name `%s` doesn't match the target table class name of `%s`.", $className, - get_class($this->_targetTable) + $this->_targetTable::class, )); } @@ -389,10 +355,10 @@ public function setTarget(Table $table) */ public function getTarget(): Table { - if ($this->_targetTable === null) { - if (strpos($this->_className, '.')) { + if (!isset($this->_targetTable)) { + if (str_contains($this->_className, '.')) { [$plugin] = pluginSplit($this->_className, true); - $registryAlias = (string)$plugin . $this->_name; + $registryAlias = $plugin . $this->_name; } else { $registryAlias = $this->_name; } @@ -410,17 +376,17 @@ public function getTarget(): Table $className = App::className($this->_className, 'Model/Table', 'Table') ?: Table::class; if (!$this->_targetTable instanceof $className) { - $errorMessage = '%s association "%s" of type "%s" to "%s" doesn\'t match the expected class "%s". '; - $errorMessage .= 'You can\'t have an association of the same name with a different target '; - $errorMessage .= '"className" option anywhere in your app.'; + $msg = "`%s` association `%s` of type `%s` to `%s` doesn't match the expected class `%s`. "; + $msg .= "You can't have an association of the same name with a different target "; + $msg .= '"className" option anywhere in your app.'; - throw new RuntimeException(sprintf( - $errorMessage, - $this->_sourceTable === null ? 'null' : get_class($this->_sourceTable), + throw new DatabaseException(sprintf( + $msg, + isset($this->_sourceTable) ? $this->_sourceTable::class : 'null', $this->getName(), $this->type(), - get_class($this->_targetTable), - $className + $this->_targetTable::class, + $className, )); } } @@ -437,7 +403,7 @@ public function getTarget(): Table * @see \Cake\Database\Query::where() for examples on the format of the array * @return $this */ - public function setConditions($conditions) + public function setConditions(Closure|array $conditions) { $this->_conditions = $conditions; @@ -451,7 +417,7 @@ public function setConditions($conditions) * @see \Cake\Database\Query::where() for examples on the format of the array * @return \Closure|array */ - public function getConditions() + public function getConditions(): Closure|array { return $this->_conditions; } @@ -463,7 +429,7 @@ public function getConditions() * @param array|string $key the table field or fields to be used to link both tables together * @return $this */ - public function setBindingKey($key) + public function setBindingKey(array|string $key) { $this->_bindingKey = $key; @@ -476,9 +442,9 @@ public function setBindingKey($key) * * @return array|string */ - public function getBindingKey() + public function getBindingKey(): array|string { - if ($this->_bindingKey === null) { + if (!isset($this->_bindingKey)) { $this->_bindingKey = $this->isOwningSide($this->getSource()) ? $this->getSource()->getPrimaryKey() : $this->getTarget()->getPrimaryKey(); @@ -490,9 +456,9 @@ public function getBindingKey() /** * Gets the name of the field representing the foreign key to the target table. * - * @return array|string + * @return array|string|false */ - public function getForeignKey() + public function getForeignKey(): array|string|false { return $this->_foreignKey; } @@ -503,7 +469,7 @@ public function getForeignKey() * @param array|string $key the key or keys to be used to link both tables together * @return $this */ - public function setForeignKey($key) + public function setForeignKey(array|string $key) { $this->_foreignKey = $key; @@ -599,14 +565,14 @@ public function setProperty(string $name) */ public function getProperty(): string { - if (!$this->_propertyName) { + if (!isset($this->_propertyName)) { $this->_propertyName = $this->_propertyName(); if (in_array($this->_propertyName, $this->_sourceTable->getSchema()->columns(), true)) { - $msg = 'Association property name "%s" clashes with field of same name of table "%s".' . - ' You should explicitly specify the "propertyName" option.'; + $msg = 'Association property name `%s` clashes with field of same name of table `%s`.' . + ' You should explicitly specify the `propertyName` option.'; trigger_error( sprintf($msg, $this->_propertyName, $this->_sourceTable->getTable()), - E_USER_WARNING + E_USER_WARNING, ); } } @@ -639,9 +605,9 @@ public function setStrategy(string $name) { if (!in_array($name, $this->_validStrategies, true)) { throw new InvalidArgumentException(sprintf( - 'Invalid strategy "%s" was provided. Valid options are (%s).', + 'Invalid strategy `%s` was provided. Valid options are `(%s)`.', $name, - implode(', ', $this->_validStrategies) + implode(', ', $this->_validStrategies), )); } $this->_strategy = $name; @@ -666,7 +632,7 @@ public function getStrategy(): string * * @return array|string */ - public function getFinder() + public function getFinder(): array|string { return $this->_finder; } @@ -677,7 +643,7 @@ public function getFinder() * @param array|string $finder the finder name to use or array of finder name and option. * @return $this */ - public function setFinder($finder) + public function setFinder(array|string $finder) { $this->_finder = $finder; @@ -699,7 +665,7 @@ protected function _options(array $options): void * Alters a Query object to include the associated target table data in the final * result * - * The options array accept the following keys: + * The options array accepts the following keys: * * - includeFields: Whether to include target model fields in the result or not * - foreignKey: The name of the field to use as foreign key, if false none @@ -716,12 +682,12 @@ protected function _options(array $options): void * - negateMatch: Will append a condition to the passed query for excluding matches. * with this association. * - * @param \Cake\ORM\Query $query the query to be altered to include the target table data - * @param array $options Any extra options or overrides to be taken in account + * @param \Cake\ORM\Query\SelectQuery $query the query to be altered to include the target table data + * @param array $options Any extra options or overrides to be taken into account * @return void * @throws \RuntimeException Unable to build the query or associations. */ - public function attachTo(Query $query, array $options = []): void + public function attachTo(SelectQuery $query, array $options = []): void { $target = $this->getTarget(); $table = $target->getTable(); @@ -742,7 +708,7 @@ public function attachTo(Query $query, array $options = []): void $options['includeFields'] = false; } - if (!empty($options['foreignKey'])) { + if ($options['foreignKey']) { $joinCondition = $this->_joinCondition($options); if ($joinCondition) { $options['conditions'][] = $joinCondition; @@ -751,15 +717,15 @@ public function attachTo(Query $query, array $options = []): void [$finder, $opts] = $this->_extractFinder($options['finder']); $dummy = $this - ->find($finder, $opts) + ->find($finder, ...$opts) ->eagerLoaded(true); if (!empty($options['queryBuilder'])) { $dummy = $options['queryBuilder']($dummy); - if (!($dummy instanceof Query)) { - throw new RuntimeException(sprintf( - 'Query builder for association "%s" did not return a query', - $this->getName() + if (!($dummy instanceof SelectQuery)) { + throw new DatabaseException(sprintf( + 'Query builder for association `%s` did not return a query.', + $this->getName(), )); } } @@ -769,9 +735,10 @@ public function attachTo(Query $query, array $options = []): void $this->_strategy === static::STRATEGY_JOIN && $dummy->getContain() ) { - throw new RuntimeException( - "`{$this->getName()}` association cannot contain() associations when using JOIN strategy." - ); + throw new DatabaseException(sprintf( + '`%s` association cannot contain() associations when using JOIN strategy.', + $this->getName(), + )); } $dummy->where($options['conditions']); @@ -793,17 +760,19 @@ public function attachTo(Query $query, array $options = []): void * Conditionally adds a condition to the passed Query that will make it find * records where there is no match with this association. * - * @param \Cake\ORM\Query $query The query to modify + * @param \Cake\ORM\Query\SelectQuery $query The query to modify * @param array $options Options array containing the `negateMatch` key. * @return void */ - protected function _appendNotMatching(Query $query, array $options): void + protected function _appendNotMatching(SelectQuery $query, array $options): void { - $target = $this->_targetTable; + $target = $this->getTarget(); if (!empty($options['negateMatch'])) { $primaryKey = $query->aliasFields((array)$target->getPrimaryKey(), $this->_name); $query->andWhere(function ($exp) use ($primaryKey) { - array_map([$exp, 'isNull'], $primaryKey); + /** @var callable $callable */ + $callable = [$exp, 'isNull']; + array_map($callable, $primaryKey); return $exp; }); @@ -814,13 +783,13 @@ protected function _appendNotMatching(Query $query, array $options): void * Correctly nests a result row associated values into the correct array keys inside the * source results. * - * @param array $row The row to transform + * @param array $row The row to transform * @param string $nestKey The array key under which the results for this association * should be found * @param bool $joined Whether the row is a result of a direct join * with this association * @param string|null $targetProperty The property name in the source results where the association - * data shuld be nested in. Will use the default one if not provided. + * data should be nested in. Will use the default one if not provided. * @return array */ public function transformRow(array $row, string $nestKey, bool $joined, ?string $targetProperty = null): array @@ -861,19 +830,21 @@ public function defaultRowValue(array $row, bool $joined): array * and modifies the query accordingly based of this association * configuration * - * @param array|string|null $type the type of query to perform, if an array is passed, - * it will be interpreted as the `$options` parameter - * @param array $options The options to for the find + * @param array|string|null $type the type of query to perform if an array is passed, + * it will be interpreted as the `$args` parameter + * @param mixed ...$args Arguments that match up to finder-specific parameters * @see \Cake\ORM\Table::find() - * @return \Cake\ORM\Query + * @return \Cake\ORM\Query\SelectQuery */ - public function find($type = null, array $options = []): Query + public function find(array|string|null $type = null, mixed ...$args): SelectQuery { $type = $type ?: $this->getFinder(); [$type, $opts] = $this->_extractFinder($type); + $args += $opts; + return $this->getTarget() - ->find($type, $options + $opts) + ->find($type, ...$args) ->where($this->getConditions()); } @@ -886,7 +857,7 @@ public function find($type = null, array $options = []): Query * @see \Cake\ORM\Table::exists() * @return bool */ - public function exists($conditions): bool + public function exists(ExpressionInterface|Closure|array|string|null $conditions): bool { $conditions = $this->find() ->where($conditions) @@ -896,16 +867,17 @@ public function exists($conditions): bool } /** - * Proxies the update operation to the target table's updateAll method + * Proxies the update operation to the target `Table::updateAll()` method * - * @param array $fields A hash of field => new value. - * @param \Cake\Database\ExpressionInterface|\Closure|array|string|null $conditions Conditions to be used, accepts anything Query::where() - * can take. - * @see \Cake\ORM\Table::updateAll() + * @param \Cake\Database\Expression\QueryExpression|\Closure|array|string $fields A hash of field => new value. + * @param \Cake\Database\Expression\QueryExpression|\Closure|array|string|null $conditions Conditions to be used, accepts anything Query::where() * @return int Count Returns the affected rows. + * @see \Cake\ORM\Table::updateAll() */ - public function updateAll(array $fields, $conditions): int - { + public function updateAll( + QueryExpression|Closure|array|string $fields, + QueryExpression|Closure|array|string|null $conditions, + ): int { $expression = $this->find() ->where($conditions) ->clause('where'); @@ -914,14 +886,14 @@ public function updateAll(array $fields, $conditions): int } /** - * Proxies the delete operation to the target table's deleteAll method + * Proxies the delete operation to the target `Table::deleteAll()` method * - * @param \Cake\Database\ExpressionInterface|\Closure|array|string|null $conditions Conditions to be used, accepts anything Query::where() + * @param \Cake\Database\Expression\QueryExpression|\Closure|array|string|null $conditions Conditions to be used, accepts anything Query::where() * can take. * @return int Returns the number of affected rows. * @see \Cake\ORM\Table::deleteAll() */ - public function deleteAll($conditions): int + public function deleteAll(QueryExpression|Closure|array|string|null $conditions): int { $expression = $this->find() ->where($conditions) @@ -945,13 +917,13 @@ public function requiresKeys(array $options = []): bool } /** - * Triggers beforeFind on the target table for the query this association is + * Triggers `beforeFind` on the target table for the query this association is * attaching to * - * @param \Cake\ORM\Query $query the query this association is attaching itself to + * @param \Cake\ORM\Query\SelectQuery $query the query this association is attaching itself to * @return void */ - protected function _dispatchBeforeFind(Query $query): void + protected function _dispatchBeforeFind(SelectQuery $query): void { $query->triggerBeforeFind(); } @@ -960,12 +932,12 @@ protected function _dispatchBeforeFind(Query $query): void * Helper function used to conditionally append fields to the select clause of * a query from the fields found in another query object. * - * @param \Cake\ORM\Query $query the query that will get the fields appended to - * @param \Cake\ORM\Query $surrogate the query having the fields to be copied from + * @param \Cake\ORM\Query\SelectQuery $query the query that will get the fields appended to + * @param \Cake\ORM\Query\SelectQuery $surrogate the query having the fields to be copied from * @param array $options options passed to the method `attachTo` * @return void */ - protected function _appendFields(Query $query, Query $surrogate, array $options): void + protected function _appendFields(SelectQuery $query, SelectQuery $surrogate, array $options): void { if ($query->getEagerLoader()->isAutoFieldsEnabled() === false) { return; @@ -974,30 +946,30 @@ protected function _appendFields(Query $query, Query $surrogate, array $options) $fields = array_merge($surrogate->clause('select'), $options['fields']); if ( - (empty($fields) && $options['includeFields']) || + ($fields === [] && $options['includeFields']) || $surrogate->isAutoFieldsEnabled() ) { - $fields = array_merge($fields, $this->_targetTable->getSchema()->columns()); + $fields = array_merge($fields, $this->getTarget()->getSchema()->columns()); } $query->select($query->aliasFields($fields, $this->_name)); - $query->addDefaultTypes($this->_targetTable); + $query->addDefaultTypes($this->getTarget()); } /** * Adds a formatter function to the passed `$query` if the `$surrogate` query - * declares any other formatter. Since the `$surrogate` query correspond to + * declares any other formatter. Since the `$surrogate` query corresponds to * the associated target table, the resulting formatter will be the result of * applying the surrogate formatters to only the property corresponding to - * such table. + * such a table. * - * @param \Cake\ORM\Query $query the query that will get the formatter applied to - * @param \Cake\ORM\Query $surrogate the query having formatters for the associated + * @param \Cake\ORM\Query\SelectQuery $query the query that will get the formatter applied to + * @param \Cake\ORM\Query\SelectQuery $surrogate the query having formatters for the associated * target table. * @param array $options options passed to the method `attachTo` * @return void */ - protected function _formatAssociationResults(Query $query, Query $surrogate, array $options): void + protected function _formatAssociationResults(SelectQuery $query, SelectQuery $surrogate, array $options): void { $formatters = $surrogate->getResultFormatters(); @@ -1008,7 +980,7 @@ protected function _formatAssociationResults(Query $query, Query $surrogate, arr $property = $options['propertyPath']; $propertyPath = explode('.', $property); $query->formatResults( - function (CollectionInterface $results, $query) use ($formatters, $property, $propertyPath) { + function (CollectionInterface $results, SelectQuery $query) use ($formatters, $property, $propertyPath) { $extracted = []; foreach ($results as $result) { foreach ($propertyPath as $propertyPathItem) { @@ -1020,17 +992,18 @@ function (CollectionInterface $results, $query) use ($formatters, $property, $pr } $extracted[] = $result; } - $extracted = new Collection($extracted); + $extracted = $query->resultSetFactory()->createResultSet($extracted); + $resultSetClass = $query->resultSetFactory()->getResultSetClass(); foreach ($formatters as $callable) { $extracted = $callable($extracted, $query); if (!$extracted instanceof ResultSetInterface) { - $extracted = new ResultSetDecorator($extracted); + $extracted = new $resultSetClass($extracted); } } $results = $results->insert($property, $extracted); if ($query->isHydrationEnabled()) { - $results = $results->map(function ($result) { + return $results->map(function (EntityInterface $result) { $result->clean(); return $result; @@ -1039,7 +1012,7 @@ function (CollectionInterface $results, $query) use ($formatters, $property, $pr return $results; }, - Query::PREPEND + SelectQuery::PREPEND, ); } @@ -1048,15 +1021,15 @@ function (CollectionInterface $results, $query) use ($formatters, $property, $pr * in the `$surrogate` query. * * Copies all contained associations from the `$surrogate` query into the - * passed `$query`. Containments are altered so that they respect the associations + * passed `$query`. Containments are altered so that they respect the association * chain from which they originated. * - * @param \Cake\ORM\Query $query the query that will get the associations attached to - * @param \Cake\ORM\Query $surrogate the query having the containments to be attached + * @param \Cake\ORM\Query\SelectQuery $query the query that will get the associations attached to + * @param \Cake\ORM\Query\SelectQuery $surrogate the query having the containments to be attached * @param array $options options passed to the method `attachTo` * @return void */ - protected function _bindNewAssociations(Query $query, Query $surrogate, array $options): void + protected function _bindNewAssociations(SelectQuery $query, SelectQuery $surrogate, array $options): void { $loader = $surrogate->getEagerLoader(); $contain = $loader->getContain(); @@ -1080,7 +1053,7 @@ protected function _bindNewAssociations(Query $query, Query $surrogate, array $o $eagerLoader->setMatching( $options['aliasPath'] . '.' . $alias, $value['queryBuilder'], - $value + $value, ); } } @@ -1091,7 +1064,7 @@ protected function _bindNewAssociations(Query $query, Query $surrogate, array $o * * @param array $options list of options passed to attachTo method * @return array - * @throws \RuntimeException if the number of columns in the foreignKey do not + * @throws \Cake\Database\Exception\DatabaseException if the number of columns in the foreignKey do not * match the number of columns in the source table primaryKey */ protected function _joinCondition(array $options): array @@ -1103,21 +1076,21 @@ protected function _joinCondition(array $options): array $bindingKey = (array)$this->getBindingKey(); if (count($foreignKey) !== count($bindingKey)) { - if (empty($bindingKey)) { + if (!$bindingKey) { $table = $this->getTarget()->getTable(); if ($this->isOwningSide($this->getSource())) { $table = $this->getSource()->getTable(); } - $msg = 'The "%s" table does not define a primary key, and cannot have join conditions generated.'; - throw new RuntimeException(sprintf($msg, $table)); + $msg = 'The `%s` table does not define a primary key, and cannot have join conditions generated.'; + throw new DatabaseException(sprintf($msg, $table)); } - $msg = 'Cannot match provided foreignKey for "%s", got "(%s)" but expected foreign key for "(%s)"'; - throw new RuntimeException(sprintf( + $msg = 'Cannot match provided foreignKey for `%s`, got `(%s)` but expected foreign key for `(%s)`'; + throw new DatabaseException(sprintf( $msg, $this->_name, implode(', ', $foreignKey), - implode(', ', $bindingKey) + implode(', ', $bindingKey), )); } @@ -1146,7 +1119,7 @@ protected function _joinCondition(array $options): array * and options as value. * @return array */ - protected function _extractFinder($finderData): array + protected function _extractFinder(array|string $finderData): array { $finderData = (array)$finderData; @@ -1162,10 +1135,10 @@ protected function _extractFinder($finderData): array * association's associations * * @param string $property the property name - * @return \Cake\ORM\Association - * @throws \RuntimeException if no association with such name exists + * @return self + * @throws \RuntimeException if no association with such a name exists */ - public function __get($property) + public function __get(string $property): Association { return $this->getTarget()->{$property}; } @@ -1177,7 +1150,7 @@ public function __get($property) * @param string $property the property name * @return bool true if the property exists */ - public function __isset($property) + public function __isset(string $property): bool { return isset($this->getTarget()->{$property}); } @@ -1190,7 +1163,7 @@ public function __isset($property) * @return mixed * @throws \BadMethodCallException */ - public function __call($method, $argument) + public function __call(string $method, array $argument): mixed { return $this->getTarget()->$method(...$argument); } @@ -1219,7 +1192,7 @@ abstract public function type(): string; * * Options array accepts the following keys: * - * - query: Query object setup to find the source table records + * - query: SelectQuery object setup to find the source table records * - keys: List of primary key values from the source table * - foreignKey: The name of the field used to relate both tables * - conditions: List of conditions to be passed to the query where() method @@ -1266,5 +1239,5 @@ abstract public function isOwningSide(Table $side): bool; * the saved entity * @see \Cake\ORM\Table::save() */ - abstract public function saveAssociated(EntityInterface $entity, array $options = []); + abstract public function saveAssociated(EntityInterface $entity, array $options = []): EntityInterface|false; } diff --git a/app/vendor/cakephp/cakephp/src/ORM/Association/BelongsTo.php b/app/vendor/cakephp/cakephp/src/ORM/Association/BelongsTo.php index 3072073a5..2ecfce120 100644 --- a/app/vendor/cakephp/cakephp/src/ORM/Association/BelongsTo.php +++ b/app/vendor/cakephp/cakephp/src/ORM/Association/BelongsTo.php @@ -16,6 +16,7 @@ */ namespace Cake\ORM\Association; +use Cake\Database\Exception\DatabaseException; use Cake\Database\Expression\IdentifierExpression; use Cake\Datasource\EntityInterface; use Cake\ORM\Association; @@ -23,7 +24,6 @@ use Cake\ORM\Table; use Cake\Utility\Inflector; use Closure; -use RuntimeException; use function Cake\Core\pluginSplit; /** @@ -42,25 +42,37 @@ class BelongsTo extends Association * * @var array */ - protected $_validStrategies = [ + protected array $_validStrategies = [ self::STRATEGY_JOIN, self::STRATEGY_SELECT, ]; /** - * Gets the name of the field representing the foreign key to the target table. - * - * @return array|string + * @inheritDoc */ - public function getForeignKey() + public function getForeignKey(): array|string|false { - if ($this->_foreignKey === null) { + if (!isset($this->_foreignKey)) { $this->_foreignKey = $this->_modelKey($this->getTarget()->getAlias()); } return $this->_foreignKey; } + /** + * Sets the name of the field representing the foreign key to the target table. + * + * @param array|string|false $key the key or keys to be used to link both tables together, if set to `false` + * no join conditions will be generated automatically. + * @return $this + */ + public function setForeignKey(array|string|false $key) + { + $this->_foreignKey = $key; + + return $this; + } + /** * Handle cascading deletes. * @@ -122,10 +134,10 @@ public function type(): string * the saved entity * @see \Cake\ORM\Table::save() */ - public function saveAssociated(EntityInterface $entity, array $options = []) + public function saveAssociated(EntityInterface $entity, array $options = []): EntityInterface|false { $targetEntity = $entity->get($this->getProperty()); - if (empty($targetEntity) || !($targetEntity instanceof EntityInterface)) { + if (!$targetEntity instanceof EntityInterface) { return $entity; } @@ -135,11 +147,18 @@ public function saveAssociated(EntityInterface $entity, array $options = []) return false; } + /** @var array $foreignKeys */ + $foreignKeys = (array)$this->getForeignKey(); $properties = array_combine( - (array)$this->getForeignKey(), - $targetEntity->extract((array)$this->getBindingKey()) + $foreignKeys, + $targetEntity->extract((array)$this->getBindingKey()), ); - $entity->set($properties, ['guard' => false]); + + if (method_exists($entity, 'patch')) { + $entity = $entity->patch($properties, ['guard' => false]); + } else { + $entity->set($properties, ['guard' => false]); + } return $entity; } @@ -150,7 +169,7 @@ public function saveAssociated(EntityInterface $entity, array $options = []) * * @param array $options list of options passed to attachTo method * @return array<\Cake\Database\Expression\IdentifierExpression> - * @throws \RuntimeException if the number of columns in the foreignKey do not + * @throws \Cake\Database\Exception\DatabaseException if the number of columns in the foreignKey do not * match the number of columns in the target table primaryKey */ protected function _joinCondition(array $options): array @@ -162,17 +181,17 @@ protected function _joinCondition(array $options): array $bindingKey = (array)$this->getBindingKey(); if (count($foreignKey) !== count($bindingKey)) { - if (empty($bindingKey)) { - $msg = 'The "%s" table does not define a primary key. Please set one.'; - throw new RuntimeException(sprintf($msg, $this->getTarget()->getTable())); + if (!$bindingKey) { + $msg = 'The `%s` table does not define a primary key. Please set one.'; + throw new DatabaseException(sprintf($msg, $this->getTarget()->getTable())); } - $msg = 'Cannot match provided foreignKey for "%s", got "(%s)" but expected foreign key for "(%s)"'; - throw new RuntimeException(sprintf( + $msg = 'Cannot match provided foreignKey for `%s`, got `(%s)` but expected foreign key for `(%s)`.'; + throw new DatabaseException(sprintf( $msg, $this->_name, implode(', ', $foreignKey), - implode(', ', $bindingKey) + implode(', ', $bindingKey), )); } @@ -198,7 +217,7 @@ public function eagerLoader(array $options): Closure 'bindingKey' => $this->getBindingKey(), 'strategy' => $this->getStrategy(), 'associationType' => $this->type(), - 'finder' => [$this, 'find'], + 'finder' => $this->find(...), ]); return $loader->buildEagerLoader($options); diff --git a/app/vendor/cakephp/cakephp/src/ORM/Association/BelongsToMany.php b/app/vendor/cakephp/cakephp/src/ORM/Association/BelongsToMany.php index 71b26e4a0..1ad935135 100644 --- a/app/vendor/cakephp/cakephp/src/ORM/Association/BelongsToMany.php +++ b/app/vendor/cakephp/cakephp/src/ORM/Association/BelongsToMany.php @@ -23,7 +23,7 @@ use Cake\Datasource\EntityInterface; use Cake\ORM\Association; use Cake\ORM\Association\Loader\SelectWithPivotLoader; -use Cake\ORM\Query; +use Cake\ORM\Query\SelectQuery; use Cake\ORM\Table; use Cake\Utility\Hash; use Cake\Utility\Inflector; @@ -62,28 +62,28 @@ class BelongsToMany extends Association * * @var string */ - protected $_joinType = Query::JOIN_TYPE_INNER; + protected string $_joinType = SelectQuery::JOIN_TYPE_INNER; /** * The strategy name to be used to fetch associated records. * * @var string */ - protected $_strategy = self::STRATEGY_SELECT; + protected string $_strategy = self::STRATEGY_SELECT; /** * Junction table instance * * @var \Cake\ORM\Table */ - protected $_junctionTable; + protected Table $_junctionTable; /** * Junction table name * * @var string */ - protected $_junctionTableName; + protected string $_junctionTableName; /** * The name of the hasMany association from the target table @@ -91,7 +91,7 @@ class BelongsToMany extends Association * * @var string */ - protected $_junctionAssociationName; + protected string $_junctionAssociationName; /** * The name of the property to be set containing data from the junction table @@ -99,35 +99,35 @@ class BelongsToMany extends Association * * @var string */ - protected $_junctionProperty = '_joinData'; + protected string $_junctionProperty = '_joinData'; /** * Saving strategy to be used by this association * * @var string */ - protected $_saveStrategy = self::SAVE_REPLACE; + protected string $_saveStrategy = self::SAVE_REPLACE; /** * The name of the field representing the foreign key to the target table * * @var array|string|null */ - protected $_targetForeignKey; + protected array|string|null $_targetForeignKey = null; /** * The table instance for the junction relation. * - * @var \Cake\ORM\Table|string + * @var \Cake\ORM\Table|string|null */ - protected $_through; + protected Table|string|null $_through = null; /** * Valid strategies for this type of association * * @var array */ - protected $_validStrategies = [ + protected array $_validStrategies = [ self::STRATEGY_SELECT, self::STRATEGY_SUBQUERY, ]; @@ -140,28 +140,28 @@ class BelongsToMany extends Association * * @var bool */ - protected $_dependent = true; + protected bool $_dependent = true; /** * Filtered conditions that reference the target table. * * @var array|null */ - protected $_targetConditions; + protected ?array $_targetConditions = null; /** * Filtered conditions that reference the junction table. * * @var array|null */ - protected $_junctionConditions; + protected ?array $_junctionConditions = null; /** * Order in which target records should be returned * - * @var mixed + * @var \Cake\Database\ExpressionInterface|\Closure|array<\Cake\Database\ExpressionInterface|string>|string|null */ - protected $_sort; + protected ExpressionInterface|Closure|array|string|null $_sort = null; /** * Sets the name of the field representing the foreign key to the target table. @@ -169,7 +169,7 @@ class BelongsToMany extends Association * @param array|string $key the key to be used to link both tables together * @return $this */ - public function setTargetForeignKey($key) + public function setTargetForeignKey(array|string $key) { $this->_targetForeignKey = $key; @@ -181,13 +181,9 @@ public function setTargetForeignKey($key) * * @return array|string */ - public function getTargetForeignKey() + public function getTargetForeignKey(): array|string { - if ($this->_targetForeignKey === null) { - $this->_targetForeignKey = $this->_modelKey($this->getTarget()->getAlias()); - } - - return $this->_targetForeignKey; + return $this->_targetForeignKey ??= $this->_modelKey($this->getTarget()->getAlias()); } /** @@ -203,13 +199,11 @@ public function canBeJoined(array $options = []): bool } /** - * Gets the name of the field representing the foreign key to the source table. - * - * @return array|string + * @inheritDoc */ - public function getForeignKey() + public function getForeignKey(): array|string|false { - if ($this->_foreignKey === null) { + if (!isset($this->_foreignKey)) { $this->_foreignKey = $this->_modelKey($this->getSource()->getTable()); } @@ -219,10 +213,10 @@ public function getForeignKey() /** * Sets the sort order in which target records should be returned. * - * @param mixed $sort A find() compatible order clause + * @param \Cake\Database\ExpressionInterface|\Closure|array<\Cake\Database\ExpressionInterface|string>|string $sort A find() compatible order clause * @return $this */ - public function setSort($sort) + public function setSort(ExpressionInterface|Closure|array|string $sort) { $this->_sort = $sort; @@ -232,9 +226,9 @@ public function setSort($sort) /** * Gets the sort order in which target records should be returned. * - * @return mixed + * @return \Cake\Database\ExpressionInterface|\Closure|array<\Cake\Database\ExpressionInterface|string>|string|null */ - public function getSort() + public function getSort(): ExpressionInterface|Closure|array|string|null { return $this->_sort; } @@ -260,14 +254,14 @@ public function defaultRowValue(array $row, bool $joined): array * @return \Cake\ORM\Table * @throws \InvalidArgumentException If the expected associations are incompatible with existing associations. */ - public function junction($table = null): Table + public function junction(Table|string|null $table = null): Table { - if ($table === null && $this->_junctionTable !== null) { + if ($table === null && isset($this->_junctionTable)) { return $this->_junctionTable; } $tableLocator = $this->getTableLocator(); - if ($table === null && $this->_through) { + if ($table === null && $this->_through !== null) { $table = $this->_through; } elseif ($table === null) { $tableName = $this->_junctionTableName(); @@ -295,7 +289,7 @@ public function junction($table = null): Table throw new InvalidArgumentException(sprintf( 'The `%s` association on `%s` cannot target the same table.', $this->getName(), - $source->getAlias() + $source->getAlias(), )); } @@ -306,6 +300,29 @@ public function junction($table = null): Table return $this->_junctionTable = $table; } + /** + * Set the junction property name. + * + * @param string $junctionProperty Property name. + * @return $this + */ + public function setJunctionProperty(string $junctionProperty) + { + $this->_junctionProperty = $junctionProperty; + + return $this; + } + + /** + * Get the junction property naeme. + * + * @return string + */ + public function getJunctionProperty(): string + { + return $this->_junctionProperty; + } + /** * Generate reciprocal associations as necessary. * @@ -423,7 +440,7 @@ protected function _generateJunctionAssociations(Table $junction, Table $source, ) { throw new InvalidArgumentException( "The existing `{$tAlias}` association on `{$junction->getAlias()}` " . - "is incompatible with the `{$this->getName()}` association on `{$source->getAlias()}`" + "is incompatible with the `{$this->getName()}` association on `{$source->getAlias()}`", ); } } @@ -450,11 +467,11 @@ protected function _generateJunctionAssociations(Table $junction, Table $source, * - fields: a list of fields in the target table to include in the result * - type: The type of join to be used (e.g. INNER) * - * @param \Cake\ORM\Query $query the query to be altered to include the target table data + * @param \Cake\ORM\Query\SelectQuery $query the query to be altered to include the target table data * @param array $options Any extra options or overrides to be taken in account * @return void */ - public function attachTo(Query $query, array $options = []): void + public function attachTo(SelectQuery $query, array $options = []): void { if (!empty($options['negateMatch'])) { $this->_appendNotMatching($query, $options); @@ -469,8 +486,8 @@ public function attachTo(Query $query, array $options = []): void $includeFields = $options['includeFields'] ?? null; - // Attach the junction table as well we need it to populate _joinData. - $assoc = $this->_targetTable->getAssociation($junction->getAlias()); + // Attach the junction table as well we need it to populate junction property (_joinData). + $assoc = $this->getTarget()->getAssociation($junction->getAlias()); $newOptions = array_intersect_key($options, ['joinType' => 1, 'fields' => 1]); $newOptions += [ 'conditions' => $cond, @@ -484,18 +501,20 @@ public function attachTo(Query $query, array $options = []): void $foreignKey = $this->getTargetForeignKey(); $thisJoin = $query->clause('join')[$this->getName()]; - $thisJoin['conditions']->add($assoc->_joinCondition(['foreignKey' => $foreignKey])); + /** @var \Cake\Database\Expression\QueryExpression $conditions */ + $conditions = $thisJoin['conditions']; + $conditions->add($assoc->_joinCondition(['foreignKey' => $foreignKey])); } /** * @inheritDoc */ - protected function _appendNotMatching(Query $query, array $options): void + protected function _appendNotMatching(SelectQuery $query, array $options): void { if (empty($options['negateMatch'])) { return; } - $options['conditions'] = $options['conditions'] ?? []; + $options['conditions'] ??= []; $junction = $this->junction(); $belongsTo = $junction->getAssociation($this->getSource()->getAlias()); $conds = $belongsTo->_joinCondition(['foreignKey' => $belongsTo->getForeignKey()]); @@ -522,7 +541,7 @@ protected function _appendNotMatching(Query $query, array $options): void return $exp ->or([ $exp->notIn($identifiers, $subquery), - $nullExp->and(array_map([$nullExp, 'isNull'], array_keys($conds))), + $nullExp->and(array_map($nullExp->isNull(...), array_keys($conds))), ]); }); } @@ -587,12 +606,14 @@ public function cascadeDelete(EntityInterface $entity, array $options = []): boo if (!$this->getDependent()) { return true; } - $foreignKey = (array)$this->getForeignKey(); - $bindingKey = (array)$this->getBindingKey(); + + /** @var array $foreignKeys */ + $foreignKeys = (array)$this->getForeignKey(); + $bindingKeys = (array)$this->getBindingKey(); $conditions = []; - if (!empty($bindingKey)) { - $conditions = array_combine($foreignKey, $entity->extract($bindingKey)); + if ($bindingKeys) { + $conditions = array_combine($foreignKeys, $entity->extract($bindingKeys)); } $table = $this->junction(); @@ -642,7 +663,7 @@ public function isOwningSide(Table $side): bool public function setSaveStrategy(string $strategy) { if (!in_array($strategy, [self::SAVE_APPEND, self::SAVE_REPLACE], true)) { - $msg = sprintf('Invalid save strategy "%s"', $strategy); + $msg = sprintf('Invalid save strategy `%s`', $strategy); throw new InvalidArgumentException($msg); } @@ -685,7 +706,7 @@ public function getSaveStrategy(): string * @see \Cake\ORM\Table::save() * @see \Cake\ORM\Association\BelongsToMany::replaceLinks() */ - public function saveAssociated(EntityInterface $entity, array $options = []) + public function saveAssociated(EntityInterface $entity, array $options = []): EntityInterface|false { $targetEntity = $entity->get($this->getProperty()); $strategy = $this->getSaveStrategy(); @@ -723,8 +744,11 @@ public function saveAssociated(EntityInterface $entity, array $options = []) * @return \Cake\Datasource\EntityInterface|false The parent entity after all links have been * created if no errors happened, false otherwise */ - protected function _saveTarget(EntityInterface $parentEntity, array $entities, $options) - { + protected function _saveTarget( + EntityInterface $parentEntity, + array $entities, + array $options, + ): EntityInterface|false { $joinAssociations = false; if (isset($options['associated']) && is_array($options['associated'])) { if (!empty($options['associated'][$this->_junctionProperty]['associated'])) { @@ -756,11 +780,12 @@ protected function _saveTarget(EntityInterface $parentEntity, array $entities, $ // Saving the new linked entity failed, copy errors back into the // original entity if applicable and abort. if (!empty($options['atomic'])) { - $original[$k]->setErrors($entity->getErrors()); - } - if ($saved === false) { - return false; + /** @var \Cake\Datasource\EntityInterface $originalEntity */ + $originalEntity = $original[$k]; + $originalEntity->setErrors($entity->getErrors()); } + + return false; } $options['associated'] = $joinAssociations; @@ -792,7 +817,9 @@ protected function _saveLinks(EntityInterface $sourceEntity, array $targetEntiti $junction = $this->junction(); $entityClass = $junction->getEntityClass(); $belongsTo = $junction->getAssociation($target->getAlias()); + /** @var array $foreignKey */ $foreignKey = (array)$this->getForeignKey(); + /** @var array $assocForeignKey */ $assocForeignKey = (array)$belongsTo->getForeignKey(); $targetBindingKey = (array)$belongsTo->getBindingKey(); $bindingKey = (array)$this->getBindingKey(); @@ -801,24 +828,27 @@ protected function _saveLinks(EntityInterface $sourceEntity, array $targetEntiti foreach ($targetEntities as $e) { $joint = $e->get($jointProperty); - if (!$joint || !($joint instanceof EntityInterface)) { + if (!($joint instanceof EntityInterface)) { $joint = new $entityClass([], ['markNew' => true, 'source' => $junctionRegistryAlias]); } $sourceKeys = array_combine($foreignKey, $sourceEntity->extract($bindingKey)); $targetKeys = array_combine($assocForeignKey, $e->extract($targetBindingKey)); - $changedKeys = ( - $sourceKeys !== $joint->extract($foreignKey) || - $targetKeys !== $joint->extract($assocForeignKey) - ); + $changedKeys = $sourceKeys !== $joint->extract($foreignKey) || + $targetKeys !== $joint->extract($assocForeignKey); + // Keys were changed, the junction table record _could_ be // new. By clearing the primary key values, and marking the entity // as new, we let save() sort out whether we have a new link // or if we are updating an existing link. if ($changedKeys) { $joint->setNew(true); - $joint->unset($junction->getPrimaryKey()) - ->set(array_merge($sourceKeys, $targetKeys), ['guard' => false]); + $joint->unset($junction->getPrimaryKey()); + if (method_exists($joint, 'patch')) { + $joint->patch(array_merge($sourceKeys, $targetKeys), ['guard' => false]); + } else { + $joint->set(array_merge($sourceKeys, $targetKeys), ['guard' => false]); + } } $saved = $junction->save($joint, $options); @@ -873,7 +903,7 @@ public function link(EntityInterface $sourceEntity, array $targetEntities, array return $this->junction()->getConnection()->transactional( function () use ($sourceEntity, $targetEntities, $options) { return $this->_saveLinks($sourceEntity, $targetEntities, $options); - } + }, ); } @@ -907,13 +937,13 @@ function () use ($sourceEntity, $targetEntities, $options) { * this association. * @param array<\Cake\Datasource\EntityInterface> $targetEntities List of entities persisted in the target table for * this association. - * @param array|bool $options List of options to be passed to the internal `delete` call, + * @param array|bool $options List of options to be passed to the internal `delete` call, * or a `boolean` as `cleanProperty` key shortcut. - * @throws \InvalidArgumentException If non persisted entities are passed or if + * @throws \InvalidArgumentException If non-persisted entities are passed or if * any of them is lacking a primary key value. * @return bool Success */ - public function unlink(EntityInterface $sourceEntity, array $targetEntities, $options = []): bool + public function unlink(EntityInterface $sourceEntity, array $targetEntities, array|bool $options = []): bool { if (is_bool($options)) { $options = [ @@ -932,7 +962,7 @@ function () use ($sourceEntity, $targetEntities, $options): void { foreach ($links as $entity) { $this->_junctionTable->delete($entity, $options); } - } + }, ); /** @var array<\Cake\Datasource\EntityInterface> $existing */ @@ -944,11 +974,11 @@ function () use ($sourceEntity, $targetEntities, $options): void { /** @var \SplObjectStorage<\Cake\Datasource\EntityInterface, null> $storage */ $storage = new SplObjectStorage(); foreach ($targetEntities as $e) { - $storage->attach($e); + $storage->offsetSet($e); } foreach ($existing as $k => $e) { - if ($storage->contains($e)) { + if ($storage->offsetExists($e)) { unset($existing[$k]); } } @@ -962,10 +992,11 @@ function () use ($sourceEntity, $targetEntities, $options): void { /** * @inheritDoc */ - public function setConditions($conditions) + public function setConditions(Closure|array $conditions) { parent::setConditions($conditions); - $this->_targetConditions = $this->_junctionConditions = null; + $this->_targetConditions = null; + $this->_junctionConditions = null; return $this; } @@ -976,7 +1007,7 @@ public function setConditions($conditions) * @param \Cake\ORM\Table|string $through Name of the Table instance or the instance itself * @return $this */ - public function setThrough($through) + public function setThrough(Table|string $through) { $this->_through = $through; @@ -985,10 +1016,11 @@ public function setThrough($through) /** * Gets the current join table, either the name of the Table instance or the instance itself. + * Returns null if not defined. * - * @return \Cake\ORM\Table|string + * @return \Cake\ORM\Table|string|null */ - public function getThrough() + public function getThrough(): Table|string|null { return $this->_through; } @@ -999,11 +1031,11 @@ public function getThrough() * Any string expressions, or expression objects will * also be returned in this list. * - * @return array|\Closure|null Generally an array. If the conditions + * @return \Closure|array|null Generally an array. If the conditions * are not an array, the association conditions will be * returned unmodified. */ - protected function targetConditions() + protected function targetConditions(): mixed { if ($this->_targetConditions !== null) { return $this->_targetConditions; @@ -1015,7 +1047,7 @@ protected function targetConditions() $matching = []; $alias = $this->getAlias() . '.'; foreach ($conditions as $field => $value) { - if (is_string($field) && strpos($field, $alias) === 0) { + if (is_string($field) && str_starts_with($field, $alias)) { $matching[$field] = $value; } elseif (is_int($field) || $value instanceof ExpressionInterface) { $matching[$field] = $value; @@ -1044,7 +1076,7 @@ protected function junctionConditions(): array $alias = $this->_junctionAssociationName() . '.'; foreach ($conditions as $field => $value) { $isString = is_string($field); - if ($isString && strpos($field, $alias) === 0) { + if ($isString && str_starts_with($field, $alias)) { $matching[$field] = $value; } // Assume that operators contain junction conditions. @@ -1067,16 +1099,19 @@ protected function junctionConditions(): array * * @param array|string|null $type the type of query to perform, if an array is passed, * it will be interpreted as the `$options` parameter - * @param array $options The options to for the find + * @param mixed ...$args Arguments that match up to finder-specific parameters * @see \Cake\ORM\Table::find() - * @return \Cake\ORM\Query + * @return \Cake\ORM\Query\SelectQuery */ - public function find($type = null, array $options = []): Query + public function find(array|string|null $type = null, mixed ...$args): SelectQuery { $type = $type ?: $this->getFinder(); [$type, $opts] = $this->_extractFinder($type); + + $args += $opts; + $query = $this->getTarget() - ->find($type, $options + $opts) + ->find($type, ...$args) ->where($this->targetConditions()) ->addDefaultTypes($this->getTarget()); @@ -1090,11 +1125,11 @@ public function find($type = null, array $options = []): Query /** * Append a join to the junction table. * - * @param \Cake\ORM\Query $query The query to append. + * @param \Cake\ORM\Query\SelectQuery $query The query to append. * @param array|null $conditions The query conditions to use. - * @return \Cake\ORM\Query The modified query. + * @return \Cake\ORM\Query\SelectQuery The modified query. */ - protected function _appendJunctionJoin(Query $query, ?array $conditions = null): Query + protected function _appendJunctionJoin(SelectQuery $query, ?array $conditions = null): SelectQuery { $junctionTable = $this->junction(); if ($conditions === null) { @@ -1106,13 +1141,13 @@ protected function _appendJunctionJoin(Query $query, ?array $conditions = null): } $name = $this->_junctionAssociationName(); - /** @var array $joins */ $joins = $query->clause('join'); + assert(is_array($joins)); $matching = [ $name => [ 'table' => $junctionTable->getTable(), 'conditions' => $conditions, - 'type' => Query::JOIN_TYPE_INNER, + 'type' => SelectQuery::JOIN_TYPE_INNER, ], ]; @@ -1187,14 +1222,16 @@ function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { $junction = $this->junction(); $target = $this->getTarget(); + /** @var array $foreignKey */ $foreignKey = (array)$this->getForeignKey(); $assocForeignKey = (array)$junction->getAssociation($target->getAlias())->getForeignKey(); + $prefixedForeignKey = array_map($junction->aliasField(...), $foreignKey); - $prefixedForeignKey = array_map([$junction, 'aliasField'], $foreignKey); $junctionPrimaryKey = (array)$junction->getPrimaryKey(); $junctionQueryAlias = $junction->getAlias() . '__matches'; - - $keys = $matchesConditions = []; + $keys = []; + $matchesConditions = []; + /** @var string $key */ foreach (array_merge($assocForeignKey, $junctionPrimaryKey) as $key) { $aliased = $junction->aliasField($key); $keys[$key] = $aliased; @@ -1213,7 +1250,7 @@ function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { ->from([$junctionQueryAlias => $matches]) ->innerJoin( [$junction->getAlias() => $junction->getTable()], - $matchesConditions + $matchesConditions, ); $jointEntities = $this->_collectJointEntities($sourceEntity, $targetEntities); @@ -1228,10 +1265,10 @@ function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { $property = $this->getProperty(); - if (count($inserts)) { + if ($inserts !== []) { $inserted = array_combine( array_keys($inserts), - (array)$sourceEntity->get($property) + (array)$sourceEntity->get($property), ) ?: []; $targetEntities = $inserted + $targetEntities; } @@ -1241,7 +1278,7 @@ function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { $sourceEntity->setDirty($property, false); return true; - } + }, ); } @@ -1250,7 +1287,7 @@ function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { * `$existing` and `$jointEntities`. This method will return the values from * `$targetEntities` that were not deleted from calculating the difference. * - * @param \Cake\ORM\Query $existing a query for getting existing links + * @param \Cake\ORM\Query\SelectQuery $existing a query for getting existing links * @param array<\Cake\Datasource\EntityInterface> $jointEntities link entities that should be persisted * @param array $targetEntities entities in target table that are related to * the `$jointEntities` @@ -1258,19 +1295,23 @@ function () use ($sourceEntity, $targetEntities, $primaryValue, $options) { * @return array|false Array of entities not deleted or false in case of deletion failure for atomic saves. */ protected function _diffLinks( - Query $existing, + SelectQuery $existing, array $jointEntities, array $targetEntities, - array $options = [] - ) { + array $options = [], + ): array|false { $junction = $this->junction(); $target = $this->getTarget(); $belongsTo = $junction->getAssociation($target->getAlias()); + /** @var array $foreignKey */ $foreignKey = (array)$this->getForeignKey(); + /** @var array $assocForeignKey */ $assocForeignKey = (array)$belongsTo->getForeignKey(); $keys = array_merge($foreignKey, $assocForeignKey); - $deletes = $unmatchedEntityKeys = $present = []; + $deletes = []; + $unmatchedEntityKeys = []; + $present = []; foreach ($jointEntities as $i => $entity) { $unmatchedEntityKeys[$i] = $entity->extract($keys); @@ -1278,6 +1319,7 @@ protected function _diffLinks( } foreach ($existing as $existingLink) { + /** @var \Cake\ORM\Entity $existingLink */ $existingKeys = $existingLink->extract($keys); $found = false; foreach ($unmatchedEntityKeys as $i => $unmatchedKeys) { @@ -1389,7 +1431,7 @@ protected function _collectJointEntities(EntityInterface $sourceEntity, array $t } $joint = $entity->get($jointProperty); - if (!$joint || !($joint instanceof EntityInterface)) { + if (!($joint instanceof EntityInterface)) { $missing[] = $entity->extract($primary); continue; } @@ -1397,12 +1439,13 @@ protected function _collectJointEntities(EntityInterface $sourceEntity, array $t $result[] = $joint; } - if (empty($missing)) { + if (!$missing) { return $result; } $belongsTo = $junction->getAssociation($target->getAlias()); $hasMany = $source->getAssociation($junction->getAlias()); + /** @var array $foreignKey */ $foreignKey = (array)$this->getForeignKey(); $foreignKey = array_map(function ($key) { return $key . ' IS'; @@ -1437,7 +1480,7 @@ protected function _collectJointEntities(EntityInterface $sourceEntity, array $t */ protected function _junctionAssociationName(): string { - if (!$this->_junctionAssociationName) { + if (!isset($this->_junctionAssociationName)) { $this->_junctionAssociationName = $this->getTarget() ->getAssociation($this->junction()->getAlias()) ->getName(); @@ -1495,5 +1538,10 @@ protected function _options(array $options): void if (isset($options['sort'])) { $this->setSort($options['sort']); } + if (isset($options['junctionProperty'])) { + assert(is_string($options['junctionProperty']), '`junctionProperty` must be a string'); + + $this->_junctionProperty = $options['junctionProperty']; + } } } diff --git a/app/vendor/cakephp/cakephp/src/ORM/Association/DependentDeleteHelper.php b/app/vendor/cakephp/cakephp/src/ORM/Association/DependentDeleteHelper.php index e965d5de3..d33dea0f9 100644 --- a/app/vendor/cakephp/cakephp/src/ORM/Association/DependentDeleteHelper.php +++ b/app/vendor/cakephp/cakephp/src/ORM/Association/DependentDeleteHelper.php @@ -42,8 +42,9 @@ public function cascadeDelete(Association $association, EntityInterface $entity, return true; } $table = $association->getTarget(); - /** @psalm-suppress InvalidArgument */ - $foreignKey = array_map([$association, 'aliasField'], (array)$association->getForeignKey()); + /** @var callable $callable */ + $callable = $association->aliasField(...); + $foreignKey = array_map($callable, (array)$association->getForeignKey()); $bindingKey = (array)$association->getBindingKey(); $bindingValue = $entity->extract($bindingKey); if (in_array(null, $bindingValue, true)) { diff --git a/app/vendor/cakephp/cakephp/src/ORM/Association/HasMany.php b/app/vendor/cakephp/cakephp/src/ORM/Association/HasMany.php index 5d95621da..2e53236e9 100644 --- a/app/vendor/cakephp/cakephp/src/ORM/Association/HasMany.php +++ b/app/vendor/cakephp/cakephp/src/ORM/Association/HasMany.php @@ -19,11 +19,12 @@ use Cake\Collection\Collection; use Cake\Database\Expression\FieldInterface; use Cake\Database\Expression\QueryExpression; +use Cake\Database\ExpressionInterface; use Cake\Datasource\EntityInterface; use Cake\Datasource\InvalidPropertyInterface; use Cake\ORM\Association; use Cake\ORM\Association\Loader\SelectLoader; -use Cake\ORM\Query; +use Cake\ORM\Query\SelectQuery; use Cake\ORM\Table; use Closure; use InvalidArgumentException; @@ -42,30 +43,30 @@ class HasMany extends Association /** * Order in which target records should be returned * - * @var mixed + * @var \Cake\Database\ExpressionInterface|\Closure|array<\Cake\Database\ExpressionInterface|string>|string|null */ - protected $_sort; + protected ExpressionInterface|Closure|array|string|null $_sort = null; /** * The type of join to be used when adding the association to a query * * @var string */ - protected $_joinType = Query::JOIN_TYPE_INNER; + protected string $_joinType = SelectQuery::JOIN_TYPE_INNER; /** * The strategy name to be used to fetch associated records. * * @var string */ - protected $_strategy = self::STRATEGY_SELECT; + protected string $_strategy = self::STRATEGY_SELECT; /** * Valid strategies for this type of association * * @var array */ - protected $_validStrategies = [ + protected array $_validStrategies = [ self::STRATEGY_SELECT, self::STRATEGY_SUBQUERY, ]; @@ -89,7 +90,7 @@ class HasMany extends Association * * @var string */ - protected $_saveStrategy = self::SAVE_APPEND; + protected string $_saveStrategy = self::SAVE_APPEND; /** * Returns whether the passed table is the owning side for this @@ -114,7 +115,7 @@ public function isOwningSide(Table $side): bool public function setSaveStrategy(string $strategy) { if (!in_array($strategy, [self::SAVE_APPEND, self::SAVE_REPLACE], true)) { - $msg = sprintf('Invalid save strategy "%s"', $strategy); + $msg = sprintf('Invalid save strategy `%s`', $strategy); throw new InvalidArgumentException($msg); } @@ -146,7 +147,7 @@ public function getSaveStrategy(): string * @see \Cake\ORM\Table::save() * @throws \InvalidArgumentException when the association data cannot be traversed. */ - public function saveAssociated(EntityInterface $entity, array $options = []) + public function saveAssociated(EntityInterface $entity, array $options = []): EntityInterface|false { $targetEntities = $entity->get($this->getProperty()); @@ -168,9 +169,11 @@ public function saveAssociated(EntityInterface $entity, array $options = []) throw new InvalidArgumentException($message); } + /** @var array $foreignKeys */ + $foreignKeys = (array)$this->getForeignKey(); $foreignKeyReference = array_combine( - (array)$this->getForeignKey(), - $entity->extract((array)$this->getBindingKey()) + $foreignKeys, + $entity->extract((array)$this->getBindingKey()), ); $options['_sourceTable'] = $this->getSource(); @@ -209,7 +212,7 @@ protected function _saveTarget( array $foreignKeyReference, EntityInterface $parentEntity, array $entities, - array $options + array $options, ): bool { $foreignKey = array_keys($foreignKeyReference); $table = $this->getTarget(); @@ -225,7 +228,11 @@ protected function _saveTarget( } if ($foreignKeyReference !== $entity->extract($foreignKey)) { - $entity->set($foreignKeyReference, ['guard' => false]); + if (method_exists($entity, 'patch')) { + $entity->patch($foreignKeyReference, ['guard' => false]); + } else { + $entity->set($foreignKeyReference, ['guard' => false]); + } } if ($table->save($entity, $options)) { @@ -234,9 +241,11 @@ protected function _saveTarget( } if (!empty($options['atomic'])) { - $original[$k]->setErrors($entity->getErrors()); + /** @var \Cake\ORM\Entity $originEntity */ + $originEntity = $original[$k]; + $originEntity->setErrors($entity->getErrors()); if ($entity instanceof InvalidPropertyInterface) { - $original[$k]->setInvalid($entity->getInvalid()); + $originEntity->setInvalid($entity->getInvalid()); } return false; @@ -279,19 +288,35 @@ public function link(EntityInterface $sourceEntity, array $targetEntities, array $this->setSaveStrategy(self::SAVE_APPEND); $property = $this->getProperty(); - $currentEntities = array_unique( - array_merge( - (array)$sourceEntity->get($property), - $targetEntities - ) - ); + $currentEntities = (array)$sourceEntity->get($property); + if ($currentEntities === []) { + $currentEntities = $targetEntities; + } else { + $pkFields = (array)$this->getTarget()->getPrimaryKey(); + $targetEntities = (new Collection($targetEntities)) + ->reject( + function (EntityInterface $entity) use ($currentEntities, $pkFields) { + if ($entity->isNew()) { + return false; + } - $sourceEntity->set($property, $currentEntities); + foreach ($currentEntities as $cEntity) { + if ($entity->extract($pkFields) === $cEntity->extract($pkFields)) { + return true; + } + } + + return false; + }, + ) + ->toList(); - $savedEntity = $this->getConnection()->transactional(function () use ($sourceEntity, $options) { - return $this->saveAssociated($sourceEntity, $options); - }); + $currentEntities = array_merge($currentEntities, $targetEntities); + } + + $sourceEntity->set($property, $currentEntities); + $savedEntity = $this->getConnection()->transactional(fn() => $this->saveAssociated($sourceEntity, $options)); $ok = ($savedEntity instanceof EntityInterface); $this->setSaveStrategy($saveStrategy); @@ -344,7 +369,7 @@ public function link(EntityInterface $sourceEntity, array $targetEntities, array * any of them is lacking a primary key value * @return void */ - public function unlink(EntityInterface $sourceEntity, array $targetEntities, $options = []): void + public function unlink(EntityInterface $sourceEntity, array $targetEntities, array|bool $options = []): void { if (is_bool($options)) { $options = [ @@ -353,7 +378,7 @@ public function unlink(EntityInterface $sourceEntity, array $targetEntities, $op } else { $options += ['cleanProperty' => true]; } - if (count($targetEntities) === 0) { + if ($targetEntities === []) { return; } @@ -364,8 +389,8 @@ public function unlink(EntityInterface $sourceEntity, array $targetEntities, $op $conditions = [ 'OR' => (new Collection($targetEntities)) - ->map(function ($entity) use ($targetPrimaryKey) { - /** @var \Cake\Datasource\EntityInterface $entity */ + ->map(function (EntityInterface $entity) use ($targetPrimaryKey) { + /** @var array $targetPrimaryKey */ return $entity->extract($targetPrimaryKey); }) ->toList(), @@ -381,9 +406,9 @@ public function unlink(EntityInterface $sourceEntity, array $targetEntities, $op ->reject( function ($assoc) use ($targetEntities) { return in_array($assoc, $targetEntities); - } + }, ) - ->toList() + ->toList(), ); } @@ -443,6 +468,7 @@ public function replace(EntityInterface $sourceEntity, array $targetEntities, ar $ok = ($result instanceof EntityInterface); if ($ok) { + // phpcs:ignore SlevomatCodingStandard.Variables.UnusedVariable.UnusedVariable $sourceEntity = $result; } $this->setSaveStrategy($saveStrategy); @@ -467,26 +493,25 @@ protected function _unlinkAssociated( EntityInterface $entity, Table $target, iterable $remainingEntities = [], - array $options = [] + array $options = [], ): bool { $primaryKey = (array)$target->getPrimaryKey(); $exclusions = new Collection($remainingEntities); $exclusions = $exclusions->map( - function ($ent) use ($primaryKey) { - /** @var \Cake\Datasource\EntityInterface $ent */ + function (EntityInterface $ent) use ($primaryKey) { return $ent->extract($primaryKey); - } + }, ) ->filter( function ($v) { return !in_array(null, $v, true); - } + }, ) ->toList(); $conditions = $foreignKeyReference; - if (count($exclusions) > 0) { + if ($exclusions !== []) { $conditions = [ 'NOT' => [ 'OR' => $exclusions, @@ -527,7 +552,7 @@ protected function _unlink(array $foreignKey, Table $target, array $conditions = }); $query = $this->find()->where($conditions); $ok = true; - foreach ($query as $assoc) { + foreach ($query->all() as $assoc) { $ok = $ok && $target->delete($assoc, $options); } @@ -560,8 +585,8 @@ protected function _foreignKeyAcceptsNull(Table $table, array $properties): bool function ($prop) use ($table) { return $table->getSchema()->isNullable($prop); }, - $properties - ) + $properties, + ), ); } @@ -588,13 +613,11 @@ public function canBeJoined(array $options = []): bool } /** - * Gets the name of the field representing the foreign key to the source table. - * - * @return array|string + * @inheritDoc */ - public function getForeignKey() + public function getForeignKey(): array|string|false { - if ($this->_foreignKey === null) { + if (!isset($this->_foreignKey)) { $this->_foreignKey = $this->_modelKey($this->getSource()->getTable()); } @@ -604,10 +627,10 @@ public function getForeignKey() /** * Sets the sort order in which target records should be returned. * - * @param mixed $sort A find() compatible order clause + * @param \Cake\Database\ExpressionInterface|\Closure|array<\Cake\Database\ExpressionInterface|string>|string $sort A find() compatible order clause * @return $this */ - public function setSort($sort) + public function setSort(ExpressionInterface|Closure|array|string $sort) { $this->_sort = $sort; @@ -617,9 +640,9 @@ public function setSort($sort) /** * Gets the sort order in which target records should be returned. * - * @return mixed + * @return \Cake\Database\ExpressionInterface|\Closure|array<\Cake\Database\ExpressionInterface|string>|string|null */ - public function getSort() + public function getSort(): ExpressionInterface|Closure|array|string|null { return $this->_sort; } @@ -667,7 +690,7 @@ public function eagerLoader(array $options): Closure 'strategy' => $this->getStrategy(), 'associationType' => $this->type(), 'sort' => $this->getSort(), - 'finder' => [$this, 'find'], + 'finder' => $this->find(...), ]); return $loader->buildEagerLoader($options); diff --git a/app/vendor/cakephp/cakephp/src/ORM/Association/HasOne.php b/app/vendor/cakephp/cakephp/src/ORM/Association/HasOne.php index ff239477d..accdb1b99 100644 --- a/app/vendor/cakephp/cakephp/src/ORM/Association/HasOne.php +++ b/app/vendor/cakephp/cakephp/src/ORM/Association/HasOne.php @@ -40,25 +40,37 @@ class HasOne extends Association * * @var array */ - protected $_validStrategies = [ + protected array $_validStrategies = [ self::STRATEGY_JOIN, self::STRATEGY_SELECT, ]; /** - * Gets the name of the field representing the foreign key to the target table. - * - * @return array|string + * @inheritDoc */ - public function getForeignKey() + public function getForeignKey(): array|string|false { - if ($this->_foreignKey === null) { + if (!isset($this->_foreignKey)) { $this->_foreignKey = $this->_modelKey($this->getSource()->getAlias()); } return $this->_foreignKey; } + /** + * Sets the name of the field representing the foreign key to the target table. + * + * @param array|string|false $key the key or keys to be used to link both tables together, if set to `false` + * no join conditions will be generated automatically. + * @return $this + */ + public function setForeignKey(array|string|false $key) + { + $this->_foreignKey = $key; + + return $this; + } + /** * Returns default property name based on association name. * @@ -106,18 +118,24 @@ public function type(): string * the saved entity * @see \Cake\ORM\Table::save() */ - public function saveAssociated(EntityInterface $entity, array $options = []) + public function saveAssociated(EntityInterface $entity, array $options = []): EntityInterface|false { $targetEntity = $entity->get($this->getProperty()); - if (empty($targetEntity) || !($targetEntity instanceof EntityInterface)) { + if (!$targetEntity instanceof EntityInterface) { return $entity; } + /** @var array $foreignKeys */ + $foreignKeys = (array)$this->getForeignKey(); $properties = array_combine( - (array)$this->getForeignKey(), - $entity->extract((array)$this->getBindingKey()) + $foreignKeys, + $entity->extract((array)$this->getBindingKey()), ); - $targetEntity->set($properties, ['guard' => false]); + if (method_exists($targetEntity, 'patch')) { + $targetEntity = $targetEntity->patch($properties, ['guard' => false]); + } else { + $targetEntity->set($properties, ['guard' => false]); + } if (!$this->getTarget()->save($targetEntity, $options)) { $targetEntity->unset(array_keys($properties)); @@ -141,7 +159,7 @@ public function eagerLoader(array $options): Closure 'bindingKey' => $this->getBindingKey(), 'strategy' => $this->getStrategy(), 'associationType' => $this->type(), - 'finder' => [$this, 'find'], + 'finder' => $this->find(...), ]); return $loader->buildEagerLoader($options); diff --git a/app/vendor/cakephp/cakephp/src/ORM/Association/Loader/SelectLoader.php b/app/vendor/cakephp/cakephp/src/ORM/Association/Loader/SelectLoader.php index dce3b3200..35113ab62 100644 --- a/app/vendor/cakephp/cakephp/src/ORM/Association/Loader/SelectLoader.php +++ b/app/vendor/cakephp/cakephp/src/ORM/Association/Loader/SelectLoader.php @@ -16,14 +16,15 @@ */ namespace Cake\ORM\Association\Loader; +use Cake\Database\Exception\DatabaseException; use Cake\Database\Expression\IdentifierExpression; use Cake\Database\Expression\TupleComparison; +use Cake\Database\ExpressionInterface; use Cake\Database\ValueBinder; use Cake\ORM\Association; -use Cake\ORM\Query; +use Cake\ORM\Query\SelectQuery; use Closure; use InvalidArgumentException; -use RuntimeException; /** * Implements the logic for loading an association using a SELECT query @@ -37,42 +38,42 @@ class SelectLoader * * @var string */ - protected $alias; + protected string $alias; /** * The alias of the source association * * @var string */ - protected $sourceAlias; + protected string $sourceAlias; /** * The alias of the target association * * @var string */ - protected $targetAlias; + protected string $targetAlias; /** * The foreignKey to the target association * * @var array|string */ - protected $foreignKey; + protected array|string $foreignKey; /** * The strategy to use for loading, either select or subquery * * @var string */ - protected $strategy; + protected string $strategy; /** * The binding key for the source association. * - * @var string + * @var array|string */ - protected $bindingKey; + protected array|string $bindingKey; /** * A callable that will return a query object used for loading the association results @@ -86,14 +87,14 @@ class SelectLoader * * @var string */ - protected $associationType; + protected string $associationType; /** * The sorting options for loading the association * - * @var string + * @var \Cake\Database\ExpressionInterface|\Closure|array|string|null */ - protected $sort; + protected ExpressionInterface|Closure|array|string|null $sort = null; /** * Copies the options array to properties in this class. The keys in the array correspond @@ -152,44 +153,48 @@ protected function _defaultOptions(): array * the source table * * @param array $options options accepted by eagerLoader() - * @return \Cake\ORM\Query + * @return \Cake\ORM\Query\SelectQuery * @throws \InvalidArgumentException When a key is required for associations but not selected. */ - protected function _buildQuery(array $options): Query + protected function _buildQuery(array $options): SelectQuery { $key = $this->_linkField($options); $filter = $options['keys']; $useSubquery = $options['strategy'] === Association::STRATEGY_SUBQUERY; $finder = $this->finder; - $options['fields'] = $options['fields'] ?? []; + $options['fields'] ??= []; - /** @var \Cake\ORM\Query $query */ $query = $finder(); + assert($query instanceof SelectQuery); if (isset($options['finder'])) { [$finderName, $opts] = $this->_extractFinder($options['finder']); - $query = $query->find($finderName, $opts); + $query = $query->find($finderName, ...$opts); } + /** @var \Cake\ORM\Query\SelectQuery $selectQuery */ + $selectQuery = $options['query']; + $fetchQuery = $query ->select($options['fields']) ->where($options['conditions']) ->eagerLoaded(true) - ->enableHydration($options['query']->isHydrationEnabled()); - if ($options['query']->isResultsCastingEnabled()) { + ->enableHydration($selectQuery->isHydrationEnabled()) + ->setConnectionRole($selectQuery->getConnectionRole()); + if ($selectQuery->isResultsCastingEnabled()) { $fetchQuery->enableResultsCasting(); } else { $fetchQuery->disableResultsCasting(); } if ($useSubquery) { - $filter = $this->_buildSubquery($options['query']); + $filter = $this->_buildSubquery($selectQuery); $fetchQuery = $this->_addFilteringJoin($fetchQuery, $key, $filter); } else { $fetchQuery = $this->_addFilteringCondition($fetchQuery, $key, $filter); } if (!empty($options['sort'])) { - $fetchQuery->order($options['sort']); + $fetchQuery->orderBy($options['sort']); } if (!empty($options['contain'])) { @@ -221,7 +226,7 @@ protected function _buildQuery(array $options): Query * and options as value. * @return array */ - protected function _extractFinder($finderData): array + protected function _extractFinder(array|string $finderData): array { $finderData = (array)$finderData; @@ -237,22 +242,22 @@ protected function _extractFinder($finderData): array * has the foreignKey fields selected. * If the required fields are missing, throws an exception. * - * @param \Cake\ORM\Query $fetchQuery The association fetching query + * @param \Cake\ORM\Query\SelectQuery $fetchQuery The association fetching query * @param array $key The foreign key fields to check * @return void * @throws \InvalidArgumentException */ - protected function _assertFieldsPresent(Query $fetchQuery, array $key): void + protected function _assertFieldsPresent(SelectQuery $fetchQuery, array $key): void { if ($fetchQuery->isAutoFieldsEnabled()) { return; } $select = $fetchQuery->aliasFields($fetchQuery->clause('select')); - if (empty($select)) { + if (!$select) { return; } - $missingKey = function ($fieldList, $key) { + $missingKey = function ($fieldList, $key): bool { foreach ($key as $keyField) { if (!in_array($keyField, $fieldList, true)) { return true; @@ -265,7 +270,7 @@ protected function _assertFieldsPresent(Query $fetchQuery, array $key): void $missingFields = $missingKey($select, $key); if ($missingFields) { $driver = $fetchQuery->getConnection()->getDriver(); - $quoted = array_map([$driver, 'quoteIdentifier'], $key); + $quoted = array_map($driver->quoteIdentifier(...), $key); $missingFields = $missingKey($select, $quoted); } @@ -273,8 +278,8 @@ protected function _assertFieldsPresent(Query $fetchQuery, array $key): void throw new InvalidArgumentException( sprintf( 'You are required to select the "%s" field(s)', - implode(', ', $key) - ) + implode(', ', $key), + ), ); } } @@ -284,12 +289,12 @@ protected function _assertFieldsPresent(Query $fetchQuery, array $key): void * target table query given a filter key and some filtering values when the * filtering needs to be done using a subquery. * - * @param \Cake\ORM\Query $query Target table's query + * @param \Cake\ORM\Query\SelectQuery $query Target table's query * @param array|string $key the fields that should be used for filtering - * @param \Cake\ORM\Query $subquery The Subquery to use for filtering - * @return \Cake\ORM\Query + * @param \Cake\ORM\Query\SelectQuery $subquery The Subquery to use for filtering + * @return \Cake\ORM\Query\SelectQuery */ - protected function _addFilteringJoin(Query $query, $key, $subquery): Query + protected function _addFilteringJoin(SelectQuery $query, array|string $key, SelectQuery $subquery): SelectQuery { $filter = []; $aliasedTable = $this->sourceAlias; @@ -312,7 +317,7 @@ protected function _addFilteringJoin(Query $query, $key, $subquery): Query return $query->innerJoin( [$aliasedTable => $subquery], - $conditions + $conditions, ); } @@ -320,12 +325,12 @@ protected function _addFilteringJoin(Query $query, $key, $subquery): Query * Appends any conditions required to load the relevant set of records in the * target table query given a filter key and some filtering values. * - * @param \Cake\ORM\Query $query Target table's query + * @param \Cake\ORM\Query\SelectQuery $query Target table's query * @param array|string $key The fields that should be used for filtering * @param mixed $filter The value that should be used to match for $key - * @return \Cake\ORM\Query + * @return \Cake\ORM\Query\SelectQuery */ - protected function _addFilteringCondition(Query $query, $key, $filter): Query + protected function _addFilteringCondition(SelectQuery $query, array|string $key, mixed $filter): SelectQuery { if (is_array($key)) { $conditions = $this->_createTupleCondition($query, $key, $filter, 'IN'); @@ -340,14 +345,18 @@ protected function _addFilteringCondition(Query $query, $key, $filter): Query * Returns a TupleComparison object that can be used for matching all the fields * from $keys with the tuple values in $filter using the provided operator. * - * @param \Cake\ORM\Query $query Target table's query + * @param \Cake\ORM\Query\SelectQuery $query Target table's query * @param array $keys the fields that should be used for filtering * @param mixed $filter the value that should be used to match for $key * @param string $operator The operator for comparing the tuples * @return \Cake\Database\Expression\TupleComparison */ - protected function _createTupleCondition(Query $query, array $keys, $filter, $operator): TupleComparison - { + protected function _createTupleCondition( + SelectQuery $query, + array $keys, + mixed $filter, + string $operator, + ): TupleComparison { $types = []; $defaults = $query->getDefaultTypes(); foreach ($keys as $k) { @@ -365,9 +374,9 @@ protected function _createTupleCondition(Query $query, array $keys, $filter, $op * * @param array $options The options for getting the link field. * @return array|string - * @throws \RuntimeException + * @throws \Cake\Database\Exception\DatabaseException */ - protected function _linkField(array $options) + protected function _linkField(array $options): array|string { $links = []; $name = $this->alias; @@ -375,7 +384,7 @@ protected function _linkField(array $options) if ($options['foreignKey'] === false && $this->associationType === Association::ONE_TO_MANY) { $msg = 'Cannot have foreignKey = false for hasMany associations. ' . 'You must provide a foreignKey column.'; - throw new RuntimeException($msg); + throw new DatabaseException($msg); } $keys = in_array($this->associationType, [Association::ONE_TO_ONE, Association::ONE_TO_MANY], true) ? @@ -398,10 +407,10 @@ protected function _linkField(array $options) * target table, it is constructed by cloning the original query that was used * to load records in the source table. * - * @param \Cake\ORM\Query $query the original query used to load source records - * @return \Cake\ORM\Query + * @param \Cake\ORM\Query\SelectQuery $query the original query used to load source records + * @return \Cake\ORM\Query\SelectQuery */ - protected function _buildSubquery(Query $query): Query + protected function _buildSubquery(SelectQuery $query): SelectQuery { $filterQuery = clone $query; $filterQuery->disableAutoFields(); @@ -413,12 +422,12 @@ protected function _buildSubquery(Query $query): Query // Ignore limit if there is no order since we need all rows to find matches if (!$filterQuery->clause('limit') || !$filterQuery->clause('order')) { $filterQuery->limit(null); - $filterQuery->order([], true); + $filterQuery->orderBy([], true); $filterQuery->offset(null); } $fields = $this->_subqueryFields($query); - $filterQuery->select($fields['select'], true)->group($fields['group']); + $filterQuery->select($fields['select'], true)->groupBy($fields['group']); return $filterQuery; } @@ -430,10 +439,10 @@ protected function _buildSubquery(Query $query): Query * those columns are also included as the fields may be calculated or constant values, * that need to be present to ensure the correct association data is loaded. * - * @param \Cake\ORM\Query $query The query to get fields from. + * @param \Cake\ORM\Query\SelectQuery $query The query to get fields from. * @return array The list of fields for the subquery. */ - protected function _subqueryFields(Query $query): array + protected function _subqueryFields(SelectQuery $query): array { $keys = (array)$this->bindingKey; @@ -442,8 +451,10 @@ protected function _subqueryFields(Query $query): array } $fields = $query->aliasFields($keys, $this->sourceAlias); - $group = $fields = array_values($fields); + $group = array_values($fields); + $fields = $group; + /** @var \Cake\Database\Expression\QueryExpression $order */ $order = $query->clause('order'); if ($order) { $columns = $query->clause('select'); @@ -461,11 +472,11 @@ protected function _subqueryFields(Query $query): array * Builds an array containing the results from fetchQuery indexed by * the foreignKey value corresponding to this association. * - * @param \Cake\ORM\Query $fetchQuery The query to get results from + * @param \Cake\ORM\Query\SelectQuery $fetchQuery The query to get results from * @param array $options The options passed to the eager loader * @return array */ - protected function _buildResultMap(Query $fetchQuery, array $options): array + protected function _buildResultMap(SelectQuery $fetchQuery, array $options): array { $resultMap = []; $singleResult = in_array($this->associationType, [Association::MANY_TO_ONE, Association::ONE_TO_ONE], true); @@ -474,16 +485,25 @@ protected function _buildResultMap(Query $fetchQuery, array $options): array $this->bindingKey; $key = (array)$keys; - foreach ($fetchQuery->all() as $result) { + $preserveKeys = $fetchQuery->getOptions()['preserveKeys'] ?? false; + + foreach ($fetchQuery->all() as $i => $result) { $values = []; foreach ($key as $k) { $values[] = $result[$k]; } + if ($singleResult) { $resultMap[implode(';', $values)] = $result; - } else { - $resultMap[implode(';', $values)][] = $result; + continue; } + + if ($preserveKeys) { + $resultMap[implode(';', $values)][$i] = $result; + continue; + } + + $resultMap[implode(';', $values)][] = $result; } return $resultMap; @@ -493,13 +513,13 @@ protected function _buildResultMap(Query $fetchQuery, array $options): array * Returns a callable to be used for each row in a query result set * for injecting the eager loaded rows * - * @param \Cake\ORM\Query $fetchQuery the Query used to fetch results + * @param \Cake\ORM\Query\SelectQuery $fetchQuery the Query used to fetch results * @param array $resultMap an array with the foreignKey as keys and * the corresponding target table results as value. * @param array $options The options passed to the eagerLoader method * @return \Closure */ - protected function _resultInjector(Query $fetchQuery, array $resultMap, array $options): Closure + protected function _resultInjector(SelectQuery $fetchQuery, array $resultMap, array $options): Closure { $keys = $this->associationType === Association::MANY_TO_ONE ? $this->foreignKey : @@ -508,7 +528,7 @@ protected function _resultInjector(Query $fetchQuery, array $resultMap, array $o $sourceKeys = []; foreach ((array)$keys as $key) { $f = $fetchQuery->aliasField($key, $this->sourceAlias); - $sourceKeys[] = key($f); + $sourceKeys[] = (string)key($f); } $nestKey = $options['nestKey']; diff --git a/app/vendor/cakephp/cakephp/src/ORM/Association/Loader/SelectWithPivotLoader.php b/app/vendor/cakephp/cakephp/src/ORM/Association/Loader/SelectWithPivotLoader.php index 318a8e45e..f3d5baf11 100644 --- a/app/vendor/cakephp/cakephp/src/ORM/Association/Loader/SelectWithPivotLoader.php +++ b/app/vendor/cakephp/cakephp/src/ORM/Association/Loader/SelectWithPivotLoader.php @@ -16,8 +16,11 @@ */ namespace Cake\ORM\Association\Loader; -use Cake\ORM\Query; -use RuntimeException; +use Cake\Database\Exception\DatabaseException; +use Cake\Database\ExpressionInterface; +use Cake\ORM\Association\HasMany; +use Cake\ORM\Query\SelectQuery; +use Closure; /** * Implements the logic for loading an association using a SELECT query and a pivot table @@ -31,28 +34,28 @@ class SelectWithPivotLoader extends SelectLoader * * @var string */ - protected $junctionAssociationName; + protected string $junctionAssociationName; /** * The property name for the junction association, where its results should be nested at. * * @var string */ - protected $junctionProperty; + protected string $junctionProperty; /** * The junction association instance * * @var \Cake\ORM\Association\HasMany */ - protected $junctionAssoc; + protected HasMany $junctionAssoc; /** * Custom conditions for the junction association * * @var \Cake\Database\ExpressionInterface|\Closure|array|string|null */ - protected $junctionConditions; + protected ExpressionInterface|Closure|array|string|null $junctionConditions = null; /** * @inheritDoc @@ -74,10 +77,10 @@ public function __construct(array $options) * This is used for eager loading records on the target table based on conditions. * * @param array $options options accepted by eagerLoader() - * @return \Cake\ORM\Query + * @return \Cake\ORM\Query\SelectQuery * @throws \InvalidArgumentException When a key is required for associations but not selected. */ - protected function _buildQuery(array $options): Query + protected function _buildQuery(array $options): SelectQuery { $name = $this->junctionAssociationName; $assoc = $this->junctionAssoc; @@ -91,6 +94,7 @@ protected function _buildQuery(array $options): Query $query = parent::_buildQuery($options); if ($queryBuilder) { + /** @var \Cake\ORM\Query\SelectQuery $query */ $query = $queryBuilder($query); } @@ -103,11 +107,12 @@ protected function _buildQuery(array $options): Query $tempName = $this->alias . '_CJoin'; $schema = $assoc->getSchema(); - $joinFields = $types = []; + $joinFields = []; + $types = []; foreach ($schema->typeMap() as $f => $type) { $key = $tempName . '__' . $f; - $joinFields[$key] = "$name.$f"; + $joinFields[$key] = "{$name}.{$f}"; $types[$key] = $type; } @@ -132,7 +137,7 @@ protected function _buildQuery(array $options): Query /** * @inheritDoc */ - protected function _assertFieldsPresent(Query $fetchQuery, array $key): void + protected function _assertFieldsPresent(SelectQuery $fetchQuery, array $key): void { // _buildQuery() manually adds in required fields from junction table } @@ -144,7 +149,7 @@ protected function _assertFieldsPresent(Query $fetchQuery, array $key): void * @param array $options the options to use for getting the link field. * @return array|string */ - protected function _linkField(array $options) + protected function _linkField(array $options): array|string { $links = []; $name = $this->junctionAssociationName; @@ -164,21 +169,22 @@ protected function _linkField(array $options) * Builds an array containing the results from fetchQuery indexed by * the foreignKey value corresponding to this association. * - * @param \Cake\ORM\Query $fetchQuery The query to get results from + * @param \Cake\ORM\Query\SelectQuery $fetchQuery The query to get results from * @param array $options The options passed to the eager loader * @return array - * @throws \RuntimeException when the association property is not part of the results set. + * @throws \Cake\Database\Exception\DatabaseException when the association property is not part of the results set. */ - protected function _buildResultMap(Query $fetchQuery, array $options): array + protected function _buildResultMap(SelectQuery $fetchQuery, array $options): array { $resultMap = []; $key = (array)$options['foreignKey']; + $preserveKeys = $fetchQuery->getOptions()['preserveKeys'] ?? false; - foreach ($fetchQuery->all() as $result) { + foreach ($fetchQuery->all() as $i => $result) { if (!isset($result[$this->junctionProperty])) { - throw new RuntimeException(sprintf( - '"%s" is missing from the belongsToMany results. Results cannot be created.', - $this->junctionProperty + throw new DatabaseException(sprintf( + '`%s` is missing from the belongsToMany results. Results cannot be created.', + $this->junctionProperty, )); } @@ -186,6 +192,12 @@ protected function _buildResultMap(Query $fetchQuery, array $options): array foreach ($key as $k) { $values[] = $result[$this->junctionProperty][$k]; } + + if ($preserveKeys) { + $resultMap[implode(';', $values)][$i] = $result; + continue; + } + $resultMap[implode(';', $values)][] = $result; } diff --git a/app/vendor/cakephp/cakephp/src/ORM/AssociationCollection.php b/app/vendor/cakephp/cakephp/src/ORM/AssociationCollection.php index 9f213145a..0818746ba 100644 --- a/app/vendor/cakephp/cakephp/src/ORM/AssociationCollection.php +++ b/app/vendor/cakephp/cakephp/src/ORM/AssociationCollection.php @@ -17,6 +17,7 @@ namespace Cake\ORM; use ArrayIterator; +use Cake\Core\Exception\CakeException; use Cake\Datasource\EntityInterface; use Cake\ORM\Locator\LocatorAwareTrait; use Cake\ORM\Locator\LocatorInterface; @@ -42,9 +43,9 @@ class AssociationCollection implements IteratorAggregate /** * Stored associations * - * @var array<\Cake\ORM\Association> + * @var array */ - protected $_items = []; + protected array $_items = []; /** * Constructor. @@ -70,14 +71,19 @@ public function __construct(?LocatorInterface $tableLocator = null) * @param string $alias The association alias * @param \Cake\ORM\Association $association The association to add. * @return \Cake\ORM\Association The association object being added. + * @throws \Cake\Core\Exception\CakeException If the alias is already added. * @template T of \Cake\ORM\Association - * @psalm-param T $association - * @psalm-return T + * @phpstan-param T $association + * @phpstan-return T */ public function add(string $alias, Association $association): Association { [, $alias] = pluginSplit($alias); + if (isset($this->_items[$alias])) { + throw new CakeException(sprintf('Association alias `%s` is already set.', $alias)); + } + return $this->_items[$alias] = $association; } @@ -90,8 +96,8 @@ public function add(string $alias, Association $association): Association * @return \Cake\ORM\Association * @throws \InvalidArgumentException * @template T of \Cake\ORM\Association - * @psalm-param class-string $className - * @psalm-return T + * @phpstan-param class-string $className + * @phpstan-return T */ public function load(string $className, string $associated, array $options = []): Association { @@ -161,12 +167,12 @@ public function keys(): array * @return array<\Cake\ORM\Association> An array of Association objects. * @since 3.5.3 */ - public function getByType($class): array + public function getByType(array|string $class): array { $class = array_map('strtolower', (array)$class); $out = array_filter($this->_items, function ($assoc) use ($class) { - [, $name] = namespaceSplit(get_class($assoc)); + [, $name] = namespaceSplit($assoc::class); return in_array(strtolower($name), $class, true); }); @@ -216,7 +222,7 @@ public function removeAll(): void */ public function saveParents(Table $table, EntityInterface $entity, array $associations, array $options = []): bool { - if (empty($associations)) { + if (!$associations) { return true; } @@ -238,7 +244,7 @@ public function saveParents(Table $table, EntityInterface $entity, array $associ */ public function saveChildren(Table $table, EntityInterface $entity, array $associations, array $options): bool { - if (empty($associations)) { + if (!$associations) { return true; } @@ -262,7 +268,7 @@ protected function _saveAssociations( EntityInterface $entity, array $associations, array $options, - bool $owningSide + bool $owningSide, ): bool { unset($options['associated']); foreach ($associations as $alias => $nested) { @@ -273,9 +279,9 @@ protected function _saveAssociations( $relation = $this->get($alias); if (!$relation) { $msg = sprintf( - 'Cannot save %s, it is not associated to %s', + 'Cannot save `%s`, it is not associated to `%s`.', $alias, - $table->getAlias() + $table->getAlias(), ); throw new InvalidArgumentException($msg); } @@ -303,12 +309,12 @@ protected function _save( Association $association, EntityInterface $entity, array $nested, - array $options + array $options, ): bool { if (!$entity->isDirty($association->getProperty())) { return true; } - if (!empty($nested)) { + if ($nested) { $options = $nested + $options; } @@ -352,16 +358,16 @@ public function cascadeDelete(EntityInterface $entity, array $options): bool * array. If true is passed, then it returns all association names * in this collection. * - * @param array|bool $keys the list of association names to normalize + * @param array|string|bool $keys the list of association names to normalize * @return array */ - public function normalizeKeys($keys): array + public function normalizeKeys(array|string|bool $keys): array { if ($keys === true) { $keys = $this->keys(); } - if (empty($keys)) { + if (!$keys) { return []; } diff --git a/app/vendor/cakephp/cakephp/src/ORM/AssociationsNormalizerTrait.php b/app/vendor/cakephp/cakephp/src/ORM/AssociationsNormalizerTrait.php index 5b27a7dbe..6f869d219 100644 --- a/app/vendor/cakephp/cakephp/src/ORM/AssociationsNormalizerTrait.php +++ b/app/vendor/cakephp/cakephp/src/ORM/AssociationsNormalizerTrait.php @@ -29,7 +29,7 @@ trait AssociationsNormalizerTrait * @param array|string $associations The array of included associations. * @return array An array having dot notation transformed into nested arrays */ - protected function _normalizeAssociations($associations): array + protected function _normalizeAssociations(array|string $associations): array { $result = []; foreach ((array)$associations as $table => $options) { @@ -40,15 +40,16 @@ protected function _normalizeAssociations($associations): array $options = []; } - if (!strpos($table, '.')) { + if (!str_contains($table, '.')) { $result[$table] = $options; continue; } $path = explode('.', $table); $table = array_pop($path); - /** @var string $first */ $first = array_shift($path); + assert(is_string($first)); + $pointer += [$first => []]; $pointer = &$pointer[$first]; $pointer += ['associated' => []]; diff --git a/app/vendor/cakephp/cakephp/src/ORM/Behavior.php b/app/vendor/cakephp/cakephp/src/ORM/Behavior.php index 485770880..5c688409b 100644 --- a/app/vendor/cakephp/cakephp/src/ORM/Behavior.php +++ b/app/vendor/cakephp/cakephp/src/ORM/Behavior.php @@ -21,7 +21,6 @@ use Cake\Event\EventListenerInterface; use ReflectionClass; use ReflectionMethod; -use function Cake\Core\deprecationWarning; /** * Base class for behaviors. @@ -51,11 +50,11 @@ * CakePHP provides a number of lifecycle events your behaviors can * listen to: * - * - `beforeFind(EventInterface $event, Query $query, ArrayObject $options, boolean $primary)` + * - `beforeFind(EventInterface $event, SelectQuery $query, ArrayObject $options, boolean $primary)` * Fired before each find operation. By stopping the event and supplying a * return value you can bypass the find operation entirely. Any changes done * to the $query instance will be retained for the rest of the find. The - * $primary parameter indicates whether this is the root query, + * $primary parameter indicates whether this is the root query * or an associated query. * * - `buildValidator(EventInterface $event, Validator $validator, string $name)` @@ -92,7 +91,7 @@ * event fired from your Table classes including custom application * specific ones. * - * You can set the priority of a behaviors callbacks by using the + * You can set the priority of behaviors' callbacks by using the * `priority` setting when attaching a behavior. This will set the * priority for all the callbacks a behavior provides. * @@ -107,7 +106,7 @@ * methods should expect the following arguments: * * ``` - * findSlugged(Query $query, array $options) + * findSlugged(SelectQuery $query, array $options) * ``` * * @see \Cake\ORM\Table::addBehavior() @@ -122,7 +121,7 @@ class Behavior implements EventListenerInterface * * @var \Cake\ORM\Table */ - protected $_table; + protected Table $_table; /** * Reflection method cache for behaviors. @@ -132,7 +131,7 @@ class Behavior implements EventListenerInterface * * @var array */ - protected static $_reflectionCache = []; + protected static array $_reflectionCache = []; /** * Default configuration @@ -141,7 +140,7 @@ class Behavior implements EventListenerInterface * * @var array */ - protected $_defaultConfig = []; + protected array $_defaultConfig = []; /** * Constructor @@ -156,12 +155,12 @@ public function __construct(Table $table, array $config = []) $config = $this->_resolveMethodAliases( 'implementedFinders', $this->_defaultConfig, - $config + $config, ); $config = $this->_resolveMethodAliases( 'implementedMethods', $this->_defaultConfig, - $config + $config, ); $this->_table = $table; $this->setConfig($config); @@ -181,19 +180,6 @@ public function initialize(array $config): void { } - /** - * Get the table instance this behavior is bound to. - * - * @return \Cake\ORM\Table The bound table instance. - * @deprecated 4.2.0 Use table() instead. - */ - public function getTable(): Table - { - deprecationWarning('Behavior::getTable() is deprecated. Use table() instead.'); - - return $this->table(); - } - /** * Get the table instance this behavior is bound to. * @@ -217,7 +203,7 @@ protected function _resolveMethodAliases(string $key, array $defaults, array $co if (!isset($defaults[$key], $config[$key])) { return $config; } - if (isset($config[$key]) && $config[$key] === []) { + if ($config[$key] === []) { $this->setConfig($key, [], false); unset($config[$key]); @@ -256,9 +242,9 @@ public function verifyConfig(): void foreach ($this->_config[$key] as $method) { if (!is_callable([$this, $method])) { throw new CakeException(sprintf( - 'The method %s is not callable on class %s', + 'The method `%s` is not callable on class `%s`.', $method, - static::class + static::class, )); } } @@ -339,7 +325,7 @@ public function implementedEvents(): array public function implementedFinders(): array { $methods = $this->getConfig('implementedFinders'); - if (isset($methods)) { + if ($methods !== null) { return $methods; } @@ -371,7 +357,7 @@ public function implementedFinders(): array public function implementedMethods(): array { $methods = $this->getConfig('implementedMethods'); - if (isset($methods)) { + if ($methods !== null) { return $methods; } @@ -399,8 +385,8 @@ protected function _reflectionCache(): array $eventMethods = []; foreach ($events as $binding) { if (is_array($binding) && isset($binding['callable'])) { - /** @var string $callable */ $callable = $binding['callable']; + assert(is_string($callable)); $binding = $callable; } $eventMethods[$binding] = true; @@ -430,7 +416,7 @@ protected function _reflectionCache(): array continue; } - if (substr($methodName, 0, 4) === 'find') { + if (str_starts_with($methodName, 'find')) { $return['finders'][lcfirst(substr($methodName, 4))] = $methodName; } else { $return['methods'][$methodName] = $methodName; diff --git a/app/vendor/cakephp/cakephp/src/ORM/Behavior/CounterCacheBehavior.php b/app/vendor/cakephp/cakephp/src/ORM/Behavior/CounterCacheBehavior.php index ce8511469..67464821b 100644 --- a/app/vendor/cakephp/cakephp/src/ORM/Behavior/CounterCacheBehavior.php +++ b/app/vendor/cakephp/cakephp/src/ORM/Behavior/CounterCacheBehavior.php @@ -20,7 +20,9 @@ use Cake\Datasource\EntityInterface; use Cake\Event\EventInterface; use Cake\ORM\Association; +use Cake\ORM\Association\BelongsTo; use Cake\ORM\Behavior; +use Cake\ORM\Query\SelectQuery; use Closure; /** @@ -108,19 +110,19 @@ class CounterCacheBehavior extends Behavior * * @var array> */ - protected $_ignoreDirty = []; + protected array $_ignoreDirty = []; /** * beforeSave callback. * * Check if a field, which should be ignored, is dirty * - * @param \Cake\Event\EventInterface $event The beforeSave event that was fired + * @param \Cake\Event\EventInterface<\Cake\ORM\Table> $event The beforeSave event that was fired * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved - * @param \ArrayObject $options The options for the query + * @param \ArrayObject $options The options for the query * @return void */ - public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options) + public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options): void { if (isset($options['ignoreCounterCache']) && $options['ignoreCounterCache'] === true) { return; @@ -128,6 +130,7 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array foreach ($this->_config as $assoc => $settings) { $assoc = $this->_table->getAssociation($assoc); + /** @var string|int $field */ foreach ($settings as $field => $config) { if (is_int($field)) { continue; @@ -135,12 +138,14 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array $registryAlias = $assoc->getTarget()->getRegistryAlias(); $entityAlias = $assoc->getProperty(); + /** @var \Cake\Datasource\EntityInterface $assocEntity */ + $assocEntity = $entity->$entityAlias; if ( !is_callable($config) && isset($config['ignoreDirty']) && $config['ignoreDirty'] === true && - $entity->$entityAlias->isDirty($field) + $assocEntity->isDirty($field) ) { $this->_ignoreDirty[$registryAlias][$field] = true; } @@ -153,9 +158,9 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array * * Makes sure to update counter cache when a new record is created or updated. * - * @param \Cake\Event\EventInterface $event The afterSave event that was fired. + * @param \Cake\Event\EventInterface<\Cake\ORM\Table> $event The afterSave event that was fired. * @param \Cake\Datasource\EntityInterface $entity The entity that was saved. - * @param \ArrayObject $options The options for the query + * @param \ArrayObject $options The options for the query * @return void */ public function afterSave(EventInterface $event, EntityInterface $entity, ArrayObject $options): void @@ -173,12 +178,12 @@ public function afterSave(EventInterface $event, EntityInterface $entity, ArrayO * * Makes sure to update counter cache when a record is deleted. * - * @param \Cake\Event\EventInterface $event The afterDelete event that was fired. + * @param \Cake\Event\EventInterface<\Cake\ORM\Table> $event The afterDelete event that was fired. * @param \Cake\Datasource\EntityInterface $entity The entity that was deleted. - * @param \ArrayObject $options The options for the query + * @param \ArrayObject $options The options for the query * @return void */ - public function afterDelete(EventInterface $event, EntityInterface $entity, ArrayObject $options) + public function afterDelete(EventInterface $event, EntityInterface $entity, ArrayObject $options): void { if (isset($options['ignoreCounterCache']) && $options['ignoreCounterCache'] === true) { return; @@ -187,10 +192,106 @@ public function afterDelete(EventInterface $event, EntityInterface $entity, Arra $this->_processAssociations($event, $entity); } + /** + * Update counter cache for a batch of records. + * + * Counter caches configured to use closures will not be updated by the method. + * + * @param string|null $assocName The association name to update counter cache for. + * If null, all configured associations will be processed. + * @param int $limit The number of records to update per page/iteration. + * @param int|null $page The page/iteration number. If null (default), all + * records will be updated one page at a time. + * @return void + * @since 5.2.0 + */ + public function updateCounterCache(?string $assocName = null, int $limit = 100, ?int $page = null): void + { + $config = $this->_config; + if ($assocName !== null) { + $config = [$assocName => $config[$assocName]]; + } + + foreach ($config as $assoc => $settings) { + /** @var \Cake\ORM\Association\BelongsTo $belongsTo */ + $belongsTo = $this->_table->getAssociation($assoc); + + foreach ($settings as $field => $config) { + if ($config instanceof Closure) { + // Cannot update counter cache which use a closure + return; + } + + if (is_int($field)) { + $field = $config; + $config = []; + } + + $this->updateCountForAssociation($belongsTo, $field, $config, $limit, $page); + } + } + } + + /** + * Update counter cache for the given association. + * + * @param \Cake\ORM\Association\BelongsTo $assoc The association object. + * @param string $field Counter cache field. + * @param array $config Config array. + * @param int $limit Limit. + * @param int|null $page Page number. + * @return void + */ + protected function updateCountForAssociation( + BelongsTo $assoc, + string $field, + array $config, + int $limit = 100, + ?int $page = null, + ): void { + $primaryKeys = (array)$assoc->getBindingKey(); + /** @var array $foreignKeys */ + $foreignKeys = (array)$assoc->getForeignKey(); + + $query = $assoc->getTarget()->find() + ->select($primaryKeys) + ->limit($limit); + + foreach ($primaryKeys as $key) { + $query->orderByAsc($key); + } + + $singlePage = $page !== null; + $page ??= 1; + + do { + $results = $query + ->page($page++) + ->all(); + + /** @var \Cake\Datasource\EntityInterface $entity */ + foreach ($results as $entity) { + $updateConditions = $entity->extract($primaryKeys); + + foreach ($updateConditions as $f => $value) { + if ($value === null) { + $updateConditions[$f . ' IS'] = $value; + unset($updateConditions[$f]); + } + } + + $countConditions = array_combine($foreignKeys, $updateConditions); + + $count = $this->_getCount($config, $countConditions); + $assoc->getTarget()->updateAll([$field => $count], $updateConditions); + } + } while (!$singlePage && $results->count() === $limit); + } + /** * Iterate all associations and update counter caches. * - * @param \Cake\Event\EventInterface $event Event instance. + * @param \Cake\Event\EventInterface<\Cake\ORM\Table> $event Event instance. * @param \Cake\Datasource\EntityInterface $entity Entity. * @return void */ @@ -205,7 +306,7 @@ protected function _processAssociations(EventInterface $event, EntityInterface $ /** * Updates counter cache for a single association * - * @param \Cake\Event\EventInterface $event Event instance. + * @param \Cake\Event\EventInterface<\Cake\ORM\Table> $event Event instance. * @param \Cake\Datasource\EntityInterface $entity Entity * @param \Cake\ORM\Association $assoc The association object * @param array $settings The settings for counter cache for this association @@ -216,8 +317,9 @@ protected function _processAssociation( EventInterface $event, EntityInterface $entity, Association $assoc, - array $settings + array $settings, ): void { + /** @var array $foreignKeys */ $foreignKeys = (array)$assoc->getForeignKey(); $countConditions = $entity->extract($foreignKeys); @@ -232,6 +334,7 @@ protected function _processAssociation( $updateConditions = array_combine($primaryKeys, $countConditions); $countOriginalConditions = $entity->extractOriginalChanged($foreignKeys); + $updateOriginalConditions = null; if ($countOriginalConditions !== []) { $updateOriginalConditions = array_combine($primaryKeys, $countOriginalConditions); } @@ -260,7 +363,7 @@ protected function _processAssociation( } } - if (isset($updateOriginalConditions) && $this->_shouldUpdateCount($updateOriginalConditions)) { + if ($updateOriginalConditions && $this->_shouldUpdateCount($updateOriginalConditions)) { if ($config instanceof Closure) { $count = $config($event, $entity, $this->_table, true); } else { @@ -279,7 +382,7 @@ protected function _processAssociation( * @param array $conditions Conditions to update count. * @return bool True if the count update should happen, false otherwise. */ - protected function _shouldUpdateCount(array $conditions) + protected function _shouldUpdateCount(array $conditions): bool { return !empty(array_filter($conditions, function ($value) { return $value !== null; @@ -291,9 +394,10 @@ protected function _shouldUpdateCount(array $conditions) * * @param array $config The counter cache configuration for a single field * @param array $conditions Additional conditions given to the query - * @return int The number of relations matching the given config and conditions + * @return \Cake\ORM\Query\SelectQuery|int The query to fetch the number of + * relations matching the given config and conditions or the number itself. */ - protected function _getCount(array $config, array $conditions): int + protected function _getCount(array $config, array $conditions): SelectQuery|int { $finder = 'all'; if (!empty($config['finder'])) { @@ -302,8 +406,14 @@ protected function _getCount(array $config, array $conditions): int } $config['conditions'] = array_merge($conditions, $config['conditions'] ?? []); - $query = $this->_table->find($finder, $config); + $query = $this->_table->find($finder, ...$config); + + if (isset($config['useSubQuery']) && $config['useSubQuery'] === false) { + return $query->count(); + } - return $query->count(); + return $query + ->select(['count' => $query->func()->count('*')], true) + ->orderBy([], true); } } diff --git a/app/vendor/cakephp/cakephp/src/ORM/Behavior/TimestampBehavior.php b/app/vendor/cakephp/cakephp/src/ORM/Behavior/TimestampBehavior.php index 57e3ec4e9..99932bae5 100644 --- a/app/vendor/cakephp/cakephp/src/ORM/Behavior/TimestampBehavior.php +++ b/app/vendor/cakephp/cakephp/src/ORM/Behavior/TimestampBehavior.php @@ -20,10 +20,9 @@ use Cake\Database\TypeFactory; use Cake\Datasource\EntityInterface; use Cake\Event\EventInterface; -use Cake\I18n\FrozenTime; +use Cake\I18n\DateTime; use Cake\ORM\Behavior; use DateTimeInterface; -use RuntimeException; use UnexpectedValueException; /** @@ -46,7 +45,7 @@ class TimestampBehavior extends Behavior * * @var array */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'implementedFinders' => [], 'implementedMethods' => [ 'timestamp' => 'timestamp', @@ -64,9 +63,9 @@ class TimestampBehavior extends Behavior /** * Current timestamp * - * @var \Cake\I18n\FrozenTime|null + * @var \Cake\I18n\DateTime|null */ - protected $_ts; + protected ?DateTime $_ts = null; /** * Initialize hook @@ -87,25 +86,25 @@ public function initialize(array $config): void /** * There is only one event handler, it can be configured to be called for any event * - * @param \Cake\Event\EventInterface $event Event instance. + * @param \Cake\Event\EventInterface<\Cake\ORM\Table> $event Event instance. * @param \Cake\Datasource\EntityInterface $entity Entity instance. - * @throws \UnexpectedValueException if a field's when value is misdefined - * @return true Returns true irrespective of the behavior logic, the save will not be prevented. - * @throws \UnexpectedValueException When the value for an event is not 'always', 'new' or 'existing' + * @throws \UnexpectedValueException If a field's value is misdefined. + * @throws \UnexpectedValueException When the value for an event is not 'always', 'new' or 'existing'. + * @return void */ - public function handleEvent(EventInterface $event, EntityInterface $entity): bool + public function handleEvent(EventInterface $event, EntityInterface $entity): void { $eventName = $event->getName(); $events = $this->_config['events']; - $new = $entity->isNew() !== false; + $new = $entity->isNew(); $refresh = $this->_config['refreshTimestamp']; foreach ($events[$eventName] as $field => $when) { if (!in_array($when, ['always', 'new', 'existing'], true)) { throw new UnexpectedValueException(sprintf( - 'When should be one of "always", "new" or "existing". The passed value "%s" is invalid', - $when + 'When should be one of "always", "new" or "existing". The passed value `%s` is invalid.', + $when, )); } if ( @@ -122,8 +121,6 @@ public function handleEvent(EventInterface $event, EntityInterface $entity): boo $this->_updateField($entity, $field, $refresh); } } - - return true; } /** @@ -135,6 +132,7 @@ public function handleEvent(EventInterface $event, EntityInterface $entity): boo */ public function implementedEvents(): array { + /** @var array */ return array_fill_keys(array_keys($this->_config['events']), 'handleEvent'); } @@ -147,17 +145,17 @@ public function implementedEvents(): array * * @param \DateTimeInterface|null $ts Timestamp * @param bool $refreshTimestamp If true timestamp is refreshed. - * @return \Cake\I18n\FrozenTime + * @return \Cake\I18n\DateTime */ - public function timestamp(?DateTimeInterface $ts = null, bool $refreshTimestamp = false): DateTimeInterface + public function timestamp(?DateTimeInterface $ts = null, bool $refreshTimestamp = false): DateTime { if ($ts) { if ($this->_config['refreshTimestamp']) { $this->_config['refreshTimestamp'] = false; } - $this->_ts = new FrozenTime($ts); + $this->_ts = new DateTime($ts); } elseif ($this->_ts === null || $refreshTimestamp) { - $this->_ts = new FrozenTime(); + $this->_ts = new DateTime(); } return $this->_ts; @@ -216,13 +214,13 @@ protected function _updateField(EntityInterface $entity, string $field, bool $re return; } - /** @var \Cake\Database\Type\DateTimeType $type */ $type = TypeFactory::build($columnType); + assert( + $type instanceof DateTimeType, + sprintf('TimestampBehavior only supports columns of type `%s`.', DateTimeType::class), + ); - if (!$type instanceof DateTimeType) { - throw new RuntimeException('TimestampBehavior only supports columns of type DateTimeType.'); - } - + /** @var class-string<\Cake\I18n\DateTime> $class */ $class = $type->getDateTimeClassName(); $entity->set($field, new $class($ts)); diff --git a/app/vendor/cakephp/cakephp/src/ORM/Behavior/Translate/EavStrategy.php b/app/vendor/cakephp/cakephp/src/ORM/Behavior/Translate/EavStrategy.php index 4b1e89ec4..cc357d978 100644 --- a/app/vendor/cakephp/cakephp/src/ORM/Behavior/Translate/EavStrategy.php +++ b/app/vendor/cakephp/cakephp/src/ORM/Behavior/Translate/EavStrategy.php @@ -22,9 +22,8 @@ use Cake\Core\InstanceConfigTrait; use Cake\Datasource\EntityInterface; use Cake\Event\EventInterface; -use Cake\ORM\Entity; use Cake\ORM\Locator\LocatorAwareTrait; -use Cake\ORM\Query; +use Cake\ORM\Query\SelectQuery; use Cake\ORM\Table; use Cake\Utility\Hash; @@ -54,7 +53,7 @@ class EavStrategy implements TranslateStrategyInterface * * @var array */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'fields' => [], 'translationTable' => 'I18n', 'defaultLocale' => null, @@ -82,7 +81,7 @@ public function __construct(Table $table, array $config = []) $this->table = $table; $this->translationTable = $this->getTableLocator()->get( $this->_config['translationTable'], - ['allowFallbackClass' => true] + ['allowFallbackClass' => true], ); $this->setupAssociations(); @@ -97,7 +96,7 @@ public function __construct(Table $table, array $config = []) * * @return void */ - protected function setupAssociations() + protected function setupAssociations(): void { $fields = $this->_config['fields']; $table = $this->_config['translationTable']; @@ -131,20 +130,27 @@ protected function setupAssociations() $conditions[$name . '.content !='] = ''; } + if ($this->table->associations()->has($name)) { + $this->table->associations()->remove($name); + } + $this->table->hasOne($name, [ 'targetTable' => $fieldTable, 'foreignKey' => 'foreign_key', - 'joinType' => $filter ? Query::JOIN_TYPE_INNER : Query::JOIN_TYPE_LEFT, + 'joinType' => $filter ? SelectQuery::JOIN_TYPE_INNER : SelectQuery::JOIN_TYPE_LEFT, 'conditions' => $conditions, 'propertyName' => $field . '_translation', ]); } - $conditions = ["$targetAlias.model" => $model]; + $conditions = ["{$targetAlias}.model" => $model]; if (!$this->_config['allowEmptyTranslations']) { - $conditions["$targetAlias.content !="] = ''; + $conditions["{$targetAlias}.content !="] = ''; } + if ($this->table->associations()->has($targetAlias)) { + $this->table->associations()->remove($targetAlias); + } $this->table->hasMany($targetAlias, [ 'className' => $table, 'foreignKey' => 'foreign_key', @@ -160,12 +166,12 @@ protected function setupAssociations() * table. It modifies the passed query by eager loading the translated fields * and adding a formatter to copy the values into the main table records. * - * @param \Cake\Event\EventInterface $event The beforeFind event that was fired. - * @param \Cake\ORM\Query $query Query - * @param \ArrayObject $options The options for the query + * @param \Cake\Event\EventInterface<\Cake\ORM\Table> $event The beforeFind event that was fired. + * @param \Cake\ORM\Query\SelectQuery $query Query + * @param \ArrayObject $options The options for the query * @return void */ - public function beforeFind(EventInterface $event, Query $query, ArrayObject $options) + public function beforeFind(EventInterface $event, SelectQuery $query, ArrayObject $options): void { $locale = Hash::get($options, 'locale', $this->getLocale()); @@ -173,9 +179,10 @@ public function beforeFind(EventInterface $event, Query $query, ArrayObject $opt return; } - $conditions = function ($field, $locale, $query, $select) { - return function ($q) use ($field, $locale, $query, $select) { - $q->where([$q->getRepository()->aliasField('locale') => $locale]); + $conditions = function (string $field, string $locale, SelectQuery $query, array $select) { + return function (SelectQuery $q) use ($field, $locale, $query, $select) { + $table = $q->getRepository(); + $q->where([$table->aliasField('locale') => $locale]); if ( $query->isAutoFieldsEnabled() || @@ -204,35 +211,36 @@ public function beforeFind(EventInterface $event, Query $query, ArrayObject $opt $field, $locale, $query, - $select + $select, ); if ($changeFilter) { $filter = $options['filterByCurrentLocale'] - ? Query::JOIN_TYPE_INNER - : Query::JOIN_TYPE_LEFT; + ? SelectQuery::JOIN_TYPE_INNER + : SelectQuery::JOIN_TYPE_LEFT; $contain[$name]['joinType'] = $filter; } } $query->contain($contain); - $query->formatResults(function ($results) use ($locale) { - return $this->rowMapper($results, $locale); - }, $query::PREPEND); + $query->formatResults( + fn(CollectionInterface $results) => $this->rowMapper($results, $locale), + $query::PREPEND, + ); } /** * Modifies the entity before it is saved so that translated fields are persisted * in the database too. * - * @param \Cake\Event\EventInterface $event The beforeSave event that was fired + * @param \Cake\Event\EventInterface<\Cake\ORM\Table> $event The beforeSave event that was fired * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved - * @param \ArrayObject $options the options passed to the save method + * @param \ArrayObject $options the options passed to the save method * @return void */ - public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options) + public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options): void { - $locale = $entity->get('_locale') ?: $this->getLocale(); + $locale = $entity->has('_locale') ? $entity->get('_locale') : $this->getLocale(); $newOptions = [$this->translationTable->getAlias() => ['validate' => false]]; $options['associated'] = $newOptions + $options['associated']; @@ -244,7 +252,7 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array } $this->bundleTranslatedFields($entity); - $bundled = $entity->get('_i18n') ?: []; + $bundled = $entity->has('_i18n') ? $entity->get('_i18n') : []; $noBundled = count($bundled) === 0; // No additional translation records need to be saved, @@ -255,17 +263,18 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array $values = $entity->extract($this->_config['fields'], true); $fields = array_keys($values); - $noFields = empty($fields); + $noFields = $fields === []; // If there are no fields and no bundled translations, or both fields // in the default locale and bundled translations we can - // skip the remaining logic as its not necessary. + // skip the remaining logic as it is not necessary. if ($noFields && $noBundled || ($fields && $bundled)) { return; } - $primaryKey = (array)$this->table->getPrimaryKey(); - $key = $entity->get(current($primaryKey)); + /** @var string $primaryKey */ + $primaryKey = current((array)$this->table->getPrimaryKey()); + $key = $entity->has($primaryKey) ? $entity->get($primaryKey) : null; // When we have no key and bundled translations, we // need to mark the entity dirty so the root @@ -286,6 +295,7 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array $preexistent = []; if ($key) { + /** @var \Traversable $preexistent */ $preexistent = $this->translationTable->find() ->select(['id', 'field']) ->where([ @@ -294,7 +304,6 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array 'foreign_key' => $key, 'model' => $model, ]) - ->disableBufferedResults() ->all() ->indexBy('field'); } @@ -305,9 +314,10 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array $modified[$field] = $translation; } + $entityClass = $this->translationTable->getEntityClass(); $new = array_diff_key($values, $modified); foreach ($new as $field => $content) { - $new[$field] = new Entity(compact('locale', 'field', 'content', 'model'), [ + $new[$field] = new $entityClass(compact('locale', 'field', 'content', 'model'), [ 'useSetters' => false, 'markNew' => true, ]); @@ -351,18 +361,18 @@ public function translationField(string $field): string * Modifies the results from a table find in order to merge the translated fields * into each entity for a given locale. * - * @param \Cake\Datasource\ResultSetInterface $results Results to map. + * @param \Cake\Collection\CollectionInterface $results Results to map. * @param string $locale Locale string * @return \Cake\Collection\CollectionInterface */ - protected function rowMapper($results, $locale) + protected function rowMapper(CollectionInterface $results, string $locale): CollectionInterface { return $results->map(function ($row) use ($locale) { /** @var \Cake\Datasource\EntityInterface|array|null $row */ if ($row === null) { return $row; } - $hydrated = !is_array($row); + $hydrated = $row instanceof EntityInterface; foreach ($this->_config['fields'] as $field) { $name = $field . '_translation'; @@ -376,6 +386,11 @@ protected function rowMapper($results, $locale) $content = $translation['content'] ?? null; if ($content !== null) { $row[$field] = $content; + + if ($hydrated) { + /** @var \Cake\Datasource\EntityInterface $row */ + $row->setDirty($field, false); + } } unset($row[$name]); @@ -383,8 +398,8 @@ protected function rowMapper($results, $locale) $row['_locale'] = $locale; if ($hydrated) { - /** @psalm-suppress PossiblyInvalidMethodCall */ - $row->clean(); + /** @var \Cake\Datasource\EntityInterface $row */ + $row->setDirty('_locale', false); } return $row; @@ -395,24 +410,34 @@ protected function rowMapper($results, $locale) * Modifies the results from a table find in order to merge full translation * records into each entity under the `_translations` key. * - * @param \Cake\Datasource\ResultSetInterface $results Results to modify. + * @param \Cake\Collection\CollectionInterface $results Results to modify. * @return \Cake\Collection\CollectionInterface */ - public function groupTranslations($results): CollectionInterface + public function groupTranslations(CollectionInterface $results): CollectionInterface { return $results->map(function ($row) { if (!$row instanceof EntityInterface) { return $row; } - $translations = (array)$row->get('_i18n'); - if (empty($translations) && $row->get('_translations')) { + + $translations = $row->has('_i18n') ? $row->get('_i18n') : []; + if ($translations === []) { + if ($row->has('_translations')) { + return $row; + } + + $row->set('_translations', []) + ->setDirty('_translations', false); + unset($row['_i18n']); + return $row; } + $grouped = new Collection($translations); + $entityClass = $this->table->getEntityClass(); $result = []; foreach ($grouped->combine('field', 'content', 'locale') as $locale => $keys) { - $entityClass = $this->table->getEntityClass(); $translation = new $entityClass($keys + ['locale' => $locale], [ 'markNew' => false, 'useSetters' => false, @@ -421,10 +446,9 @@ public function groupTranslations($results): CollectionInterface $result[$locale] = $translation; } - $options = ['setter' => false, 'guard' => false]; - $row->set('_translations', $result, $options); + $row->set('_translations', $result, ['setter' => false, 'guard' => false]) + ->setDirty('_translations', false); unset($row['_i18n']); - $row->clean(); return $row; }); @@ -438,19 +462,25 @@ public function groupTranslations($results): CollectionInterface * @param \Cake\Datasource\EntityInterface $entity Entity * @return void */ - protected function bundleTranslatedFields($entity) + protected function bundleTranslatedFields(EntityInterface $entity): void { - $translations = (array)$entity->get('_translations'); + /** @var array $translations */ + $translations = $entity->has('_translations') ? (array)$entity->get('_translations') : []; - if (empty($translations) && !$entity->isDirty('_translations')) { + if (!$translations && !$entity->isDirty('_translations')) { return; } $fields = $this->_config['fields']; - $primaryKey = (array)$this->table->getPrimaryKey(); - $key = $entity->get(current($primaryKey)); + if ($entity->isNew()) { + $key = null; + } else { + $primaryKey = (array)$this->table->getPrimaryKey(); + $key = $entity->get((string)current($primaryKey)); + } $find = []; $contents = []; + $entityClass = $this->translationTable->getEntityClass(); foreach ($translations as $lang => $translation) { foreach ($fields as $field) { @@ -458,13 +488,13 @@ protected function bundleTranslatedFields($entity) continue; } $find[] = ['locale' => $lang, 'field' => $field, 'foreign_key IS' => $key]; - $contents[] = new Entity(['content' => $translation->get($field)], [ + $contents[] = new $entityClass(['content' => $translation->get($field)], [ 'useSetters' => false, ]); } } - if (empty($find)) { + if (!$find) { return; } @@ -476,7 +506,12 @@ protected function bundleTranslatedFields($entity) $contents[$i]->setNew(false); } else { $translation['model'] = $this->_config['referenceName']; - $contents[$i]->set($translation, ['setter' => false, 'guard' => false]); + unset($translation['foreign_key IS']); + if (method_exists($contents[$i], 'patch')) { + $contents[$i]->patch($translation, ['setter' => false, 'guard' => false]); + } else { + $contents[$i]->set($translation, ['setter' => false, 'guard' => false]); + } $contents[$i]->setNew(true); } } @@ -492,15 +527,14 @@ protected function bundleTranslatedFields($entity) * @param array $ruleSet An array of array of conditions to be used for finding each * @return array */ - protected function findExistingTranslations($ruleSet) + protected function findExistingTranslations(array $ruleSet): array { $association = $this->table->getAssociation($this->translationTable->getAlias()); $query = $association->find() ->select(['id', 'num' => 0]) ->where(current($ruleSet)) - ->disableHydration() - ->disableBufferedResults(); + ->disableHydration(); unset($ruleSet[0]); foreach ($ruleSet as $i => $conditions) { diff --git a/app/vendor/cakephp/cakephp/src/ORM/Behavior/Translate/ShadowTableStrategy.php b/app/vendor/cakephp/cakephp/src/ORM/Behavior/Translate/ShadowTableStrategy.php index 2408090b1..4d7873785 100644 --- a/app/vendor/cakephp/cakephp/src/ORM/Behavior/Translate/ShadowTableStrategy.php +++ b/app/vendor/cakephp/cakephp/src/ORM/Behavior/Translate/ShadowTableStrategy.php @@ -20,11 +20,12 @@ use Cake\Collection\CollectionInterface; use Cake\Core\InstanceConfigTrait; use Cake\Database\Expression\FieldInterface; +use Cake\Database\Expression\QueryExpression; use Cake\Datasource\EntityInterface; use Cake\Event\EventInterface; use Cake\ORM\Locator\LocatorAwareTrait; use Cake\ORM\Marshaller; -use Cake\ORM\Query; +use Cake\ORM\Query\SelectQuery; use Cake\ORM\Table; use Cake\Utility\Hash; use function Cake\Core\pluginSplit; @@ -48,7 +49,7 @@ class ShadowTableStrategy implements TranslateStrategyInterface * * @var array */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'fields' => [], 'defaultLocale' => null, 'referenceName' => null, @@ -85,7 +86,7 @@ public function __construct(Table $table, array $config = []) $this->table = $table; $this->translationTable = $this->getTableLocator()->get( $this->_config['translationTable'], - ['allowFallbackClass' => true] + ['allowFallbackClass' => true], ); $this->setupAssociations(); @@ -99,11 +100,16 @@ public function __construct(Table $table, array $config = []) * * @return void */ - protected function setupAssociations() + protected function setupAssociations(): void { $config = $this->getConfig(); $targetAlias = $this->translationTable->getAlias(); + + if ($this->table->associations()->has($targetAlias)) { + $this->table->associations()->remove($targetAlias); + } + $this->table->hasMany($targetAlias, [ 'className' => $config['translationTable'], 'foreignKey' => 'id', @@ -118,12 +124,12 @@ protected function setupAssociations() * table. It modifies the passed query by eager loading the translated fields * and adding a formatter to copy the values into the main table records. * - * @param \Cake\Event\EventInterface $event The beforeFind event that was fired. - * @param \Cake\ORM\Query $query Query. - * @param \ArrayObject $options The options for the query. + * @param \Cake\Event\EventInterface<\Cake\ORM\Table> $event The beforeFind event that was fired. + * @param \Cake\ORM\Query\SelectQuery $query Query. + * @param \ArrayObject $options The options for the query. * @return void */ - public function beforeFind(EventInterface $event, Query $query, ArrayObject $options) + public function beforeFind(EventInterface $event, SelectQuery $query, ArrayObject $options): void { $locale = Hash::get($options, 'locale', $this->getLocale()); $config = $this->getConfig(); @@ -147,16 +153,17 @@ public function beforeFind(EventInterface $event, Query $query, ArrayObject $opt $query->contain([$config['hasOneAlias']]); - $query->formatResults(function ($results) use ($locale) { - return $this->rowMapper($results, $locale); - }, $query::PREPEND); + $query->formatResults( + fn(CollectionInterface $results) => $this->rowMapper($results, $locale), + $query::PREPEND, + ); } /** * Create a hasOne association for record with required locale. * * @param string $locale Locale - * @param \ArrayObject $options Find options + * @param \ArrayObject $options Find options * @return void */ protected function setupHasOneAssociation(string $locale, ArrayObject $options): void @@ -172,7 +179,7 @@ protected function setupHasOneAssociation(string $locale, ArrayObject $options): [ 'className' => $config['translationTable'], 'allowFallbackClass' => true, - ] + ], ); } @@ -182,6 +189,10 @@ protected function setupHasOneAssociation(string $locale, ArrayObject $options): $joinType = $config['onlyTranslated'] ? 'INNER' : 'LEFT'; } + if ($this->table->associations()->has($config['hasOneAlias'])) { + $this->table->associations()->remove($config['hasOneAlias']); + } + $this->table->hasOne($config['hasOneAlias'], [ 'foreignKey' => ['id'], 'joinType' => $joinType, @@ -202,11 +213,11 @@ protected function setupHasOneAssociation(string $locale, ArrayObject $options): * Only add translations for fields that are in the main table, always * add the locale field though. * - * @param \Cake\ORM\Query $query The query to check. + * @param \Cake\ORM\Query\SelectQuery $query The query to check. * @param array $config The config to use for adding fields. * @return bool Whether a join to the translation table is required. */ - protected function addFieldsToQuery($query, array $config) + protected function addFieldsToQuery(SelectQuery $query, array $config): bool { if ($query->isAutoFieldsEnabled()) { return true; @@ -223,7 +234,7 @@ protected function addFieldsToQuery($query, array $config) $alias = $config['mainTableAlias']; $joinRequired = false; foreach ($this->translatedFields() as $field) { - if (array_intersect($select, [$field, "$alias.$field"])) { + if (array_intersect($select, [$field, "{$alias}.{$field}"])) { $joinRequired = true; $query->select($query->aliasField($field, $config['hasOneAlias'])); } @@ -243,14 +254,15 @@ protected function addFieldsToQuery($query, array $config) * prefixing fields with the appropriate table alias. This method currently * expects to receive an order clause only. * - * @param \Cake\ORM\Query $query the query to check. + * @param \Cake\ORM\Query\SelectQuery $query the query to check. * @param string $name The clause name. * @param array $config The config to use for adding fields. * @return bool Whether a join to the translation table is required. */ - protected function iterateClause($query, $name = '', $config = []): bool + protected function iterateClause(SelectQuery $query, string $name = '', array $config = []): bool { $clause = $query->clause($name); + assert($clause === null || $clause instanceof QueryExpression); if (!$clause || !$clause->count()) { return false; } @@ -263,19 +275,19 @@ protected function iterateClause($query, $name = '', $config = []): bool $clause->iterateParts( function ($c, &$field) use ($fields, $alias, $mainTableAlias, $mainTableFields, &$joinRequired) { - if (!is_string($field) || strpos($field, '.')) { + if (!is_string($field) || str_contains($field, '.')) { return $c; } if (in_array($field, $fields, true)) { $joinRequired = true; - $field = "$alias.$field"; + $field = "{$alias}.{$field}"; } elseif (in_array($field, $mainTableFields, true)) { - $field = "$mainTableAlias.$field"; + $field = "{$mainTableAlias}.{$field}"; } return $c; - } + }, ); return $joinRequired; @@ -288,13 +300,14 @@ function ($c, &$field) use ($fields, $alias, $mainTableAlias, $mainTableFields, * prefixing fields with the appropriate table alias. This method currently * expects to receive a where clause only. * - * @param \Cake\ORM\Query $query the query to check. + * @param \Cake\ORM\Query\SelectQuery $query the query to check. * @param string $name The clause name. * @param array $config The config to use for adding fields. * @return bool Whether a join to the translation table is required. */ - protected function traverseClause($query, $name = '', $config = []): bool + protected function traverseClause(SelectQuery $query, string $name = '', array $config = []): bool { + /** @var \Cake\Database\Expression\QueryExpression|null $clause */ $clause = $query->clause($name); if (!$clause || !$clause->count()) { return false; @@ -307,26 +320,26 @@ protected function traverseClause($query, $name = '', $config = []): bool $joinRequired = false; $clause->traverse( - function ($expression) use ($fields, $alias, $mainTableAlias, $mainTableFields, &$joinRequired) { + function ($expression) use ($fields, $alias, $mainTableAlias, $mainTableFields, &$joinRequired): void { if (!($expression instanceof FieldInterface)) { return; } $field = $expression->getField(); - if (!is_string($field) || strpos($field, '.')) { + if (!is_string($field) || str_contains($field, '.')) { return; } if (in_array($field, $fields, true)) { $joinRequired = true; - $expression->setField("$alias.$field"); + $expression->setField("{$alias}.{$field}"); return; } if (in_array($field, $mainTableFields, true)) { - $expression->setField("$mainTableAlias.$field"); + $expression->setField("{$mainTableAlias}.{$field}"); } - } + }, ); return $joinRequired; @@ -336,14 +349,14 @@ function ($expression) use ($fields, $alias, $mainTableAlias, $mainTableFields, * Modifies the entity before it is saved so that translated fields are persisted * in the database too. * - * @param \Cake\Event\EventInterface $event The beforeSave event that was fired. + * @param \Cake\Event\EventInterface<\Cake\ORM\Table> $event The beforeSave event that was fired. * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved. - * @param \ArrayObject $options the options passed to the save method. + * @param \ArrayObject $options the options passed to the save method. * @return void */ - public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options) + public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options): void { - $locale = $entity->get('_locale') ?: $this->getLocale(); + $locale = $entity->has('_locale') ? $entity->get('_locale') : $this->getLocale(); $newOptions = [$this->translationTable->getAlias() => ['validate' => false]]; $options['associated'] = $newOptions + $options['associated']; @@ -355,7 +368,7 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array } $this->bundleTranslatedFields($entity); - $bundled = $entity->get('_i18n') ?: []; + $bundled = $entity->has('_i18n') ? (array)$entity->get('_i18n') : []; $noBundled = count($bundled) === 0; // No additional translation records need to be saved, @@ -366,17 +379,18 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array $values = $entity->extract($this->translatedFields(), true); $fields = array_keys($values); - $noFields = empty($fields); + $noFields = $fields === []; // If there are no fields and no bundled translations, or both fields // in the default locale and bundled translations we can - // skip the remaining logic as its not necessary. + // skip the remaining logic as it is not necessary. if ($noFields && $noBundled || ($fields && $bundled)) { return; } - $primaryKey = (array)$this->table->getPrimaryKey(); - $id = $entity->get(current($primaryKey)); + /** @var string $primaryKey */ + $primaryKey = current((array)$this->table->getPrimaryKey()); + $id = $entity->has($primaryKey) ? $entity->get($primaryKey) : null; // When we have no key and bundled translations, we // need to mark the entity dirty so the root @@ -402,19 +416,22 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array $translation = $this->translationTable->find() ->select(array_merge(['id', 'locale'], $fields)) ->where($where) - ->disableBufferedResults() ->first(); } if ($translation) { - $translation->set($values); + if (method_exists($translation, 'patch')) { + $translation->patch($values); + } else { + $translation->set($values); + } } else { - $translation = $this->translationTable->newEntity( + $translation = new ($this->translationTable->getEntityClass())( $where + $values, [ 'useSetters' => false, 'markNew' => true, - ] + ], ); } @@ -465,42 +482,43 @@ public function translationField(string $field): string * Modifies the results from a table find in order to merge the translated * fields into each entity for a given locale. * - * @param \Cake\Datasource\ResultSetInterface $results Results to map. + * @param \Cake\Collection\CollectionInterface $results Results to map. * @param string $locale Locale string * @return \Cake\Collection\CollectionInterface */ - protected function rowMapper($results, $locale) + protected function rowMapper(CollectionInterface $results, string $locale): CollectionInterface { $allowEmpty = $this->_config['allowEmptyTranslations']; return $results->map(function ($row) use ($allowEmpty, $locale) { - /** @var \Cake\Datasource\EntityInterface|array|null $row */ if ($row === null) { return $row; } - $hydrated = !is_array($row); + $hydrated = $row instanceof EntityInterface; if (empty($row['translation'])) { $row['_locale'] = $locale; unset($row['translation']); if ($hydrated) { - /** @psalm-suppress PossiblyInvalidMethodCall */ - $row->clean(); + /** @var \Cake\Datasource\EntityInterface $row */ + $row->setDirty('_locale', false); } return $row; } - /** @var \Cake\ORM\Entity|array $translation */ $translation = $row['translation']; + assert($translation instanceof EntityInterface || is_array($translation)); - /** - * @psalm-suppress PossiblyInvalidMethodCall - * @psalm-suppress PossiblyInvalidArgument - */ - $keys = $hydrated ? $translation->getVisible() : array_keys($translation); + if ($hydrated) { + /** @var \Cake\Datasource\EntityInterface $translation */ + $keys = $translation->getVisible(); + } else { + /** @var non-empty-array $translation */ + $keys = array_keys($translation); + } foreach ($keys as $field) { if ($field === 'locale') { @@ -508,18 +526,21 @@ protected function rowMapper($results, $locale) continue; } - if ($translation[$field] !== null) { - if ($allowEmpty || $translation[$field] !== '') { - $row[$field] = $translation[$field]; + if ($translation[$field] !== null && ($allowEmpty || $translation[$field] !== '')) { + $row[$field] = $translation[$field]; + if ($hydrated) { + /** @var \Cake\Datasource\EntityInterface $row */ + $row->setDirty($field, false); } } } + /** @var array $row */ unset($row['translation']); if ($hydrated) { - /** @psalm-suppress PossiblyInvalidMethodCall */ - $row->clean(); + /** @var \Cake\Datasource\EntityInterface $row */ + $row->setDirty('_locale', false); } return $row; @@ -530,17 +551,26 @@ protected function rowMapper($results, $locale) * Modifies the results from a table find in order to merge full translation * records into each entity under the `_translations` key. * - * @param \Cake\Datasource\ResultSetInterface $results Results to modify. + * @param \Cake\Collection\CollectionInterface $results Results to modify. * @return \Cake\Collection\CollectionInterface */ - public function groupTranslations($results): CollectionInterface + public function groupTranslations(CollectionInterface $results): CollectionInterface { return $results->map(function ($row) { - if (!($row instanceof EntityInterface)) { + if (!$row instanceof EntityInterface) { return $row; } - $translations = (array)$row->get('_i18n'); - if (empty($translations) && $row->get('_translations')) { + + $translations = $row->has('_i18n') ? $row->get('_i18n') : []; + if ($translations === []) { + if ($row->has('_translations')) { + return $row; + } + + $row->set('_translations', []) + ->setDirty('_translations', false); + unset($row['_i18n']); + return $row; } @@ -550,11 +580,9 @@ public function groupTranslations($results): CollectionInterface $result[$translation['locale']] = $translation; } - $row['_translations'] = $result; + $row->set('_translations', $result) + ->setDirty('_translations', false); unset($row['_i18n']); - if ($row instanceof EntityInterface) { - $row->clean(); - } return $row; }); @@ -568,24 +596,35 @@ public function groupTranslations($results): CollectionInterface * @param \Cake\Datasource\EntityInterface $entity Entity. * @return void */ - protected function bundleTranslatedFields($entity) + protected function bundleTranslatedFields(EntityInterface $entity): void { - $translations = (array)$entity->get('_translations'); + /** @var array $translations */ + $translations = $entity->has('_translations') ? (array)$entity->get('_translations') : []; - if (empty($translations) && !$entity->isDirty('_translations')) { + if (!$translations && !$entity->isDirty('_translations')) { return; } - $primaryKey = (array)$this->table->getPrimaryKey(); - $key = $entity->get(current($primaryKey)); + if ($entity->isNew()) { + $key = null; + } else { + $primaryKey = (array)$this->table->getPrimaryKey(); + $key = $entity->get((string)current($primaryKey)); + } foreach ($translations as $lang => $translation) { - if (!$translation->id) { + if ($translation->isNew()) { $update = [ - 'id' => $key, 'locale' => $lang, ]; - $translation->set($update, ['guard' => false]); + if ($key !== null) { + $update['id'] = $key; + } + if (method_exists($translation, 'patch')) { + $translation->patch($update, ['guard' => false]); + } else { + $translation->set($update, ['guard' => false]); + } } } @@ -597,8 +636,9 @@ protected function bundleTranslatedFields($entity) * * @return array */ - protected function mainFields() + protected function mainFields(): array { + /** @var array $fields */ $fields = $this->getConfig('mainTableFields'); if ($fields) { @@ -617,7 +657,7 @@ protected function mainFields() * * @return array */ - protected function translatedFields() + protected function translatedFields(): array { $fields = $this->getConfig('fields'); diff --git a/app/vendor/cakephp/cakephp/src/ORM/Behavior/Translate/TranslateStrategyInterface.php b/app/vendor/cakephp/cakephp/src/ORM/Behavior/Translate/TranslateStrategyInterface.php index 41536e6c4..7d36e11ff 100644 --- a/app/vendor/cakephp/cakephp/src/ORM/Behavior/Translate/TranslateStrategyInterface.php +++ b/app/vendor/cakephp/cakephp/src/ORM/Behavior/Translate/TranslateStrategyInterface.php @@ -19,9 +19,10 @@ use ArrayObject; use Cake\Collection\CollectionInterface; use Cake\Datasource\EntityInterface; +use Cake\Datasource\ResultSetInterface; use Cake\Event\EventInterface; use Cake\ORM\PropertyMarshalInterface; -use Cake\ORM\Query; +use Cake\ORM\Query\SelectQuery; use Cake\ORM\Table; /** @@ -48,7 +49,7 @@ public function getTranslationTable(): Table; * * @param string|null $locale The locale to use for fetching and saving * records. Pass `null` in order to unset the current locale, and to make - * the behavior fall back to using the globally configured locale. + * the behavior falls back to using the globally configured locale. * @return $this */ public function setLocale(?string $locale); @@ -79,40 +80,40 @@ public function translationField(string $field): string; * Modifies the results from a table find in order to merge full translation records * into each entity under the `_translations` key * - * @param \Cake\Datasource\ResultSetInterface $results Results to modify. + * @param \Cake\Datasource\ResultSetInterface<\Cake\Datasource\EntityInterface|array> $results Results to modify. * @return \Cake\Collection\CollectionInterface */ - public function groupTranslations($results): CollectionInterface; + public function groupTranslations(ResultSetInterface $results): CollectionInterface; /** * Callback method that listens to the `beforeFind` event in the bound * table. It modifies the passed query by eager loading the translated fields * and adding a formatter to copy the values into the main table records. * - * @param \Cake\Event\EventInterface $event The beforeFind event that was fired. - * @param \Cake\ORM\Query $query Query - * @param \ArrayObject $options The options for the query + * @param \Cake\Event\EventInterface<\Cake\ORM\Table> $event The beforeFind event that was fired. + * @param \Cake\ORM\Query\SelectQuery $query Query + * @param \ArrayObject $options The options for the query * @return void */ - public function beforeFind(EventInterface $event, Query $query, ArrayObject $options); + public function beforeFind(EventInterface $event, SelectQuery $query, ArrayObject $options): void; /** * Modifies the entity before it is saved so that translated fields are persisted * in the database too. * - * @param \Cake\Event\EventInterface $event The beforeSave event that was fired + * @param \Cake\Event\EventInterface<\Cake\ORM\Table> $event The beforeSave event that was fired * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved - * @param \ArrayObject $options the options passed to the save method + * @param \ArrayObject $options the options passed to the save method * @return void */ - public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options); + public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options): void; /** * Unsets the temporary `_i18n` property after the entity has been saved * - * @param \Cake\Event\EventInterface $event The beforeSave event that was fired + * @param \Cake\Event\EventInterface<\Cake\ORM\Table> $event The beforeSave event that was fired * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved * @return void */ - public function afterSave(EventInterface $event, EntityInterface $entity); + public function afterSave(EventInterface $event, EntityInterface $entity): void; } diff --git a/app/vendor/cakephp/cakephp/src/ORM/Behavior/Translate/TranslateStrategyTrait.php b/app/vendor/cakephp/cakephp/src/ORM/Behavior/Translate/TranslateStrategyTrait.php index bdfbb9b8d..197da6400 100644 --- a/app/vendor/cakephp/cakephp/src/ORM/Behavior/Translate/TranslateStrategyTrait.php +++ b/app/vendor/cakephp/cakephp/src/ORM/Behavior/Translate/TranslateStrategyTrait.php @@ -32,7 +32,7 @@ trait TranslateStrategyTrait * * @var \Cake\ORM\Table */ - protected $table; + protected Table $table; /** * The locale name that will be used to override fields in the bound table @@ -40,14 +40,14 @@ trait TranslateStrategyTrait * * @var string|null */ - protected $locale; + protected ?string $locale = null; /** * Instance of Table responsible for translating * * @var \Cake\ORM\Table */ - protected $translationTable; + protected Table $translationTable; /** * Return translation table instance. @@ -71,7 +71,7 @@ public function getTranslationTable(): Table * * @param string|null $locale The locale to use for fetching and saving * records. Pass `null` in order to unset the current locale, and to make - * the behavior fall back to using the globally configured locale. + * the behavior falls back to using the globally configured locale. * @return $this */ public function setLocale(?string $locale) @@ -85,7 +85,7 @@ public function setLocale(?string $locale) * Returns the current locale. * * If no locale has been explicitly set via `setLocale()`, this method will return - * the currently configured global locale. + * the currently configured global locale excluding any options set after @. * * @return string * @see \Cake\I18n\I18n::getLocale() @@ -93,7 +93,7 @@ public function setLocale(?string $locale) */ public function getLocale(): string { - return $this->locale ?: I18n::getLocale(); + return $this->locale ?: explode('@', I18n::getLocale())[0]; } /** @@ -104,10 +104,14 @@ public function getLocale(): string * @param \Cake\Datasource\EntityInterface $entity The entity to check for empty translations fields inside. * @return void */ - protected function unsetEmptyFields($entity) + protected function unsetEmptyFields(EntityInterface $entity): void { - /** @var array<\Cake\ORM\Entity> $translations */ - $translations = (array)$entity->get('_translations'); + if (!$entity->has('_translations')) { + return; + } + + /** @var array<\Cake\Datasource\EntityInterface> $translations */ + $translations = $entity->get('_translations'); foreach ($translations as $locale => $translation) { $fields = $translation->extract($this->_config['fields'], false); foreach ($fields as $field => $value) { @@ -120,28 +124,29 @@ protected function unsetEmptyFields($entity) // If now, the current locale property is empty, // unset it completely. - if (empty(array_filter($translation))) { - unset($entity->get('_translations')[$locale]); + if (array_filter($translation) === []) { + unset($translations[$locale]); } } - // If now, the whole _translations property is empty, - // unset it completely and return - if (empty($entity->get('_translations'))) { + // If now, the whole $translations is empty, unset _translations property completely + if ($translations === []) { $entity->unset('_translations'); + } else { + $entity->set('_translations', $translations); } } /** - * Build a set of properties that should be included in the marshalling process. + * Build a set of properties that should be included in the marshaling process. - * Add in `_translations` marshalling handlers. You can disable marshalling + * Add in `_translations` marshaling handlers. You can disable marshaling * of translations by setting `'translations' => false` in the options * provided to `Table::newEntity()` or `Table::patchEntity()`. * - * @param \Cake\ORM\Marshaller $marshaller The marhshaller of the table the behavior is attached to. + * @param \Cake\ORM\Marshaller $marshaller The marshaler of the table the behavior is attached to. * @param array $map The property map being built. - * @param array $options The options array used in the marshalling call. + * @param array $options The options array used in the marshaling call. * @return array A map of `[property => callable]` of additional properties to marshal. */ public function buildMarshalMap(Marshaller $marshaller, array $map, array $options): array @@ -151,16 +156,13 @@ public function buildMarshalMap(Marshaller $marshaller, array $map, array $optio } return [ - '_translations' => function ($value, $entity) use ($marshaller, $options) { + '_translations' => function ($value, EntityInterface $entity) use ($marshaller, $options) { if (!is_array($value)) { return null; } - /** @var array|null $translations */ - $translations = $entity->get('_translations'); - if ($translations === null) { - $translations = []; - } + /** @var array $translations */ + $translations = $entity->has('_translations') ? (array)$entity->get('_translations') : []; $options['validate'] = $this->_config['validator']; $errors = []; @@ -189,11 +191,11 @@ public function buildMarshalMap(Marshaller $marshaller, array $map, array $optio /** * Unsets the temporary `_i18n` property after the entity has been saved * - * @param \Cake\Event\EventInterface $event The beforeSave event that was fired + * @param \Cake\Event\EventInterface<\Cake\ORM\Table> $event The beforeSave event that was fired * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved * @return void */ - public function afterSave(EventInterface $event, EntityInterface $entity) + public function afterSave(EventInterface $event, EntityInterface $entity): void { $entity->unset('_i18n'); } diff --git a/app/vendor/cakephp/cakephp/src/ORM/Behavior/Translate/TranslateTrait.php b/app/vendor/cakephp/cakephp/src/ORM/Behavior/Translate/TranslateTrait.php index 95741243b..69f25cc7a 100644 --- a/app/vendor/cakephp/cakephp/src/ORM/Behavior/Translate/TranslateTrait.php +++ b/app/vendor/cakephp/cakephp/src/ORM/Behavior/Translate/TranslateTrait.php @@ -39,10 +39,10 @@ public function translation(string $language) return $this; } - $i18n = $this->get('_translations'); + $i18n = $this->has('_translations') ? $this->get('_translations') : null; $created = false; - if (empty($i18n)) { + if (!$i18n) { $i18n = []; $created = true; } diff --git a/app/vendor/cakephp/cakephp/src/ORM/Behavior/TranslateBehavior.php b/app/vendor/cakephp/cakephp/src/ORM/Behavior/TranslateBehavior.php index 576ea8909..8b930c168 100644 --- a/app/vendor/cakephp/cakephp/src/ORM/Behavior/TranslateBehavior.php +++ b/app/vendor/cakephp/cakephp/src/ORM/Behavior/TranslateBehavior.php @@ -16,13 +16,16 @@ */ namespace Cake\ORM\Behavior; +use ArrayObject; +use Cake\Datasource\QueryInterface; +use Cake\Event\EventInterface; use Cake\I18n\I18n; use Cake\ORM\Behavior; -use Cake\ORM\Behavior\Translate\EavStrategy; +use Cake\ORM\Behavior\Translate\ShadowTableStrategy; use Cake\ORM\Behavior\Translate\TranslateStrategyInterface; use Cake\ORM\Marshaller; use Cake\ORM\PropertyMarshalInterface; -use Cake\ORM\Query; +use Cake\ORM\Query\SelectQuery; use Cake\ORM\Table; use Cake\Utility\Inflector; use function Cake\Core\namespaceSplit; @@ -48,12 +51,13 @@ class TranslateBehavior extends Behavior implements PropertyMarshalInterface * * @var array */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'implementedFinders' => ['translations' => 'findTranslations'], 'implementedMethods' => [ 'setLocale' => 'setLocale', 'getLocale' => 'getLocale', 'translationField' => 'translationField', + 'getStrategy' => 'getStrategy', ], 'fields' => [], 'defaultLocale' => null, @@ -63,22 +67,23 @@ class TranslateBehavior extends Behavior implements PropertyMarshalInterface 'strategy' => 'subquery', 'tableLocator' => null, 'validator' => false, + 'strategyClass' => null, ]; /** * Default strategy class name. * * @var string - * @psalm-var class-string<\Cake\ORM\Behavior\Translate\TranslateStrategyInterface> + * @phpstan-var class-string<\Cake\ORM\Behavior\Translate\TranslateStrategyInterface> */ - protected static $defaultStrategyClass = EavStrategy::class; + protected static string $defaultStrategyClass = ShadowTableStrategy::class; /** * Translation strategy instance. * * @var \Cake\ORM\Behavior\Translate\TranslateStrategyInterface|null */ - protected $strategy; + protected ?TranslateStrategyInterface $strategy = null; /** * Constructor @@ -90,7 +95,7 @@ class TranslateBehavior extends Behavior implements PropertyMarshalInterface * using `ShadowTableStrategy` then the list will be auto generated based on * shadow table schema. * - `defaultLocale`: The locale which is treated as default by the behavior. - * Fields values for defaut locale will be stored in the primary table itself + * Fields values for default locale will be stored in the primary table itself * and the rest in translation table. If not explicitly set the value of * `I18n::getDefaultLocale()` will be used to get default locale. * If you do not want any default locale and want translated fields @@ -134,9 +139,9 @@ public function initialize(array $config): void * @param string $class Class name. * @return void * @since 4.0.0 - * @psalm-param class-string<\Cake\ORM\Behavior\Translate\TranslateStrategyInterface> $class + * @phpstan-param class-string<\Cake\ORM\Behavior\Translate\TranslateStrategyInterface> $class */ - public static function setDefaultStrategyClass(string $class) + public static function setDefaultStrategyClass(string $class): void { static::$defaultStrategyClass = $class; } @@ -146,7 +151,7 @@ public static function setDefaultStrategyClass(string $class) * * @return string * @since 4.0.0 - * @psalm-return class-string<\Cake\ORM\Behavior\Translate\TranslateStrategyInterface> + * @phpstan-return class-string<\Cake\ORM\Behavior\Translate\TranslateStrategyInterface> */ public static function getDefaultStrategyClass(): string { @@ -174,11 +179,11 @@ public function getStrategy(): TranslateStrategyInterface * @return \Cake\ORM\Behavior\Translate\TranslateStrategyInterface * @since 4.0.0 */ - protected function createStrategy() + protected function createStrategy(): TranslateStrategyInterface { $config = array_diff_key( $this->_config, - ['implementedFinders', 'implementedMethods', 'strategyClass'] + ['implementedFinders', 'implementedMethods', 'strategyClass'], ); /** @var class-string<\Cake\ORM\Behavior\Translate\TranslateStrategyInterface> $className */ $className = $this->getConfig('strategyClass', static::$defaultStrategyClass); @@ -209,21 +214,52 @@ public function implementedEvents(): array { return [ 'Model.beforeFind' => 'beforeFind', + 'Model.beforeMarshal' => 'beforeMarshal', 'Model.beforeSave' => 'beforeSave', 'Model.afterSave' => 'afterSave', ]; } + /** + * Hoist fields for the default locale under `_translations` key to the root + * in the data. + * + * This allows `_translations.{locale}.field_name` type naming even for the + * default locale in forms. + * + * @param \Cake\Event\EventInterface $event + * @param \ArrayObject $data + * @param \ArrayObject $options + * @return void + */ + public function beforeMarshal(EventInterface $event, ArrayObject $data, ArrayObject $options): void + { + if (isset($options['translations']) && !$options['translations']) { + return; + } + + $defaultLocale = $this->getConfig('defaultLocale'); + if (!isset($data['_translations'][$defaultLocale])) { + return; + } + + foreach ($data['_translations'][$defaultLocale] as $field => $value) { + $data[$field] = $value; + } + + unset($data['_translations'][$defaultLocale]); + } + /** * {@inheritDoc} * - * Add in `_translations` marshalling handlers. You can disable marshalling + * Add in `_translations` marshaling handlers. You can disable marshaling * of translations by setting `'translations' => false` in the options * provided to `Table::newEntity()` or `Table::patchEntity()`. * - * @param \Cake\ORM\Marshaller $marshaller The marhshaller of the table the behavior is attached to. + * @param \Cake\ORM\Marshaller $marshaller The marshaler of the table the behavior is attached to. * @param array $map The property map being built. - * @param array $options The options array used in the marshalling call. + * @param array $options The options array used in the marshaling call. * @return array A map of `[property => callable]` of additional properties to marshal. */ public function buildMarshalMap(Marshaller $marshaller, array $map, array $options): array @@ -244,12 +280,12 @@ public function buildMarshalMap(Marshaller $marshaller, array $map, array $optio * that matter)! * * @param string|null $locale The locale to use for fetching and saving records. Pass `null` - * in order to unset the current locale, and to make the behavior fall back to using the + * in order to unset the current locale, and to make the behavior falls back to using the * globally configured locale. * @return $this * @see \Cake\ORM\Behavior\TranslateBehavior::getLocale() - * @link https://book.cakephp.org/4/en/orm/behaviors/translate.html#retrieving-one-language-without-using-i18n-locale - * @link https://book.cakephp.org/4/en/orm/behaviors/translate.html#saving-in-another-language + * @link https://book.cakephp.org/5/en/orm/behaviors/translate.html#retrieving-one-language-without-using-i18n-locale + * @link https://book.cakephp.org/5/en/orm/behaviors/translate.html#saving-in-another-language */ public function setLocale(?string $locale) { @@ -299,32 +335,30 @@ public function translationField(string $field): string * ### Example: * * ``` - * $article = $articles->find('translations', ['locales' => ['eng', 'deu'])->first(); + * $article = $articles->find('translations', locales: ['eng', 'deu'])->first(); * $englishTranslatedFields = $article->get('_translations')['eng']; * ``` * * If the `locales` array is not passed, it will bring all translations found * for each record. * - * @param \Cake\ORM\Query $query The original query to modify - * @param array $options Options - * @return \Cake\ORM\Query + * @param \Cake\ORM\Query\SelectQuery $query The original query to modify + * @param array $locales A list of locales or options with the `locales` key defined + * @return \Cake\ORM\Query\SelectQuery */ - public function findTranslations(Query $query, array $options): Query + public function findTranslations(SelectQuery $query, array $locales = []): SelectQuery { - $locales = $options['locales'] ?? []; $targetAlias = $this->getStrategy()->getTranslationTable()->getAlias(); return $query - ->contain([$targetAlias => function ($query) use ($locales, $targetAlias) { - /** @var \Cake\Datasource\QueryInterface $query */ + ->contain([$targetAlias => function (QueryInterface $query) use ($locales, $targetAlias) { if ($locales) { - $query->where(["$targetAlias.locale IN" => $locales]); + $query->where(["{$targetAlias}.locale IN" => $locales]); } return $query; }]) - ->formatResults([$this->getStrategy(), 'groupTranslations'], $query::PREPEND); + ->formatResults($this->getStrategy()->groupTranslations(...), $query::PREPEND); } /** @@ -334,7 +368,7 @@ public function findTranslations(Query $query, array $options): Query * @param array $args Method arguments. * @return mixed */ - public function __call($method, $args) + public function __call(string $method, array $args): mixed { return $this->strategy->{$method}(...$args); } @@ -352,9 +386,9 @@ public function __call($method, $args) */ protected function referenceName(Table $table): string { - $name = namespaceSplit(get_class($table)); - $name = substr(end($name), 0, -5); - if (empty($name)) { + $name = namespaceSplit($table::class); + $name = substr((string)end($name), 0, -5); + if (!$name) { $name = $table->getTable() ?: $table->getAlias(); $name = Inflector::camelize($name); } diff --git a/app/vendor/cakephp/cakephp/src/ORM/Behavior/TreeBehavior.php b/app/vendor/cakephp/cakephp/src/ORM/Behavior/TreeBehavior.php index 493bea37d..c489af000 100644 --- a/app/vendor/cakephp/cakephp/src/ORM/Behavior/TreeBehavior.php +++ b/app/vendor/cakephp/cakephp/src/ORM/Behavior/TreeBehavior.php @@ -17,15 +17,18 @@ namespace Cake\ORM\Behavior; use Cake\Collection\CollectionInterface; +use Cake\Collection\Iterator\TreeIterator; +use Cake\Database\Exception\DatabaseException; use Cake\Database\Expression\IdentifierExpression; use Cake\Database\Expression\QueryExpression; use Cake\Datasource\EntityInterface; use Cake\Datasource\Exception\RecordNotFoundException; use Cake\Event\EventInterface; use Cake\ORM\Behavior; -use Cake\ORM\Query; -use InvalidArgumentException; -use RuntimeException; +use Cake\ORM\Query\DeleteQuery; +use Cake\ORM\Query\SelectQuery; +use Cake\ORM\Query\UpdateQuery; +use Closure; /** * Makes the table to which this is attached to behave like a nested set and @@ -46,7 +49,7 @@ class TreeBehavior extends Behavior * * @var string */ - protected $_primaryKey; + protected string $_primaryKey = ''; /** * Default config @@ -55,7 +58,7 @@ class TreeBehavior extends Behavior * * @var array */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'implementedFinders' => [ 'path' => 'findPath', 'children' => 'findChildren', @@ -93,12 +96,12 @@ public function initialize(array $config): void * Transparently manages setting the lft and rght fields if the parent field is * included in the parameters to be saved. * - * @param \Cake\Event\EventInterface $event The beforeSave event that was fired + * @param \Cake\Event\EventInterface<\Cake\ORM\Table> $event The beforeSave event that was fired * @param \Cake\Datasource\EntityInterface $entity the entity that is going to be saved * @return void - * @throws \RuntimeException if the parent to set for the node is invalid + * @throws \Cake\Database\Exception\DatabaseException if the parent to set for the node is invalid */ - public function beforeSave(EventInterface $event, EntityInterface $entity) + public function beforeSave(EventInterface $event, EntityInterface $entity): void { $isNew = $entity->isNew(); $config = $this->getConfig(); @@ -108,24 +111,24 @@ public function beforeSave(EventInterface $event, EntityInterface $entity) $level = $config['level']; if ($parent && $entity->get($primaryKey) === $parent) { - throw new RuntimeException("Cannot set a node's parent as itself"); + throw new DatabaseException("Cannot set a node's parent as itself."); } - if ($isNew && $parent) { - $parentNode = $this->_getNode($parent); - $edge = $parentNode->get($config['right']); - $entity->set($config['left'], $edge); - $entity->set($config['right'], $edge + 1); - $this->_sync(2, '+', ">= {$edge}"); + if ($isNew) { + if ($parent) { + $parentNode = $this->_getNode($parent); + $edge = $parentNode->get($config['right']); + $entity->set($config['left'], $edge); + $entity->set($config['right'], $edge + 1); + $this->_sync(2, '+', ">= {$edge}"); - if ($level) { - $entity->set($level, $parentNode[$level] + 1); - } + if ($level) { + $entity->set($level, $parentNode[$level] + 1); + } - return; - } + return; + } - if ($isNew && !$parent) { $edge = $this->_getMax(); $entity->set($config['left'], $edge + 1); $entity->set($config['right'], $edge + 2); @@ -137,18 +140,18 @@ public function beforeSave(EventInterface $event, EntityInterface $entity) return; } - if ($dirty && $parent) { - $this->_setParent($entity, $parent); + if ($dirty) { + if ($parent) { + $this->_setParent($entity, $parent); - if ($level) { - $parentNode = $this->_getNode($parent); - $entity->set($level, $parentNode[$level] + 1); - } + if ($level) { + $parentNode = $this->_getNode($parent); + $entity->set($level, $parentNode[$level] + 1); + } - return; - } + return; + } - if ($dirty && !$parent) { $this->_setAsRoot($entity); if ($level) { @@ -162,11 +165,11 @@ public function beforeSave(EventInterface $event, EntityInterface $entity) * * Manages updating level of descendants of currently saved entity. * - * @param \Cake\Event\EventInterface $event The afterSave event that was fired + * @param \Cake\Event\EventInterface<\Cake\ORM\Table> $event The afterSave event that was fired * @param \Cake\Datasource\EntityInterface $entity the entity that is going to be saved * @return void */ - public function afterSave(EventInterface $event, EntityInterface $entity) + public function afterSave(EventInterface $event, EntityInterface $entity): void { if (!$this->_config['level'] || $entity->isNew()) { return; @@ -193,13 +196,15 @@ protected function _setChildrenLevel(EntityInterface $entity): void $primaryKeyValue = $entity->get($primaryKey); $depths = [$primaryKeyValue => $entity->get($config['level'])]; - $children = $this->_table->find('children', [ - 'for' => $primaryKeyValue, - 'fields' => [$this->_getPrimaryKey(), $config['parent'], $config['level']], - 'order' => $config['left'], - ]); + /** @var \Traversable<\Cake\Datasource\EntityInterface> $children */ + $children = $this->_table->find( + 'children', + for: $primaryKeyValue, + fields: [$this->_getPrimaryKey(), $config['parent'], $config['level']], + order: $config['left'], + ) + ->all(); - /** @var \Cake\Datasource\EntityInterface $node */ foreach ($children as $node) { $parentIdValue = $node->get($config['parent']); $depth = $depths[$parentIdValue] + 1; @@ -207,7 +212,7 @@ protected function _setChildrenLevel(EntityInterface $entity): void $this->_table->updateAll( [$config['level'] => $depth], - [$primaryKey => $node->get($primaryKey)] + [$primaryKey => $node->get($primaryKey)], ); } } @@ -215,39 +220,39 @@ protected function _setChildrenLevel(EntityInterface $entity): void /** * Also deletes the nodes in the subtree of the entity to be delete * - * @param \Cake\Event\EventInterface $event The beforeDelete event that was fired + * @param \Cake\Event\EventInterface<\Cake\ORM\Table> $event The beforeDelete event that was fired * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved * @return void */ - public function beforeDelete(EventInterface $event, EntityInterface $entity) + public function beforeDelete(EventInterface $event, EntityInterface $entity): void { $config = $this->getConfig(); $this->_ensureFields($entity); $left = $entity->get($config['left']); $right = $entity->get($config['right']); - $diff = $right - $left + 1; + $diff = (int)($right - $left + 1); if ($diff > 2) { if ($this->getConfig('cascadeCallbacks')) { - $query = $this->_scope($this->_table->selectQuery()) - ->where(function (QueryExpression $exp) use ($config, $left, $right) { - return $exp + $query = $this->_scope($this->_table->query()) + ->where( + fn(QueryExpression $exp) => $exp ->gte($config['leftField'], $left + 1) - ->lte($config['leftField'], $right - 1); - }); + ->lte($config['leftField'], $right - 1), + ); + $entities = $query->toArray(); foreach ($entities as $entityToDelete) { $this->_table->delete($entityToDelete, ['atomic' => false]); } } else { - $query = $this->_scope($this->_table->deleteQuery()) - ->where(function (QueryExpression $exp) use ($config, $left, $right) { - return $exp + $this->_scope($this->_table->deleteQuery()) + ->where( + fn(QueryExpression $exp) => $exp ->gte($config['leftField'], $left + 1) - ->lte($config['leftField'], $right - 1); - }); - $statement = $query->execute(); - $statement->closeCursor(); + ->lte($config['leftField'], $right - 1), + ) + ->execute(); } } @@ -262,9 +267,9 @@ public function beforeDelete(EventInterface $event, EntityInterface $entity) * @param \Cake\Datasource\EntityInterface $entity The entity to re-parent * @param mixed $parent the id of the parent to set * @return void - * @throws \RuntimeException if the parent to set to the entity is not valid + * @throws \Cake\Database\Exception\DatabaseException if the parent to set to the entity is not valid */ - protected function _setParent(EntityInterface $entity, $parent): void + protected function _setParent(EntityInterface $entity, mixed $parent): void { $config = $this->getConfig(); $parentNode = $this->_getNode($parent); @@ -275,10 +280,10 @@ protected function _setParent(EntityInterface $entity, $parent): void $left = $entity->get($config['left']); if ($parentLeft > $left && $parentLeft < $right) { - throw new RuntimeException(sprintf( - 'Cannot use node "%s" as parent for entity "%s"', + throw new DatabaseException(sprintf( + 'Cannot use node `%s` as parent for entity `%s`.', $parent, - $entity->get($this->_getPrimaryKey()) + $entity->get($this->_getPrimaryKey()), )); } @@ -361,8 +366,7 @@ protected function _unmarkInternalTree(): void { $config = $this->getConfig(); $this->_table->updateAll( - function ($exp) use ($config) { - /** @var \Cake\Database\Expression\QueryExpression $exp */ + function (QueryExpression $exp) use ($config) { $leftInverse = clone $exp; $leftInverse->setConjunction('*')->add('-1'); $rightInverse = clone $leftInverse; @@ -371,10 +375,7 @@ function ($exp) use ($config) { ->eq($config['leftField'], $leftInverse->add($config['leftField'])) ->eq($config['rightField'], $rightInverse->add($config['rightField'])); }, - function ($exp) use ($config) { - /** @var \Cake\Database\Expression\QueryExpression $exp */ - return $exp->lt($config['leftField'], 0); - } + fn(QueryExpression $exp) => $exp->lt($config['leftField'], 0), ); } @@ -383,33 +384,29 @@ function ($exp) use ($config) { * to a specific node in the tree. This custom finder requires that the key 'for' * is passed in the options containing the id of the node to get its path for. * - * @param \Cake\ORM\Query $query The constructed query to modify - * @param array $options the list of options for the query - * @return \Cake\ORM\Query + * @param \Cake\ORM\Query\SelectQuery $query The constructed query to modify + * @param string|int $for The path to find or an array of options with `for`. + * @return \Cake\ORM\Query\SelectQuery * @throws \InvalidArgumentException If the 'for' key is missing in options */ - public function findPath(Query $query, array $options): Query + public function findPath(SelectQuery $query, string|int $for): SelectQuery { - if (empty($options['for'])) { - throw new InvalidArgumentException("The 'for' key is required for find('path')"); - } - $config = $this->getConfig(); [$left, $right] = array_map( function ($field) { return $this->_table->aliasField($field); }, - [$config['left'], $config['right']] + [$config['left'], $config['right']], ); - $node = $this->_table->get($options['for'], ['fields' => [$left, $right]]); + $node = $this->_table->get($for, select: [$left, $right]); return $this->_scope($query) ->where([ - "$left <=" => $node->get($config['left']), - "$right >=" => $node->get($config['right']), + "{$left} <=" => $node->get($config['left']), + "{$right} >=" => $node->get($config['right']), ]) - ->order([$left => 'ASC']); + ->orderBy([$left => 'ASC']); } /** @@ -437,40 +434,29 @@ public function childCount(EntityInterface $node, bool $direct = false): int } /** - * Get the children nodes of the current model + * Get the children nodes of the current model. * - * Available options are: + * If the direct option is set to true, only the direct children are returned + * (based upon the parent_id field). * - * - for: The id of the record to read. - * - direct: Boolean, whether to return only the direct (true), or all (false) children, - * defaults to false (all children). - * - * If the direct option is set to true, only the direct children are returned (based upon the parent_id field) - * - * @param \Cake\ORM\Query $query Query. - * @param array $options Array of options as described above - * @return \Cake\ORM\Query + * @param \Cake\ORM\Query\SelectQuery $query Query. + * @param string|int $for The id of the record to read. Can also be an array of options. + * @param bool $direct Whether to return only the direct (true) or all children (false). + * @return \Cake\ORM\Query\SelectQuery * @throws \InvalidArgumentException When the 'for' key is not passed in $options */ - public function findChildren(Query $query, array $options): Query + public function findChildren(SelectQuery $query, int|string $for, bool $direct = false): SelectQuery { $config = $this->getConfig(); - $options += ['for' => null, 'direct' => false]; [$parent, $left, $right] = array_map( function ($field) { return $this->_table->aliasField($field); }, - [$config['parent'], $config['left'], $config['right']] + [$config['parent'], $config['left'], $config['right']], ); - [$for, $direct] = [$options['for'], $options['direct']]; - - if (empty($for)) { - throw new InvalidArgumentException("The 'for' key is required for find('children')"); - } - if ($query->clause('order') === null) { - $query->order([$left => 'ASC']); + $query->orderBy([$left => 'ASC']); } if ($direct) { @@ -491,29 +477,26 @@ function ($field) { * the primary key for the table and the values are the display field for the table. * Values are prefixed to visually indicate relative depth in the tree. * - * ### Options - * - * - keyPath: A dot separated path to fetch the field to use for the array key, or a closure to + * @param \Cake\ORM\Query\SelectQuery $query Query. + * @param \Closure|string|null $keyPath A dot separated path to fetch the field to use for the array key, or a closure to * return the key out of the provided row. - * - valuePath: A dot separated path to fetch the field to use for the array value, or a closure to + * @param \Closure|string|null $valuePath A dot separated path to fetch the field to use for the array value, or a closure to * return the value out of the provided row. - * - spacer: A string to be used as prefix for denoting the depth in the tree for each item - * - * @param \Cake\ORM\Query $query Query. - * @param array $options Array of options as described above. - * @return \Cake\ORM\Query + * @param string|null $spacer A string to be used as prefix for denoting the depth in the tree for each item. + * @return \Cake\ORM\Query\SelectQuery */ - public function findTreeList(Query $query, array $options): Query - { + public function findTreeList( + SelectQuery $query, + Closure|string|null $keyPath = null, + Closure|string|null $valuePath = null, + ?string $spacer = null, + ): SelectQuery { $left = $this->_table->aliasField($this->getConfig('left')); $results = $this->_scope($query) - ->find('threaded', [ - 'parentField' => $this->getConfig('parent'), - 'order' => [$left => 'ASC'], - ]); + ->find('threaded', parentField: $this->getConfig('parent'), order: [$left => 'ASC']); - return $this->formatTreeList($results, $options); + return $this->formatTreeList($results, $keyPath, $valuePath, $spacer); } /** @@ -521,32 +504,33 @@ public function findTreeList(Query $query, array $options): Query * and the values are the display field for the table. Values are prefixed to visually * indicate relative depth in the tree. * - * ### Options - * - * - keyPath: A dot separated path to the field that will be the result array key, or a closure to + * @param \Cake\ORM\Query\SelectQuery $query The query object to format. + * @param \Closure|string|null $keyPath A dot separated path to the field that will be the result array key, or a closure to * return the key from the provided row. - * - valuePath: A dot separated path to the field that is the array's value, or a closure to + * @param \Closure|string|null $valuePath A dot separated path to the field that is the array's value, or a closure to * return the value from the provided row. - * - spacer: A string to be used as prefix for denoting the depth in the tree for each item. - * - * @param \Cake\ORM\Query $query The query object to format. - * @param array $options Array of options as described above. - * @return \Cake\ORM\Query Augmented query. + * @param string|null $spacer A string to be used as prefix for denoting the depth in the tree for each item. + * @return \Cake\ORM\Query\SelectQuery Augmented query. */ - public function formatTreeList(Query $query, array $options = []): Query - { - return $query->formatResults(function (CollectionInterface $results) use ($options) { - $options += [ - 'keyPath' => $this->_getPrimaryKey(), - 'valuePath' => $this->_table->getDisplayField(), - 'spacer' => '_', - ]; - - /** @var \Cake\Collection\Iterator\TreeIterator $nested */ - $nested = $results->listNested(); - - return $nested->printer($options['valuePath'], $options['keyPath'], $options['spacer']); - }); + public function formatTreeList( + SelectQuery $query, + Closure|string|null $keyPath = null, + Closure|string|null $valuePath = null, + ?string $spacer = null, + ): SelectQuery { + return $query->formatResults( + function (CollectionInterface $results) use ($keyPath, $valuePath, $spacer) { + $keyPath ??= $this->_getPrimaryKey(); + $valuePath ??= $this->_table->getDisplayField(); + $spacer ??= '_'; + + $nested = $results->listNested(); + assert($nested instanceof TreeIterator); + assert(is_callable($valuePath) || is_string($valuePath)); + + return $nested->printer($valuePath, $keyPath, $spacer); + }, + ); } /** @@ -560,7 +544,7 @@ public function formatTreeList(Query $query, array $options = []): Query * @return \Cake\Datasource\EntityInterface|false the node after being removed from the tree or * false on error */ - public function removeFromTree(EntityInterface $node) + public function removeFromTree(EntityInterface $node): EntityInterface|false { return $this->_table->getConnection()->transactional(function () use ($node) { $this->_ensureFields($node); @@ -576,7 +560,7 @@ public function removeFromTree(EntityInterface $node) * @return \Cake\Datasource\EntityInterface|false the node after being removed from the tree or * false on error */ - protected function _removeFromTree(EntityInterface $node) + protected function _removeFromTree(EntityInterface $node): EntityInterface|false { $config = $this->getConfig(); $left = $node->get($config['left']); @@ -592,7 +576,7 @@ protected function _removeFromTree(EntityInterface $node) $primary = $this->_getPrimaryKey(); $this->_table->updateAll( [$config['parent'] => $parent], - [$config['parent'] => $node->get($primary)] + [$config['parent'] => $node->get($primary)], ); $this->_sync(1, '-', 'BETWEEN ' . ($left + 1) . ' AND ' . ($right - 1)); $this->_sync(2, '-', "> {$right}"); @@ -621,7 +605,7 @@ protected function _removeFromTree(EntityInterface $node) * @throws \Cake\Datasource\Exception\RecordNotFoundException When node was not found * @return \Cake\Datasource\EntityInterface|false $node The node after being moved or false if `$number` is < 1 */ - public function moveUp(EntityInterface $node, $number = 1) + public function moveUp(EntityInterface $node, int|bool $number = 1): EntityInterface|false { if ($number < 1) { return false; @@ -642,7 +626,7 @@ public function moveUp(EntityInterface $node, $number = 1) * @return \Cake\Datasource\EntityInterface $node The node after being moved * @throws \Cake\Datasource\Exception\RecordNotFoundException When node was not found */ - protected function _moveUp(EntityInterface $node, $number): EntityInterface + protected function _moveUp(EntityInterface $node, int|bool $number): EntityInterface { $config = $this->getConfig(); [$parent, $left, $right] = [$config['parent'], $config['left'], $config['right']]; @@ -653,12 +637,9 @@ protected function _moveUp(EntityInterface $node, $number): EntityInterface /** @var \Cake\Datasource\EntityInterface|null $targetNode */ $targetNode = $this->_scope($this->_table->find()) ->select([$left, $right]) - ->where(["$parent IS" => $nodeParent]) - ->where(function ($exp) use ($config, $nodeLeft) { - /** @var \Cake\Database\Expression\QueryExpression $exp */ - return $exp->lt($config['rightField'], $nodeLeft); - }) - ->orderDesc($config['leftField']) + ->where(["{$parent} IS" => $nodeParent]) + ->where(fn(QueryExpression $exp) => $exp->lt($config['rightField'], $nodeLeft)) + ->orderByDesc($config['leftField']) ->offset($number - 1) ->limit(1) ->first(); @@ -667,12 +648,9 @@ protected function _moveUp(EntityInterface $node, $number): EntityInterface /** @var \Cake\Datasource\EntityInterface|null $targetNode */ $targetNode = $this->_scope($this->_table->find()) ->select([$left, $right]) - ->where(["$parent IS" => $nodeParent]) - ->where(function ($exp) use ($config, $nodeLeft) { - /** @var \Cake\Database\Expression\QueryExpression $exp */ - return $exp->lt($config['rightField'], $nodeLeft); - }) - ->orderAsc($config['leftField']) + ->where(["{$parent} IS" => $nodeParent]) + ->where(fn(QueryExpression $exp) => $exp->lt($config['rightField'], $nodeLeft)) + ->orderByAsc($config['leftField']) ->limit(1) ->first(); @@ -693,7 +671,9 @@ protected function _moveUp(EntityInterface $node, $number): EntityInterface $this->_sync($shift, '+', "BETWEEN {$leftBoundary} AND {$rightBoundary}"); $this->_sync($nodeToHole, '-', "> {$edge}"); + /** @var string $left */ $node->set($left, $targetLeft); + /** @var string $right */ $node->set($right, $targetLeft + $nodeRight - $nodeLeft); $node->setDirty($left, false); @@ -713,7 +693,7 @@ protected function _moveUp(EntityInterface $node, $number): EntityInterface * @throws \Cake\Datasource\Exception\RecordNotFoundException When node was not found * @return \Cake\Datasource\EntityInterface|false the entity after being moved or false if `$number` is < 1 */ - public function moveDown(EntityInterface $node, $number = 1) + public function moveDown(EntityInterface $node, int|bool $number = 1): EntityInterface|false { if ($number < 1) { return false; @@ -734,10 +714,11 @@ public function moveDown(EntityInterface $node, $number = 1) * @return \Cake\Datasource\EntityInterface $node The node after being moved * @throws \Cake\Datasource\Exception\RecordNotFoundException When node was not found */ - protected function _moveDown(EntityInterface $node, $number): EntityInterface + protected function _moveDown(EntityInterface $node, int|bool $number): EntityInterface { $config = $this->getConfig(); [$parent, $left, $right] = [$config['parent'], $config['left'], $config['right']]; + assert(is_string($parent) && is_string($left) && is_string($right)); [$nodeParent, $nodeLeft, $nodeRight] = array_values($node->extract([$parent, $left, $right])); $targetNode = null; @@ -745,12 +726,9 @@ protected function _moveDown(EntityInterface $node, $number): EntityInterface /** @var \Cake\Datasource\EntityInterface|null $targetNode */ $targetNode = $this->_scope($this->_table->find()) ->select([$left, $right]) - ->where(["$parent IS" => $nodeParent]) - ->where(function ($exp) use ($config, $nodeRight) { - /** @var \Cake\Database\Expression\QueryExpression $exp */ - return $exp->gt($config['leftField'], $nodeRight); - }) - ->orderAsc($config['leftField']) + ->where(["{$parent} IS" => $nodeParent]) + ->where(fn(QueryExpression $exp) => $exp->gt($config['leftField'], $nodeRight)) + ->orderByAsc($config['leftField']) ->offset($number - 1) ->limit(1) ->first(); @@ -759,12 +737,9 @@ protected function _moveDown(EntityInterface $node, $number): EntityInterface /** @var \Cake\Datasource\EntityInterface|null $targetNode */ $targetNode = $this->_scope($this->_table->find()) ->select([$left, $right]) - ->where(["$parent IS" => $nodeParent]) - ->where(function ($exp) use ($config, $nodeRight) { - /** @var \Cake\Database\Expression\QueryExpression $exp */ - return $exp->gt($config['leftField'], $nodeRight); - }) - ->orderDesc($config['leftField']) + ->where(["{$parent} IS" => $nodeParent]) + ->where(fn(QueryExpression $exp) => $exp->gt($config['leftField'], $nodeRight)) + ->orderByDesc($config['leftField']) ->limit(1) ->first(); @@ -800,9 +775,8 @@ protected function _moveDown(EntityInterface $node, $number): EntityInterface * @param mixed $id Record id. * @return \Cake\Datasource\EntityInterface * @throws \Cake\Datasource\Exception\RecordNotFoundException When node was not found - * @psalm-suppress InvalidReturnType */ - protected function _getNode($id): EntityInterface + protected function _getNode(mixed $id): EntityInterface { $config = $this->getConfig(); [$parent, $left, $right] = [$config['parent'], $config['left'], $config['right']]; @@ -818,10 +792,9 @@ protected function _getNode($id): EntityInterface ->first(); if (!$node) { - throw new RecordNotFoundException("Node \"{$id}\" was not found in the tree."); + throw new RecordNotFoundException(sprintf('Node `%s` was not found in the tree.', $id)); } - /** @psalm-suppress InvalidReturnStatement */ return $node; } @@ -846,7 +819,7 @@ public function recover(): void * @param int $level Node level * @return int The next lftRght value */ - protected function _recoverTree(int $lftRght = 1, $parentId = null, $level = 0): int + protected function _recoverTree(int $lftRght = 1, mixed $parentId = null, int $level = 0): int { $config = $this->getConfig(); [$parent, $left, $right] = [$config['parent'], $config['left'], $config['right']]; @@ -856,7 +829,7 @@ protected function _recoverTree(int $lftRght = 1, $parentId = null, $level = 0): $nodes = $this->_scope($this->_table->selectQuery()) ->select($primaryKey) ->where([$parent . ' IS' => $parentId]) - ->order($order) + ->orderBy($order) ->disableHydration() ->all(); @@ -871,7 +844,7 @@ protected function _recoverTree(int $lftRght = 1, $parentId = null, $level = 0): $this->_table->updateAll( $fields, - [$primaryKey => $node[$primaryKey]] + [$primaryKey => $node[$primaryKey]], ); } @@ -889,7 +862,7 @@ protected function _getMax(): int $rightField = $this->_config['rightField']; $edge = $this->_scope($this->_table->find()) ->select([$field]) - ->orderDesc($rightField) + ->orderByDesc($rightField) ->first(); if ($edge === null || empty($edge[$field])) { @@ -915,6 +888,7 @@ protected function _sync(int $shift, string $dir, string $conditions, bool $mark { $config = $this->_config; + /** @var \Cake\Database\Expression\IdentifierExpression $field */ foreach ([$config['leftField'], $config['rightField']] as $field) { $query = $this->_scope($this->_table->updateQuery()); $exp = $query->newExpr(); @@ -930,8 +904,10 @@ protected function _sync(int $shift, string $dir, string $conditions, bool $mark $where = clone $exp; $where->add($field)->add($conditions)->setConjunction(''); - $query->set($exp->eq($field, $movement))->where($where); - $query->execute()->closeCursor(); + $query + ->set($exp->eq($field, $movement)) + ->where($where) + ->execute(); } } @@ -939,21 +915,21 @@ protected function _sync(int $shift, string $dir, string $conditions, bool $mark * Alters the passed query so that it only returns scoped records as defined * in the tree configuration. * - * @param \Cake\ORM\Query $query the Query to modify - * @return \Cake\ORM\Query + * @param \Cake\ORM\Query\SelectQuery|\Cake\ORM\Query\UpdateQuery|\Cake\ORM\Query\DeleteQuery $query the Query to modify + * @return \Cake\ORM\Query\SelectQuery|\Cake\ORM\Query\UpdateQuery|\Cake\ORM\Query\DeleteQuery + * @template T of \Cake\ORM\Query\SelectQuery|\Cake\ORM\Query\UpdateQuery|\Cake\ORM\Query\DeleteQuery + * @phpstan-param T $query + * @phpstan-return T */ - protected function _scope(Query $query): Query + protected function _scope(SelectQuery|UpdateQuery|DeleteQuery $query): SelectQuery|UpdateQuery|DeleteQuery { $scope = $this->getConfig('scope'); - if (is_array($scope)) { - return $query->where($scope); - } - if (is_callable($scope)) { - return $scope($query); + if ($scope === null) { + return $query; } - return $query; + return $query->where($scope); } /** @@ -973,7 +949,11 @@ protected function _ensureFields(EntityInterface $entity): void } $fresh = $this->_table->get($entity->get($this->_getPrimaryKey())); - $entity->set($fresh->extract($fields), ['guard' => false]); + if (method_exists($entity, 'patch')) { + $entity->patch($fresh->extract($fields), ['guard' => false]); + } else { + $entity->set($fresh->extract($fields), ['guard' => false]); + } foreach ($fields as $field) { $entity->setDirty($field, false); @@ -1001,7 +981,7 @@ protected function _getPrimaryKey(): string * @param \Cake\Datasource\EntityInterface|string|int $entity The entity or primary key get the level of. * @return int|false Integer of the level or false if the node does not exist. */ - public function getLevel($entity) + public function getLevel(EntityInterface|string|int $entity): int|false { $primaryKey = $this->_getPrimaryKey(); $id = $entity; diff --git a/app/vendor/cakephp/cakephp/src/ORM/BehaviorRegistry.php b/app/vendor/cakephp/cakephp/src/ORM/BehaviorRegistry.php index fc06b3118..fe0c7c716 100644 --- a/app/vendor/cakephp/cakephp/src/ORM/BehaviorRegistry.php +++ b/app/vendor/cakephp/cakephp/src/ORM/BehaviorRegistry.php @@ -22,6 +22,7 @@ use Cake\Event\EventDispatcherInterface; use Cake\Event\EventDispatcherTrait; use Cake\ORM\Exception\MissingBehaviorException; +use Cake\ORM\Query\SelectQuery; use LogicException; /** @@ -31,9 +32,13 @@ * This class also provides method for checking and dispatching behavior methods. * * @extends \Cake\Core\ObjectRegistry<\Cake\ORM\Behavior> + * @implements \Cake\Event\EventDispatcherInterface<\Cake\ORM\Table> */ class BehaviorRegistry extends ObjectRegistry implements EventDispatcherInterface { + /** + * @use \Cake\Event\EventDispatcherTrait<\Cake\ORM\Table> + */ use EventDispatcherTrait; /** @@ -41,21 +46,21 @@ class BehaviorRegistry extends ObjectRegistry implements EventDispatcherInterfac * * @var \Cake\ORM\Table */ - protected $_table; + protected Table $_table; /** * Method mappings. * * @var array */ - protected $_methodMap = []; + protected array $_methodMap = []; /** * Finder method mappings. * * @var array */ - protected $_finderMap = []; + protected array $_finderMap = []; /** * Constructor @@ -86,7 +91,7 @@ public function setTable(Table $table): void * * @param string $class Partial classname to resolve. * @return string|null Either the correct classname or null. - * @psalm-return class-string|null + * @phpstan-return class-string|null */ public static function className(string $class): ?string { @@ -100,11 +105,11 @@ public static function className(string $class): ?string * Part of the template method for Cake\Core\ObjectRegistry::load() * * @param string $class Partial classname to resolve. - * @return string|null Either the correct class name or null. - * @psalm-return class-string|null + * @return class-string<\Cake\ORM\Behavior>|null Either the correct class name or null. */ protected function _resolveClassName(string $class): ?string { + /** @var class-string<\Cake\ORM\Behavior>|null */ return static::className($class); } @@ -133,16 +138,19 @@ protected function _throwMissingClassError(string $class, ?string $plugin): void * Part of the template method for Cake\Core\ObjectRegistry::load() * Enabled behaviors will be registered with the event manager. * - * @param string $class The classname that is missing. + * @param \Cake\ORM\Behavior|class-string<\Cake\ORM\Behavior> $class The classname that is missing. * @param string $alias The alias of the object. * @param array $config An array of config to use for the behavior. * @return \Cake\ORM\Behavior The constructed behavior class. - * @psalm-suppress MoreSpecificImplementedParamType */ - protected function _create($class, string $alias, array $config): Behavior + protected function _create(object|string $class, string $alias, array $config): Behavior { - /** @var \Cake\ORM\Behavior $instance */ + if (is_object($class)) { + return $class; + } + $instance = new $class($this->_table, $config); + $enable = $config['enabled'] ?? true; if ($enable) { $this->getEventManager()->on($instance); @@ -176,10 +184,10 @@ protected function _getMethods(Behavior $instance, string $class, string $alias) if (isset($this->_finderMap[$finder]) && $this->has($this->_finderMap[$finder][0])) { $duplicate = $this->_finderMap[$finder]; $error = sprintf( - '%s contains duplicate finder "%s" which is already provided by "%s"', + '`%s` contains duplicate finder `%s` which is already provided by `%s`.', $class, $finder, - $duplicate[0] + $duplicate[0], ); throw new LogicException($error); } @@ -190,10 +198,10 @@ protected function _getMethods(Behavior $instance, string $class, string $alias) if (isset($this->_methodMap[$method]) && $this->has($this->_methodMap[$method][0])) { $duplicate = $this->_methodMap[$method]; $error = sprintf( - '%s contains duplicate method "%s" which is already provided by "%s"', + '`%s` contains duplicate method `%s` which is already provided by `%s`.', $class, $method, - $duplicate[0] + $duplicate[0], ); throw new LogicException($error); } @@ -214,7 +222,7 @@ public function set(string $name, object $object) { parent::set($name, $object); - $methods = $this->_getMethods($object, get_class($object), $name); + $methods = $this->_getMethods($object, $object::class, $name); $this->_methodMap += $methods['methods']; $this->_finderMap += $methods['finders']; @@ -234,12 +242,12 @@ public function unload(string $name) $instance = $this->get($name); $result = parent::unload($name); - $methods = array_change_key_case($instance->implementedMethods()); - foreach (array_keys($methods) as $method) { + $methods = array_map('strtolower', array_keys($instance->implementedMethods())); + foreach ($methods as $method) { unset($this->_methodMap[$method]); } - $finders = array_change_key_case($instance->implementedFinders()); - foreach (array_keys($finders) as $finder) { + $finders = array_map('strtolower', array_keys($instance->implementedFinders())); + foreach ($finders as $finder) { unset($this->_finderMap[$finder]); } @@ -286,7 +294,7 @@ public function hasFinder(string $method): bool * @return mixed The return value depends on the underlying behavior method. * @throws \BadMethodCallException When the method is unknown. */ - public function call(string $method, array $args = []) + public function call(string $method, array $args = []): mixed { $method = strtolower($method); if ($this->hasMethod($method) && $this->has($this->_methodMap[$method][0])) { @@ -296,31 +304,34 @@ public function call(string $method, array $args = []) } throw new BadMethodCallException( - sprintf('Cannot call "%s" it does not belong to any attached behavior.', $method) + sprintf('Cannot call `%s`, it does not belong to any attached behavior.', $method), ); } /** * Invoke a finder on a behavior. * + * @internal + * @template TSubject of \Cake\Datasource\EntityInterface|array * @param string $type The finder type to invoke. - * @param array $args The arguments you want to invoke the method with. - * @return \Cake\ORM\Query The return value depends on the underlying behavior method. + * @param \Cake\ORM\Query\SelectQuery $query The query object to apply the finder options to. + * @param mixed ...$args Arguments that match up to finder-specific parameters + * @return \Cake\ORM\Query\SelectQuery The return value depends on the underlying behavior method. * @throws \BadMethodCallException When the method is unknown. */ - public function callFinder(string $type, array $args = []): Query + public function callFinder(string $type, SelectQuery $query, mixed ...$args): SelectQuery { $type = strtolower($type); - if ($this->hasFinder($type) && $this->has($this->_finderMap[$type][0])) { + if ($this->hasFinder($type)) { [$behavior, $callMethod] = $this->_finderMap[$type]; - $callable = [$this->_loaded[$behavior], $callMethod]; + $callable = $this->_loaded[$behavior]->$callMethod(...); - return $callable(...$args); + return $this->_table->invokeFinder($callable, $query, $args); } throw new BadMethodCallException( - sprintf('Cannot call finder "%s" it does not belong to any attached behavior.', $type) + sprintf('Cannot call finder `%s`, it does not belong to any attached behavior.', $type), ); } } diff --git a/app/vendor/cakephp/cakephp/src/ORM/EagerLoadable.php b/app/vendor/cakephp/cakephp/src/ORM/EagerLoadable.php index fc5e226f2..80636dfad 100644 --- a/app/vendor/cakephp/cakephp/src/ORM/EagerLoadable.php +++ b/app/vendor/cakephp/cakephp/src/ORM/EagerLoadable.php @@ -16,6 +16,8 @@ */ namespace Cake\ORM; +use Cake\Database\Exception\DatabaseException; + /** * Represents a single level in the associations tree to be eagerly loaded * for a specific query. This contains all the information required to @@ -31,21 +33,21 @@ class EagerLoadable * * @var string */ - protected $_name; + protected string $_name; /** * A list of other associations to load from this level. * * @var array<\Cake\ORM\EagerLoadable> */ - protected $_associations = []; + protected array $_associations = []; /** * The Association class instance to use for loading the records. * * @var \Cake\ORM\Association|null */ - protected $_instance; + protected ?Association $_instance = null; /** * A list of options to pass to the association object for loading @@ -53,7 +55,7 @@ class EagerLoadable * * @var array */ - protected $_config = []; + protected array $_config = []; /** * A dotted separated string representing the path of associations @@ -61,7 +63,7 @@ class EagerLoadable * * @var string */ - protected $_aliasPath; + protected string $_aliasPath; /** * A dotted separated string representing the path of entity properties @@ -77,14 +79,14 @@ class EagerLoadable * * @var string|null */ - protected $_propertyPath; + protected ?string $_propertyPath = null; /** * Whether this level can be fetched using a join. * * @var bool */ - protected $_canBeJoined = false; + protected bool $_canBeJoined = false; /** * Whether this level was meant for a "matching" fetch @@ -92,7 +94,7 @@ class EagerLoadable * * @var bool|null */ - protected $_forMatching; + protected ?bool $_forMatching = null; /** * The property name where the association result should be nested @@ -108,7 +110,7 @@ class EagerLoadable * * @var string|null */ - protected $_targetProperty; + protected ?string $_targetProperty = null; /** * Constructor. The $config parameter accepts the following array @@ -168,12 +170,12 @@ public function associations(): array * Gets the Association class instance to use for loading the records. * * @return \Cake\ORM\Association - * @throws \RuntimeException + * @throws \Cake\Database\Exception\DatabaseException */ public function instance(): Association { if ($this->_instance === null) { - throw new \RuntimeException('No instance set.'); + throw new DatabaseException('No instance set.'); } return $this->_instance; diff --git a/app/vendor/cakephp/cakephp/src/ORM/EagerLoader.php b/app/vendor/cakephp/cakephp/src/ORM/EagerLoader.php index 21e337dcd..665483fe9 100644 --- a/app/vendor/cakephp/cakephp/src/ORM/EagerLoader.php +++ b/app/vendor/cakephp/cakephp/src/ORM/EagerLoader.php @@ -16,9 +16,7 @@ */ namespace Cake\ORM; -use Cake\Database\Statement\BufferedStatement; -use Cake\Database\Statement\CallbackStatement; -use Cake\Database\StatementInterface; +use Cake\ORM\Query\SelectQuery; use Closure; use InvalidArgumentException; @@ -36,7 +34,7 @@ class EagerLoader * * @var array */ - protected $_containments = []; + protected array $_containments = []; /** * Contains a nested array with the compiled containments tree @@ -44,15 +42,15 @@ class EagerLoader * * @var \Cake\ORM\EagerLoadable|array<\Cake\ORM\EagerLoadable>|null */ - protected $_normalized; + protected EagerLoadable|array|null $_normalized = null; /** * List of options accepted by associations in contain() - * index by key for faster access + * index by key for faster access. * * @var array */ - protected $_containOptions = [ + protected array $_containOptions = [ 'associations' => 1, 'foreignKey' => 1, 'conditions' => 1, @@ -68,25 +66,25 @@ class EagerLoader ]; /** - * A list of associations that should be loaded with a separate query + * A list of associations that should be loaded with a separate query. * * @var array<\Cake\ORM\EagerLoadable> */ - protected $_loadExternal = []; + protected array $_loadExternal = []; /** - * Contains a list of the association names that are to be eagerly loaded + * Contains a list of the association names that are to be eagerly loaded. * * @var array */ - protected $_aliasList = []; + protected array $_aliasList = []; /** * Another EagerLoader instance that will be used for 'matching' associations. * * @var \Cake\ORM\EagerLoader|null */ - protected $_matching; + protected ?EagerLoader $_matching = null; /** * A map of table aliases pointing to the association objects they represent @@ -94,16 +92,15 @@ class EagerLoader * * @var array */ - protected $_joinsMap = []; + protected array $_joinsMap = []; /** - * Controls whether fields from associated tables - * will be eagerly loaded. When set to false, no fields will - * be loaded from associations. + * Controls whether fields from associated tables will be eagerly loaded. + * When set to false, no fields will be loaded from associations. * * @var bool */ - protected $_autoFields = true; + protected bool $_autoFields = true; /** * Sets the list of associations that should be eagerly loaded along for a @@ -117,28 +114,28 @@ class EagerLoader * * Accepted options per passed association: * - * - foreignKey: Used to set a different field to match both tables, if set to false + * - `foreignKey`: Used to set a different field to match both tables, if set to false * no join conditions will be generated automatically - * - fields: An array with the fields that should be fetched from the association - * - queryBuilder: Equivalent to passing a callable instead of an options array - * - matching: Whether to inform the association class that it should filter the + * - `fields`: An array with the fields that should be fetched from the association + * - `queryBuilder`: Equivalent to passing a callback instead of an options array + * - `matching`: Whether to inform the association class that it should filter the * main query by the results fetched by that class. - * - joinType: For joinable associations, the SQL join type to use. - * - strategy: The loading strategy to use (join, select, subquery) + * - `joinType`: For joinable associations, the SQL join type to use. + * - `strategy`: The loading strategy to use (join, select, subquery) * - * @param array|string $associations list of table aliases to be queried. + * @param array|string $associations List of table aliases to be queried. * When this method is called multiple times it will merge previous list with * the new one. - * @param callable|null $queryBuilder The query builder callable + * @param \Closure|null $queryBuilder The query builder callback. * @return array Containments. * @throws \InvalidArgumentException When using $queryBuilder with an array of $associations */ - public function contain($associations, ?callable $queryBuilder = null): array + public function contain(array|string $associations, ?Closure $queryBuilder = null): array { if ($queryBuilder) { if (!is_string($associations)) { throw new InvalidArgumentException( - 'Cannot set containments. To use $queryBuilder, $associations must be a string' + 'Cannot set containments. To use $queryBuilder, $associations must be a string', ); } @@ -235,19 +232,17 @@ public function isAutoFieldsEnabled(): bool * - `fields`: Fields to contain * - `negateMatch`: Whether to add conditions negate match on target association * - * @param string $associationPath Dot separated association path, 'Name1.Name2.Name3' - * @param callable|null $builder the callback function to be used for setting extra - * options to the filtering query + * @param string $associationPath Dot separated association path, 'Name1.Name2.Name3'. + * @param \Closure|null $builder the callback function to be used for setting extra + * options to the filtering query. * @param array $options Extra options for the association matching. * @return $this */ - public function setMatching(string $associationPath, ?callable $builder = null, array $options = []) + public function setMatching(string $associationPath, ?Closure $builder = null, array $options = []) { - if ($this->_matching === null) { - $this->_matching = new static(); - } + $this->_matching ??= new static(); - $options += ['joinType' => Query::JOIN_TYPE_INNER]; + $options += ['joinType' => SelectQuery::JOIN_TYPE_INNER]; $sharedOptions = ['negateMatch' => false, 'matching' => true] + $options; $contains = []; @@ -269,13 +264,11 @@ public function setMatching(string $associationPath, ?callable $builder = null, /** * Returns the current tree of associations to be matched. * - * @return array The resulting containments array + * @return array The resulting containments array. */ public function getMatching(): array { - if ($this->_matching === null) { - $this->_matching = new static(); - } + $this->_matching ??= new static(); return $this->_matching->getContain(); } @@ -293,12 +286,12 @@ public function getMatching(): array * association instance from the corresponding source table * * @param \Cake\ORM\Table $repository The table containing the association that - * will be normalized + * will be normalized. * @return array */ public function normalized(Table $repository): array { - if ($this->_normalized !== null || empty($this->_containments)) { + if ($this->_normalized !== null || $this->_containments === []) { return (array)$this->_normalized; } @@ -312,7 +305,7 @@ public function normalized(Table $repository): array $repository, $alias, $options, - ['root' => null] + ['root' => null], ); } @@ -322,11 +315,11 @@ public function normalized(Table $repository): array /** * Formats the containments array so that associations are always set as keys * in the array. This function merges the original associations array with - * the new associations provided + * the new associations provided. * - * @param array $associations user provided containments array + * @param array $associations User provided containments array. * @param array $original The original containments array to merge - * with the new one + * with the new one. * @return array */ protected function _reformatContain(array $associations, array $original): array @@ -351,7 +344,7 @@ protected function _reformatContain(array $associations, array $original): array continue; } - if (strpos($table, '.')) { + if (str_contains($table, '.')) { $path = explode('.', $table); $table = array_pop($path); foreach ($path as $t) { @@ -366,7 +359,7 @@ protected function _reformatContain(array $associations, array $original): array $options; $options = $this->_reformatContain( $options, - $pointer[$table] ?? [] + $pointer[$table] ?? [], ); } @@ -379,13 +372,10 @@ protected function _reformatContain(array $associations, array $original): array if (isset($options['queryBuilder'], $pointer[$table]['queryBuilder'])) { $first = $pointer[$table]['queryBuilder']; $second = $options['queryBuilder']; - $options['queryBuilder'] = function ($query) use ($first, $second) { - return $second($first($query)); - }; + $options['queryBuilder'] = fn($query) => $second($first($query)); } if (!is_array($options)) { - /** @psalm-suppress InvalidArrayOffset */ $options = [$options => []]; } @@ -401,16 +391,16 @@ protected function _reformatContain(array $associations, array $original): array * This method will not modify the query for loading external associations, i.e. * those that cannot be loaded without executing a separate query. * - * @param \Cake\ORM\Query $query The query to be modified + * @param \Cake\ORM\Query\SelectQuery $query The query to be modified. * @param \Cake\ORM\Table $repository The repository containing the associations * @param bool $includeFields whether to append all fields from the associations * to the passed query. This can be overridden according to the settings defined - * per association in the containments array + * per association in the containments array. * @return void */ - public function attachAssociations(Query $query, Table $repository, bool $includeFields): void + public function attachAssociations(SelectQuery $query, Table $repository, bool $includeFields): void { - if (empty($this->_containments) && $this->_matching === null) { + if (!$this->_containments && $this->_matching === null) { return; } @@ -429,7 +419,7 @@ public function attachAssociations(Query $query, Table $repository, bool $includ $newAttachable = $this->attachableAssociations($repository); $attachable = array_diff_key($newAttachable, $processed); - } while (!empty($attachable)); + } while ($attachable !== []); } /** @@ -438,7 +428,7 @@ public function attachAssociations(Query $query, Table $repository, bool $includ * with Cake\ORM\EagerLoadable objects. * * @param \Cake\ORM\Table $repository The table containing the associations to be - * attached + * attached. * @return array<\Cake\ORM\EagerLoadable> */ public function attachableAssociations(Table $repository): array @@ -456,7 +446,7 @@ public function attachableAssociations(Table $repository): array * separate query, each array value will contain a {@link \Cake\ORM\EagerLoadable} object. * * @param \Cake\ORM\Table $repository The table containing the associations - * to be loaded + * to be loaded. * @return array<\Cake\ORM\EagerLoadable> */ public function externalAssociations(Table $repository): array @@ -472,11 +462,11 @@ public function externalAssociations(Table $repository): array /** * Auxiliary function responsible for fully normalizing deep associations defined - * using `contain()` + * using `contain()`. * - * @param \Cake\ORM\Table $parent owning side of the association - * @param string $alias name of the association to be loaded - * @param array $options list of extra options to use for this association + * @param \Cake\ORM\Table $parent Owning side of the association. + * @param string $alias Name of the association to be loaded. + * @param array $options List of extra options to use for this association. * @param array $paths An array with two values, the first one is a list of dot * separated strings representing associations that lead to this `$alias` in the * chain of associations to be loaded. The second value is the path to follow in @@ -524,7 +514,7 @@ protected function _normalizeContain(Table $parent, string $alias, array $option foreach ($extra as $t => $assoc) { $eagerLoadable->addAssociation( $t, - $this->_normalizeContain($table, $t, $assoc, $paths) + $this->_normalizeContain($table, $t, $assoc, $paths), ); } @@ -547,9 +537,9 @@ protected function _fixStrategies(): void if (count($configs) < 2) { continue; } - /** @var \Cake\ORM\EagerLoadable $loadable */ foreach ($configs as $loadable) { - if (strpos($loadable->aliasPath(), '.')) { + assert($loadable instanceof EagerLoadable); + if (str_contains($loadable->aliasPath(), '.')) { $this->_correctStrategy($loadable); } } @@ -559,18 +549,17 @@ protected function _fixStrategies(): void /** * Changes the association fetching strategy if required because of duplicate - * under the same direct associations chain + * under the same direct associations chain. * - * @param \Cake\ORM\EagerLoadable $loadable The association config + * @param \Cake\ORM\EagerLoadable $loadable The association config. * @return void */ protected function _correctStrategy(EagerLoadable $loadable): void { $config = $loadable->getConfig(); - $currentStrategy = $config['strategy'] ?? - 'join'; + $currentStrategy = $config['strategy'] ?? Association::STRATEGY_JOIN; - if (!$loadable->canBeJoined() || $currentStrategy !== 'join') { + if (!$loadable->canBeJoined() || $currentStrategy !== Association::STRATEGY_JOIN) { return; } @@ -583,8 +572,8 @@ protected function _correctStrategy(EagerLoadable $loadable): void * Helper function used to compile a list of all associations that can be * joined in the query. * - * @param array<\Cake\ORM\EagerLoadable> $associations list of associations from which to obtain joins. - * @param array<\Cake\ORM\EagerLoadable> $matching list of associations that should be forcibly joined. + * @param array<\Cake\ORM\EagerLoadable> $associations List of associations from which to obtain joins. + * @param array<\Cake\ORM\EagerLoadable> $matching List of associations that should be forcibly joined. * @return array<\Cake\ORM\EagerLoadable> */ protected function _resolveJoins(array $associations, array $matching = []): array @@ -614,30 +603,33 @@ protected function _resolveJoins(array $associations, array $matching = []): arr } /** - * Decorates the passed statement object in order to inject data from associations - * that cannot be joined directly. + * Inject data from associations that cannot be joined directly. * - * @param \Cake\ORM\Query $query The query for which to eager load external - * associations - * @param \Cake\Database\StatementInterface $statement The statement created after executing the $query - * @return \Cake\Database\StatementInterface statement modified statement with extra loaders + * @param \Cake\ORM\Query\SelectQuery $query The query for which to eager load external. + * associations. + * @param iterable $results Results. + * @return iterable * @throws \RuntimeException */ - public function loadExternal(Query $query, StatementInterface $statement): StatementInterface + public function loadExternal(SelectQuery $query, iterable $results): iterable { - $table = $query->getRepository(); - $external = $this->externalAssociations($table); - if (empty($external)) { - return $statement; + if (!$results) { + return $results; } - $driver = $query->getConnection()->getDriver($query->getConnectionRole()); - [$collected, $statement] = $this->_collectKeys($external, $query, $statement); + $external = $this->externalAssociations($query->getRepository()); + if (!$external) { + return $results; + } - // No records found, skip trying to attach associations. - if (empty($collected) && $statement->count() === 0) { - return $statement; + if (!is_array($results)) { + $results = iterator_to_array($results); } + if (!$results) { + return $results; + } + + $collected = $this->_collectKeys($external, $query, $results); foreach ($external as $meta) { $contain = $meta->associations(); @@ -652,7 +644,7 @@ public function loadExternal(Query $query, StatementInterface $statement): State // Nested paths are not subject to this condition because they could // be attached to joined associations. if ( - strpos($path, '.') === false && + !str_contains($path, '.') && (!array_key_exists($path, $collected) || !array_key_exists($alias, $collected[$path])) ) { $message = "Unable to load `{$path}` association. Ensure foreign key in `{$alias}` is selected."; @@ -667,44 +659,45 @@ public function loadExternal(Query $query, StatementInterface $statement): State } $keys = $collected[$path][$alias] ?? null; - $f = $instance->eagerLoader( + $callback = $instance->eagerLoader( $config + [ 'query' => $query, 'contain' => $contain, 'keys' => $keys, 'nestKey' => $meta->aliasPath(), - ] + ], ); - $statement = new CallbackStatement($statement, $driver, $f); + $results = array_map($callback, $results); } - return $statement; + return $results; } /** * Returns an array having as keys a dotted path of associations that participate - * in this eager loader. The values of the array will contain the following keys + * in this eager loader. The values of the array will contain the following keys: * - * - alias: The association alias - * - instance: The association instance - * - canBeJoined: Whether the association will be loaded using a JOIN - * - entityClass: The entity that should be used for hydrating the results - * - nestKey: A dotted path that can be used to correctly insert the data into the results. - * - matching: Whether it is an association loaded through `matching()`. + * - `alias`: The association alias + * - `instance`: The association instance + * - `canBeJoined`: Whether the association will be loaded using a JOIN + * - `entityClass`: The entity that should be used for hydrating the results + * - `nestKey`: A dotted path that can be used to correctly insert the data into the results. + * - `matching`: Whether it is an association loaded through `matching()`. * * @param \Cake\ORM\Table $table The table containing the association that - * will be normalized + * will be normalized. * @return array */ public function associationsMap(Table $table): array { $map = []; - if (!$this->getMatching() && !$this->getContain() && empty($this->_joinsMap)) { + if (!$this->getMatching() && !$this->getContain() && $this->_joinsMap === []) { return $map; } - /** @psalm-suppress PossiblyNullReference */ + assert($this->_matching !== null, 'EagerLoader not available'); + $map = $this->_buildAssociationsMap($map, $this->_matching->normalized($table), true); $map = $this->_buildAssociationsMap($map, $this->normalized($table)); @@ -751,7 +744,7 @@ protected function _buildAssociationsMap(array $map, array $level, bool $matchin * * @param string $alias The table alias as it appears in the query. * @param \Cake\ORM\Association $assoc The association object the alias represents; - * will be normalized + * will be normalized. * @param bool $asMatching Whether this join results should be treated as a * 'matching' association. * @param string|null $targetProperty The property name where the results of the join should be nested at. @@ -762,7 +755,7 @@ public function addToJoinsMap( string $alias, Association $assoc, bool $asMatching = false, - ?string $targetProperty = null + ?string $targetProperty = null, ): void { $this->_joinsMap[$alias] = new EagerLoadable($alias, [ 'aliasPath' => $alias, @@ -777,12 +770,12 @@ public function addToJoinsMap( * Helper function used to return the keys from the query records that will be used * to eagerly load associations. * - * @param array<\Cake\ORM\EagerLoadable> $external the list of external associations to be loaded - * @param \Cake\ORM\Query $query The query from which the results where generated - * @param \Cake\Database\StatementInterface $statement The statement to work on + * @param array<\Cake\ORM\EagerLoadable> $external The list of external associations to be loaded. + * @param \Cake\ORM\Query\SelectQuery $query The query from which the results where generated. + * @param array $results Results array. * @return array */ - protected function _collectKeys(array $external, Query $query, $statement): array + protected function _collectKeys(array $external, SelectQuery $query, array $results): array { $collectKeys = []; foreach ($external as $meta) { @@ -798,34 +791,31 @@ protected function _collectKeys(array $external, Query $query, $statement): arra $alias = $source->getAlias(); $pkFields = []; + /** @var string $key */ foreach ($keys as $key) { $pkFields[] = key($query->aliasField($key, $alias)); } $collectKeys[$meta->aliasPath()] = [$alias, $pkFields, count($pkFields) === 1]; } - if (empty($collectKeys)) { - return [[], $statement]; - } - - if (!($statement instanceof BufferedStatement)) { - $statement = new BufferedStatement($statement, $query->getConnection()->getDriver()); + if (!$collectKeys) { + return []; } - return [$this->_groupKeys($statement, $collectKeys), $statement]; + return $this->_groupKeys($results, $collectKeys); } /** * Helper function used to iterate a statement and extract the columns - * defined in $collectKeys + * defined in $collectKeys. * - * @param \Cake\Database\Statement\BufferedStatement $statement The statement to read from. - * @param array $collectKeys The keys to collect + * @param array $results Results array. + * @param array $collectKeys The keys to collect. * @return array */ - protected function _groupKeys(BufferedStatement $statement, array $collectKeys): array + protected function _groupKeys(array $results, array $collectKeys): array { $keys = []; - foreach (($statement->fetchAll('assoc') ?: []) as $result) { + foreach ($results as $result) { foreach ($collectKeys as $nestKey => $parts) { if ($parts[2] === true) { // Missed joins will have null in the results. @@ -852,7 +842,6 @@ protected function _groupKeys(BufferedStatement $statement, array $collectKeys): $keys[$nestKey][$parts[0]][implode(';', $collected)] = $collected; } } - $statement->rewind(); return $keys; } diff --git a/app/vendor/cakephp/cakephp/src/ORM/Entity.php b/app/vendor/cakephp/cakephp/src/ORM/Entity.php index 14e5d57ea..48c55fac1 100644 --- a/app/vendor/cakephp/cakephp/src/ORM/Entity.php +++ b/app/vendor/cakephp/cakephp/src/ORM/Entity.php @@ -57,7 +57,7 @@ public function __construct(array $properties = [], array $options = []) 'source' => null, ]; - if (!empty($options['source'])) { + if ($options['source'] !== null) { $this->setSource($options['source']); } @@ -65,14 +65,18 @@ public function __construct(array $properties = [], array $options = []) $this->setNew($options['markNew']); } - if (!empty($properties) && $options['markClean'] && !$options['useSetters']) { - $this->_fields = $properties; + if ($properties) { + //Remember the original field names here. + $this->setOriginalField(array_keys($properties)); - return; - } + if ($options['markClean'] && !$options['useSetters']) { + $this->_fields = $properties; + + return; + } - if (!empty($properties)) { - $this->set($properties, [ + $this->patch($properties, [ + 'asOriginal' => true, 'setter' => $options['useSetters'], 'guard' => $options['guard'], ]); diff --git a/app/vendor/cakephp/cakephp/src/ORM/Exception/MissingBehaviorException.php b/app/vendor/cakephp/cakephp/src/ORM/Exception/MissingBehaviorException.php index fe6411eb0..a9fd0ade5 100644 --- a/app/vendor/cakephp/cakephp/src/ORM/Exception/MissingBehaviorException.php +++ b/app/vendor/cakephp/cakephp/src/ORM/Exception/MissingBehaviorException.php @@ -24,5 +24,5 @@ class MissingBehaviorException extends CakeException /** * @var string */ - protected $_messageTemplate = 'Behavior class %s could not be found.'; + protected string $_messageTemplate = 'Behavior class `%s` could not be found.'; } diff --git a/app/vendor/cakephp/cakephp/src/ORM/Exception/MissingEntityException.php b/app/vendor/cakephp/cakephp/src/ORM/Exception/MissingEntityException.php index c363c123c..902cf6b18 100644 --- a/app/vendor/cakephp/cakephp/src/ORM/Exception/MissingEntityException.php +++ b/app/vendor/cakephp/cakephp/src/ORM/Exception/MissingEntityException.php @@ -28,5 +28,5 @@ class MissingEntityException extends CakeException /** * @var string */ - protected $_messageTemplate = 'Entity class %s could not be found.'; + protected string $_messageTemplate = 'Entity class `%s` could not be found.'; } diff --git a/app/vendor/cakephp/cakephp/src/ORM/Exception/MissingTableClassException.php b/app/vendor/cakephp/cakephp/src/ORM/Exception/MissingTableClassException.php index accf334b2..5bea8ef3f 100644 --- a/app/vendor/cakephp/cakephp/src/ORM/Exception/MissingTableClassException.php +++ b/app/vendor/cakephp/cakephp/src/ORM/Exception/MissingTableClassException.php @@ -26,5 +26,5 @@ class MissingTableClassException extends CakeException /** * @var string */ - protected $_messageTemplate = 'Table class %s could not be found.'; + protected string $_messageTemplate = 'Table class %s could not be found.'; } diff --git a/app/vendor/cakephp/cakephp/src/ORM/Exception/PersistenceFailedException.php b/app/vendor/cakephp/cakephp/src/ORM/Exception/PersistenceFailedException.php index 291d8ced5..1e56c6a8f 100644 --- a/app/vendor/cakephp/cakephp/src/ORM/Exception/PersistenceFailedException.php +++ b/app/vendor/cakephp/cakephp/src/ORM/Exception/PersistenceFailedException.php @@ -29,12 +29,12 @@ class PersistenceFailedException extends CakeException * * @var \Cake\Datasource\EntityInterface */ - protected $_entity; + protected EntityInterface $_entity; /** * @inheritDoc */ - protected $_messageTemplate = 'Entity %s failure.'; + protected string $_messageTemplate = 'Entity %s failure.'; /** * Constructor. @@ -45,8 +45,12 @@ class PersistenceFailedException extends CakeException * @param int|null $code The code of the error, is also the HTTP status code for the error. * @param \Throwable|null $previous the previous exception. */ - public function __construct(EntityInterface $entity, $message, ?int $code = null, ?Throwable $previous = null) - { + public function __construct( + EntityInterface $entity, + array|string $message, + ?int $code = null, + ?Throwable $previous = null, + ) { $this->_entity = $entity; if (is_array($message)) { $errors = []; diff --git a/app/vendor/cakephp/cakephp/src/ORM/Exception/RolledbackTransactionException.php b/app/vendor/cakephp/cakephp/src/ORM/Exception/RolledbackTransactionException.php index b1f107bd8..c542b1fa9 100644 --- a/app/vendor/cakephp/cakephp/src/ORM/Exception/RolledbackTransactionException.php +++ b/app/vendor/cakephp/cakephp/src/ORM/Exception/RolledbackTransactionException.php @@ -24,6 +24,6 @@ class RolledbackTransactionException extends CakeException /** * @var string */ - protected $_messageTemplate = 'The afterSave event in "%s" is aborting the transaction' + protected string $_messageTemplate = 'The afterSave event in `%s` is aborting the transaction' . ' before the save process is done.'; } diff --git a/app/vendor/cakephp/cakephp/src/ORM/LazyEagerLoader.php b/app/vendor/cakephp/cakephp/src/ORM/LazyEagerLoader.php index 0e7a0103d..431459f53 100644 --- a/app/vendor/cakephp/cakephp/src/ORM/LazyEagerLoader.php +++ b/app/vendor/cakephp/cakephp/src/ORM/LazyEagerLoader.php @@ -16,10 +16,11 @@ */ namespace Cake\ORM; -use Cake\Collection\Collection; -use Cake\Collection\CollectionInterface; +use Cake\Database\Expression\QueryExpression; use Cake\Database\Expression\TupleComparison; use Cake\Datasource\EntityInterface; +use Cake\ORM\Query\SelectQuery; +use Cake\Utility\Hash; /** * Contains methods that are capable of injecting eagerly loaded associations into @@ -42,7 +43,7 @@ class LazyEagerLoader * @param \Cake\ORM\Table $source The table to use for fetching the top level entities * @return \Cake\Datasource\EntityInterface|array<\Cake\Datasource\EntityInterface> */ - public function loadInto($entities, array $contain, Table $source) + public function loadInto(EntityInterface|array $entities, array $contain, Table $source): EntityInterface|array { $returnSingle = false; @@ -51,12 +52,12 @@ public function loadInto($entities, array $contain, Table $source) $returnSingle = true; } - $entities = new Collection($entities); $query = $this->_getQuery($entities, $contain, $source); $associations = array_keys($query->getContain()); $entities = $this->_injectResults($entities, $query, $associations, $source); + /** @var \Cake\Datasource\EntityInterface|array<\Cake\Datasource\EntityInterface> */ return $returnSingle ? array_shift($entities) : $entities; } @@ -64,40 +65,34 @@ public function loadInto($entities, array $contain, Table $source) * Builds a query for loading the passed list of entity objects along with the * associations specified in $contain. * - * @param \Cake\Collection\CollectionInterface $objects The original entities + * @param array<\Cake\Datasource\EntityInterface> $entities The original entities * @param array $contain The associations to be loaded * @param \Cake\ORM\Table $source The table to use for fetching the top level entities - * @return \Cake\ORM\Query + * @return \Cake\ORM\Query\SelectQuery */ - protected function _getQuery(CollectionInterface $objects, array $contain, Table $source): Query + protected function _getQuery(array $entities, array $contain, Table $source): SelectQuery { $primaryKey = $source->getPrimaryKey(); $method = is_string($primaryKey) ? 'get' : 'extract'; - $keys = $objects->map(function ($entity) use ($primaryKey, $method) { - return $entity->{$method}($primaryKey); - }); + $keys = Hash::map($entities, '{*}', fn(EntityInterface $entity) => $entity->{$method}($primaryKey)); $query = $source ->find() ->select((array)$primaryKey) - ->where(function ($exp, $q) use ($primaryKey, $keys, $source) { - /** - * @var \Cake\Database\Expression\QueryExpression $exp - * @var \Cake\ORM\Query $q - */ + ->where(function (QueryExpression $exp, SelectQuery $q) use ($primaryKey, $keys, $source) { if (is_array($primaryKey) && count($primaryKey) === 1) { $primaryKey = current($primaryKey); } if (is_string($primaryKey)) { - return $exp->in($source->aliasField($primaryKey), $keys->toList()); + return $exp->in($source->aliasField($primaryKey), $keys); } $types = array_intersect_key($q->getDefaultTypes(), array_flip($primaryKey)); - $primaryKey = array_map([$source, 'aliasField'], $primaryKey); + $primaryKey = array_map($source->aliasField(...), $primaryKey); - return new TupleComparison($primaryKey, $keys->toList(), $types, 'IN'); + return new TupleComparison($primaryKey, $keys, $types, 'IN'); }) ->enableAutoFields() ->contain($contain); @@ -117,15 +112,16 @@ protected function _getQuery(CollectionInterface $objects, array $contain, Table * * @param \Cake\ORM\Table $source The table having the top level associations * @param array $associations The name of the top level associations - * @return array + * @return array */ protected function _getPropertyMap(Table $source, array $associations): array { $map = []; $container = $source->associations(); foreach ($associations as $assoc) { - /** @psalm-suppress PossiblyNullReference */ - $map[$assoc] = $container->get($assoc)->getProperty(); + /** @var \Cake\ORM\Association $association */ + $association = $container->get($assoc); + $map[$assoc] = $association->getProperty(); } return $map; @@ -135,33 +131,34 @@ protected function _getPropertyMap(Table $source, array $associations): array * Injects the results of the eager loader query into the original list of * entities. * - * @param iterable<\Cake\Datasource\EntityInterface> $objects The original list of entities - * @param \Cake\ORM\Query $results The loaded results + * @param array<\Cake\Datasource\EntityInterface> $entities The original list of entities + * @param \Cake\ORM\Query\SelectQuery $query The query to load results * @param array $associations The top level associations that were loaded * @param \Cake\ORM\Table $source The table where the entities came from * @return array<\Cake\Datasource\EntityInterface> */ - protected function _injectResults(iterable $objects, $results, array $associations, Table $source): array - { + protected function _injectResults( + array $entities, + SelectQuery $query, + array $associations, + Table $source, + ): array { $injected = []; $properties = $this->_getPropertyMap($source, $associations); $primaryKey = (array)$source->getPrimaryKey(); - $results = $results + /** @var array<\Cake\Datasource\EntityInterface> $results */ + $results = $query ->all() - ->indexBy(function ($e) use ($primaryKey) { - /** @var \Cake\Datasource\EntityInterface $e */ - return implode(';', $e->extract($primaryKey)); - }) + ->indexBy(fn(EntityInterface $e) => implode(';', $e->extract($primaryKey))) ->toArray(); - foreach ($objects as $k => $object) { + foreach ($entities as $k => $object) { $key = implode(';', $object->extract($primaryKey)); if (!isset($results[$key])) { $injected[$k] = $object; continue; } - /** @var \Cake\Datasource\EntityInterface $loaded */ $loaded = $results[$key]; foreach ($associations as $assoc) { $property = $properties[$assoc]; diff --git a/app/vendor/cakephp/cakephp/src/ORM/Locator/LocatorAwareTrait.php b/app/vendor/cakephp/cakephp/src/ORM/Locator/LocatorAwareTrait.php index 1aa4a9e39..204934cc7 100644 --- a/app/vendor/cakephp/cakephp/src/ORM/Locator/LocatorAwareTrait.php +++ b/app/vendor/cakephp/cakephp/src/ORM/Locator/LocatorAwareTrait.php @@ -30,14 +30,14 @@ trait LocatorAwareTrait * * @var string|null */ - protected $defaultTable = null; + protected ?string $defaultTable = null; /** * Table locator instance * * @var \Cake\ORM\Locator\LocatorInterface|null */ - protected $_tableLocator; + protected ?LocatorInterface $_tableLocator = null; /** * Sets the table locator. @@ -59,13 +59,17 @@ public function setTableLocator(LocatorInterface $tableLocator) */ public function getTableLocator(): LocatorInterface { - if ($this->_tableLocator === null) { - /** @psalm-suppress InvalidPropertyAssignmentValue */ - $this->_tableLocator = FactoryLocator::get('Table'); + if ($this->_tableLocator !== null) { + return $this->_tableLocator; } - /** @var \Cake\ORM\Locator\LocatorInterface */ - return $this->_tableLocator; + $locator = FactoryLocator::get('Table'); + assert( + $locator instanceof LocatorInterface, + '`FactoryLocator` must return an instance of Cake\ORM\LocatorInterface for type `Table`.', + ); + + return $this->_tableLocator = $locator; } /** @@ -82,10 +86,10 @@ public function getTableLocator(): LocatorInterface */ public function fetchTable(?string $alias = null, array $options = []): Table { - $alias = $alias ?? $this->defaultTable; - if (empty($alias)) { + $alias ??= $this->defaultTable; + if (!$alias) { throw new UnexpectedValueException( - 'You must provide an `$alias` or set the `$defaultTable` property to a non empty string.' + 'You must provide an `$alias` or set the `$defaultTable` property to a non empty string.', ); } diff --git a/app/vendor/cakephp/cakephp/src/ORM/Locator/LocatorInterface.php b/app/vendor/cakephp/cakephp/src/ORM/Locator/LocatorInterface.php index 24516e8ed..15d0ba2c4 100644 --- a/app/vendor/cakephp/cakephp/src/ORM/Locator/LocatorInterface.php +++ b/app/vendor/cakephp/cakephp/src/ORM/Locator/LocatorInterface.php @@ -45,7 +45,7 @@ public function getConfig(?string $alias = null): array; * @throws \RuntimeException When you attempt to configure an existing * table instance. */ - public function setConfig($alias, $options = null); + public function setConfig(array|string $alias, ?array $options = null); /** * Get a table instance from the registry. @@ -62,7 +62,6 @@ public function get(string $alias, array $options = []): Table; * @param string $alias The alias to set. * @param \Cake\ORM\Table $repository The table to set. * @return \Cake\ORM\Table - * @psalm-suppress MoreSpecificImplementedParamType */ public function set(string $alias, RepositoryInterface $repository): Table; } diff --git a/app/vendor/cakephp/cakephp/src/ORM/Locator/TableLocator.php b/app/vendor/cakephp/cakephp/src/ORM/Locator/TableLocator.php index 3fefe3f40..0f9a7f658 100644 --- a/app/vendor/cakephp/cakephp/src/ORM/Locator/TableLocator.php +++ b/app/vendor/cakephp/cakephp/src/ORM/Locator/TableLocator.php @@ -17,14 +17,15 @@ namespace Cake\ORM\Locator; use Cake\Core\App; +use Cake\Database\Exception\DatabaseException; use Cake\Datasource\ConnectionManager; use Cake\Datasource\Locator\AbstractLocator; use Cake\Datasource\RepositoryInterface; use Cake\ORM\AssociationCollection; use Cake\ORM\Exception\MissingTableClassException; +use Cake\ORM\Query\QueryFactory; use Cake\ORM\Table; use Cake\Utility\Inflector; -use RuntimeException; use function Cake\Core\pluginSplit; /** @@ -37,21 +38,21 @@ class TableLocator extends AbstractLocator implements LocatorInterface * * @var array */ - protected $locations = []; + protected array $locations = []; /** * Configuration for aliases. * * @var array */ - protected $_config = []; + protected array $_config = []; /** * Instances that belong to the registry. * * @var array */ - protected $instances = []; + protected array $instances = []; /** * Contains a list of Table objects that were created out of the @@ -59,22 +60,24 @@ class TableLocator extends AbstractLocator implements LocatorInterface * * @var array<\Cake\ORM\Table> */ - protected $_fallbacked = []; + protected array $_fallbacked = []; /** * Fallback class to use * * @var string - * @psalm-var class-string<\Cake\ORM\Table> + * @phpstan-var class-string<\Cake\ORM\Table> */ - protected $fallbackClassName = Table::class; + protected string $fallbackClassName = Table::class; /** * Whether fallback class should be used if a table class could not be found. * * @var bool */ - protected $allowFallbackClass = true; + protected bool $allowFallbackClass = true; + + protected QueryFactory $queryFactory; /** * Constructor. @@ -82,7 +85,7 @@ class TableLocator extends AbstractLocator implements LocatorInterface * @param array|null $locations Locations where tables should be looked for. * If none provided, the default `Model\Table` under your app's namespace is used. */ - public function __construct(?array $locations = null) + public function __construct(?array $locations = null, ?QueryFactory $queryFactory = null) { if ($locations === null) { $locations = [ @@ -93,6 +96,8 @@ public function __construct(?array $locations = null) foreach ($locations as $location) { $this->addLocation($location); } + + $this->queryFactory = $queryFactory ?: new QueryFactory(); } /** @@ -120,9 +125,9 @@ public function allowFallbackClass(bool $allow) * * @param string $className Fallback class name * @return $this - * @psalm-param class-string<\Cake\ORM\Table> $className + * @phpstan-param class-string<\Cake\ORM\Table> $className */ - public function setFallbackClassName($className) + public function setFallbackClassName(string $className) { $this->fallbackClassName = $className; @@ -132,7 +137,7 @@ public function setFallbackClassName($className) /** * @inheritDoc */ - public function setConfig($alias, $options = null) + public function setConfig(array|string $alias, ?array $options = null) { if (!is_string($alias)) { $this->_config = $alias; @@ -141,9 +146,9 @@ public function setConfig($alias, $options = null) } if (isset($this->instances[$alias])) { - throw new RuntimeException(sprintf( - 'You cannot configure "%s", it has already been constructed.', - $alias + throw new DatabaseException(sprintf( + 'You cannot configure `%s`, it has already been constructed.', + $alias, )); } @@ -209,9 +214,9 @@ public function get(string $alias, array $options = []): Table /** * @inheritDoc */ - protected function createInstance(string $alias, array $options) + protected function createInstance(string $alias, array $options): Table { - if (strpos($alias, '\\') === false) { + if (!str_contains($alias, '\\')) { [, $classAlias] = pluginSplit($alias); $options = ['alias' => $classAlias] + $options; } elseif (!isset($options['alias'])) { @@ -230,7 +235,7 @@ protected function createInstance(string $alias, array $options) if (empty($options['className'])) { $options['className'] = $alias; } - if (!isset($options['table']) && strpos($options['className'], '\\') === false) { + if (!isset($options['table']) && !str_contains($options['className'], '\\')) { [, $table] = pluginSplit($options['className']); $options['table'] = Inflector::underscore($table); } @@ -238,7 +243,7 @@ protected function createInstance(string $alias, array $options) } else { $message = $options['className'] ?? $alias; $message = '`' . $message . '`'; - if (strpos($message, '\\') === false) { + if (!str_contains($message, '\\')) { $message = 'for alias ' . $message; } throw new MissingTableClassException([$message]); @@ -248,7 +253,7 @@ protected function createInstance(string $alias, array $options) if (!empty($options['connectionName'])) { $connectionName = $options['connectionName']; } else { - /** @var \Cake\ORM\Table $className */ + /** @var class-string<\Cake\ORM\Table> $className */ $className = $options['className']; $connectionName = $className::defaultConnectionName(); } @@ -258,6 +263,9 @@ protected function createInstance(string $alias, array $options) $associations = new AssociationCollection($this); $options['associations'] = $associations; } + if (empty($options['queryFactory'])) { + $options['queryFactory'] = $this->queryFactory; + } $options['registryAlias'] = $alias; $instance = $this->_create($options); @@ -282,7 +290,7 @@ protected function _getClassName(string $alias, array $options = []): ?string $options['className'] = $alias; } - if (strpos($options['className'], '\\') !== false && class_exists($options['className'])) { + if (str_contains($options['className'], '\\') && class_exists($options['className'])) { return $options['className']; } @@ -304,8 +312,10 @@ protected function _getClassName(string $alias, array $options = []): ?string */ protected function _create(array $options): Table { - /** @var \Cake\ORM\Table */ - return new $options['className']($options); + /** @var class-string<\Cake\ORM\Table> $class */ + $class = $options['className']; + + return new $class($options); } /** @@ -314,7 +324,6 @@ protected function _create(array $options): Table * @param string $alias The alias to set. * @param \Cake\ORM\Table $repository The Table to set. * @return \Cake\ORM\Table - * @psalm-suppress MoreSpecificImplementedParamType */ public function set(string $alias, RepositoryInterface $repository): Table { diff --git a/app/vendor/cakephp/cakephp/src/ORM/Marshaller.php b/app/vendor/cakephp/cakephp/src/ORM/Marshaller.php index 2caa7e573..0a5f58db1 100644 --- a/app/vendor/cakephp/cakephp/src/ORM/Marshaller.php +++ b/app/vendor/cakephp/cakephp/src/ORM/Marshaller.php @@ -18,6 +18,7 @@ use ArrayObject; use Cake\Collection\Collection; +use Cake\Database\Expression\QueryExpression; use Cake\Database\Expression\TupleComparison; use Cake\Database\TypeFactory; use Cake\Datasource\EntityInterface; @@ -25,9 +26,6 @@ use Cake\ORM\Association\BelongsToMany; use Cake\Utility\Hash; use InvalidArgumentException; -use RuntimeException; -use function Cake\Core\deprecationWarning; -use function Cake\Core\getTypeName; /** * Contains logic to convert array data into entities. @@ -48,7 +46,7 @@ class Marshaller * * @var \Cake\ORM\Table */ - protected $_table; + protected Table $_table; /** * Constructor. @@ -61,9 +59,9 @@ public function __construct(Table $table) } /** - * Build the map of property => marshalling callable. + * Build the map of property => marshaling callable. * - * @param array $data The data being marshalled. + * @param array $data The data being marshaled. * @param array $options List of options containing the 'associated' key. * @throws \InvalidArgumentException When associations do not exist. * @return array @@ -78,46 +76,59 @@ protected function _buildPropertyMap(array $data, array $options): array $prop = (string)$prop; $columnType = $schema->getColumnType($prop); if ($columnType) { - $map[$prop] = function ($value, $entity) use ($columnType) { - return TypeFactory::build($columnType)->marshal($value); - }; + $map[$prop] = fn($value) => TypeFactory::build($columnType)->marshal($value); } } // Map associations - $options['associated'] = $options['associated'] ?? []; + $options['associated'] ??= []; $include = $this->_normalizeAssociations($options['associated']); foreach ($include as $key => $nested) { if (is_int($key) && is_scalar($nested)) { $key = $nested; $nested = []; } + + $stringifiedKey = (string)$key; // If the key is not a special field like _ids or _joinData // it is a missing association that we should error on. - if (!$this->_table->hasAssociation($key)) { - if (substr($key, 0, 1) !== '_') { + if (!$this->_table->hasAssociation($stringifiedKey)) { + if ( + !str_starts_with($stringifiedKey, '_') + && (!isset($options['junctionProperty']) || $options['junctionProperty'] !== $stringifiedKey) + ) { throw new InvalidArgumentException(sprintf( - 'Cannot marshal data for "%s" association. It is not associated with "%s".', - (string)$key, - $this->_table->getAlias() + 'Cannot marshal data for `%s` association. It is not associated with `%s`.', + $stringifiedKey, + $this->_table->getAlias(), )); } continue; } - $assoc = $this->_table->getAssociation($key); + $assoc = $this->_table->getAssociation($stringifiedKey); if (isset($options['forceNew'])) { $nested['forceNew'] = $options['forceNew']; } if (isset($options['isMerge'])) { - $callback = function ($value, $entity) use ($assoc, $nested) { - /** @var \Cake\Datasource\EntityInterface $entity */ + $callback = function ( + $value, + EntityInterface $entity, + ) use ( + $assoc, + $nested, + ): array|EntityInterface|null { $options = $nested + ['associated' => [], 'association' => $assoc]; - return $this->_mergeAssociation($entity->get($assoc->getProperty()), $assoc, $value, $options); + return $this->_mergeAssociation( + $this->fieldValue($entity, $assoc->getProperty()), + $assoc, + $value, + $options, + ); }; } else { - $callback = function ($value, $entity) use ($assoc, $nested) { + $callback = function ($value, $entity) use ($assoc, $nested): array|EntityInterface|null { $options = $nested + ['associated' => []]; return $this->_marshalAssociation($assoc, $value, $options); @@ -144,7 +155,7 @@ protected function _buildPropertyMap(array $data, array $options): array * * - validate: Set to false to disable validation. Can also be a string of the validator ruleset to be applied. * Defaults to true/default. - * - associated: Associations listed here will be marshalled as well. Defaults to null. + * - associated: Associations listed here will be marshaled as well. Defaults to null. * - fields: An allowed list of fields to be assigned to the entity. If not present, * the accessible fields list in the entity will be used. Defaults to null. * - accessibleFields: A list of fields to allow or deny in entity accessible fields. Defaults to null @@ -170,7 +181,17 @@ protected function _buildPropertyMap(array $data, array $options): array * ]); * ``` * - * @param array $data The data to hydrate. + * ``` + * $result = $marshaller->one($data, [ + * 'associated' => [ + * 'Tags' => [ + * 'associated' => ['DeeperAssoc1', 'DeeperAssoc2'] + * ] + * ] + * ]); + * ``` + * + * @param array $data The data to hydrate. * @param array $options List of options * @return \Cake\Datasource\EntityInterface * @see \Cake\ORM\Table::newEntity() @@ -181,20 +202,21 @@ public function one(array $data, array $options = []): EntityInterface [$data, $options] = $this->_prepareDataAndOptions($data, $options); $primaryKey = (array)$this->_table->getPrimaryKey(); - $entityClass = $this->_table->getEntityClass(); - $entity = new $entityClass(); - $entity->setSource($this->_table->getRegistryAlias()); + $entity = $this->_table->newEmptyEntity(); if (isset($options['accessibleFields'])) { foreach ((array)$options['accessibleFields'] as $key => $value) { $entity->setAccess($key, $value); } } - $errors = $this->_validate($data, $options, true); + $errors = $this->_validate($data, $options['validate'], true); $options['isMerge'] = false; $propertyMap = $this->_buildPropertyMap($data, $options); $properties = []; + /** + * @var string $key + */ foreach ($data as $key => $value) { if (!empty($errors[$key])) { if ($entity instanceof InvalidPropertyInterface) { @@ -204,7 +226,7 @@ public function one(array $data, array $options = []): EntityInterface } if ($value === '' && in_array($key, $primaryKey, true)) { - // Skip marshalling '' for pk fields. + // Skip marshaling '' for pk fields. continue; } if (isset($propertyMap[$key])) { @@ -217,11 +239,15 @@ public function one(array $data, array $options = []): EntityInterface if (isset($options['fields'])) { foreach ((array)$options['fields'] as $field) { if (array_key_exists($field, $properties)) { - $entity->set($field, $properties[$field]); + $entity->set($field, $properties[$field], ['asOriginal' => true]); } } } else { - $entity->set($properties); + if (method_exists($entity, 'patch')) { + $entity->patch($properties, ['asOriginal' => true]); + } else { + $entity->set($properties, ['asOriginal' => true]); + } } // Don't flag clean association entities as @@ -242,45 +268,28 @@ public function one(array $data, array $options = []): EntityInterface * Returns the validation errors for a data set based on the passed options * * @param array $data The data to validate. - * @param array $options The options passed to this marshaller. + * @param string|bool $validator Validator name or `true` for default validator. * @param bool $isNew Whether it is a new entity or one to be updated. * @return array The list of validation errors. * @throws \RuntimeException If no validator can be created. */ - protected function _validate(array $data, array $options, bool $isNew): array + protected function _validate(array $data, string|bool $validator, bool $isNew): array { - if (!$options['validate']) { + if (!$validator) { return []; } - $validator = null; - if ($options['validate'] === true) { - $validator = $this->_table->getValidator(); - } elseif (is_string($options['validate'])) { - $validator = $this->_table->getValidator($options['validate']); - } elseif (is_object($options['validate'])) { - deprecationWarning( - 'Passing validator instance for the `validate` option is deprecated,' - . ' use `ValidatorAwareTrait::setValidator() instead.`' - ); - - /** @var \Cake\Validation\Validator $validator */ - $validator = $options['validate']; - } - - if ($validator === null) { - throw new RuntimeException( - sprintf('validate must be a boolean, a string or an object. Got %s.', getTypeName($options['validate'])) - ); + if ($validator === true) { + $validator = null; } - return $validator->validate($data, $isNew); + return $this->_table->getValidator($validator)->validate($data, $isNew); } /** * Returns data and options prepared to validate and marshall. * - * @param array $data The data to prepare. + * @param array $data The data to prepare. * @param array $options The options passed to this marshaller. * @return array An array containing prepared data and options. */ @@ -309,7 +318,7 @@ protected function _prepareDataAndOptions(array $data, array $options): array * @param array $options List of options. * @return \Cake\Datasource\EntityInterface|array<\Cake\Datasource\EntityInterface>|null */ - protected function _marshalAssociation(Association $assoc, $value, array $options) + protected function _marshalAssociation(Association $assoc, mixed $value, array $options): EntityInterface|array|null { if (!is_array($value)) { return null; @@ -333,7 +342,8 @@ protected function _marshalAssociation(Association $assoc, $value, array $option } } if ($type === Association::MANY_TO_MANY) { - /** @psalm-suppress ArgumentTypeCoercion */ + assert($assoc instanceof BelongsToMany); + return $marshaller->_belongsToMany($assoc, $value, $options); } @@ -347,7 +357,7 @@ protected function _marshalAssociation(Association $assoc, $value, array $option * * - validate: Set to false to disable validation. Can also be a string of the validator ruleset to be applied. * Defaults to true/default. - * - associated: Associations listed here will be marshalled as well. Defaults to null. + * - associated: Associations listed here will be marshaled as well. Defaults to null. * - fields: An allowed list of fields to be assigned to the entity. If not present, * the accessible fields list in the entity will be used. Defaults to null. * - accessibleFields: A list of fields to allow or deny in entity accessible fields. Defaults to null @@ -397,8 +407,11 @@ protected function _belongsToMany(BelongsToMany $assoc, array $data, array $opti $target = $assoc->getTarget(); $primaryKey = array_flip((array)$target->getPrimaryKey()); - $records = $conditions = []; + $records = []; + $conditions = []; $primaryCount = count($primaryKey); + $junctionProperty = $assoc->getJunctionProperty(); + $options += ['junctionProperty' => $junctionProperty]; foreach ($data as $i => $row) { if (!is_array($row)) { @@ -423,17 +436,16 @@ protected function _belongsToMany(BelongsToMany $assoc, array $data, array $opti } } - if (!empty($conditions)) { - $query = $target->find(); - $query->andWhere(function ($exp) use ($conditions) { - /** @var \Cake\Database\Expression\QueryExpression $exp */ - return $exp->or($conditions); - }); + if ($conditions) { + /** @var \Traversable<\Cake\Datasource\EntityInterface> $results */ + $results = $target->find() + ->andWhere(fn(QueryExpression $exp) => $exp->or($conditions)) + ->all(); $keyFields = array_keys($primaryKey); $existing = []; - foreach ($query as $row) { + foreach ($results as $row) { $k = implode(';', $row->extract($keyFields)); $existing[$k] = $row; } @@ -449,7 +461,7 @@ protected function _belongsToMany(BelongsToMany $assoc, array $data, array $opti // Update existing record and child associations if (isset($existing[$key])) { - $records[$i] = $this->merge($existing[$key], $data[$i], $options); + $records[$i] = $this->merge($existing[$key], $row, $options); } } } @@ -457,15 +469,15 @@ protected function _belongsToMany(BelongsToMany $assoc, array $data, array $opti $jointMarshaller = $assoc->junction()->marshaller(); $nested = []; - if (isset($associated['_joinData'])) { - $nested = (array)$associated['_joinData']; + if (isset($associated[$junctionProperty])) { + $nested = (array)$associated[$junctionProperty]; } foreach ($records as $i => $record) { - // Update junction table data in _joinData. - if (isset($data[$i]['_joinData'])) { - $joinData = $jointMarshaller->one($data[$i]['_joinData'], $nested); - $record->set('_joinData', $joinData); + // Update junction table data in the junction property (_joinData). + if (isset($data[$i][$junctionProperty])) { + $joinData = $jointMarshaller->one($data[$i][$junctionProperty], $nested); + $record->set($junctionProperty, $joinData); } } @@ -481,14 +493,14 @@ protected function _belongsToMany(BelongsToMany $assoc, array $data, array $opti */ protected function _loadAssociatedByIds(Association $assoc, array $ids): array { - if (empty($ids)) { + if (!$ids) { return []; } $target = $assoc->getTarget(); $primaryKey = (array)$target->getPrimaryKey(); $multi = count($primaryKey) > 1; - $primaryKey = array_map([$target, 'aliasField'], $primaryKey); + $primaryKey = array_map($target->aliasField(...), $primaryKey); if ($multi) { $first = current($ids); @@ -521,7 +533,7 @@ protected function _loadAssociatedByIds(Association $assoc, array $ids): array * * ### Options: * - * - associated: Associations listed here will be marshalled as well. + * - associated: Associations listed here will be marshaled as well. * - validate: Whether to validate data before hydrating the entities. Can * also be set to a string to use a specific validator. Defaults to true/default. * - fields: An allowed list of fields to be assigned to the entity. If not present @@ -538,6 +550,16 @@ protected function _loadAssociatedByIds(Association $assoc, array $ids): array * ]); * ``` * + * ``` + * $result = $marshaller->merge($entity, $data, [ + * 'associated' => [ + * 'Tags' => [ + * 'associated' => ['DeeperAssoc1', 'DeeperAssoc2'] + * ] + * ] + * ]); + * ``` + * * @param \Cake\Datasource\EntityInterface $entity the entity that will get the * data merged in * @param array $data key value list of fields to be merged into the entity @@ -562,10 +584,13 @@ public function merge(EntityInterface $entity, array $data, array $options = []) } } - $errors = $this->_validate($data + $keys, $options, $isNew); + $errors = $this->_validate($data + $keys, $options['validate'], $isNew); $options['isMerge'] = true; $propertyMap = $this->_buildPropertyMap($data, $options); $properties = []; + /** + * @var string $key + */ foreach ($data as $key => $value) { if (!empty($errors[$key])) { if ($entity instanceof InvalidPropertyInterface) { @@ -573,39 +598,20 @@ public function merge(EntityInterface $entity, array $data, array $options = []) } continue; } - $original = $entity->get($key); if (isset($propertyMap[$key])) { $value = $propertyMap[$key]($value, $entity); - - // Don't dirty scalar values and objects that didn't - // change. Arrays will always be marked as dirty because - // the original/updated list could contain references to the - // same objects, even though those objects may have changed internally. - if ( - ( - is_scalar($value) - && $original === $value - ) - || ( - $value === null - && $original === $value - ) - || ( - is_object($value) - && !($value instanceof EntityInterface) - && $original == $value - ) - ) { - continue; - } } $properties[$key] = $value; } $entity->setErrors($errors); if (!isset($options['fields'])) { - $entity->set($properties); + if (method_exists($entity, 'patch')) { + $entity->patch($properties); + } else { + $entity->set($properties); + } foreach ($properties as $field => $value) { if ($value instanceof EntityInterface) { @@ -618,6 +624,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) } foreach ((array)$options['fields'] as $field) { + assert(is_string($field)); if (!array_key_exists($field, $properties)) { continue; } @@ -640,7 +647,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) * Records in `$data` are matched against the entities using the primary key * column. Entries in `$entities` that cannot be matched to any record in * `$data` will be discarded. Records in `$data` that could not be matched will - * be marshalled as a new entity. + * be marshaled as a new entity. * * When merging HasMany or BelongsToMany associations, all the entities in the * `$data` array will appear, those that can be matched by primary key will get @@ -650,7 +657,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) * * - validate: Whether to validate data before hydrating the entities. Can * also be set to a string to use a specific validator. Defaults to true/default. - * - associated: Associations listed here will be marshalled as well. + * - associated: Associations listed here will be marshaled as well. * - fields: An allowed list of fields to be assigned to the entity. If not present, * the accessible fields list in the entity will be used. * - accessibleFields: A list of fields to allow or deny in entity accessible fields. @@ -702,19 +709,19 @@ public function mergeMany(iterable $entities, array $data, array $options = []): ->map(function ($data, $key) { return explode(';', (string)$key); }) - ->filter(function ($keys) use ($primary) { - return count(Hash::filter($keys)) === count($primary); - }) + ->filter(fn($keys) => count(Hash::filter($keys)) === count($primary)) ->reduce(function ($conditions, $keys) use ($primary) { - $fields = array_map([$this->_table, 'aliasField'], $primary); + $fields = array_map($this->_table->aliasField(...), $primary); $conditions['OR'][] = array_combine($fields, $keys); return $conditions; }, ['OR' => []]); $maybeExistentQuery = $this->_table->find()->where($conditions); - if (!empty($indexed) && count($maybeExistentQuery->clause('where'))) { - foreach ($maybeExistentQuery as $entity) { + if ($indexed && count($maybeExistentQuery->clause('where'))) { + /** @var \Traversable<\Cake\Datasource\EntityInterface> $existent */ + $existent = $maybeExistentQuery->all(); + foreach ($existent as $entity) { $key = implode(';', $entity->extract($primary)); if (isset($indexed[$key])) { $output[] = $this->merge($entity, $indexed[$key], $options); @@ -736,14 +743,18 @@ public function mergeMany(iterable $entities, array $data, array $options = []): /** * Creates a new sub-marshaller and merges the associated data. * - * @param \Cake\Datasource\EntityInterface|array<\Cake\Datasource\EntityInterface> $original The original entity + * @param \Cake\Datasource\EntityInterface|non-empty-array<\Cake\Datasource\EntityInterface>|null $original The original entity * @param \Cake\ORM\Association $assoc The association to merge * @param mixed $value The array of data to hydrate. If not an array, this method will return null. * @param array $options List of options. * @return \Cake\Datasource\EntityInterface|array<\Cake\Datasource\EntityInterface>|null */ - protected function _mergeAssociation($original, Association $assoc, $value, array $options) - { + protected function _mergeAssociation( + EntityInterface|array|null $original, + Association $assoc, + mixed $value, + array $options, + ): EntityInterface|array|null { if (!$original) { return $this->_marshalAssociation($assoc, $value, $options); } @@ -756,10 +767,12 @@ protected function _mergeAssociation($original, Association $assoc, $value, arra $types = [Association::ONE_TO_ONE, Association::MANY_TO_ONE]; $type = $assoc->type(); if (in_array($type, $types, true)) { + /** @var \Cake\Datasource\EntityInterface $original */ return $marshaller->merge($original, $value, $options); } - if ($type === Association::MANY_TO_MANY) { - /** @psalm-suppress PossiblyInvalidArgument, ArgumentTypeCoercion */ + if ($type === Association::MANY_TO_MANY && is_array($original)) { + assert($assoc instanceof BelongsToMany); + return $marshaller->_mergeBelongsToMany($original, $assoc, $value, $options); } @@ -774,7 +787,9 @@ protected function _mergeAssociation($original, Association $assoc, $value, arra } } - /** @psalm-suppress PossiblyInvalidArgument */ + /** + * @var non-empty-array<\Cake\Datasource\EntityInterface> $original + */ return $marshaller->mergeMany($original, $value, $options); } @@ -802,7 +817,8 @@ protected function _mergeBelongsToMany(array $original, BelongsToMany $assoc, ar return []; } - if (!empty($associated) && !in_array('_joinData', $associated, true) && !isset($associated['_joinData'])) { + $junctionProperty = $assoc->getJunctionProperty(); + if ($associated && !in_array($junctionProperty, $associated, true) && !isset($associated[$junctionProperty])) { return $this->mergeMany($original, $value, $options); } @@ -810,7 +826,7 @@ protected function _mergeBelongsToMany(array $original, BelongsToMany $assoc, ar } /** - * Merge the special _joinData property into the entity set. + * Merge the special junction property (_joinData) into the entity set. * * @param array<\Cake\Datasource\EntityInterface> $original The original entities list. * @param \Cake\ORM\Association\BelongsToMany $assoc The association to marshall @@ -822,12 +838,13 @@ protected function _mergeJoinData(array $original, BelongsToMany $assoc, array $ { $associated = $options['associated'] ?? []; $extra = []; + $junctionProperty = $assoc->getJunctionProperty(); foreach ($original as $entity) { // Mark joinData as accessible so we can marshal it properly. - $entity->setAccess('_joinData', true); + $entity->setAccess($junctionProperty, true); - $joinData = $entity->get('_joinData'); - if ($joinData && $joinData instanceof EntityInterface) { + $joinData = $this->fieldValue($entity, $junctionProperty); + if ($joinData instanceof EntityInterface) { $extra[spl_object_hash($entity)] = $joinData; } } @@ -836,34 +853,34 @@ protected function _mergeJoinData(array $original, BelongsToMany $assoc, array $ $marshaller = $joint->marshaller(); $nested = []; - if (isset($associated['_joinData'])) { - $nested = (array)$associated['_joinData']; + if (isset($associated[$junctionProperty])) { + $nested = (array)$associated[$junctionProperty]; } - $options['accessibleFields'] = ['_joinData' => true]; + $options['accessibleFields'] = [$junctionProperty => true]; $records = $this->mergeMany($original, $value, $options); foreach ($records as $record) { $hash = spl_object_hash($record); - $value = $record->get('_joinData'); + $value = $this->fieldValue($record, $junctionProperty); - // Already an entity, no further marshalling required. + // Already an entity, no further marshaling required. if ($value instanceof EntityInterface) { continue; } // Scalar data can't be handled if (!is_array($value)) { - $record->unset('_joinData'); + $record->unset($junctionProperty); continue; } // Marshal data into the old object, or make a new joinData object. if (isset($extra[$hash])) { - $record->set('_joinData', $marshaller->merge($extra[$hash], $value, $nested)); + $record->set($junctionProperty, $marshaller->merge($extra[$hash], $value, $nested)); } else { $joinData = $marshaller->one($value, $nested); - $record->set('_joinData', $joinData); + $record->set($junctionProperty, $joinData); } } @@ -884,4 +901,19 @@ protected function dispatchAfterMarshal(EntityInterface $entity, array $data, ar $options = new ArrayObject($options); $this->_table->dispatchEvent('Model.afterMarshal', compact('entity', 'data', 'options')); } + + /** + * Get the value of a field from an entity. + * + * It checks whether the field exists in the entity before getting the value + * to avoid MissingPropertyException if `requireFieldPresence` is enabled. + * + * @param \Cake\Datasource\EntityInterface $entity The entity to extract the field from. + * @param string $field The field to extract. + * @return mixed + */ + protected function fieldValue(EntityInterface $entity, string $field): mixed + { + return $entity->has($field) ? $entity->get($field) : null; + } } diff --git a/app/vendor/cakephp/cakephp/src/ORM/PropertyMarshalInterface.php b/app/vendor/cakephp/cakephp/src/ORM/PropertyMarshalInterface.php index 8317d9eb9..d7510ba88 100644 --- a/app/vendor/cakephp/cakephp/src/ORM/PropertyMarshalInterface.php +++ b/app/vendor/cakephp/cakephp/src/ORM/PropertyMarshalInterface.php @@ -17,19 +17,19 @@ namespace Cake\ORM; /** - * Behaviors implementing this interface can participate in entity marshalling. + * Behaviors implementing this interface can participate in entity marshaling. * * This enables behaviors to define behavior for how the properties they provide/manage - * should be marshalled. + * should be marshaled. */ interface PropertyMarshalInterface { /** - * Build a set of properties that should be included in the marshalling process. + * Build a set of properties that should be included in the marshaling process. * - * @param \Cake\ORM\Marshaller $marshaller The marhshaller of the table the behavior is attached to. + * @param \Cake\ORM\Marshaller $marshaller The marshaler of the table the behavior is attached to. * @param array $map The property map being built. - * @param array $options The options array used in the marshalling call. + * @param array $options The options array used in the marshaling call. * @return array A map of `[property => callable]` of additional properties to marshal. */ public function buildMarshalMap(Marshaller $marshaller, array $map, array $options): array; diff --git a/app/vendor/cakephp/cakephp/src/ORM/Query.php b/app/vendor/cakephp/cakephp/src/ORM/Query.php index 3f40d5b6f..0aa206233 100644 --- a/app/vendor/cakephp/cakephp/src/ORM/Query.php +++ b/app/vendor/cakephp/cakephp/src/ORM/Query.php @@ -6,1472 +6,11 @@ * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License - * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice. * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) - * @link https://cakephp.org CakePHP(tm) Project * @since 3.0.0 * @license https://opensource.org/licenses/mit-license.php MIT License */ -namespace Cake\ORM; -use ArrayObject; -use BadMethodCallException; -use Cake\Database\Connection; -use Cake\Database\ExpressionInterface; -use Cake\Database\Query as DatabaseQuery; -use Cake\Database\TypedResultInterface; -use Cake\Database\TypeMap; -use Cake\Database\ValueBinder; -use Cake\Datasource\QueryInterface; -use Cake\Datasource\QueryTrait; -use Cake\Datasource\ResultSetInterface; -use InvalidArgumentException; -use JsonSerializable; -use RuntimeException; -use Traversable; -use function Cake\Core\deprecationWarning; - -/** - * Extends the base Query class to provide new methods related to association - * loading, automatic fields selection, automatic type casting and to wrap results - * into a specific iterator that will be responsible for hydrating results if - * required. - * - * @see \Cake\Collection\CollectionInterface For a full description of the collection methods supported by this class - * @property \Cake\ORM\Table $_repository Instance of a table object this query is bound to. - * @method \Cake\ORM\Table getRepository() Returns the default table object that will be used by this query, - * that is, the table that will appear in the from clause. - * @method \Cake\Collection\CollectionInterface each(callable $c) Passes each of the query results to the callable - * @method \Cake\Collection\CollectionInterface sortBy(callable|string $path, int $order = \SORT_DESC, int $sort = \SORT_NUMERIC) Sorts the query with the callback - * @method \Cake\Collection\CollectionInterface filter(callable $c = null) Keeps the results using passing the callable test - * @method \Cake\Collection\CollectionInterface reject(callable $c) Removes the results passing the callable test - * @method bool every(callable $c) Returns true if all the results pass the callable test - * @method bool some(callable $c) Returns true if at least one of the results pass the callable test - * @method \Cake\Collection\CollectionInterface map(callable $c) Modifies each of the results using the callable - * @method mixed reduce(callable $c, $zero = null) Folds all the results into a single value using the callable. - * @method \Cake\Collection\CollectionInterface extract($field) Extracts a single column from each row - * @method mixed max($field, $sort = \SORT_NUMERIC) Returns the maximum value for a single column in all the results. - * @method mixed min($field, $sort = \SORT_NUMERIC) Returns the minimum value for a single column in all the results. - * @method \Cake\Collection\CollectionInterface groupBy(callable|string $field) In-memory group all results by the value of a column. - * @method \Cake\Collection\CollectionInterface indexBy(callable|string $callback) Returns the results indexed by the value of a column. - * @method \Cake\Collection\CollectionInterface countBy(callable|string $field) Returns the number of unique values for a column - * @method int|float sumOf($field = null) Returns the sum of all values for a single column - * @method \Cake\Collection\CollectionInterface shuffle() In-memory randomize the order the results are returned - * @method \Cake\Collection\CollectionInterface sample(int $size = 10) In-memory shuffle the results and return a subset of them. - * @method \Cake\Collection\CollectionInterface take(int $size = 1, int $from = 0) In-memory limit and offset for the query results. - * @method \Cake\Collection\CollectionInterface skip(int $howMany) Skips some rows from the start of the query result. - * @method mixed last() Return the last row of the query result - * @method \Cake\Collection\CollectionInterface append(mixed $items) Appends more rows to the result of the query. - * @method \Cake\Collection\CollectionInterface combine($k, $v, $g = null) Returns the values of the column $v index by column $k, - * and grouped by $g. - * @method \Cake\Collection\CollectionInterface nest($k, $p, $n = 'children') Creates a tree structure by nesting the values of column $p into that - * with the same value for $k using $n as the nesting key. - * @method array toArray() Returns a key-value array with the results of this query. - * @method array toList() Returns a numerically indexed array with the results of this query. - * @method \Cake\Collection\CollectionInterface stopWhen(callable|array $c) Returns each row until the callable returns true. - * @method \Cake\Collection\CollectionInterface zip(iterable $c) Returns the first result of both the query and $c in an array, - * then the second results and so on. - * @method \Cake\Collection\CollectionInterface zipWith(iterable $collections, callable $callable) Returns each of the results out of calling $c - * with the first rows of the query and each of the items, then the second rows and so on. - * @method \Cake\Collection\CollectionInterface chunk(int $size) Groups the results in arrays of $size rows each. - * @method bool isEmpty() Returns true if this query found no results. - */ -class Query extends DatabaseQuery implements JsonSerializable, QueryInterface -{ - use QueryTrait { - cache as private _cache; - all as private _all; - _decorateResults as private _applyDecorators; - __call as private _call; - } - - /** - * Indicates that the operation should append to the list - * - * @var int - */ - public const APPEND = 0; - - /** - * Indicates that the operation should prepend to the list - * - * @var int - */ - public const PREPEND = 1; - - /** - * Indicates that the operation should overwrite the list - * - * @var bool - */ - public const OVERWRITE = true; - - /** - * Whether the user select any fields before being executed, this is used - * to determined if any fields should be automatically be selected. - * - * @var bool|null - */ - protected $_hasFields; - - /** - * Tracks whether the original query should include - * fields from the top level table. - * - * @var bool|null - */ - protected $_autoFields; - - /** - * Whether to hydrate results into entity objects - * - * @var bool - */ - protected $_hydrate = true; - - /** - * Whether aliases are generated for fields. - * - * @var bool - */ - protected $aliasingEnabled = true; - - /** - * A callable function that can be used to calculate the total amount of - * records this query will match when not using `limit` - * - * @var callable|null - */ - protected $_counter; - - /** - * Instance of a class responsible for storing association containments and - * for eager loading them when this query is executed - * - * @var \Cake\ORM\EagerLoader|null - */ - protected $_eagerLoader; - - /** - * True if the beforeFind event has already been triggered for this query - * - * @var bool - */ - protected $_beforeFindFired = false; - - /** - * The COUNT(*) for the query. - * - * When set, count query execution will be bypassed. - * - * @var int|null - */ - protected $_resultsCount; - - /** - * Constructor - * - * @param \Cake\Database\Connection $connection The connection object - * @param \Cake\ORM\Table $table The table this query is starting on - */ - public function __construct(Connection $connection, Table $table) - { - parent::__construct($connection); - $this->setRepository($table); - - if ($this->_repository !== null) { - $this->addDefaultTypes($this->_repository); - } - } - - /** - * Adds new fields to be returned by a `SELECT` statement when this query is - * executed. Fields can be passed as an array of strings, array of expression - * objects, a single expression or a single string. - * - * If an array is passed, keys will be used to alias fields using the value as the - * real field to be aliased. It is possible to alias strings, Expression objects or - * even other Query objects. - * - * If a callable function is passed, the returning array of the function will - * be used as the list of fields. - * - * By default this function will append any passed argument to the list of fields - * to be selected, unless the second argument is set to true. - * - * ### Examples: - * - * ``` - * $query->select(['id', 'title']); // Produces SELECT id, title - * $query->select(['author' => 'author_id']); // Appends author: SELECT id, title, author_id as author - * $query->select('id', true); // Resets the list: SELECT id - * $query->select(['total' => $countQuery]); // SELECT id, (SELECT ...) AS total - * $query->select(function ($query) { - * return ['article_id', 'total' => $query->count('*')]; - * }) - * ``` - * - * By default no fields are selected, if you have an instance of `Cake\ORM\Query` and try to append - * fields you should also call `Cake\ORM\Query::enableAutoFields()` to select the default fields - * from the table. - * - * If you pass an instance of a `Cake\ORM\Table` or `Cake\ORM\Association` class, - * all the fields in the schema of the table or the association will be added to - * the select clause. - * - * @param \Cake\Database\ExpressionInterface|\Cake\ORM\Table|\Cake\ORM\Association|callable|array|string $fields Fields - * to be added to the list. - * @param bool $overwrite whether to reset fields with passed list or not - * @return $this - */ - public function select($fields = [], bool $overwrite = false) - { - if ($fields instanceof Association) { - $fields = $fields->getTarget(); - } - - if ($fields instanceof Table) { - if ($this->aliasingEnabled) { - $fields = $this->aliasFields($fields->getSchema()->columns(), $fields->getAlias()); - } else { - $fields = $fields->getSchema()->columns(); - } - } - - return parent::select($fields, $overwrite); - } - - /** - * Behaves the exact same as `select()` except adds the field to the list of fields selected and - * does not disable auto-selecting fields for Associations. - * - * Use this instead of calling `select()` then `enableAutoFields()` to re-enable auto-fields. - * - * @param \Cake\Database\ExpressionInterface|\Cake\ORM\Table|\Cake\ORM\Association|callable|array|string $fields Fields - * to be added to the list. - * @return $this - */ - public function selectAlso($fields) - { - $this->select($fields); - $this->_autoFields = true; - - return $this; - } - - /** - * All the fields associated with the passed table except the excluded - * fields will be added to the select clause of the query. Passed excluded fields should not be aliased. - * After the first call to this method, a second call cannot be used to remove fields that have already - * been added to the query by the first. If you need to change the list after the first call, - * pass overwrite boolean true which will reset the select clause removing all previous additions. - * - * @param \Cake\ORM\Table|\Cake\ORM\Association $table The table to use to get an array of columns - * @param array $excludedFields The un-aliased column names you do not want selected from $table - * @param bool $overwrite Whether to reset/remove previous selected fields - * @return $this - * @throws \InvalidArgumentException If Association|Table is not passed in first argument - */ - public function selectAllExcept($table, array $excludedFields, bool $overwrite = false) - { - if ($table instanceof Association) { - $table = $table->getTarget(); - } - - if (!($table instanceof Table)) { - throw new InvalidArgumentException('You must provide either an Association or a Table object'); - } - - $fields = array_diff($table->getSchema()->columns(), $excludedFields); - if ($this->aliasingEnabled) { - $fields = $this->aliasFields($fields); - } - - return $this->select($fields, $overwrite); - } - - /** - * Hints this object to associate the correct types when casting conditions - * for the database. This is done by extracting the field types from the schema - * associated to the passed table object. This prevents the user from repeating - * themselves when specifying conditions. - * - * This method returns the same query object for chaining. - * - * @param \Cake\ORM\Table $table The table to pull types from - * @return $this - */ - public function addDefaultTypes(Table $table) - { - $alias = $table->getAlias(); - $map = $table->getSchema()->typeMap(); - $fields = []; - foreach ($map as $f => $type) { - $fields[$f] = $fields[$alias . '.' . $f] = $fields[$alias . '__' . $f] = $type; - } - $this->getTypeMap()->addDefaults($fields); - - return $this; - } - - /** - * Sets the instance of the eager loader class to use for loading associations - * and storing containments. - * - * @param \Cake\ORM\EagerLoader $instance The eager loader to use. - * @return $this - */ - public function setEagerLoader(EagerLoader $instance) - { - $this->_eagerLoader = $instance; - - return $this; - } - - /** - * Returns the currently configured instance. - * - * @return \Cake\ORM\EagerLoader - */ - public function getEagerLoader(): EagerLoader - { - if ($this->_eagerLoader === null) { - $this->_eagerLoader = new EagerLoader(); - } - - return $this->_eagerLoader; - } - - /** - * Sets the list of associations that should be eagerly loaded along with this - * query. The list of associated tables passed must have been previously set as - * associations using the Table API. - * - * ### Example: - * - * ``` - * // Bring articles' author information - * $query->contain('Author'); - * - * // Also bring the category and tags associated to each article - * $query->contain(['Category', 'Tag']); - * ``` - * - * Associations can be arbitrarily nested using dot notation or nested arrays, - * this allows this object to calculate joins or any additional queries that - * must be executed to bring the required associated data. - * - * ### Example: - * - * ``` - * // Eager load the product info, and for each product load other 2 associations - * $query->contain(['Product' => ['Manufacturer', 'Distributor']); - * - * // Which is equivalent to calling - * $query->contain(['Products.Manufactures', 'Products.Distributors']); - * - * // For an author query, load his region, state and country - * $query->contain('Regions.States.Countries'); - * ``` - * - * It is possible to control the conditions and fields selected for each of the - * contained associations: - * - * ### Example: - * - * ``` - * $query->contain(['Tags' => function ($q) { - * return $q->where(['Tags.is_popular' => true]); - * }]); - * - * $query->contain(['Products.Manufactures' => function ($q) { - * return $q->select(['name'])->where(['Manufactures.active' => true]); - * }]); - * ``` - * - * Each association might define special options when eager loaded, the allowed - * options that can be set per association are: - * - * - `foreignKey`: Used to set a different field to match both tables, if set to false - * no join conditions will be generated automatically. `false` can only be used on - * joinable associations and cannot be used with hasMany or belongsToMany associations. - * - `fields`: An array with the fields that should be fetched from the association. - * - `finder`: The finder to use when loading associated records. Either the name of the - * finder as a string, or an array to define options to pass to the finder. - * - `queryBuilder`: Equivalent to passing a callable instead of an options array. - * - * ### Example: - * - * ``` - * // Set options for the hasMany articles that will be eagerly loaded for an author - * $query->contain([ - * 'Articles' => [ - * 'fields' => ['title', 'author_id'] - * ] - * ]); - * ``` - * - * Finders can be configured to use options. - * - * ``` - * // Retrieve translations for the articles, but only those for the `en` and `es` locales - * $query->contain([ - * 'Articles' => [ - * 'finder' => [ - * 'translations' => [ - * 'locales' => ['en', 'es'] - * ] - * ] - * ] - * ]); - * ``` - * - * When containing associations, it is important to include foreign key columns. - * Failing to do so will trigger exceptions. - * - * ``` - * // Use a query builder to add conditions to the containment - * $query->contain('Authors', function ($q) { - * return $q->where(...); // add conditions - * }); - * // Use special join conditions for multiple containments in the same method call - * $query->contain([ - * 'Authors' => [ - * 'foreignKey' => false, - * 'queryBuilder' => function ($q) { - * return $q->where(...); // Add full filtering conditions - * } - * ], - * 'Tags' => function ($q) { - * return $q->where(...); // add conditions - * } - * ]); - * ``` - * - * If called with an empty first argument and `$override` is set to true, the - * previous list will be emptied. - * - * @param array|string $associations List of table aliases to be queried. - * @param callable|bool $override The query builder for the association, or - * if associations is an array, a bool on whether to override previous list - * with the one passed - * defaults to merging previous list with the new one. - * @return $this - */ - public function contain($associations, $override = false) - { - $loader = $this->getEagerLoader(); - if ($override === true) { - $this->clearContain(); - } - - $queryBuilder = null; - if (is_callable($override)) { - $queryBuilder = $override; - } - - if ($associations) { - $loader->contain($associations, $queryBuilder); - } - $this->_addAssociationsToTypeMap( - $this->getRepository(), - $this->getTypeMap(), - $loader->getContain() - ); - - return $this; - } - - /** - * @return array - */ - public function getContain(): array - { - return $this->getEagerLoader()->getContain(); - } - - /** - * Clears the contained associations from the current query. - * - * @return $this - */ - public function clearContain() - { - $this->getEagerLoader()->clearContain(); - $this->_dirty(); - - return $this; - } - - /** - * Used to recursively add contained association column types to - * the query. - * - * @param \Cake\ORM\Table $table The table instance to pluck associations from. - * @param \Cake\Database\TypeMap $typeMap The typemap to check for columns in. - * This typemap is indirectly mutated via {@link \Cake\ORM\Query::addDefaultTypes()} - * @param array $associations The nested tree of associations to walk. - * @return void - */ - protected function _addAssociationsToTypeMap(Table $table, TypeMap $typeMap, array $associations): void - { - foreach ($associations as $name => $nested) { - if (!$table->hasAssociation($name)) { - continue; - } - $association = $table->getAssociation($name); - $target = $association->getTarget(); - $primary = (array)$target->getPrimaryKey(); - if (empty($primary) || $typeMap->type($target->aliasField($primary[0])) === null) { - $this->addDefaultTypes($target); - } - if (!empty($nested)) { - $this->_addAssociationsToTypeMap($target, $typeMap, $nested); - } - } - } - - /** - * Adds filtering conditions to this query to only bring rows that have a relation - * to another from an associated table, based on conditions in the associated table. - * - * This function will add entries in the `contain` graph. - * - * ### Example: - * - * ``` - * // Bring only articles that were tagged with 'cake' - * $query->matching('Tags', function ($q) { - * return $q->where(['name' => 'cake']); - * }); - * ``` - * - * It is possible to filter by deep associations by using dot notation: - * - * ### Example: - * - * ``` - * // Bring only articles that were commented by 'markstory' - * $query->matching('Comments.Users', function ($q) { - * return $q->where(['username' => 'markstory']); - * }); - * ``` - * - * As this function will create `INNER JOIN`, you might want to consider - * calling `distinct` on this query as you might get duplicate rows if - * your conditions don't filter them already. This might be the case, for example, - * of the same user commenting more than once in the same article. - * - * ### Example: - * - * ``` - * // Bring unique articles that were commented by 'markstory' - * $query->distinct(['Articles.id']) - * ->matching('Comments.Users', function ($q) { - * return $q->where(['username' => 'markstory']); - * }); - * ``` - * - * Please note that the query passed to the closure will only accept calling - * `select`, `where`, `andWhere` and `orWhere` on it. If you wish to - * add more complex clauses you can do it directly in the main query. - * - * @param string $assoc The association to filter by - * @param callable|null $builder a function that will receive a pre-made query object - * that can be used to add custom conditions or selecting some fields - * @return $this - */ - public function matching(string $assoc, ?callable $builder = null) - { - $result = $this->getEagerLoader()->setMatching($assoc, $builder)->getMatching(); - $this->_addAssociationsToTypeMap($this->getRepository(), $this->getTypeMap(), $result); - $this->_dirty(); - - return $this; - } - - /** - * Creates a LEFT JOIN with the passed association table while preserving - * the foreign key matching and the custom conditions that were originally set - * for it. - * - * This function will add entries in the `contain` graph. - * - * ### Example: - * - * ``` - * // Get the count of articles per user - * $usersQuery - * ->select(['total_articles' => $query->func()->count('Articles.id')]) - * ->leftJoinWith('Articles') - * ->group(['Users.id']) - * ->enableAutoFields(); - * ``` - * - * You can also customize the conditions passed to the LEFT JOIN: - * - * ``` - * // Get the count of articles per user with at least 5 votes - * $usersQuery - * ->select(['total_articles' => $query->func()->count('Articles.id')]) - * ->leftJoinWith('Articles', function ($q) { - * return $q->where(['Articles.votes >=' => 5]); - * }) - * ->group(['Users.id']) - * ->enableAutoFields(); - * ``` - * - * This will create the following SQL: - * - * ``` - * SELECT COUNT(Articles.id) AS total_articles, Users.* - * FROM users Users - * LEFT JOIN articles Articles ON Articles.user_id = Users.id AND Articles.votes >= 5 - * GROUP BY USers.id - * ``` - * - * It is possible to left join deep associations by using dot notation - * - * ### Example: - * - * ``` - * // Total comments in articles by 'markstory' - * $query - * ->select(['total_comments' => $query->func()->count('Comments.id')]) - * ->leftJoinWith('Comments.Users', function ($q) { - * return $q->where(['username' => 'markstory']); - * }) - * ->group(['Users.id']); - * ``` - * - * Please note that the query passed to the closure will only accept calling - * `select`, `where`, `andWhere` and `orWhere` on it. If you wish to - * add more complex clauses you can do it directly in the main query. - * - * @param string $assoc The association to join with - * @param callable|null $builder a function that will receive a pre-made query object - * that can be used to add custom conditions or selecting some fields - * @return $this - */ - public function leftJoinWith(string $assoc, ?callable $builder = null) - { - $result = $this->getEagerLoader() - ->setMatching($assoc, $builder, [ - 'joinType' => Query::JOIN_TYPE_LEFT, - 'fields' => false, - ]) - ->getMatching(); - $this->_addAssociationsToTypeMap($this->getRepository(), $this->getTypeMap(), $result); - $this->_dirty(); - - return $this; - } - - /** - * Creates an INNER JOIN with the passed association table while preserving - * the foreign key matching and the custom conditions that were originally set - * for it. - * - * This function will add entries in the `contain` graph. - * - * ### Example: - * - * ``` - * // Bring only articles that were tagged with 'cake' - * $query->innerJoinWith('Tags', function ($q) { - * return $q->where(['name' => 'cake']); - * }); - * ``` - * - * This will create the following SQL: - * - * ``` - * SELECT Articles.* - * FROM articles Articles - * INNER JOIN tags Tags ON Tags.name = 'cake' - * INNER JOIN articles_tags ArticlesTags ON ArticlesTags.tag_id = Tags.id - * AND ArticlesTags.articles_id = Articles.id - * ``` - * - * This function works the same as `matching()` with the difference that it - * will select no fields from the association. - * - * @param string $assoc The association to join with - * @param callable|null $builder a function that will receive a pre-made query object - * that can be used to add custom conditions or selecting some fields - * @return $this - * @see \Cake\ORM\Query::matching() - */ - public function innerJoinWith(string $assoc, ?callable $builder = null) - { - $result = $this->getEagerLoader() - ->setMatching($assoc, $builder, [ - 'joinType' => Query::JOIN_TYPE_INNER, - 'fields' => false, - ]) - ->getMatching(); - $this->_addAssociationsToTypeMap($this->getRepository(), $this->getTypeMap(), $result); - $this->_dirty(); - - return $this; - } - - /** - * Adds filtering conditions to this query to only bring rows that have no match - * to another from an associated table, based on conditions in the associated table. - * - * This function will add entries in the `contain` graph. - * - * ### Example: - * - * ``` - * // Bring only articles that were not tagged with 'cake' - * $query->notMatching('Tags', function ($q) { - * return $q->where(['name' => 'cake']); - * }); - * ``` - * - * It is possible to filter by deep associations by using dot notation: - * - * ### Example: - * - * ``` - * // Bring only articles that weren't commented by 'markstory' - * $query->notMatching('Comments.Users', function ($q) { - * return $q->where(['username' => 'markstory']); - * }); - * ``` - * - * As this function will create a `LEFT JOIN`, you might want to consider - * calling `distinct` on this query as you might get duplicate rows if - * your conditions don't filter them already. This might be the case, for example, - * of the same article having multiple comments. - * - * ### Example: - * - * ``` - * // Bring unique articles that were commented by 'markstory' - * $query->distinct(['Articles.id']) - * ->notMatching('Comments.Users', function ($q) { - * return $q->where(['username' => 'markstory']); - * }); - * ``` - * - * Please note that the query passed to the closure will only accept calling - * `select`, `where`, `andWhere` and `orWhere` on it. If you wish to - * add more complex clauses you can do it directly in the main query. - * - * @param string $assoc The association to filter by - * @param callable|null $builder a function that will receive a pre-made query object - * that can be used to add custom conditions or selecting some fields - * @return $this - */ - public function notMatching(string $assoc, ?callable $builder = null) - { - $result = $this->getEagerLoader() - ->setMatching($assoc, $builder, [ - 'joinType' => Query::JOIN_TYPE_LEFT, - 'fields' => false, - 'negateMatch' => true, - ]) - ->getMatching(); - $this->_addAssociationsToTypeMap($this->getRepository(), $this->getTypeMap(), $result); - $this->_dirty(); - - return $this; - } - - /** - * Populates or adds parts to current query clauses using an array. - * This is handy for passing all query clauses at once. - * - * The method accepts the following query clause related options: - * - * - fields: Maps to the select method - * - conditions: Maps to the where method - * - limit: Maps to the limit method - * - order: Maps to the order method - * - offset: Maps to the offset method - * - group: Maps to the group method - * - having: Maps to the having method - * - contain: Maps to the contain options for eager loading - * - join: Maps to the join method - * - page: Maps to the page method - * - * All other options will not affect the query, but will be stored - * as custom options that can be read via `getOptions()`. Furthermore - * they are automatically passed to `Model.beforeFind`. - * - * ### Example: - * - * ``` - * $query->applyOptions([ - * 'fields' => ['id', 'name'], - * 'conditions' => [ - * 'created >=' => '2013-01-01' - * ], - * 'limit' => 10, - * ]); - * ``` - * - * Is equivalent to: - * - * ``` - * $query - * ->select(['id', 'name']) - * ->where(['created >=' => '2013-01-01']) - * ->limit(10) - * ``` - * - * Custom options can be read via `getOptions()`: - * - * ``` - * $query->applyOptions([ - * 'fields' => ['id', 'name'], - * 'custom' => 'value', - * ]); - * ``` - * - * Here `$options` will hold `['custom' => 'value']` (the `fields` - * option will be applied to the query instead of being stored, as - * it's a query clause related option): - * - * ``` - * $options = $query->getOptions(); - * ``` - * - * @param array $options The options to be applied - * @return $this - * @see getOptions() - */ - public function applyOptions(array $options) - { - $valid = [ - 'fields' => 'select', - 'conditions' => 'where', - 'join' => 'join', - 'order' => 'order', - 'limit' => 'limit', - 'offset' => 'offset', - 'group' => 'group', - 'having' => 'having', - 'contain' => 'contain', - 'page' => 'page', - ]; - - ksort($options); - foreach ($options as $option => $values) { - if (isset($valid[$option], $values)) { - $this->{$valid[$option]}($values); - } else { - $this->_options[$option] = $values; - } - } - - return $this; - } - - /** - * Creates a copy of this current query, triggers beforeFind and resets some state. - * - * The following state will be cleared: - * - * - autoFields - * - limit - * - offset - * - map/reduce functions - * - result formatters - * - order - * - containments - * - * This method creates query clones that are useful when working with subqueries. - * - * @return static - */ - public function cleanCopy() - { - $clone = clone $this; - $clone->triggerBeforeFind(); - $clone->disableAutoFields(); - $clone->limit(null); - $clone->order([], true); - $clone->offset(null); - $clone->mapReduce(null, null, true); - $clone->formatResults(null, self::OVERWRITE); - $clone->setSelectTypeMap(new TypeMap()); - $clone->decorateResults(null, true); - - return $clone; - } - - /** - * Clears the internal result cache and the internal count value from the current - * query object. - * - * @return $this - */ - public function clearResult() - { - $this->_dirty(); - - return $this; - } - - /** - * {@inheritDoc} - * - * Handles cloning eager loaders. - */ - public function __clone() - { - parent::__clone(); - if ($this->_eagerLoader !== null) { - $this->_eagerLoader = clone $this->_eagerLoader; - } - } - - /** - * {@inheritDoc} - * - * Returns the COUNT(*) for the query. If the query has not been - * modified, and the count has already been performed the cached - * value is returned - * - * @return int - */ - public function count(): int - { - if ($this->_resultsCount === null) { - $this->_resultsCount = $this->_performCount(); - } - - return $this->_resultsCount; - } - - /** - * Performs and returns the COUNT(*) for the query. - * - * @return int - */ - protected function _performCount(): int - { - $query = $this->cleanCopy(); - $counter = $this->_counter; - if ($counter !== null) { - $query->counter(null); - - return (int)$counter($query); - } - - $complex = ( - $query->clause('distinct') || - count($query->clause('group')) || - count($query->clause('union')) || - $query->clause('having') - ); - - if (!$complex) { - // Expression fields could have bound parameters. - foreach ($query->clause('select') as $field) { - if ($field instanceof ExpressionInterface) { - $complex = true; - break; - } - } - } - - if (!$complex && $this->_valueBinder !== null) { - $order = $this->clause('order'); - $complex = $order === null ? false : $order->hasNestedExpression(); - } - - $count = ['count' => $query->func()->count('*')]; - - if (!$complex) { - $query->getEagerLoader()->disableAutoFields(); - $statement = $query - ->select($count, true) - ->disableAutoFields() - ->execute(); - } else { - $statement = $this->getConnection() - ->selectQuery($count) - ->from(['count_source' => $query]) - ->execute(); - } - - $result = $statement->fetch('assoc'); - $statement->closeCursor(); - - if ($result === false) { - return 0; - } - - return (int)$result['count']; - } - - /** - * Registers a callable function that will be executed when the `count` method in - * this query is called. The return value for the function will be set as the - * return value of the `count` method. - * - * This is particularly useful when you need to optimize a query for returning the - * count, for example removing unnecessary joins, removing group by or just return - * an estimated number of rows. - * - * The callback will receive as first argument a clone of this query and not this - * query itself. - * - * If the first param is a null value, the built-in counter function will be called - * instead - * - * @param callable|null $counter The counter value - * @return $this - */ - public function counter(?callable $counter) - { - $this->_counter = $counter; - - return $this; - } - - /** - * Toggle hydrating entities. - * - * If set to false array results will be returned for the query. - * - * @param bool $enable Use a boolean to set the hydration mode. - * @return $this - */ - public function enableHydration(bool $enable = true) - { - $this->_dirty(); - $this->_hydrate = $enable; - - return $this; - } - - /** - * Disable hydrating entities. - * - * Disabling hydration will cause array results to be returned for the query - * instead of entities. - * - * @return $this - */ - public function disableHydration() - { - $this->_dirty(); - $this->_hydrate = false; - - return $this; - } - - /** - * Returns the current hydration mode. - * - * @return bool - */ - public function isHydrationEnabled(): bool - { - return $this->_hydrate; - } - - /** - * {@inheritDoc} - * - * @param \Closure|string|false $key Either the cache key or a function to generate the cache key. - * When using a function, this query instance will be supplied as an argument. - * @param \Cake\Cache\CacheEngine|string $config Either the name of the cache config to use, or - * a cache config instance. - * @return $this - * @throws \RuntimeException When you attempt to cache a non-select query. - */ - public function cache($key, $config = 'default') - { - if ($this->_type !== 'select' && $this->_type !== null) { - throw new RuntimeException('You cannot cache the results of non-select queries.'); - } - - return $this->_cache($key, $config); - } - - /** - * {@inheritDoc} - * - * @return \Cake\Datasource\ResultSetInterface - * @throws \RuntimeException if this method is called on a non-select Query. - */ - public function all(): ResultSetInterface - { - if ($this->_type !== 'select' && $this->_type !== null) { - throw new RuntimeException( - 'You cannot call all() on a non-select query. Use execute() instead.' - ); - } - - return $this->_all(); - } - - /** - * Trigger the beforeFind event on the query's repository object. - * - * Will not trigger more than once, and only for select queries. - * - * @return void - */ - public function triggerBeforeFind(): void - { - if (!$this->_beforeFindFired && $this->_type === 'select') { - $this->_beforeFindFired = true; - - $repository = $this->getRepository(); - $repository->dispatchEvent('Model.beforeFind', [ - $this, - new ArrayObject($this->_options), - !$this->isEagerLoaded(), - ]); - } - } - - /** - * @inheritDoc - */ - public function sql(?ValueBinder $binder = null): string - { - $this->triggerBeforeFind(); - - $this->_transformQuery(); - - return parent::sql($binder); - } - - /** - * Executes this query and returns a ResultSet object containing the results. - * This will also setup the correct statement class in order to eager load deep - * associations. - * - * @return \Cake\Datasource\ResultSetInterface - */ - protected function _execute(): ResultSetInterface - { - $this->triggerBeforeFind(); - if ($this->_results) { - $decorator = $this->_decoratorClass(); - - return new $decorator($this->_results); - } - - $statement = $this->getEagerLoader()->loadExternal($this, $this->execute()); - - return new ResultSet($this, $statement); - } - - /** - * Applies some defaults to the query object before it is executed. - * - * Specifically add the FROM clause, adds default table fields if none are - * specified and applies the joins required to eager load associations defined - * using `contain` - * - * It also sets the default types for the columns in the select clause - * - * @see \Cake\Database\Query::execute() - * @return void - */ - protected function _transformQuery(): void - { - if (!$this->_dirty || $this->_type !== 'select') { - return; - } - - $repository = $this->getRepository(); - - if (empty($this->_parts['from'])) { - $this->from([$repository->getAlias() => $repository->getTable()]); - } - $this->_addDefaultFields(); - $this->getEagerLoader()->attachAssociations($this, $repository, !$this->_hasFields); - $this->_addDefaultSelectTypes(); - } - - /** - * Inspects if there are any set fields for selecting, otherwise adds all - * the fields for the default table. - * - * @return void - */ - protected function _addDefaultFields(): void - { - $select = $this->clause('select'); - $this->_hasFields = true; - - $repository = $this->getRepository(); - - if (!count($select) || $this->_autoFields === true) { - $this->_hasFields = false; - $this->select($repository->getSchema()->columns()); - $select = $this->clause('select'); - } - - if ($this->aliasingEnabled) { - $select = $this->aliasFields($select, $repository->getAlias()); - } - $this->select($select, true); - } - - /** - * Sets the default types for converting the fields in the select clause - * - * @return void - */ - protected function _addDefaultSelectTypes(): void - { - $typeMap = $this->getTypeMap()->getDefaults(); - $select = $this->clause('select'); - $types = []; - - foreach ($select as $alias => $value) { - if ($value instanceof TypedResultInterface) { - $types[$alias] = $value->getReturnType(); - continue; - } - if (isset($typeMap[$alias])) { - $types[$alias] = $typeMap[$alias]; - continue; - } - if (is_string($value) && isset($typeMap[$value])) { - $types[$alias] = $typeMap[$value]; - } - } - $this->getSelectTypeMap()->addDefaults($types); - } - - /** - * {@inheritDoc} - * - * @param string $finder The finder method to use. - * @param array $options The options for the finder. - * @return static Returns a modified query. - * @psalm-suppress MoreSpecificReturnType - */ - public function find(string $finder, array $options = []) - { - $table = $this->getRepository(); - - /** @psalm-suppress LessSpecificReturnStatement */ - return $table->callFinder($finder, $this, $options); - } - - /** - * Marks a query as dirty, removing any preprocessed information - * from in memory caching such as previous results - * - * @return void - */ - protected function _dirty(): void - { - $this->_results = null; - $this->_resultsCount = null; - parent::_dirty(); - } - - /** - * Create an update query. - * - * This changes the query type to be 'update'. - * Can be combined with set() and where() methods to create update queries. - * - * @param \Cake\Database\ExpressionInterface|string|null $table Unused parameter. - * @return $this - */ - public function update($table = null) - { - if (!$table) { - $repository = $this->getRepository(); - $table = $repository->getTable(); - } - - return parent::update($table); - } - - /** - * Create a delete query. - * - * This changes the query type to be 'delete'. - * Can be combined with the where() method to create delete queries. - * - * @param string|null $table Unused parameter. - * @return $this - */ - public function delete(?string $table = null) - { - $repository = $this->getRepository(); - $this->from([$repository->getAlias() => $repository->getTable()]); - - // We do not pass $table to parent class here - return parent::delete(); - } - - /** - * Create an insert query. - * - * This changes the query type to be 'insert'. - * Note calling this method will reset any data previously set - * with Query::values() - * - * Can be combined with the where() method to create delete queries. - * - * @param array $columns The columns to insert into. - * @param array $types A map between columns & their datatypes. - * @return $this - */ - public function insert(array $columns, array $types = []) - { - $repository = $this->getRepository(); - $table = $repository->getTable(); - $this->into($table); - - return parent::insert($columns, $types); - } - - /** - * Returns a new Query that has automatic field aliasing disabled. - * - * @param \Cake\ORM\Table $table The table this query is starting on - * @return static - */ - public static function subquery(Table $table) - { - $query = new static($table->getConnection(), $table); - $query->aliasingEnabled = false; - - return $query; - } - - /** - * {@inheritDoc} - * - * @param string $method the method to call - * @param array $arguments list of arguments for the method to call - * @return mixed - * @throws \BadMethodCallException if the method is called for a non-select query - */ - public function __call(string $method, array $arguments) - { - if ($this->type() === 'select') { - return $this->_call($method, $arguments); - } - - throw new BadMethodCallException( - sprintf('Cannot call method "%s" on a "%s" query', $method, $this->type()) - ); - } - - /** - * @inheritDoc - */ - public function __debugInfo(): array - { - $eagerLoader = $this->getEagerLoader(); - - return parent::__debugInfo() + [ - 'hydrate' => $this->_hydrate, - 'buffered' => $this->_useBufferedResults, - 'formatters' => count($this->_formatters), - 'mapReducers' => count($this->_mapReduce), - 'contain' => $eagerLoader->getContain(), - 'matching' => $eagerLoader->getMatching(), - 'extraOptions' => $this->_options, - 'repository' => $this->_repository, - ]; - } - - /** - * Executes the query and converts the result set into JSON. - * - * Part of JsonSerializable interface. - * - * @return \Cake\Datasource\ResultSetInterface The data to convert to JSON. - */ - public function jsonSerialize(): ResultSetInterface - { - return $this->all(); - } - - /** - * Sets whether the ORM should automatically append fields. - * - * By default calling select() will disable auto-fields. You can re-enable - * auto-fields with this method. - * - * @param bool $value Set true to enable, false to disable. - * @return $this - */ - public function enableAutoFields(bool $value = true) - { - $this->_autoFields = $value; - - return $this; - } - - /** - * Disables automatically appending fields. - * - * @return $this - */ - public function disableAutoFields() - { - $this->_autoFields = false; - - return $this; - } - - /** - * Gets whether the ORM should automatically append fields. - * - * By default calling select() will disable auto-fields. You can re-enable - * auto-fields with enableAutoFields(). - * - * @return bool|null The current value. Returns null if neither enabled or disabled yet. - */ - public function isAutoFieldsEnabled(): ?bool - { - return $this->_autoFields; - } - - /** - * Decorates the results iterator with MapReduce routines and formatters - * - * @param \Traversable $result Original results - * @return \Cake\Datasource\ResultSetInterface - */ - protected function _decorateResults(Traversable $result): ResultSetInterface - { - $result = $this->_applyDecorators($result); - - if (!($result instanceof ResultSet) && $this->isBufferedResultsEnabled()) { - $class = $this->_decoratorClass(); - $result = new $class($result->buffered()); - } - - return $result; - } - - /** - * Helper for ORM\Query exceptions - * - * @param string $method The method that is invalid. - * @param string $message An additional message. - * @return void - * @internal - */ - protected function _deprecatedMethod($method, $message = '') - { - $class = static::class; - $text = "As of 4.5.0 calling {$method}() on {$class} is deprecated. " . $message; - deprecationWarning($text); - } -} +class_alias('Cake\ORM\Query\SelectQuery', 'Cake\ORM\Query'); diff --git a/app/vendor/cakephp/cakephp/src/ORM/Query/CommonQueryTrait.php b/app/vendor/cakephp/cakephp/src/ORM/Query/CommonQueryTrait.php new file mode 100644 index 000000000..1b45f9923 --- /dev/null +++ b/app/vendor/cakephp/cakephp/src/ORM/Query/CommonQueryTrait.php @@ -0,0 +1,87 @@ +getAlias(); + $map = $table->getSchema()->typeMap(); + $fields = []; + foreach ($map as $f => $type) { + $fields[$f] = $fields[$alias . '.' . $f] = $fields[$alias . '__' . $f] = $type; + } + $this->getTypeMap()->addDefaults($fields); + + return $this; + } + + /** + * Set the default Table object that will be used by this query + * and form the `FROM` clause. + * + * @param \Cake\Datasource\RepositoryInterface $repository The default table object to use + * @return $this + */ + public function setRepository(RepositoryInterface $repository) + { + assert( + $repository instanceof Table, + '`$repository` must be an instance of `' . Table::class . '`.', + ); + + $this->_repository = $repository; + + return $this; + } + + /** + * Returns the default repository object that will be used by this query, + * that is, the table that will appear in the "from" clause. + * + * @return \Cake\ORM\Table + */ + public function getRepository(): Table + { + return $this->_repository; + } +} diff --git a/app/vendor/cakephp/cakephp/src/ORM/Query/DeleteQuery.php b/app/vendor/cakephp/cakephp/src/ORM/Query/DeleteQuery.php index 40470210e..f70c4a3a0 100644 --- a/app/vendor/cakephp/cakephp/src/ORM/Query/DeleteQuery.php +++ b/app/vendor/cakephp/cakephp/src/ORM/Query/DeleteQuery.php @@ -16,322 +16,40 @@ */ namespace Cake\ORM\Query; +use Cake\Database\Query\DeleteQuery as DbDeleteQuery; use Cake\Database\ValueBinder; -use Cake\Datasource\ResultSetInterface; -use Cake\ORM\Query; +use Cake\ORM\Table; /** - * Delete Query forward compatibility shim. + * @inheritDoc */ -class DeleteQuery extends Query +class DeleteQuery extends DbDeleteQuery { + use CommonQueryTrait; + /** - * Type of this query (select, insert, update, delete). + * Constructor * - * @var string + * @param \Cake\ORM\Table $table The table this query is starting on */ - protected $_type = 'delete'; + public function __construct(Table $table) + { + parent::__construct($table->getConnection()); + + $this->setRepository($table); + $this->addDefaultTypes($table); + } /** * @inheritDoc */ public function sql(?ValueBinder $binder = null): string { - if ($this->_type === 'delete' && empty($this->_parts['from'])) { + if (empty($this->_parts['from'])) { $repository = $this->getRepository(); $this->from([$repository->getAlias() => $repository->getTable()]); } return parent::sql($binder); } - - /** - * @inheritDoc - */ - public function delete(?string $table = null) - { - $this->_deprecatedMethod('delete()', 'Remove this method call.'); - - return parent::delete($table); - } - - /** - * @inheritDoc - */ - public function cache($key, $config = 'default') - { - $this->_deprecatedMethod('cache()', 'Use execute() instead.'); - - return parent::cache($key, $config); - } - - /** - * @inheritDoc - */ - public function all(): ResultSetInterface - { - $this->_deprecatedMethod('all()', 'Use execute() instead.'); - - return parent::all(); - } - - /** - * @inheritDoc - */ - public function select($fields = [], bool $overwrite = false) - { - $this->_deprecatedMethod('select()', 'Create your query with selectQuery() instead.'); - - return parent::select($fields, $overwrite); - } - - /** - * @inheritDoc - */ - public function distinct($on = [], $overwrite = false) - { - $this->_deprecatedMethod('distinct()'); - - return parent::distinct($on, $overwrite); - } - - /** - * @inheritDoc - */ - public function modifier($modifiers, $overwrite = false) - { - $this->_deprecatedMethod('modifier()'); - - return parent::modifier($modifiers, $overwrite); - } - - /** - * @inheritDoc - */ - public function join($tables, $types = [], $overwrite = false) - { - $this->_deprecatedMethod('join()'); - - return parent::join($tables, $types, $overwrite); - } - - /** - * @inheritDoc - */ - public function removeJoin(string $name) - { - $this->_deprecatedMethod('removeJoin()'); - - return parent::removeJoin($name); - } - - /** - * @inheritDoc - */ - public function leftJoin($table, $conditions = [], $types = []) - { - $this->_deprecatedMethod('leftJoin()'); - - return parent::leftJoin($table, $conditions, $types); - } - - /** - * @inheritDoc - */ - public function rightJoin($table, $conditions = [], $types = []) - { - $this->_deprecatedMethod('rightJoin()'); - - return parent::rightJoin($table, $conditions, $types); - } - - /** - * @inheritDoc - */ - public function leftJoinWith(string $assoc, ?callable $builder = null) - { - $this->_deprecatedMethod('leftJoinWith()'); - - return parent::leftJoinWith($assoc, $builder); - } - - /** - * @inheritDoc - */ - public function innerJoin($table, $conditions = [], $types = []) - { - $this->_deprecatedMethod('innerJoin()'); - - return parent::innerJoin($table, $conditions, $types); - } - - /** - * @inheritDoc - */ - public function innerJoinWith(string $assoc, ?callable $builder = null) - { - $this->_deprecatedMethod('innerJoinWith()'); - - return parent::innerJoinWith($assoc, $builder); - } - - /** - * @inheritDoc - */ - public function group($fields, $overwrite = false) - { - $this->_deprecatedMethod('group()'); - - return parent::group($fields, $overwrite); - } - - /** - * @inheritDoc - */ - public function having($conditions = null, $types = [], $overwrite = false) - { - $this->_deprecatedMethod('having()'); - - return parent::having($conditions, $types, $overwrite); - } - - /** - * @inheritDoc - */ - public function andHaving($conditions, $types = []) - { - $this->_deprecatedMethod('andHaving()'); - - return parent::andHaving($conditions, $types); - } - - /** - * @inheritDoc - */ - public function page(int $num, ?int $limit = null) - { - $this->_deprecatedMethod('page()'); - - return parent::page($num, $limit); - } - - /** - * @inheritDoc - */ - public function union($query, $overwrite = false) - { - $this->_deprecatedMethod('union()'); - - return parent::union($query, $overwrite); - } - - /** - * @inheritDoc - */ - public function unionAll($query, $overwrite = false) - { - $this->_deprecatedMethod('union()'); - - return parent::unionAll($query, $overwrite); - } - - /** - * @inheritDoc - */ - public function insert(array $columns, array $types = []) - { - $this->_deprecatedMethod('insert()', 'Create your query with insertQuery() instead.'); - - return parent::insert($columns, $types); - } - - /** - * @inheritDoc - */ - public function into(string $table) - { - $this->_deprecatedMethod('into()', 'Use from() instead.'); - - return parent::into($table); - } - - /** - * @inheritDoc - */ - public function values($data) - { - $this->_deprecatedMethod('values()'); - - return parent::values($data); - } - - /** - * @inheritDoc - */ - public function update($table = null) - { - $this->_deprecatedMethod('update()', 'Create your query with updateQuery() instead.'); - - return parent::update($table); - } - - /** - * @inheritDoc - */ - public function set($key, $value = null, $types = []) - { - $this->_deprecatedMethod('set()'); - - return parent::set($key, $value, $types); - } - - /** - * @inheritDoc - */ - public function matching(string $assoc, ?callable $builder = null) - { - $this->_deprecatedMethod('matching()'); - - return parent::matching($assoc, $builder); - } - - /** - * @inheritDoc - */ - public function notMatching(string $assoc, ?callable $builder = null) - { - $this->_deprecatedMethod('notMatching()'); - - return parent::notMatching($assoc, $builder); - } - - /** - * @inheritDoc - */ - public function contain($associations, $override = false) - { - $this->_deprecatedMethod('contain()'); - - return parent::contain($associations, $override); - } - - /** - * @inheritDoc - */ - public function getContain(): array - { - $this->_deprecatedMethod('getContain()'); - - return parent::getContain(); - } - - /** - * @inheritDoc - */ - public function find(string $finder, array $options = []) - { - $this->_deprecatedMethod('find()'); - - return parent::find($finder, $options); - } } diff --git a/app/vendor/cakephp/cakephp/src/ORM/Query/InsertQuery.php b/app/vendor/cakephp/cakephp/src/ORM/Query/InsertQuery.php index 6c3462f97..d9026eb39 100644 --- a/app/vendor/cakephp/cakephp/src/ORM/Query/InsertQuery.php +++ b/app/vendor/cakephp/cakephp/src/ORM/Query/InsertQuery.php @@ -16,398 +16,40 @@ */ namespace Cake\ORM\Query; -use Cake\Datasource\ResultSetInterface; -use Cake\ORM\Query; +use Cake\Database\Query\InsertQuery as DbInsertQuery; +use Cake\Database\ValueBinder; +use Cake\ORM\Table; /** - * Insert Query forward compatibility shim. + * @inheritDoc */ -class InsertQuery extends Query +class InsertQuery extends DbInsertQuery { - /** - * Type of this query (select, insert, update, delete). - * - * @var string - */ - protected $_type = 'insert'; - - /** - * @inheritDoc - */ - public function delete(?string $table = null) - { - $this->_deprecatedMethod('delete()', 'Create your query with deleteQuery() instead.'); - - return parent::delete($table); - } - - /** - * @inheritDoc - */ - public function cache($key, $config = 'default') - { - $this->_deprecatedMethod('cache()', 'Use execute() instead.'); - - return parent::cache($key, $config); - } - - /** - * @inheritDoc - */ - public function all(): ResultSetInterface - { - $this->_deprecatedMethod('all()', 'Use execute() instead.'); - - return parent::all(); - } - - /** - * @inheritDoc - */ - public function select($fields = [], bool $overwrite = false) - { - $this->_deprecatedMethod('select()', 'Create your query with selectQuery() instead.'); - - return parent::select($fields, $overwrite); - } - - /** - * @inheritDoc - */ - public function distinct($on = [], $overwrite = false) - { - $this->_deprecatedMethod('distinct()'); - - return parent::distinct($on, $overwrite); - } - - /** - * @inheritDoc - */ - public function modifier($modifiers, $overwrite = false) - { - $this->_deprecatedMethod('modifier()'); - - return parent::modifier($modifiers, $overwrite); - } - - /** - * @inheritDoc - */ - public function join($tables, $types = [], $overwrite = false) - { - $this->_deprecatedMethod('join()'); - - return parent::join($tables, $types, $overwrite); - } - - /** - * @inheritDoc - */ - public function removeJoin(string $name) - { - $this->_deprecatedMethod('removeJoin()'); - - return parent::removeJoin($name); - } - - /** - * @inheritDoc - */ - public function leftJoin($table, $conditions = [], $types = []) - { - $this->_deprecatedMethod('leftJoin()'); - - return parent::leftJoin($table, $conditions, $types); - } - - /** - * @inheritDoc - */ - public function rightJoin($table, $conditions = [], $types = []) - { - $this->_deprecatedMethod('rightJoin()'); - - return parent::rightJoin($table, $conditions, $types); - } - - /** - * @inheritDoc - */ - public function leftJoinWith(string $assoc, ?callable $builder = null) - { - $this->_deprecatedMethod('leftJoinWith()'); - - return parent::leftJoinWith($assoc, $builder); - } - - /** - * @inheritDoc - */ - public function innerJoin($table, $conditions = [], $types = []) - { - $this->_deprecatedMethod('innerJoin()'); - - return parent::innerJoin($table, $conditions, $types); - } - - /** - * @inheritDoc - */ - public function innerJoinWith(string $assoc, ?callable $builder = null) - { - $this->_deprecatedMethod('innerJoinWith()'); - - return parent::innerJoinWith($assoc, $builder); - } - - /** - * @inheritDoc - */ - public function group($fields, $overwrite = false) - { - $this->_deprecatedMethod('group()'); - - return parent::group($fields, $overwrite); - } - - /** - * @inheritDoc - */ - public function having($conditions = null, $types = [], $overwrite = false) - { - $this->_deprecatedMethod('having()'); - - return parent::having($conditions, $types, $overwrite); - } - - /** - * @inheritDoc - */ - public function andHaving($conditions, $types = []) - { - $this->_deprecatedMethod('andHaving()'); - - return parent::andHaving($conditions, $types); - } - - /** - * @inheritDoc - */ - public function page(int $num, ?int $limit = null) - { - $this->_deprecatedMethod('page()'); - - return parent::page($num, $limit); - } - - /** - * @inheritDoc - */ - public function offset($offset) - { - $this->_deprecatedMethod('offset()'); - - return parent::offset($offset); - } + use CommonQueryTrait; /** - * @inheritDoc - */ - public function union($query, $overwrite = false) - { - $this->_deprecatedMethod('union()'); - - return parent::union($query, $overwrite); - } - - /** - * @inheritDoc - */ - public function unionAll($query, $overwrite = false) - { - $this->_deprecatedMethod('union()'); - - return parent::unionAll($query, $overwrite); - } - - /** - * @inheritDoc - */ - public function from($tables = [], $overwrite = false) - { - $this->_deprecatedMethod('from()', 'Use into() instead.'); - - return parent::from($tables, $overwrite); - } - - /** - * @inheritDoc - */ - public function update($table = null) - { - $this->_deprecatedMethod('update()', 'Create your query with updateQuery() instead.'); - - return parent::update($table); - } - - /** - * @inheritDoc - */ - public function set($key, $value = null, $types = []) - { - $this->_deprecatedMethod('set()'); - - return parent::set($key, $value, $types); - } - - /** - * @inheritDoc - */ - public function matching(string $assoc, ?callable $builder = null) - { - $this->_deprecatedMethod('matching()'); - - return parent::matching($assoc, $builder); - } - - /** - * @inheritDoc - */ - public function notMatching(string $assoc, ?callable $builder = null) - { - $this->_deprecatedMethod('notMatching()'); - - return parent::notMatching($assoc, $builder); - } - - /** - * @inheritDoc - */ - public function contain($associations, $override = false) - { - $this->_deprecatedMethod('contain()'); - - return parent::contain($associations, $override); - } - - /** - * @inheritDoc - */ - public function getContain(): array - { - $this->_deprecatedMethod('getContain()'); - - return parent::getContain(); - } - - /** - * @inheritDoc - */ - public function find(string $finder, array $options = []) - { - $this->_deprecatedMethod('find()'); - - return parent::find($finder, $options); - } - - /** - * @inheritDoc - */ - public function where($conditions = null, array $types = [], bool $overwrite = false) - { - $this->_deprecatedMethod('where()'); - - return parent::where($conditions, $types, $overwrite); - } - - /** - * @inheritDoc - */ - public function whereNotNull($fields) - { - $this->_deprecatedMethod('whereNotNull()'); - - return parent::whereNotNull($fields); - } - - /** - * @inheritDoc - */ - public function whereNull($fields) - { - $this->_deprecatedMethod('whereNull()'); - - return parent::whereNull($fields); - } - - /** - * @inheritDoc - */ - public function whereInList(string $field, array $values, array $options = []) - { - $this->_deprecatedMethod('whereInList()'); - - return parent::whereInList($field, $values, $options); - } - - /** - * @inheritDoc - */ - public function whereNotInList(string $field, array $values, array $options = []) - { - $this->_deprecatedMethod('whereNotInList()'); - - return parent::whereNotInList($field, $values, $options); - } - - /** - * @inheritDoc - */ - public function whereNotInListOrNull(string $field, array $values, array $options = []) - { - $this->_deprecatedMethod('whereNotInListOrNull()'); - - return parent::whereNotInListOrNull($field, $values, $options); - } - - /** - * @inheritDoc - */ - public function andWhere($conditions, array $types = []) - { - $this->_deprecatedMethod('andWhere()'); - - return parent::andWhere($conditions, $types); - } - - /** - * @inheritDoc - */ - public function order($fields, $overwrite = false) - { - $this->_deprecatedMethod('order()'); - - return parent::order($fields, $overwrite); - } - - /** - * @inheritDoc + * Constructor + * + * @param \Cake\ORM\Table $table The table this query is starting on */ - public function orderAsc($field, $overwrite = false) + public function __construct(Table $table) { - $this->_deprecatedMethod('orderAsc()'); + parent::__construct($table->getConnection()); - return parent::orderAsc($field, $overwrite); + $this->setRepository($table); + $this->addDefaultTypes($table); } /** * @inheritDoc */ - public function orderDesc($field, $overwrite = false) + public function sql(?ValueBinder $binder = null): string { - $this->_deprecatedMethod('orderDesc()'); + if (empty($this->_parts['into'])) { + $repository = $this->getRepository(); + $this->into($repository->getTable()); + } - return parent::orderDesc($field, $overwrite); + return parent::sql($binder); } } diff --git a/app/vendor/cakephp/cakephp/src/ORM/Query/QueryFactory.php b/app/vendor/cakephp/cakephp/src/ORM/Query/QueryFactory.php new file mode 100644 index 000000000..b9d049938 --- /dev/null +++ b/app/vendor/cakephp/cakephp/src/ORM/Query/QueryFactory.php @@ -0,0 +1,69 @@ + */ -class SelectQuery extends Query +class SelectQuery extends DbSelectQuery implements JsonSerializable, QueryInterface { + use CommonQueryTrait; + + /** + * Indicates that the operation should append to the list + * + * @var int + */ + public const APPEND = 0; + + /** + * Indicates that the operation should prepend to the list + * + * @var int + */ + public const PREPEND = 1; + + /** + * Indicates that the operation should overwrite the list + * + * @var bool + */ + public const OVERWRITE = true; + + /** + * Whether the user select any fields before being executed, this is used + * to determined if any fields should be automatically be selected. + * + * @var bool|null + */ + protected ?bool $_hasFields = null; + + /** + * Tracks whether the original query should include + * fields from the top level table. + * + * @var bool|null + */ + protected ?bool $_autoFields = null; + + /** + * Whether to hydrate results into entity objects + * + * @var bool + */ + protected bool $_hydrate = true; + + /** + * Whether aliases are generated for fields. + * + * @var bool + */ + protected bool $aliasingEnabled = true; + + /** + * A callback used to calculate the total amount of + * records this query will match when not using `limit` + * + * @var \Closure|null + */ + protected ?Closure $_counter = null; + + /** + * Instance of a class responsible for storing association containments and + * for eager loading them when this query is executed + * + * @var \Cake\ORM\EagerLoader|null + */ + protected ?EagerLoader $_eagerLoader = null; + + /** + * Whether the query is standalone or the product of an eager load operation. + * + * @var bool + */ + protected bool $_eagerLoaded = false; + + /** + * True if the beforeFind event has already been triggered for this query + * + * @var bool + */ + protected bool $_beforeFindFired = false; + + /** + * The COUNT(*) for the query. + * + * When set, count query execution will be bypassed. + * + * @var int|null + */ + protected ?int $_resultsCount = null; + + /** + * Result set factory + * + * @var \Cake\ORM\ResultSetFactory<\Cake\Datasource\EntityInterface|array> + */ + protected ResultSetFactory $resultSetFactory; + + /** + * A ResultSet. + * + * When set, SelectQuery execution will be bypassed. + * + * @var iterable|null + * @see \Cake\Datasource\QueryTrait::setResult() + */ + protected ?iterable $_results = null; + + /** + * List of map-reduce routines that should be applied over the query + * result + * + * @var array + */ + protected array $_mapReduce = []; + + /** + * List of formatter classes or callbacks that will post-process the + * results when fetched + * + * @var array<\Closure> + */ + protected array $_formatters = []; + + /** + * A query cacher instance if this query has caching enabled. + * + * @var \Cake\Datasource\QueryCacher|null + */ + protected ?QueryCacher $_cache = null; + + /** + * Holds any custom options passed using applyOptions that could not be processed + * by any method in this class. + * + * @var array + */ + protected array $_options = []; + + /** + * Constructor + * + * @param \Cake\ORM\Table $table The table this query is starting on + */ + public function __construct(Table $table) + { + parent::__construct($table->getConnection()); + + $this->setRepository($table); + $this->addDefaultTypes($table); + } + + /** + * Set the result set for a query. + * + * Setting the result set of a query will make execute() a no-op. Instead + * of executing the SQL query and fetching results, the ResultSet provided to this + * method will be returned. + * + * This method is most useful when combined with results stored in a persistent cache. + * + * @param iterable $results The results this query should return. + * @return $this + */ + public function setResult(iterable $results) + { + $this->_results = $results; + + return $this; + } + + /** + * Executes this query and returns a results iterator. This function is required + * for implementing the IteratorAggregate interface and allows the query to be + * iterated without having to call execute() manually, thus making it look like + * a result set instead of the query itself. + * + * @return \Cake\Datasource\ResultSetInterface<\Cake\Datasource\EntityInterface|array> + */ + public function getIterator(): ResultSetInterface + { + return $this->all(); + } + + /** + * Enable result caching for this query. + * + * If a query has caching enabled, it will do the following when executed: + * + * - Check the cache for $key. If there are results no SQL will be executed. + * Instead the cached results will be returned. + * - When the cached data is stale/missing the result set will be cached as the query + * is executed. + * + * ### Usage + * + * ``` + * // Simple string key + config + * $query->cache('my_key', 'db_results'); + * + * // Function to generate key. + * $query->cache(function ($q) { + * $key = serialize($q->clause('select')); + * $key .= serialize($q->clause('where')); + * return md5($key); + * }); + * + * // Using a pre-built cache engine. + * $query->cache('my_key', $engine); + * + * // Disable caching + * $query->cache(false); + * ``` + * + * @param \Closure|string|false $key Either the cache key or a function to generate the cache key. + * When using a function, this query instance will be supplied as an argument. + * @param \Psr\SimpleCache\CacheInterface|string $config Either the name of the cache config to use, or + * a cache engine instance. + * @return $this + */ + public function cache(Closure|string|false $key, CacheInterface|string $config = 'default') + { + if ($key === false) { + $this->_cache = null; + + return $this; + } + + $this->_cache = new QueryCacher($key, $config); + + return $this; + } + + /** + * Returns the current configured query `_eagerLoaded` value + * + * @return bool + */ + public function isEagerLoaded(): bool + { + return $this->_eagerLoaded; + } + + /** + * Sets the query instance to be an eager loaded query. If no argument is + * passed, the current configured query `_eagerLoaded` value is returned. + * + * @param bool $value Whether to eager load. + * @return $this + */ + public function eagerLoaded(bool $value) + { + $this->_eagerLoaded = $value; + + return $this; + } + + /** + * Returns a key => value array representing a single aliased field + * that can be passed directly to the select() method. + * The key will contain the alias and the value the actual field name. + * + * If the field is already aliased, then it will not be changed. + * If no $alias is passed, the default table for this query will be used. + * + * @param string $field The field to alias + * @param string|null $alias the alias used to prefix the field + * @return array + */ + public function aliasField(string $field, ?string $alias = null): array + { + if (str_contains($field, '.')) { + $aliasedField = $field; + [$alias, $field] = explode('.', $field); + } else { + $alias = $alias ?: $this->getRepository()->getAlias(); + $aliasedField = $alias . '.' . $field; + } + + $key = sprintf('%s__%s', $alias, $field); + + return [$key => $aliasedField]; + } + + /** + * Runs `aliasField()` for each field in the provided list and returns + * the result under a single array. + * + * @param array $fields The fields to alias + * @param string|null $defaultAlias The default alias + * @return array + */ + public function aliasFields(array $fields, ?string $defaultAlias = null): array + { + $aliased = []; + foreach ($fields as $alias => $field) { + if (is_numeric($alias) && is_string($field)) { + $aliased += $this->aliasField($field, $defaultAlias); + continue; + } + $aliased[$alias] = $field; + } + + return $aliased; + } + + /** + * Fetch the results for this query. + * + * Will return either the results set through setResult(), or execute this query + * and return the ResultSetDecorator object ready for streaming of results. + * + * ResultSetDecorator is a traversable object that implements the methods found + * on Cake\Collection\Collection. + * + * @return \Cake\Datasource\ResultSetInterface + */ + public function all(): ResultSetInterface + { + if ($this->_results !== null) { + if (!($this->_results instanceof ResultSetInterface)) { + $this->_results = $this->_decorateResults($this->_results); + } + + return $this->_results; + } + + $results = $this->_cache?->fetch($this); + if ($results === null) { + $results = $this->_decorateResults($this->_execute()); + $this->_cache?->store($this, $results); + } + $this->_results = $results; + + return $results; + } + + /** + * Returns an array representation of the results after executing the query. + * + * @return array + */ + public function toArray(): array + { + return $this->all()->toArray(); + } + + /** + * Register a new MapReduce routine to be executed on top of the database results + * + * The MapReduce routing will only be run when the query is executed and the first + * result is attempted to be fetched. + * + * If the third argument is set to true, it will erase previous map reducers + * and replace it with the arguments passed. + * + * @param \Closure|null $mapper The mapper function + * @param \Closure|null $reducer The reducing function + * @param bool $overwrite Set to true to overwrite existing map + reduce functions. + * @return $this + * @see \Cake\Collection\Iterator\MapReduce for details on how to use emit data to the map reducer. + */ + public function mapReduce(?Closure $mapper = null, ?Closure $reducer = null, bool $overwrite = false) + { + if ($overwrite) { + $this->_mapReduce = []; + } + if ($mapper === null) { + if (!$overwrite) { + throw new InvalidArgumentException('$mapper can be null only when $overwrite is true.'); + } + + return $this; + } + $this->_mapReduce[] = compact('mapper', 'reducer'); + + return $this; + } + + /** + * Returns the list of previously registered map reduce routines. + * + * @return array + */ + public function getMapReducers(): array + { + return $this->_mapReduce; + } + + /** + * Registers a new formatter callback function that is to be executed when trying + * to fetch the results from the database. + * + * If the second argument is set to true, it will erase previous formatters + * and replace them with the passed first argument. + * + * Callbacks are required to return an iterator object, which will be used as + * the return value for this query's result. Formatter functions are applied + * after all the `MapReduce` routines for this query have been executed. + * + * Formatting callbacks will receive two arguments, the first one being an object + * implementing `\Cake\Collection\CollectionInterface`, that can be traversed and + * modified at will. The second one being the query instance on which the formatter + * callback is being applied. + * + * Usually the query instance received by the formatter callback is the same query + * instance on which the callback was attached to, except for in a joined + * association, in that case the callback will be invoked on the association source + * side query, and it will receive that query instance instead of the one on which + * the callback was originally attached to - see the examples below! + * + * ### Examples: + * + * Return all results from the table indexed by id: + * + * ``` + * $query->select(['id', 'name'])->formatResults(function ($results) { + * return $results->indexBy('id'); + * }); + * ``` + * + * Add a new column to the ResultSet: + * + * ``` + * $query->select(['name', 'birth_date'])->formatResults(function ($results) { + * return $results->map(function ($row) { + * $row['age'] = $row['birth_date']->diff(new DateTime)->y; + * + * return $row; + * }); + * }); + * ``` + * + * Add a new column to the results with respect to the query's hydration configuration: + * + * ``` + * $query->formatResults(function ($results, $query) { + * return $results->map(function ($row) use ($query) { + * $data = [ + * 'bar' => 'baz', + * ]; + * + * if ($query->isHydrationEnabled()) { + * $row['foo'] = new Foo($data) + * } else { + * $row['foo'] = $data; + * } + * + * return $row; + * }); + * }); + * ``` + * + * Retaining access to the association target query instance of joined associations, + * by inheriting the contain callback's query argument: + * + * ``` + * // Assuming a `Articles belongsTo Authors` association that uses the join strategy + * + * $articlesQuery->contain('Authors', function ($authorsQuery) { + * return $authorsQuery->formatResults(function ($results, $query) use ($authorsQuery) { + * // Here `$authorsQuery` will always be the instance + * // where the callback was attached to. + * + * // The instance passed to the callback in the second + * // argument (`$query`), will be the one where the + * // callback is actually being applied to, in this + * // example that would be `$articlesQuery`. + * + * // ... + * + * return $results; + * }); + * }); + * ``` + * + * @param \Closure|null $formatter The formatting function + * @param int|bool $mode Whether to overwrite, append or prepend the formatter. + * @return $this + * @throws \InvalidArgumentException + */ + public function formatResults(?Closure $formatter = null, int|bool $mode = self::APPEND) + { + if ($mode === self::OVERWRITE) { + $this->_formatters = []; + } + if ($formatter === null) { + if ($mode !== self::OVERWRITE) { + throw new InvalidArgumentException('$formatter can be null only when $mode is overwrite.'); + } + + return $this; + } + + if ($mode === self::PREPEND) { + array_unshift($this->_formatters, $formatter); + + return $this; + } + + $this->_formatters[] = $formatter; + + return $this; + } + + /** + * Returns the list of previously registered format routines. + * + * @return array<\Closure> + */ + public function getResultFormatters(): array + { + return $this->_formatters; + } + + /** + * Returns the first result out of executing this query, if the query has not been + * executed before, it will set the limit clause to 1 for performance reasons. + * + * ### Example: + * + * ``` + * $singleUser = $query->select(['id', 'username'])->first(); + * ``` + * + * @return mixed The first result from the ResultSet. + */ + public function first(): mixed + { + if ($this->_dirty) { + $this->limit(1); + } + + return $this->all()->first(); + } + + /** + * Get the first result from the executing query or raise an exception. + * + * @throws \Cake\Datasource\Exception\RecordNotFoundException When there is no first record. + * @return mixed The first result from the ResultSet. + */ + public function firstOrFail(): mixed + { + $entity = $this->first(); + if (!$entity) { + $table = $this->getRepository(); + throw new RecordNotFoundException(sprintf( + 'Record not found in table `%s`.', + $table->getTable(), + )); + } + + return $entity; + } + + /** + * Returns an array with the custom options that were applied to this query + * and that were not already processed by another method in this class. + * + * ### Example: + * + * ``` + * $query->applyOptions(['doABarrelRoll' => true, 'fields' => ['id', 'name']); + * $query->getOptions(); // Returns ['doABarrelRoll' => true] + * ``` + * + * @see \Cake\Datasource\QueryInterface::applyOptions() to read about the options that will + * be processed by this class and not returned by this function + * @return array + * @see applyOptions() + */ + public function getOptions(): array + { + return $this->_options; + } + + /** + * Populates or adds parts to current query clauses using an array. + * This is handy for passing all query clauses at once. + * + * The method accepts the following query clause related options: + * + * - fields: Maps to the select method + * - conditions: Maps to the where method + * - limit: Maps to the limit method + * - order: Maps to the order method + * - offset: Maps to the offset method + * - group: Maps to the group method + * - having: Maps to the having method + * - contain: Maps to the contain options for eager loading + * - join: Maps to the join method + * - page: Maps to the page method + * + * All other options will not affect the query, but will be stored + * as custom options that can be read via `getOptions()`. Furthermore + * they are automatically passed to `Model.beforeFind`. + * + * ### Example: + * + * ``` + * $query->applyOptions([ + * 'fields' => ['id', 'name'], + * 'conditions' => [ + * 'created >=' => '2013-01-01' + * ], + * 'limit' => 10, + * ]); + * ``` + * + * Is equivalent to: + * + * ``` + * $query + * ->select(['id', 'name']) + * ->where(['created >=' => '2013-01-01']) + * ->limit(10) + * ``` + * + * Custom options can be read via `getOptions()`: + * + * ``` + * $query->applyOptions([ + * 'fields' => ['id', 'name'], + * 'custom' => 'value', + * ]); + * ``` + * + * Here `$options` will hold `['custom' => 'value']` (the `fields` + * option will be applied to the query instead of being stored, as + * it's a query clause related option): + * + * ``` + * $options = $query->getOptions(); + * ``` + * + * @param array $options The options to be applied + * @return $this + * @see getOptions() + */ + public function applyOptions(array $options) + { + $valid = [ + 'select' => 'select', + 'fields' => 'select', + 'conditions' => 'where', + 'where' => 'where', + 'join' => 'join', + 'order' => 'orderBy', + 'orderBy' => 'orderBy', + 'limit' => 'limit', + 'offset' => 'offset', + 'group' => 'groupBy', + 'groupBy' => 'groupBy', + 'having' => 'having', + 'contain' => 'contain', + 'page' => 'page', + ]; + + ksort($options); + foreach ($options as $option => $values) { + if (isset($valid[$option], $values)) { + $this->{$valid[$option]}($values); + } else { + $this->_options[$option] = $values; + } + } + + return $this; + } + + /** + * Decorates the results iterator with MapReduce routines and formatters + * + * @param iterable $result Original results + * @return \Cake\Datasource\ResultSetInterface<\Cake\Datasource\EntityInterface|mixed> + */ + protected function _decorateResults(iterable $result): ResultSetInterface + { + $resultSetClass = $this->resultSetFactory()->getResultSetClass(); + + if ($this->_mapReduce) { + foreach ($this->_mapReduce as $functions) { + $result = new MapReduce($result, $functions['mapper'], $functions['reducer']); + } + $result = new $resultSetClass($result); + } + + if (!($result instanceof ResultSetInterface)) { + $result = new $resultSetClass($result); + } + + if ($this->_formatters) { + foreach ($this->_formatters as $formatter) { + $result = $formatter($result, $this); + } + + if (!($result instanceof ResultSetInterface)) { + $result = new $resultSetClass($result); + } + } + + return $result; + } + + /** + * Adds new fields to be returned by a `SELECT` statement when this query is + * executed. Fields can be passed as an array of strings, array of expression + * objects, a single expression or a single string. + * + * If an array is passed, keys will be used to alias fields using the value as the + * real field to be aliased. It is possible to alias strings, Expression objects or + * even other Query objects. + * + * If a callback is passed, the returning array of the function will + * be used as the list of fields. + * + * By default, this function will append any passed argument to the list of fields + * to be selected, unless the second argument is set to true. + * + * ### Examples: + * + * ``` + * $query->select(['id', 'title']); // Produces SELECT id, title + * $query->select(['author' => 'author_id']); // Appends author: SELECT id, title, author_id as author + * $query->select('id', true); // Resets the list: SELECT id + * $query->select(['total' => $countQuery]); // SELECT id, (SELECT ...) AS total + * $query->select(function ($query) { + * return ['article_id', 'total' => $query->count('*')]; + * }) + * ``` + * + * By default, no fields are selected, if you have an instance of `Cake\ORM\Query` and try to append + * fields you should also call `Cake\ORM\Query::enableAutoFields()` to select the default fields + * from the table. + * + * If you pass an instance of a `Cake\ORM\Table` or `Cake\ORM\Association` class, + * all the fields in the schema of the table or the association will be added to + * the select clause. + * + * @param \Cake\Database\ExpressionInterface|\Cake\ORM\Table|\Cake\ORM\Association|\Closure|array|string|float|int $fields Fields + * to be added to the list. + * @param bool $overwrite whether to reset fields with passed list or not + * @return $this + */ + public function select( + ExpressionInterface|Table|Association|Closure|array|string|float|int $fields = [], + bool $overwrite = false, + ) { + if ($fields instanceof Association) { + $fields = $fields->getTarget(); + } + + if ($fields instanceof Table) { + if ($this->aliasingEnabled) { + $fields = $this->aliasFields($fields->getSchema()->columns(), $fields->getAlias()); + } else { + $fields = $fields->getSchema()->columns(); + } + } + + return parent::select($fields, $overwrite); + } + + /** + * Behaves the exact same as `select()` except adds the field to the list of fields selected and + * does not disable auto-selecting fields for Associations. + * + * Use this instead of calling `select()` then `enableAutoFields()` to re-enable auto-fields. + * + * @param \Cake\Database\ExpressionInterface|\Cake\ORM\Table|\Cake\ORM\Association|\Closure|array|string|float|int $fields Fields + * to be added to the list. + * @return $this + */ + public function selectAlso( + ExpressionInterface|Table|Association|Closure|array|string|float|int $fields, + ) { + $this->select($fields); + $this->_autoFields = true; + + return $this; + } + + /** + * All the fields associated with the passed table except the excluded + * fields will be added to the select clause of the query. Passed excluded fields should not be aliased. + * After the first call to this method, a second call cannot be used to remove fields that have already + * been added to the query by the first. If you need to change the list after the first call, + * pass overwrite boolean true which will reset the select clause removing all previous additions. + * + * @param \Cake\ORM\Table|\Cake\ORM\Association $table The table to use to get an array of columns + * @param array $excludedFields The un-aliased column names you do not want selected from $table + * @param bool $overwrite Whether to reset/remove previous selected fields + * @return $this + */ + public function selectAllExcept(Table|Association $table, array $excludedFields, bool $overwrite = false) + { + if ($table instanceof Association) { + $table = $table->getTarget(); + } + + $fields = array_diff($table->getSchema()->columns(), $excludedFields); + if ($this->aliasingEnabled) { + $fields = $this->aliasFields($fields); + } + + return $this->select($fields, $overwrite); + } + + /** + * Sets the instance of the eager loader class to use for loading associations + * and storing containments. + * + * @param \Cake\ORM\EagerLoader $instance The eager loader to use. + * @return $this + */ + public function setEagerLoader(EagerLoader $instance) + { + $this->_eagerLoader = $instance; + + return $this; + } + + /** + * Returns the currently configured instance. + * + * @return \Cake\ORM\EagerLoader + */ + public function getEagerLoader(): EagerLoader + { + return $this->_eagerLoader ??= new EagerLoader(); + } + + /** + * Sets the list of associations that should be eagerly loaded along with this + * query. The list of associated tables passed must have been previously set as + * associations using the Table API. + * + * ### Example: + * + * ``` + * // Bring articles' author information + * $query->contain('Author'); + * + * // Also bring the category and tags associated to each article + * $query->contain(['Category', 'Tag']); + * ``` + * + * Associations can be arbitrarily nested using dot notation or nested arrays, + * this allows this object to calculate joins or any additional queries that + * must be executed to bring the required associated data. + * + * ### Example: + * + * ``` + * // Eager load the product info, and for each product load other 2 associations + * $query->contain(['Product' => ['Manufacturer', 'Distributor']); + * + * // Which is equivalent to calling + * $query->contain(['Products.Manufactures', 'Products.Distributors']); + * + * // For an author query, load his region, state and country + * $query->contain('Regions.States.Countries'); + * ``` + * + * It is possible to control the conditions and fields selected for each of the + * contained associations: + * + * ### Example: + * + * ``` + * $query->contain(['Tags' => function ($q) { + * return $q->where(['Tags.is_popular' => true]); + * }]); + * + * $query->contain(['Products.Manufactures' => function ($q) { + * return $q->select(['name'])->where(['Manufactures.active' => true]); + * }]); + * ``` + * + * Each association might define special options when eager loaded, the allowed + * options that can be set per association are: + * + * - `foreignKey`: Used to set a different field to match both tables, if set to false + * no join conditions will be generated automatically. `false` can only be used on + * joinable associations and cannot be used with hasMany or belongsToMany associations. + * - `fields`: An array with the fields that should be fetched from the association. + * - `finder`: The finder to use when loading associated records. Either the name of the + * finder as a string, or an array to define options to pass to the finder. + * - `queryBuilder`: Equivalent to passing a callback instead of an options array. + * + * ### Example: + * + * ``` + * // Set options for the hasMany articles that will be eagerly loaded for an author + * $query->contain([ + * 'Articles' => [ + * 'fields' => ['title', 'author_id'] + * ] + * ]); + * ``` + * + * Finders can be configured to use options. + * + * ``` + * // Retrieve translations for the articles, but only those for the `en` and `es` locales + * $query->contain([ + * 'Articles' => [ + * 'finder' => [ + * 'translations' => [ + * 'locales' => ['en', 'es'] + * ] + * ] + * ] + * ]); + * ``` + * + * When containing associations, it is important to include foreign key columns. + * Failing to do so will trigger exceptions. + * + * ``` + * // Use a query builder to add conditions to the containment + * $query->contain('Authors', function ($q) { + * return $q->where(...); // add conditions + * }); + * // Use special join conditions for multiple containments in the same method call + * $query->contain([ + * 'Authors' => [ + * 'foreignKey' => false, + * 'queryBuilder' => function ($q) { + * return $q->where(...); // Add full filtering conditions + * } + * ], + * 'Tags' => function ($q) { + * return $q->where(...); // add conditions + * } + * ]); + * ``` + * + * If called with an empty first argument and `$override` is set to true, the + * previous list will be emptied. + * + * @param array|string $associations List of table aliases to be queried. + * @param \Closure|bool $override The query builder for the association, or + * if associations is an array, a bool on whether to override previous list + * with the one passed + * defaults to merging previous list with the new one. + * @return $this + */ + public function contain(array|string $associations, Closure|bool $override = false) + { + $loader = $this->getEagerLoader(); + if ($override === true) { + $this->clearContain(); + } + + $queryBuilder = null; + if ($override instanceof Closure) { + $queryBuilder = $override; + } + + if ($associations) { + $loader->contain($associations, $queryBuilder); + } + $this->_addAssociationsToTypeMap( + $this->getRepository(), + $this->getTypeMap(), + $loader->getContain(), + ); + + return $this; + } + + /** + * @return array + */ + public function getContain(): array + { + return $this->getEagerLoader()->getContain(); + } + /** - * Type of this query (select, insert, update, delete). + * Clears the contained associations from the current query. * - * @var string + * @return $this */ - protected $_type = 'select'; + public function clearContain() + { + $this->getEagerLoader()->clearContain(); + $this->_dirty(); + + return $this; + } /** - * @inheritDoc + * Used to recursively add contained association column types to + * the query. + * + * @param \Cake\ORM\Table $table The table instance to pluck associations from. + * @param \Cake\Database\TypeMap $typeMap The typemap to check for columns in. + * This typemap is indirectly mutated via {@link \Cake\ORM\Query\SelectQuery::addDefaultTypes()} + * @param array $associations The nested tree of associations to walk. + * @return void + */ + protected function _addAssociationsToTypeMap(Table $table, TypeMap $typeMap, array $associations): void + { + foreach ($associations as $name => $nested) { + if (!$table->hasAssociation($name)) { + continue; + } + $association = $table->getAssociation($name); + $target = $association->getTarget(); + $primary = (array)$target->getPrimaryKey(); + if (!$primary || $typeMap->type($target->aliasField($primary[0])) === null) { + $this->addDefaultTypes($target); + } + if ($nested) { + $this->_addAssociationsToTypeMap($target, $typeMap, $nested); + } + } + } + + /** + * Adds filtering conditions to this query to only bring rows that have a relation + * to another from an associated table, based on conditions in the associated table. + * + * This function will add entries in the `contain` graph. + * + * ### Example: + * + * ``` + * // Bring only articles that were tagged with 'cake' + * $query->matching('Tags', function ($q) { + * return $q->where(['name' => 'cake']); + * }); + * ``` + * + * It is possible to filter by deep associations by using dot notation: + * + * ### Example: + * + * ``` + * // Bring only articles that were commented by 'markstory' + * $query->matching('Comments.Users', function ($q) { + * return $q->where(['username' => 'markstory']); + * }); + * ``` + * + * As this function will create `INNER JOIN`, you might want to consider + * calling `distinct` on this query as you might get duplicate rows if + * your conditions don't filter them already. This might be the case, for example, + * of the same user commenting more than once in the same article. + * + * ### Example: + * + * ``` + * // Bring unique articles that were commented by 'markstory' + * $query->distinct(['Articles.id']) + * ->matching('Comments.Users', function ($q) { + * return $q->where(['username' => 'markstory']); + * }); + * ``` + * + * Please note that the query passed to the closure will only accept calling + * `select`, `where`, `andWhere` and `orWhere` on it. If you wish to + * add more complex clauses you can do it directly in the main query. + * + * @param string $assoc The association to filter by + * @param \Closure|null $builder a function that will receive a pre-made query object + * that can be used to add custom conditions or selecting some fields + * @return $this */ - public function delete(?string $table = null) + public function matching(string $assoc, ?Closure $builder = null) { - $this->_deprecatedMethod('delete()', 'Create your query with deleteQuery() instead.'); + $result = $this->getEagerLoader()->setMatching($assoc, $builder)->getMatching(); + $this->_addAssociationsToTypeMap($this->getRepository(), $this->getTypeMap(), $result); + $this->_dirty(); - return parent::delete($table); + return $this; } /** - * @inheritDoc + * Creates a LEFT JOIN with the passed association table while preserving + * the foreign key matching and the custom conditions that were originally set + * for it. + * + * This function will add entries in the `contain` graph. + * + * ### Example: + * + * ``` + * // Get the count of articles per user + * $usersQuery + * ->select(['total_articles' => $query->func()->count('Articles.id')]) + * ->leftJoinWith('Articles') + * ->groupBy(['Users.id']) + * ->enableAutoFields(); + * ``` + * + * You can also customize the conditions passed to the LEFT JOIN: + * + * ``` + * // Get the count of articles per user with at least 5 votes + * $usersQuery + * ->select(['total_articles' => $query->func()->count('Articles.id')]) + * ->leftJoinWith('Articles', function ($q) { + * return $q->where(['Articles.votes >=' => 5]); + * }) + * ->groupBy(['Users.id']) + * ->enableAutoFields(); + * ``` + * + * This will create the following SQL: + * + * ``` + * SELECT COUNT(Articles.id) AS total_articles, Users.* + * FROM users Users + * LEFT JOIN articles Articles ON Articles.user_id = Users.id AND Articles.votes >= 5 + * GROUP BY USers.id + * ``` + * + * It is possible to left join deep associations by using dot notation + * + * ### Example: + * + * ``` + * // Total comments in articles by 'markstory' + * $query + * ->select(['total_comments' => $query->func()->count('Comments.id')]) + * ->leftJoinWith('Comments.Users', function ($q) { + * return $q->where(['username' => 'markstory']); + * }) + * ->groupBy(['Users.id']); + * ``` + * + * Please note that the query passed to the closure will only accept calling + * `select`, `where`, `andWhere` and `orWhere` on it. If you wish to + * add more complex clauses you can do it directly in the main query. + * + * @param string $assoc The association to join with + * @param \Closure|null $builder a function that will receive a pre-made query object + * that can be used to add custom conditions or selecting some fields + * @return $this + */ + public function leftJoinWith(string $assoc, ?Closure $builder = null) + { + $result = $this->getEagerLoader() + ->setMatching($assoc, $builder, [ + 'joinType' => static::JOIN_TYPE_LEFT, + 'fields' => false, + ]) + ->getMatching(); + $this->_addAssociationsToTypeMap($this->getRepository(), $this->getTypeMap(), $result); + $this->_dirty(); + + return $this; + } + + /** + * Creates an INNER JOIN with the passed association table while preserving + * the foreign key matching and the custom conditions that were originally set + * for it. + * + * This function will add entries in the `contain` graph. + * + * ### Example: + * + * ``` + * // Bring only articles that were tagged with 'cake' + * $query->innerJoinWith('Tags', function ($q) { + * return $q->where(['name' => 'cake']); + * }); + * ``` + * + * This will create the following SQL: + * + * ``` + * SELECT Articles.* + * FROM articles Articles + * INNER JOIN tags Tags ON Tags.name = 'cake' + * INNER JOIN articles_tags ArticlesTags ON ArticlesTags.tag_id = Tags.id + * AND ArticlesTags.articles_id = Articles.id + * ``` + * + * This function works the same as `matching()` with the difference that it + * will select no fields from the association. + * + * @param string $assoc The association to join with + * @param \Closure|null $builder a function that will receive a pre-made query object + * that can be used to add custom conditions or selecting some fields + * @return $this + * @see \Cake\ORM\Query\SeletQuery::matching() */ - public function insert(array $columns, array $types = []) + public function innerJoinWith(string $assoc, ?Closure $builder = null) { - $this->_deprecatedMethod('insert()', 'Create your query with insertQuery() instead.'); + $result = $this->getEagerLoader() + ->setMatching($assoc, $builder, [ + 'joinType' => static::JOIN_TYPE_INNER, + 'fields' => false, + ]) + ->getMatching(); + $this->_addAssociationsToTypeMap($this->getRepository(), $this->getTypeMap(), $result); + $this->_dirty(); - return parent::insert($columns, $types); + return $this; } /** - * @inheritDoc + * Adds filtering conditions to this query to only bring rows that have no match + * to another from an associated table, based on conditions in the associated table. + * + * This function will add entries in the `contain` graph. + * + * ### Example: + * + * ``` + * // Bring only articles that were not tagged with 'cake' + * $query->notMatching('Tags', function ($q) { + * return $q->where(['name' => 'cake']); + * }); + * ``` + * + * It is possible to filter by deep associations by using dot notation: + * + * ### Example: + * + * ``` + * // Bring only articles that weren't commented by 'markstory' + * $query->notMatching('Comments.Users', function ($q) { + * return $q->where(['username' => 'markstory']); + * }); + * ``` + * + * As this function will create a `LEFT JOIN`, you might want to consider + * calling `distinct` on this query as you might get duplicate rows if + * your conditions don't filter them already. This might be the case, for example, + * of the same article having multiple comments. + * + * ### Example: + * + * ``` + * // Bring unique articles that were commented by 'markstory' + * $query->distinct(['Articles.id']) + * ->notMatching('Comments.Users', function ($q) { + * return $q->where(['username' => 'markstory']); + * }); + * ``` + * + * Please note that the query passed to the closure will only accept calling + * `select`, `where`, `andWhere` and `orWhere` on it. If you wish to + * add more complex clauses you can do it directly in the main query. + * + * @param string $assoc The association to filter by + * @param \Closure|null $builder a function that will receive a pre-made query object + * that can be used to add custom conditions or selecting some fields + * @return $this */ - public function into(string $table) + public function notMatching(string $assoc, ?Closure $builder = null) { - $this->_deprecatedMethod('into()', 'Use from() instead.'); + $result = $this->getEagerLoader() + ->setMatching($assoc, $builder, [ + 'joinType' => static::JOIN_TYPE_LEFT, + 'fields' => false, + 'negateMatch' => true, + ]) + ->getMatching(); + $this->_addAssociationsToTypeMap($this->getRepository(), $this->getTypeMap(), $result); + $this->_dirty(); - return parent::into($table); + return $this; } /** - * @inheritDoc + * Creates a copy of this current query, triggers beforeFind and resets some state. + * + * The following state will be cleared: + * + * - autoFields + * - limit + * - offset + * - map/reduce functions + * - result formatters + * - order + * - containments + * + * This method creates query clones that are useful when working with subqueries. + * + * @return static */ - public function values($data) + public function cleanCopy(): static { - $this->_deprecatedMethod('values()'); + $clone = clone $this; + $clone->triggerBeforeFind(); + $clone->disableAutoFields(); + $clone->limit(null); + $clone->orderBy([], true); + $clone->offset(null); + $clone->mapReduce(null, null, true); + $clone->formatResults(null, self::OVERWRITE); + $clone->setSelectTypeMap(new TypeMap()); + $clone->decorateResults(null, true); - return parent::values($data); + return $clone; } /** - * @inheritDoc + * Clears the internal result cache and the internal count value from the current + * query object. + * + * @return $this + */ + public function clearResult() + { + $this->_dirty(); + + return $this; + } + + /** + * {@inheritDoc} + * + * Handles cloning eager loaders. + */ + public function __clone() + { + parent::__clone(); + if ($this->_eagerLoader !== null) { + $this->_eagerLoader = clone $this->_eagerLoader; + } + } + + /** + * {@inheritDoc} + * + * Returns the COUNT(*) for the query. If the query has not been + * modified, and the count has already been performed the cached + * value is returned + * + * @return int + */ + public function count(): int + { + return $this->_resultsCount ??= $this->_performCount(); + } + + /** + * Performs and returns the COUNT(*) for the query. + * + * @return int + */ + protected function _performCount(): int + { + $query = $this->cleanCopy(); + $counter = $this->_counter; + if ($counter !== null) { + $query->counter(null); + + return (int)$counter($query); + } + + $complex = ( + $query->clause('distinct') || + count($query->clause('group')) || + count($query->clause('union')) || + count($query->clause('intersect')) || + $query->clause('having') + ); + + if (!$complex) { + // Expression fields could have bound parameters. + foreach ($query->clause('select') as $field) { + if ($field instanceof ExpressionInterface) { + $complex = true; + break; + } + } + } + + if (!$complex && $this->_valueBinder !== null) { + $order = $this->clause('order'); + assert($order === null || $order instanceof QueryExpression); + $complex = $order === null ? false : $order->hasNestedExpression(); + } + + $count = ['count' => $query->func()->count('*')]; + + if ($complex) { + $statement = $this->getConnection()->selectQuery() + ->select($count) + ->from(['count_source' => $query]) + ->execute(); + } else { + $query->getEagerLoader()->disableAutoFields(); + $statement = $query + ->select($count, true) + ->disableAutoFields() + ->execute(); + } + + $result = $statement->fetch(PDO::FETCH_ASSOC); + + return $result === false ? 0 : (int)$result['count']; + } + + /** + * Registers a callback that will be executed when the `count` method in + * this query is called. The return value for the function will be set as the + * return value of the `count` method. + * + * This is particularly useful when you need to optimize a query for returning the + * count, for example removing unnecessary joins, removing group by or just return + * an estimated number of rows. + * + * The callback will receive as first argument a clone of this query and not this + * query itself. + * + * If the first param is a null value, the built-in counter function will be called + * instead + * + * @param \Closure|null $counter The counter value + * @return $this + */ + public function counter(?Closure $counter) + { + $this->_counter = $counter; + + return $this; + } + + /** + * Toggle hydrating entities. + * + * If set to false array results will be returned for the query. + * + * @param bool $enable Use a boolean to set the hydration mode. + * @return $this + */ + public function enableHydration(bool $enable = true) + { + $this->_dirty(); + $this->_hydrate = $enable; + + return $this; + } + + /** + * Disable hydrating entities. + * + * Disabling hydration will cause array results to be returned for the query + * instead of entities. + * + * @return $this */ - public function update($table = null) + public function disableHydration() { - $this->_deprecatedMethod('update()', 'Create your query with updateQuery() instead.'); + $this->_dirty(); + $this->_hydrate = false; - return parent::update($table); + return $this; + } + + /** + * Returns the current hydration mode. + * + * @return bool + */ + public function isHydrationEnabled(): bool + { + return $this->_hydrate; + } + + /** + * Trigger the beforeFind event on the query's repository object. + * + * Will not trigger more than once, and only for select queries. + * + * @return void + */ + public function triggerBeforeFind(): void + { + if (!$this->_beforeFindFired) { + $this->_beforeFindFired = true; + + $repository = $this->getRepository(); + $repository->dispatchEvent('Model.beforeFind', [ + $this, + new ArrayObject($this->_options), + !$this->isEagerLoaded(), + ]); + } } /** * @inheritDoc */ - public function set($key, $value = null, $types = []) + public function sql(?ValueBinder $binder = null): string + { + $this->triggerBeforeFind(); + + $this->_transformQuery(); + + return parent::sql($binder); + } + + /** + * Executes this query and returns an iterable containing the results. + * + * @return iterable + */ + protected function _execute(): iterable + { + $this->triggerBeforeFind(); + if ($this->_results !== null) { + return $this->_results; + } + + if ($this->bufferedResults) { + $results = parent::all(); + } else { + $results = $this->execute(); + } + $results = $this->getEagerLoader()->loadExternal($this, $results); + + return $this->resultSetFactory()->createResultSet($results, $this); + } + + /** + * Get result set factory. + * + * @return \Cake\ORM\ResultSetFactory + */ + public function resultSetFactory(): ResultSetFactory + { + return $this->resultSetFactory ??= new ResultSetFactory(); + } + + /** + * Applies some defaults to the query object before it is executed. + * + * Specifically add the FROM clause, adds default table fields if none are + * specified and applies the joins required to eager load associations defined + * using `contain` + * + * It also sets the default types for the columns in the select clause + * + * @see \Cake\Database\Query::execute() + * @return void + */ + protected function _transformQuery(): void + { + if (!$this->_dirty) { + return; + } + + $repository = $this->getRepository(); + + if (empty($this->_parts['from'])) { + $this->from([$repository->getAlias() => $repository->getTable()]); + } + $this->_addDefaultFields(); + $this->getEagerLoader()->attachAssociations($this, $repository, !$this->_hasFields); + $this->_addDefaultSelectTypes(); + } + + /** + * Inspects if there are any set fields for selecting, otherwise adds all + * the fields for the default table. + * + * @return void + */ + protected function _addDefaultFields(): void + { + $select = $this->clause('select'); + $this->_hasFields = true; + + $repository = $this->getRepository(); + + if (!count($select) || $this->_autoFields === true) { + $this->_hasFields = false; + $this->select($repository->getSchema()->columns()); + $select = $this->clause('select'); + } + + if ($this->aliasingEnabled) { + $select = $this->aliasFields($select, $repository->getAlias()); + } + $this->select($select, true); + } + + /** + * Sets the default types for converting the fields in the select clause + * + * @return void + */ + protected function _addDefaultSelectTypes(): void { - $this->_deprecatedMethod('set()'); + $typeMap = $this->getTypeMap()->getDefaults(); + $select = $this->clause('select'); + $types = []; - return parent::set($key, $value, $types); + foreach ($select as $alias => $value) { + if ($value instanceof TypedResultInterface) { + $types[$alias] = $value->getReturnType(); + continue; + } + if (isset($typeMap[$alias])) { + $types[$alias] = $typeMap[$alias]; + continue; + } + if (is_string($value) && isset($typeMap[$value])) { + $types[$alias] = $typeMap[$value]; + } + } + $this->getSelectTypeMap()->addDefaults($types); } /** - * Sets the connection role. + * {@inheritDoc} + * + * @param string $finder The finder method to use. + * @param mixed ...$args Arguments that match up to finder-specific parameters + * @return static Returns a modified query. + */ + public function find(string $finder, mixed ...$args): static + { + $table = $this->getRepository(); + + return $table->callFinder($finder, $this, ...$args); + } + + /** + * Disable auto adding table's alias to the fields of SELECT clause. * - * @param string $role Connection role ('read' or 'write') * @return $this */ - public function setConnectionRole(string $role) + public function disableAutoAliasing() { - assert($role === Connection::ROLE_READ || $role === Connection::ROLE_WRITE); - $this->connectionRole = $role; + $this->aliasingEnabled = false; return $this; } /** - * Sets the connection role to read. + * Marks a query as dirty, removing any preprocessed information + * from in memory caching such as previous results + * + * @return void + */ + protected function _dirty(): void + { + $this->_results = null; + $this->_resultsCount = null; + parent::_dirty(); + } + + /** + * @inheritDoc + */ + public function __debugInfo(): array + { + $eagerLoader = $this->getEagerLoader(); + + return parent::__debugInfo() + [ + 'hydrate' => $this->_hydrate, + 'formatters' => count($this->_formatters), + 'mapReducers' => count($this->_mapReduce), + 'contain' => $eagerLoader->getContain(), + 'matching' => $eagerLoader->getMatching(), + 'extraOptions' => $this->_options, + 'repository' => $this->_repository, + ]; + } + + /** + * Executes the query and converts the result set into JSON. + * + * Part of JsonSerializable interface. + * + * @return \Cake\Datasource\ResultSetInterface<(\Cake\Datasource\EntityInterface|mixed)> The data to convert to JSON. + */ + public function jsonSerialize(): ResultSetInterface + { + return $this->all(); + } + + /** + * Sets whether the ORM should automatically append fields. + * + * By default, calling select() will disable auto-fields. You can re-enable + * auto-fields with this method. * + * @param bool $value Set true to enable, false to disable. * @return $this */ - public function useReadRole() + public function enableAutoFields(bool $value = true) { - return $this->setConnectionRole(Connection::ROLE_READ); + $this->_autoFields = $value; + + return $this; } /** - * Sets the connection role to write. + * Disables automatically appending fields. * * @return $this */ - public function useWriteRole() + public function disableAutoFields() + { + $this->_autoFields = false; + + return $this; + } + + /** + * Gets whether the ORM should automatically append fields. + * + * By default, calling select() will disable auto-fields. You can re-enable + * auto-fields with enableAutoFields(). + * + * @return bool|null The current value. Returns null if neither enabled or disabled yet. + */ + public function isAutoFieldsEnabled(): ?bool { - return $this->setConnectionRole(Connection::ROLE_WRITE); + return $this->_autoFields; } } + +// phpcs:disable +class_exists(\Cake\ORM\Query::class); +// phpcs:enable diff --git a/app/vendor/cakephp/cakephp/src/ORM/Query/UpdateQuery.php b/app/vendor/cakephp/cakephp/src/ORM/Query/UpdateQuery.php index ff25a89da..bda5c14d4 100644 --- a/app/vendor/cakephp/cakephp/src/ORM/Query/UpdateQuery.php +++ b/app/vendor/cakephp/cakephp/src/ORM/Query/UpdateQuery.php @@ -16,252 +16,40 @@ */ namespace Cake\ORM\Query; +use Cake\Database\Query\UpdateQuery as DbUpdateQuery; use Cake\Database\ValueBinder; -use Cake\Datasource\ResultSetInterface; -use Cake\ORM\Query; +use Cake\ORM\Table; /** - * Update Query forward compatibility shim. + * @inheritDoc */ -class UpdateQuery extends Query +class UpdateQuery extends DbUpdateQuery { + use CommonQueryTrait; + /** - * Type of this query (select, insert, update, delete). + * Constructor * - * @var string + * @param \Cake\ORM\Table $table The table this query is starting on */ - protected $_type = 'update'; + public function __construct(Table $table) + { + parent::__construct($table->getConnection()); + + $this->setRepository($table); + $this->addDefaultTypes($table); + } /** * @inheritDoc */ public function sql(?ValueBinder $binder = null): string { - if ($this->_type === 'update' && empty($this->_parts['update'])) { + if (empty($this->_parts['update'])) { $repository = $this->getRepository(); $this->update($repository->getTable()); } return parent::sql($binder); } - - /** - * @inheritDoc - */ - public function delete(?string $table = null) - { - $this->_deprecatedMethod('delete()', 'Create your query with deleteQuery() instead.'); - - return parent::delete($table); - } - - /** - * @inheritDoc - */ - public function cache($key, $config = 'default') - { - $this->_deprecatedMethod('cache()', 'Use execute() instead.'); - - return parent::cache($key, $config); - } - - /** - * @inheritDoc - */ - public function all(): ResultSetInterface - { - $this->_deprecatedMethod('all()', 'Use execute() instead.'); - - return parent::all(); - } - - /** - * @inheritDoc - */ - public function select($fields = [], bool $overwrite = false) - { - $this->_deprecatedMethod('select()', 'Create your query with selectQuery() instead.'); - - return parent::select($fields, $overwrite); - } - - /** - * @inheritDoc - */ - public function distinct($on = [], $overwrite = false) - { - $this->_deprecatedMethod('distinct()'); - - return parent::distinct($on, $overwrite); - } - - /** - * @inheritDoc - */ - public function modifier($modifiers, $overwrite = false) - { - $this->_deprecatedMethod('modifier()'); - - return parent::modifier($modifiers, $overwrite); - } - - /** - * @inheritDoc - */ - public function group($fields, $overwrite = false) - { - $this->_deprecatedMethod('group()'); - - return parent::group($fields, $overwrite); - } - - /** - * @inheritDoc - */ - public function having($conditions = null, $types = [], $overwrite = false) - { - $this->_deprecatedMethod('having()'); - - return parent::having($conditions, $types, $overwrite); - } - - /** - * @inheritDoc - */ - public function andHaving($conditions, $types = []) - { - $this->_deprecatedMethod('andHaving()'); - - return parent::andHaving($conditions, $types); - } - - /** - * @inheritDoc - */ - public function page(int $num, ?int $limit = null) - { - $this->_deprecatedMethod('page()'); - - return parent::page($num, $limit); - } - - /** - * @inheritDoc - */ - public function offset($offset) - { - $this->_deprecatedMethod('offset()'); - - return parent::offset($offset); - } - - /** - * @inheritDoc - */ - public function union($query, $overwrite = false) - { - $this->_deprecatedMethod('union()'); - - return parent::union($query, $overwrite); - } - - /** - * @inheritDoc - */ - public function unionAll($query, $overwrite = false) - { - $this->_deprecatedMethod('union()'); - - return parent::unionAll($query, $overwrite); - } - - /** - * @inheritDoc - */ - public function insert(array $columns, array $types = []) - { - $this->_deprecatedMethod('insert()', 'Create your query with insertQuery() instead.'); - - return parent::insert($columns, $types); - } - - /** - * @inheritDoc - */ - public function into(string $table) - { - $this->_deprecatedMethod('into()', 'Use update() instead.'); - - return parent::into($table); - } - - /** - * @inheritDoc - */ - public function values($data) - { - $this->_deprecatedMethod('values()'); - - return parent::values($data); - } - - /** - * @inheritDoc - */ - public function from($tables = [], $overwrite = false) - { - $this->_deprecatedMethod('from()', 'Use update() instead.'); - - return parent::from($tables, $overwrite); - } - - /** - * @inheritDoc - */ - public function matching(string $assoc, ?callable $builder = null) - { - $this->_deprecatedMethod('matching()'); - - return parent::matching($assoc, $builder); - } - - /** - * @inheritDoc - */ - public function notMatching(string $assoc, ?callable $builder = null) - { - $this->_deprecatedMethod('notMatching()'); - - return parent::notMatching($assoc, $builder); - } - - /** - * @inheritDoc - */ - public function contain($associations, $override = false) - { - $this->_deprecatedMethod('contain()'); - - return parent::contain($associations, $override); - } - - /** - * @inheritDoc - */ - public function getContain(): array - { - $this->_deprecatedMethod('getContain()'); - - return parent::getContain(); - } - - /** - * @inheritDoc - */ - public function find(string $finder, array $options = []) - { - $this->_deprecatedMethod('find()'); - - return parent::find($finder, $options); - } } diff --git a/app/vendor/cakephp/cakephp/src/ORM/README.md b/app/vendor/cakephp/cakephp/src/ORM/README.md index 5af443ac1..a6b3c5134 100644 --- a/app/vendor/cakephp/cakephp/src/ORM/README.md +++ b/app/vendor/cakephp/cakephp/src/ORM/README.md @@ -83,7 +83,7 @@ supports 4 association types out of the box: * belongsToMany - E.g. An article belongsToMany tags. You define associations in your table's `initialize()` method. See the -[documentation](https://book.cakephp.org/4/en/orm/associations.html) for +[documentation](https://book.cakephp.org/5/en/orm/associations.html) for complete examples. ## Reading Data @@ -99,8 +99,8 @@ foreach ($articles->find() as $article) { } ``` -You can use the [query builder](https://book.cakephp.org/4/en/orm/query-builder.html) to create -complex queries, and a [variety of methods](https://book.cakephp.org/4/en/orm/retrieving-data-and-resultsets.html) +You can use the [query builder](https://book.cakephp.org/5/en/orm/query-builder.html) to create +complex queries, and a [variety of methods](https://book.cakephp.org/5/en/orm/retrieving-data-and-resultsets.html) to access your data. ## Saving Data @@ -134,7 +134,7 @@ $articles->save($article, [ ``` The above shows how you can easily marshal and save an entity and its -associations in a simple & powerful way. Consult the [ORM documentation](https://book.cakephp.org/4/en/orm/saving-data.html) +associations in a simple & powerful way. Consult the [ORM documentation](https://book.cakephp.org/5/en/orm/saving-data.html) for more in-depth examples. ## Deleting Data @@ -237,5 +237,5 @@ Configure::write('App.namespace', 'My\Log\SubNamespace'); ## Additional Documentation -Consult [the CakePHP ORM documentation](https://book.cakephp.org/4/en/orm.html) +Consult [the CakePHP ORM documentation](https://book.cakephp.org/5/en/orm.html) for more in-depth documentation. diff --git a/app/vendor/cakephp/cakephp/src/ORM/ResultSet.php b/app/vendor/cakephp/cakephp/src/ORM/ResultSet.php index 012c40025..103d913c0 100644 --- a/app/vendor/cakephp/cakephp/src/ORM/ResultSet.php +++ b/app/vendor/cakephp/cakephp/src/ORM/ResultSet.php @@ -17,12 +17,7 @@ namespace Cake\ORM; use Cake\Collection\Collection; -use Cake\Collection\CollectionTrait; -use Cake\Database\Exception\DatabaseException; -use Cake\Database\StatementInterface; -use Cake\Datasource\EntityInterface; use Cake\Datasource\ResultSetInterface; -use SplFixedArray; /** * Represents the results obtained after executing a query for a specific table @@ -30,558 +25,28 @@ * the query, casting each field to the correct type and executing the extra * queries required for eager loading external associations. * - * @template T of \Cake\Datasource\EntityInterface|array + * @template T * @implements \Cake\Datasource\ResultSetInterface */ -class ResultSet implements ResultSetInterface +class ResultSet extends Collection implements ResultSetInterface { - use CollectionTrait; - - /** - * Database statement holding the results - * - * @var \Cake\Database\StatementInterface - */ - protected $_statement; - - /** - * Points to the next record number that should be fetched - * - * @var int - */ - protected $_index = 0; - - /** - * Last record fetched from the statement - * - * @var \Cake\Datasource\EntityInterface|array - * @psalm-var T - */ - protected $_current; - - /** - * Default table instance - * - * @var \Cake\ORM\Table - */ - protected $_defaultTable; - - /** - * The default table alias - * - * @var string - */ - protected $_defaultAlias; - - /** - * List of associations that should be placed under the `_matchingData` - * result key. - * - * @var array - */ - protected $_matchingMap = []; - - /** - * List of associations that should be eager loaded. - * - * @var array - */ - protected $_containMap = []; - - /** - * Map of fields that are fetched from the statement with - * their type and the table they belong to - * - * @var array - */ - protected $_map = []; - - /** - * List of matching associations and the column keys to expect - * from each of them. - * - * @var array - */ - protected $_matchingMapColumns = []; - - /** - * Results that have been fetched or hydrated into the results. - * - * @var \SplFixedArray|array - */ - protected $_results = []; - - /** - * Whether to hydrate results into objects or not - * - * @var bool - */ - protected $_hydrate = true; - - /** - * Tracks value of $_autoFields property of $query passed to constructor. - * - * @var bool|null - */ - protected $_autoFields; - - /** - * The fully namespaced name of the class to use for hydrating results - * - * @var string - */ - protected $_entityClass; - - /** - * Whether to buffer results fetched from the statement - * - * @var bool - */ - protected $_useBuffering = true; - - /** - * Holds the count of records in this result set - * - * @var int - */ - protected $_count; - - /** - * The Database driver object. - * - * Cached in a property to avoid multiple calls to the same function. - * - * @var \Cake\Database\DriverInterface - */ - protected $_driver; - - /** - * Constructor - * - * @param \Cake\ORM\Query $query Query from where results come - * @param \Cake\Database\StatementInterface $statement The statement to fetch from - */ - public function __construct(Query $query, StatementInterface $statement) - { - $repository = $query->getRepository(); - $this->_statement = $statement; - $this->_driver = $query->getConnection()->getDriver($query->getConnectionRole()); - $this->_defaultTable = $repository; - $this->_calculateAssociationMap($query); - $this->_hydrate = $query->isHydrationEnabled(); - $this->_entityClass = $repository->getEntityClass(); - $this->_useBuffering = $query->isBufferedResultsEnabled(); - $this->_defaultAlias = $this->_defaultTable->getAlias(); - $this->_calculateColumnMap($query); - $this->_autoFields = $query->isAutoFieldsEnabled(); - - if ($this->_useBuffering) { - $count = $this->count(); - $this->_results = new SplFixedArray($count); - } - } - - /** - * Returns the current record in the result iterator - * - * Part of Iterator interface. - * - * @return \Cake\Datasource\EntityInterface|array - * @psalm-return T - */ - #[\ReturnTypeWillChange] - public function current() - { - return $this->_current; - } - - /** - * Returns the key of the current record in the iterator - * - * Part of Iterator interface. - * - * @return int - */ - public function key(): int - { - return $this->_index; - } - - /** - * Advances the iterator pointer to the next record - * - * Part of Iterator interface. - * - * @return void - */ - public function next(): void - { - $this->_index++; - } - - /** - * Rewinds a ResultSet. - * - * Part of Iterator interface. - * - * @throws \Cake\Database\Exception\DatabaseException - * @return void - */ - public function rewind(): void - { - if ($this->_index === 0) { - return; - } - - if (!$this->_useBuffering) { - $msg = 'You cannot rewind an un-buffered ResultSet. ' - . 'Use Query::bufferResults() to get a buffered ResultSet.'; - throw new DatabaseException($msg); - } - - $this->_index = 0; - } - - /** - * Whether there are more results to be fetched from the iterator - * - * Part of Iterator interface. - * - * @return bool - */ - public function valid(): bool - { - if ($this->_useBuffering) { - $valid = $this->_index < $this->_count; - if ($valid && $this->_results[$this->_index] !== null) { - $this->_current = $this->_results[$this->_index]; - - return true; - } - if (!$valid) { - return $valid; - } - } - - $this->_current = $this->_fetchResult(); - $valid = $this->_current !== false; - - if ($valid && $this->_useBuffering) { - $this->_results[$this->_index] = $this->_current; - } - if (!$valid && $this->_statement !== null) { - $this->_statement->closeCursor(); - } - - return $valid; - } - - /** - * Get the first record from a result set. - * - * This method will also close the underlying statement cursor. - * - * @return \Cake\Datasource\EntityInterface|array|null - * @psalm-return T|null - */ - public function first() - { - foreach ($this as $result) { - if ($this->_statement !== null && !$this->_useBuffering) { - $this->_statement->closeCursor(); - } - - return $result; - } - - return null; - } - - /** - * Serializes a resultset. - * - * Part of Serializable interface. - * - * @return string Serialized object - */ - public function serialize(): string - { - return serialize($this->__serialize()); - } - - /** - * Serializes a resultset. - * - * @return array - */ - public function __serialize(): array - { - if (!$this->_useBuffering) { - $msg = 'You cannot serialize an un-buffered ResultSet. ' - . 'Use Query::bufferResults() to get a buffered ResultSet.'; - throw new DatabaseException($msg); - } - - while ($this->valid()) { - $this->next(); - } - - if ($this->_results instanceof SplFixedArray) { - return $this->_results->toArray(); - } - - return $this->_results; - } - - /** - * Unserializes a resultset. - * - * Part of Serializable interface. - * - * @param string $serialized Serialized object - * @return void - */ - public function unserialize($serialized) - { - $this->__unserialize((array)(unserialize($serialized) ?: [])); - } - - /** - * Unserializes a resultset. - * - * @param array $data Data array. - * @return void - */ - public function __unserialize(array $data): void - { - $this->_results = SplFixedArray::fromArray($data); - $this->_useBuffering = true; - $this->_count = $this->_results->count(); - } - - /** - * Gives the number of rows in the result set. - * - * Part of the Countable interface. - * - * @return int - */ - public function count(): int - { - if ($this->_count !== null) { - return $this->_count; - } - if ($this->_statement !== null) { - return $this->_count = $this->_statement->rowCount(); - } - - if ($this->_results instanceof SplFixedArray) { - $this->_count = $this->_results->count(); - } else { - $this->_count = count($this->_results); - } - - return $this->_count; - } - - /** - * Calculates the list of associations that should get eager loaded - * when fetching each record - * - * @param \Cake\ORM\Query $query The query from where to derive the associations - * @return void - */ - protected function _calculateAssociationMap(Query $query): void - { - $map = $query->getEagerLoader()->associationsMap($this->_defaultTable); - $this->_matchingMap = (new Collection($map)) - ->match(['matching' => true]) - ->indexBy('alias') - ->toArray(); - - $this->_containMap = (new Collection(array_reverse($map))) - ->match(['matching' => false]) - ->indexBy('nestKey') - ->toArray(); - } - - /** - * Creates a map of row keys out of the query select clause that can be - * used to hydrate nested result sets more quickly. - * - * @param \Cake\ORM\Query $query The query from where to derive the column map - * @return void - */ - protected function _calculateColumnMap(Query $query): void - { - $map = []; - foreach ($query->clause('select') as $key => $field) { - $key = trim($key, '"`[]'); - - if (strpos($key, '__') <= 0) { - $map[$this->_defaultAlias][$key] = $key; - continue; - } - - $parts = explode('__', $key, 2); - $map[$parts[0]][$key] = $parts[1]; - } - - foreach ($this->_matchingMap as $alias => $assoc) { - if (!isset($map[$alias])) { - continue; - } - $this->_matchingMapColumns[$alias] = $map[$alias]; - unset($map[$alias]); - } - - $this->_map = $map; - } - - /** - * Helper function to fetch the next result from the statement or - * seeded results. - * - * @return mixed - */ - protected function _fetchResult() - { - if ($this->_statement === null) { - return false; - } - - $row = $this->_statement->fetch('assoc'); - if ($row === false) { - return $row; - } - - return $this->_groupResult($row); - } - - /** - * Correctly nests results keys including those coming from associations - * - * @param array $row Array containing columns and values or false if there is no results - * @return \Cake\Datasource\EntityInterface|array Results - */ - protected function _groupResult(array $row) - { - $defaultAlias = $this->_defaultAlias; - $results = $presentAliases = []; - $options = [ - 'useSetters' => false, - 'markClean' => true, - 'markNew' => false, - 'guard' => false, - ]; - - foreach ($this->_matchingMapColumns as $alias => $keys) { - $matching = $this->_matchingMap[$alias]; - $results['_matchingData'][$alias] = array_combine( - $keys, - array_intersect_key($row, $keys) - ); - if ($this->_hydrate) { - /** @var \Cake\ORM\Table $table */ - $table = $matching['instance']; - $options['source'] = $table->getRegistryAlias(); - /** @var \Cake\Datasource\EntityInterface $entity */ - $entity = new $matching['entityClass']($results['_matchingData'][$alias], $options); - $results['_matchingData'][$alias] = $entity; - } - } - - foreach ($this->_map as $table => $keys) { - $results[$table] = array_combine($keys, array_intersect_key($row, $keys)); - $presentAliases[$table] = true; - } - - // If the default table is not in the results, set - // it to an empty array so that any contained - // associations hydrate correctly. - $results[$defaultAlias] = $results[$defaultAlias] ?? []; - - unset($presentAliases[$defaultAlias]); - - foreach ($this->_containMap as $assoc) { - $alias = $assoc['nestKey']; - - if ($assoc['canBeJoined'] && empty($this->_map[$alias])) { - continue; - } - - /** @var \Cake\ORM\Association $instance */ - $instance = $assoc['instance']; - - if (!$assoc['canBeJoined'] && !isset($row[$alias])) { - $results = $instance->defaultRowValue($results, $assoc['canBeJoined']); - continue; - } - - if (!$assoc['canBeJoined']) { - $results[$alias] = $row[$alias]; - } - - $target = $instance->getTarget(); - $options['source'] = $target->getRegistryAlias(); - unset($presentAliases[$alias]); - - if ($assoc['canBeJoined'] && $this->_autoFields !== false) { - $hasData = false; - foreach ($results[$alias] as $v) { - if ($v !== null && $v !== []) { - $hasData = true; - break; - } - } - - if (!$hasData) { - $results[$alias] = null; - } - } - - if ($this->_hydrate && $results[$alias] !== null && $assoc['canBeJoined']) { - $entity = new $assoc['entityClass']($results[$alias], $options); - $results[$alias] = $entity; - } - - $results = $instance->transformRow($results, $alias, $assoc['canBeJoined'], $assoc['targetProperty']); - } - - foreach ($presentAliases as $alias => $present) { - if (!isset($results[$alias])) { - continue; - } - $results[$defaultAlias][$alias] = $results[$alias]; - } - - if (isset($results['_matchingData'])) { - $results[$defaultAlias]['_matchingData'] = $results['_matchingData']; - } - - $options['source'] = $this->_defaultTable->getRegistryAlias(); - if (isset($results[$defaultAlias])) { - $results = $results[$defaultAlias]; - } - if ($this->_hydrate && !($results instanceof EntityInterface)) { - $results = new $this->_entityClass($results, $options); - } - - return $results; - } - /** * Returns an array that can be used to describe the internal state of this * object. * * @return array */ - public function __debugInfo() + public function __debugInfo(): array { - $currentIndex = $this->_index; - // toArray() adjusts the current index, so we have to reset it + $key = $this->key(); $items = $this->toArray(); - $this->_index = $currentIndex; + + $this->rewind(); + // Move the internal pointer to the previous position otherwise it creates problems with Xdebug + // https://github.com/cakephp/cakephp/issues/18234 + while ($this->key() !== $key) { + $this->next(); + } return [ 'items' => $items, diff --git a/app/vendor/cakephp/cakephp/src/ORM/ResultSetFactory.php b/app/vendor/cakephp/cakephp/src/ORM/ResultSetFactory.php new file mode 100644 index 000000000..072510db4 --- /dev/null +++ b/app/vendor/cakephp/cakephp/src/ORM/ResultSetFactory.php @@ -0,0 +1,275 @@ + + */ + protected string $resultSetClass = ResultSet::class; + + /** + * Create a result set instance. + * + * @param iterable $results Results. + * @param \Cake\ORM\Query\SelectQuery|null $query Query from where results came. + * @return \Cake\Datasource\ResultSetInterface + */ + public function createResultSet(iterable $results, ?SelectQuery $query = null): ResultSetInterface + { + if ($query) { + $data = $this->collectData($query); + + if (is_array($results)) { + foreach ($results as $i => $row) { + $results[$i] = $this->groupResult($row, $data); + } + + $results = SplFixedArray::fromArray($results); + } else { + $results = (new Collection($results)) + ->map(function ($row) use ($data) { + return $this->groupResult($row, $data); + }); + } + } + + return new $this->resultSetClass($results); + } + + /** + * Get repository and it's associations data for nesting results key and + * entity hydration. + * + * @param \Cake\ORM\Query\SelectQuery $query The query from where to derive the data. + * @return array + */ + protected function collectData(SelectQuery $query): array + { + $primaryTable = $query->getRepository(); + $data = [ + 'primaryAlias' => $primaryTable->getAlias(), + 'registryAlias' => $primaryTable->getRegistryAlias(), + 'entityClass' => $primaryTable->getEntityClass(), + 'hydrate' => $query->isHydrationEnabled(), + 'autoFields' => $query->isAutoFieldsEnabled(), + 'matchingColumns' => [], + ]; + + $assocMap = $query->getEagerLoader()->associationsMap($primaryTable); + $data['matchingAssoc'] = (new Collection($assocMap)) + ->match(['matching' => true]) + ->indexBy('alias') + ->toArray(); + + $data['containAssoc'] = (new Collection(array_reverse($assocMap))) + ->match(['matching' => false]) + ->indexBy('nestKey') + ->toArray(); + + $fields = []; + foreach ($query->clause('select') as $key => $field) { + $key = trim((string)$key, '"`[]'); + + if (strpos($key, '__') <= 0) { + $fields[$data['primaryAlias']][$key] = $key; + continue; + } + + $parts = explode('__', $key, 2); + $fields[$parts[0]][$key] = $parts[1]; + } + + foreach ($data['matchingAssoc'] as $alias => $assoc) { + if (!isset($fields[$alias])) { + continue; + } + $data['matchingColumns'][$alias] = $fields[$alias]; + unset($fields[$alias]); + } + + $data['fields'] = $fields; + + return $data; + } + + /** + * Correctly nests results keys including those coming from associations. + * + * Hydrate row array into entity if hydration is enabled. + * + * @param array $row Array containing columns and values. + * @param array $data Array containing table and query metadata + * @return \Cake\Datasource\EntityInterface|array + */ + protected function groupResult(array $row, array $data): EntityInterface|array + { + $results = []; + $presentAliases = []; + $options = [ + 'useSetters' => false, + 'markClean' => true, + 'markNew' => false, + 'guard' => false, + ]; + + foreach ($data['matchingColumns'] as $alias => $keys) { + $matching = $data['matchingAssoc'][$alias]; + $results['_matchingData'][$alias] = array_combine( + $keys, + array_intersect_key($row, $keys), + ); + if ($data['hydrate']) { + $table = $matching['instance']; + assert($table instanceof Table || $table instanceof Association); + + $options['source'] = $table->getRegistryAlias(); + $entity = new $matching['entityClass']($results['_matchingData'][$alias], $options); + assert($entity instanceof EntityInterface); + + $results['_matchingData'][$alias] = $entity; + } + } + + foreach ($data['fields'] as $table => $keys) { + $results[$table] = array_combine($keys, array_intersect_key($row, $keys)); + $presentAliases[$table] = true; + } + + // If the default table is not in the results, set + // it to an empty array so that any contained + // associations hydrate correctly. + $results[$data['primaryAlias']] ??= []; + + unset($presentAliases[$data['primaryAlias']]); + + foreach ($data['containAssoc'] as $assoc) { + $alias = $assoc['nestKey']; + /** @var bool $canBeJoined */ + $canBeJoined = $assoc['canBeJoined']; + if ($canBeJoined && empty($data['fields'][$alias])) { + continue; + } + + $instance = $assoc['instance']; + assert($instance instanceof Association); + + if (!$canBeJoined && !isset($row[$alias])) { + $results = $instance->defaultRowValue($results, $canBeJoined); + continue; + } + + if (!$canBeJoined) { + $results[$alias] = $row[$alias]; + } + + $target = $instance->getTarget(); + $options['source'] = $target->getRegistryAlias(); + unset($presentAliases[$alias]); + + if ($assoc['canBeJoined'] && $data['autoFields'] !== false) { + $hasData = false; + foreach ($results[$alias] as $v) { + if ($v !== null && $v !== []) { + $hasData = true; + break; + } + } + + if (!$hasData) { + $results[$alias] = null; + } + } + + if ($data['hydrate'] && $results[$alias] !== null && $assoc['canBeJoined']) { + $entity = new $assoc['entityClass']($results[$alias], $options); + $results[$alias] = $entity; + } + + $results = $instance->transformRow($results, $alias, $assoc['canBeJoined'], $assoc['targetProperty']); + } + + foreach ($presentAliases as $alias => $present) { + if (!isset($results[$alias])) { + continue; + } + $results[$data['primaryAlias']][$alias] = $results[$alias]; + } + + if (isset($results['_matchingData'])) { + $results[$data['primaryAlias']]['_matchingData'] = $results['_matchingData']; + } + + $options['source'] = $data['registryAlias']; + if (isset($results[$data['primaryAlias']])) { + $results = $results[$data['primaryAlias']]; + } + if ($data['hydrate'] && !($results instanceof EntityInterface)) { + /** @var \Cake\Datasource\EntityInterface */ + return new $data['entityClass']($results, $options); + } + + return $results; + } + + /** + * Set the ResultSet class to use. + * + * @param class-string<\Cake\Datasource\ResultSetInterface> $resultSetClass Class name. + * @return $this + */ + public function setResultSetClass(string $resultSetClass) + { + if (!is_a($resultSetClass, ResultSetInterface::class, true)) { + throw new InvalidArgumentException(sprintf( + 'Invalid ResultSet class `%s`. It must implement `%s`', + $resultSetClass, + ResultSetInterface::class, + )); + } + + $this->resultSetClass = $resultSetClass; + + return $this; + } + + /** + * Get the ResultSet class to use. + * + * @return class-string<\Cake\Datasource\ResultSetInterface> + */ + public function getResultSetClass(): string + { + return $this->resultSetClass; + } +} diff --git a/app/vendor/cakephp/cakephp/src/ORM/Rule/ExistsIn.php b/app/vendor/cakephp/cakephp/src/ORM/Rule/ExistsIn.php index 912c7199e..a19c10302 100644 --- a/app/vendor/cakephp/cakephp/src/ORM/Rule/ExistsIn.php +++ b/app/vendor/cakephp/cakephp/src/ORM/Rule/ExistsIn.php @@ -16,10 +16,10 @@ */ namespace Cake\ORM\Rule; +use Cake\Database\Exception\DatabaseException; use Cake\Datasource\EntityInterface; use Cake\ORM\Association; use Cake\ORM\Table; -use RuntimeException; /** * Checks that the value provided in a field exists as the primary key of another @@ -32,21 +32,21 @@ class ExistsIn * * @var array */ - protected $_fields; + protected array $_fields; /** * The repository where the field will be looked for * * @var \Cake\ORM\Table|\Cake\ORM\Association|string */ - protected $_repository; + protected Table|Association|string $_repository; /** * Options for the constructor * * @var array */ - protected $_options = []; + protected array $_options = []; /** * Constructor. @@ -61,7 +61,7 @@ class ExistsIn * Options 'allowNullableNulls' will make the rule pass if given foreign keys are set to `null`. * Notice: allowNullableNulls cannot pass by database columns set to `NOT NULL`. */ - public function __construct($fields, $repository, array $options = []) + public function __construct(array|string $fields, Table|Association|string $repository, array $options = []) { $options += ['allowNullableNulls' => false]; $this->_options = $options; @@ -76,26 +76,30 @@ public function __construct($fields, $repository, array $options = []) * @param \Cake\Datasource\EntityInterface $entity The entity from where to extract the fields * @param array $options Options passed to the check, * where the `repository` key is required. - * @throws \RuntimeException When the rule refers to an undefined association. + * @throws \Cake\Database\Exception\DatabaseException When the rule refers to an undefined association. * @return bool */ public function __invoke(EntityInterface $entity, array $options): bool { if (is_string($this->_repository)) { - if (!$options['repository']->hasAssociation($this->_repository)) { - throw new RuntimeException(sprintf( - "ExistsIn rule for '%s' is invalid. '%s' is not associated with '%s'.", + /** @var \Cake\ORM\Table $table */ + $table = $options['repository']; + + if (!$table->hasAssociation($this->_repository)) { + throw new DatabaseException(sprintf( + 'ExistsIn rule for `%s` is invalid. `%s` is not associated with `%s`.', implode(', ', $this->_fields), $this->_repository, - get_class($options['repository']) + $options['repository']::class, )); } - $repository = $options['repository']->getAssociation($this->_repository); + $repository = $table->getAssociation($this->_repository); $this->_repository = $repository; } $fields = $this->_fields; - $source = $target = $this->_repository; + $source = $this->_repository; + $target = $this->_repository; if ($target instanceof Association) { $bindingKey = (array)$target->getBindingKey(); $realTarget = $target->getTarget(); @@ -124,6 +128,7 @@ public function __invoke(EntityInterface $entity, array $options): bool } if ($this->_options['allowNullableNulls']) { + /** @var \Cake\ORM\Table $source */ $schema = $source->getSchema(); foreach ($fields as $i => $field) { if ($schema->getColumn($field) && $schema->isNullable($field) && $entity->get($field) === null) { @@ -133,14 +138,12 @@ public function __invoke(EntityInterface $entity, array $options): bool } $primary = array_map( - function ($key) use ($target) { - return $target->aliasField($key) . ' IS'; - }, - $bindingKey + fn($key) => $target->aliasField($key) . ' IS', + $bindingKey, ); $conditions = array_combine( $primary, - $entity->extract($fields) + $entity->extract($fields), ); return $target->exists($conditions); diff --git a/app/vendor/cakephp/cakephp/src/ORM/Rule/IsUnique.php b/app/vendor/cakephp/cakephp/src/ORM/Rule/IsUnique.php index a86bfe3e2..5c3d4ad42 100644 --- a/app/vendor/cakephp/cakephp/src/ORM/Rule/IsUnique.php +++ b/app/vendor/cakephp/cakephp/src/ORM/Rule/IsUnique.php @@ -29,15 +29,15 @@ class IsUnique * * @var array */ - protected $_fields; + protected array $_fields; /** * The unique check options * * @var array */ - protected $_options = [ - 'allowMultipleNulls' => false, + protected array $_options = [ + 'allowMultipleNulls' => true, ]; /** @@ -45,7 +45,7 @@ class IsUnique * * ### Options * - * - `allowMultipleNulls` Allows any field to have multiple null values. Defaults to false. + * - `allowMultipleNulls` Allows any field to have multiple null values. Defaults to true. * * @param array $fields The list of fields to check uniqueness for * @param array $options The options for unique checks. @@ -75,17 +75,20 @@ public function __invoke(EntityInterface $entity, array $options): bool return true; } - $alias = $options['repository']->getAlias(); + /** @var \Cake\ORM\Table $repository */ + $repository = $options['repository']; + + $alias = $repository->getAlias(); $conditions = $this->_alias($alias, $fields); if ($entity->isNew() === false) { - $keys = (array)$options['repository']->getPrimaryKey(); + $keys = (array)$repository->getPrimaryKey(); $keys = $this->_alias($alias, $entity->extract($keys)); if (Hash::filter($keys)) { $conditions['NOT'] = $keys; } } - return !$options['repository']->exists($conditions); + return !$repository->exists($conditions); } /** @@ -99,7 +102,7 @@ protected function _alias(string $alias, array $conditions): array { $aliased = []; foreach ($conditions as $key => $value) { - $aliased["$alias.$key IS"] = $value; + $aliased["{$alias}.{$key} IS"] = $value; } return $aliased; diff --git a/app/vendor/cakephp/cakephp/src/ORM/Rule/LinkConstraint.php b/app/vendor/cakephp/cakephp/src/ORM/Rule/LinkConstraint.php index fe2b1cc76..1a512ab3e 100644 --- a/app/vendor/cakephp/cakephp/src/ORM/Rule/LinkConstraint.php +++ b/app/vendor/cakephp/cakephp/src/ORM/Rule/LinkConstraint.php @@ -16,10 +16,11 @@ */ namespace Cake\ORM\Rule; +use Cake\Database\Exception\DatabaseException; use Cake\Datasource\EntityInterface; use Cake\ORM\Association; use Cake\ORM\Table; -use function Cake\Core\getTypeName; +use InvalidArgumentException; /** * Checks whether links to a given association exist / do not exist. @@ -45,14 +46,14 @@ class LinkConstraint * * @var \Cake\ORM\Association|string */ - protected $_association; + protected Association|string $_association; /** * The link status that is required to be present in order for the check to succeed. * * @var string */ - protected $_requiredLinkState; + protected string $_requiredLinkState; /** * Constructor. @@ -61,21 +62,11 @@ class LinkConstraint * @param string $requiredLinkStatus The link status that is required to be present in order for the check to * succeed. */ - public function __construct($association, string $requiredLinkStatus) + public function __construct(Association|string $association, string $requiredLinkStatus) { - if ( - !is_string($association) && - !($association instanceof Association) - ) { - throw new \InvalidArgumentException(sprintf( - 'Argument 1 is expected to be of type `\Cake\ORM\Association|string`, `%s` given.', - getTypeName($association) - )); - } - if (!in_array($requiredLinkStatus, [static::STATUS_LINKED, static::STATUS_NOT_LINKED], true)) { - throw new \InvalidArgumentException( - 'Argument 2 is expected to match one of the `\Cake\ORM\Rule\LinkConstraint::STATUS_*` constants.' + throw new InvalidArgumentException( + 'Argument 2 is expected to match one of the `\Cake\ORM\Rule\LinkConstraint::STATUS_*` constants.', ); } @@ -96,8 +87,8 @@ public function __invoke(EntityInterface $entity, array $options): bool { $table = $options['repository'] ?? null; if (!($table instanceof Table)) { - throw new \InvalidArgumentException( - 'Argument 2 is expected to have a `repository` key that holds an instance of `\Cake\ORM\Table`.' + throw new InvalidArgumentException( + 'Argument 2 is expected to have a `repository` key that holds an instance of `\Cake\ORM\Table`.', ); } @@ -143,17 +134,17 @@ protected function _aliasFields(array $fields, Table $source): array /** * Build conditions. * - * @param array $fields The condition fields. + * @param array $fields The condition fields. * @param array $values The condition values. - * @return array A conditions array combined from the passed fields and values. + * @return array A conditions array combined from the passed fields and values. */ protected function _buildConditions(array $fields, array $values): array { if (count($fields) !== count($values)) { - throw new \InvalidArgumentException(sprintf( + throw new InvalidArgumentException(sprintf( 'The number of fields is expected to match the number of values, got %d field(s) and %d value(s).', count($fields), - count($values) + count($values), )); } @@ -171,21 +162,22 @@ protected function _countLinks(Association $association, EntityInterface $entity { $source = $association->getSource(); + /** @var array $primaryKey */ $primaryKey = (array)$source->getPrimaryKey(); if (!$entity->has($primaryKey)) { - throw new \RuntimeException(sprintf( + throw new DatabaseException(sprintf( 'LinkConstraint rule on `%s` requires all primary key values for building the counting ' . 'conditions, expected values for `(%s)`, got `(%s)`.', $source->getAlias(), implode(', ', $primaryKey), - implode(', ', $entity->extract($primaryKey)) + implode(', ', $entity->extract($primaryKey)), )); } $aliasedPrimaryKey = $this->_aliasFields($primaryKey, $source); $conditions = $this->_buildConditions( $aliasedPrimaryKey, - $entity->extract($primaryKey) + $entity->extract($primaryKey), ); return $source diff --git a/app/vendor/cakephp/cakephp/src/ORM/Rule/ValidCount.php b/app/vendor/cakephp/cakephp/src/ORM/Rule/ValidCount.php index 89d18d033..06722848a 100644 --- a/app/vendor/cakephp/cakephp/src/ORM/Rule/ValidCount.php +++ b/app/vendor/cakephp/cakephp/src/ORM/Rule/ValidCount.php @@ -30,7 +30,7 @@ class ValidCount * * @var string */ - protected $_field; + protected string $_field; /** * Constructor. diff --git a/app/vendor/cakephp/cakephp/src/ORM/RulesChecker.php b/app/vendor/cakephp/cakephp/src/ORM/RulesChecker.php index dd6a8ee4e..7fef06357 100644 --- a/app/vendor/cakephp/cakephp/src/ORM/RulesChecker.php +++ b/app/vendor/cakephp/cakephp/src/ORM/RulesChecker.php @@ -23,11 +23,10 @@ use Cake\ORM\Rule\LinkConstraint; use Cake\ORM\Rule\ValidCount; use Cake\Utility\Inflector; -use function Cake\Core\getTypeName; use function Cake\I18n\__d; /** - * ORM flavoured rules checker. + * ORM flavored rules checker. * * Adds ORM related features to the RulesChecker class. * @@ -54,7 +53,7 @@ class RulesChecker extends BaseRulesChecker * also be an array of options. When an array, the 'message' key can be used to provide a message. * @return \Cake\Datasource\RuleInvoker */ - public function isUnique(array $fields, $message = null): RuleInvoker + public function isUnique(array $fields, array|string|null $message = null): RuleInvoker { $options = is_array($message) ? $message : ['message' => $message]; $message = $options['message'] ?? null; @@ -98,8 +97,11 @@ public function isUnique(array $fields, $message = null): RuleInvoker * also be an array of options. When an array, the 'message' key can be used to provide a message. * @return \Cake\Datasource\RuleInvoker */ - public function existsIn($field, $table, $message = null): RuleInvoker - { + public function existsIn( + array|string $field, + Table|Association|string $table, + array|string|null $message = null, + ): RuleInvoker { $options = []; if (is_array($message)) { $options = $message + ['message' => null]; @@ -139,14 +141,17 @@ public function existsIn($field, $table, $message = null): RuleInvoker * @return \Cake\Datasource\RuleInvoker * @since 4.0.0 */ - public function isLinkedTo($association, ?string $field = null, ?string $message = null): RuleInvoker - { + public function isLinkedTo( + Association|string $association, + ?string $field = null, + ?string $message = null, + ): RuleInvoker { return $this->_addLinkConstraintRule( $association, $field, $message, LinkConstraint::STATUS_LINKED, - '_isLinkedTo' + '_isLinkedTo', ); } @@ -169,14 +174,17 @@ public function isLinkedTo($association, ?string $field = null, ?string $message * @return \Cake\Datasource\RuleInvoker * @since 4.0.0 */ - public function isNotLinkedTo($association, ?string $field = null, ?string $message = null): RuleInvoker - { + public function isNotLinkedTo( + Association|string $association, + ?string $field = null, + ?string $message = null, + ): RuleInvoker { return $this->_addLinkConstraintRule( $association, $field, $message, LinkConstraint::STATUS_NOT_LINKED, - '_isNotLinkedTo' + '_isNotLinkedTo', ); } @@ -198,19 +206,16 @@ public function isNotLinkedTo($association, ?string $field = null, ?string $mess * @see \Cake\ORM\Rule\LinkConstraint::STATUS_NOT_LINKED */ protected function _addLinkConstraintRule( - $association, + Association|string $association, ?string $errorField, ?string $message, string $linkStatus, - string $ruleName + string $ruleName, ): RuleInvoker { if ($association instanceof Association) { $associationAlias = $association->getName(); - - if ($errorField === null) { - $errorField = $association->getProperty(); - } - } elseif (is_string($association)) { + $errorField ??= $association->getProperty(); + } else { $associationAlias = $association; if ($errorField === null) { @@ -222,11 +227,6 @@ protected function _addLinkConstraintRule( $errorField = Inflector::underscore($association); } } - } else { - throw new \InvalidArgumentException(sprintf( - 'Argument 1 is expected to be of type `\Cake\ORM\Association|string`, `%s` given.', - getTypeName($association) - )); } if (!$message) { @@ -234,19 +234,19 @@ protected function _addLinkConstraintRule( $message = __d( 'cake', 'Cannot modify row: a constraint for the `{0}` association fails.', - $associationAlias + $associationAlias, ); } else { $message = sprintf( 'Cannot modify row: a constraint for the `%s` association fails.', - $associationAlias + $associationAlias, ); } } $rule = new LinkConstraint( $association, - $linkStatus + $linkStatus, ); return $this->_addError($rule, $ruleName, compact('errorField', 'message')); @@ -265,7 +265,7 @@ public function validCount( string $field, int $count = 0, string $operator = '>', - ?string $message = null + ?string $message = null, ): RuleInvoker { if (!$message) { if ($this->_useI18n) { @@ -280,7 +280,7 @@ public function validCount( return $this->_addError( new ValidCount($field), '_validCount', - compact('count', 'operator', 'errorField', 'message') + compact('count', 'operator', 'errorField', 'message'), ); } } diff --git a/app/vendor/cakephp/cakephp/src/ORM/SaveOptionsBuilder.php b/app/vendor/cakephp/cakephp/src/ORM/SaveOptionsBuilder.php deleted file mode 100644 index ad81f9055..000000000 --- a/app/vendor/cakephp/cakephp/src/ORM/SaveOptionsBuilder.php +++ /dev/null @@ -1,227 +0,0 @@ - - */ - protected $_options = []; - - /** - * Table object. - * - * @var \Cake\ORM\Table - */ - protected $_table; - - /** - * Constructor. - * - * @param \Cake\ORM\Table $table A table instance. - * @param array $options Options to parse when instantiating. - */ - public function __construct(Table $table, array $options = []) - { - $this->_table = $table; - $this->parseArrayOptions($options); - - parent::__construct(); - } - - /** - * Takes an options array and populates the option object with the data. - * - * This can be used to turn an options array into the object. - * - * @throws \InvalidArgumentException If a given option key does not exist. - * @param array $array Options array. - * @return $this - */ - public function parseArrayOptions(array $array) - { - foreach ($array as $key => $value) { - $this->{$key}($value); - } - - return $this; - } - - /** - * Set associated options. - * - * @param array|string $associated String or array of associations. - * @return $this - */ - public function associated($associated) - { - $associated = $this->_normalizeAssociations($associated); - $this->_associated($this->_table, $associated); - $this->_options['associated'] = $associated; - - return $this; - } - - /** - * Checks that the associations exists recursively. - * - * @param \Cake\ORM\Table $table Table object. - * @param array $associations An associations array. - * @return void - */ - protected function _associated(Table $table, array $associations): void - { - foreach ($associations as $key => $associated) { - if (is_int($key)) { - $this->_checkAssociation($table, $associated); - continue; - } - $this->_checkAssociation($table, $key); - if (isset($associated['associated'])) { - $this->_associated($table->getAssociation($key)->getTarget(), $associated['associated']); - continue; - } - } - } - - /** - * Checks if an association exists. - * - * @throws \RuntimeException If no such association exists for the given table. - * @param \Cake\ORM\Table $table Table object. - * @param string $association Association name. - * @return void - */ - protected function _checkAssociation(Table $table, string $association): void - { - if (!$table->associations()->has($association)) { - throw new RuntimeException(sprintf( - 'Table `%s` is not associated with `%s`', - get_class($table), - $association - )); - } - } - - /** - * Set the guard option. - * - * @param bool $guard Guard the properties or not. - * @return $this - */ - public function guard(bool $guard) - { - $this->_options['guard'] = $guard; - - return $this; - } - - /** - * Set the validation rule set to use. - * - * @param string $validate Name of the validation rule set to use. - * @return $this - */ - public function validate(string $validate) - { - $this->_table->getValidator($validate); - $this->_options['validate'] = $validate; - - return $this; - } - - /** - * Set check existing option. - * - * @param bool $checkExisting Guard the properties or not. - * @return $this - */ - public function checkExisting(bool $checkExisting) - { - $this->_options['checkExisting'] = $checkExisting; - - return $this; - } - - /** - * Option to check the rules. - * - * @param bool $checkRules Check the rules or not. - * @return $this - */ - public function checkRules(bool $checkRules) - { - $this->_options['checkRules'] = $checkRules; - - return $this; - } - - /** - * Sets the atomic option. - * - * @param bool $atomic Atomic or not. - * @return $this - */ - public function atomic(bool $atomic) - { - $this->_options['atomic'] = $atomic; - - return $this; - } - - /** - * @return array - */ - public function toArray(): array - { - return $this->_options; - } - - /** - * Setting custom options. - * - * @param string $option Option key. - * @param mixed $value Option value. - * @return $this - */ - public function set(string $option, $value) - { - if (method_exists($this, $option)) { - return $this->{$option}($value); - } - $this->_options[$option] = $value; - - return $this; - } -} diff --git a/app/vendor/cakephp/cakephp/src/ORM/Table.php b/app/vendor/cakephp/cakephp/src/ORM/Table.php index b101dcddf..f8df15f54 100644 --- a/app/vendor/cakephp/cakephp/src/ORM/Table.php +++ b/app/vendor/cakephp/cakephp/src/ORM/Table.php @@ -18,10 +18,13 @@ use ArrayObject; use BadMethodCallException; +use Cake\Collection\CollectionInterface; use Cake\Core\App; use Cake\Core\Configure; use Cake\Core\Exception\CakeException; use Cake\Database\Connection; +use Cake\Database\Exception\DatabaseException; +use Cake\Database\Expression\QueryExpression; use Cake\Database\Schema\TableSchemaInterface; use Cake\Database\TypeFactory; use Cake\Datasource\ConnectionManager; @@ -42,18 +45,20 @@ use Cake\ORM\Exception\RolledbackTransactionException; use Cake\ORM\Query\DeleteQuery; use Cake\ORM\Query\InsertQuery; +use Cake\ORM\Query\QueryFactory; use Cake\ORM\Query\SelectQuery; use Cake\ORM\Query\UpdateQuery; use Cake\ORM\Rule\IsUnique; use Cake\Utility\Inflector; use Cake\Validation\ValidatorAwareInterface; use Cake\Validation\ValidatorAwareTrait; +use Closure; use Exception; use InvalidArgumentException; -use ReflectionMethod; -use RuntimeException; +use Psr\SimpleCache\CacheInterface; +use ReflectionFunction; +use ReflectionNamedType; use function Cake\Core\deprecationWarning; -use function Cake\Core\getTypeName; use function Cake\Core\namespaceSplit; /** @@ -105,6 +110,8 @@ * for the provided named validator. * * - `Model.buildRules` Allows listeners to modify the rules checker by adding more rules. + * Behaviors or custom listerners can subscribe to this even. For tables you don't + * need to subscribe to this event, simply override the `Table::buildRules()` method. * * - `Model.beforeRules` Fired before an entity is validated using the rules checker. * By stopping this event, you can return the final value of the rules checking operation. @@ -133,11 +140,10 @@ * You can subscribe to the events listed above in your table classes by implementing the * lifecycle methods below: * - * - `beforeFind(EventInterface $event, Query $query, ArrayObject $options, boolean $primary)` + * - `beforeFind(EventInterface $event, SelectQuery $query, ArrayObject $options, boolean $primary)` * - `beforeMarshal(EventInterface $event, ArrayObject $data, ArrayObject $options)` * - `afterMarshal(EventInterface $event, EntityInterface $entity, ArrayObject $options)` * - `buildValidator(EventInterface $event, Validator $validator, string $name)` - * - `buildRules(RulesChecker $rules)` * - `beforeRules(EventInterface $event, EntityInterface $entity, ArrayObject $options, string $operation)` * - `afterRules(EventInterface $event, EntityInterface $entity, ArrayObject $options, bool $result, string $operation)` * - `beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options)` @@ -148,10 +154,15 @@ * - `afterDeleteCommit(EventInterface $event, EntityInterface $entity, ArrayObject $options)` * * @see \Cake\Event\EventManager for reference on the events system. - * @link https://book.cakephp.org/4/en/orm/table-objects.html#event-list + * @link https://book.cakephp.org/5/en/orm/table-objects.html#event-list + * @template TBehaviors of array = array{} + * @implements \Cake\Event\EventDispatcherInterface<\Cake\ORM\Table> */ class Table implements RepositoryInterface, EventListenerInterface, EventDispatcherInterface, ValidatorAwareInterface { + /** + * @use \Cake\Event\EventDispatcherTrait<\Cake\ORM\Table> + */ use EventDispatcherTrait; use RulesAwareTrait; use ValidatorAwareTrait; @@ -180,14 +191,14 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc /** * The rules class name that is used. * - * @var string + * @var class-string<\Cake\ORM\RulesChecker> */ public const RULES_CLASS = RulesChecker::class; /** * The IsUnique class name that is used. * - * @var string + * @var class-string<\Cake\ORM\Rule\IsUnique> */ public const IS_UNIQUE_CLASS = IsUnique::class; @@ -196,7 +207,7 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc * * @var string|null */ - protected $_table; + protected ?string $_table = null; /** * Human name giving to this particular instance. Multiple objects representing @@ -204,64 +215,66 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc * * @var string|null */ - protected $_alias; + protected ?string $_alias = null; /** * Connection instance * * @var \Cake\Database\Connection|null */ - protected $_connection; + protected ?Connection $_connection = null; /** * The schema object containing a description of this table fields * * @var \Cake\Database\Schema\TableSchemaInterface|null */ - protected $_schema; + protected ?TableSchemaInterface $_schema = null; /** * The name of the field that represents the primary key in the table * * @var array|string|null */ - protected $_primaryKey; + protected array|string|null $_primaryKey = null; /** * The name of the field that represents a human-readable representation of a row * * @var array|string|null */ - protected $_displayField; + protected array|string|null $_displayField = null; /** * The associations container for this Table. * * @var \Cake\ORM\AssociationCollection */ - protected $_associations; + protected AssociationCollection $_associations; /** * BehaviorRegistry for this table * * @var \Cake\ORM\BehaviorRegistry */ - protected $_behaviors; + protected BehaviorRegistry $_behaviors; /** * The name of the class that represent a single row for this table * - * @var string - * @psalm-var class-string<\Cake\Datasource\EntityInterface> + * @var string|null + * @phpstan-var class-string<\Cake\Datasource\EntityInterface>|null */ - protected $_entityClass; + protected ?string $_entityClass = null; /** * Registry key used to create this table object * * @var string|null */ - protected $_registryAlias; + protected ?string $_registryAlias = null; + + protected QueryFactory $queryFactory; /** * Initializes a new instance @@ -282,53 +295,42 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc * validation set, or an associative array, where key is the name of the * validation set and value the Validator instance. * - * @param array $config List of options for this table + * @param array $config List of options for this table. */ public function __construct(array $config = []) { - if (!empty($config['registryAlias'])) { - $this->setRegistryAlias($config['registryAlias']); - } - if (!empty($config['table'])) { - $this->setTable($config['table']); - } - if (!empty($config['alias'])) { - $this->setAlias($config['alias']); - } - if (!empty($config['connection'])) { - $this->setConnection($config['connection']); - } - if (!empty($config['schema'])) { - $this->setSchema($config['schema']); - } - if (!empty($config['entityClass'])) { - $this->setEntityClass($config['entityClass']); - } - $eventManager = $behaviors = $associations = null; - if (!empty($config['eventManager'])) { - $eventManager = $config['eventManager']; - } - if (!empty($config['behaviors'])) { - $behaviors = $config['behaviors']; - } - if (!empty($config['associations'])) { - $associations = $config['associations']; + $methodConfigs = [ + 'registryAlias', + 'table', + 'alias', + 'connection', + 'schema', + 'entityClass', + ]; + foreach ($methodConfigs as $cfg) { + if (isset($config[$cfg])) { + $this->{'set' . $cfg}($config[$cfg]); + } } - if (!empty($config['validator'])) { - if (!is_array($config['validator'])) { - $this->setValidator(static::DEFAULT_VALIDATOR, $config['validator']); - } else { + if (isset($config['validator'])) { + if (is_array($config['validator'])) { foreach ($config['validator'] as $name => $validator) { $this->setValidator($name, $validator); } + } else { + $this->setValidator(static::DEFAULT_VALIDATOR, $config['validator']); } } - $this->_eventManager = $eventManager ?: new EventManager(); - $this->_behaviors = $behaviors ?: new BehaviorRegistry(); + $this->_eventManager = $config['eventManager'] ?? new EventManager(); + $this->_behaviors = $config['behaviors'] ?? new BehaviorRegistry(); $this->_behaviors->setTable($this); - $this->_associations = $associations ?: new AssociationCollection(); + $this->_associations = $config['associations'] ?? new AssociationCollection(); + $this->queryFactory = $config['queryFactory'] ?? new QueryFactory(); $this->initialize($config); + + assert($this->_eventManager !== null, 'EventManager not available'); + $this->_eventManager->on($this); $this->dispatchEvent('Model.initialize'); } @@ -396,10 +398,10 @@ public function getTable(): string { if ($this->_table === null) { $table = namespaceSplit(static::class); - $table = substr(end($table), 0, -5) ?: $this->_alias; + $table = substr((string)end($table), 0, -5) ?: $this->_alias; if (!$table) { throw new CakeException( - 'You must specify either the `alias` or the `table` option for the constructor.' + 'You must specify either the `alias` or the `table` option for the constructor.', ); } $this->_table = Inflector::underscore($table); @@ -430,10 +432,10 @@ public function getAlias(): string { if ($this->_alias === null) { $alias = namespaceSplit(static::class); - $alias = substr(end($alias), 0, -5) ?: $this->_table; + $alias = substr((string)end($alias), 0, -5) ?: $this->_table; if (!$alias) { throw new CakeException( - 'You must specify either the `alias` or the `table` option for the constructor.' + 'You must specify either the `alias` or the `table` option for the constructor.', ); } $this->_alias = $alias; @@ -445,14 +447,14 @@ public function getAlias(): string /** * Alias a field with the table's current alias. * - * If field is already aliased it will result in no-op. + * If field is already aliased, it will result in no-op. * * @param string $field The field to alias. * @return string The field prefixed with the table alias. */ public function aliasField(string $field): string { - if (strpos($field, '.') !== false) { + if (str_contains($field, '.')) { return $field; } @@ -479,11 +481,7 @@ public function setRegistryAlias(string $registryAlias) */ public function getRegistryAlias(): string { - if ($this->_registryAlias === null) { - $this->_registryAlias = $this->getAlias(); - } - - return $this->_registryAlias; + return $this->_registryAlias ??= $this->getAlias(); } /** @@ -507,8 +505,8 @@ public function setConnection(Connection $connection) public function getConnection(): Connection { if (!$this->_connection) { - /** @var \Cake\Database\Connection $connection */ $connection = ConnectionManager::get(static::defaultConnectionName()); + assert($connection instanceof Connection); $this->_connection = $connection; } @@ -526,19 +524,12 @@ public function getSchema(): TableSchemaInterface $this->_schema = $this->getConnection() ->getSchemaCollection() ->describe($this->getTable()); - - $method = new ReflectionMethod($this, '_initializeSchema'); - if ($method->getDeclaringClass()->getName() != Table::class) { - deprecationWarning( - 'Table::_initializeSchema() is deprecated. Override `getSchema()` with a parent call instead.' - ); - $this->_schema = $this->_initializeSchema($this->_schema); - } if (Configure::read('debug')) { $this->checkAliasLengths(); } } + /** @var \Cake\Database\Schema\TableSchemaInterface */ return $this->_schema; } @@ -551,7 +542,7 @@ public function getSchema(): TableSchemaInterface * @param \Cake\Database\Schema\TableSchemaInterface|array $schema Schema to be used for this table * @return $this */ - public function setSchema($schema) + public function setSchema(TableSchemaInterface|array $schema) { if (is_array($schema)) { $constraints = []; @@ -581,18 +572,18 @@ public function setSchema($schema) * queries fit into the max length allowed by database driver. * * @return void - * @throws \RuntimeException When an alias combination is too long + * @throws \Cake\Database\Exception\DatabaseException When an alias combination is too long */ protected function checkAliasLengths(): void { if ($this->_schema === null) { - throw new RuntimeException("Unable to check max alias lengths for `{$this->getAlias()}` without schema."); + throw new DatabaseException(sprintf( + 'Unable to check max alias lengths for `%s` without schema.', + $this->getAlias(), + )); } - $maxLength = null; - if (method_exists($this->getConnection()->getDriver(), 'getMaxAliasLength')) { - $maxLength = $this->getConnection()->getDriver()->getMaxAliasLength(); - } + $maxLength = $this->getConnection()->getDriver()->getMaxAliasLength(); if ($maxLength === null) { return; } @@ -601,40 +592,16 @@ protected function checkAliasLengths(): void foreach ($this->_schema->columns() as $name) { if (strlen($table . '__' . $name) > $maxLength) { $nameLength = $maxLength - 2; - throw new RuntimeException( + throw new DatabaseException( 'ORM queries generate field aliases using the table name/alias and column name. ' . "The table alias `{$table}` and column `{$name}` create an alias longer than ({$nameLength}). " . 'You must change the table schema in the database and shorten either the table or column ' . - 'identifier so they fit within the database alias limits.' + 'identifier so they fit within the database alias limits.', ); } } } - /** - * Override this function in order to alter the schema used by this table. - * This function is only called after fetching the schema out of the database. - * If you wish to provide your own schema to this table without touching the - * database, you can override schema() or inject the definitions though that - * method. - * - * ### Example: - * - * ``` - * protected function _initializeSchema(\Cake\Database\Schema\TableSchemaInterface $schema) { - * $schema->setColumnType('preferences', 'json'); - * return $schema; - * } - * ``` - * - * @param \Cake\Database\Schema\TableSchemaInterface $schema The table definition fetched from database. - * @return \Cake\Database\Schema\TableSchemaInterface the altered schema - */ - protected function _initializeSchema(TableSchemaInterface $schema): TableSchemaInterface - { - return $schema; - } - /** * Test to see if a Table has a specific field/column. * @@ -655,7 +622,7 @@ public function hasField(string $field): bool * @param array|string $key Sets a new name to be used as primary key * @return $this */ - public function setPrimaryKey($key) + public function setPrimaryKey(array|string $key) { $this->_primaryKey = $key; @@ -667,7 +634,7 @@ public function setPrimaryKey($key) * * @return array|string */ - public function getPrimaryKey() + public function getPrimaryKey(): array|string { if ($this->_primaryKey === null) { $key = $this->getSchema()->getPrimaryKey(); @@ -686,7 +653,7 @@ public function getPrimaryKey() * @param array|string $field Name to be used as display field. * @return $this */ - public function setDisplayField($field) + public function setDisplayField(array|string $field) { $this->_displayField = $field; @@ -696,9 +663,9 @@ public function setDisplayField($field) /** * Returns the display field. * - * @return array|string + * @return array|string|null */ - public function getDisplayField() + public function getDisplayField(): array|string|null { if ($this->_displayField !== null) { return $this->_displayField; @@ -729,8 +696,7 @@ public function getDisplayField() /** * Returns the class used to hydrate rows for this table. * - * @return string - * @psalm-return class-string<\Cake\Datasource\EntityInterface> + * @return class-string<\Cake\Datasource\EntityInterface> */ public function getEntityClass(): string { @@ -770,7 +736,7 @@ public function getEntityClass(): string */ public function setEntityClass(string $name) { - /** @psalm-var class-string<\Cake\Datasource\EntityInterface>|null */ + /** @var class-string<\Cake\Datasource\EntityInterface>|null $class */ $class = App::className($name, 'Model/Entity'); if ($class === null) { throw new MissingEntityException([$name]); @@ -879,18 +845,22 @@ public function behaviors(): BehaviorRegistry * * @param string $name The behavior alias to get from the registry. * @return \Cake\ORM\Behavior + * @template TName of key-of + * @phpstan-param TName $name The behavior alias to get from the registry. + * @phpstan-return TBehaviors[TName] * @throws \InvalidArgumentException If the behavior does not exist. */ public function getBehavior(string $name): Behavior { if (!$this->_behaviors->has($name)) { throw new InvalidArgumentException(sprintf( - 'The %s behavior is not defined on %s.', + 'The `%s` behavior is not defined on `%s`.', $name, - static::class + static::class, )); } + /** @var \Cake\ORM\Behavior */ return $this->_behaviors->get($name); } @@ -926,11 +896,11 @@ public function getAssociation(string $name): Association { $association = $this->findAssociation($name); if (!$association) { - $assocations = $this->associations()->keys(); + $associations = $this->associations()->keys(); $message = "The `{$name}` association is not defined on `{$this->getAlias()}`."; - if ($assocations) { - $message .= "\nValid associations are: " . implode(', ', $assocations); + if ($associations) { + $message .= "\nValid associations are: " . implode(', ', $associations); } throw new InvalidArgumentException($message); } @@ -969,7 +939,7 @@ public function hasAssociation(string $name): bool */ protected function findAssociation(string $name): ?Association { - if (strpos($name, '.') === false) { + if (!str_contains($name, '.')) { return $this->_associations->get($name); } @@ -980,7 +950,7 @@ protected function findAssociation(string $name): ?Association } if ($result !== null && $next !== null) { - $result = $result->getTarget()->getAssociation($next); + return $result->getTarget()->getAssociation($next); } return $result; @@ -1072,6 +1042,7 @@ public function belongsTo(string $associated, array $options = []): BelongsTo { $options += ['sourceTable' => $this]; + /** @var \Cake\ORM\Association\BelongsTo */ return $this->_associations->load(BelongsTo::class, $associated, $options); } @@ -1115,6 +1086,7 @@ public function hasOne(string $associated, array $options = []): HasOne { $options += ['sourceTable' => $this]; + /** @var \Cake\ORM\Association\HasOne */ return $this->_associations->load(HasOne::class, $associated, $options); } @@ -1164,6 +1136,7 @@ public function hasMany(string $associated, array $options = []): HasMany { $options += ['sourceTable' => $this]; + /** @var \Cake\ORM\Association\HasMany */ return $this->_associations->load(HasMany::class, $associated, $options); } @@ -1215,6 +1188,7 @@ public function belongsToMany(string $associated, array $options = []): BelongsT { $options += ['sourceTable' => $this]; + /** @var \Cake\ORM\Association\BelongsToMany */ return $this->_associations->load(BelongsToMany::class, $associated, $options); } @@ -1227,7 +1201,8 @@ public function belongsToMany(string $associated, array $options = []): BelongsT * Each find() will trigger a `Model.beforeFind` event for all attached * listeners. Any listener can set a valid result set using $query * - * By default, `$options` will recognize the following keys: + * By default, following special named arguments are recognized which are + * used as select query options: * * - fields * - conditions @@ -1242,14 +1217,12 @@ public function belongsToMany(string $associated, array $options = []): BelongsT * * ### Usage * - * Using the options array: - * * ``` - * $query = $articles->find('all', [ - * 'conditions' => ['published' => 1], - * 'limit' => 10, - * 'contain' => ['Users', 'Comments'] - * ]); + * $query = $articles->find('all', + * conditions: ['published' => 1], + * limit: 10, + * contain: ['Users', 'Comments'] + * ); * ``` * * Using the builder interface: @@ -1264,34 +1237,58 @@ public function belongsToMany(string $associated, array $options = []): BelongsT * ### Calling finders * * The find() method is the entry point for custom finder methods. - * You can invoke a finder by specifying the type: + * You can invoke a finder by specifying the type. + * + * This will invoke the `findPublished` method: * * ``` * $query = $articles->find('published'); * ``` * - * Would invoke the `findPublished` method. + * ## Typed finder arguments + * + * Finders must have a `SelectQuery` instance as their 1st argument and any + * additional parameters as needed. + * + * Here, the finder "findByCategory" has an integer `$category` parameter: + * + * ``` + * function findByCategory(SelectQuery $query, int $category): SelectQuery + * { + * return $query; + * } + * ``` + * + * This finder can be called as: + * + * ``` + * $query = $articles->find('byCategory', $category); + * ``` + * + * or using named arguments as: + * ``` + * $query = $articles->find(type: 'byCategory', category: $category); + * ``` * * @param string $type the type of query to perform - * @param array $options An array that will be passed to Query::applyOptions() - * @return \Cake\ORM\Query The query builder + * @param mixed ...$args Arguments that match up to finder-specific parameters + * @return \Cake\ORM\Query\SelectQuery The query builder */ - public function find(string $type = 'all', array $options = []): Query + public function find(string $type = 'all', mixed ...$args): SelectQuery { - return $this->callFinder($type, $this->selectQuery()->select(), $options); + return $this->callFinder($type, $this->selectQuery(), ...$args); } /** * Returns the query as passed. * - * By default findAll() applies no conditions, you - * can override this method in subclasses to modify how `find('all')` works. + * By default findAll() applies no query clauses, you can override this + * method in subclasses to modify how `find('all')` works. * - * @param \Cake\ORM\Query $query The query to find with - * @param array $options The options to use for the find - * @return \Cake\ORM\Query The query builder + * @param \Cake\ORM\Query\SelectQuery $query The query to find with + * @return \Cake\ORM\Query\SelectQuery The query builder */ - public function findAll(Query $query, array $options): Query + public function findAll(SelectQuery $query): SelectQuery { return $query; } @@ -1315,25 +1312,19 @@ public function findAll(Query $query, array $options): Query * ] * ``` * - * You can specify which property will be used as the key and which as value - * by using the `$options` array, when not specified, it will use the results - * of calling `primaryKey` and `displayField` respectively in this table: + * You can specify which property will be used as the key and which as value, + * when not specified, it will use the results of calling `primaryKey` and + * `displayField` respectively in this table: * * ``` - * $table->find('list', [ - * 'keyField' => 'name', - * 'valueField' => 'age' - * ]); + * $table->find('list', keyField: 'name', valueField: 'age'); * ``` * * The `valueField` can also be an array, in which case you can also specify * the `valueSeparator` option to control how the values will be concatenated: * * ``` - * $table->find('list', [ - * 'valueField' => ['first_name', 'last_name'], - * 'valueSeparator' => ' | ', - * ]); + * $table->find('list', valueField: ['first_name', 'last_name'], valueSeparator: ' | '); * ``` * * The results of this finder will be in the following form: @@ -1349,9 +1340,7 @@ public function findAll(Query $query, array $options): Query * can customize the property to use for grouping by setting `groupField`: * * ``` - * $table->find('list', [ - * 'groupField' => 'category_id', - * ]); + * $table->find('list', groupField: 'category_id'); * ``` * * When using a `groupField` results will be returned in this format: @@ -1368,29 +1357,29 @@ public function findAll(Query $query, array $options): Query * ] * ``` * - * @param \Cake\ORM\Query $query The query to find with - * @param array $options The options for the find - * @return \Cake\ORM\Query The query builder + * @param \Cake\ORM\Query\SelectQuery $query The query to find with + * @return \Cake\ORM\Query\SelectQuery The query builder */ - public function findList(Query $query, array $options): Query - { - $options += [ - 'keyField' => $this->getPrimaryKey(), - 'valueField' => $this->getDisplayField(), - 'groupField' => null, - 'valueSeparator' => ';', - ]; + public function findList( + SelectQuery $query, + Closure|array|string|null $keyField = null, + Closure|array|string|null $valueField = null, + Closure|array|string|null $groupField = null, + string $valueSeparator = ' ', + ): SelectQuery { + $keyField ??= $this->getPrimaryKey(); + $valueField ??= $this->getDisplayField(); if ( !$query->clause('select') && - !is_object($options['keyField']) && - !is_object($options['valueField']) && - !is_object($options['groupField']) + !is_object($keyField) && + !is_object($valueField) && + !is_object($groupField) ) { $fields = array_merge( - (array)$options['keyField'], - (array)$options['valueField'], - (array)$options['groupField'] + (array)$keyField, + (array)$valueField, + (array)$groupField, ); $columns = $this->getSchema()->columns(); if (count($fields) === count(array_intersect($fields, $columns))) { @@ -1399,18 +1388,15 @@ public function findList(Query $query, array $options): Query } $options = $this->_setFieldMatchers( - $options, - ['keyField', 'valueField', 'groupField'] + compact('keyField', 'valueField', 'groupField', 'valueSeparator'), + ['keyField', 'valueField', 'groupField'], ); - return $query->formatResults(function ($results) use ($options) { - /** @var \Cake\Collection\CollectionInterface $results */ - return $results->combine( - $options['keyField'], - $options['valueField'], - $options['groupField'] - ); - }); + return $query->formatResults(fn(CollectionInterface $results) => $results->combine( + $options['keyField'], + $options['valueField'], + $options['groupField'], + )); } /** @@ -1422,35 +1408,34 @@ public function findList(Query $query, array $options): Query * * You can customize what fields are used for nesting results, by default the * primary key and the `parent_id` fields are used. If you wish to change - * these defaults you need to provide the keys `keyField`, `parentField` or `nestingKey` in - * `$options`: + * these defaults you need to provide the `keyField`, `parentField` or `nestingKey` + * arguments: * * ``` - * $table->find('threaded', [ - * 'keyField' => 'id', - * 'parentField' => 'ancestor_id', - * 'nestingKey' => 'children' - * ]); + * $table->find('threaded', keyField: 'id', parentField: 'ancestor_id', nestingKey: 'children'); * ``` * - * @param \Cake\ORM\Query $query The query to find with - * @param array $options The options to find with - * @return \Cake\ORM\Query The query builder + * @param \Cake\ORM\Query\SelectQuery $query The query to find with + * @param \Closure|array|string|null $keyField The path to the key field. + * @param \Closure|array|string $parentField The path to the parent field. + * @param string $nestingKey The key to nest children under. + * @return \Cake\ORM\Query\SelectQuery The query builder */ - public function findThreaded(Query $query, array $options): Query - { - $options += [ - 'keyField' => $this->getPrimaryKey(), - 'parentField' => 'parent_id', - 'nestingKey' => 'children', - ]; + public function findThreaded( + SelectQuery $query, + Closure|array|string|null $keyField = null, + Closure|array|string $parentField = 'parent_id', + string $nestingKey = 'children', + ): SelectQuery { + $keyField ??= $this->getPrimaryKey(); - $options = $this->_setFieldMatchers($options, ['keyField', 'parentField']); + $options = $this->_setFieldMatchers(compact('keyField', 'parentField'), ['keyField', 'parentField']); - return $query->formatResults(function ($results) use ($options) { - /** @var \Cake\Collection\CollectionInterface $results */ - return $results->nest($options['keyField'], $options['parentField'], $options['nestingKey']); - }); + return $query->formatResults(fn(CollectionInterface $results) => $results->nest( + $options['keyField'], + $options['parentField'], + $nestingKey, + )); } /** @@ -1480,7 +1465,7 @@ protected function _setFieldMatchers(array $options, array $keys): array $fields = $options[$field]; $glue = in_array($field, ['keyField', 'parentField'], true) ? ';' : $options['valueSeparator']; - $options[$field] = function ($row) use ($fields, $glue) { + $options[$field] = function ($row) use ($fields, $glue): string { $matches = []; foreach ($fields as $field) { $matches[] = $row[$field]; @@ -1501,25 +1486,34 @@ protected function _setFieldMatchers(array $options, array $keys): array * Get an article and some relationships: * * ``` - * $article = $articles->get(1, ['contain' => ['Users', 'Comments']]); + * $article = $articles->get(1, contain: ['Users', 'Comments']); * ``` * * @param mixed $primaryKey primary key value to find - * @param array $options options accepted by `Table::find()` + * @param array|string $finder The finder to use. Passing an options array is deprecated. + * @param \Psr\SimpleCache\CacheInterface|string|null $cache The cache config to use. + * Defaults to `null`, i.e. no caching. + * @param \Closure|string|null $cacheKey The cache key to use. If not provided + * one will be autogenerated if `$cache` is not null. + * @param mixed ...$args Arguments that query options or finder specific parameters. * @return \Cake\Datasource\EntityInterface * @throws \Cake\Datasource\Exception\RecordNotFoundException if the record with such id * could not be found * @throws \Cake\Datasource\Exception\InvalidPrimaryKeyException When $primaryKey has an * incorrect number of elements. * @see \Cake\Datasource\RepositoryInterface::find() - * @psalm-suppress InvalidReturnType */ - public function get($primaryKey, array $options = []): EntityInterface - { + public function get( + mixed $primaryKey, + array|string $finder = 'all', + CacheInterface|string|null $cache = null, + Closure|string|null $cacheKey = null, + mixed ...$args, + ): EntityInterface { if ($primaryKey === null) { throw new InvalidPrimaryKeyException(sprintf( - 'Record not found in table "%s" with primary key [NULL]', - $this->getTable() + 'Record not found in table `%s` with primary key `[NULL]`.', + $this->getTable(), )); } @@ -1538,33 +1532,45 @@ public function get($primaryKey, array $options = []): EntityInterface }, $primaryKey); throw new InvalidPrimaryKeyException(sprintf( - 'Record not found in table "%s" with primary key [%s]', + 'Record not found in table `%s` with primary key `[%s]`.', $this->getTable(), - implode(', ', $primaryKey) + implode(', ', $primaryKey), )); } $conditions = array_combine($key, $primaryKey); - $cacheConfig = $options['cache'] ?? false; - $cacheKey = $options['key'] ?? false; - $finder = $options['finder'] ?? 'all'; - unset($options['key'], $options['cache'], $options['finder']); + if (is_array($finder)) { + deprecationWarning( + '5.0.0', + 'Calling Table::get() with options array is deprecated.' + . ' Use named arguments instead.', + ); + + $args += $finder; + $finder = $args['finder'] ?? 'all'; + if (isset($args['cache'])) { + $cache = $args['cache']; + } + if (isset($args['key'])) { + $cacheKey = $args['key']; + } + unset($args['key'], $args['cache'], $args['finder']); + } - $query = $this->find($finder, $options)->where($conditions); + $query = $this->find($finder, ...$args)->where($conditions); - if ($cacheConfig) { + if ($cache) { if (!$cacheKey) { $cacheKey = sprintf( 'get-%s-%s-%s', $this->getConnection()->configName(), $this->getTable(), - json_encode($primaryKey) + json_encode($primaryKey, JSON_THROW_ON_ERROR), ); } - $query->cache($cacheKey, $cacheConfig); + $query->cache($cacheKey, $cache); } - /** @psalm-suppress InvalidReturnStatement */ return $query->firstOrFail(); } @@ -1575,12 +1581,10 @@ public function get($primaryKey, array $options = []): EntityInterface * @param bool $atomic Whether to execute the worker inside a database transaction. * @return mixed */ - protected function _executeTransaction(callable $worker, bool $atomic = true) + protected function _executeTransaction(callable $worker, bool $atomic = true): mixed { if ($atomic) { - return $this->getConnection()->transactional(function () use ($worker) { - return $worker(); - }); + return $this->getConnection()->transactional(fn() => $worker()); } return $worker(); @@ -1611,7 +1615,7 @@ protected function _transactionCommitted(bool $atomic, bool $primary): bool * entity will be saved and returned. * * If your find conditions require custom order, associations or conditions, then the $search - * parameter can be a callable that takes the Query as the argument, or a \Cake\ORM\Query object passed + * parameter can be a callable that takes the Query as the argument, or a \Cake\ORM\Query\SelectQuery object passed * as the $search parameter. Allowing you to customize the find results. * * ### Options @@ -1622,26 +1626,30 @@ protected function _transactionCommitted(bool $atomic, bool $primary): bool * transaction (default: true) * - defaults: Whether to use the search criteria as default values for the new entity (default: true) * - * @param \Cake\ORM\Query|callable|array $search The criteria to find existing + * @param \Cake\ORM\Query\SelectQuery|callable|array $search The criteria to find existing * records by. Note that when you pass a query object you'll have to use * the 2nd arg of the method to modify the entity data before saving. - * @param callable|null $callback A callback that will be invoked for newly - * created entities. This callback will be called *before* the entity + * @param callable|array|null $callback An array of data key/value pairs or a callback that will + * be invoked for newly created entities. This callback will be called *before* the entity * is persisted. * @param array $options The options to use when saving. * @return \Cake\Datasource\EntityInterface An entity. * @throws \Cake\ORM\Exception\PersistenceFailedException When the entity couldn't be saved */ - public function findOrCreate($search, ?callable $callback = null, $options = []): EntityInterface - { + public function findOrCreate( + SelectQuery|callable|array $search, + callable|array|null $callback = null, + array $options = [], + ): EntityInterface { $options = new ArrayObject($options + [ 'atomic' => true, 'defaults' => true, ]); - $entity = $this->_executeTransaction(function () use ($search, $callback, $options) { - return $this->_processFindOrCreate($search, $callback, $options->getArrayCopy()); - }, $options['atomic']); + $entity = $this->_executeTransaction( + fn() => $this->_processFindOrCreate($search, $callback, $options->getArrayCopy()), + $options['atomic'], + ); if ($entity && $this->_transactionCommitted($options['atomic'], true)) { $this->dispatchEvent('Model.afterSaveCommit', compact('entity', 'options')); @@ -1653,9 +1661,9 @@ public function findOrCreate($search, ?callable $callback = null, $options = []) /** * Performs the actual find and/or create of an entity based on the passed options. * - * @param \Cake\ORM\Query|callable|array $search The criteria to find an existing record by, or a callable tha will + * @param \Cake\ORM\Query\SelectQuery|callable|array $search The criteria to find an existing record by, or a callable tha will * customize the find query. - * @param callable|null $callback A callback that will be invoked for newly + * @param callable|array|null $callback Data or a callback that will be invoked for newly * created entities. This callback will be called *before* the entity * is persisted. * @param array $options The options to use when saving. @@ -1663,8 +1671,11 @@ public function findOrCreate($search, ?callable $callback = null, $options = []) * @throws \Cake\ORM\Exception\PersistenceFailedException When the entity couldn't be saved * @throws \InvalidArgumentException */ - protected function _processFindOrCreate($search, ?callable $callback = null, $options = []) - { + protected function _processFindOrCreate( + SelectQuery|callable|array $search, + callable|array|null $callback = null, + array $options = [], + ): EntityInterface|array { $query = $this->_getFindOrCreateQuery($search); $row = $query->first(); @@ -1672,10 +1683,16 @@ protected function _processFindOrCreate($search, ?callable $callback = null, $op return $row; } + $data = $search; + if (is_array($callback) && !is_callable($callback)) { + $data = $callback + $search; + $callback = null; + } + $entity = $this->newEmptyEntity(); - if ($options['defaults'] && is_array($search)) { - $accessibleFields = array_combine(array_keys($search), array_fill(0, count($search), true)); - $entity = $this->patchEntity($entity, $search, ['accessibleFields' => $accessibleFields]); + if ($options['defaults'] && is_array($data)) { + $accessibleFields = array_combine(array_keys($data), array_fill(0, count($data), true)); + $entity = $this->patchEntity($entity, $data, ['accessibleFields' => $accessibleFields]); } if ($callback !== null) { $entity = $callback($entity) ?: $entity; @@ -1694,119 +1711,127 @@ protected function _processFindOrCreate($search, ?callable $callback = null, $op /** * Gets the query object for findOrCreate(). * - * @param \Cake\ORM\Query|callable|array $search The criteria to find existing records by. - * @return \Cake\ORM\Query + * @param \Cake\ORM\Query\SelectQuery|callable|array $search The criteria to find existing records by. + * @return \Cake\ORM\Query\SelectQuery */ - protected function _getFindOrCreateQuery($search): Query + protected function _getFindOrCreateQuery(SelectQuery|callable|array $search): SelectQuery { if (is_callable($search)) { $query = $this->find(); $search($query); } elseif (is_array($search)) { $query = $this->find()->where($search); - } elseif ($search instanceof Query) { - $query = $search; } else { - throw new InvalidArgumentException(sprintf( - 'Search criteria must be an array, callable or Query. Got "%s"', - getTypeName($search) - )); + $query = $search; } return $query; } /** - * Creates a new Query instance for a table. + * Creates a new SelectQuery instance for a table. * - * @return \Cake\ORM\Query + * @return \Cake\ORM\Query\SelectQuery */ - public function query(): Query + public function query(): SelectQuery { - deprecationWarning( - 'As of 4.5.0 using query() is deprecated. Instead use `insertQuery()`, ' . - '`deleteQuery()`, `selectQuery()` or `updateQuery()`. The query objects ' . - 'returned by these methods will emit deprecations that will become fatal errors in 5.0.' . - 'See https://book.cakephp.org/4/en/appendices/4-5-migration-guide.html for more information.' - ); - - return new Query($this->getConnection(), $this); + return $this->selectQuery(); } /** - * Creates a new DeleteQuery instance for a table. + * Creates a new select query * - * @return \Cake\ORM\Query\DeleteQuery + * @return \Cake\ORM\Query\SelectQuery */ - public function deleteQuery(): DeleteQuery + public function selectQuery(): SelectQuery { - return new DeleteQuery($this->getConnection(), $this); + return $this->queryFactory->select($this); } /** - * Creates a new InsertQuery instance for a table. + * Creates a new insert query * * @return \Cake\ORM\Query\InsertQuery */ public function insertQuery(): InsertQuery { - return new InsertQuery($this->getConnection(), $this); + return $this->queryFactory->insert($this); } /** - * Creates a new SelectQuery instance for a table. + * Creates a new update query * - * @return \Cake\ORM\Query\SelectQuery + * @return \Cake\ORM\Query\UpdateQuery */ - public function selectQuery(): SelectQuery + public function updateQuery(): UpdateQuery { - return new SelectQuery($this->getConnection(), $this); + return $this->queryFactory->update($this); } /** - * Creates a new UpdateQuery instance for a table. + * Creates a new delete query * - * @return \Cake\ORM\Query\UpdateQuery + * @return \Cake\ORM\Query\DeleteQuery */ - public function updateQuery(): UpdateQuery + public function deleteQuery(): DeleteQuery { - return new UpdateQuery($this->getConnection(), $this); + return $this->queryFactory->delete($this); } /** - * Creates a new Query::subquery() instance for a table. + * Creates a new Query instance with field auto aliasing disabled. * - * @return \Cake\ORM\Query - * @see \Cake\ORM\Query::subquery() + * This is useful for subqueries. + * + * @return \Cake\ORM\Query\SelectQuery */ - public function subquery(): Query + public function subquery(): SelectQuery { - return Query::subquery($this); + return $this->queryFactory->select($this)->disableAutoAliasing(); } /** - * @inheritDoc + * Update all matching records. + * + * Sets the $fields to the provided values based on $conditions. + * This method will *not* trigger beforeSave/afterSave events. If you need those + * first load a collection of records and update them. + * + * @param \Cake\Database\Expression\QueryExpression|\Closure|array|string $fields A hash of field => new value. + * @param \Cake\Database\Expression\QueryExpression|\Closure|array|string|null $conditions Conditions to be used, accepts anything Query::where() + * @return int Count Returns the affected rows. */ - public function updateAll($fields, $conditions): int - { + public function updateAll( + QueryExpression|Closure|array|string $fields, + QueryExpression|Closure|array|string|null $conditions, + ): int { $statement = $this->updateQuery() ->set($fields) ->where($conditions) ->execute(); - $statement->closeCursor(); return $statement->rowCount(); } /** - * @inheritDoc + * Deletes all records matching the provided conditions. + * + * This method will *not* trigger beforeDelete/afterDelete events. If you + * need those first load a collection of records and delete them. + * + * This method will *not* execute on associations' `cascade` attribute. You should + * use database foreign keys + ON CASCADE rules if you need cascading deletes combined + * with this method. + * + * @param \Cake\Database\Expression\QueryExpression|\Closure|array|string|null $conditions Conditions to be used, accepts anything Query::where() + * can take. + * @return int Returns the number of affected rows. */ - public function deleteAll($conditions): int + public function deleteAll(QueryExpression|Closure|array|string|null $conditions): int { $statement = $this->deleteQuery() ->where($conditions) ->execute(); - $statement->closeCursor(); return $statement->rowCount(); } @@ -1814,7 +1839,7 @@ public function deleteAll($conditions): int /** * @inheritDoc */ - public function exists($conditions): bool + public function exists(QueryExpression|Closure|array|string|null $conditions): bool { return (bool)count( $this->find('all') @@ -1822,7 +1847,7 @@ public function exists($conditions): bool ->where($conditions) ->limit(1) ->disableHydration() - ->toArray() + ->toArray(), ); } @@ -1908,18 +1933,15 @@ public function exists($conditions): bool * ``` * * @param \Cake\Datasource\EntityInterface $entity the entity to be saved - * @param \Cake\ORM\SaveOptionsBuilder|\ArrayAccess|array $options The options to use when saving. + * @param array $options The options to use when saving. * @return \Cake\Datasource\EntityInterface|false * @throws \Cake\ORM\Exception\RolledbackTransactionException If the transaction is aborted in the afterSave event. */ - public function save(EntityInterface $entity, $options = []) - { - if ($options instanceof SaveOptionsBuilder) { - deprecationWarning('SaveOptionsBuilder is deprecated. Use a normal array for options instead.'); - $options = $options->toArray(); - } - - $options = new ArrayObject((array)$options + [ + public function save( + EntityInterface $entity, + array $options = [], + ): EntityInterface|false { + $options = new ArrayObject($options + [ 'atomic' => true, 'associated' => true, 'checkRules' => true, @@ -1936,9 +1958,10 @@ public function save(EntityInterface $entity, $options = []) return $entity; } - $success = $this->_executeTransaction(function () use ($entity, $options) { - return $this->_processSave($entity, $options); - }, $options['atomic']); + $success = $this->_executeTransaction( + fn() => $this->_processSave($entity, $options), + $options['atomic'], + ); if ($success) { if ($this->_transactionCommitted($options['atomic'], $options['_primary'])) { @@ -1961,12 +1984,12 @@ public function save(EntityInterface $entity, $options = []) * the entity contains errors or the save was aborted by a callback. * * @param \Cake\Datasource\EntityInterface $entity the entity to be saved - * @param \ArrayAccess|array $options The options to use when saving. + * @param array $options The options to use when saving. * @return \Cake\Datasource\EntityInterface * @throws \Cake\ORM\Exception\PersistenceFailedException When the entity couldn't be saved * @see \Cake\ORM\Table::save() */ - public function saveOrFail(EntityInterface $entity, $options = []): EntityInterface + public function saveOrFail(EntityInterface $entity, array $options = []): EntityInterface { $saved = $this->save($entity, $options); if ($saved === false) { @@ -1980,13 +2003,13 @@ public function saveOrFail(EntityInterface $entity, $options = []): EntityInterf * Performs the actual saving of an entity based on the passed options. * * @param \Cake\Datasource\EntityInterface $entity the entity to be saved - * @param \ArrayObject $options the options to use for the save operation + * @param \ArrayObject $options the options to use for the save operation * @return \Cake\Datasource\EntityInterface|false - * @throws \RuntimeException When an entity is missing some of the primary keys. + * @throws \Cake\Database\Exception\DatabaseException When an entity is missing some of the primary keys. * @throws \Cake\ORM\Exception\RolledbackTransactionException If the transaction * is aborted in the afterSave event. */ - protected function _processSave(EntityInterface $entity, ArrayObject $options) + protected function _processSave(EntityInterface $entity, ArrayObject $options): EntityInterface|false { $primaryColumns = (array)$this->getPrimaryKey(); @@ -1994,7 +2017,7 @@ protected function _processSave(EntityInterface $entity, ArrayObject $options) $alias = $this->getAlias(); $conditions = []; foreach ($entity->extract($primaryColumns) as $k => $v) { - $conditions["$alias.$k"] = $v; + $conditions["{$alias}.{$k}"] = $v; } $entity->setNew(!$this->exists($conditions)); } @@ -2013,11 +2036,15 @@ protected function _processSave(EntityInterface $entity, ArrayObject $options) return false; } - if ($result !== false && !($result instanceof EntityInterface)) { - throw new RuntimeException(sprintf( - 'The beforeSave callback must return `false` or `EntityInterface` instance. Got `%s` instead.', - getTypeName($result) - )); + if ($result !== false) { + assert( + $result instanceof EntityInterface, + sprintf( + 'The result for the `Model.beforeSave` event must be `false` or `EntityInterface` instance.' + . ' Got `%s` instead.', + get_debug_type($result), + ), + ); } return $result; @@ -2027,7 +2054,7 @@ protected function _processSave(EntityInterface $entity, ArrayObject $options) $this, $entity, $options['associated'], - ['_primary' => false] + $options->getArrayCopy() + ['_primary' => false] + $options->getArrayCopy(), ); if (!$saved && $options['atomic']) { @@ -2060,7 +2087,7 @@ protected function _processSave(EntityInterface $entity, ArrayObject $options) * once the entity for this table has been saved successfully. * * @param \Cake\Datasource\EntityInterface $entity the entity to be saved - * @param \ArrayObject $options the options to use for the save operation + * @param \ArrayObject $options the options to use for the save operation * @return bool True on success * @throws \Cake\ORM\Exception\RolledbackTransactionException If the transaction * is aborted in the afterSave event. @@ -2071,7 +2098,7 @@ protected function _onSaveSuccess(EntityInterface $entity, ArrayObject $options) $this, $entity, $options['associated'], - ['_primary' => false] + $options->getArrayCopy() + ['_primary' => false] + $options->getArrayCopy(), ); if (!$success && $options['atomic']) { @@ -2099,24 +2126,24 @@ protected function _onSaveSuccess(EntityInterface $entity, ArrayObject $options) * @param \Cake\Datasource\EntityInterface $entity the subject entity from were $data was extracted * @param array $data The actual data that needs to be saved * @return \Cake\Datasource\EntityInterface|false - * @throws \RuntimeException if not all the primary keys where supplied or could + * @throws \Cake\Database\Exception\DatabaseException if not all the primary keys where supplied or could * be generated when the table has composite primary keys. Or when the table has no primary key. */ - protected function _insert(EntityInterface $entity, array $data) + protected function _insert(EntityInterface $entity, array $data): EntityInterface|false { $primary = (array)$this->getPrimaryKey(); - if (empty($primary)) { + if (!$primary) { $msg = sprintf( - 'Cannot insert row in "%s" table, it has no primary key.', - $this->getTable() + 'Cannot insert row in `%s` table, it has no primary key.', + $this->getTable(), ); - throw new RuntimeException($msg); + throw new DatabaseException($msg); } $keys = array_fill(0, count($primary), null); $id = (array)$this->_newId($primary) + $keys; // Generate primary keys preferring values in $data. - $primary = array_combine($primary, $id) ?: []; + $primary = array_combine($primary, $id); $primary = array_intersect_key($data, $primary) + $primary; $filteredKeys = array_filter($primary, function ($v) { @@ -2132,39 +2159,43 @@ protected function _insert(EntityInterface $entity, array $data) $msg .= sprintf( 'Got (%s), expecting (%s)', implode(', ', $filteredKeys + $entity->extract(array_keys($primary))), - implode(', ', array_keys($primary)) + implode(', ', array_keys($primary)), ); - throw new RuntimeException($msg); + throw new DatabaseException($msg); } } } - if (empty($data)) { + if (!$data) { return false; } - $statement = $this->insertQuery() - ->insert(array_keys($data)) + $statement = $this->insertQuery()->insert(array_keys($data)) ->values($data) ->execute(); $success = false; if ($statement->rowCount() !== 0) { $success = $entity; - $entity->set($filteredKeys, ['guard' => false]); + + if (method_exists($entity, 'patch')) { + $entity = $entity->patch($filteredKeys, ['guard' => false]); + } else { + $entity->set($filteredKeys, ['guard' => false]); + } + $schema = $this->getSchema(); $driver = $this->getConnection()->getDriver(); foreach ($primary as $key => $v) { if (!isset($data[$key])) { $id = $statement->lastInsertId($this->getTable(), $key); - /** @var string $type */ $type = $schema->getColumnType($key); + assert($type !== null); $entity->set($key, TypeFactory::build($type)->toPHP($id, $driver)); break; } } } - $statement->closeCursor(); return $success; } @@ -2182,13 +2213,13 @@ protected function _insert(EntityInterface $entity, array $data) * @param array $primary The primary key columns to get a new ID for. * @return string|null Either null or the primary key value or a list of primary key values. */ - protected function _newId(array $primary) + protected function _newId(array $primary): ?string { if (!$primary || count($primary) > 1) { return null; } - /** @var string $typeName */ $typeName = $this->getSchema()->getColumnType($primary[0]); + assert($typeName !== null); $type = TypeFactory::build($typeName); return $type->newId(); @@ -2202,26 +2233,26 @@ protected function _newId(array $primary) * @return \Cake\Datasource\EntityInterface|false * @throws \InvalidArgumentException When primary key data is missing. */ - protected function _update(EntityInterface $entity, array $data) + protected function _update(EntityInterface $entity, array $data): EntityInterface|false { $primaryColumns = (array)$this->getPrimaryKey(); $primaryKey = $entity->extract($primaryColumns); $data = array_diff_key($data, $primaryKey); - if (empty($data)) { + if (!$data) { return $entity; } - if (count($primaryColumns) === 0) { - $entityClass = get_class($entity); + if ($primaryColumns === []) { + $entityClass = $entity::class; $table = $this->getTable(); - $message = "Cannot update `$entityClass`. The `$table` has no primary key."; + $message = "Cannot update `{$entityClass}`. The `{$table}` has no primary key."; throw new InvalidArgumentException($message); } if (!$entity->has($primaryColumns)) { $message = 'All primary key value(s) are needed for updating, '; - $message .= get_class($entity) . ' is missing ' . implode(', ', $primaryColumns); + $message .= $entity::class . ' is missing ' . implode(', ', $primaryColumns); throw new InvalidArgumentException($message); } @@ -2230,10 +2261,7 @@ protected function _update(EntityInterface $entity, array $data) ->where($primaryKey) ->execute(); - $success = $statement->errorCode() === '00000' ? $entity : false; - $statement->closeCursor(); - - return $success; + return $statement->errorCode() === '00000' ? $entity : false; } /** @@ -2244,15 +2272,17 @@ protected function _update(EntityInterface $entity, array $data) * error. * * @param iterable<\Cake\Datasource\EntityInterface> $entities Entities to save. - * @param \Cake\ORM\SaveOptionsBuilder|\ArrayAccess|array $options Options used when calling Table::save() for each entity. + * @param array $options Options used when calling Table::save() for each entity. * @return iterable<\Cake\Datasource\EntityInterface>|false False on failure, entities list on success. * @throws \Exception */ - public function saveMany(iterable $entities, $options = []) - { + public function saveMany( + iterable $entities, + array $options = [], + ): iterable|false { try { return $this->_saveMany($entities, $options); - } catch (PersistenceFailedException $exception) { + } catch (PersistenceFailedException) { return false; } } @@ -2265,43 +2295,40 @@ public function saveMany(iterable $entities, $options = []) * error. * * @param iterable<\Cake\Datasource\EntityInterface> $entities Entities to save. - * @param \ArrayAccess|array $options Options used when calling Table::save() for each entity. + * @param array $options Options used when calling Table::save() for each entity. * @return iterable<\Cake\Datasource\EntityInterface> Entities list. * @throws \Exception * @throws \Cake\ORM\Exception\PersistenceFailedException If an entity couldn't be saved. */ - public function saveManyOrFail(iterable $entities, $options = []): iterable + public function saveManyOrFail(iterable $entities, array $options = []): iterable { return $this->_saveMany($entities, $options); } /** * @param iterable<\Cake\Datasource\EntityInterface> $entities Entities to save. - * @param \Cake\ORM\SaveOptionsBuilder|\ArrayAccess|array $options Options used when calling Table::save() for each entity. + * @param array $options Options used when calling Table::save() for each entity. * @throws \Cake\ORM\Exception\PersistenceFailedException If an entity couldn't be saved. * @throws \Exception If an entity couldn't be saved. * @return iterable<\Cake\Datasource\EntityInterface> Entities list. */ - protected function _saveMany(iterable $entities, $options = []): iterable - { - if ($options instanceof SaveOptionsBuilder) { - deprecationWarning('SaveOptionsBuilder is deprecated. Use a normal array for options instead.'); - $options = $options->toArray(); - } - + protected function _saveMany( + iterable $entities, + array $options = [], + ): iterable { $options = new ArrayObject( - (array)$options + [ + $options + [ 'atomic' => true, 'checkRules' => true, '_primary' => true, - ] + ], ); $options['_cleanOnSuccess'] = false; /** @var array $isNew */ $isNew = []; $cleanupOnFailure = function ($entities) use (&$isNew): void { - /** @var array<\Cake\Datasource\EntityInterface> $entities */ + /** @var iterable<\Cake\Datasource\EntityInterface> $entities */ foreach ($entities as $key => $entity) { if (isset($isNew[$key]) && $isNew[$key]) { $entity->unset($this->getPrimaryKey()); @@ -2315,6 +2342,8 @@ protected function _saveMany(iterable $entities, $options = []): iterable try { $this->getConnection() ->transactional(function () use ($entities, $options, &$isNew, &$failed) { + // Cache array cast since options are the same for each entity + $options = (array)$options; foreach ($entities as $key => $entity) { $isNew[$key] = $entity->isNew(); if ($this->save($entity, $options) === false) { @@ -2336,7 +2365,7 @@ protected function _saveMany(iterable $entities, $options = []): iterable throw new PersistenceFailedException($failed, ['saveMany']); } - $cleanupOnSuccess = function (EntityInterface $entity) use (&$cleanupOnSuccess) { + $cleanupOnSuccess = function (EntityInterface $entity) use (&$cleanupOnSuccess): void { $entity->clean(); $entity->setNew(false); @@ -2392,20 +2421,21 @@ protected function _saveMany(iterable $entities, $options = []): iterable * the options used in the delete operation. * * @param \Cake\Datasource\EntityInterface $entity The entity to remove. - * @param \ArrayAccess|array $options The options for the delete. + * @param array $options The options for the delete. * @return bool success */ - public function delete(EntityInterface $entity, $options = []): bool + public function delete(EntityInterface $entity, array $options = []): bool { - $options = new ArrayObject((array)$options + [ + $options = new ArrayObject($options + [ 'atomic' => true, 'checkRules' => true, '_primary' => true, ]); - $success = $this->_executeTransaction(function () use ($entity, $options) { - return $this->_processDelete($entity, $options); - }, $options['atomic']); + $success = $this->_executeTransaction( + fn() => $this->_processDelete($entity, $options), + $options['atomic'], + ); if ($success && $this->_transactionCommitted($options['atomic'], $options['_primary'])) { $this->dispatchEvent('Model.afterDeleteCommit', [ @@ -2425,12 +2455,12 @@ public function delete(EntityInterface $entity, $options = []): bool * error. * * @param iterable<\Cake\Datasource\EntityInterface> $entities Entities to delete. - * @param \ArrayAccess|array $options Options used when calling Table::save() for each entity. + * @param array $options Options used when calling Table::save() for each entity. * @return iterable<\Cake\Datasource\EntityInterface>|false Entities list * on success, false on failure. * @see \Cake\ORM\Table::delete() for options and events related to this method. */ - public function deleteMany(iterable $entities, $options = []) + public function deleteMany(iterable $entities, array $options = []): iterable|false { $failed = $this->_deleteMany($entities, $options); @@ -2449,12 +2479,12 @@ public function deleteMany(iterable $entities, $options = []) * error. * * @param iterable<\Cake\Datasource\EntityInterface> $entities Entities to delete. - * @param \ArrayAccess|array $options Options used when calling Table::save() for each entity. + * @param array $options Options used when calling Table::save() for each entity. * @return iterable<\Cake\Datasource\EntityInterface> Entities list. * @throws \Cake\ORM\Exception\PersistenceFailedException * @see \Cake\ORM\Table::delete() for options and events related to this method. */ - public function deleteManyOrFail(iterable $entities, $options = []): iterable + public function deleteManyOrFail(iterable $entities, array $options = []): iterable { $failed = $this->_deleteMany($entities, $options); @@ -2467,12 +2497,12 @@ public function deleteManyOrFail(iterable $entities, $options = []): iterable /** * @param iterable<\Cake\Datasource\EntityInterface> $entities Entities to delete. - * @param \ArrayAccess|array $options Options used. + * @param array $options Options used. * @return \Cake\Datasource\EntityInterface|null */ - protected function _deleteMany(iterable $entities, $options = []): ?EntityInterface + protected function _deleteMany(iterable $entities, array $options = []): ?EntityInterface { - $options = new ArrayObject((array)$options + [ + $options = new ArrayObject($options + [ 'atomic' => true, 'checkRules' => true, '_primary' => true, @@ -2505,19 +2535,19 @@ protected function _deleteMany(iterable $entities, $options = []): ?EntityInterf * has no primary key value, application rules checks failed or the delete was aborted by a callback. * * @param \Cake\Datasource\EntityInterface $entity The entity to remove. - * @param \ArrayAccess|array $options The options for the delete. + * @param array $options The options for the delete. * @return true * @throws \Cake\ORM\Exception\PersistenceFailedException * @see \Cake\ORM\Table::delete() */ - public function deleteOrFail(EntityInterface $entity, $options = []): bool + public function deleteOrFail(EntityInterface $entity, array $options = []): bool { $deleted = $this->delete($entity, $options); if ($deleted === false) { throw new PersistenceFailedException($entity, ['delete']); } - return $deleted; + return true; } /** @@ -2527,7 +2557,7 @@ public function deleteOrFail(EntityInterface $entity, $options = []): bool * dependent associations, and clear out join tables for BelongsToMany associations. * * @param \Cake\Datasource\EntityInterface $entity The entity to delete. - * @param \ArrayObject $options The options for the delete. + * @param \ArrayObject $options The options for the delete. * @throws \InvalidArgumentException if there are no primary key values of the * passed entity * @return bool success @@ -2559,7 +2589,7 @@ protected function _processDelete(EntityInterface $entity, ArrayObject $options) $success = $this->_associations->cascadeDelete( $entity, - ['_primary' => false] + $options->getArrayCopy() + ['_primary' => false] + $options->getArrayCopy(), ); if (!$success) { return $success; @@ -2597,49 +2627,138 @@ public function hasFinder(string $type): bool /** * Calls a finder method and applies it to the passed query. * + * @internal + * @template TSubject of \Cake\Datasource\EntityInterface|array * @param string $type Name of the finder to be called. - * @param \Cake\ORM\Query $query The query object to apply the finder options to. - * @param array $options List of options to pass to the finder. - * @return \Cake\ORM\Query + * @param \Cake\ORM\Query\SelectQuery $query The query object to apply the finder options to. + * @param mixed ...$args Arguments that match up to finder-specific parameters + * @return \Cake\ORM\Query\SelectQuery * @throws \BadMethodCallException * @uses findAll() * @uses findList() * @uses findThreaded() */ - public function callFinder(string $type, Query $query, array $options = []): Query + public function callFinder(string $type, SelectQuery $query, mixed ...$args): SelectQuery { - $query->applyOptions($options); - $options = $query->getOptions(); $finder = 'find' . $type; if (method_exists($this, $finder)) { - return $this->{$finder}($query, $options); + return $this->invokeFinder($this->{$finder}(...), $query, $args); } if ($this->_behaviors->hasFinder($type)) { - return $this->_behaviors->callFinder($type, [$query, $options]); + return $this->_behaviors->callFinder($type, $query, ...$args); } throw new BadMethodCallException(sprintf( - 'Unknown finder method "%s" on %s.', + 'Unknown finder method `%s` on `%s`.', $type, - static::class + static::class, )); } + /** + * @internal + * @template TSubject of \Cake\Datasource\EntityInterface|array + * @param \Closure $callable Callable. + * @param \Cake\ORM\Query\SelectQuery $query The query object. + * @param array $args Arguments for the callable. + * @return \Cake\ORM\Query\SelectQuery + */ + public function invokeFinder(Closure $callable, SelectQuery $query, array $args): SelectQuery + { + $reflected = new ReflectionFunction($callable); + $params = $reflected->getParameters(); + $secondParam = $params[1] ?? null; + + $secondParamType = $secondParam?->getType(); + $secondParamTypeName = $secondParamType instanceof ReflectionNamedType ? $secondParamType->getName() : null; + + $secondParamIsOptions = ( + count($params) === 2 && + $secondParam?->name === 'options' && + !$secondParam->isVariadic() && + ($secondParamType === null || $secondParamTypeName === 'array') + ); + + if (($args === [] || isset($args[0])) && $secondParamIsOptions) { + // Backwards compatibility of 4.x style finders + // with signature `findFoo(SelectQuery $query, array $options)` + // called as `find('foo')` or `find('foo', [..])` + if (isset($args[0])) { + deprecationWarning( + '5.0.0', + 'Calling finders with options arrays is deprecated.' + . ' Update your finder methods to used named arguments instead.', + ); + $args = $args[0]; + } + $query->applyOptions($args); + + return $callable($query, $query->getOptions()); + } + + // Backwards compatibility for 4.x style finders with signatures like + // `findFoo(SelectQuery $query, array $options)` called as + // `find('foo', key: $value)`. + if (!isset($args[0]) && $secondParamIsOptions) { + $query->applyOptions($args); + + return $callable($query, $query->getOptions()); + } + + // Backwards compatibility for core finders like `findList()` called in 4.x + // style with an array `find('list', ['valueField' => 'foo'])` instead of + // `find('list', valueField: 'foo')` + if (isset($args[0]) && is_array($args[0]) && $secondParamTypeName !== 'array') { + deprecationWarning( + '5.0.0', + "Calling `{$reflected->getName()}` finder with options array is deprecated." + . ' Use named arguments instead.', + ); + + $args = $args[0]; + } + + if ($args) { + $query->applyOptions($args); + // Fetch custom args without the query options. + $args = array_intersect_key($args, $query->getOptions()); + + unset($params[0]); + $lastParam = end($params); + reset($params); + + if ($lastParam === false || !$lastParam->isVariadic()) { + $paramNames = []; + foreach ($params as $param) { + $paramNames[] = $param->getName(); + } + + foreach ($args as $key => $value) { + if (is_string($key) && !in_array($key, $paramNames, true)) { + unset($args[$key]); + } + } + } + } + + return $callable($query, ...$args); + } + /** * Provides the dynamic findBy and findAllBy methods. * * @param string $method The method name that was fired. * @param array $args List of arguments passed to the function. - * @return \Cake\ORM\Query + * @return \Cake\ORM\Query\SelectQuery * @throws \BadMethodCallException when there are missing arguments, or when * and & or are combined. */ - protected function _dynamicFinder(string $method, array $args) + protected function _dynamicFinder(string $method, array $args): SelectQuery { $method = Inflector::underscore($method); preg_match('/^find_([\w]+)_by_/', $method, $matches); - if (empty($matches)) { + if (!$matches) { // find_by_ is 8 characters. $fields = substr($method, 8); $findType = 'all'; @@ -2647,16 +2766,16 @@ protected function _dynamicFinder(string $method, array $args) $fields = substr($method, strlen($matches[0])); $findType = Inflector::variable($matches[1]); } - $hasOr = strpos($fields, '_or_'); - $hasAnd = strpos($fields, '_and_'); + $hasOr = str_contains($fields, '_or_'); + $hasAnd = str_contains($fields, '_and_'); - $makeConditions = function ($fields, $args) { + $makeConditions = function ($fields, $args): array { $conditions = []; if (count($args) < count($fields)) { throw new BadMethodCallException(sprintf( 'Not enough arguments for magic finder. Got %s required %s', count($args), - count($fields) + count($fields), )); } foreach ($fields as $field) { @@ -2666,15 +2785,15 @@ protected function _dynamicFinder(string $method, array $args) return $conditions; }; - if ($hasOr !== false && $hasAnd !== false) { + if ($hasOr && $hasAnd) { throw new BadMethodCallException( - 'Cannot mix "and" & "or" in a magic finder. Use find() instead.' + 'Cannot mix "and" & "or" in a magic finder. Use find() instead.', ); } if ($hasOr === false && $hasAnd === false) { $conditions = $makeConditions([$fields], $args); - } elseif ($hasOr !== false) { + } elseif ($hasOr) { $fields = explode('_or_', $fields); $conditions = [ 'OR' => $makeConditions($fields, $args), @@ -2684,9 +2803,7 @@ protected function _dynamicFinder(string $method, array $args) $conditions = $makeConditions($fields, $args); } - return $this->find($findType, [ - 'conditions' => $conditions, - ]); + return $this->find($findType, conditions: $conditions); } /** @@ -2700,7 +2817,7 @@ protected function _dynamicFinder(string $method, array $args) * @return mixed * @throws \BadMethodCallException */ - public function __call($method, $args) + public function __call(string $method, array $args): mixed { if ($this->_behaviors->hasMethod($method)) { return $this->_behaviors->call($method, $args); @@ -2710,7 +2827,7 @@ public function __call($method, $args) } throw new BadMethodCallException( - sprintf('Unknown method "%s" called on %s', $method, static::class) + sprintf('Unknown method `%s` called on `%s`', $method, static::class), ); } @@ -2720,18 +2837,18 @@ public function __call($method, $args) * * @param string $property the association name * @return \Cake\ORM\Association - * @throws \RuntimeException if no association with such name exists + * @throws \Cake\Database\Exception\DatabaseException if no association with such name exists */ - public function __get($property) + public function __get(string $property): Association { $association = $this->_associations->get($property); if (!$association) { - throw new RuntimeException(sprintf( + throw new DatabaseException(sprintf( 'Undefined property `%s`. ' . 'You have not defined the `%s` association on `%s`.', $property, $property, - static::class + static::class, )); } @@ -2745,7 +2862,7 @@ public function __get($property) * @param string $property the association name * @return bool */ - public function __isset($property) + public function __isset(string $property): bool { return $this->_associations->has($property); } @@ -2754,7 +2871,7 @@ public function __isset($property) * Get the object used to marshal/convert array data into objects. * * Override this method if you want a table object to use custom - * marshalling logic. + * marshaling logic. * * @return \Cake\ORM\Marshaller * @see \Cake\ORM\Marshaller @@ -2837,7 +2954,7 @@ public function newEmptyEntity(): EntityInterface */ public function newEntity(array $data, array $options = []): EntityInterface { - $options['associated'] = $options['associated'] ?? $this->_associations->keys(); + $options['associated'] ??= $this->_associations->keys(); return $this->marshaller()->one($data, $options); } @@ -2876,7 +2993,7 @@ public function newEntity(array $data, array $options = []): EntityInterface */ public function newEntities(array $data, array $options = []): array { - $options['associated'] = $options['associated'] ?? $this->_associations->keys(); + $options['associated'] ??= $this->_associations->keys(); return $this->marshaller()->many($data, $options); } @@ -2934,7 +3051,7 @@ public function newEntities(array $data, array $options = []): array */ public function patchEntity(EntityInterface $entity, array $data, array $options = []): EntityInterface { - $options['associated'] = $options['associated'] ?? $this->_associations->keys(); + $options['associated'] ??= $this->_associations->keys(); return $this->marshaller()->merge($entity, $data, $options); } @@ -2944,7 +3061,7 @@ public function patchEntity(EntityInterface $entity, array $data, array $options * * Those entries in `$entities` that cannot be matched to any record in * `$data` will be discarded. Records in `$data` that could not be matched will - * be marshalled as a new entity. + * be marshaled as a new entity. * * When merging HasMany or BelongsToMany associations, all the entities in the * `$data` array will appear, those that can be matched by primary key will get @@ -2972,7 +3089,7 @@ public function patchEntity(EntityInterface $entity, array $data, array $options */ public function patchEntities(iterable $entities, array $data, array $options = []): array { - $options['associated'] = $options['associated'] ?? $this->_associations->keys(); + $options['associated'] ??= $this->_associations->keys(); return $this->marshaller()->mergeMany($entities, $data, $options); } @@ -3011,22 +3128,22 @@ public function patchEntities(iterable $entities, array $data, array $options = * @param array|null $context Either the validation context or null. * @return bool True if the value is unique, or false if a non-scalar, non-unique value was given. */ - public function validateUnique($value, array $options, ?array $context = null): bool + public function validateUnique(mixed $value, array $options = [], ?array $context = null): bool { if ($context === null) { $context = $options; } - $entity = new Entity( + $entity = new ($this->getEntityClass())( $context['data'], [ 'useSetters' => false, 'markNew' => $context['newRecord'], 'source' => $this->getRegistryAlias(), - ] + ], ); $fields = array_merge( [$context['field']], - isset($options['scope']) ? (array)$options['scope'] : [] + isset($options['scope']) ? (array)$options['scope'] : [], ); $values = $entity->extract($fields); foreach ($values as $field) { @@ -3035,7 +3152,6 @@ public function validateUnique($value, array $options, ?array $context = null): } } $class = static::IS_UNIQUE_CLASS; - /** @var \Cake\ORM\Rule\IsUnique $rule */ $rule = new $class($fields, $options); return $rule($entity, ['repository' => $this]); @@ -3106,18 +3222,6 @@ public function buildRules(RulesChecker $rules): RulesChecker return $rules; } - /** - * Gets a SaveOptionsBuilder instance. - * - * @param array $options Options to parse by the builder. - * @return \Cake\ORM\SaveOptionsBuilder - * @deprecated 4.4.0 Use a normal array for options instead. - */ - public function getSaveOptionsBuilder(array $options = []): SaveOptionsBuilder - { - return new SaveOptionsBuilder($this, $options); - } - /** * Loads the specified associations in the passed entity or list of entities * by executing extra queries in the database and merging the results in the @@ -3148,7 +3252,7 @@ public function getSaveOptionsBuilder(array $options = []): SaveOptionsBuilder * @see \Cake\ORM\Query::contain() * @return \Cake\Datasource\EntityInterface|array<\Cake\Datasource\EntityInterface> */ - public function loadInto($entities, array $contain) + public function loadInto(EntityInterface|array $entities, array $contain): EntityInterface|array { return (new LazyEagerLoader())->loadInto($entities, $contain, $this); } @@ -3167,7 +3271,7 @@ protected function validationMethodExists(string $name): bool * * @return array */ - public function __debugInfo() + public function __debugInfo(): array { $conn = $this->getConnection(); @@ -3176,8 +3280,10 @@ public function __debugInfo() 'table' => $this->getTable(), 'alias' => $this->getAlias(), 'entityClass' => $this->getEntityClass(), - 'associations' => $this->_associations->keys(), - 'behaviors' => $this->_behaviors->loaded(), + /** @phpstan-ignore isset.initializedProperty */ + 'associations' => isset($this->_associations) ? $this->_associations->keys() : [], + /** @phpstan-ignore isset.initializedProperty */ + 'behaviors' => isset($this->_behaviors) ? $this->_behaviors->loaded() : [], 'defaultConnection' => static::defaultConnectionName(), 'connectionName' => $conn->configName(), ]; diff --git a/app/vendor/cakephp/cakephp/src/ORM/TableRegistry.php b/app/vendor/cakephp/cakephp/src/ORM/TableRegistry.php index 7119be7d5..1c6671e1b 100644 --- a/app/vendor/cakephp/cakephp/src/ORM/TableRegistry.php +++ b/app/vendor/cakephp/cakephp/src/ORM/TableRegistry.php @@ -34,9 +34,6 @@ * * ``` * TableRegistry::getTableLocator()->setConfig('Users', ['table' => 'my_users']); - * - * // Prior to 3.6.0 - * TableRegistry::config('Users', ['table' => 'my_users']); * ``` * * Configuration data is stored *per alias* if you use the same table with @@ -51,9 +48,6 @@ * * ``` * $table = TableRegistry::getTableLocator()->get('Users', $config); - * - * // Prior to 3.6.0 - * $table = TableRegistry::get('Users', $config); * ``` */ class TableRegistry @@ -79,67 +73,4 @@ public static function setTableLocator(LocatorInterface $tableLocator): void { FactoryLocator::add('Table', $tableLocator); } - - /** - * Get a table instance from the registry. - * - * See options specification in {@link TableLocator::get()}. - * - * @param string $alias The alias name you want to get. - * @param array $options The options you want to build the table with. - * @return \Cake\ORM\Table - * @deprecated 3.6.0 Use {@link \Cake\ORM\Locator\LocatorAwareTrait::fetchTable()} instead. Will be removed in 5.0. - */ - public static function get(string $alias, array $options = []): Table - { - return static::getTableLocator()->get($alias, $options); - } - - /** - * Check to see if an instance exists in the registry. - * - * @param string $alias The alias to check for. - * @return bool - * @deprecated 3.6.0 Use {@link \Cake\ORM\Locator\TableLocator::exists()} instead. Will be removed in 5.0 - */ - public static function exists(string $alias): bool - { - return static::getTableLocator()->exists($alias); - } - - /** - * Set an instance. - * - * @param string $alias The alias to set. - * @param \Cake\ORM\Table $object The table to set. - * @return \Cake\ORM\Table - * @deprecated 3.6.0 Use {@link \Cake\ORM\Locator\TableLocator::set()} instead. Will be removed in 5.0 - */ - public static function set(string $alias, Table $object): Table - { - return static::getTableLocator()->set($alias, $object); - } - - /** - * Removes an instance from the registry. - * - * @param string $alias The alias to remove. - * @return void - * @deprecated 3.6.0 Use {@link \Cake\ORM\Locator\TableLocator::remove()} instead. Will be removed in 5.0 - */ - public static function remove(string $alias): void - { - static::getTableLocator()->remove($alias); - } - - /** - * Clears the registry of configuration and instances. - * - * @return void - * @deprecated 3.6.0 Use {@link \Cake\ORM\Locator\TableLocator::clear()} instead. Will be removed in 5.0 - */ - public static function clear(): void - { - static::getTableLocator()->clear(); - } } diff --git a/app/vendor/cakephp/cakephp/src/ORM/bootstrap.php b/app/vendor/cakephp/cakephp/src/ORM/bootstrap.php new file mode 100644 index 000000000..8b23a2d4e --- /dev/null +++ b/app/vendor/cakephp/cakephp/src/ORM/bootstrap.php @@ -0,0 +1,21 @@ +=7.4.0", - "cakephp/collection": "^4.0", - "cakephp/core": "^4.0", - "cakephp/datasource": "^4.0", - "cakephp/database": "^4.0", - "cakephp/event": "^4.0", - "cakephp/utility": "^4.0", - "cakephp/validation": "^4.0" + "php": ">=8.1", + "cakephp/collection": "5.2.*@dev", + "cakephp/core": "5.2.*@dev", + "cakephp/datasource": "5.2.*@dev", + "cakephp/database": "5.2.*@dev", + "cakephp/event": "5.2.*@dev", + "cakephp/utility": "5.2.*@dev", + "cakephp/validation": "5.2.*@dev" }, - "suggest": { - "cakephp/cache": "If you decide to use Query caching.", - "cakephp/i18n": "If you are using Translate/TimestampBehavior or Chronos types." + "require-dev": { + "cakephp/cache": "5.2.*@dev", + "cakephp/i18n": "5.2.*@dev" }, "autoload": { "psr-4": { "Cake\\ORM\\": "." + }, + "files": [ + "bootstrap.php" + ] + }, + "suggest": { + "cakephp/cache": "If you decide to use Query caching.", + "cakephp/i18n": "If you are using Translate/TimestampBehavior or Chronos types." + }, + "prefer-stable": true, + "extra": { + "branch-alias": { + "dev-5.x": "5.2.x-dev" } } } diff --git a/app/vendor/cakephp/cakephp/src/Routing/Asset.php b/app/vendor/cakephp/cakephp/src/Routing/Asset.php index ae8bd3bbe..a76dcbe5d 100644 --- a/app/vendor/cakephp/cakephp/src/Routing/Asset.php +++ b/app/vendor/cakephp/cakephp/src/Routing/Asset.php @@ -31,7 +31,7 @@ class Asset * * @var string */ - protected static $inflectionType = 'underscore'; + protected static string $inflectionType = 'underscore'; /** * Set inflection type to use when inflecting plugin/theme name. @@ -150,19 +150,20 @@ public static function url(string $path, array $options = []): string return $path; } - if (strpos($path, '://') !== false || preg_match('/^[a-z]+:/i', $path)) { + if (str_contains($path, '://') || preg_match('/^[a-z]+:/i', $path)) { return ltrim(Router::url($path), '/'); } + $plugin = null; if (!array_key_exists('plugin', $options) || $options['plugin'] !== false) { [$plugin, $path] = static::pluginSplit($path); } - if (!empty($options['pathPrefix']) && $path[0] !== '/') { + if (!empty($options['pathPrefix']) && !str_starts_with($path, '/')) { $pathPrefix = $options['pathPrefix']; $placeHolderVal = ''; if (!empty($options['theme'])) { $placeHolderVal = static::inflectString($options['theme']) . '/'; - } elseif (isset($plugin)) { + } elseif ($plugin !== null) { $placeHolderVal = static::inflectString($plugin) . '/'; } @@ -170,8 +171,8 @@ public static function url(string $path, array $options = []): string } if ( !empty($options['ext']) && - strpos($path, '?') === false && - substr($path, -strlen($options['ext'])) !== $options['ext'] + !str_contains($path, '?') && + !str_ends_with($path, $options['ext']) ) { $path .= $options['ext']; } @@ -181,7 +182,7 @@ public static function url(string $path, array $options = []): string return Router::url($path); } - if (isset($plugin)) { + if ($plugin !== null) { $path = static::inflectString($plugin) . '/' . $path; } @@ -191,7 +192,7 @@ public static function url(string $path, array $options = []): string } $webPath = static::assetTimestamp( static::webroot($path, $options), - $optionTimestamp + $optionTimestamp, ); $path = static::encodeUrl($webPath); @@ -215,7 +216,7 @@ public static function url(string $path, array $options = []): string protected static function encodeUrl(string $url): string { $path = parse_url($url, PHP_URL_PATH); - if ($path === false) { + if ($path === false || $path === null) { $path = $url; } @@ -232,24 +233,22 @@ protected static function encodeUrl(string $url): string * a timestamp will be added. * * @param string $path The file path to timestamp, the path must be inside `App.wwwRoot` in Configure. - * @param string|bool $timestamp If set will overrule the value of `Asset.timestamp` in Configure. + * @param string|bool|null $timestamp If set will overrule the value of `Asset.timestamp` in Configure. * @return string Path with a timestamp added, or not. */ - public static function assetTimestamp(string $path, $timestamp = null): string + public static function assetTimestamp(string $path, string|bool|null $timestamp = null): string { - if (strpos($path, '?') !== false) { + if (str_contains($path, '?')) { return $path; } - if ($timestamp === null) { - $timestamp = Configure::read('Asset.timestamp'); - } + $timestamp ??= Configure::read('Asset.timestamp'); $timestampEnabled = $timestamp === 'force' || ($timestamp === true && Configure::read('debug')); if ($timestampEnabled) { - $filepath = preg_replace( + $filepath = (string)preg_replace( '/^' . preg_quote(static::requestWebroot(), '/') . '/', '', - urldecode($path) + urldecode($path), ); $webrootPath = Configure::read('App.wwwRoot') . str_replace('/', DIRECTORY_SEPARATOR, $filepath); if (is_file($webrootPath)) { @@ -317,7 +316,7 @@ public static function webroot(string $file, array $options = []): string } } } - if (strpos($webPath, '//') !== false) { + if (str_contains($webPath, '//')) { return str_replace('//', '/', $webPath . $asset[1]); } @@ -357,7 +356,7 @@ protected static function requestWebroot(): string * * @param string $name The name you want to plugin split. * @return array Array with 2 indexes. 0 => plugin name, 1 => filename. - * @psalm-return array{string|null, string} + * @phpstan-return array{string|null, string} */ protected static function pluginSplit(string $name): array { diff --git a/app/vendor/cakephp/cakephp/src/Routing/Exception/DuplicateNamedRouteException.php b/app/vendor/cakephp/cakephp/src/Routing/Exception/DuplicateNamedRouteException.php index d4ccf2200..6ef184978 100644 --- a/app/vendor/cakephp/cakephp/src/Routing/Exception/DuplicateNamedRouteException.php +++ b/app/vendor/cakephp/cakephp/src/Routing/Exception/DuplicateNamedRouteException.php @@ -25,7 +25,7 @@ class DuplicateNamedRouteException extends CakeException /** * @inheritDoc */ - protected $_messageTemplate = 'A route named "%s" has already been connected to "%s".'; + protected string $_messageTemplate = 'A route named `%s` has already been connected to `%s`.'; /** * Constructor. @@ -35,7 +35,7 @@ class DuplicateNamedRouteException extends CakeException * @param int|null $code The code of the error, is also the HTTP status code for the error. Defaults to 404. * @param \Throwable|null $previous the previous exception. */ - public function __construct($message, ?int $code = 404, ?Throwable $previous = null) + public function __construct(array|string $message, ?int $code = 404, ?Throwable $previous = null) { if (is_array($message) && isset($message['message'])) { $this->_messageTemplate = $message['message']; diff --git a/app/vendor/cakephp/cakephp/src/Routing/Exception/FailedRouteCacheException.php b/app/vendor/cakephp/cakephp/src/Routing/Exception/FailedRouteCacheException.php deleted file mode 100644 index 44699962e..000000000 --- a/app/vendor/cakephp/cakephp/src/Routing/Exception/FailedRouteCacheException.php +++ /dev/null @@ -1,26 +0,0 @@ -getUri()->getPath(); - if (strpos($url, '..') !== false || strpos($url, '.') === false) { + if (str_contains($url, '..') || !str_contains($url, '.')) { return $handler->handle($request); } - if (strpos($url, '/.') !== false) { + if (str_contains($url, '/.')) { return $handler->handle($request); } @@ -85,7 +87,7 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface ->withStatus(304) ->withHeader( 'Last-Modified', - date(DATE_RFC850, $modifiedTime) + date(DATE_RFC850, $modifiedTime), ); } @@ -117,7 +119,7 @@ protected function isNotModified(ServerRequestInterface $request, SplFileInfo $f */ protected function _getAssetFile(string $url): ?string { - $parts = explode('/', ltrim($url, '/')); + $parts = explode('/', ltrim($url, '/'), 3); $pluginPart = []; for ($i = 0; $i < 2; $i++) { if (!isset($parts[$i])) { @@ -146,20 +148,27 @@ protected function _getAssetFile(string $url): ?string */ protected function deliverAsset(ServerRequestInterface $request, SplFileInfo $file): Response { - $stream = new Stream(fopen($file->getPathname(), 'rb')); + $resource = fopen($file->getPathname(), 'rb'); + if ($resource === false) { + throw new CakeException(sprintf('Cannot open resource `%s`', $file->getPathname())); + } + $stream = new Stream($resource); $response = new Response(['stream' => $stream]); - $contentType = (array)($response->getMimeType($file->getExtension()) ?: 'application/octet-stream'); + $contentType = MimeType::getMimeTypeForFile($file->getRealPath()); $modified = $file->getMTime(); $expire = strtotime($this->cacheTime); + if ($expire === false) { + throw new CakeException(sprintf('Invalid cache time value `%s`', $this->cacheTime)); + } $maxAge = $expire - time(); return $response - ->withHeader('Content-Type', $contentType[0]) + ->withHeader('Content-Type', $contentType) ->withHeader('Cache-Control', 'public,max-age=' . $maxAge) - ->withHeader('Date', gmdate(DATE_RFC7231, time())) - ->withHeader('Last-Modified', gmdate(DATE_RFC7231, $modified)) - ->withHeader('Expires', gmdate(DATE_RFC7231, $expire)); + ->withHeader('Date', gmdate(CAKE_DATE_RFC7231, time())) + ->withHeader('Last-Modified', gmdate(CAKE_DATE_RFC7231, $modified)) + ->withHeader('Expires', gmdate(CAKE_DATE_RFC7231, $expire)); } } diff --git a/app/vendor/cakephp/cakephp/src/Routing/Middleware/RoutingMiddleware.php b/app/vendor/cakephp/cakephp/src/Routing/Middleware/RoutingMiddleware.php index a292c0f43..a5232e573 100644 --- a/app/vendor/cakephp/cakephp/src/Routing/Middleware/RoutingMiddleware.php +++ b/app/vendor/cakephp/cakephp/src/Routing/Middleware/RoutingMiddleware.php @@ -16,25 +16,19 @@ */ namespace Cake\Routing\Middleware; -use Cake\Cache\Cache; -use Cake\Cache\Exception\InvalidArgumentException; use Cake\Core\ContainerApplicationInterface; use Cake\Core\PluginApplicationInterface; use Cake\Http\Exception\RedirectException; use Cake\Http\MiddlewareQueue; use Cake\Http\Runner; -use Cake\Routing\Exception\FailedRouteCacheException; -use Cake\Routing\Exception\RedirectException as DeprecatedRedirectException; -use Cake\Routing\RouteCollection; +use Cake\Http\ServerRequest; use Cake\Routing\Router; use Cake\Routing\RoutingApplicationInterface; -use Exception; use Laminas\Diactoros\Response\RedirectResponse; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; -use function Cake\Core\deprecationWarning; /** * Applies routing rules to the request and creates the controller @@ -54,92 +48,30 @@ class RoutingMiddleware implements MiddlewareInterface * * @var \Cake\Routing\RoutingApplicationInterface */ - protected $app; - - /** - * The cache configuration name to use for route collection caching, - * null to disable caching - * - * @var string|null - */ - protected $cacheConfig; + protected RoutingApplicationInterface $app; /** * Constructor * * @param \Cake\Routing\RoutingApplicationInterface $app The application instance that routes are defined on. - * @param string|null $cacheConfig The cache config name to use or null to disable routes cache */ - public function __construct(RoutingApplicationInterface $app, ?string $cacheConfig = null) + public function __construct(RoutingApplicationInterface $app) { - if ($cacheConfig !== null) { - deprecationWarning( - 'Use of routing cache is deprecated and will be removed in 5.0. ' . - 'Upgrade to the new `CakeDC/CachedRouting` plugin. ' . - 'See https://github.com/CakeDC/cakephp-cached-routing' - ); - } $this->app = $app; - $this->cacheConfig = $cacheConfig; } /** - * Trigger the application's routes() hook if the application exists and Router isn't initialized. - * Uses the routes cache if enabled via configuration param "Router.cache" - * - * If the middleware is created without an Application, routes will be - * loaded via the automatic route loading that pre-dates the routes() hook. + * Trigger the application's and plugin's routes() hook. * * @return void */ protected function loadRoutes(): void - { - $routeCollection = $this->buildRouteCollection(); - Router::setRouteCollection($routeCollection); - } - - /** - * Check if route cache is enabled and use the configured Cache to 'remember' the route collection - * - * @return \Cake\Routing\RouteCollection - */ - protected function buildRouteCollection(): RouteCollection - { - if (Cache::enabled() && $this->cacheConfig !== null) { - try { - return Cache::remember(static::ROUTE_COLLECTION_CACHE_KEY, function () { - return $this->prepareRouteCollection(); - }, $this->cacheConfig); - } catch (InvalidArgumentException $e) { - throw $e; - } catch (Exception $e) { - throw new FailedRouteCacheException( - 'Unable to cache route collection. Cached routes must be serializable. Check for route-specific - middleware or other unserializable settings in your routes. The original exception message can - show what type of object failed to serialize.', - null, - $e - ); - } - } - - return $this->prepareRouteCollection(); - } - - /** - * Generate the route collection using the builder - * - * @return \Cake\Routing\RouteCollection - */ - protected function prepareRouteCollection(): RouteCollection { $builder = Router::createRouteBuilder('/'); $this->app->routes($builder); if ($this->app instanceof PluginApplicationInterface) { $this->app->pluginRoutes($builder); } - - return Router::getRouteCollection(); } /** @@ -156,6 +88,7 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface { $this->loadRoutes(); try { + assert($request instanceof ServerRequest); Router::setRequest($request); $params = (array)$request->getAttribute('params', []); $middleware = []; @@ -168,20 +101,15 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface unset($params['_middleware'], $params['_route']); $request = $request->withAttribute('route', $route); - /** @var \Cake\Http\ServerRequest $request */ $request = $request->withAttribute('params', $params); + Router::setRequest($request); } } catch (RedirectException $e) { return new RedirectResponse( $e->getMessage(), $e->getCode(), - $e->getHeaders() - ); - } catch (DeprecatedRedirectException $e) { - return new RedirectResponse( - $e->getMessage(), - $e->getCode() + $e->getHeaders(), ); } $matching = Router::getRouteCollection()->getMiddleware($middleware); diff --git a/app/vendor/cakephp/cakephp/src/Routing/Route/DashedRoute.php b/app/vendor/cakephp/cakephp/src/Routing/Route/DashedRoute.php index 7122b8aea..c406e32b3 100644 --- a/app/vendor/cakephp/cakephp/src/Routing/Route/DashedRoute.php +++ b/app/vendor/cakephp/cakephp/src/Routing/Route/DashedRoute.php @@ -33,7 +33,7 @@ class DashedRoute extends Route * * @var array|null */ - protected $_inflectedDefaults; + protected ?array $_inflectedDefaults = null; /** * Camelizes the previously dashed plugin route taking into account plugin vendors @@ -44,7 +44,7 @@ class DashedRoute extends Route protected function _camelizePlugin(string $plugin): string { $plugin = str_replace('-', '_', $plugin); - if (strpos($plugin, '/') === false) { + if (!str_contains($plugin, '/')) { return Inflector::camelize($plugin); } [$vendor, $plugin] = explode('/', $plugin, 2); @@ -77,7 +77,7 @@ public function parse(string $url, string $method = ''): ?array $params['action'] = Inflector::variable(str_replace( '-', '_', - $params['action'] + $params['action'], )); } diff --git a/app/vendor/cakephp/cakephp/src/Routing/Route/EntityRoute.php b/app/vendor/cakephp/cakephp/src/Routing/Route/EntityRoute.php index 4a721724b..6e8d76808 100644 --- a/app/vendor/cakephp/cakephp/src/Routing/Route/EntityRoute.php +++ b/app/vendor/cakephp/cakephp/src/Routing/Route/EntityRoute.php @@ -17,8 +17,7 @@ namespace Cake\Routing\Route; use ArrayAccess; -use RuntimeException; -use function Cake\Core\getTypeName; +use Cake\Core\Exception\CakeException; /** * Matches entities to routes @@ -44,7 +43,7 @@ class EntityRoute extends Route */ public function match(array $url, array $context = []): ?string { - if (empty($this->_compiledRoute)) { + if (!$this->_compiledRoute) { $this->compile(); } @@ -66,17 +65,17 @@ public function match(array $url, array $context = []): ?string * Checks that we really deal with an entity object * * @throws \RuntimeException - * @param \ArrayAccess|array $entity Entity value from the URL options + * @param mixed $entity Entity value from the URL options * @return void */ - protected function _checkEntity($entity): void + protected function _checkEntity(mixed $entity): void { if (!$entity instanceof ArrayAccess && !is_array($entity)) { - throw new RuntimeException(sprintf( + throw new CakeException(sprintf( 'Route `%s` expects the URL option `_entity` to be an array or object implementing \ArrayAccess, ' . 'but `%s` passed.', $this->template, - getTypeName($entity) + get_debug_type($entity), )); } } diff --git a/app/vendor/cakephp/cakephp/src/Routing/Route/InflectedRoute.php b/app/vendor/cakephp/cakephp/src/Routing/Route/InflectedRoute.php index 2c819d078..fa53bdf36 100644 --- a/app/vendor/cakephp/cakephp/src/Routing/Route/InflectedRoute.php +++ b/app/vendor/cakephp/cakephp/src/Routing/Route/InflectedRoute.php @@ -32,7 +32,7 @@ class InflectedRoute extends Route * * @var array|null */ - protected $_inflectedDefaults; + protected ?array $_inflectedDefaults = null; /** * Parses a string URL into an array. If it matches, it will convert the prefix, controller and @@ -52,7 +52,7 @@ public function parse(string $url, string $method = ''): ?array $params['controller'] = Inflector::camelize($params['controller']); } if (!empty($params['plugin'])) { - if (strpos($params['plugin'], '/') === false) { + if (!str_contains($params['plugin'], '/')) { $params['plugin'] = Inflector::camelize($params['plugin']); } else { [$vendor, $plugin] = explode('/', $params['plugin'], 2); diff --git a/app/vendor/cakephp/cakephp/src/Routing/Route/RedirectRoute.php b/app/vendor/cakephp/cakephp/src/Routing/Route/RedirectRoute.php index 106c5a549..4621c332f 100644 --- a/app/vendor/cakephp/cakephp/src/Routing/Route/RedirectRoute.php +++ b/app/vendor/cakephp/cakephp/src/Routing/Route/RedirectRoute.php @@ -24,7 +24,7 @@ * are useful when you want to have Routing layer redirects occur in your * application, for when URLs move. * - * Redirection is signalled by an exception that halts route matching and + * Redirection is signaled by an exception that halts route matching and * defines the redirect URL and status code. */ class RedirectRoute extends Route @@ -34,7 +34,7 @@ class RedirectRoute extends Route * * @var array */ - public $redirect; + public array $redirect; /** * Constructor diff --git a/app/vendor/cakephp/cakephp/src/Routing/Route/Route.php b/app/vendor/cakephp/cakephp/src/Routing/Route/Route.php index 15e6c07cb..2f7606b1b 100644 --- a/app/vendor/cakephp/cakephp/src/Routing/Route/Route.php +++ b/app/vendor/cakephp/cakephp/src/Routing/Route/Route.php @@ -16,10 +16,10 @@ */ namespace Cake\Routing\Route; +use Cake\Core\Exception\CakeException; use Cake\Http\Exception\BadRequestException; use InvalidArgumentException; use Psr\Http\Message\ServerRequestInterface; -use function Cake\Core\deprecationWarning; /** * A single Route used by the Router to connect requests to @@ -36,28 +36,28 @@ class Route * * @var array */ - public $keys = []; + public array $keys = []; /** * An array of additional parameters for the Route. * - * @var array + * @var array */ - public $options = []; + public array $options = []; /** * Default parameters for a Route * * @var array */ - public $defaults = []; + public array $defaults = []; /** * The routes template string. * * @var string */ - public $template; + public string $template; /** * Is this route a greedy route? Greedy routes have a `/*` in their @@ -65,42 +65,35 @@ class Route * * @var bool */ - protected $_greedy = false; + protected bool $_greedy = false; /** * The compiled route regular expression * * @var string|null */ - protected $_compiledRoute; + protected ?string $_compiledRoute = null; /** * The name for a route. Fetch with Route::getName(); * * @var string|null */ - protected $_name; + protected ?string $_name = null; /** * List of connected extensions for this route. * * @var array */ - protected $_extensions = []; + protected array $_extensions = []; /** * List of middleware that should be applied. * * @var array */ - protected $middleware = []; - - /** - * Track whether brace keys `{var}` were used. - * - * @var bool - */ - protected $braceKeys = true; + protected array $middleware = []; /** * Valid HTTP methods. @@ -110,7 +103,7 @@ class Route public const VALID_METHODS = ['GET', 'PUT', 'POST', 'PATCH', 'DELETE', 'OPTIONS', 'HEAD']; /** - * Regex for matching braced placholders in route template. + * Regex for matching braced placeholders in route template. * * @var string */ @@ -139,6 +132,21 @@ class Route */ public function __construct(string $template, array $defaults = [], array $options = []) { + $checker = function () use ($defaults): bool { + foreach (['plugin', 'prefix', 'controller', 'action'] as $key) { + if (isset($defaults[$key]) && !is_string($defaults[$key])) { + throw new CakeException( + 'Value for `' . $key . '` in $defaults when connecting routes' + . ' must be of type `string` or `null`', + ); + } + } + + return true; + }; + + assert($checker()); + $this->template = $template; $this->defaults = $defaults; $this->options = $options + ['_ext' => [], '_middleware' => []]; @@ -195,7 +203,7 @@ public function setMethods(array $methods) * @return array|string * @throws \InvalidArgumentException When methods are not in `VALID_METHODS` list. */ - protected function normalizeAndValidateMethods($methods) + protected function normalizeAndValidateMethods(array|string $methods): array|string { $methods = is_array($methods) ? array_map('strtoupper', $methods) @@ -204,7 +212,7 @@ protected function normalizeAndValidateMethods($methods) $diff = array_diff((array)$methods, static::VALID_METHODS); if ($diff !== []) { throw new InvalidArgumentException( - sprintf('Invalid HTTP method received. `%s` is invalid.', implode(', ', $diff)) + sprintf('Invalid HTTP method received. `%s` is invalid.', implode(', ', $diff)), ); } @@ -217,7 +225,7 @@ protected function normalizeAndValidateMethods($methods) * If any of your patterns contain multibyte values, the `multibytePattern` * mode will be enabled. * - * @param array $patterns The patterns to apply to routing elements + * @param array $patterns The patterns to apply to routing elements * @return $this */ public function setPatterns(array $patterns) @@ -258,7 +266,7 @@ public function setPass(array $names) } /** - * Set the names of parameters that will persisted automatically + * Set the names of parameters that will be persisted automatically * * Persistent parameters allow you to define which route parameters should be automatically * included when generating new URLs. You can override persistent parameters @@ -266,7 +274,7 @@ public function setPass(array $names) * * ``` * // remove a persistent 'date' parameter - * Router::url(['date' => false', ...]); + * Router::url(['date' => false, ...]); * ``` * * @param array $names The names of the parameters that should be passed. @@ -302,8 +310,8 @@ public function compile(): string if ($this->_compiledRoute === null) { $this->_writeRoute(); } + assert($this->_compiledRoute !== null); - /** @var string */ return $this->_compiledRoute; } @@ -324,26 +332,12 @@ protected function _writeRoute(): void return; } $route = $this->template; - $names = $routeParams = []; + $names = []; + $routeParams = []; $parsed = preg_quote($this->template, '#'); - if (strpos($route, '{') !== false && strpos($route, '}') !== false) { - preg_match_all(static::PLACEHOLDER_REGEX, $route, $namedElements, PREG_OFFSET_CAPTURE | PREG_SET_ORDER); - } else { - $hasMatches = preg_match_all( - '/:([a-z0-9-_]+(?braceKeys = false; - if ($hasMatches) { - deprecationWarning( - 'Colon prefixed route placeholders like `:foo` are deprecated.' - . ' Use braced placeholders like `{foo}` instead.' - ); - } - } + preg_match_all(static::PLACEHOLDER_REGEX, $route, $namedElements, PREG_OFFSET_CAPTURE | PREG_SET_ORDER); + foreach ($namedElements as $matchArray) { // Placeholder name, e.g. "foo" $name = $matchArray[1][0]; @@ -361,18 +355,18 @@ protected function _writeRoute(): void } else { $routeParams[$search] = '(?:(?P<' . $name . '>' . $this->options[$name] . ')' . $option . ')' . $option; } - // phpcs:disable Generic.Files.LineLength + // phpcs:enable Generic.Files.LineLength } else { $routeParams[$search] = '(?:(?P<' . $name . '>[^/]+))'; } $names[] = $name; } if (preg_match('#\/\*\*$#', $route)) { - $parsed = preg_replace('#/\\\\\*\\\\\*$#', '(?:/(?P<_trailing_>.*))?', $parsed); + $parsed = (string)preg_replace('#/\\\\\*\\\\\*$#', '(?:/(?P<_trailing_>.*))?', $parsed); $this->_greedy = true; } if (preg_match('#\/\*$#', $route)) { - $parsed = preg_replace('#/\\\\\*$#', '(?:/(?P<_args_>.*))?', $parsed); + $parsed = (string)preg_replace('#/\\\\\*$#', '(?:/(?P<_args_>.*))?', $parsed); $this->_greedy = true; } $mode = empty($this->options['multibytePattern']) ? '' : 'u'; @@ -398,7 +392,7 @@ protected function _writeRoute(): void */ public function getName(): string { - if (!empty($this->_name)) { + if ($this->_name) { return $this->_name; } $name = ''; @@ -410,10 +404,7 @@ public function getName(): string ]; foreach ($keys as $key => $glue) { $value = null; - if ( - strpos($this->template, '{' . $key . '}') !== false - || strpos($this->template, ':' . $key) !== false - ) { + if (str_contains($this->template, '{' . $key . '}')) { $value = '_' . $key; } elseif (isset($this->defaults[$key])) { $value = $this->defaults[$key]; @@ -510,7 +501,6 @@ public function parse(string $url, string $method): ?array } if (isset($route['_args_'])) { - /** @psalm-suppress PossiblyInvalidArgument */ $pass = $this->_parseArgs($route['_args_'], $route); $route['pass'] = array_merge($route['pass'], $pass); unset($route['_args_']); @@ -521,7 +511,7 @@ public function parse(string $url, string $method): ?array unset($route['_trailing_']); } - if (!empty($ext)) { + if ($ext) { $route['_ext'] = $ext; } @@ -534,7 +524,6 @@ public function parse(string $url, string $method): ?array if (isset($this->options['pass'])) { $j = count($this->options['pass']); while ($j--) { - /** @psalm-suppress PossiblyInvalidArgument */ if (isset($route[$this->options['pass'][$j]])) { array_unshift($route['pass'], $route[$this->options['pass'][$j]]); } @@ -543,7 +532,7 @@ public function parse(string $url, string $method): ?array $route['_route'] = $this; $route['_matchedRoute'] = $this->template; - if (count($this->middleware) > 0) { + if ($this->middleware !== []) { $route['_middleware'] = $this->middleware; } @@ -572,7 +561,7 @@ public function hostMatches(string $host): bool */ protected function _parseExtension(string $url): array { - if (count($this->_extensions) && strpos($url, '.') !== false) { + if (count($this->_extensions) && str_contains($url, '.')) { foreach ($this->_extensions as $ext) { $len = strlen($ext) + 1; if (substr($url, -$len) === '.' . $ext) { @@ -601,7 +590,7 @@ protected function _parseArgs(string $args, array $context): array $urldecode = $this->options['_urldecode'] ?? true; foreach ($args as $param) { - if (empty($param) && $param !== '0') { + if (!$param && $param !== '0') { continue; } $pass[] = $urldecode ? rawurldecode($param) : $param; @@ -645,7 +634,7 @@ protected function _persistParams(array $url, array $params): array */ public function match(array $url, array $context = []): ?string { - if (empty($this->_compiledRoute)) { + if (!$this->_compiledRoute) { $this->compile(); } $defaults = $this->defaults; @@ -662,10 +651,10 @@ public function match(array $url, array $context = []): ?string // Apply the _host option if possible if (isset($this->options['_host'])) { - if (!isset($hostOptions['_host']) && strpos($this->options['_host'], '*') === false) { + if (!isset($hostOptions['_host']) && !str_contains($this->options['_host'], '*')) { $hostOptions['_host'] = $this->options['_host']; } - $hostOptions['_host'] = $hostOptions['_host'] ?? $context['_host']; + $hostOptions['_host'] ??= $context['_host']; // The host did not match the route preferences if (!$this->hostMatches((string)$hostOptions['_host'])) { @@ -698,7 +687,7 @@ public function match(array $url, array $context = []): ?string $query = !empty($url['?']) ? (array)$url['?'] : []; unset($url['_host'], $url['_scheme'], $url['_port'], $url['_base'], $url['?']); - // Move extension into the hostOptions so its not part of + // Move extension into the hostOptions so it is not part of // reverse matches. if (isset($url['_ext'])) { $hostOptions['_ext'] = $url['_ext']; @@ -748,21 +737,18 @@ public function match(array $url, array $context = []): ?string if ($numeric) { $pass[] = $value; unset($url[$key]); - continue; } } // if not a greedy route, no extra params are allowed. - if (!$this->_greedy && !empty($pass)) { + if (!$this->_greedy && $pass !== []) { return null; } // check patterns for routed params - if (!empty($this->options)) { - foreach ($this->options as $key => $pattern) { - if (isset($url[$key]) && !preg_match('#^' . $pattern . '$#u', (string)$url[$key])) { - return null; - } + foreach ($this->options as $key => $pattern) { + if (isset($url[$key]) && !preg_match('#^' . $pattern . '$#u', (string)$url[$key])) { + return null; } } $url += $hostOptions; @@ -821,25 +807,26 @@ protected function _writeUrl(array $params, array $pass = [], array $query = []) }, $pass); $pass = implode('/', $pass); $out = $this->template; - - $search = $replace = []; + $search = []; + $replace = []; foreach ($this->keys as $key) { if (!array_key_exists($key, $params)) { - throw new InvalidArgumentException("Missing required route key `{$key}`"); + throw new InvalidArgumentException(sprintf( + 'Missing required route key `%s`.', + $key, + )); } $string = $params[$key]; - if ($this->braceKeys) { - $search[] = "{{$key}}"; - } else { - $search[] = ':' . $key; - } + $search[] = "{{$key}}"; $replace[] = $string; } - if (strpos($this->template, '**') !== false) { - array_push($search, '**', '%2F'); - array_push($replace, $pass, '/'); - } elseif (strpos($this->template, '*') !== false) { + if (str_contains($this->template, '**')) { + $search[] = '**'; + $search[] = '%2F'; + $replace[] = $pass; + $replace[] = '/'; + } elseif (str_contains($this->template, '*')) { $search[] = '*'; $replace[] = $pass; } @@ -859,20 +846,20 @@ protected function _writeUrl(array $params, array $pass = [], array $query = []) ) { $host = $params['_host']; - // append the port & scheme if they exists. + // append the port and scheme if they exist. if (isset($params['_port'])) { $host .= ':' . $params['_port']; } $scheme = $params['_scheme'] ?? 'http'; $out = "{$scheme}://{$host}{$out}"; } - if (!empty($params['_ext']) || !empty($query)) { + if (!empty($params['_ext']) || $query !== []) { $out = rtrim($out, '/'); } if (!empty($params['_ext'])) { $out .= '.' . $params['_ext']; } - if (!empty($query)) { + if ($query) { $out .= rtrim('?' . http_build_query($query), '?'); } @@ -890,18 +877,13 @@ public function staticPath(): string static::PLACEHOLDER_REGEX, $this->template, $namedElements, - PREG_OFFSET_CAPTURE + PREG_OFFSET_CAPTURE, ); if ($matched) { return substr($this->template, 0, $namedElements[0][1]); } - $routeKey = strpos($this->template, ':'); - if ($routeKey !== false) { - return substr($this->template, 0, $routeKey); - } - $star = strpos($this->template, '*'); if ($star !== false) { $path = rtrim(substr($this->template, 0, $star), '/'); @@ -945,7 +927,7 @@ public function getMiddleware(): array * @param array $fields Key/Value of object attributes * @return static A new instance of the route */ - public static function __set_state(array $fields) + public static function __set_state(array $fields): static { $class = static::class; $obj = new $class(''); diff --git a/app/vendor/cakephp/cakephp/src/Routing/RouteBuilder.php b/app/vendor/cakephp/cakephp/src/Routing/RouteBuilder.php index 4f04e9773..817bd659e 100644 --- a/app/vendor/cakephp/cakephp/src/Routing/RouteBuilder.php +++ b/app/vendor/cakephp/cakephp/src/Routing/RouteBuilder.php @@ -23,15 +23,12 @@ use Cake\Routing\Route\RedirectRoute; use Cake\Routing\Route\Route; use Cake\Utility\Inflector; +use Closure; use InvalidArgumentException; -use RuntimeException; -use function Cake\Core\getTypeName; +use Psr\Http\Server\MiddlewareInterface; /** - * Provides features for building routes inside scopes. - * - * Gives an easy to use way to build routes and append them - * into a route collection. + * Provides features for building routes and parsing/matching URLs to routes. */ class RouteBuilder { @@ -54,7 +51,7 @@ class RouteBuilder * * @var array */ - protected static $_resourceMap = [ + protected static array $_resourceMap = [ 'index' => ['action' => 'index', 'method' => 'GET', 'path' => ''], 'create' => ['action' => 'add', 'method' => 'POST', 'path' => ''], 'view' => ['action' => 'view', 'method' => 'GET', 'path' => '{id}'], @@ -67,42 +64,42 @@ class RouteBuilder * * @var string */ - protected $_routeClass = Route::class; + protected string $_routeClass = Route::class; /** * The extensions that should be set into the routes connected. * * @var array */ - protected $_extensions = []; + protected array $_extensions = []; /** * The path prefix scope that this collection uses. * * @var string */ - protected $_path; + protected string $_path; /** * The scope parameters if there are any. * * @var array */ - protected $_params; + protected array $_params; /** * Name prefix for connected routes. * * @var string */ - protected $_namePrefix = ''; + protected string $_namePrefix = ''; /** * The route collection routes should be added to. * * @var \Cake\Routing\RouteCollection */ - protected $_collection; + protected RouteCollection $_collection; /** * The list of middleware that routes in this builder get @@ -110,7 +107,7 @@ class RouteBuilder * * @var array */ - protected $middleware = []; + protected array $middleware = []; /** * Constructor @@ -178,7 +175,7 @@ public function getRouteClass(): string * @param array|string $extensions The extensions to set. * @return $this */ - public function setExtensions($extensions) + public function setExtensions(array|string $extensions) { $this->_extensions = (array)$extensions; @@ -201,7 +198,7 @@ public function getExtensions(): array * @param array|string $extensions One or more extensions to add * @return $this */ - public function addExtensions($extensions) + public function addExtensions(array|string $extensions) { $extensions = array_merge($this->_extensions, (array)$extensions); $this->_extensions = array_unique($extensions); @@ -217,7 +214,7 @@ public function addExtensions($extensions) public function path(): string { $routeKey = strpos($this->_path, '{'); - if ($routeKey !== false && strpos($this->_path, '}') !== false) { + if ($routeKey !== false && str_contains($this->_path, '}')) { return substr($this->_path, 0, $routeKey); } @@ -350,12 +347,12 @@ public function namePrefix(?string $value = null): string * is available at `/posts` * * @param string $name A controller name to connect resource routes for. - * @param callable|array $options Options to use when generating REST routes, or a callback. - * @param callable|null $callback An optional callback to be executed in a nested scope. Nested + * @param \Closure|array $options Options to use when generating REST routes, or a callback. + * @param \Closure|null $callback An optional callback to be executed in a nested scope. Nested * scopes inherit the existing path and 'id' parameter. * @return $this */ - public function resources(string $name, $options = [], $callback = null) + public function resources(string $name, Closure|array $options = [], ?Closure $callback = null) { if (!is_array($options)) { $callback = $options; @@ -389,7 +386,7 @@ public function resources(string $name, $options = [], $callback = null) $resourceMap = array_merge(static::$_resourceMap, $options['map']); $only = (array)$options['only']; - if (empty($only)) { + if (!$only) { $only = array_keys($resourceMap); } @@ -443,7 +440,7 @@ public function resources(string $name, $options = [], $callback = null) * @param string|null $name The name of the route. * @return \Cake\Routing\Route\Route */ - public function get(string $template, $target, ?string $name = null): Route + public function get(string $template, array|string $target, ?string $name = null): Route { return $this->_methodRoute('GET', $template, $target, $name); } @@ -457,7 +454,7 @@ public function get(string $template, $target, ?string $name = null): Route * @param string|null $name The name of the route. * @return \Cake\Routing\Route\Route */ - public function post(string $template, $target, ?string $name = null): Route + public function post(string $template, array|string $target, ?string $name = null): Route { return $this->_methodRoute('POST', $template, $target, $name); } @@ -471,7 +468,7 @@ public function post(string $template, $target, ?string $name = null): Route * @param string|null $name The name of the route. * @return \Cake\Routing\Route\Route */ - public function put(string $template, $target, ?string $name = null): Route + public function put(string $template, array|string $target, ?string $name = null): Route { return $this->_methodRoute('PUT', $template, $target, $name); } @@ -485,7 +482,7 @@ public function put(string $template, $target, ?string $name = null): Route * @param string|null $name The name of the route. * @return \Cake\Routing\Route\Route */ - public function patch(string $template, $target, ?string $name = null): Route + public function patch(string $template, array|string $target, ?string $name = null): Route { return $this->_methodRoute('PATCH', $template, $target, $name); } @@ -499,7 +496,7 @@ public function patch(string $template, $target, ?string $name = null): Route * @param string|null $name The name of the route. * @return \Cake\Routing\Route\Route */ - public function delete(string $template, $target, ?string $name = null): Route + public function delete(string $template, array|string $target, ?string $name = null): Route { return $this->_methodRoute('DELETE', $template, $target, $name); } @@ -513,7 +510,7 @@ public function delete(string $template, $target, ?string $name = null): Route * @param string|null $name The name of the route. * @return \Cake\Routing\Route\Route */ - public function head(string $template, $target, ?string $name = null): Route + public function head(string $template, array|string $target, ?string $name = null): Route { return $this->_methodRoute('HEAD', $template, $target, $name); } @@ -527,7 +524,7 @@ public function head(string $template, $target, ?string $name = null): Route * @param string|null $name The name of the route. * @return \Cake\Routing\Route\Route */ - public function options(string $template, $target, ?string $name = null): Route + public function options(string $template, array|string $target, ?string $name = null): Route { return $this->_methodRoute('OPTIONS', $template, $target, $name); } @@ -542,7 +539,7 @@ public function options(string $template, $target, ?string $name = null): Route * @param string|null $name The name of the route. * @return \Cake\Routing\Route\Route */ - protected function _methodRoute(string $method, string $template, $target, ?string $name): Route + protected function _methodRoute(string $method, string $template, array|string $target, ?string $name): Route { if ($name !== null) { $name = $this->_namePrefix . $name; @@ -671,7 +668,7 @@ public function loadPlugin(string $name) * @throws \InvalidArgumentException * @throws \BadMethodCallException */ - public function connect($route, $defaults = [], array $options = []): Route + public function connect(Route|string $route, array|string $defaults = [], array $options = []): Route { $defaults = $this->parseDefaults($defaults); if (empty($options['_ext'])) { @@ -699,13 +696,13 @@ public function connect($route, $defaults = [], array $options = []): Route * @param array|string $defaults Defaults array from the connect() method. * @return array */ - protected function parseDefaults($defaults): array + protected function parseDefaults(array|string $defaults): array { - if (!is_string($defaults)) { - return $defaults; + if (is_string($defaults)) { + return Router::parseRoutePath($defaults); } - return Router::parseRoutePath($defaults); + return $defaults; } /** @@ -718,7 +715,7 @@ protected function parseDefaults($defaults): array * @throws \InvalidArgumentException when route class or route object is invalid. * @throws \BadMethodCallException when the route to make conflicts with the current scope */ - protected function _makeRoute($route, $defaults, $options): Route + protected function _makeRoute(Route|string $route, array $defaults, array $options): Route { if (is_string($route)) { /** @var class-string<\Cake\Routing\Route\Route>|null $routeClass */ @@ -726,7 +723,7 @@ protected function _makeRoute($route, $defaults, $options): Route if ($routeClass === null) { throw new InvalidArgumentException(sprintf( 'Cannot find route class %s', - $options['routeClass'] + $options['routeClass'], )); } @@ -744,7 +741,7 @@ protected function _makeRoute($route, $defaults, $options): Route $param, $val, $param, - $defaults[$param] + $defaults[$param], )); } } @@ -792,11 +789,11 @@ protected function _makeRoute($route, $defaults, $options): Route * @param array $options An array matching the named elements in the route to regular expressions which that * element should match. Also contains additional parameters such as which routed parameters should be * shifted into the passed arguments. As well as supplying patterns for routing parameters. - * @return \Cake\Routing\Route\Route|\Cake\Routing\Route\RedirectRoute + * @return \Cake\Routing\Route\Route */ - public function redirect(string $route, $url, array $options = []): Route + public function redirect(string $route, array|string $url, array $options = []): Route { - $options['routeClass'] = $options['routeClass'] ?? RedirectRoute::class; + $options['routeClass'] ??= RedirectRoute::class; if (is_string($url)) { $url = ['redirect' => $url]; } @@ -830,13 +827,13 @@ public function redirect(string $route, $url, array $options = []): Route * ``` * * @param string $name The prefix name to use. - * @param callable|array $params An array of routing defaults to add to each connected route. - * If you have no parameters, this argument can be a callable. - * @param callable|null $callback The callback to invoke that builds the prefixed routes. + * @param \Closure|array $params An array of routing defaults to add to each connected route. + * If you have no parameters, this argument can be a Closure. + * @param \Closure|null $callback The callback to invoke that builds the prefixed routes. * @return $this * @throws \InvalidArgumentException If a valid callback is not passed */ - public function prefix(string $name, $params = [], $callback = null) + public function prefix(string $name, Closure|array $params = [], ?Closure $callback = null) { if (!is_array($params)) { $callback = $params; @@ -876,12 +873,12 @@ public function prefix(string $name, $params = [], $callback = null) * name of any route created in a scope callback. * * @param string $name The plugin name to build routes for - * @param callable|array $options Either the options to use, or a callback to build routes. - * @param callable|null $callback The callback to invoke that builds the plugin routes + * @param \Closure|array $options Either the options to use, or a callback to build routes. + * @param \Closure|null $callback The callback to invoke that builds the plugin routes * Only required when $options is defined. * @return $this */ - public function plugin(string $name, $options = [], $callback = null) + public function plugin(string $name, Closure|array $options = [], ?Closure $callback = null) { if (!is_array($options)) { $callback = $options; @@ -909,23 +906,20 @@ public function plugin(string $name, $options = [], $callback = null) * name of any route created in a scope callback. * * @param string $path The path to create a scope for. - * @param callable|array $params Either the parameters to add to routes, or a callback. - * @param callable|null $callback The callback to invoke that builds the plugin routes. + * @param \Closure|array $params Either the parameters to add to routes, or a callback. + * @param \Closure|null $callback The callback to invoke that builds the plugin routes. * Only required when $params is defined. * @return $this * @throws \InvalidArgumentException when there is no callable parameter. */ - public function scope(string $path, $params, $callback = null) + public function scope(string $path, Closure|array $params, ?Closure $callback = null) { - if (!is_array($params)) { + if ($params instanceof Closure) { $callback = $params; $params = []; } - if (!is_callable($callback)) { - throw new InvalidArgumentException(sprintf( - 'Need a valid callable to connect routes. Got `%s` instead.', - getTypeName($callback) - )); + if ($callback === null) { + throw new InvalidArgumentException('Need a valid Closure to connect routes.'); } if ($this->_path !== '/') { @@ -978,7 +972,7 @@ public function fallbacks(?string $routeClass = null) * @return $this * @see \Cake\Routing\RouteCollection */ - public function registerMiddleware(string $name, $middleware) + public function registerMiddleware(string $name, MiddlewareInterface|Closure|string $middleware) { $this->_collection->registerMiddleware($name, $middleware); @@ -992,16 +986,17 @@ public function registerMiddleware(string $name, $middleware) * * @param string ...$names The names of the middleware to apply to the current scope. * @return $this - * @throws \RuntimeException If it cannot apply one of the given middleware or middleware groups. + * @throws \InvalidArgumentException If it cannot apply one of the given middleware or middleware groups. * @see \Cake\Routing\RouteCollection::addMiddlewareToScope() */ public function applyMiddleware(string ...$names) { + /** @var array $names */ foreach ($names as $name) { if (!$this->_collection->middlewareExists($name)) { - $message = "Cannot apply '$name' middleware or middleware group. " . - 'Use registerMiddleware() to register middleware.'; - throw new RuntimeException($message); + $message = "Cannot apply `{$name}` middleware or middleware group. " . + 'Use `registerMiddleware()` to register middleware.'; + throw new InvalidArgumentException($message); } } $this->middleware = array_unique(array_merge($this->middleware, $names)); @@ -1012,7 +1007,7 @@ public function applyMiddleware(string ...$names) /** * Get the middleware that this builder will apply to routes. * - * @return array + * @return array */ public function getMiddleware(): array { diff --git a/app/vendor/cakephp/cakephp/src/Routing/RouteCollection.php b/app/vendor/cakephp/cakephp/src/Routing/RouteCollection.php index c91e5ba9e..83bb49a0a 100644 --- a/app/vendor/cakephp/cakephp/src/Routing/RouteCollection.php +++ b/app/vendor/cakephp/cakephp/src/Routing/RouteCollection.php @@ -19,9 +19,10 @@ use Cake\Routing\Exception\DuplicateNamedRouteException; use Cake\Routing\Exception\MissingRouteException; use Cake\Routing\Route\Route; +use Closure; +use InvalidArgumentException; use Psr\Http\Message\ServerRequestInterface; -use RuntimeException; -use function Cake\Core\deprecationWarning; +use Psr\Http\Server\MiddlewareInterface; /** * Contains a collection of routes. @@ -38,49 +39,49 @@ class RouteCollection * * @var array> */ - protected $_routeTable = []; + protected array $_routeTable = []; /** * The hash map of named routes that are in this collection. * * @var array<\Cake\Routing\Route\Route> */ - protected $_named = []; + protected array $_named = []; /** * Routes indexed by static path. * * @var array> */ - protected $staticPaths = []; + protected array $staticPaths = []; /** * Routes indexed by path prefix. * * @var array> */ - protected $_paths = []; + protected array $_paths = []; /** * A map of middleware names and the related objects. * * @var array */ - protected $_middleware = []; + protected array $_middleware = []; /** * A map of middleware group names and the related middleware names. * * @var array */ - protected $_middlewareGroups = []; + protected array $_middlewareGroups = []; /** * Route extensions * * @var array */ - protected $_extensions = []; + protected array $_extensions = []; /** * Add a route to the collection. @@ -107,14 +108,14 @@ public function add(Route $route, array $options = []): void // Generated names. $name = $route->getName(); - $this->_routeTable[$name] = $this->_routeTable[$name] ?? []; + $this->_routeTable[$name] ??= []; $this->_routeTable[$name][] = $route; // Index path prefixes (for parsing) $path = $route->staticPath(); $extensions = $route->getExtensions(); - if (count($extensions) > 0) { + if ($extensions !== []) { $this->setExtensions($extensions); } @@ -125,75 +126,6 @@ public function add(Route $route, array $options = []): void $this->_paths[$path][] = $route; } - /** - * Takes the URL string and iterates the routes until one is able to parse the route. - * - * @param string $url URL to parse. - * @param string $method The HTTP method to use. - * @return array An array of request parameters parsed from the URL. - * @throws \Cake\Routing\Exception\MissingRouteException When a URL has no matching route. - * @deprecated 4.5.0 Use parseRequest() instead. - */ - public function parse(string $url, string $method = ''): array - { - deprecationWarning('4.5.0 - Use parseRequest() instead.'); - $queryParameters = []; - if (strpos($url, '?') !== false) { - [$url, $qs] = explode('?', $url, 2); - parse_str($qs, $queryParameters); - } - - $decoded = urldecode($url); - if ($decoded !== '/') { - $decoded = rtrim($decoded, '/'); - } - - if (isset($this->staticPaths[$decoded])) { - foreach ($this->staticPaths[$decoded] as $route) { - $r = $route->parse($url, $method); - if ($r === null) { - continue; - } - - if ($queryParameters) { - $r['?'] = $queryParameters; - } - - return $r; - } - } - - // Sort path segments matching longest paths first. - krsort($this->_paths); - - foreach ($this->_paths as $path => $routes) { - if (strpos($decoded, $path) !== 0) { - continue; - } - - foreach ($routes as $route) { - $r = $route->parse($url, $method); - if ($r === null) { - continue; - } - if ($queryParameters) { - $r['?'] = $queryParameters; - } - - return $r; - } - } - - $exceptionProperties = ['url' => $url]; - if ($method !== '') { - // Ensure that if the method is included, it is the first element of - // the array, to match the order that the strings are printed in the - // MissingRouteException error message, $_messageTemplateWithMethod. - $exceptionProperties = array_merge(['method' => $method], $exceptionProperties); - } - throw new MissingRouteException($exceptionProperties); - } - /** * Takes the ServerRequestInterface, iterates the routes until one is able to parse the route. * @@ -209,8 +141,8 @@ public function parseRequest(ServerRequestInterface $request): array // decode urlencoded segments, but don't decode %2f aka / $parts = explode('/', $urlPath); $parts = array_map( - fn (string $part) => str_replace('/', '%2f', urldecode($part)), - $parts + fn(string $part) => str_replace('/', '%2f', urldecode($part)), + $parts, ); $urlPath = implode('/', $parts); } @@ -236,7 +168,7 @@ public function parseRequest(ServerRequestInterface $request): array krsort($this->_paths); foreach ($this->_paths as $path => $routes) { - if (strpos($urlPath, $path) !== 0) { + if (!str_starts_with($urlPath, $path)) { continue; } @@ -399,7 +331,7 @@ public function routes(): array return array_reduce( $this->_paths, 'array_merge', - [] + [], ); } @@ -436,7 +368,7 @@ public function setExtensions(array $extensions, bool $merge = true) if ($merge) { $extensions = array_unique(array_merge( $this->_extensions, - $extensions + $extensions, )); } $this->_extensions = $extensions; @@ -453,9 +385,8 @@ public function setExtensions(array $extensions, bool $merge = true) * @param string $name The name of the middleware. Used when applying middleware to a scope. * @param \Psr\Http\Server\MiddlewareInterface|\Closure|string $middleware The middleware to register. * @return $this - * @throws \RuntimeException */ - public function registerMiddleware(string $name, $middleware) + public function registerMiddleware(string $name, MiddlewareInterface|Closure|string $middleware) { $this->_middleware[$name] = $middleware; @@ -468,19 +399,19 @@ public function registerMiddleware(string $name, $middleware) * @param string $name Name of the middleware group * @param array $middlewareNames Names of the middleware * @return $this - * @throws \RuntimeException + * @throws \InvalidArgumentException */ public function middlewareGroup(string $name, array $middlewareNames) { if ($this->hasMiddleware($name)) { - $message = "Cannot add middleware group '$name'. A middleware by this name has already been registered."; - throw new RuntimeException($message); + $message = "Cannot add middleware group '{$name}'. A middleware by this name has already been registered."; + throw new InvalidArgumentException($message); } foreach ($middlewareNames as $middlewareName) { if (!$this->hasMiddleware($middlewareName)) { - $message = "Cannot add '$middlewareName' middleware to group '$name'. It has not been registered."; - throw new RuntimeException($message); + $message = "Cannot add '{$middlewareName}' middleware to group '{$name}'. It has not been registered."; + throw new InvalidArgumentException($message); } } @@ -528,7 +459,7 @@ public function middlewareExists(string $name): bool * @param array $names The names of the middleware or groups to fetch * @return array An array of middleware. If any of the passed names are groups, * the groups middleware will be flattened into the returned list. - * @throws \RuntimeException when a requested middleware does not exist. + * @throws \InvalidArgumentException when a requested middleware does not exist. */ public function getMiddleware(array $names): array { @@ -539,9 +470,9 @@ public function getMiddleware(array $names): array continue; } if (!$this->hasMiddleware($name)) { - throw new RuntimeException(sprintf( - "The middleware named '%s' has not been registered. Use registerMiddleware() to define it.", - $name + throw new InvalidArgumentException(sprintf( + 'The middleware named `%s` has not been registered. Use registerMiddleware() to define it.', + $name, )); } $out[] = $this->_middleware[$name]; diff --git a/app/vendor/cakephp/cakephp/src/Routing/Router.php b/app/vendor/cakephp/cakephp/src/Routing/Router.php index d11b2788c..4e4b5385a 100644 --- a/app/vendor/cakephp/cakephp/src/Routing/Router.php +++ b/app/vendor/cakephp/cakephp/src/Routing/Router.php @@ -17,27 +17,21 @@ namespace Cake\Routing; use Cake\Core\Configure; +use Cake\Core\Exception\CakeException; use Cake\Http\ServerRequest; use Cake\Routing\Exception\MissingRouteException; -use Cake\Utility\Inflector; +use Cake\Routing\Route\Route; +use Closure; use InvalidArgumentException; +use Psr\Http\Message\UriInterface; use ReflectionFunction; -use ReflectionMethod; -use RuntimeException; use Throwable; -use function Cake\Core\deprecationWarning; /** * Parses the request URL into controller, action, and parameters. Uses the connected routes * to match the incoming URL string to parameters that will allow the request to be dispatched. Also * handles converting parameter lists into URL strings, using the connected routes. Routing allows you to decouple * the way the world interacts with your application (URLs) and the implementation (controllers and actions). - * - * ### Connecting routes - * - * Connecting routes is done using Router::connect(). When parsing incoming requests or reverse matching - * parameters, routes are enumerated in the order they were connected. For more information on routes and - * how to connect them see Router::connect(). */ class Router { @@ -46,7 +40,7 @@ class Router * * @var string */ - protected static $_defaultRouteClass = Route\Route::class; + protected static string $_defaultRouteClass = Route::class; /** * Contains the base string that will be applied to all generated URLs @@ -54,7 +48,7 @@ class Router * * @var string|null */ - protected static $_fullBaseUrl; + protected static ?string $_fullBaseUrl = null; /** * Regular expression for action names @@ -103,21 +97,21 @@ class Router * * @var \Cake\Routing\RouteCollection */ - protected static $_collection; + protected static RouteCollection $_collection; /** * A hash of request context data. * * @var array */ - protected static $_requestContext = []; + protected static array $_requestContext = []; /** * Named expressions * * @var array */ - protected static $_namedExpressions = [ + protected static array $_namedExpressions = [ 'Action' => Router::ACTION, 'Year' => Router::YEAR, 'Month' => Router::MONTH, @@ -129,9 +123,9 @@ class Router /** * Maintains the request object reference. * - * @var \Cake\Http\ServerRequest + * @var \Cake\Http\ServerRequest|null */ - protected static $_request; + protected static ?ServerRequest $_request = null; /** * Initial state is populated the first time reload() is called which is at the bottom @@ -140,29 +134,29 @@ class Router * * @var array */ - protected static $_initialState = []; + protected static array $_initialState = []; /** * The stack of URL filters to apply against routing URLs before passing the * parameters to the route collection. * - * @var array + * @var array<\Closure> */ - protected static $_urlFilters = []; + protected static array $_urlFilters = []; /** * Default extensions defined with Router::extensions() * * @var array */ - protected static $_defaultExtensions = []; + protected static array $_defaultExtensions = []; /** * Cache of parsed route paths * * @var array */ - protected static $_routePaths = []; + protected static array $_routePaths = []; /** * Get or set default route class. @@ -192,37 +186,7 @@ public static function getNamedExpressions(): array } /** - * Connects a new Route in the router. - * - * Compatibility proxy to \Cake\Routing\RouteBuilder::connect() in the `/` scope. - * - * @param \Cake\Routing\Route\Route|string $route A string describing the template of the route - * @param array|string $defaults An array describing the default route parameters. - * These parameters will be used by default and can supply routing parameters that are not dynamic. See above. - * @param array $options An array matching the named elements in the route to regular expressions which that - * element should match. Also contains additional parameters such as which routed parameters should be - * shifted into the passed arguments, supplying patterns for routing parameters and supplying the name of a - * custom routing class. - * @return void - * @throws \Cake\Core\Exception\CakeException - * @see \Cake\Routing\RouteBuilder::connect() - * @see \Cake\Routing\Router::scope() - * @deprecated 4.3.0 Use the non-static method `RouteBuilder::connect()` instead. - */ - public static function connect($route, $defaults = [], $options = []): void - { - deprecationWarning( - '`Router::connect()` is deprecated, use the non-static method `RouteBuilder::connect()` instead.' - ); - - static::scope('/', function ($routes) use ($route, $defaults, $options): void { - /** @var \Cake\Routing\RouteBuilder $routes */ - $routes->connect($route, $defaults, $options); - }); - } - - /** - * Get the routing parameters for the request if possible. + * Get the routing parameters for the request is possible. * * @param \Cake\Http\ServerRequest $request The request to parse request data from. * @return array Parsed elements from URL. @@ -242,16 +206,13 @@ public static function parseRequest(ServerRequest $request): array public static function setRequest(ServerRequest $request): void { static::$_request = $request; + $uri = $request->getUri(); - static::$_requestContext['_base'] = $request->getAttribute('base'); + static::$_requestContext['_base'] = $request->getAttribute('base', ''); static::$_requestContext['params'] = $request->getAttribute('params', []); - - $uri = $request->getUri(); - static::$_requestContext += [ - '_scheme' => $uri->getScheme(), - '_host' => $uri->getHost(), - '_port' => $uri->getPort(), - ]; + static::$_requestContext['_scheme'] ??= $uri->getScheme(); + static::$_requestContext['_host'] ??= $uri->getHost(); + static::$_requestContext['_port'] ??= $uri->getPort(); } /** @@ -272,14 +233,14 @@ public static function getRequest(): ?ServerRequest */ public static function reload(): void { - if (empty(static::$_initialState)) { + if (static::$_initialState === []) { static::$_collection = new RouteCollection(); static::$_initialState = get_class_vars(static::class); return; } foreach (static::$_initialState as $key => $val) { - if ($key !== '_initialState') { + if ($key !== '_initialState' && $key !== '_collection') { static::${$key} = $val; } } @@ -336,10 +297,10 @@ public static function resetRoutes(): void * }); * ``` * - * @param callable $function The function to add + * @param \Closure $function The function to add * @return void */ - public static function addUrlFilter(callable $function): void + public static function addUrlFilter(Closure $function): void { static::$_urlFilters[] = $function; } @@ -359,19 +320,14 @@ protected static function _applyUrlFilters(array $url): array try { $url = $filter($url, $request); } catch (Throwable $e) { - if (is_array($filter)) { - $ref = new ReflectionMethod($filter[0], $filter[1]); - } else { - /** @psalm-var \Closure|callable-string $filter */ - $ref = new ReflectionFunction($filter); - } + $ref = new ReflectionFunction($filter); $message = sprintf( 'URL filter defined in %s on line %s could not be applied. The filter failed with: %s', $ref->getFileName(), $ref->getStartLine(), - $e->getMessage() + $e->getMessage(), ); - throw new RuntimeException($message, (int)$e->getCode(), $e); + throw new CakeException($message, (int)$e->getCode(), $e); } } @@ -416,18 +372,17 @@ protected static function _applyUrlFilters(array $url): array * @return string Full translated URL with base path. * @throws \Cake\Core\Exception\CakeException When the route name is not found */ - public static function url($url = null, bool $full = false): string + public static function url(UriInterface|array|string|null $url = null, bool $full = false): string { $context = static::$_requestContext; - $request = static::getRequest(); - - $context['_base'] = $context['_base'] ?? Configure::read('App.base') ?: ''; + // For CLI request context would be empty + $context['_base'] ??= Configure::read('App.base', ''); - if (empty($url)) { - $here = $request ? $request->getRequestTarget() : '/'; + if (!$url) { + $here = static::getRequest()?->getRequestTarget() ?? '/'; $output = $context['_base'] . $here; if ($full) { - $output = static::fullBaseUrl() . $output; + return static::fullBaseUrl() . $output; } return $output; @@ -450,12 +405,6 @@ public static function url($url = null, bool $full = false): string $url = self::unwrapShortString($url); } - if (isset($url['_ssl'])) { - deprecationWarning('`_ssl` option is deprecated. Use `_https` instead.'); - $url['_https'] = $url['_ssl']; - unset($url['_ssl']); - } - if (isset($url['_https'])) { $url['_scheme'] = $url['_https'] === true ? 'https' : 'http'; } @@ -506,20 +455,19 @@ public static function url($url = null, bool $full = false): string } else { $url = (string)$url; - $plainString = ( - strpos($url, 'javascript:') === 0 || - strpos($url, 'mailto:') === 0 || - strpos($url, 'tel:') === 0 || - strpos($url, 'sms:') === 0 || - strpos($url, '#') === 0 || - strpos($url, '?') === 0 || - strpos($url, '//') === 0 || - strpos($url, '://') !== false - ); - - if ($plainString) { + if ( + str_starts_with($url, 'javascript:') || + str_starts_with($url, 'mailto:') || + str_starts_with($url, 'tel:') || + str_starts_with($url, 'sms:') || + str_starts_with($url, '#') || + str_starts_with($url, '?') || + str_starts_with($url, '//') || + str_contains($url, '://') + ) { return $url; } + $output = $context['_base'] . $url; } @@ -571,13 +519,13 @@ public static function pathUrl(string $path, array $params = [], bool $full = fa * Default is false. * @return bool */ - public static function routeExists($url = null, bool $full = false): bool + public static function routeExists(array|string|null $url = null, bool $full = false): bool { try { static::url($url, $full); return true; - } catch (MissingRouteException $e) { + } catch (MissingRouteException) { return false; } } @@ -614,7 +562,7 @@ public static function fullBaseUrl(?string $base = null): string $base = sprintf( '%s://%s', static::$_requestContext['_scheme'], - static::$_requestContext['_host'] + static::$_requestContext['_host'], ); if (!empty(static::$_requestContext['_port'])) { $base .= ':' . static::$_requestContext['_port']; @@ -646,16 +594,19 @@ public static function fullBaseUrl(?string $base = null): string * handled in order to reverse a params array into a string URL. * * @param \Cake\Http\ServerRequest|array $params The params array or - * Cake\Http\ServerRequest object that needs to be reversed. + * {@link \Cake\Http\ServerRequest} object that needs to be reversed. * @return array The URL array ready to be used for redirect or HTML link. */ - public static function reverseToArray($params): array + public static function reverseToArray(ServerRequest|array $params): array { $route = null; if ($params instanceof ServerRequest) { $route = $params->getAttribute('route'); + assert($route === null || $route instanceof Route); + $queryString = $params->getQueryParams(); $params = $params->getAttribute('params'); + assert(is_array($params)); $params['?'] = $queryString; } $pass = $params['pass'] ?? []; @@ -664,7 +615,7 @@ public static function reverseToArray($params): array unset( $params['pass'], $params['_matchedRoute'], - $params['_name'] + $params['_name'], ); if (!$route && $template) { // Locate the route that was used to match this route @@ -693,12 +644,12 @@ public static function reverseToArray($params): array * handled in order to reverse a params array into a string URL. * * @param \Cake\Http\ServerRequest|array $params The params array or - * Cake\Http\ServerRequest object that needs to be reversed. + * {@link \Cake\Http\ServerRequest} object that needs to be reversed. * @param bool $full Set to true to include the full URL including the * protocol when reversing the URL. * @return string The string that is the reversed result of the array */ - public static function reverse($params, $full = false): string + public static function reverse(ServerRequest|array $params, bool $full = false): string { $params = static::reverseToArray($params); @@ -714,7 +665,7 @@ public static function reverse($params, $full = false): string * @param array|string $url URL to normalize Either an array or a string URL. * @return string Normalized URL */ - public static function normalize($url = '/'): string + public static function normalize(array|string $url = '/'): string { if (is_array($url)) { $url = static::url($url); @@ -727,17 +678,17 @@ public static function normalize($url = '/'): string if ($request) { $base = $request->getAttribute('base', ''); if ($base !== '' && stristr($url, $base)) { - $url = preg_replace('/^' . preg_quote($base, '/') . '/', '', $url, 1); + $url = (string)preg_replace('/^' . preg_quote($base, '/') . '/', '', $url, 1); } } $url = '/' . $url; - while (strpos($url, '//') !== false) { + while (str_contains($url, '//')) { $url = str_replace('//', '/', $url); } $url = preg_replace('/(?:(\/$))/', '', $url); - if (empty($url)) { + if (!$url) { return '/'; } @@ -750,9 +701,9 @@ public static function normalize($url = '/'): string * Instructs the router to parse out file extensions * from the URL. For example, http://example.com/posts.rss would yield a file * extension of "rss". The file extension itself is made available in the - * controller as `$this->request->getParam('_ext')`, and is used by the RequestHandler - * component to automatically switch to alternate layouts and templates, and - * load helpers corresponding to the given content, i.e. RssHelper. Switching + * controller as `$this->request->getParam('_ext')`, and is used by content + * type negotiation to automatically switch to alternate layouts and templates, and + * load helpers corresponding to the given content. Switching * layouts and helpers requires that the chosen extension has a defined mime type * in `Cake\Http\Response`. * @@ -764,7 +715,7 @@ public static function normalize($url = '/'): string * Defaults to `true`. * @return array Array of extensions Router is configured to parse. */ - public static function extensions($extensions = null, $merge = true): array + public static function extensions(array|string|null $extensions = null, bool $merge = true): array { $collection = static::$_collection; if ($extensions === null) { @@ -801,146 +752,7 @@ public static function createRouteBuilder(string $path, array $options = []): Ro } /** - * Create a routing scope. - * - * Routing scopes allow you to keep your routes DRY and avoid repeating - * common path prefixes, and or parameter sets. - * - * Scoped collections will be indexed by path for faster route parsing. If you - * re-open or re-use a scope the connected routes will be merged with the - * existing ones. - * - * ### Options - * - * The `$params` array allows you to define options for the routing scope. - * The options listed below *are not* available to be used as routing defaults - * - * - `routeClass` The route class to use in this scope. Defaults to - * `Router::defaultRouteClass()` - * - `extensions` The extensions to enable in this scope. Defaults to the globally - * enabled extensions set with `Router::extensions()` - * - * ### Example - * - * ``` - * Router::scope('/blog', ['plugin' => 'Blog'], function ($routes) { - * $routes->connect('/', ['controller' => 'Articles']); - * }); - * ``` - * - * The above would result in a `/blog/` route being created, with both the - * plugin & controller default parameters set. - * - * You can use `Router::plugin()` and `Router::prefix()` as shortcuts to creating - * specific kinds of scopes. - * - * @param string $path The path prefix for the scope. This path will be prepended - * to all routes connected in the scoped collection. - * @param callable|array $params An array of routing defaults to add to each connected route. - * If you have no parameters, this argument can be a callable. - * @param callable|null $callback The callback to invoke with the scoped collection. - * @throws \InvalidArgumentException When an invalid callable is provided. - * @return void - * @deprecated 4.3.0 Use the non-static method `RouteBuilder::scope()` instead. - */ - public static function scope(string $path, $params = [], $callback = null): void - { - deprecationWarning( - '`Router::scope()` is deprecated, use the non-static method `RouteBuilder::scope()` instead.' - ); - - $options = []; - if (is_array($params)) { - $options = $params; - unset($params['routeClass'], $params['extensions']); - } - $builder = static::createRouteBuilder('/', $options); - $builder->scope($path, $params, $callback); - } - - /** - * Create prefixed routes. - * - * This method creates a scoped route collection that includes - * relevant prefix information. - * - * The path parameter is used to generate the routing parameter name. - * For example a path of `admin` would result in `'prefix' => 'Admin'` being - * applied to all connected routes. - * - * The prefix name will be inflected to the dasherized version to create - * the routing path. If you want a custom path name, use the `path` option. - * - * You can re-open a prefix as many times as necessary, as well as nest prefixes. - * Nested prefixes will result in prefix values like `Admin/Api` which translates - * to the `Controller\Admin\Api\` namespace. - * - * @param string $name The prefix name to use. - * @param callable|array $params An array of routing defaults to add to each connected route. - * If you have no parameters, this argument can be a callable. - * @param callable|null $callback The callback to invoke that builds the prefixed routes. - * @return void - * @deprecated 4.3.0 Use the non-static method `RouteBuilder::prefix()` instead. - */ - public static function prefix(string $name, $params = [], $callback = null): void - { - deprecationWarning( - '`Router::prefix()` is deprecated, use the non-static method `RouteBuilder::prefix()` instead.' - ); - - if (!is_array($params)) { - $callback = $params; - $params = []; - } - - $path = $params['path'] ?? '/' . Inflector::dasherize($name); - unset($params['path']); - - $params = array_merge($params, ['prefix' => Inflector::camelize($name)]); - static::scope($path, $params, $callback); - } - - /** - * Add plugin routes. - * - * This method creates a scoped route collection that includes - * relevant plugin information. - * - * The plugin name will be inflected to the dasherized version to create - * the routing path. If you want a custom path name, use the `path` option. - * - * Routes connected in the scoped collection will have the correct path segment - * prepended, and have a matching plugin routing key set. - * - * @param string $name The plugin name to build routes for - * @param callable|array $options Either the options to use, or a callback - * @param callable|null $callback The callback to invoke that builds the plugin routes. - * Only required when $options is defined - * @return void - * @deprecated 4.3.0 Use the non-static method `RouteBuilder::plugin()` instead. - */ - public static function plugin(string $name, $options = [], $callback = null): void - { - deprecationWarning( - '`Router::plugin()` is deprecated, use the non-static method `RouteBuilder::plugin()` instead.' - ); - - if (!is_array($options)) { - $callback = $options; - $options = []; - } - $params = ['plugin' => $name]; - $path = $options['path'] ?? '/' . Inflector::dasherize($name); - if (isset($options['_namePrefix'])) { - $params['_namePrefix'] = $options['_namePrefix']; - } - static::scope($path, $params, $callback); - } - - /** - * Get the all of the routes currently connected. - * - * Routes will not always be returned in the order they were defined. + * Get the route scopes and their connected routes. * * @return array<\Cake\Routing\Route\Route> */ @@ -976,12 +788,12 @@ public static function setRouteCollection(RouteCollection $routeCollection): voi * @param array $url Route array with `_path` key * @return array */ - protected static function unwrapShortString(array $url) + protected static function unwrapShortString(array $url): array { foreach (['plugin', 'prefix', 'controller', 'action'] as $key) { if (array_key_exists($key, $url)) { throw new InvalidArgumentException( - "`$key` cannot be used when defining route targets with a string route path." + "`{$key}` cannot be used when defining route targets with a string route path.", ); } } @@ -1025,7 +837,7 @@ public static function parseRoutePath(string $url): array $#ix'; if (!preg_match($regex, $url, $matches)) { - throw new InvalidArgumentException("Could not parse a string route path `{$url}`."); + throw new InvalidArgumentException(sprintf('Could not parse a string route path `%s`.', $url)); } $defaults = [ @@ -1042,16 +854,16 @@ public static function parseRoutePath(string $url): array if (isset($matches['params']) && $matches['params'] !== '') { $paramsArray = explode('/', trim($matches['params'], '/')); foreach ($paramsArray as $param) { - if (strpos($param, '=') !== false) { + if (str_contains($param, '=')) { if (!preg_match('/(?.+?)=(?.*)/', $param, $paramMatches)) { throw new InvalidArgumentException( - "Could not parse a key=value from `{$param}` in route path `{$url}`." + "Could not parse a key=value from `{$param}` in route path `{$url}`.", ); } $paramKey = $paramMatches['key']; if (!preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $paramKey)) { throw new InvalidArgumentException( - "Param key `{$paramKey}` is not valid in route path `{$url}`." + "Param key `{$paramKey}` is not valid in route path `{$url}`.", ); } $defaults[$paramKey] = trim($paramMatches['value'], '\'"'); diff --git a/app/vendor/cakephp/cakephp/src/Routing/functions.php b/app/vendor/cakephp/cakephp/src/Routing/functions.php index 310ce6990..65724f02e 100644 --- a/app/vendor/cakephp/cakephp/src/Routing/functions.php +++ b/app/vendor/cakephp/cakephp/src/Routing/functions.php @@ -17,24 +17,7 @@ // phpcs:disable PSR1.Files.SideEffects namespace Cake\Routing; -/** - * Convenience wrapper for Router::url(). - * - * @param \Psr\Http\Message\UriInterface|array|string|null $url An array specifying any of the following: - * 'controller', 'action', 'plugin' additionally, you can provide routed - * elements or query string parameters. If string it can be name any valid url - * string or it can be an UriInterface instance. - * @param bool $full If true, the full base URL will be prepended to the result. - * Default is false. - * @return string Full translated URL with base path. - * @throws \Cake\Core\Exception\CakeException When the route name is not found - * @see \Cake\Routing\Router::url() - * @since 4.5.0 - */ -function url($url = null, bool $full = false): string -{ - return Router::url($url, $full); -} +use Psr\Http\Message\UriInterface; /** * Returns an array URL from a route path string. @@ -57,8 +40,20 @@ function urlArray(string $path, array $params = []): array } /** - * Include global functions. + * Convenience wrapper for Router::url(). + * + * @param \Psr\Http\Message\UriInterface|array|string|null $url An array specifying any of the following: + * 'controller', 'action', 'plugin' additionally, you can provide routed + * elements or query string parameters. If string it can be name any valid url + * string or it can be an UriInterface instance. + * @param bool $full If true, the full base URL will be prepended to the result. + * Default is false. + * @return string Full translated URL with base path. + * @throws \Cake\Core\Exception\CakeException When the route name is not found + * @see \Cake\Routing\Router::url() + * @since 4.5.0 */ -if (!getenv('CAKE_DISABLE_GLOBAL_FUNCS')) { - include 'functions_global.php'; +function url(UriInterface|array|string|null $url = null, bool $full = false): string +{ + return Router::url($url, $full); } diff --git a/app/vendor/cakephp/cakephp/src/Routing/functions_global.php b/app/vendor/cakephp/cakephp/src/Routing/functions_global.php index e13958b0c..b893ebd82 100644 --- a/app/vendor/cakephp/cakephp/src/Routing/functions_global.php +++ b/app/vendor/cakephp/cakephp/src/Routing/functions_global.php @@ -14,7 +14,9 @@ * @since 4.1.0 * @license https://opensource.org/licenses/mit-license.php MIT License */ +// phpcs:disable PSR1.Files.SideEffects +use Psr\Http\Message\UriInterface; use function Cake\Routing\url as cakeUrl; use function Cake\Routing\urlArray as cakeUrlArray; @@ -33,7 +35,7 @@ * @see \Cake\Routing\Router::url() * @since 4.5.0 */ - function url($url = null, bool $full = false): string + function url(UriInterface|array|string|null $url = null, bool $full = false): string { return cakeUrl($url, $full); } diff --git a/app/vendor/cakephp/cakephp/src/Shell/Task/CommandTask.php b/app/vendor/cakephp/cakephp/src/Shell/Task/CommandTask.php deleted file mode 100644 index 1b945aa6c..000000000 --- a/app/vendor/cakephp/cakephp/src/Shell/Task/CommandTask.php +++ /dev/null @@ -1,130 +0,0 @@ - null, 'app' => null]; - - $appPath = App::classPath('Shell'); - $shellList = $this->_findShells($shellList, $appPath[0], 'app', $skipFiles); - - $appPath = App::classPath('Command'); - $shellList = $this->_findShells($shellList, $appPath[0], 'app', $skipFiles); - - $skipCore = array_merge($skipFiles, $hiddenCommands, $shellList['app']); - $corePath = dirname(__DIR__); - $shellList = $this->_findShells($shellList, $corePath, 'CORE', $skipCore); - - $corePath = dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR . 'Command'; - $shellList = $this->_findShells($shellList, $corePath, 'CORE', $skipCore); - - foreach ($plugins as $plugin) { - $pluginPath = Plugin::classPath($plugin) . 'Shell'; - $shellList = $this->_findShells($shellList, $pluginPath, $plugin, []); - } - - return array_filter($shellList); - } - - /** - * Find shells in $path and add them to $shellList - * - * @param array $shellList The shell listing array. - * @param string $path The path to look in. - * @param string $key The key to add shells to - * @param array $skip A list of commands to exclude. - * @return array The updated list of shells. - */ - protected function _findShells(array $shellList, string $path, string $key, array $skip): array - { - $shells = $this->_scanDir($path); - - return $this->_appendShells($key, $shells, $shellList, $skip); - } - - /** - * Scan the provided paths for shells, and append them into $shellList - * - * @param string $type The type of object. - * @param array $shells The shell names. - * @param array $shellList List of shells. - * @param array $skip List of command names to skip. - * @return array The updated $shellList - */ - protected function _appendShells(string $type, array $shells, array $shellList, array $skip): array - { - $shellList[$type] = $shellList[$type] ?? []; - - foreach ($shells as $shell) { - $name = Inflector::underscore(preg_replace('/(Shell|Command)$/', '', $shell)); - if (!in_array($name, $skip, true)) { - $shellList[$type][] = $name; - } - } - sort($shellList[$type]); - - return $shellList; - } - - /** - * Scan a directory for .php files and return the class names that - * should be within them. - * - * @param string $dir The directory to read. - * @return array The list of shell classnames based on conventions. - */ - protected function _scanDir(string $dir): array - { - if (!is_dir($dir)) { - return []; - } - - $fs = new Filesystem(); - $files = $fs->find($dir, '/\.php$/'); - - $shells = []; - foreach ($files as $file) { - $shells[] = $file->getBasename('.php'); - } - - sort($shells); - - return $shells; - } -} diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/ConnectionHelper.php b/app/vendor/cakephp/cakephp/src/TestSuite/ConnectionHelper.php index f696d6d12..be96b8d16 100644 --- a/app/vendor/cakephp/cakephp/src/TestSuite/ConnectionHelper.php +++ b/app/vendor/cakephp/cakephp/src/TestSuite/ConnectionHelper.php @@ -16,7 +16,8 @@ namespace Cake\TestSuite; use Cake\Database\Connection; -use Cake\Database\DriverInterface; +use Cake\Database\DriverFeatureEnum; +use Cake\Database\Log\QueryLogger; use Cake\Datasource\ConnectionManager; use Closure; @@ -38,7 +39,7 @@ class ConnectionHelper * * @return void */ - public function addTestAliases(): void + public static function addTestAliases(): void { ConnectionManager::alias('test', 'default'); foreach (ConnectionManager::configured() as $connection) { @@ -46,7 +47,7 @@ public function addTestAliases(): void continue; } - if (strpos($connection, 'test_') === 0) { + if (str_starts_with($connection, 'test_')) { $original = substr($connection, 5); ConnectionManager::alias($connection, $original); } else { @@ -62,13 +63,18 @@ public function addTestAliases(): void * @param array|null $connections Connection names or null for all. * @return void */ - public function enableQueryLogging(?array $connections = null): void + public static function enableQueryLogging(?array $connections = null): void { - $connections = $connections ?? ConnectionManager::configured(); + $connections ??= ConnectionManager::configured(); foreach ($connections as $connection) { $connection = ConnectionManager::get($connection); - if ($connection instanceof Connection) { - $connection->enableQueryLogging(); + $message = '--Starting test run ' . date('Y-m-d H:i:s'); + if ( + $connection instanceof Connection && + $connection->getDriver()->log($message) === false + ) { + $connection->getDriver()->setLogger(new QueryLogger()); + $connection->getDriver()->log($message); } } } @@ -80,34 +86,31 @@ public function enableQueryLogging(?array $connections = null): void * @param array|null $tables List of tables names or null for all. * @return void */ - public function dropTables(string $connectionName, ?array $tables = null): void + public static function dropTables(string $connectionName, ?array $tables = null): void { - /** @var \Cake\Database\Connection $connection */ $connection = ConnectionManager::get($connectionName); + assert($connection instanceof Connection); $collection = $connection->getSchemaCollection(); + $allTables = $collection->listTablesWithoutViews(); - if (method_exists($collection, 'listTablesWithoutViews')) { - $allTables = $collection->listTablesWithoutViews(); - } else { - $allTables = $collection->listTables(); - } + // Skip special tables. + // spatial_ref_sys - postgis and it is undroppable. + $skip = ['spatial_ref_sys']; + $allTables = array_diff($allTables, $skip); $tables = $tables !== null ? array_intersect($tables, $allTables) : $allTables; - $schemas = array_map(function ($table) use ($collection) { - return $collection->describe($table); - }, $tables); + /** @var array<\Cake\Database\Schema\TableSchema> $schemas Specify type for psalm */ + $schemas = array_map(fn($table) => $collection->describe($table), $tables); $dialect = $connection->getDriver()->schemaDialect(); - /** @var \Cake\Database\Schema\TableSchema $schema */ foreach ($schemas as $schema) { foreach ($dialect->dropConstraintSql($schema) as $statement) { - $connection->execute($statement)->closeCursor(); + $connection->execute($statement); } } - /** @var \Cake\Database\Schema\TableSchema $schema */ foreach ($schemas as $schema) { foreach ($dialect->dropTableSql($schema) as $statement) { - $connection->execute($statement)->closeCursor(); + $connection->execute($statement); } } } @@ -119,24 +122,22 @@ public function dropTables(string $connectionName, ?array $tables = null): void * @param array|null $tables List of tables names or null for all. * @return void */ - public function truncateTables(string $connectionName, ?array $tables = null): void + public static function truncateTables(string $connectionName, ?array $tables = null): void { - /** @var \Cake\Database\Connection $connection */ $connection = ConnectionManager::get($connectionName); + assert($connection instanceof Connection); $collection = $connection->getSchemaCollection(); $allTables = $collection->listTablesWithoutViews(); $tables = $tables !== null ? array_intersect($tables, $allTables) : $allTables; - $schemas = array_map(function ($table) use ($collection) { - return $collection->describe($table); - }, $tables); + /** @var array<\Cake\Database\Schema\TableSchema> $schemas Specify type for psalm */ + $schemas = array_map(fn($table) => $collection->describe($table), $tables); - $this->runWithoutConstraints($connection, function (Connection $connection) use ($schemas): void { + self::runWithoutConstraints($connection, function (Connection $connection) use ($schemas): void { $dialect = $connection->getDriver()->schemaDialect(); - /** @var \Cake\Database\Schema\TableSchema $schema */ foreach ($schemas as $schema) { foreach ($dialect->truncateTableSql($schema) as $statement) { - $connection->execute($statement)->closeCursor(); + $connection->execute($statement); } } }); @@ -149,17 +150,13 @@ public function truncateTables(string $connectionName, ?array $tables = null): v * @param \Closure $callback callback * @return void */ - public function runWithoutConstraints(Connection $connection, Closure $callback): void + public static function runWithoutConstraints(Connection $connection, Closure $callback): void { - if ($connection->getDriver()->supports(DriverInterface::FEATURE_DISABLE_CONSTRAINT_WITHOUT_TRANSACTION)) { - $connection->disableConstraints(function (Connection $connection) use ($callback): void { - $callback($connection); - }); + if ($connection->getDriver()->supports(DriverFeatureEnum::DISABLE_CONSTRAINT_WITHOUT_TRANSACTION)) { + $connection->disableConstraints(fn(Connection $connection) => $callback($connection)); } else { $connection->transactional(function (Connection $connection) use ($callback): void { - $connection->disableConstraints(function (Connection $connection) use ($callback): void { - $callback($connection); - }); + $connection->disableConstraints(fn(Connection $connection) => $callback($connection)); }); } } diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/ConsoleIntegrationTestCase.php b/app/vendor/cakephp/cakephp/src/TestSuite/ConsoleIntegrationTestCase.php deleted file mode 100644 index 09c2252d8..000000000 --- a/app/vendor/cakephp/cakephp/src/TestSuite/ConsoleIntegrationTestCase.php +++ /dev/null @@ -1,29 +0,0 @@ - */ - public function getMessages() + public function getMessages(): array { $messages = TestEmailTransport::getMessages(); diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/MailContains.php b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/MailContains.php index 74ba2aace..9c96c19a5 100644 --- a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/MailContains.php +++ b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/MailContains.php @@ -28,7 +28,7 @@ class MailContains extends MailConstraintBase * * @var string|null */ - protected $type; + protected ?string $type = null; /** * Checks constraint @@ -36,7 +36,7 @@ class MailContains extends MailConstraintBase * @param mixed $other Constraint check * @return bool */ - public function matches($other): bool + public function matches(mixed $other): bool { $other = preg_quote($other, '/'); $messages = $this->getMessages(); @@ -44,7 +44,7 @@ public function matches($other): bool $method = $this->getTypeMethod(); $message = $message->$method(); - if (preg_match("/$other/", $message) > 0) { + if (preg_match("/{$other}/", $message) > 0) { return true; } } diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/MailContainsAttachment.php b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/MailContainsAttachment.php index 731612482..7ed1f54d8 100644 --- a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/MailContainsAttachment.php +++ b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/MailContainsAttachment.php @@ -29,17 +29,17 @@ class MailContainsAttachment extends MailContains * @param mixed $other Constraint check * @return bool */ - public function matches($other): bool + public function matches(mixed $other): bool { [$expectedFilename, $expectedFileInfo] = $other; $messages = $this->getMessages(); foreach ($messages as $message) { foreach ($message->getAttachments() as $filename => $fileInfo) { - if ($filename === $expectedFilename && empty($expectedFileInfo)) { + if ($filename === $expectedFilename && !$expectedFileInfo) { return true; } - if (!empty($expectedFileInfo) && array_intersect($expectedFileInfo, $fileInfo) === $expectedFileInfo) { + if ($expectedFileInfo && array_intersect($expectedFileInfo, $fileInfo) === $expectedFileInfo) { return true; } } @@ -68,10 +68,10 @@ public function toString(): string * @param mixed $other Value * @return string */ - protected function failureDescription($other): string + protected function failureDescription(mixed $other): string { [$expectedFilename] = $other; - return '\'' . $expectedFilename . '\' ' . $this->toString(); + return "'" . $expectedFilename . "' " . $this->toString(); } } diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/MailContainsHtml.php b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/MailContainsHtml.php index 70f5c869e..58f2195b0 100644 --- a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/MailContainsHtml.php +++ b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/MailContainsHtml.php @@ -28,7 +28,7 @@ class MailContainsHtml extends MailContains /** * @inheritDoc */ - protected $type = Message::MESSAGE_HTML; + protected ?string $type = Message::MESSAGE_HTML; /** * Assertion message string diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/MailContainsText.php b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/MailContainsText.php index a2c047a19..8529cd5d0 100644 --- a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/MailContainsText.php +++ b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/MailContainsText.php @@ -28,7 +28,7 @@ class MailContainsText extends MailContains /** * @inheritDoc */ - protected $type = Message::MESSAGE_TEXT; + protected ?string $type = Message::MESSAGE_TEXT; /** * Assertion message string diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/MailCount.php b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/MailCount.php index d1242de59..69cec28d1 100644 --- a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/MailCount.php +++ b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/MailCount.php @@ -29,7 +29,7 @@ class MailCount extends MailConstraintBase * @param mixed $other Constraint check * @return bool */ - public function matches($other): bool + public function matches(mixed $other): bool { return count($this->getMessages()) === $other; } diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/MailSentFrom.php b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/MailSentFrom.php index 6dea5243a..b617c2dfb 100644 --- a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/MailSentFrom.php +++ b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/MailSentFrom.php @@ -26,7 +26,7 @@ class MailSentFrom extends MailSentWith /** * @var string */ - protected $method = 'from'; + protected string $method = 'from'; /** * Assertion message string diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/MailSentTo.php b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/MailSentTo.php index 07126e3dc..afb01d0e4 100644 --- a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/MailSentTo.php +++ b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/MailSentTo.php @@ -26,7 +26,7 @@ class MailSentTo extends MailSentWith /** * @var string */ - protected $method = 'to'; + protected string $method = 'to'; /** * Assertion message string diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/MailSentWith.php b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/MailSentWith.php index 16a9e761c..be3badc83 100644 --- a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/MailSentWith.php +++ b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/MailSentWith.php @@ -26,7 +26,7 @@ class MailSentWith extends MailConstraintBase /** * @var string */ - protected $method; + protected string $method; /** * Constructor @@ -50,7 +50,7 @@ public function __construct(?int $at = null, ?string $method = null) * @param mixed $other Constraint check * @return bool */ - public function matches($other): bool + public function matches(mixed $other): bool { $emails = $this->getMessages(); foreach ($emails as $email) { diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/MailSubjectContains.php b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/MailSubjectContains.php index ca5d7c93f..a65a02b25 100644 --- a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/MailSubjectContains.php +++ b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/MailSubjectContains.php @@ -31,17 +31,17 @@ class MailSubjectContains extends MailConstraintBase * @param mixed $other Constraint check * @return bool */ - public function matches($other): bool + public function matches(mixed $other): bool { if (!is_string($other)) { throw new InvalidArgumentException( - 'Invalid data type, must be a string.' + 'Invalid data type, must be a string.', ); } $messages = $this->getMessages(); foreach ($messages as $message) { $subject = $message->getOriginalSubject(); - if (strpos($subject, $other) !== false) { + if (str_contains($subject, $other)) { return true; } } diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/NoMailSent.php b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/NoMailSent.php index 0d7d48a5c..f714f7b03 100644 --- a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/NoMailSent.php +++ b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Email/NoMailSent.php @@ -29,9 +29,9 @@ class NoMailSent extends MailConstraintBase * @param mixed $other Constraint check * @return bool */ - public function matches($other): bool + public function matches(mixed $other): bool { - return count($this->getMessages()) === 0; + return $this->getMessages() === []; } /** @@ -50,7 +50,7 @@ public function toString(): string * @param mixed $other Value * @return string */ - protected function failureDescription($other): string + protected function failureDescription(mixed $other): string { return $this->toString(); } diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/EventFired.php b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/EventFired.php index a02964f93..5ec467c3b 100644 --- a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/EventFired.php +++ b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/EventFired.php @@ -31,7 +31,7 @@ class EventFired extends Constraint * * @var \Cake\Event\EventManager */ - protected $_eventManager; + protected EventManager $_eventManager; /** * Constructor @@ -44,7 +44,7 @@ public function __construct(EventManager $eventManager) if ($this->_eventManager->getEventList() === null) { throw new AssertionFailedError( - 'The event manager you are asserting against is not configured to track events.' + 'The event manager you are asserting against is not configured to track events.', ); } } @@ -55,7 +55,7 @@ public function __construct(EventManager $eventManager) * @param mixed $other Constraint check * @return bool */ - public function matches($other): bool + public function matches(mixed $other): bool { $list = $this->_eventManager->getEventList(); diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/EventFiredWith.php b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/EventFiredWith.php index da8b96707..e0515b08f 100644 --- a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/EventFiredWith.php +++ b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/EventFiredWith.php @@ -21,21 +21,21 @@ class EventFiredWith extends Constraint * * @var \Cake\Event\EventManager */ - protected $_eventManager; + protected EventManager $_eventManager; /** * Event data key * * @var string */ - protected $_dataKey; + protected string $_dataKey; /** * Event data value * * @var mixed */ - protected $_dataValue; + protected mixed $_dataValue; /** * Constructor @@ -44,7 +44,7 @@ class EventFiredWith extends Constraint * @param string $dataKey Data key * @param mixed $dataValue Data value */ - public function __construct(EventManager $eventManager, string $dataKey, $dataValue) + public function __construct(EventManager $eventManager, string $dataKey, mixed $dataValue) { $this->_eventManager = $eventManager; $this->_dataKey = $dataKey; @@ -52,7 +52,7 @@ public function __construct(EventManager $eventManager, string $dataKey, $dataVa if ($this->_eventManager->getEventList() === null) { throw new AssertionFailedError( - 'The event manager you are asserting against is not configured to track events.' + 'The event manager you are asserting against is not configured to track events.', ); } } @@ -64,7 +64,7 @@ public function __construct(EventManager $eventManager, string $dataKey, $dataVa * @return bool * @throws \PHPUnit\Framework\AssertionFailedError */ - public function matches($other): bool + public function matches(mixed $other): bool { $firedEvents = []; $list = $this->_eventManager->getEventList(); @@ -85,14 +85,14 @@ public function matches($other): bool return false; } - /** @var array<\Cake\Event\EventInterface> $events */ + /** @var array<\Cake\Event\EventInterface> $events */ $events = $eventGroup[$other]; if (count($events) > 1) { throw new AssertionFailedError(sprintf( - 'Event "%s" was fired %d times, cannot make data assertion', + 'Event `%s` was fired %d times, cannot make data assertion', $other, - count($events) + count($events), )); } diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/BodyContains.php b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/BodyContains.php index b63c482d6..b9f7ede17 100644 --- a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/BodyContains.php +++ b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/BodyContains.php @@ -27,7 +27,7 @@ class BodyContains extends ResponseBase /** * @var bool */ - protected $ignoreCase; + protected bool $ignoreCase; /** * Constructor. @@ -47,6 +47,7 @@ public function __construct(ResponseInterface $response, bool $ignoreCase = fals * * @param mixed $other Expected type * @return bool + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint */ public function matches($other): bool { diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/BodyEmpty.php b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/BodyEmpty.php index c1b076a77..0ce51b2b9 100644 --- a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/BodyEmpty.php +++ b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/BodyEmpty.php @@ -27,6 +27,7 @@ class BodyEmpty extends ResponseBase * * @param mixed $other Expected type * @return bool + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint */ public function matches($other): bool { @@ -49,7 +50,7 @@ public function toString(): string * @param mixed $other Value * @return string */ - protected function failureDescription($other): string + protected function failureDescription(mixed $other): string { return $this->toString(); } diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/BodyEquals.php b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/BodyEquals.php index 19e61cd65..f030c9690 100644 --- a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/BodyEquals.php +++ b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/BodyEquals.php @@ -27,6 +27,7 @@ class BodyEquals extends ResponseBase * * @param mixed $other Expected type * @return bool + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint */ public function matches($other): bool { diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/BodyNotContains.php b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/BodyNotContains.php index 66fe3fef4..14e0c4668 100644 --- a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/BodyNotContains.php +++ b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/BodyNotContains.php @@ -27,6 +27,7 @@ class BodyNotContains extends BodyContains * * @param mixed $other Expected type * @return bool + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint */ public function matches($other): bool { diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/BodyNotEmpty.php b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/BodyNotEmpty.php index 8ae4bfc05..1d12afd6b 100644 --- a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/BodyNotEmpty.php +++ b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/BodyNotEmpty.php @@ -27,6 +27,7 @@ class BodyNotEmpty extends BodyEmpty * * @param mixed $other Expected type * @return bool + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint */ public function matches($other): bool { diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/BodyNotEquals.php b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/BodyNotEquals.php index 1cd4385ce..4dd19e9c9 100644 --- a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/BodyNotEquals.php +++ b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/BodyNotEquals.php @@ -27,6 +27,7 @@ class BodyNotEquals extends BodyEquals * * @param mixed $other Expected type * @return bool + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint */ public function matches($other): bool { diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/BodyNotRegExp.php b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/BodyNotRegExp.php index 6bb16968f..d270053be 100644 --- a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/BodyNotRegExp.php +++ b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/BodyNotRegExp.php @@ -27,6 +27,7 @@ class BodyNotRegExp extends BodyRegExp * * @param mixed $other Expected pattern * @return bool + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint */ public function matches($other): bool { diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/BodyRegExp.php b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/BodyRegExp.php index bf35f12cc..49992a275 100644 --- a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/BodyRegExp.php +++ b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/BodyRegExp.php @@ -27,6 +27,7 @@ class BodyRegExp extends ResponseBase * * @param mixed $other Expected pattern * @return bool + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint */ public function matches($other): bool { @@ -47,7 +48,7 @@ public function toString(): string * @param mixed $other Expected * @return string */ - public function failureDescription($other): string + public function failureDescription(mixed $other): string { return '`' . $other . '`' . ' ' . $this->toString(); } diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/ContentType.php b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/ContentType.php index 7a21522c6..320cd56b6 100644 --- a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/ContentType.php +++ b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/ContentType.php @@ -15,6 +15,9 @@ */ namespace Cake\TestSuite\Constraint\Response; +use Cake\Http\MimeType; +use Psr\Http\Message\ResponseInterface; + /** * ContentType * @@ -25,19 +28,20 @@ class ContentType extends ResponseBase /** * @var \Cake\Http\Response */ - protected $response; + protected ResponseInterface $response; /** * Checks assertion * * @param mixed $other Expected type * @return bool + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint */ public function matches($other): bool { - $alias = $this->response->getMimeType($other); - if ($alias !== false) { - $other = $alias; + $mimeType = MimeType::getMimeType($other); + if ($mimeType !== null) { + $other = $mimeType; } return $other === $this->response->getType(); diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/CookieEncryptedEquals.php b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/CookieEncryptedEquals.php index 3bc281407..163b69c65 100644 --- a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/CookieEncryptedEquals.php +++ b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/CookieEncryptedEquals.php @@ -17,6 +17,7 @@ use Cake\Http\Response; use Cake\Utility\CookieCryptTrait; +use Psr\Http\Message\ResponseInterface; /** * CookieEncryptedEquals @@ -30,17 +31,17 @@ class CookieEncryptedEquals extends CookieEquals /** * @var \Cake\Http\Response */ - protected $response; + protected ResponseInterface $response; /** * @var string */ - protected $key; + protected string $key; /** * @var string */ - protected $mode; + protected string $mode; /** * Constructor. @@ -63,6 +64,7 @@ public function __construct(?Response $response, string $cookieName, string $mod * * @param mixed $other Expected content * @return bool + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint */ public function matches($other): bool { @@ -78,7 +80,7 @@ public function matches($other): bool */ public function toString(): string { - return sprintf('is encrypted in cookie \'%s\'', $this->cookieName); + return sprintf("is encrypted in cookie '%s'", $this->cookieName); } /** diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/CookieEquals.php b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/CookieEquals.php index 2edbec970..b17f7ee93 100644 --- a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/CookieEquals.php +++ b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/CookieEquals.php @@ -16,6 +16,7 @@ namespace Cake\TestSuite\Constraint\Response; use Cake\Http\Response; +use Psr\Http\Message\ResponseInterface; /** * CookieEquals @@ -27,12 +28,12 @@ class CookieEquals extends ResponseBase /** * @var \Cake\Http\Response */ - protected $response; + protected ResponseInterface $response; /** * @var string */ - protected $cookieName; + protected string $cookieName; /** * Constructor. @@ -52,6 +53,7 @@ public function __construct(?Response $response, string $cookieName) * * @param mixed $other Expected content * @return bool + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint */ public function matches($other): bool { @@ -67,6 +69,6 @@ public function matches($other): bool */ public function toString(): string { - return sprintf('is in cookie \'%s\'', $this->cookieName); + return sprintf("is in cookie '%s'", $this->cookieName); } } diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/CookieNotSet.php b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/CookieNotSet.php index f67e58102..6bead8591 100644 --- a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/CookieNotSet.php +++ b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/CookieNotSet.php @@ -27,6 +27,7 @@ class CookieNotSet extends CookieSet * * @param mixed $other Expected content * @return bool + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint */ public function matches($other): bool { diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/CookieSet.php b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/CookieSet.php index bd31234f4..202f66a3a 100644 --- a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/CookieSet.php +++ b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/CookieSet.php @@ -15,6 +15,8 @@ */ namespace Cake\TestSuite\Constraint\Response; +use Psr\Http\Message\ResponseInterface; + /** * CookieSet * @@ -25,13 +27,14 @@ class CookieSet extends ResponseBase /** * @var \Cake\Http\Response */ - protected $response; + protected ResponseInterface $response; /** * Checks assertion * * @param mixed $other Expected content * @return bool + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint */ public function matches($other): bool { diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/FileSent.php b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/FileSent.php index 6143eab4c..076e96e68 100644 --- a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/FileSent.php +++ b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/FileSent.php @@ -15,6 +15,8 @@ */ namespace Cake\TestSuite\Constraint\Response; +use Psr\Http\Message\ResponseInterface; + /** * FileSent * @@ -25,13 +27,14 @@ class FileSent extends ResponseBase /** * @var \Cake\Http\Response */ - protected $response; + protected ResponseInterface $response; /** * Checks assertion * * @param mixed $other Expected type * @return bool + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint */ public function matches($other): bool { @@ -54,7 +57,7 @@ public function toString(): string * @param mixed $other Value * @return string */ - protected function failureDescription($other): string + protected function failureDescription(mixed $other): string { return $this->toString(); } diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/FileSentAs.php b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/FileSentAs.php index 7ea144e84..09acde831 100644 --- a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/FileSentAs.php +++ b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/FileSentAs.php @@ -15,6 +15,8 @@ */ namespace Cake\TestSuite\Constraint\Response; +use Psr\Http\Message\ResponseInterface; + /** * FileSentAs * @@ -25,13 +27,14 @@ class FileSentAs extends ResponseBase /** * @var \Cake\Http\Response */ - protected $response; + protected ResponseInterface $response; /** * Checks assertion * * @param mixed $other Expected type * @return bool + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint */ public function matches($other): bool { diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/HeaderContains.php b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/HeaderContains.php index 1fef513bd..658f3a6a0 100644 --- a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/HeaderContains.php +++ b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/HeaderContains.php @@ -27,6 +27,7 @@ class HeaderContains extends HeaderEquals * * @param mixed $other Expected content * @return bool + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint */ public function matches($other): bool { @@ -41,9 +42,9 @@ public function matches($other): bool public function toString(): string { return sprintf( - 'is in header \'%s\' (`%s`)', + "is in header '%s' (`%s`)", $this->headerName, - $this->response->getHeaderLine($this->headerName) + $this->response->getHeaderLine($this->headerName), ); } } diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/HeaderEquals.php b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/HeaderEquals.php index a00756393..461f788b0 100644 --- a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/HeaderEquals.php +++ b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/HeaderEquals.php @@ -27,7 +27,7 @@ class HeaderEquals extends ResponseBase /** * @var string */ - protected $headerName; + protected string $headerName; /** * Constructor. @@ -47,6 +47,7 @@ public function __construct(ResponseInterface $response, string $headerName) * * @param mixed $other Expected content * @return bool + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint */ public function matches($other): bool { @@ -62,6 +63,6 @@ public function toString(): string { $responseHeader = $this->response->getHeaderLine($this->headerName); - return sprintf('equals content in header \'%s\' (`%s`)', $this->headerName, $responseHeader); + return sprintf("equals content in header '%s' (`%s`)", $this->headerName, $responseHeader); } } diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/HeaderNotContains.php b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/HeaderNotContains.php index 3717e72f5..3929c7dfd 100644 --- a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/HeaderNotContains.php +++ b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/HeaderNotContains.php @@ -27,6 +27,7 @@ class HeaderNotContains extends HeaderContains * * @param mixed $other Expected content * @return bool + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint */ public function matches($other): bool { @@ -43,7 +44,7 @@ public function toString(): string return sprintf( "is not in header '%s' (`%s`)", $this->headerName, - $this->response->getHeaderLine($this->headerName) + $this->response->getHeaderLine($this->headerName), ); } } diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/HeaderNotSet.php b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/HeaderNotSet.php index 2e655925f..f38b7e749 100644 --- a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/HeaderNotSet.php +++ b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/HeaderNotSet.php @@ -27,6 +27,7 @@ class HeaderNotSet extends HeaderSet * * @param mixed $other Expected content * @return bool + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint */ public function matches($other): bool { diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/HeaderSet.php b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/HeaderSet.php index 4ec5d2dad..073b6e145 100644 --- a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/HeaderSet.php +++ b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/HeaderSet.php @@ -27,7 +27,7 @@ class HeaderSet extends ResponseBase /** * @var string */ - protected $headerName; + protected string $headerName; /** * Constructor. @@ -47,6 +47,7 @@ public function __construct(?ResponseInterface $response, string $headerName) * * @param mixed $other Expected content * @return bool + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint */ public function matches($other): bool { @@ -60,7 +61,7 @@ public function matches($other): bool */ public function toString(): string { - return sprintf('response has header \'%s\'', $this->headerName); + return sprintf("response has header '%s'", $this->headerName); } /** @@ -69,7 +70,7 @@ public function toString(): string * @param mixed $other Value * @return string */ - protected function failureDescription($other): string + protected function failureDescription(mixed $other): string { return $this->toString(); } diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/ResponseBase.php b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/ResponseBase.php index 5b43b0124..7c06e6d03 100644 --- a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/ResponseBase.php +++ b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/ResponseBase.php @@ -30,7 +30,7 @@ abstract class ResponseBase extends Constraint /** * @var \Psr\Http\Message\ResponseInterface */ - protected $response; + protected ResponseInterface $response; /** * Constructor diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/StatusCode.php b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/StatusCode.php index 2ef29e4f0..ad033df9d 100644 --- a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/StatusCode.php +++ b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/StatusCode.php @@ -38,7 +38,7 @@ public function toString(): string * @param mixed $other Expected code * @return string */ - public function failureDescription($other): string + public function failureDescription(mixed $other): string { return '`' . $other . '` ' . $this->toString(); } diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/StatusCodeBase.php b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/StatusCodeBase.php index 7e9ae6488..2342368f3 100644 --- a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/StatusCodeBase.php +++ b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/StatusCodeBase.php @@ -25,14 +25,14 @@ abstract class StatusCodeBase extends ResponseBase /** * @var array|int */ - protected $code; + protected array|int $code; /** * Check assertion * * @param array|int $other Array of min/max status codes, or a single code * @return bool - * @psalm-suppress MoreSpecificImplementedParamType + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint */ public function matches($other): bool { @@ -65,9 +65,8 @@ protected function statusCodeBetween(int $min, int $max): bool * @param mixed $other Value * @return string */ - protected function failureDescription($other): string + protected function failureDescription(mixed $other): string { - /** @psalm-suppress InternalMethod */ return $this->toString(); } } diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/StatusError.php b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/StatusError.php index 30448297b..3722e8b67 100644 --- a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/StatusError.php +++ b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/StatusError.php @@ -25,7 +25,7 @@ class StatusError extends StatusCodeBase /** * @var array|int */ - protected $code = [400, 429]; + protected array|int $code = [400, 429]; /** * Assertion message diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/StatusFailure.php b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/StatusFailure.php index 71d23ca77..bcc9e9cb5 100644 --- a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/StatusFailure.php +++ b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/StatusFailure.php @@ -25,7 +25,7 @@ class StatusFailure extends StatusCodeBase /** * @var array|int */ - protected $code = [500, 505]; + protected array|int $code = [500, 505]; /** * Assertion message diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/StatusOk.php b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/StatusOk.php index 1d2496402..d01bfdc19 100644 --- a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/StatusOk.php +++ b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/StatusOk.php @@ -25,7 +25,7 @@ class StatusOk extends StatusCodeBase /** * @var array|int */ - protected $code = [200, 204]; + protected array|int $code = [200, 204]; /** * Assertion message diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/StatusSuccess.php b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/StatusSuccess.php index 6248457fd..0305ac2cf 100644 --- a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/StatusSuccess.php +++ b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Response/StatusSuccess.php @@ -25,7 +25,7 @@ class StatusSuccess extends StatusCodeBase /** * @var array|int */ - protected $code = [200, 308]; + protected array|int $code = [200, 308]; /** * Assertion message diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Session/FlashParamEquals.php b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Session/FlashParamEquals.php index 592ceecae..2e89dfcff 100644 --- a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Session/FlashParamEquals.php +++ b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Session/FlashParamEquals.php @@ -30,22 +30,22 @@ class FlashParamEquals extends Constraint /** * @var \Cake\Http\Session */ - protected $session; + protected Session $session; /** * @var string */ - protected $key; + protected string $key; /** * @var string */ - protected $param; + protected string $param; /** * @var int|null */ - protected $at; + protected ?int $at = null; /** * Constructor @@ -75,15 +75,13 @@ public function __construct(?Session $session, string $key, string $param, ?int * @param mixed $other Value to compare with * @return bool */ - public function matches($other): bool + public function matches(mixed $other): bool { // Server::run calls Session::close at the end of the request. // Which means, that we cannot use Session object here to access the session data. // Call to Session::read will start new session (and will erase the data). - /** @psalm-suppress InvalidScalarArgument */ $messages = (array)Hash::get($_SESSION, 'Flash.' . $this->key); if ($this->at) { - /** @psalm-suppress InvalidScalarArgument */ $messages = [Hash::get($_SESSION, 'Flash.' . $this->key . '.' . $this->at)]; } @@ -107,9 +105,9 @@ public function matches($other): bool public function toString(): string { if ($this->at !== null) { - return sprintf('is in \'%s\' %s #%d', $this->key, $this->param, $this->at); + return sprintf("is in '%s' %s #%d", $this->key, $this->param, $this->at); } - return sprintf('is in \'%s\' %s', $this->key, $this->param); + return sprintf("is in '%s' %s", $this->key, $this->param); } } diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Session/SessionEquals.php b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Session/SessionEquals.php index 577d8d7df..eb4ddbf36 100644 --- a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Session/SessionEquals.php +++ b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Session/SessionEquals.php @@ -28,7 +28,7 @@ class SessionEquals extends Constraint /** * @var string */ - protected $path; + protected string $path; /** * Constructor @@ -46,12 +46,11 @@ public function __construct(string $path) * @param mixed $other Value to compare with * @return bool */ - public function matches($other): bool + public function matches(mixed $other): bool { // Server::run calls Session::close at the end of the request. // Which means, that we cannot use Session object here to access the session data. // Call to Session::read will start new session (and will erase the data). - /** @psalm-suppress InvalidScalarArgument */ return Hash::get($_SESSION, $this->path) === $other; } @@ -62,6 +61,6 @@ public function matches($other): bool */ public function toString(): string { - return sprintf('is in session path \'%s\'', $this->path); + return sprintf("is in session path '%s'", $this->path); } } diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Session/SessionHasKey.php b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Session/SessionHasKey.php index d7ffa7872..7b81d049f 100644 --- a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Session/SessionHasKey.php +++ b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/Session/SessionHasKey.php @@ -28,7 +28,7 @@ class SessionHasKey extends Constraint /** * @var string */ - protected $path; + protected string $path; /** * Constructor @@ -46,13 +46,12 @@ public function __construct(string $path) * @param mixed $other Value to compare with * @return bool */ - public function matches($other): bool + public function matches(mixed $other): bool { // Server::run calls Session::close at the end of the request. // Which means, that we cannot use Session object here to access the session data. // Call to Session::read will start new session (and will erase the data). - /** @psalm-suppress InvalidScalarArgument */ - return Hash::check($_SESSION, $this->path) === true; + return Hash::check($_SESSION, $this->path); } /** diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/View/TemplateFileEquals.php b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/View/TemplateFileEquals.php index 1f7fff21d..c71f1eea0 100644 --- a/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/View/TemplateFileEquals.php +++ b/app/vendor/cakephp/cakephp/src/TestSuite/Constraint/View/TemplateFileEquals.php @@ -27,7 +27,7 @@ class TemplateFileEquals extends Constraint /** * @var string */ - protected $filename; + protected string $filename; /** * Constructor @@ -45,9 +45,9 @@ public function __construct(string $filename) * @param mixed $other Expected filename * @return bool */ - public function matches($other): bool + public function matches(mixed $other): bool { - return strpos($this->filename, $other) !== false; + return str_contains($this->filename, $other); } /** diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/ContainerStubTrait.php b/app/vendor/cakephp/cakephp/src/TestSuite/ContainerStubTrait.php deleted file mode 100644 index 8cd3f99bf..000000000 --- a/app/vendor/cakephp/cakephp/src/TestSuite/ContainerStubTrait.php +++ /dev/null @@ -1,10 +0,0 @@ -assertThat($address, new MailSentFrom(), $message); } diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Fixture/Extension/PHPUnitExtension.php b/app/vendor/cakephp/cakephp/src/TestSuite/Fixture/Extension/PHPUnitExtension.php new file mode 100644 index 000000000..df79cfddd --- /dev/null +++ b/app/vendor/cakephp/cakephp/src/TestSuite/Fixture/Extension/PHPUnitExtension.php @@ -0,0 +1,41 @@ +registerSubscriber( + new PHPUnitStartedSubscriber(), + ); + } +} diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Fixture/Extension/PHPUnitStartedSubscriber.php b/app/vendor/cakephp/cakephp/src/TestSuite/Fixture/Extension/PHPUnitStartedSubscriber.php new file mode 100644 index 000000000..a3734d5fe --- /dev/null +++ b/app/vendor/cakephp/cakephp/src/TestSuite/Fixture/Extension/PHPUnitStartedSubscriber.php @@ -0,0 +1,48 @@ + 'Console', + 'stream' => 'php://stderr', + 'scopes' => ['cake.database.queries'], + ]); + } + } +} diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Fixture/FixtureHelper.php b/app/vendor/cakephp/cakephp/src/TestSuite/Fixture/FixtureHelper.php index 852319d59..58d4bb9f8 100644 --- a/app/vendor/cakephp/cakephp/src/TestSuite/Fixture/FixtureHelper.php +++ b/app/vendor/cakephp/cakephp/src/TestSuite/Fixture/FixtureHelper.php @@ -19,7 +19,7 @@ use Cake\Core\Configure; use Cake\Core\Exception\CakeException; use Cake\Database\Connection; -use Cake\Database\DriverInterface; +use Cake\Database\DriverFeatureEnum; use Cake\Database\Schema\TableSchema; use Cake\Datasource\ConnectionInterface; use Cake\Datasource\ConnectionManager; @@ -46,7 +46,7 @@ public function loadFixtures(array $fixtureNames): array $fixtures = []; foreach ($fixtureNames as $fixtureName) { - if (strpos($fixtureName, '.')) { + if (str_contains($fixtureName, '.')) { [$type, $pathName] = explode('.', $fixtureName, 2); $path = explode('/', $pathName); $name = array_pop($path); @@ -75,19 +75,19 @@ public function loadFixtures(array $fixtureNames): array $additionalPath, $name . 'Fixture', ]; - /** @psalm-var class-string<\Cake\Datasource\FixtureInterface> */ + /** @var class-string<\Cake\Datasource\FixtureInterface> $className */ $className = implode('\\', array_filter($nameSegments)); } else { - /** @psalm-var class-string<\Cake\Datasource\FixtureInterface> */ + /** @var class-string<\Cake\Datasource\FixtureInterface> $className */ $className = $fixtureName; } if (isset($fixtures[$className])) { - throw new UnexpectedValueException("Found duplicate fixture `$fixtureName`."); + throw new UnexpectedValueException(sprintf('Found duplicate fixture `%s`.', $fixtureName)); } if (!class_exists($className)) { - throw new UnexpectedValueException("Could not find fixture `$fixtureName`."); + throw new UnexpectedValueException(sprintf('Could not find fixture `%s`.', $fixtureName)); } if (!isset($cachedFixtures[$className])) { @@ -139,12 +139,9 @@ public function insert(array $fixtures): void if ($sortedFixtures) { $this->insertConnection($connection, $sortedFixtures); } else { - $helper = new ConnectionHelper(); - $helper->runWithoutConstraints( + ConnectionHelper::runWithoutConstraints( $connection, - function (Connection $connection) use ($groupFixtures): void { - $this->insertConnection($connection, $groupFixtures); - } + fn(Connection $connection) => $this->insertConnection($connection, $groupFixtures), ); } } else { @@ -170,7 +167,7 @@ protected function insertConnection(ConnectionInterface $connection, array $fixt 'Unable to insert rows for table `%s`.' . " Fixture records might have invalid data or unknown constraints.\n%s", $fixture->sourceName(), - $exception->getMessage() + $exception->getMessage(), ); throw new CakeException($message); } @@ -189,7 +186,7 @@ public function truncate(array $fixtures): void $this->runPerConnection(function (ConnectionInterface $connection, array $groupFixtures): void { if ($connection instanceof Connection) { $sortedFixtures = null; - if ($connection->getDriver()->supports(DriverInterface::FEATURE_TRUNCATE_WITH_CONSTRAINTS)) { + if ($connection->getDriver()->supports(DriverFeatureEnum::TRUNCATE_WITH_CONSTRAINTS)) { $sortedFixtures = $this->sortByConstraint($connection, $groupFixtures); } @@ -199,9 +196,7 @@ public function truncate(array $fixtures): void $helper = new ConnectionHelper(); $helper->runWithoutConstraints( $connection, - function (Connection $connection) use ($groupFixtures): void { - $this->truncateConnection($connection, $groupFixtures); - } + fn(Connection $connection) => $this->truncateConnection($connection, $groupFixtures), ); } } else { @@ -225,9 +220,9 @@ protected function truncateConnection(ConnectionInterface $connection, array $fi } catch (PDOException $exception) { $message = sprintf( 'Unable to truncate table `%s`.' - . " Fixture records might have invalid data or unknown contraints.\n%s", + . " Fixture records might have invalid data or unknown constraints.\n%s", $fixture->sourceName(), - $exception->getMessage() + $exception->getMessage(), ); throw new CakeException($message); } @@ -254,7 +249,7 @@ protected function sortByConstraint(Connection $connection, array $fixtures): ?a } } - // Check if any fixtures reference another fixture with constrants + // Check if any fixtures reference another fixture with constraints // If they do, then there might be cross-dependencies which we don't support sorting foreach ($constrained as ['references' => $references]) { foreach ($references as $reference) { @@ -276,6 +271,7 @@ protected function sortByConstraint(Connection $connection, array $fixtures): ?a */ protected function getForeignReferences(Connection $connection, FixtureInterface $fixture): array { + /** @var array $schemas */ static $schemas = []; // Get and cache off the schema since TestFixture generates a fake schema based on $fields diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Fixture/FixtureInjector.php b/app/vendor/cakephp/cakephp/src/TestSuite/Fixture/FixtureInjector.php deleted file mode 100644 index 9abd73e98..000000000 --- a/app/vendor/cakephp/cakephp/src/TestSuite/Fixture/FixtureInjector.php +++ /dev/null @@ -1,127 +0,0 @@ -setDebug(in_array('--debug', $_SERVER['argv'], true)); - } - $this->_fixtureManager = $manager; - $this->_fixtureManager->shutDown(); - TestCase::$fixtureManager = $manager; - } - - /** - * Iterates the tests inside a test suite and creates the required fixtures as - * they were expressed inside each test case. - * - * @param \PHPUnit\Framework\TestSuite $suite The test suite - * @return void - */ - public function startTestSuite(TestSuite $suite): void - { - if (empty($this->_first)) { - deprecationWarning( - 'You are using the listener based PHPUnit integration. ' . - 'This fixture system is deprecated, and we recommend you ' . - 'upgrade to the extension based PHPUnit integration. ' . - 'See https://book.cakephp.org/4/en/appendices/fixture-upgrade.html', - 0 - ); - $this->_first = $suite; - } - } - - /** - * Destroys the fixtures created by the fixture manager at the end of the test - * suite run - * - * @param \PHPUnit\Framework\TestSuite $suite The test suite - * @return void - */ - public function endTestSuite(TestSuite $suite): void - { - if ($this->_first === $suite) { - $this->_fixtureManager->shutDown(); - } - } - - /** - * Adds fixtures to a test case when it starts. - * - * @param \PHPUnit\Framework\Test $test The test case - * @return void - */ - public function startTest(Test $test): void - { - if ($test instanceof TestCase) { - $this->_fixtureManager->fixturize($test); - $this->_fixtureManager->load($test); - } - } - - /** - * Unloads fixtures from the test case. - * - * @param \PHPUnit\Framework\Test $test The test case - * @param float $time current time - * @return void - */ - public function endTest(Test $test, float $time): void - { - if ($test instanceof TestCase) { - $this->_fixtureManager->unload($test); - } - } -} diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Fixture/FixtureManager.php b/app/vendor/cakephp/cakephp/src/TestSuite/Fixture/FixtureManager.php deleted file mode 100644 index be84722a1..000000000 --- a/app/vendor/cakephp/cakephp/src/TestSuite/Fixture/FixtureManager.php +++ /dev/null @@ -1,518 +0,0 @@ - - */ - protected $_loaded = []; - - /** - * Holds the fixture classes that where instantiated indexed by class name - * - * @var array<\Cake\Datasource\FixtureInterface> - */ - protected $_fixtureMap = []; - - /** - * A map of connection names and the fixture currently in it. - * - * @var array> - */ - protected $_insertionMap = []; - - /** - * List of TestCase class name that have been processed - * - * @var array - */ - protected $_processed = []; - - /** - * Is the test runner being run with `--debug` enabled. - * When true, fixture SQL will also be logged. - * - * @var bool - */ - protected $_debug = false; - - /** - * Modify the debug mode. - * - * @param bool $debug Whether fixture debug mode is enabled. - * @return void - */ - public function setDebug(bool $debug): void - { - $this->_debug = $debug; - } - - /** - * @param \Cake\TestSuite\TestCase $test Test case - * @return void - */ - public function fixturize(TestCase $test): void - { - $this->_initDb(); - if (!$test->getFixtures() || !empty($this->_processed[get_class($test)])) { - return; - } - $this->_loadFixtures($test); - $this->_processed[get_class($test)] = true; - } - - /** - * @return \Cake\Datasource\FixtureInterface[] - */ - public function loaded(): array - { - return $this->_loaded; - } - - /** - * @return array - */ - public function getInserted(): array - { - $inserted = []; - foreach ($this->_insertionMap as $fixtures) { - foreach ($fixtures as $fixture) { - /** @var \Cake\TestSuite\Fixture\TestFixture $fixture */ - $inserted[] = $fixture->table; - } - } - - return $inserted; - } - - /** - * Add aliases for all non test prefixed connections. - * - * This allows models to use the test connections without - * a pile of configuration work. - * - * @return void - */ - protected function _aliasConnections(): void - { - $connections = ConnectionManager::configured(); - ConnectionManager::alias('test', 'default'); - $map = []; - foreach ($connections as $connection) { - if ($connection === 'test' || $connection === 'default') { - continue; - } - if (isset($map[$connection])) { - continue; - } - if (strpos($connection, 'test_') === 0) { - $map[$connection] = substr($connection, 5); - } else { - $map['test_' . $connection] = $connection; - } - } - foreach ($map as $testConnection => $normal) { - ConnectionManager::alias($testConnection, $normal); - } - } - - /** - * Initializes this class with a DataSource object to use as default for all fixtures - * - * @return void - */ - protected function _initDb(): void - { - if ($this->_initialized) { - return; - } - $this->_aliasConnections(); - $this->_initialized = true; - } - - /** - * Looks for fixture files and instantiates the classes accordingly - * - * @param \Cake\TestSuite\TestCase $test The test suite to load fixtures for. - * @return void - * @throws \UnexpectedValueException when a referenced fixture does not exist. - */ - protected function _loadFixtures(TestCase $test): void - { - $fixtures = $test->getFixtures(); - if (!$fixtures) { - return; - } - foreach ($fixtures as $fixture) { - if (isset($this->_loaded[$fixture])) { - continue; - } - - if (strpos($fixture, '.')) { - [$type, $pathName] = explode('.', $fixture, 2); - $path = explode('/', $pathName); - $name = array_pop($path); - $additionalPath = implode('\\', $path); - - if ($type === 'core') { - $baseNamespace = 'Cake'; - } elseif ($type === 'app') { - $baseNamespace = Configure::read('App.namespace'); - } elseif ($type === 'plugin') { - [$plugin, $name] = explode('.', $pathName); - $baseNamespace = str_replace('/', '\\', $plugin); - $additionalPath = null; - } else { - $baseNamespace = ''; - $name = $fixture; - } - - if (strpos($name, '/') > 0) { - $name = str_replace('/', '\\', $name); - } - - $nameSegments = [ - $baseNamespace, - 'Test\Fixture', - $additionalPath, - $name . 'Fixture', - ]; - /** @psalm-var class-string<\Cake\Datasource\FixtureInterface> */ - $className = implode('\\', array_filter($nameSegments)); - } else { - /** @psalm-var class-string<\Cake\Datasource\FixtureInterface> */ - $className = $fixture; - $name = preg_replace('/Fixture\z/', '', substr(strrchr($fixture, '\\'), 1)); - } - - if (class_exists($className)) { - $this->_loaded[$fixture] = new $className(); - $this->_fixtureMap[$name] = $this->_loaded[$fixture]; - } else { - $msg = sprintf( - 'Referenced fixture class "%s" not found. Fixture "%s" was referenced in test case "%s".', - $className, - $fixture, - get_class($test) - ); - throw new UnexpectedValueException($msg); - } - } - } - - /** - * Runs the drop and create commands on the fixtures if necessary. - * - * @param \Cake\Datasource\FixtureInterface $fixture the fixture object to create - * @param \Cake\Datasource\ConnectionInterface $db The Connection object instance to use - * @param array $sources The existing tables in the datasource. - * @param bool $drop whether drop the fixture if it is already created or not - * @return void - */ - protected function _setupTable( - FixtureInterface $fixture, - ConnectionInterface $db, - array $sources, - bool $drop = true - ): void { - $configName = $db->configName(); - $isFixtureSetup = $this->isFixtureSetup($configName, $fixture); - if ($isFixtureSetup) { - return; - } - - $table = $fixture->sourceName(); - $exists = in_array($table, $sources, true); - - $hasSchema = $fixture instanceof TableSchemaAwareInterface && $fixture->getTableSchema() instanceof TableSchema; - - if (($drop && $exists) || ($exists && $hasSchema)) { - $fixture->drop($db); - $fixture->create($db); - } elseif (!$exists) { - $fixture->create($db); - } else { - $fixture->truncate($db); - } - - $this->_insertionMap[$configName][] = $fixture; - } - - /** - * @param \Cake\TestSuite\TestCase $test Test case - * @return void - * @throws \RuntimeException - */ - public function load(TestCase $test): void - { - $fixtures = $test->getFixtures(); - if (!$fixtures || !$test->autoFixtures) { - return; - } - - try { - $createTables = function (ConnectionInterface $db, array $fixtures) use ($test): void { - /** @var array<\Cake\Datasource\FixtureInterface> $fixtures */ - $tables = $db->getSchemaCollection()->listTables(); - $configName = $db->configName(); - $this->_insertionMap[$configName] = $this->_insertionMap[$configName] ?? []; - - foreach ($fixtures as $fixture) { - if (!$fixture instanceof ConstraintsInterface) { - continue; - } - - if (in_array($fixture->sourceName(), $tables, true)) { - try { - $fixture->dropConstraints($db); - } catch (PDOException $e) { - $msg = sprintf( - 'Unable to drop constraints for fixture "%s" in "%s" test case: ' . "\n" . '%s', - get_class($fixture), - get_class($test), - $e->getMessage() - ); - throw new CakeException($msg, null, $e); - } - } - } - - foreach ($fixtures as $fixture) { - if (!in_array($fixture, $this->_insertionMap[$configName], true)) { - $this->_setupTable($fixture, $db, $tables, $test->dropTables); - } else { - $fixture->truncate($db); - } - } - - foreach ($fixtures as $fixture) { - if (!$fixture instanceof ConstraintsInterface) { - continue; - } - - try { - $fixture->createConstraints($db); - } catch (PDOException $e) { - $msg = sprintf( - 'Unable to create constraints for fixture "%s" in "%s" test case: ' . "\n" . '%s', - get_class($fixture), - get_class($test), - $e->getMessage() - ); - throw new CakeException($msg, null, $e); - } - } - }; - $this->_runOperation($fixtures, $createTables); - - // Use a separate transaction because of postgres. - $insert = function (ConnectionInterface $db, array $fixtures) use ($test): void { - foreach ($fixtures as $fixture) { - try { - $fixture->insert($db); - } catch (PDOException $e) { - $msg = sprintf( - 'Unable to insert fixture "%s" in "%s" test case: ' . "\n" . '%s', - get_class($fixture), - get_class($test), - $e->getMessage() - ); - throw new CakeException($msg, null, $e); - } - } - }; - $this->_runOperation($fixtures, $insert); - } catch (PDOException $e) { - $msg = sprintf( - 'Unable to insert fixtures for "%s" test case. %s', - get_class($test), - $e->getMessage() - ); - throw new RuntimeException($msg, 0, $e); - } - } - - /** - * Run a function on each connection and collection of fixtures. - * - * @param array $fixtures A list of fixtures to operate on. - * @param callable $operation The operation to run on each connection + fixture set. - * @return void - */ - protected function _runOperation(array $fixtures, callable $operation): void - { - $dbs = $this->_fixtureConnections($fixtures); - foreach ($dbs as $connection => $fixtures) { - $db = ConnectionManager::get($connection); - $logQueries = $db->isQueryLoggingEnabled(); - - if ($logQueries && !$this->_debug) { - $db->disableQueryLogging(); - } - $db->transactional(function (ConnectionInterface $db) use ($fixtures, $operation): void { - $db->disableConstraints(function (ConnectionInterface $db) use ($fixtures, $operation): void { - $operation($db, $fixtures); - }); - }); - if ($logQueries) { - $db->enableQueryLogging(true); - } - } - } - - /** - * Get the unique list of connections that a set of fixtures contains. - * - * @param array $fixtures The array of fixtures a list of connections is needed from. - * @return array An array of connection names. - */ - protected function _fixtureConnections(array $fixtures): array - { - $dbs = []; - foreach ($fixtures as $name) { - if (!empty($this->_loaded[$name])) { - $fixture = $this->_loaded[$name]; - $dbs[$fixture->connection()][$name] = $fixture; - } - } - - return $dbs; - } - - /** - * Truncates the fixtures tables - * - * @param \Cake\TestSuite\TestCase $test The test to inspect for fixture unloading. - * @return void - */ - public function unload(TestCase $test): void - { - $fixtures = $test->getFixtures(); - if (!$fixtures) { - return; - } - $truncate = function (ConnectionInterface $db, array $fixtures): void { - $configName = $db->configName(); - - foreach ($fixtures as $fixture) { - if ( - $this->isFixtureSetup($configName, $fixture) - && $fixture instanceof ConstraintsInterface - ) { - $fixture->dropConstraints($db); - } - } - }; - $this->_runOperation($fixtures, $truncate); - } - - /** - * @param string $name Name - * @param \Cake\Datasource\ConnectionInterface|null $connection Connection - * @param bool $dropTables Drop all tables prior to loading schema files - * @return void - * @throws \UnexpectedValueException - */ - public function loadSingle(string $name, ?ConnectionInterface $connection = null, bool $dropTables = true): void - { - if (!isset($this->_fixtureMap[$name])) { - throw new UnexpectedValueException(sprintf('Referenced fixture class %s not found', $name)); - } - - $fixture = $this->_fixtureMap[$name]; - if (!$connection) { - $connection = ConnectionManager::get($fixture->connection()); - } - - if (!$this->isFixtureSetup($connection->configName(), $fixture)) { - $sources = $connection->getSchemaCollection()->listTables(); - $this->_setupTable($fixture, $connection, $sources, $dropTables); - } - - if (!$dropTables) { - if ($fixture instanceof ConstraintsInterface) { - $fixture->dropConstraints($connection); - } - $fixture->truncate($connection); - } - - if ($fixture instanceof ConstraintsInterface) { - $fixture->createConstraints($connection); - } - $fixture->insert($connection); - } - - /** - * Drop all fixture tables loaded by this class - * - * @return void - */ - public function shutDown(): void - { - $shutdown = function (ConnectionInterface $db, array $fixtures): void { - $connection = $db->configName(); - /** @var \Cake\Datasource\FixtureInterface $fixture */ - foreach ($fixtures as $fixture) { - if ($this->isFixtureSetup($connection, $fixture)) { - $fixture->drop($db); - $index = array_search($fixture, $this->_insertionMap[$connection], true); - unset($this->_insertionMap[$connection][$index]); - } - } - }; - $this->_runOperation(array_keys($this->_loaded), $shutdown); - } - - /** - * Check whether a fixture has been inserted in a given connection name. - * - * @param string $connection The connection name. - * @param \Cake\Datasource\FixtureInterface $fixture The fixture to check. - * @return bool - */ - public function isFixtureSetup(string $connection, FixtureInterface $fixture): bool - { - return isset($this->_insertionMap[$connection]) && in_array($fixture, $this->_insertionMap[$connection], true); - } -} diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Fixture/FixtureStrategyInterface.php b/app/vendor/cakephp/cakephp/src/TestSuite/Fixture/FixtureStrategyInterface.php index 09706c395..f848889ed 100644 --- a/app/vendor/cakephp/cakephp/src/TestSuite/Fixture/FixtureStrategyInterface.php +++ b/app/vendor/cakephp/cakephp/src/TestSuite/Fixture/FixtureStrategyInterface.php @@ -11,7 +11,7 @@ * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://cakephp.org CakePHP(tm) Project - * @since 4.3.0 + * @since 5.0.0 * @license https://opensource.org/licenses/mit-license.php MIT License */ namespace Cake\TestSuite\Fixture; diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Fixture/PHPUnitExtension.php b/app/vendor/cakephp/cakephp/src/TestSuite/Fixture/PHPUnitExtension.php deleted file mode 100644 index 00eb4887f..000000000 --- a/app/vendor/cakephp/cakephp/src/TestSuite/Fixture/PHPUnitExtension.php +++ /dev/null @@ -1,49 +0,0 @@ -addTestAliases(); - - $enableLogging = in_array('--debug', $_SERVER['argv'] ?? [], true); - if ($enableLogging) { - $helper->enableQueryLogging(); - Log::drop('queries'); - Log::setConfig('queries', [ - 'className' => 'Console', - 'stream' => 'php://stderr', - 'scopes' => ['queriesLog'], - ]); - } - } -} diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Fixture/SchemaLoader.php b/app/vendor/cakephp/cakephp/src/TestSuite/Fixture/SchemaLoader.php index 5e200f207..17bae5580 100644 --- a/app/vendor/cakephp/cakephp/src/TestSuite/Fixture/SchemaLoader.php +++ b/app/vendor/cakephp/cakephp/src/TestSuite/Fixture/SchemaLoader.php @@ -15,6 +15,8 @@ */ namespace Cake\TestSuite\Fixture; +use Cake\Core\Exception\CakeException; +use Cake\Database\Connection; use Cake\Database\Schema\TableSchema; use Cake\Datasource\ConnectionManager; use Cake\TestSuite\ConnectionHelper; @@ -33,19 +35,6 @@ */ class SchemaLoader { - /** - * @var \Cake\TestSuite\ConnectionHelper - */ - protected $helper; - - /** - * Constructor. - */ - public function __construct() - { - $this->helper = new ConnectionHelper(); - } - /** * Load and apply schema sql file, or an array of files. * @@ -56,10 +45,10 @@ public function __construct() * @return void */ public function loadSqlFiles( - $paths, + array|string $paths, string $connectionName = 'test', bool $dropTables = true, - bool $truncateTables = false + bool $truncateTables = false, ): void { $files = (array)$paths; @@ -69,25 +58,28 @@ public function loadSqlFiles( } if ($dropTables) { - $this->helper->dropTables($connectionName); + ConnectionHelper::dropTables($connectionName); } /** @var \Cake\Database\Connection $connection */ $connection = ConnectionManager::get($connectionName); foreach ($files as $file) { if (!file_exists($file)) { - throw new InvalidArgumentException("Unable to load SQL file `$file`."); + throw new InvalidArgumentException(sprintf('Unable to load SQL file `%s`.', $file)); } $sql = file_get_contents($file); + if ($sql === false) { + throw new CakeException(sprintf('Cannot read file content of `%s`', $file)); + } // Use the underlying PDO connection so we can avoid prepared statements // which don't support multiple queries in postgres. $driver = $connection->getDriver(); - $driver->getConnection()->exec($sql); + $driver->exec($sql); } if ($truncateTables) { - $this->helper->truncateTables($connectionName); + ConnectionHelper::truncateTables($connectionName); } } @@ -135,7 +127,7 @@ public function loadSqlFiles( * * This schema format can be useful for plugins that want to include * tables to test against but don't need to include production - * ready schema via migrations. Applications should favour using migrations + * ready schema via migrations. Applications should favor using migrations * or SQL dump files over this format for ease of maintenance. * * A more complete example can be found in `tests/schema.php`. @@ -152,18 +144,21 @@ public function loadInternalFile(string $file, string $connectionName = 'test'): return; } - $this->helper->dropTables($connectionName); + ConnectionHelper::dropTables($connectionName); $tables = include $file; + /** + * @var \Cake\Database\Connection $connection + */ $connection = ConnectionManager::get($connectionName); - $connection->disableConstraints(function ($connection) use ($tables) { + $connection->disableConstraints(function (Connection $connection) use ($tables): void { foreach ($tables as $tableName => $table) { $name = $table['table'] ?? $tableName; if (!is_string($name)) { throw new InvalidArgumentException( sprintf('`%s` is not a valid table name. Either use a string key for the table definition' - . '(`\'articles\' => [...]`) or define the `table` key in the table definition.', $name) + . "(`'articles' => [...]`) or define the `table` key in the table definition.", $name), ); } $schema = new TableSchema($name, $table['columns']); diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Fixture/TestFixture.php b/app/vendor/cakephp/cakephp/src/TestSuite/Fixture/TestFixture.php index 7eefb8ac4..c74033b9f 100644 --- a/app/vendor/cakephp/cakephp/src/TestSuite/Fixture/TestFixture.php +++ b/app/vendor/cakephp/cakephp/src/TestSuite/Fixture/TestFixture.php @@ -16,23 +16,22 @@ namespace Cake\TestSuite\Fixture; use Cake\Core\Exception\CakeException; -use Cake\Database\ConstraintsInterface; +use Cake\Database\Connection; +use Cake\Database\Schema\SqlGeneratorInterface; use Cake\Database\Schema\TableSchema; -use Cake\Database\Schema\TableSchemaAwareInterface; +use Cake\Database\Schema\TableSchemaInterface; use Cake\Datasource\ConnectionInterface; use Cake\Datasource\ConnectionManager; use Cake\Datasource\FixtureInterface; -use Cake\Log\Log; use Cake\ORM\Locator\LocatorAwareTrait; use Cake\Utility\Inflector; -use Exception; use function Cake\Core\namespaceSplit; /** * Cake TestFixture is responsible for building and destroying tables to be used * during testing. */ -class TestFixture implements ConstraintsInterface, FixtureInterface, TableSchemaAwareInterface +class TestFixture implements FixtureInterface { use LocatorAwareTrait; @@ -41,59 +40,34 @@ class TestFixture implements ConstraintsInterface, FixtureInterface, TableSchema * * @var string */ - public $connection = 'test'; + public string $connection = 'test'; /** * Full Table Name * * @var string - * @psalm-suppress PropertyNotSetInConstructor */ - public $table; - - /** - * Fields / Schema for the fixture. - * - * This array should be compatible with {@link \Cake\Database\Schema\Schema}. - * The `_constraints`, `_options` and `_indexes` keys are reserved for defining - * constraints, options and indexes respectively. - * - * @var array - */ - public $fields = []; - - /** - * Configuration for importing fixture schema - * - * Accepts a `connection` and `model` or `table` key, to define - * which table and which connection contain the schema to be - * imported. - * - * @var array|null - */ - public $import; + public string $table = ''; /** * Fixture records to be inserted. * * @var array */ - public $records = []; + public array $records = []; /** * The schema for this fixture. * * @var \Cake\Database\Schema\TableSchemaInterface&\Cake\Database\Schema\SqlGeneratorInterface - * @psalm-suppress PropertyNotSetInConstructor */ - protected $_schema; + protected TableSchemaInterface&SqlGeneratorInterface $_schema; /** - * Fixture constraints to be created. - * - * @var array + * Whether to be strict about invalid fields. + * Useful for catching typos. */ - protected $_constraints = []; + protected bool $strictFields = false; /** * Instantiate the fixture. @@ -102,13 +76,13 @@ class TestFixture implements ConstraintsInterface, FixtureInterface, TableSchema */ public function __construct() { - if (!empty($this->connection)) { + if ($this->connection) { $connection = $this->connection; - if (strpos($connection, 'test') !== 0) { + if (!str_starts_with($connection, 'test')) { $message = sprintf( - 'Invalid datasource name "%s" for "%s" fixture. Fixture datasource names must begin with "test".', + 'Invalid datasource name `%s` for `%s` fixture. Fixture datasource names must begin with `test`.', $connection, - static::class + static::class, ); throw new CakeException($message); } @@ -140,21 +114,11 @@ public function sourceName(): string */ public function init(): void { - if ($this->table === null) { + if (!$this->table) { $this->table = $this->_tableFromClass(); } - if (empty($this->import) && !empty($this->fields)) { - $this->_schemaFromFields(); - } - - if (!empty($this->import)) { - $this->_schemaFromImport(); - } - - if (empty($this->import) && empty($this->fields)) { - $this->_schemaFromReflection(); - } + $this->_schemaFromReflection(); } /** @@ -171,72 +135,6 @@ protected function _tableFromClass(): string return Inflector::tableize($table); } - /** - * Build the fixtures table schema from the fields property. - * - * @return void - */ - protected function _schemaFromFields(): void - { - $connection = ConnectionManager::get($this->connection()); - $this->_schema = $connection->getDriver()->newTableSchema($this->table); - foreach ($this->fields as $field => $data) { - if ($field === '_constraints' || $field === '_indexes' || $field === '_options') { - continue; - } - $this->_schema->addColumn($field, $data); - } - if (!empty($this->fields['_constraints'])) { - foreach ($this->fields['_constraints'] as $name => $data) { - if (!$connection->supportsDynamicConstraints() || $data['type'] !== TableSchema::CONSTRAINT_FOREIGN) { - $this->_schema->addConstraint($name, $data); - } else { - $this->_constraints[$name] = $data; - } - } - } - if (!empty($this->fields['_indexes'])) { - foreach ($this->fields['_indexes'] as $name => $data) { - $this->_schema->addIndex($name, $data); - } - } - if (!empty($this->fields['_options'])) { - $this->_schema->setOptions($this->fields['_options']); - } - } - - /** - * Build fixture schema from a table in another datasource. - * - * @return void - * @throws \Cake\Core\Exception\CakeException when trying to import from an empty table. - */ - protected function _schemaFromImport(): void - { - if (!is_array($this->import)) { - return; - } - $import = $this->import + ['connection' => 'default', 'table' => null, 'model' => null]; - - if (!empty($import['model'])) { - if (!empty($import['table'])) { - throw new CakeException('You cannot define both table and model.'); - } - $import['table'] = $this->getTableLocator()->get($import['model'])->getTable(); - } - - if (empty($import['table'])) { - throw new CakeException('Cannot import from undefined table.'); - } - - $this->table = $import['table']; - - $db = ConnectionManager::get($import['connection'], false); - $schemaCollection = $db->getSchemaCollection(); - $table = $schemaCollection->describe($import['table']); - $this->_schema = $table; - } - /** * Build fixture schema directly from the datasource * @@ -246,12 +144,17 @@ protected function _schemaFromImport(): void protected function _schemaFromReflection(): void { $db = ConnectionManager::get($this->connection()); + assert($db instanceof Connection); try { $name = Inflector::camelize($this->table); $ormTable = $this->fetchTable($name, ['connection' => $db]); - /** @var \Cake\Database\Schema\TableSchema $schema */ + // Remove the fetched table from the locator to avoid conflicts + // with test cases that need to (re)configure the alias. + $this->getTableLocator()->remove($name); + $schema = $ormTable->getSchema(); + assert($schema instanceof TableSchema); $this->_schema = $schema; $this->getTableLocator()->clear(); @@ -259,7 +162,7 @@ protected function _schemaFromReflection(): void $message = sprintf( 'Cannot describe schema for table `%s` for fixture `%s`. The table does not exist.', $this->table, - static::class + static::class, ); throw new CakeException($message, null, $e); } @@ -268,75 +171,11 @@ protected function _schemaFromReflection(): void /** * @inheritDoc */ - public function create(ConnectionInterface $connection): bool + public function insert(ConnectionInterface $connection): bool { - /** @psalm-suppress RedundantPropertyInitializationCheck */ - if (!isset($this->_schema)) { - return false; - } - - if (empty($this->import) && empty($this->fields)) { - return true; - } - - try { - /** @psalm-suppress ArgumentTypeCoercion */ - $queries = $this->_schema->createSql($connection); - foreach ($queries as $query) { - $stmt = $connection->prepare($query); - $stmt->execute(); - $stmt->closeCursor(); - } - } catch (Exception $e) { - $msg = sprintf( - 'Fixture creation for "%s" failed "%s"', - $this->table, - $e->getMessage() - ); - Log::error($msg); - trigger_error($msg, E_USER_WARNING); - - return false; - } - - return true; - } - - /** - * @inheritDoc - */ - public function drop(ConnectionInterface $connection): bool - { - /** @psalm-suppress RedundantPropertyInitializationCheck */ - if (!isset($this->_schema)) { - return false; - } - - if (empty($this->import) && empty($this->fields)) { - return $this->truncate($connection); - } - - try { - /** @psalm-suppress ArgumentTypeCoercion */ - $sql = $this->_schema->dropSql($connection); - foreach ($sql as $stmt) { - $connection->execute($stmt)->closeCursor(); - } - } catch (Exception $e) { - return false; - } - - return true; - } - - /** - * @inheritDoc - */ - public function insert(ConnectionInterface $connection) - { - if (!empty($this->records)) { + assert($connection instanceof Connection); + if ($this->records) { [$fields, $values, $types] = $this->_getRecords(); - /** @var \Cake\Database\Connection $connection */ $query = $connection->insertQuery() ->insert($fields, $types) ->into($this->sourceName()); @@ -344,64 +183,7 @@ public function insert(ConnectionInterface $connection) foreach ($values as $row) { $query->values($row); } - $statement = $query->execute(); - $statement->closeCursor(); - - return $statement; - } - - return true; - } - - /** - * @inheritDoc - */ - public function createConstraints(ConnectionInterface $connection): bool - { - if (empty($this->_constraints)) { - return true; - } - - foreach ($this->_constraints as $name => $data) { - $this->_schema->addConstraint($name, $data); - } - - /** @psalm-suppress ArgumentTypeCoercion */ - $sql = $this->_schema->addConstraintSql($connection); - - if (empty($sql)) { - return true; - } - - foreach ($sql as $stmt) { - $connection->execute($stmt)->closeCursor(); - } - - return true; - } - - /** - * @inheritDoc - */ - public function dropConstraints(ConnectionInterface $connection): bool - { - if (empty($this->_constraints)) { - return true; - } - - /** @psalm-suppress ArgumentTypeCoercion */ - $sql = $this->_schema->dropConstraintSql($connection); - - if (empty($sql)) { - return true; - } - - foreach ($sql as $stmt) { - $connection->execute($stmt)->closeCursor(); - } - - foreach ($this->_constraints as $name => $data) { - $this->_schema->dropConstraint($name); + $query->execute(); } return true; @@ -414,15 +196,31 @@ public function dropConstraints(ConnectionInterface $connection): bool */ protected function _getRecords(): array { - $fields = $values = $types = []; + $fields = []; + $values = []; + $types = []; $columns = $this->_schema->columns(); - foreach ($this->records as $record) { - $fields = array_merge($fields, array_intersect(array_keys($record), $columns)); + foreach ($this->records as $index => $record) { + $recordFields = array_keys($record); + if ($this->strictFields) { + $invalidFields = array_values(array_filter($recordFields, fn($f) => !in_array($f, $columns, true))); + if ($invalidFields !== []) { + throw new CakeException( + "Record #{$index} in fixture has additional fields that do not exist in the schema. " . + 'Remove the following fields: ' . json_encode($invalidFields), + ); + } + } else { + $recordFields = array_intersect($recordFields, $columns); + } + + $fields = array_unique(array_merge($fields, $recordFields)); } - $fields = array_values(array_unique($fields)); + /** @var list $fields */ + $fields = array_values($fields); foreach ($fields as $field) { - /** @var array $column */ $column = $this->_schema->getColumn($field); + assert($column !== null); $types[$field] = $column['type']; } $default = array_fill_keys($fields, null); @@ -438,30 +236,22 @@ protected function _getRecords(): array */ public function truncate(ConnectionInterface $connection): bool { - /** @psalm-suppress ArgumentTypeCoercion */ + assert($connection instanceof Connection); $sql = $this->_schema->truncateSql($connection); foreach ($sql as $stmt) { - $connection->execute($stmt)->closeCursor(); + $connection->execute($stmt); } return true; } /** - * @inheritDoc + * Returns the table schema for this fixture. + * + * @return \Cake\Database\Schema\TableSchemaInterface&\Cake\Database\Schema\SqlGeneratorInterface */ - public function getTableSchema() + public function getTableSchema(): TableSchemaInterface&SqlGeneratorInterface { return $this->_schema; } - - /** - * @inheritDoc - */ - public function setTableSchema($schema) - { - $this->_schema = $schema; - - return $this; - } } diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Fixture/TransactionFixtureStrategy.php b/app/vendor/cakephp/cakephp/src/TestSuite/Fixture/TransactionFixtureStrategy.php new file mode 100644 index 000000000..4a01e9f3a --- /dev/null +++ b/app/vendor/cakephp/cakephp/src/TestSuite/Fixture/TransactionFixtureStrategy.php @@ -0,0 +1,89 @@ + + */ + protected array $fixtures = []; + + /** + * Initialize strategy. + */ + public function __construct() + { + $this->helper = new FixtureHelper(); + } + + /** + * @inheritDoc + */ + public function setupTest(array $fixtureNames): void + { + $this->fixtures = $this->helper->loadFixtures($fixtureNames); + + $this->helper->runPerConnection(function ($connection): void { + if ($connection instanceof Connection) { + assert( + $connection->inTransaction() === false, + 'Cannot start transaction strategy inside a transaction. This is most likely a bug.', + ); + $connection->enableSavePoints(); + if (!$connection->isSavePointsEnabled()) { + throw new DatabaseException( + "Could not enable save points for the `{$connection->configName()}` connection. " . + 'Your database needs to support savepoints in order to use ' . + 'TransactionFixtureStrategy.', + ); + } + + $connection->begin(); + $connection->createSavePoint('__fixtures__'); + } + }, $this->fixtures); + + $this->helper->insert($this->fixtures); + } + + /** + * @inheritDoc + */ + public function teardownTest(): void + { + $this->helper->runPerConnection(function (Connection $connection): void { + if ($connection->inTransaction()) { + $connection->rollback(true); + } + }, $this->fixtures); + } +} diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Fixture/TransactionStrategy.php b/app/vendor/cakephp/cakephp/src/TestSuite/Fixture/TransactionStrategy.php index 88cb75747..981dbf80f 100644 --- a/app/vendor/cakephp/cakephp/src/TestSuite/Fixture/TransactionStrategy.php +++ b/app/vendor/cakephp/cakephp/src/TestSuite/Fixture/TransactionStrategy.php @@ -17,7 +17,7 @@ namespace Cake\TestSuite\Fixture; use Cake\Database\Connection; -use RuntimeException; +use Cake\Database\Exception\DatabaseException; /** * Fixture strategy that wraps fixtures in a transaction that is rolled back @@ -30,12 +30,12 @@ class TransactionStrategy implements FixtureStrategyInterface /** * @var \Cake\TestSuite\Fixture\FixtureHelper */ - protected $helper; + protected FixtureHelper $helper; /** * @var array<\Cake\Datasource\FixtureInterface> */ - protected $fixtures = []; + protected array $fixtures = []; /** * Initialize strategy. @@ -50,25 +50,25 @@ public function __construct() */ public function setupTest(array $fixtureNames): void { - if (empty($fixtureNames)) { + if (!$fixtureNames) { return; } $this->fixtures = $this->helper->loadFixtures($fixtureNames); - $this->helper->runPerConnection(function ($connection) { + $this->helper->runPerConnection(function ($connection): void { if ($connection instanceof Connection) { assert( $connection->inTransaction() === false, 'Cannot start transaction strategy inside a transaction. ' . - 'Ensure you have closed all open transactions.' + 'Ensure you have closed all open transactions.', ); $connection->enableSavePoints(); if (!$connection->isSavePointsEnabled()) { - throw new RuntimeException( + throw new DatabaseException( "Could not enable save points for the `{$connection->configName()}` connection. " . 'Your database needs to support savepoints in order to use ' . - 'TransactionStrategy.' + 'TransactionStrategy.', ); } @@ -85,7 +85,7 @@ public function setupTest(array $fixtureNames): void */ public function teardownTest(): void { - $this->helper->runPerConnection(function ($connection) { + $this->helper->runPerConnection(function (Connection $connection): void { if ($connection->inTransaction()) { $connection->rollback(true); } diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Fixture/TruncateFixtureStrategy.php b/app/vendor/cakephp/cakephp/src/TestSuite/Fixture/TruncateFixtureStrategy.php new file mode 100644 index 000000000..9573adf3e --- /dev/null +++ b/app/vendor/cakephp/cakephp/src/TestSuite/Fixture/TruncateFixtureStrategy.php @@ -0,0 +1,58 @@ + + */ + protected array $fixtures = []; + + /** + * Initialize strategy. + */ + public function __construct() + { + $this->helper = new FixtureHelper(); + } + + /** + * @inheritDoc + */ + public function setupTest(array $fixtureNames): void + { + $this->fixtures = $this->helper->loadFixtures($fixtureNames); + $this->helper->insert($this->fixtures); + } + + /** + * @inheritDoc + */ + public function teardownTest(): void + { + $this->helper->truncate($this->fixtures); + } +} diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Fixture/TruncateStrategy.php b/app/vendor/cakephp/cakephp/src/TestSuite/Fixture/TruncateStrategy.php index 218c2ca7e..a2f4438bd 100644 --- a/app/vendor/cakephp/cakephp/src/TestSuite/Fixture/TruncateStrategy.php +++ b/app/vendor/cakephp/cakephp/src/TestSuite/Fixture/TruncateStrategy.php @@ -24,12 +24,12 @@ class TruncateStrategy implements FixtureStrategyInterface /** * @var \Cake\TestSuite\Fixture\FixtureHelper */ - protected $helper; + protected FixtureHelper $helper; /** * @var array<\Cake\Datasource\FixtureInterface> */ - protected $fixtures = []; + protected array $fixtures = []; /** * Initialize strategy. @@ -44,7 +44,7 @@ public function __construct() */ public function setupTest(array $fixtureNames): void { - if (empty($fixtureNames)) { + if (!$fixtureNames) { return; } diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/HttpClientTrait.php b/app/vendor/cakephp/cakephp/src/TestSuite/HttpClientTrait.php deleted file mode 100644 index 0bafe7e65..000000000 --- a/app/vendor/cakephp/cakephp/src/TestSuite/HttpClientTrait.php +++ /dev/null @@ -1,10 +0,0 @@ - */ - protected $_unlockedFields = []; + protected array $_unlockedFields = []; /** * The name that will be used when retrieving the csrf token. * * @var string */ - protected $_csrfKeyName = 'csrfToken'; + protected string $_csrfKeyName = 'csrfToken'; /** * Clears the state used for requests. * - * @after * @return void - * @psalm-suppress PossiblyNullPropertyAssignmentValue */ + #[After] public function cleanup(): void { $this->_request = []; @@ -217,9 +221,9 @@ public function cleanup(): void } /** - * Calling this method will enable a SecurityComponent + * Calling this method will enable a FormProtectionComponent * compatible token to be added to request data. This - * lets you easily test actions protected by SecurityComponent. + * lets you easily test actions protected by FormProtectionComponent. * * @return void */ @@ -266,20 +270,46 @@ public function enableRetainFlashMessages(): void } /** - * Configures the data for the *next* request. + * Configures the data for the *next* request merging with existing state. * * This data is cleared in the tearDown() method. * * You can call this method multiple times to append into - * the current state. - * Sub-keys like 'headers' will be reset, though. + * the current state. Sub-keys will be merged with existing + * state. * * @param array $data The request data to use. * @return void */ public function configRequest(array $data): void { - $this->_request = $data + $this->_request; + $this->_request = array_merge_recursive($data, $this->_request); + } + + /** + * Configures the data for the *next* request replacing existing state. + * + * @param array $data The request data to use. + * @return void + */ + public function replaceRequest(array $data): void + { + $this->_request = $data; + } + + /** + * Sets HTTP headers for the *next* request to be identified as JSON request. + * + * @return void + */ + public function requestAsJson(): void + { + $this->configRequest([ + 'headers' => [ + 'Accept' => 'application/json', + 'Content-Type' => 'application/json', + ], + ]); } /** @@ -311,10 +341,10 @@ public function session(array $data): void * the current state. * * @param string $name The cookie name to use. - * @param mixed $value The value of the cookie. + * @param string $value The value of the cookie. * @return void */ - public function cookie(string $name, $value): void + public function cookie(string $name, string $value): void { $this->_cookie[$name] = $value; } @@ -336,15 +366,19 @@ protected function _getCookieEncryptionKey(): string * value like the CookieComponent. * * @param string $name The cookie name to use. - * @param mixed $value The value of the cookie. + * @param array|string $value The value of the cookie. * @param string|false $encrypt Encryption mode to use. * @param string|null $key Encryption key used. Defaults * to Security.salt. * @return void * @see \Cake\Utility\CookieCryptTrait::_encrypt() */ - public function cookieEncrypted(string $name, $value, $encrypt = 'aes', $key = null): void - { + public function cookieEncrypted( + string $name, + array|string $value, + string|false $encrypt = 'aes', + ?string $key = null, + ): void { $this->_cookieEncryptionKey = $key; $this->_cookie[$name] = $this->_encrypt($value, $encrypt); } @@ -359,7 +393,7 @@ public function cookieEncrypted(string $name, $value, $encrypt = 'aes', $key = n * @param array|string $url The URL to request. * @return void */ - public function get($url): void + public function get(array|string $url): void { $this->_sendRequest($url, 'GET'); } @@ -375,7 +409,7 @@ public function get($url): void * @param array|string $data The data for the request. * @return void */ - public function post($url, $data = []): void + public function post(array|string $url, array|string $data = []): void { $this->_sendRequest($url, 'POST', $data); } @@ -391,7 +425,7 @@ public function post($url, $data = []): void * @param array|string $data The data for the request. * @return void */ - public function patch($url, $data = []): void + public function patch(array|string $url, array|string $data = []): void { $this->_sendRequest($url, 'PATCH', $data); } @@ -407,7 +441,7 @@ public function patch($url, $data = []): void * @param array|string $data The data for the request. * @return void */ - public function put($url, $data = []): void + public function put(array|string $url, array|string $data = []): void { $this->_sendRequest($url, 'PUT', $data); } @@ -422,7 +456,7 @@ public function put($url, $data = []): void * @param array|string $url The URL to request. * @return void */ - public function delete($url): void + public function delete(array|string $url): void { $this->_sendRequest($url, 'DELETE'); } @@ -437,7 +471,7 @@ public function delete($url): void * @param array|string $url The URL to request. * @return void */ - public function head($url): void + public function head(array|string $url): void { $this->_sendRequest($url, 'HEAD'); } @@ -452,7 +486,7 @@ public function head($url): void * @param array|string $url The URL to request. * @return void */ - public function options($url): void + public function options(array|string $url): void { $this->_sendRequest($url, 'OPTIONS'); } @@ -468,17 +502,18 @@ public function options($url): void * @return void * @throws \PHPUnit\Exception|\Throwable */ - protected function _sendRequest($url, $method, $data = []): void + protected function _sendRequest(array|string $url, string $method, array|string $data = []): void { + $url = $this->resolveUrl($url); $dispatcher = $this->_makeDispatcher(); - $url = $dispatcher->resolveUrl($url); try { $request = $this->_buildRequest($url, $method, $data); $response = $dispatcher->execute($request); $this->_requestSession = $request['session']; if ($this->_retainFlashMessages && $this->_flashMessages) { - $this->_requestSession->write('Flash', $this->_flashMessages); + $_SESSION['Flash'] = $this->_flashMessages; + $this->_requestSession->write($_SESSION); } $this->_response = $response; } catch (PHPUnitException | DatabaseException $e) { @@ -490,6 +525,55 @@ protected function _sendRequest($url, $method, $data = []): void } } + /** + * Resolve the provided URL into a string. + * + * @param array|string $url The URL array/string to resolve. + * @return string + * @since 5.1.0 + */ + public function resolveUrl(array|string $url): string + { + // If we need to resolve a Route URL but there are no routes, load routes. + if (is_array($url) && Router::getRouteCollection()->routes() === []) { + return $this->resolveRoute($url); + } + + return Router::url($url); + } + + /** + * Convert a URL array into a string URL via routing. + * + * @param array $url The url to resolve + * @return string + * @since 5.1.0 + */ + protected function resolveRoute(array $url): string + { + $app = $this->createApp(); + + // Simulate application bootstrap and route loading. + // We need both to ensure plugins are loaded. + $app->bootstrap(); + if ($app instanceof PluginApplicationInterface) { + $app->pluginBootstrap(); + } + $builder = Router::createRouteBuilder('/'); + + if ($app instanceof RoutingApplicationInterface) { + $app->routes($builder); + } + if ($app instanceof PluginApplicationInterface) { + $app->pluginRoutes($builder); + } + + $out = Router::url($url); + Router::resetRoutes(); + + return $out; + } + /** * Get the correct dispatcher instance. * @@ -497,9 +581,9 @@ protected function _sendRequest($url, $method, $data = []): void */ protected function _makeDispatcher(): MiddlewareDispatcher { - EventManager::instance()->on('Controller.initialize', [$this, 'controllerSpy']); - /** @var \Cake\Core\HttpApplicationInterface $app */ + EventManager::instance()->on('Controller.initialize', $this->controllerSpy(...)); $app = $this->createApp(); + assert($app instanceof HttpApplicationInterface); return new MiddlewareDispatcher($app); } @@ -514,8 +598,8 @@ protected function _makeDispatcher(): MiddlewareDispatcher public function controllerSpy(EventInterface $event, ?Controller $controller = null): void { if (!$controller) { - /** @var \Cake\Controller\Controller $controller */ $controller = $event->getSubject(); + assert($controller instanceof Controller); } $this->_controller = $controller; $events = $controller->getEventManager(); @@ -526,7 +610,7 @@ public function controllerSpy(EventInterface $event, ?Controller $controller = n $controller = $event->getSubject(); $this->_flashMessages = Hash::merge( $this->_flashMessages, - $controller->getRequest()->getSession()->read('Flash') + $controller->getRequest()->getSession()->read('Flash'), ); }; $events->on('Controller.beforeRedirect', ['priority' => -100], $flashCapture); @@ -553,7 +637,7 @@ public function controllerSpy(EventInterface $event, ?Controller $controller = n protected function _handleError(Throwable $exception): void { $class = Configure::read('Error.exceptionRenderer'); - if (empty($class) || !class_exists($class)) { + if (!$class || !class_exists($class)) { $class = WebExceptionRenderer::class; } /** @var \Cake\Error\Renderer\WebExceptionRenderer $instance */ @@ -569,7 +653,7 @@ protected function _handleError(Throwable $exception): void * @param array|string $data The request data. * @return array The request context */ - protected function _buildRequest(string $url, $method, $data = []): array + protected function _buildRequest(string $url, string $method, array|string $data = []): array { $sessionConfig = (array)Configure::read('Session') + [ 'defaults' => 'php', @@ -622,7 +706,9 @@ protected function _buildRequest(string $url, $method, $data = []): array ) { $props['input'] = http_build_query($data); } else { - $data = $this->_addTokens($tokenUrl, $data); + if ($method !== 'GET' || $data !== []) { + $data = $this->_addTokens($tokenUrl, $data, $method); + } $props['post'] = $this->_castToString($data); } @@ -633,19 +719,20 @@ protected function _buildRequest(string $url, $method, $data = []): array } /** - * Add the CSRF and Security Component tokens if necessary. + * Add the CSRF and FormProtectionComponent tokens if necessary. * * @param string $url The URL the form is being submitted on. * @param array $data The request body data. + * @param string $method The request method. * @return array The request body with tokens added. */ - protected function _addTokens(string $url, array $data): array + protected function _addTokens(string $url, array $data, string $method): array { if ($this->_securityToken === true) { $fields = array_diff_key($data, array_flip($this->_unlockedFields)); $keys = array_map(function ($field) { - return preg_replace('/(\.\d+)+$/', '', $field); + return preg_replace('/(\.\d+)+$/', '', (string)$field); }, array_keys(Hash::flatten($fields))); $formProtector = new FormProtector(['unlockedFields' => $this->_unlockedFields]); @@ -674,7 +761,7 @@ protected function _addTokens(string $url, array $data): array // the inverse. $this->_session[$this->_csrfKeyName] = $token; $this->_cookie[$this->_csrfKeyName] = $token; - if (!isset($data['_csrfToken'])) { + if (!isset($data['_csrfToken']) && !in_array($method, ['GET', 'OPTIONS'])) { $data['_csrfToken'] = $token; } } @@ -756,9 +843,9 @@ protected function _getBodyAsString(): string * @param string $name The view variable to get. * @return mixed The view variable if set. */ - public function viewVariable(string $name) + public function viewVariable(string $name): mixed { - return $this->_controller ? $this->_controller->viewBuilder()->getVar($name) : null; + return $this->_controller?->viewBuilder()->getVar($name); } /** @@ -828,7 +915,7 @@ public function assertResponseCode(int $code, string $message = ''): void * @param string $message The failure message that will be appended to the generated message. * @return void */ - public function assertRedirect($url = null, $message = ''): void + public function assertRedirect(array|string|null $url = null, string $message = ''): void { if (!$this->_response) { $this->fail('No response set, cannot assert header.'); @@ -841,7 +928,7 @@ public function assertRedirect($url = null, $message = ''): void $this->assertThat( Router::url($url, true), new HeaderEquals($this->_response, 'Location'), - $verboseMessage + $verboseMessage, ); } } @@ -855,7 +942,7 @@ public function assertRedirect($url = null, $message = ''): void * @param string $message The failure message that will be appended to the generated message. * @return void */ - public function assertRedirectEquals($url = null, $message = '') + public function assertRedirectEquals(array|string|null $url = null, string $message = ''): void { if (!$this->_response) { $this->fail('No response set, cannot assert header.'); @@ -994,9 +1081,12 @@ public function assertContentType(string $type, string $message = ''): void * @param string $message The failure message that will be appended to the generated message. * @return void */ - public function assertResponseEquals($content, $message = ''): void + public function assertResponseEquals(mixed $content, string $message = ''): void { $verboseMessage = $this->extractVerboseMessage($message); + if ($this->isDebug()) { + $verboseMessage .= $this->responseBody(); + } $this->assertThat($content, new BodyEquals($this->_response), $verboseMessage); } @@ -1007,9 +1097,12 @@ public function assertResponseEquals($content, $message = ''): void * @param string $message The failure message that will be appended to the generated message. * @return void */ - public function assertResponseNotEquals($content, $message = ''): void + public function assertResponseNotEquals(mixed $content, string $message = ''): void { $verboseMessage = $this->extractVerboseMessage($message); + if ($this->isDebug()) { + $verboseMessage .= $this->responseBody(); + } $this->assertThat($content, new BodyNotEquals($this->_response), $verboseMessage); } @@ -1028,6 +1121,9 @@ public function assertResponseContains(string $content, string $message = '', bo } $verboseMessage = $this->extractVerboseMessage($message); + if ($this->isDebug()) { + $verboseMessage .= $this->responseBody(); + } $this->assertThat($content, new BodyContains($this->_response, $ignoreCase), $verboseMessage); } @@ -1046,6 +1142,9 @@ public function assertResponseNotContains(string $content, string $message = '', } $verboseMessage = $this->extractVerboseMessage($message); + if ($this->isDebug()) { + $verboseMessage .= $this->responseBody(); + } $this->assertThat($content, new BodyNotContains($this->_response, $ignoreCase), $verboseMessage); } @@ -1059,6 +1158,9 @@ public function assertResponseNotContains(string $content, string $message = '', public function assertResponseRegExp(string $pattern, string $message = ''): void { $verboseMessage = $this->extractVerboseMessage($message); + if ($this->isDebug()) { + $verboseMessage .= $this->responseBody(); + } $this->assertThat($pattern, new BodyRegExp($this->_response), $verboseMessage); } @@ -1072,6 +1174,9 @@ public function assertResponseRegExp(string $pattern, string $message = ''): voi public function assertResponseNotRegExp(string $pattern, string $message = ''): void { $verboseMessage = $this->extractVerboseMessage($message); + if ($this->isDebug()) { + $verboseMessage .= $this->responseBody(); + } $this->assertThat($pattern, new BodyNotRegExp($this->_response), $verboseMessage); } @@ -1083,6 +1188,9 @@ public function assertResponseNotRegExp(string $pattern, string $message = ''): */ public function assertResponseNotEmpty(string $message = ''): void { + if ($this->isDebug()) { + $message .= $this->responseBody(); + } $this->assertThat(null, new BodyNotEmpty($this->_response), $message); } @@ -1094,6 +1202,9 @@ public function assertResponseNotEmpty(string $message = ''): void */ public function assertResponseEmpty(string $message = ''): void { + if ($this->isDebug()) { + $message .= $this->responseBody(); + } $this->assertThat(null, new BodyEmpty($this->_response), $message); } @@ -1131,7 +1242,7 @@ public function assertLayout(string $content, string $message = ''): void * @param string $message The failure message that will be appended to the generated message. * @return void */ - public function assertSession($expected, string $path, string $message = ''): void + public function assertSession(mixed $expected, string $path, string $message = ''): void { $verboseMessage = $this->extractVerboseMessage($message); $this->assertThat($expected, new SessionEquals($path), $verboseMessage); @@ -1192,7 +1303,7 @@ public function assertFlashMessageAt(int $at, string $expected, string $key = 'f $this->assertThat( $expected, new FlashParamEquals($this->_requestSession, $key, 'message', $at), - $verboseMessage + $verboseMessage, ); } @@ -1210,7 +1321,7 @@ public function assertFlashElement(string $expected, string $key = 'flash', stri $this->assertThat( $expected, new FlashParamEquals($this->_requestSession, $key, 'element'), - $verboseMessage + $verboseMessage, ); } @@ -1229,7 +1340,7 @@ public function assertFlashElementAt(int $at, string $expected, string $key = 'f $this->assertThat( $expected, new FlashParamEquals($this->_requestSession, $key, 'element', $at), - $verboseMessage + $verboseMessage, ); } @@ -1241,7 +1352,7 @@ public function assertFlashElementAt(int $at, string $expected, string $key = 'f * @param string $message The failure message that will be appended to the generated message. * @return void */ - public function assertCookie($expected, string $name, string $message = ''): void + public function assertCookie(mixed $expected, string $name, string $message = ''): void { $verboseMessage = $this->extractVerboseMessage($message); $this->assertThat($name, new CookieSet($this->_response), $verboseMessage); @@ -1308,11 +1419,11 @@ public function disableErrorHandlerMiddleware(): void * @see \Cake\Utility\CookieCryptTrait::_encrypt() */ public function assertCookieEncrypted( - $expected, + mixed $expected, string $name, string $encrypt = 'aes', ?string $key = null, - string $message = '' + string $message = '', ): void { $verboseMessage = $this->extractVerboseMessage($message); $this->assertThat($name, new CookieSet($this->_response), $verboseMessage); @@ -1320,7 +1431,7 @@ public function assertCookieEncrypted( $this->_cookieEncryptionKey = $key; $this->assertThat( $expected, - new CookieEncryptedEquals($this->_response, $name, $encrypt, $this->_getCookieEncryptionKey()) + new CookieEncryptedEquals($this->_response, $name, $encrypt, $this->_getCookieEncryptionKey()), ); } @@ -1382,10 +1493,10 @@ protected function extractExceptionMessage(Exception $exception): string $message = PHP_EOL; foreach ($exceptions as $i => $error) { if ($i == 0) { - $message .= sprintf('Possibly related to %s: "%s"', get_class($error), $error->getMessage()); + $message .= sprintf('Possibly related to `%s`: "%s"', $error::class, $error->getMessage()); $message .= PHP_EOL; } else { - $message .= sprintf('Caused by %s: "%s"', get_class($error), $error->getMessage()); + $message .= sprintf('Caused by `%s`: "%s"', $error::class, $error->getMessage()); $message .= PHP_EOL; } $message .= $error->getTraceAsString(); @@ -1400,7 +1511,29 @@ protected function extractExceptionMessage(Exception $exception): string */ protected function getSession(): TestSession { - /** @psalm-suppress InvalidScalarArgument */ return new TestSession($_SESSION); } + + /** + * Checks if debug flag is set. + * + * Flag is set via `--debug`. + * Allows additional stuff like non-mocking when enabling debug. Or displaying of response body. + * + * @return bool Success + */ + protected function isDebug(): bool + { + return !empty($_SERVER['argv']) && in_array('--debug', $_SERVER['argv'], true); + } + + /** + * Debug content of response body. + * + * @return string + */ + protected function responseBody(): string + { + return PHP_EOL . '------' . PHP_EOL . $this->_response->getBody() . PHP_EOL . '------' . PHP_EOL; + } } diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/LegacyCommandRunner.php b/app/vendor/cakephp/cakephp/src/TestSuite/LegacyCommandRunner.php deleted file mode 100644 index d386817d2..000000000 --- a/app/vendor/cakephp/cakephp/src/TestSuite/LegacyCommandRunner.php +++ /dev/null @@ -1,10 +0,0 @@ - $levelConfig) { + if (is_int($levelName) && is_string($levelConfig)) { + // string value = level name. + Log::setConfig("test-{$levelConfig}", [ + 'className' => 'Array', + 'levels' => [$levelConfig], + ]); + } + if (is_array($levelConfig)) { + $levelConfig['className'] = 'Array'; + $levelConfig['levels'] ??= $levelName; + $name = $levelConfig['name'] ?? "test-{$levelName}"; + Log::setConfig($name, $levelConfig); + } + } + } + + /** + * Ensure that no log messages of a given level were captured by test loggers. + * + * @param string $level The level of the expected message + * @param string $failMsg The error message if the message was not in the log engine + * @return void + */ + public function assertLogAbsent(string $level, string $failMsg = ''): void + { + foreach (Log::configured() as $engineName) { + $engineObj = Log::engine($engineName); + if (!$engineObj instanceof ArrayLog) { + continue; + } + $levels = $engineObj->levels(); + if (in_array($level, $levels)) { + $this->assertEquals(0, count($engineObj->read()), $failMsg); + } + } + } + + /** + * @param string $level The level of the expected message + * @param string $expectedMessage The message which should be inside the log engine + * @param string|null $scope The scope of the expected message. If a message has + * multiple scopes, the provided scope must be within the message's set. + * @param string $failMsg The error message if the message was not in the log engine + * @return void + */ + public function assertLogMessage( + string $level, + string $expectedMessage, + ?string $scope = null, + string $failMsg = '', + ): void { + $this->_expectLogMessage($level, $expectedMessage, $scope, $failMsg); + } + + /** + * @param string $level The level which should receive a log message + * @param string $expectedMessage The message which should be inside the log engine + * @param string|null $scope The scope of the expected message. If a message has + * multiple scopes, the provided scope must be within the message's set. + * @param string $failMsg The error message if the message was not in the log engine + * @return void + */ + public function assertLogMessageContains( + string $level, + string $expectedMessage, + ?string $scope = null, + string $failMsg = '', + ): void { + $this->_expectLogMessage($level, $expectedMessage, $scope, $failMsg, true); + } + + /** + * @param string $level The level which should receive a log message + * @param string $expectedMessage The message which should be inside the log engine + * @param string|null $scope The scope of the expected message. If a message has + * multiple scopes, the provided scope must be within the message's set. + * @param string $failMsg The error message if the message was not in the log engine + * @param bool $contains Flag to decide if the expectedMessage can only be part of the logged message + * @return void + */ + protected function _expectLogMessage( + string $level, + string $expectedMessage, + ?string $scope, + string $failMsg = '', + bool $contains = false, + ): void { + $messageFound = false; + $expectedMessage = sprintf('%s: %s', $level, $expectedMessage); + foreach (Log::configured() as $engineName) { + $engineObj = Log::engine($engineName); + if (!$engineObj instanceof ArrayLog) { + continue; + } + $messages = $engineObj->read(); + $engineScopes = $engineObj->scopes(); + // No overlapping scopes + if ($scope !== null && !in_array($scope, $engineScopes, true)) { + continue; + } + foreach ($messages as $message) { + if ($contains && str_contains($message, $expectedMessage) || $message === $expectedMessage) { + $messageFound = true; + break; + } + } + } + if (!$messageFound) { + $failMsg = "Could not find the message `{$expectedMessage}` in logs. " . $failMsg; + $this->fail($failMsg); + } + $this->assertTrue(true); + } +} diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/MiddlewareDispatcher.php b/app/vendor/cakephp/cakephp/src/TestSuite/MiddlewareDispatcher.php index 6ca6e767e..4ebf8aa47 100644 --- a/app/vendor/cakephp/cakephp/src/TestSuite/MiddlewareDispatcher.php +++ b/app/vendor/cakephp/cakephp/src/TestSuite/MiddlewareDispatcher.php @@ -38,7 +38,7 @@ class MiddlewareDispatcher * * @var \Cake\Core\HttpApplicationInterface */ - protected $app; + protected HttpApplicationInterface $app; /** * Constructor @@ -55,11 +55,12 @@ public function __construct(HttpApplicationInterface $app) * * @param array|string $url The URL array/string to resolve. * @return string + * @deprecated 5.1.0 Use IntegrationTestTrait::resolveUrl() instead. */ - public function resolveUrl($url): string + public function resolveUrl(array|string $url): string { // If we need to resolve a Route URL but there are no routes, load routes. - if (is_array($url) && count(Router::getRouteCollection()->routes()) === 0) { + if (is_array($url) && Router::getRouteCollection()->routes() === []) { return $this->resolveRoute($url); } @@ -71,6 +72,7 @@ public function resolveUrl($url): string * * @param array $url The url to resolve * @return string + * @deprecated 5.1.0 Use IntegrationTestTrait::resolveRouter() instead. */ protected function resolveRoute(array $url): string { @@ -109,9 +111,9 @@ protected function _createRequest(array $spec): ServerRequest } $environment = array_merge( array_merge($_SERVER, ['REQUEST_URI' => $spec['url']]), - $spec['environment'] + $spec['environment'], ); - if (strpos($environment['PHP_SELF'], 'phpunit') !== false) { + if (str_contains($environment['PHP_SELF'], 'phpunit')) { $environment['PHP_SELF'] = '/'; } $request = ServerRequestFactory::fromGlobals( @@ -119,7 +121,7 @@ protected function _createRequest(array $spec): ServerRequest $spec['query'], $spec['post'], $spec['cookies'], - $spec['files'] + $spec['files'], ); return $request diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/PHPUnitConsecutiveTrait.php b/app/vendor/cakephp/cakephp/src/TestSuite/PHPUnitConsecutiveTrait.php new file mode 100644 index 000000000..5c3f1b8d9 --- /dev/null +++ b/app/vendor/cakephp/cakephp/src/TestSuite/PHPUnitConsecutiveTrait.php @@ -0,0 +1,65 @@ + $argument) { + yield new Callback( + static function (mixed $actualArgument) use ( + $argumentList, + &$mockedMethodCall, + &$callbackCall, + $index, + $numberOfArguments, + ): bool { + $expected = $argumentList[$index][$mockedMethodCall] ?? null; + + $callbackCall++; + $mockedMethodCall = (int)($callbackCall / $numberOfArguments); + + if ($expected instanceof Constraint) { + self::assertThat($actualArgument, $expected); + } else { + self::assertEquals($expected, $actualArgument); + } + + return true; + }, + ); + } + } +} diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/StringCompareTrait.php b/app/vendor/cakephp/cakephp/src/TestSuite/StringCompareTrait.php index b37298b9e..1fcd1b2a9 100644 --- a/app/vendor/cakephp/cakephp/src/TestSuite/StringCompareTrait.php +++ b/app/vendor/cakephp/cakephp/src/TestSuite/StringCompareTrait.php @@ -33,7 +33,7 @@ trait StringCompareTrait * * @var string */ - protected $_compareBasePath = ''; + protected string $_compareBasePath = ''; /** * Update comparisons to match test changes @@ -42,7 +42,7 @@ trait StringCompareTrait * * @var bool */ - protected $_updateComparisons; + protected bool $_updateComparisons; /** * Compare the result to the contents of the file @@ -62,9 +62,7 @@ public function assertSameAsFile(string $path, string $result): void $path = $this->_compareBasePath . $path; } - if ($this->_updateComparisons === null) { - $this->_updateComparisons = env('UPDATE_TEST_COMPARISON_FILES'); - } + $this->_updateComparisons ??= (bool)env('UPDATE_TEST_COMPARISON_FILES'); if ($this->_updateComparisons) { file_put_contents($path, $result); diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/Stub/ConsoleInput.php b/app/vendor/cakephp/cakephp/src/TestSuite/Stub/ConsoleInput.php deleted file mode 100644 index f0e4500f1..000000000 --- a/app/vendor/cakephp/cakephp/src/TestSuite/Stub/ConsoleInput.php +++ /dev/null @@ -1,10 +0,0 @@ - */ - protected $fixtures = []; - - /** - * By default, all fixtures attached to this class will be truncated and reloaded after each test. - * Set this to false to handle manually - * - * @var bool - * @deprecated 4.3.0 autoFixtures is only used by deprecated fixture features. - * This property will be removed in 5.0 - */ - public $autoFixtures = true; - - /** - * Control table create/drops on each test method. - * - * If true, tables will still be dropped at the - * end of each test runner execution. - * - * @var bool - * @deprecated 4.3.0 dropTables is only used by deprecated fixture features. - * This property will be removed in 5.0 - */ - public $dropTables = false; + protected array $fixtures = []; /** * @var \Cake\TestSuite\Fixture\FixtureStrategyInterface|null */ - protected $fixtureStrategy = null; + protected ?FixtureStrategyInterface $fixtureStrategy = null; /** * Configure values to restore at end of test. * * @var array */ - protected $_configure = []; - - /** - * @var \Cake\Error\PhpError|null - */ - private $_capturedError; - - /** - * Asserts that a string matches a given regular expression. - * - * @param string $pattern Regex pattern - * @param string $string String to test - * @param string $message Message - * @return void - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @codeCoverageIgnore - */ - public static function assertMatchesRegularExpression(string $pattern, string $string, string $message = ''): void - { - static::assertThat($string, new RegularExpression($pattern), $message); - } - - /** - * Asserts that a string does not match a given regular expression. - * - * @param string $pattern Regex pattern - * @param string $string String to test - * @param string $message Message - * @return void - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - public static function assertDoesNotMatchRegularExpression( - string $pattern, - string $string, - string $message = '' - ): void { - static::assertThat( - $string, - new LogicalNot( - new RegularExpression($pattern) - ), - $message - ); - } + protected array $_configure = []; /** - * Asserts that a file does not exist. + * Plugins to be loaded after app instance is created ContainerStubTrait::creatApp() * - * @param string $filename Filename - * @param string $message Message - * @return void - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @codeCoverageIgnore + * @var array */ - public static function assertFileDoesNotExist(string $filename, string $message = ''): void - { - static::assertThat($filename, new LogicalNot(new FileExists()), $message); - } + protected array $appPluginsToLoad = []; /** - * Asserts that a directory does not exist. - * - * @param string $directory Directory - * @param string $message Message - * @return void - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @codeCoverageIgnore + * @var \Cake\Error\PhpError|null */ - public static function assertDirectoryDoesNotExist(string $directory, string $message = ''): void - { - static::assertThat($directory, new LogicalNot(new DirectoryExists()), $message); - } + private ?PhpError $_capturedError = null; /** * Overrides SimpleTestCase::skipIf to provide a boolean return value @@ -223,11 +136,12 @@ public function captureError(int $errorLevel, Closure $callable): PhpError set_error_handler( function (int $code, string $description, string $file, int $line) { $trace = Debugger::trace(['start' => 1, 'format' => 'points']); + assert(is_array($trace)); $this->_capturedError = new PhpError($code, $description, $file, $line, $trace); return true; }, - $errorLevel + $errorLevel, ); try { @@ -246,19 +160,37 @@ function (int $code, string $description, string $file, int $line) { /** * Helper method for check deprecation methods * - * @param callable $callable callable function that will receive asserts + * @param \Closure $callable callable function that will receive asserts. + * @param int $type Error level to expect, E_DEPRECATED or E_USER_DEPRECATED. + * @param string|null $phpVersion If set, only applies to this version forward, e.g. `8.4`. * @return void */ - public function deprecated(callable $callable): void + public function deprecated(Closure $callable, int $type = E_USER_DEPRECATED, ?string $phpVersion = null): void { + if ($phpVersion !== null && version_compare(PHP_VERSION, $phpVersion, '<')) { + $callable(); + + return; + } + $duplicate = Configure::read('Error.allowDuplicateDeprecations'); Configure::write('Error.allowDuplicateDeprecations', true); - /** @var bool $deprecation */ + /** @var bool $deprecation Expand type for psalm */ $deprecation = false; $previousHandler = set_error_handler( - function ($code, $message, $file, $line, $context = null) use (&$previousHandler, &$deprecation): bool { - if ($code == E_USER_DEPRECATED) { + function ( + $code, + $message, + $file, + $line, + $context = null, + ) use ( + &$previousHandler, + &$deprecation, + $type, + ): bool { + if ($code == $type) { $deprecation = true; return true; @@ -268,7 +200,7 @@ function ($code, $message, $file, $line, $context = null) use (&$previousHandler } return false; - } + }, ); try { $callable(); @@ -281,6 +213,23 @@ function ($code, $message, $file, $line, $context = null) use (&$previousHandler $this->assertTrue($deprecation, 'Should have at least one deprecation warning'); } + /** + * This method is called between test and tearDown(). + * + * Gets the count of expectations on the mocks produced through Mockery. + * + * @return void + */ + protected function assertPostConditions(): void + { + parent::assertPostConditions(); + + if (class_exists(Mockery::class)) { + // @phpstan-ignore method.internal + $this->addToAssertionCount(Mockery::getContainer()->mockery_getExpectationCount()); + } + } + /** * Setup the test case, backup the static object values so they can be restored. * Specifically backs up the contents of Configure and paths in App if they have @@ -301,6 +250,12 @@ protected function setUp(): void } EventManager::instance(new EventManager()); + + /** @var int|false $errorLevelOverwrite */ + $errorLevelOverwrite = Configure::read('TestSuite.errorLevel', E_ALL); + if ($errorLevelOverwrite !== false) { + error_reporting($errorLevelOverwrite); + } } /** @@ -320,6 +275,9 @@ protected function tearDown(): void $this->getTableLocator()->clear(); $this->_configure = []; $this->_tableLocator = null; + if (class_exists(Mockery::class)) { + Mockery::close(); + } } /** @@ -331,17 +289,6 @@ protected function setupFixtures(): void { $fixtureNames = $this->getFixtures(); - if (!empty($fixtureNames) && static::$fixtureManager) { - if (!$this->autoFixtures) { - deprecationWarning('`$autoFixtures` is deprecated and will be removed in 5.0.', 0); - } - if ($this->dropTables) { - deprecationWarning('`$dropTables` is deprecated and will be removed in 5.0.', 0); - } - // legacy fixtures are managed by FixtureInjector - return; - } - $this->fixtureStrategy = $this->getFixtureStrategy(); $this->fixtureStrategy->setupTest($fixtureNames); } @@ -366,40 +313,10 @@ protected function teardownFixtures(): void */ protected function getFixtureStrategy(): FixtureStrategyInterface { - return new TruncateStrategy(); - } - - /** - * Chooses which fixtures to load for a given test - * - * Each parameter is a model name that corresponds to a fixture, i.e. 'Posts', 'Authors', etc. - * Passing no parameters will cause all fixtures on the test case to load. - * - * @return void - * @see \Cake\TestSuite\TestCase::$autoFixtures - * @throws \RuntimeException when no fixture manager is available. - * @deprecated 4.3.0 Disabling auto-fixtures is deprecated and only available using FixtureInjector fixture system. - */ - public function loadFixtures(): void - { - if ($this->autoFixtures) { - throw new RuntimeException('Cannot use `loadFixtures()` with `$autoFixtures` enabled.'); - } - if (static::$fixtureManager === null) { - throw new RuntimeException('No fixture manager to load the test fixture'); - } + /** @var class-string<\Cake\TestSuite\Fixture\FixtureStrategyInterface> $className */ + $className = Configure::read('TestSuite.fixtureStrategy') ?: TruncateStrategy::class; - $args = func_get_args(); - foreach ($args as $class) { - static::$fixtureManager->loadSingle($class, null, $this->dropTables); - } - - if (empty($args)) { - $autoFixtures = $this->autoFixtures; - $this->autoFixtures = true; - static::$fixtureManager->load($this); - $this->autoFixtures = $autoFixtures; - } + return new $className(); } /** @@ -416,15 +333,15 @@ public function loadFixtures(): void */ public function loadRoutes(?array $appArgs = null): void { - $appArgs = $appArgs ?? [rtrim(CONFIG, DIRECTORY_SEPARATOR)]; - /** @psalm-var class-string */ + $appArgs ??= [rtrim(CONFIG, DIRECTORY_SEPARATOR)]; + /** @var class-string $className */ $className = Configure::read('App.namespace') . '\\Application'; try { $reflect = new ReflectionClass($className); - /** @var \Cake\Routing\RoutingApplicationInterface $app */ $app = $reflect->newInstanceArgs($appArgs); + assert($app instanceof RoutingApplicationInterface); } catch (ReflectionException $e) { - throw new LogicException(sprintf('Cannot load "%s" to load routes from.', $className), 0, $e); + throw new LogicException(sprintf('Cannot load `%s` to load routes from.', $className), 0, $e); } $builder = Router::createRouteBuilder('/'); $app->routes($builder); @@ -436,16 +353,24 @@ public function loadRoutes(?array $appArgs = null): void * Useful to test how plugins being loaded/not loaded interact with other * elements in CakePHP or applications. * - * @param array $plugins List of Plugins to load. + * @param array $plugins List of Plugins to load. * @return \Cake\Http\BaseApplication */ public function loadPlugins(array $plugins = []): BaseApplication { - /** @var \Cake\Http\BaseApplication $app */ - $app = $this->getMockForAbstractClass( - BaseApplication::class, - [''] - ); + $this->appPluginsToLoad = $plugins; + + $app = new class ('') extends BaseApplication + { + /** + * @param \Cake\Http\MiddlewareQueue $middlewareQueue + * @return \Cake\Http\MiddlewareQueue + */ + public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue + { + return $middlewareQueue; + } + }; foreach ($plugins as $pluginName => $config) { if (is_array($config)) { @@ -520,9 +445,9 @@ public function assertEventFired(string $name, ?EventManager $eventManager = nul public function assertEventFiredWith( string $name, string $dataKey, - $dataValue, + mixed $dataValue, ?EventManager $eventManager = null, - string $message = '' + string $message = '', ): void { if (!$eventManager) { $eventManager = EventManager::instance(); @@ -575,6 +500,7 @@ public function assertTextStartsWith(string $prefix, string $string, string $mes { $prefix = str_replace(["\r\n", "\r"], "\n", $prefix); $string = str_replace(["\r\n", "\r"], "\n", $string); + $this->assertNotEmpty($prefix); $this->assertStringStartsWith($prefix, $string, $message); } @@ -591,6 +517,7 @@ public function assertTextStartsNotWith(string $prefix, string $string, string $ { $prefix = str_replace(["\r\n", "\r"], "\n", $prefix); $string = str_replace(["\r\n", "\r"], "\n", $string); + $this->assertNotEmpty($prefix); $this->assertStringStartsNotWith($prefix, $string, $message); } @@ -607,6 +534,7 @@ public function assertTextEndsWith(string $suffix, string $string, string $messa { $suffix = str_replace(["\r\n", "\r"], "\n", $suffix); $string = str_replace(["\r\n", "\r"], "\n", $string); + $this->assertNotEmpty($suffix); $this->assertStringEndsWith($suffix, $string, $message); } @@ -623,6 +551,7 @@ public function assertTextEndsNotWith(string $suffix, string $string, string $me { $suffix = str_replace(["\r\n", "\r"], "\n", $suffix); $string = str_replace(["\r\n", "\r"], "\n", $string); + $this->assertNotEmpty($suffix); $this->assertStringEndsNotWith($suffix, $string, $message); } @@ -640,7 +569,7 @@ public function assertTextContains( string $needle, string $haystack, string $message = '', - bool $ignoreCase = false + bool $ignoreCase = false, ): void { $needle = str_replace(["\r\n", "\r"], "\n", $needle); $haystack = str_replace(["\r\n", "\r"], "\n", $haystack); @@ -666,7 +595,7 @@ public function assertTextNotContains( string $needle, string $haystack, string $message = '', - bool $ignoreCase = false + bool $ignoreCase = false, ): void { $needle = str_replace(["\r\n", "\r"], "\n", $needle); $haystack = str_replace(["\r\n", "\r"], "\n", $haystack); @@ -689,7 +618,7 @@ public function assertTextNotContains( public function assertEqualsSql( string $expected, string $actual, - string $message = '' + string $message = '', ): void { $this->assertEquals($expected, preg_replace('/[`"\[\]]/', '', $actual), $message); } @@ -774,8 +703,7 @@ public function assertHtml(array $expected, string $string, bool $fullDebug = fa $tags = (string)$tags; } $i++; - if (is_string($tags) && $tags[0] === '<') { - /** @psalm-suppress InvalidArrayOffset */ + if (is_string($tags) && str_starts_with($tags, '<')) { $tags = [substr($tags, 1) => []]; } elseif (is_string($tags)) { $tagsTrimmed = preg_replace('/\s+/m', '', $tags); @@ -793,7 +721,7 @@ public function assertHtml(array $expected, string $string, bool $fullDebug = fa ]; continue; } - if (!empty($tags) && preg_match('/^preg\:\/(.+)\/$/i', $tags, $matches)) { + if ($tags && preg_match('/^preg\:\/(.+)\/$/i', $tags, $matches)) { $tags = $matches[1]; $type = 'Regex matches'; } else { @@ -801,7 +729,7 @@ public function assertHtml(array $expected, string $string, bool $fullDebug = fa $type = 'Text equals'; } $regex[] = [ - sprintf('%s "%s"', $type, $tags), + sprintf('%s `%s`', $type, $tags), $tags, $i, ]; @@ -820,9 +748,9 @@ public function assertHtml(array $expected, string $string, bool $fullDebug = fa $explanations = []; $i = 1; foreach ($attributes as $attr => $val) { - if (is_numeric($attr) && preg_match('/^preg\:\/(.+)\/$/i', (string)$val, $matches)) { + if (is_numeric($attr) && preg_match('/^preg:\/(.+)\/$/i', (string)$val, $matches)) { $attrs[] = $matches[1]; - $explanations[] = sprintf('Regex "%s" matches', $matches[1]); + $explanations[] = sprintf('Regex `%s` matches', $matches[1]); continue; } $val = (string)$val; @@ -831,18 +759,18 @@ public function assertHtml(array $expected, string $string, bool $fullDebug = fa if (is_numeric($attr)) { $attr = $val; $val = '.+?'; - $explanations[] = sprintf('Attribute "%s" present', $attr); - } elseif (!empty($val) && preg_match('/^preg\:\/(.+)\/$/i', $val, $matches)) { + $explanations[] = sprintf('Attribute `%s` present', $attr); + } elseif ($val && preg_match('/^preg:\/(.+)\/$/i', $val, $matches)) { $val = str_replace( ['.*', '.+'], ['.*?', '.+?'], - $matches[1] + $matches[1], ); $quotes = $val !== $matches[1] ? '["\']' : '["\']?'; - $explanations[] = sprintf('Attribute "%s" matches "%s"', $attr, $val); + $explanations[] = sprintf('Attribute `%s` matches `%s`', $attr, $val); } else { - $explanations[] = sprintf('Attribute "%s" == "%s"', $attr, $val); + $explanations[] = sprintf('Attribute `%s` == `%s`', $attr, $val); $val = preg_quote($val, '/'); } $attrs[] = '[\s]+' . preg_quote($attr, '/') . '=' . $quotes . $val . $quotes; @@ -861,14 +789,16 @@ public function assertHtml(array $expected, string $string, bool $fullDebug = fa ]; } } - /** - * @var array $assertion - */ + foreach ($regex as $i => $assertion) { $matches = false; if (isset($assertion['attrs'])) { + /** + * @var array $assertion + * @var string $string + */ $string = $this->_assertAttributes($assertion, $string, $fullDebug, $regex); - if ($fullDebug === true && $string === false) { + if ($fullDebug && $string === false) { debug($string, true); debug($regex, true); } @@ -876,25 +806,28 @@ public function assertHtml(array $expected, string $string, bool $fullDebug = fa } // If 'attrs' is not present then the array is just a regular int-offset one + /** + * @var array $assertion + */ [$description, $expressions, $itemNum] = $assertion; $expression = ''; foreach ((array)$expressions as $expression) { $expression = sprintf('/^%s/s', $expression); - if (preg_match($expression, $string, $match)) { + if ($string && preg_match($expression, $string, $match)) { $matches = true; $string = substr($string, strlen($match[0])); break; } } if (!$matches) { - if ($fullDebug === true) { + if ($fullDebug) { debug($string); debug($regex); } $this->assertMatchesRegularExpression( $expression, - $string, - sprintf('Item #%d / regex #%d failed: %s', $itemNum, $i, $description) + (string)$string, + sprintf('Item #%d / regex #%d failed: %s', $itemNum, $i, $description), ); return false; @@ -915,8 +848,12 @@ public function assertHtml(array $expected, string $string, bool $fullDebug = fa * @param array|string $regex Full regexp from `assertHtml` * @return string|false */ - protected function _assertAttributes(array $assertions, string $string, bool $fullDebug = false, $regex = '') - { + protected function _assertAttributes( + array $assertions, + string $string, + bool $fullDebug = false, + array|string $regex = '', + ): string|false { $asserts = $assertions['attrs']; $explains = $assertions['explains']; do { @@ -932,7 +869,7 @@ protected function _assertAttributes(array $assertions, string $string, bool $fu } } if ($matches === false) { - if ($fullDebug === true) { + if ($fullDebug) { debug($string); debug($regex); } @@ -1031,7 +968,7 @@ protected function skipUnless($condition, $message = '') * @throws \Cake\ORM\Exception\MissingTableClassException * @return \Cake\ORM\Table|\PHPUnit\Framework\MockObject\MockObject */ - public function getMockForModel(string $alias, array $methods = [], array $options = []) + public function getMockForModel(string $alias, array $methods = [], array $options = []): Table|MockObject { $className = $this->_getTableClassName($alias, $options); $connectionName = $className::defaultConnectionName(); @@ -1048,6 +985,7 @@ public function getMockForModel(string $alias, array $methods = [], array $optio }, $reflection->getMethods()); $existingMethods = array_intersect($classMethods, $methods); + /** @var list $nonExistingMethods */ $nonExistingMethods = array_diff($methods, $existingMethods); $builder = $this->getMockBuilder($className) @@ -1058,11 +996,20 @@ public function getMockForModel(string $alias, array $methods = [], array $optio } if ($nonExistingMethods) { + trigger_error( + sprintf( + 'Adding non-existent methods (%s) to model `%s` ' . + 'when mocking will not work in future PHPUnit versions.', + implode(',', $nonExistingMethods), + $alias, + ), + E_USER_DEPRECATED, + ); $builder->addMethods($nonExistingMethods); } - /** @var \Cake\ORM\Table $mock */ $mock = $builder->getMock(); + assert($mock instanceof Table); if (empty($options['entityClass']) && $mock->getEntityClass() === Entity::class) { $parts = explode('\\', $className); @@ -1088,15 +1035,14 @@ public function getMockForModel(string $alias, array $methods = [], array $optio * * @param string $alias The model to get a mock for. * @param array $options The config data for the mock's constructor. - * @return string + * @return class-string<\Cake\ORM\Table> * @throws \Cake\ORM\Exception\MissingTableClassException - * @psalm-return class-string<\Cake\ORM\Table> */ protected function _getTableClassName(string $alias, array $options): string { if (empty($options['className'])) { $class = Inflector::camelize($alias); - /** @psalm-var class-string<\Cake\ORM\Table>|null */ + /** @var class-string<\Cake\ORM\Table>|null $className */ $className = App::className($class, 'Model/Table', 'Table'); if (!$className) { throw new MissingTableClassException([$alias]); @@ -1151,4 +1097,70 @@ public function getFixtures(): array { return $this->fixtures; } + + /** + * @param string $regex A regex to match against the warning message + * @param \Closure $callable Callable which should trigger the warning + * @return void + * @throws \Exception + */ + public function expectNoticeMessageMatches(string $regex, Closure $callable): void + { + $this->expectErrorHandlerMessageMatches($regex, $callable, E_USER_NOTICE); + } + + /** + * @param string $regex A regex to match against the deprecation message + * @param \Closure $callable Callable which should trigger the warning + * @return void + * @throws \Exception + */ + public function expectDeprecationMessageMatches(string $regex, Closure $callable): void + { + $this->expectErrorHandlerMessageMatches($regex, $callable, E_USER_DEPRECATED); + } + + /** + * @param string $regex A regex to match against the warning message + * @param \Closure $callable Callable which should trigger the warning + * @return void + * @throws \Exception + */ + public function expectWarningMessageMatches(string $regex, Closure $callable): void + { + $this->expectErrorHandlerMessageMatches($regex, $callable, E_USER_WARNING); + } + + /** + * @param string $regex A regex to match against the error message + * @param \Closure $callable Callable which should trigger the warning + * @return void + * @throws \Exception + */ + public function expectErrorMessageMatches(string $regex, Closure $callable): void + { + $this->expectErrorHandlerMessageMatches($regex, $callable, E_ERROR | E_USER_ERROR); + } + + /** + * @param string $regex A regex to match against the warning message + * @param \Closure $callable Callable which should trigger the warning + * @param int $errorLevel The error level to listen to + * @return void + * @throws \Exception + */ + protected function expectErrorHandlerMessageMatches(string $regex, Closure $callable, int $errorLevel): void + { + set_error_handler(static function (int $errno, string $errstr): never { + throw new Exception($errstr, $errno); + }, $errorLevel); + + $this->expectException(Exception::class); + $this->expectExceptionMessageMatches($regex); + try { + $callable(); + } finally { + restore_error_handler(); + } + } } diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/TestEmailTransport.php b/app/vendor/cakephp/cakephp/src/TestSuite/TestEmailTransport.php index f70f872fa..0b34fd257 100644 --- a/app/vendor/cakephp/cakephp/src/TestSuite/TestEmailTransport.php +++ b/app/vendor/cakephp/cakephp/src/TestSuite/TestEmailTransport.php @@ -32,13 +32,14 @@ class TestEmailTransport extends DebugTransport /** * @var array */ - private static $messages = []; + protected static array $messages = []; /** * Stores email for later assertions * * @param \Cake\Mailer\Message $message Message - * @return array{headers: string, message: string} + * @return array Contains 'headers' and 'message' keys. Additional keys allowed. + * @phpstan-return array{headers: string, message: string, ...} */ public function send(Message $message): array { @@ -69,7 +70,7 @@ public static function replaceAllTransports(): void * * @return array<\Cake\Mailer\Message> */ - public static function getMessages() + public static function getMessages(): array { return static::$messages; } diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/TestListenerTrait.php b/app/vendor/cakephp/cakephp/src/TestSuite/TestListenerTrait.php deleted file mode 100644 index f51e74590..000000000 --- a/app/vendor/cakephp/cakephp/src/TestSuite/TestListenerTrait.php +++ /dev/null @@ -1,102 +0,0 @@ -session === null) { return null; diff --git a/app/vendor/cakephp/cakephp/src/TestSuite/TestSuite.php b/app/vendor/cakephp/cakephp/src/TestSuite/TestSuite.php deleted file mode 100644 index eb96dffe9..000000000 --- a/app/vendor/cakephp/cakephp/src/TestSuite/TestSuite.php +++ /dev/null @@ -1,69 +0,0 @@ -find($directory, '/\.php$/'); - foreach ($files as $file => $fileInfo) { - $this->addTestFile($file); - } - } - - /** - * Recursively adds all the files in a directory to the test suite. - * - * @param string $directory The directory subtree to add tests from. - * @return void - */ - public function addTestDirectoryRecursive(string $directory = '.'): void - { - deprecationWarning('4.5.0 - TestSuite is deprecated as PHPunit is removing support for testsuites.'); - $fs = new Filesystem(); - $files = $fs->findRecursive($directory, function (SplFileInfo $current) { - $file = $current->getFilename(); - if ($file[0] === '.' || !preg_match('/\.php$/', $file)) { - return false; - } - - return true; - }); - foreach ($files as $file => $fileInfo) { - $this->addTestFile($file); - } - } -} diff --git a/app/vendor/cakephp/cakephp/src/Utility/CookieCryptTrait.php b/app/vendor/cakephp/cakephp/src/Utility/CookieCryptTrait.php index a57bfc034..70e3c8517 100644 --- a/app/vendor/cakephp/cakephp/src/Utility/CookieCryptTrait.php +++ b/app/vendor/cakephp/cakephp/src/Utility/CookieCryptTrait.php @@ -16,14 +16,14 @@ */ namespace Cake\Utility; -use RuntimeException; +use InvalidArgumentException; /** * Cookie Crypt Trait. * * Provides the encrypt/decrypt logic for the CookieComponent. * - * @link https://book.cakephp.org/4/en/controllers/components/cookie.html + * @link https://book.cakephp.org/5/en/controllers/components/cookie.html */ trait CookieCryptTrait { @@ -32,7 +32,7 @@ trait CookieCryptTrait * * @var array */ - protected $_validCiphers = ['aes']; + protected array $_validCiphers = ['aes']; /** * Returns the encryption key to be used. @@ -50,7 +50,7 @@ abstract protected function _getCookieEncryptionKey(): string; * @param string|null $key Used as the security salt if specified. * @return string Encoded values */ - protected function _encrypt($value, $encrypt, ?string $key = null): string + protected function _encrypt(array|string $value, string|false $encrypt, ?string $key = null): string { if (is_array($value)) { $value = $this->_implode($value); @@ -61,9 +61,7 @@ protected function _encrypt($value, $encrypt, ?string $key = null): string $this->_checkCipher($encrypt); $prefix = 'Q2FrZQ==.'; $cipher = ''; - if ($key === null) { - $key = $this->_getCookieEncryptionKey(); - } + $key ??= $this->_getCookieEncryptionKey(); if ($encrypt === 'aes') { $cipher = Security::encrypt($value, $key); } @@ -83,9 +81,9 @@ protected function _checkCipher(string $encrypt): void if (!in_array($encrypt, $this->_validCiphers, true)) { $msg = sprintf( 'Invalid encryption cipher. Must be one of %s or false.', - implode(', ', $this->_validCiphers) + implode(', ', $this->_validCiphers), ); - throw new RuntimeException($msg); + throw new InvalidArgumentException($msg); } } @@ -97,7 +95,7 @@ protected function _checkCipher(string $encrypt): void * @param string|null $key Used as the security salt if specified. * @return array|string Decrypted values */ - protected function _decrypt($values, $mode, ?string $key = null) + protected function _decrypt(array|string $values, string|false $mode, ?string $key = null): array|string { if (is_string($values)) { return $this->_decode($values, $mode, $key); @@ -119,7 +117,7 @@ protected function _decrypt($values, $mode, ?string $key = null) * @param string|null $key Used as the security salt if specified. * @return array|string Decoded values. */ - protected function _decode(string $value, $encrypt, ?string $key) + protected function _decode(string $value, string|false $encrypt, ?string $key): array|string { if (!$encrypt) { return $this->_explode($value); @@ -138,9 +136,7 @@ protected function _decode(string $value, $encrypt, ?string $key) return ''; } - if ($key === null) { - $key = $this->_getCookieEncryptionKey(); - } + $key ??= $this->_getCookieEncryptionKey(); if ($encrypt === 'aes') { $value = Security::decrypt($value, $key); } @@ -160,7 +156,7 @@ protected function _decode(string $value, $encrypt, ?string $key) */ protected function _implode(array $array): string { - return json_encode($array); + return json_encode($array, JSON_THROW_ON_ERROR); } /** @@ -170,13 +166,11 @@ protected function _implode(array $array): string * @param string $string A string containing JSON encoded data, or a bare string. * @return array|string Map of key and values */ - protected function _explode(string $string) + protected function _explode(string $string): array|string { $first = substr($string, 0, 1); if ($first === '{' || $first === '[') { - $ret = json_decode($string, true); - - return $ret ?? $string; + return json_decode($string, true) ?? $string; } $array = []; foreach (explode(',', $string) as $pair) { diff --git a/app/vendor/cakephp/cakephp/src/Utility/Crypto/OpenSsl.php b/app/vendor/cakephp/cakephp/src/Utility/Crypto/OpenSsl.php index 99590d389..ea1f6e235 100644 --- a/app/vendor/cakephp/cakephp/src/Utility/Crypto/OpenSsl.php +++ b/app/vendor/cakephp/cakephp/src/Utility/Crypto/OpenSsl.php @@ -16,6 +16,8 @@ */ namespace Cake\Utility\Crypto; +use Cake\Core\Exception\CakeException; + /** * OpenSSL implementation of crypto features for Cake\Utility\Security * @@ -47,6 +49,9 @@ public static function encrypt(string $plain, string $key): string { $method = static::METHOD_AES_256_CBC; $ivSize = openssl_cipher_iv_length($method); + if ($ivSize === false) { + throw new CakeException(sprintf('Cannot get the cipher iv length for `%s`', $method)); + } $iv = openssl_random_pseudo_bytes($ivSize); @@ -58,13 +63,16 @@ public static function encrypt(string $plain, string $key): string * * @param string $cipher The ciphertext to decrypt. * @param string $key The 256 bit/32 byte key to use as a cipher key. - * @return string Decrypted data. Any trailing null bytes will be removed. + * @return string|null Decrypted data. Any trailing null bytes will be removed. * @throws \InvalidArgumentException On invalid data or key. */ public static function decrypt(string $cipher, string $key): ?string { $method = static::METHOD_AES_256_CBC; $ivSize = openssl_cipher_iv_length($method); + if ($ivSize === false) { + throw new CakeException(sprintf('Cannot get the cipher iv length for `%s`', $method)); + } $iv = mb_substr($cipher, 0, $ivSize, '8bit'); $cipher = mb_substr($cipher, $ivSize, null, '8bit'); diff --git a/app/vendor/cakephp/cakephp/src/Filesystem/Filesystem.php b/app/vendor/cakephp/cakephp/src/Utility/Filesystem.php similarity index 86% rename from app/vendor/cakephp/cakephp/src/Filesystem/Filesystem.php rename to app/vendor/cakephp/cakephp/src/Utility/Filesystem.php index 3f3921855..fa51ad3d6 100644 --- a/app/vendor/cakephp/cakephp/src/Filesystem/Filesystem.php +++ b/app/vendor/cakephp/cakephp/src/Utility/Filesystem.php @@ -14,10 +14,11 @@ * @since 4.0.0 * @license https://opensource.org/licenses/mit-license.php MIT License */ -namespace Cake\Filesystem; +namespace Cake\Utility; use Cake\Core\Exception\CakeException; use CallbackFilterIterator; +use Closure; use FilesystemIterator; use Iterator; use RecursiveCallbackFilterIterator; @@ -47,14 +48,14 @@ class Filesystem * Find files / directories (non-recursively) in given directory path. * * @param string $path Directory path. - * @param mixed $filter If string will be used as regex for filtering using + * @param \Closure|string|null $filter If string will be used as regex for filtering using * `RegexIterator`, if callable will be as callback for `CallbackFilterIterator`. * @param int|null $flags Flags for FilesystemIterator::__construct(); * @return \Iterator */ - public function find(string $path, $filter = null, ?int $flags = null): Iterator + public function find(string $path, Closure|string|null $filter = null, ?int $flags = null): Iterator { - $flags = $flags ?? FilesystemIterator::KEY_AS_PATHNAME + $flags ??= FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::SKIP_DOTS; $directory = new FilesystemIterator($path, $flags); @@ -70,15 +71,15 @@ public function find(string $path, $filter = null, ?int $flags = null): Iterator * Find files/ directories recursively in given directory path. * * @param string $path Directory path. - * @param mixed $filter If string will be used as regex for filtering using + * @param \Closure|string|null $filter If string will be used as regex for filtering using * `RegexIterator`, if callable will be as callback for `CallbackFilterIterator`. * Hidden directories (starting with dot e.g. .git) are always skipped. * @param int|null $flags Flags for FilesystemIterator::__construct(); * @return \Iterator */ - public function findRecursive(string $path, $filter = null, ?int $flags = null): Iterator + public function findRecursive(string $path, Closure|string|null $filter = null, ?int $flags = null): Iterator { - $flags = $flags ?? FilesystemIterator::KEY_AS_PATHNAME + $flags ??= FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::SKIP_DOTS; $directory = new RecursiveDirectoryIterator($path, $flags); @@ -86,17 +87,17 @@ public function findRecursive(string $path, $filter = null, ?int $flags = null): $dirFilter = new RecursiveCallbackFilterIterator( $directory, function (SplFileInfo $current) { - if ($current->getFilename()[0] === '.' && $current->isDir()) { + if (str_starts_with($current->getFilename(), '.') && $current->isDir()) { return false; } return true; - } + }, ); $flatten = new RecursiveIteratorIterator( $dirFilter, - RecursiveIteratorIterator::CHILD_FIRST + RecursiveIteratorIterator::CHILD_FIRST, ); if ($filter === null) { @@ -110,10 +111,10 @@ function (SplFileInfo $current) { * Wrap iterator in additional filtering iterator. * * @param \Iterator $iterator Iterator - * @param mixed $filter Regex string or callback. + * @param \Closure|string $filter Regex string or callback. * @return \Iterator */ - protected function filterIterator(Iterator $iterator, $filter): Iterator + protected function filterIterator(Iterator $iterator, Closure|string $filter): Iterator { if (is_string($filter)) { return new RegexIterator($iterator, $filter); @@ -174,7 +175,7 @@ public function mkdir(string $dir, int $mode = 0755): void // phpcs:ignore if (@mkdir($dir, $mode, true) === false) { umask($old); - throw new CakeException(sprintf('Failed to create directory "%s"', $dir)); + throw new CakeException(sprintf('Failed to create directory `%s`', $dir)); } umask($old); @@ -194,15 +195,16 @@ public function deleteDir(string $path): bool } if (!is_dir($path)) { - throw new CakeException(sprintf('"%s" is not a directory', $path)); + throw new CakeException(sprintf('`%s` is not a directory', $path)); } $iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS), - RecursiveIteratorIterator::CHILD_FIRST + RecursiveIteratorIterator::CHILD_FIRST, ); $result = true; + /** @var \SplFileInfo $fileInfo */ foreach ($iterator as $fileInfo) { $isWindowsLink = DIRECTORY_SEPARATOR === '\\' && $fileInfo->getType() === 'link'; if ($fileInfo->getType() === self::TYPE_DIR || $isWindowsLink) { @@ -241,6 +243,7 @@ public function copyDir(string $source, string $destination): bool $this->mkdir($destination); } + /** @var \FilesystemIterator<\SplFileInfo> $iterator */ $iterator = new FilesystemIterator($source); $result = true; @@ -248,13 +251,13 @@ public function copyDir(string $source, string $destination): bool if ($fileInfo->isDir()) { $result = $result && $this->copyDir( $fileInfo->getPathname(), - $destination . DIRECTORY_SEPARATOR . $fileInfo->getFilename() + $destination . DIRECTORY_SEPARATOR . $fileInfo->getFilename(), ); } else { // phpcs:ignore $result = $result && @copy( $fileInfo->getPathname(), - $destination . DIRECTORY_SEPARATOR . $fileInfo->getFilename() + $destination . DIRECTORY_SEPARATOR . $fileInfo->getFilename(), ); } } @@ -270,6 +273,6 @@ public function copyDir(string $source, string $destination): bool */ public function isStream(string $path): bool { - return strpos($path, '://') !== false; + return str_contains($path, '://'); } } diff --git a/app/vendor/cakephp/cakephp/src/Utility/Hash.php b/app/vendor/cakephp/cakephp/src/Utility/Hash.php index 32781929f..617e1f7d5 100644 --- a/app/vendor/cakephp/cakephp/src/Utility/Hash.php +++ b/app/vendor/cakephp/cakephp/src/Utility/Hash.php @@ -16,8 +16,15 @@ namespace Cake\Utility; use ArrayAccess; +use Cake\Core\Exception\CakeException; use InvalidArgumentException; -use RuntimeException; +use const SORT_ASC; +use const SORT_DESC; +use const SORT_LOCALE_STRING; +use const SORT_NATURAL; +use const SORT_NUMERIC; +use const SORT_REGULAR; +use const SORT_STRING; /** * Library of array functions for manipulating and extracting data @@ -28,7 +35,7 @@ * support for pseudo Xpath, its more fully featured dot notation provides * similar features in a more consistent implementation. * - * @link https://book.cakephp.org/4/en/core-libraries/hash.html + * @link https://book.cakephp.org/5/en/core-libraries/hash.html */ class Hash { @@ -44,30 +51,17 @@ class Hash * @param mixed $default The return value when the path does not exist * @throws \InvalidArgumentException * @return mixed The value fetched from the array, or null. - * @link https://book.cakephp.org/4/en/core-libraries/hash.html#Cake\Utility\Hash::get + * @link https://book.cakephp.org/5/en/core-libraries/hash.html#Cake\Utility\Hash::get */ - public static function get($data, $path, $default = null) + public static function get(ArrayAccess|array $data, array|string|int|null $path, mixed $default = null): mixed { - if (!(is_array($data) || $data instanceof ArrayAccess)) { - throw new InvalidArgumentException( - 'Invalid data type, must be an array or \ArrayAccess instance.' - ); - } - - if (empty($data) || $path === null) { + if (!$data || $path === null) { return $default; } - if (is_string($path) || is_numeric($path)) { + if (is_string($path) || is_int($path)) { $parts = explode('.', (string)$path); } else { - if (!is_array($path)) { - throw new InvalidArgumentException(sprintf( - 'Invalid Parameter %s, should be dot separated path or array.', - $path - )); - } - $parts = $path; } @@ -120,31 +114,26 @@ public static function get($data, $path, $default = null) * @param string $path The path to extract. * @return \ArrayAccess|array An array of the extracted values. Returns an empty array * if there are no matches. - * @link https://book.cakephp.org/4/en/core-libraries/hash.html#Cake\Utility\Hash::extract + * @link https://book.cakephp.org/5/en/core-libraries/hash.html#Cake\Utility\Hash::extract + * @phpstan-return ($path is non-empty-string ? array : \ArrayAccess|array) */ - public static function extract($data, string $path) + public static function extract(ArrayAccess|array $data, string $path): ArrayAccess|array { - if (!(is_array($data) || $data instanceof ArrayAccess)) { - throw new InvalidArgumentException( - 'Invalid data type, must be an array or \ArrayAccess instance.' - ); - } - - if (empty($path)) { + if (!$path) { return $data; } // Simple paths. if (!preg_match('/[{\[]/', $path)) { $data = static::get($data, $path); - if ($data !== null && !(is_array($data) || $data instanceof ArrayAccess)) { + if ($data !== null && (!is_array($data) && !$data instanceof ArrayAccess)) { return [$data]; } return $data !== null ? (array)$data : []; } - if (strpos($path, '[') === false) { + if (!str_contains($path, '[')) { $tokens = explode('.', $path); } else { $tokens = Text::tokenize($path, '.', '[', ']'); @@ -196,8 +185,8 @@ public static function extract($data, string $path) /** * Split token conditions * - * @param string $token the token being splitted. - * @return array [token, conditions] with token splitted + * @param string $token the token being split. + * @return array [token, conditions] with token split */ protected static function _splitConditions(string $token): array { @@ -218,18 +207,14 @@ protected static function _splitConditions(string $token): array * @param string $token The token being matched. * @return bool */ - protected static function _matchToken($key, string $token): bool + protected static function _matchToken(mixed $key, string $token): bool { - switch ($token) { - case '{n}': - return is_numeric($key); - case '{s}': - return is_string($key); - case '{*}': - return true; - default: - return is_numeric($token) ? ($key == $token) : $key === $token; - } + return match ($token) { + '{n}' => is_numeric($key), + '{s}' => is_string($key), + '{*}' => true, + default => is_numeric($token) ? ($key == $token) : $key === $token, + }; } /** @@ -239,13 +224,13 @@ protected static function _matchToken($key, string $token): bool * @param string $selector The patterns to match. * @return bool Fitness of expression. */ - protected static function _matches($data, string $selector): bool + protected static function _matches(ArrayAccess|array $data, string $selector): bool { preg_match_all( '/(\[ (?P[^=>[><]) \s* (?P(?:\/.*?\/ | [^\]]+)) )? \])/x', $selector, $conditions, - PREG_SET_ORDER + PREG_SET_ORDER, ); foreach ($conditions as $cond) { @@ -254,7 +239,7 @@ protected static function _matches($data, string $selector): bool $val = $cond['val'] ?? null; // Presence test. - if (empty($op) && empty($val) && !isset($data[$attr])) { + if (!$op && !$val && !isset($data[$attr])) { return false; } @@ -304,16 +289,18 @@ protected static function _matches($data, string $selector): bool * Insert $values into an array with the given $path. You can use * `{n}` and `{s}` elements to insert $data multiple times. * - * @param array $data The data to insert into. + * @template T of \ArrayAccess|array + * @param T $data The data to insert into. * @param string $path The path to insert at. * @param mixed $values The values to insert. - * @return array The data with $values inserted. - * @link https://book.cakephp.org/4/en/core-libraries/hash.html#Cake\Utility\Hash::insert + * @return \ArrayAccess|array The data with $values inserted. + * @phpstan-return (T is array ? array : \ArrayAccess) + * @link https://book.cakephp.org/5/en/core-libraries/hash.html#Cake\Utility\Hash::insert */ - public static function insert(array $data, string $path, $values = null): array + public static function insert(ArrayAccess|array $data, string $path, mixed $values = null): ArrayAccess|array { - $noTokens = strpos($path, '[') === false; - if ($noTokens && strpos($path, '.') === false) { + $noTokens = !str_contains($path, '['); + if ($noTokens && !str_contains($path, '.')) { $data[$path] = $values; return $data; @@ -325,25 +312,28 @@ public static function insert(array $data, string $path, $values = null): array $tokens = Text::tokenize($path, '.', '[', ']'); } - if ($noTokens && strpos($path, '{') === false) { + if ($noTokens && !str_contains($path, '{')) { return static::_simpleOp('insert', $data, $tokens, $values); } + if (!is_iterable($data)) { + throw new CakeException('Cannot use path tokens of type `{}` or `[]` for non-iterable objects.'); + } + + /** @var string $token */ $token = array_shift($tokens); $nextPath = implode('.', $tokens); [$token, $conditions] = static::_splitConditions($token); foreach ($data as $k => $v) { - if (static::_matchToken($k, $token)) { - if ( - !$conditions || - ((is_array($v) || $v instanceof ArrayAccess) && static::_matches($v, $conditions)) - ) { - $data[$k] = $nextPath - ? static::insert($v, $nextPath, $values) - : array_merge($v, (array)$values); - } + if ( + static::_matchToken($k, $token) && + (!$conditions || ((is_array($v) || $v instanceof ArrayAccess) && static::_matches($v, $conditions))) + ) { + $data[$k] = $nextPath + ? static::insert($v, $nextPath, $values) + : array_merge($v, (array)$values); } } @@ -354,13 +344,17 @@ public static function insert(array $data, string $path, $values = null): array * Perform a simple insert/remove operation. * * @param string $op The operation to do. - * @param array $data The data to operate on. + * @param \ArrayAccess|array $data The data to operate on. * @param array $path The path to work on. * @param mixed $values The values to insert when doing inserts. - * @return array data. + * @return \ArrayAccess|array */ - protected static function _simpleOp(string $op, array $data, array $path, $values = null): array - { + protected static function _simpleOp( + string $op, + ArrayAccess|array $data, + array $path, + mixed $values = null, + ): ArrayAccess|array { $_list = &$data; $count = count($path); @@ -372,14 +366,14 @@ protected static function _simpleOp(string $op, array $data, array $path, $value return $data; } - $_list[$key] = $_list[$key] ?? []; + $_list[$key] ??= []; $_list = &$_list[$key]; - if (!is_array($_list)) { + if (!is_array($_list) && !$_list instanceof ArrayAccess) { $_list = []; } } elseif ($op === 'remove') { if ($i === $last) { - if (is_array($_list)) { + if (is_array($_list) || $_list instanceof ArrayAccess) { unset($_list[$key]); } @@ -400,17 +394,19 @@ protected static function _simpleOp(string $op, array $data, array $path, $value * You can use `{n}` and `{s}` to remove multiple elements * from $data. * - * @param array $data The data to operate on + * @template T of \ArrayAccess|array + * @param T $data The data to operate on * @param string $path A path expression to use to remove. - * @return array The modified array. - * @link https://book.cakephp.org/4/en/core-libraries/hash.html#Cake\Utility\Hash::remove + * @return \ArrayAccess|array The modified array. + * @phpstan-return (T is array ? array : \ArrayAccess) + * @link https://book.cakephp.org/5/en/core-libraries/hash.html#Cake\Utility\Hash::remove */ - public static function remove(array $data, string $path): array + public static function remove(ArrayAccess|array $data, string $path): ArrayAccess|array { - $noTokens = strpos($path, '[') === false; - $noExpansion = strpos($path, '{') === false; + $noTokens = !str_contains($path, '['); + $noExpansion = !str_contains($path, '{'); - if ($noExpansion && $noTokens && strpos($path, '.') === false) { + if ($noExpansion && $noTokens && !str_contains($path, '.')) { unset($data[$path]); return $data; @@ -422,6 +418,11 @@ public static function remove(array $data, string $path): array return static::_simpleOp('remove', $data, $tokens); } + if (!is_iterable($data)) { + throw new CakeException('Cannot use path tokens of type `{}` or `[]` for non-iterable objects.'); + } + + /** @var string $token */ $token = array_shift($tokens); $nextPath = implode('.', $tokens); @@ -429,7 +430,7 @@ public static function remove(array $data, string $path): array foreach ($data as $k => $v) { $match = static::_matchToken($k, $token); - if ($match && is_array($v)) { + if ($match && (is_array($v) || $v instanceof ArrayAccess)) { if ($conditions) { if (static::_matches($v, $conditions)) { if ($nextPath !== '') { @@ -463,56 +464,61 @@ public static function remove(array $data, string $path): array * @param array|string|null $valuePath A dot-separated string. * @param string|null $groupPath A dot-separated string. * @return array Combined array - * @link https://book.cakephp.org/4/en/core-libraries/hash.html#Cake\Utility\Hash::combine - * @throws \RuntimeException When keys and values count is unequal. + * @link https://book.cakephp.org/5/en/core-libraries/hash.html#Cake\Utility\Hash::combine + * @throws \InvalidArgumentException When keys and values count is unequal. */ - public static function combine(array $data, $keyPath, $valuePath = null, ?string $groupPath = null): array - { - if (empty($data)) { + public static function combine( + array $data, + array|string|null $keyPath, + array|string|null $valuePath = null, + ?string $groupPath = null, + ): array { + if (!$data) { return []; } if (is_array($keyPath)) { + /** @var string $format */ $format = array_shift($keyPath); - /** @var array $keys */ $keys = static::format($data, $keyPath, $format); + assert(is_array($keys)); } elseif ($keyPath === null) { $keys = $keyPath; } else { - /** @var array $keys */ $keys = static::extract($data, $keyPath); + assert(is_array($keys)); } if ($keyPath !== null && empty($keys)) { return []; } $vals = null; - if (!empty($valuePath) && is_array($valuePath)) { + if ($valuePath && is_array($valuePath)) { $format = array_shift($valuePath); - /** @var array $vals */ $vals = static::format($data, $valuePath, $format); - } elseif (!empty($valuePath)) { - /** @var array $vals */ + assert(is_array($vals)); + } elseif ($valuePath) { $vals = static::extract($data, $valuePath); + assert(is_array($vals)); } - if (empty($vals)) { + if (!$vals) { $vals = array_fill(0, $keys === null ? count($data) : count($keys), null); } if (is_array($keys) && count($keys) !== count($vals)) { - throw new RuntimeException( - 'Hash::combine() needs an equal number of keys + values.' + throw new InvalidArgumentException( + '`Hash::combine()` needs an equal number of keys + values.', ); } if ($groupPath !== null) { $group = static::extract($data, $groupPath); - if (!empty($group)) { + if ($group) { $c = is_array($keys) ? count($keys) : count($vals); $out = []; for ($i = 0; $i < $c; $i++) { - $group[$i] = $group[$i] ?? 0; - $out[$group[$i]] = $out[$group[$i]] ?? []; + $group[$i] ??= 0; + $out[$group[$i]] ??= []; if ($keys === null) { $out[$group[$i]][] = $vals[$i]; } else { @@ -523,7 +529,7 @@ public static function combine(array $data, $keyPath, $valuePath = null, ?string return $out; } } - if (empty($vals)) { + if (!$vals) { return []; } @@ -546,16 +552,17 @@ public static function combine(array $data, $keyPath, $valuePath = null, ?string * @param array $paths An array containing one or more Hash::extract()-style key paths * @param string $format Format string into which values will be inserted, see sprintf() * @return array|null An array of strings extracted from `$path` and formatted with `$format` - * @link https://book.cakephp.org/4/en/core-libraries/hash.html#Cake\Utility\Hash::format + * @link https://book.cakephp.org/5/en/core-libraries/hash.html#Cake\Utility\Hash::format * @see sprintf() * @see \Cake\Utility\Hash::extract() + * @phpstan-return ($paths is non-empty-array ? array : null) */ public static function format(array $data, array $paths, string $format): ?array { $extracted = []; $count = count($paths); - if (!$count) { + if ($count === 0) { return null; } @@ -587,16 +594,16 @@ public static function format(array $data, array $paths, string $format): ?array * @param array $data The data to search through. * @param array $needle The values to file in $data * @return bool true If $data contains $needle, false otherwise - * @link https://book.cakephp.org/4/en/core-libraries/hash.html#Cake\Utility\Hash::contains + * @link https://book.cakephp.org/5/en/core-libraries/hash.html#Cake\Utility\Hash::contains */ public static function contains(array $data, array $needle): bool { - if (empty($data) || empty($needle)) { + if (!$data || !$needle) { return false; } $stack = []; - while (!empty($needle)) { + while ($needle) { $key = key($needle); $val = $needle[$key]; unset($needle[$key]); @@ -605,14 +612,14 @@ public static function contains(array $data, array $needle): bool $next = $data[$key]; unset($data[$key]); - if (!empty($val)) { + if ($val) { $stack[] = [$val, $next]; } } elseif (!array_key_exists($key, $data) || $data[$key] != $val) { return false; } - if (empty($needle) && !empty($stack)) { + if (!$needle && $stack) { [$needle, $data] = array_pop($stack); } } @@ -631,7 +638,7 @@ public static function contains(array $data, array $needle): bool * @param string $path The path to check for. * @return bool Existence of path. * @see \Cake\Utility\Hash::extract() - * @link https://book.cakephp.org/4/en/core-libraries/hash.html#Cake\Utility\Hash::check + * @link https://book.cakephp.org/5/en/core-libraries/hash.html#Cake\Utility\Hash::check */ public static function check(array $data, string $path): bool { @@ -640,19 +647,19 @@ public static function check(array $data, string $path): bool return false; } - return count($results) > 0; + return $results !== []; } /** * Recursively filters a data set. * * @param array $data Either an array to filter, or value when in callback - * @param callable|array $callback A function to filter the data with. Defaults to - * `static::_filter()` Which strips out all non-zero empty values. + * @param callable|null $callback A function to filter the data with. Defaults to + * all non-empty or zero values. * @return array Filtered array - * @link https://book.cakephp.org/4/en/core-libraries/hash.html#Cake\Utility\Hash::filter + * @link https://book.cakephp.org/5/en/core-libraries/hash.html#Cake\Utility\Hash::filter */ - public static function filter(array $data, $callback = [Hash::class, '_filter']): array + public static function filter(array $data, ?callable $callback = null): array { foreach ($data as $k => $v) { if (is_array($v)) { @@ -660,7 +667,7 @@ public static function filter(array $data, $callback = [Hash::class, '_filter']) } } - return array_filter($data, $callback); + return array_filter($data, $callback ?? static::_filter(...)); } /** @@ -669,20 +676,19 @@ public static function filter(array $data, $callback = [Hash::class, '_filter']) * @param mixed $var Array to filter. * @return bool */ - protected static function _filter($var): bool + protected static function _filter(mixed $var): bool { return $var === 0 || $var === 0.0 || $var === '0' || !empty($var); } /** * Collapses a multi-dimensional array into a single dimension, using a delimited array path for - * each array element's key, i.e. [['Foo' => ['Bar' => 'Far']]] becomes - * ['0.Foo.Bar' => 'Far'].) + * each array element's key, i.e. `[['Foo' => ['Bar' => 'Far']]]` becomes `['0.Foo.Bar' => 'Far']`. * * @param array $data Array to flatten * @param string $separator String used to separate array key elements in a path, defaults to '.' * @return array - * @link https://book.cakephp.org/4/en/core-libraries/hash.html#Cake\Utility\Hash::flatten + * @link https://book.cakephp.org/5/en/core-libraries/hash.html#Cake\Utility\Hash::flatten */ public static function flatten(array $data, string $separator = '.'): array { @@ -696,8 +702,8 @@ public static function flatten(array $data, string $separator = '.'): array $element = $data[$key]; unset($data[$key]); - if (is_array($element) && !empty($element)) { - if (!empty($data)) { + if (is_array($element) && $element !== []) { + if ($data) { $stack[] = [$data, $path]; } $data = $element; @@ -707,7 +713,7 @@ public static function flatten(array $data, string $separator = '.'): array $result[$path . $key] = $element; } - if (empty($data) && !empty($stack)) { + if (!$data && $stack) { [$data, $path] = array_pop($stack); reset($data); } @@ -727,7 +733,7 @@ public static function flatten(array $data, string $separator = '.'): array * @param array $data Flattened array * @param string $separator The delimiter used * @return array - * @link https://book.cakephp.org/4/en/core-libraries/hash.html#Cake\Utility\Hash::expand + * @link https://book.cakephp.org/5/en/core-libraries/hash.html#Cake\Utility\Hash::expand */ public static function expand(array $data, string $separator = '.'): array { @@ -767,9 +773,9 @@ public static function expand(array $data, string $separator = '.'): array * @param array $data Array to be merged * @param mixed $merge Array to merge with. The argument and all trailing arguments will be array cast when merged * @return array Merged array - * @link https://book.cakephp.org/4/en/core-libraries/hash.html#Cake\Utility\Hash::merge + * @link https://book.cakephp.org/5/en/core-libraries/hash.html#Cake\Utility\Hash::merge */ - public static function merge(array $data, $merge): array + public static function merge(array $data, mixed $merge): array { $args = array_slice(func_get_args(), 1); $return = $data; @@ -793,7 +799,7 @@ public static function merge(array $data, $merge): array */ protected static function _merge(array $stack, array &$return): void { - while (!empty($stack)) { + while ($stack !== []) { foreach ($stack as $curKey => &$curMerge) { foreach ($curMerge[0] as $key => &$val) { if (!is_array($curMerge[1])) { @@ -824,11 +830,11 @@ protected static function _merge(array $stack, array &$return): void * * @param array $data The array to check. * @return bool true if values are numeric, false otherwise - * @link https://book.cakephp.org/4/en/core-libraries/hash.html#Cake\Utility\Hash::numeric + * @link https://book.cakephp.org/5/en/core-libraries/hash.html#Cake\Utility\Hash::numeric */ public static function numeric(array $data): bool { - if (empty($data)) { + if (!$data) { return false; } @@ -844,11 +850,11 @@ public static function numeric(array $data): bool * * @param array $data Array to count dimensions on * @return int The number of dimensions in $data - * @link https://book.cakephp.org/4/en/core-libraries/hash.html#Cake\Utility\Hash::dimensions + * @link https://book.cakephp.org/5/en/core-libraries/hash.html#Cake\Utility\Hash::dimensions */ public static function dimensions(array $data): int { - if (empty($data)) { + if (!$data) { return 0; } reset($data); @@ -871,22 +877,20 @@ public static function dimensions(array $data): int * * @param array $data Array to count dimensions on * @return int The maximum number of dimensions in $data - * @link https://book.cakephp.org/4/en/core-libraries/hash.html#Cake\Utility\Hash::maxDimensions + * @link https://book.cakephp.org/5/en/core-libraries/hash.html#Cake\Utility\Hash::maxDimensions */ public static function maxDimensions(array $data): int { $depth = []; - if (!empty($data)) { - foreach ($data as $value) { - if (is_array($value)) { - $depth[] = static::maxDimensions($value) + 1; - } else { - $depth[] = 1; - } + foreach ($data as $value) { + if (is_array($value)) { + $depth[] = static::maxDimensions($value) + 1; + } else { + $depth[] = 1; } } - return empty($depth) ? 0 : max($depth); + return $depth === [] ? 0 : max($depth); } /** @@ -897,7 +901,7 @@ public static function maxDimensions(array $data): int * @param string $path The path to extract for mapping over. * @param callable $function The function to call on each extracted value. * @return array An array of the modified values. - * @link https://book.cakephp.org/4/en/core-libraries/hash.html#Cake\Utility\Hash::map + * @link https://book.cakephp.org/5/en/core-libraries/hash.html#Cake\Utility\Hash::map */ public static function map(array $data, string $path, callable $function): array { @@ -913,9 +917,9 @@ public static function map(array $data, string $path, callable $function): array * @param string $path The path to extract from $data. * @param callable $function The function to call on each extracted value. * @return mixed The reduced value. - * @link https://book.cakephp.org/4/en/core-libraries/hash.html#Cake\Utility\Hash::reduce + * @link https://book.cakephp.org/5/en/core-libraries/hash.html#Cake\Utility\Hash::reduce */ - public static function reduce(array $data, string $path, callable $function) + public static function reduce(array $data, string $path, callable $function): mixed { $values = (array)static::extract($data, $path); @@ -945,9 +949,9 @@ public static function reduce(array $data, string $path, callable $function) * @param string $path The path to extract from $data. * @param callable $function The function to call on each extracted value. * @return mixed The results of the applied method. - * @link https://book.cakephp.org/4/en/core-libraries/hash.html#Cake\Utility\Hash::apply + * @link https://book.cakephp.org/5/en/core-libraries/hash.html#Cake\Utility\Hash::apply */ - public static function apply(array $data, string $path, callable $function) + public static function apply(array $data, string $path, callable $function): mixed { $values = (array)static::extract($data, $path); @@ -985,11 +989,15 @@ public static function apply(array $data, string $path, callable $function) * @param string|int $dir See directions above. Defaults to 'asc'. * @param array|string $type See direction types above. Defaults to 'regular'. * @return array Sorted array of data - * @link https://book.cakephp.org/4/en/core-libraries/hash.html#Cake\Utility\Hash::sort + * @link https://book.cakephp.org/5/en/core-libraries/hash.html#Cake\Utility\Hash::sort */ - public static function sort(array $data, string $path, $dir = 'asc', $type = 'regular'): array - { - if (empty($data)) { + public static function sort( + array $data, + string $path, + string|int $dir = 'asc', + array|string $type = 'regular', + ): array { + if (!$data) { return []; } $originalKeys = array_keys($data); @@ -997,8 +1005,8 @@ public static function sort(array $data, string $path, $dir = 'asc', $type = 're if ($numeric) { $data = array_values($data); } - /** @var array $sortValues */ $sortValues = static::extract($data, $path); + assert(is_array($sortValues)); $dataCount = count($data); // Make sortValues match the data length, as some keys could be missing @@ -1014,16 +1022,15 @@ public static function sort(array $data, string $path, $dir = 'asc', $type = 're $sortValues = array_pad($sortValues, $dataCount, null); } $result = static::_squash($sortValues); - /** @var array $keys */ $keys = static::extract($result, '{n}.id'); - /** @var array $values */ + $values = static::extract($result, '{n}.value'); if (is_string($dir)) { $dir = strtolower($dir); } - if (!in_array($dir, [\SORT_ASC, \SORT_DESC], true)) { - $dir = $dir === 'asc' ? \SORT_ASC : \SORT_DESC; + if (!in_array($dir, [SORT_ASC, SORT_DESC], true)) { + $dir = $dir === 'asc' ? SORT_ASC : SORT_DESC; } $ignoreCase = false; @@ -1037,15 +1044,15 @@ public static function sort(array $data, string $path, $dir = 'asc', $type = 're $type = strtolower($type); if ($type === 'numeric') { - $type = \SORT_NUMERIC; + $type = SORT_NUMERIC; } elseif ($type === 'string') { - $type = \SORT_STRING; + $type = SORT_STRING; } elseif ($type === 'natural') { - $type = \SORT_NATURAL; + $type = SORT_NATURAL; } elseif ($type === 'locale') { - $type = \SORT_LOCALE_STRING; + $type = SORT_LOCALE_STRING; } else { - $type = \SORT_REGULAR; + $type = SORT_REGULAR; } if ($ignoreCase) { $values = array_map('mb_strtolower', $values); @@ -1074,10 +1081,10 @@ public static function sort(array $data, string $path, $dir = 'asc', $type = 're * Squashes an array to a single hash so it can be sorted. * * @param array $data The data to squash. - * @param mixed $key The key for the data. + * @param string|int|null $key The key for the data. * @return array */ - protected static function _squash(array $data, $key = null): array + protected static function _squash(array $data, string|int|null $key = null): array { $stack = []; foreach ($data as $k => $r) { @@ -1085,7 +1092,7 @@ protected static function _squash(array $data, $key = null): array if ($key !== null) { $id = $key; } - if (is_array($r) && !empty($r)) { + if (is_array($r) && $r !== []) { $stack = array_merge($stack, static::_squash($r, $id)); } else { $stack[] = ['id' => $id, 'value' => $r]; @@ -1104,14 +1111,14 @@ protected static function _squash(array $data, $key = null): array * @param array $compare Second value * @return array Returns the key => value pairs that are not common in $data and $compare * The expression for this function is ($data - $compare) + ($compare - ($data - $compare)) - * @link https://book.cakephp.org/4/en/core-libraries/hash.html#Cake\Utility\Hash::diff + * @link https://book.cakephp.org/5/en/core-libraries/hash.html#Cake\Utility\Hash::diff */ public static function diff(array $data, array $compare): array { - if (empty($data)) { + if (!$data) { return $compare; } - if (empty($compare)) { + if (!$compare) { return $data; } $intersection = array_intersect_key($data, $compare); @@ -1131,14 +1138,14 @@ public static function diff(array $data, array $compare): array * @param array $data The data to append onto. * @param array $compare The data to compare and append onto. * @return array The merged array. - * @link https://book.cakephp.org/4/en/core-libraries/hash.html#Cake\Utility\Hash::mergeDiff + * @link https://book.cakephp.org/5/en/core-libraries/hash.html#Cake\Utility\Hash::mergeDiff */ public static function mergeDiff(array $data, array $compare): array { - if (empty($data) && !empty($compare)) { + if (!$data && $compare !== []) { return $compare; } - if (empty($compare)) { + if (!$compare) { return $data; } foreach ($compare as $key => $value) { @@ -1159,9 +1166,9 @@ public static function mergeDiff(array $data, array $compare): array * @param bool $assoc If true, $data will be converted to an associative array. * @param mixed $default The default value to use when a top level numeric key is converted to associative form. * @return array - * @link https://book.cakephp.org/4/en/core-libraries/hash.html#Cake\Utility\Hash::normalize + * @link https://book.cakephp.org/5/en/core-libraries/hash.html#Cake\Utility\Hash::normalize */ - public static function normalize(array $data, bool $assoc = true, $default = null): array + public static function normalize(array $data, bool $assoc = true, mixed $default = null): array { $keys = array_keys($data); $count = count($keys); @@ -1195,7 +1202,7 @@ public static function normalize(array $data, bool $assoc = true, $default = nul * * ### Options: * - * - `children` The key name to use in the resultset for children. + * - `children` The key name to use in the result set for children. * - `idPath` The path to a key that identifies each entry. Should be * compatible with Hash::extract(). Defaults to `{n}.$alias.id` * - `parentPath` The path to a key that identifies the parent of each entry. @@ -1203,11 +1210,12 @@ public static function normalize(array $data, bool $assoc = true, $default = nul * - `root` The id of the desired top-most result. * * @param array $data The data to nest. - * @param array $options Options are: + * @param array $options Options. * @return array of results, nested * @see \Cake\Utility\Hash::extract() * @throws \InvalidArgumentException When providing invalid data. - * @link https://book.cakephp.org/4/en/core-libraries/hash.html#Cake\Utility\Hash::nest + * @link https://book.cakephp.org/5/en/core-libraries/hash.html#Cake\Utility\Hash::nest + * @phpstan-param array{idPath?: string, parentPath?: string, children?: string, root?: string|null} $options */ public static function nest(array $data, array $options = []): array { @@ -1217,15 +1225,15 @@ public static function nest(array $data, array $options = []): array $alias = key(current($data)); $options += [ - 'idPath' => "{n}.$alias.id", - 'parentPath' => "{n}.$alias.parent_id", + 'idPath' => "{n}.{$alias}.id", + 'parentPath' => "{n}.{$alias}.parent_id", 'children' => 'children', 'root' => null, ]; - - $return = $idMap = []; - /** @var array $ids */ + $return = []; + $idMap = []; $ids = static::extract($data, $options['idPath']); + assert(is_array($ids)); $idKeys = explode('.', $options['idPath']); array_shift($idKeys); @@ -1269,6 +1277,7 @@ public static function nest(array $data, array $options = []): array } } + /** @var array */ return array_values($return); } } diff --git a/app/vendor/cakephp/cakephp/src/Utility/Inflector.php b/app/vendor/cakephp/cakephp/src/Utility/Inflector.php index 8abcd079d..7a7c06057 100644 --- a/app/vendor/cakephp/cakephp/src/Utility/Inflector.php +++ b/app/vendor/cakephp/cakephp/src/Utility/Inflector.php @@ -22,7 +22,7 @@ * Inflector pluralizes and singularizes English nouns. * Used by CakePHP's naming conventions throughout the framework. * - * @link https://book.cakephp.org/4/en/core-libraries/inflector.html + * @link https://book.cakephp.org/5/en/core-libraries/inflector.html */ class Inflector { @@ -31,7 +31,7 @@ class Inflector * * @var array */ - protected static $_plural = [ + protected static array $_plural = [ '/(s)tatus$/i' => '\1tatuses', '/(quiz)$/i' => '\1zes', '/^(ox)$/i' => '\1\2en', @@ -62,7 +62,7 @@ class Inflector * * @var array */ - protected static $_singular = [ + protected static array $_singular = [ '/(s)tatuses$/i' => '\1\2tatus', '/^(.*)(menu)s$/i' => '\1\2', '/(quiz)zes$/i' => '\\1', @@ -105,7 +105,7 @@ class Inflector * * @var array */ - protected static $_irregular = [ + protected static array $_irregular = [ 'atlas' => 'atlases', 'beef' => 'beefs', 'brief' => 'briefs', @@ -155,7 +155,7 @@ class Inflector * * @var array */ - protected static $_uninflected = [ + protected static array $_uninflected = [ '.*[nrlm]ese', '.*data', '.*deer', '.*fish', '.*measles', '.*ois', '.*pox', '.*sheep', 'people', 'feedback', 'stadia', '.*?media', 'chassis', 'clippers', 'debris', 'diabetes', 'equipment', 'gallows', @@ -168,14 +168,14 @@ class Inflector * * @var array */ - protected static $_cache = []; + protected static array $_cache = []; /** * The initial state of Inflector so reset() works. * * @var array */ - protected static $_initialState = []; + protected static array $_initialState = []; /** * Cache inflected values, and return if already available @@ -185,7 +185,7 @@ class Inflector * @param string|false $value Inflected value * @return string|false Inflected value on cache hit or false on cache miss. */ - protected static function _cache(string $type, string $key, $value = false) + protected static function _cache(string $type, string $key, string|false $value = false): string|false { $key = '_' . $key; $type = '_' . $type; @@ -209,7 +209,7 @@ protected static function _cache(string $type, string $key, $value = false) */ public static function reset(): void { - if (empty(static::$_initialState)) { + if (static::$_initialState === []) { static::$_initialState = get_class_vars(self::class); return; @@ -249,7 +249,7 @@ public static function rules(string $type, array $rules, bool $reset = false): v } elseif ($type === 'uninflected') { static::$_uninflected = array_merge( $rules, - static::$_uninflected + static::$_uninflected, ); } else { static::${$var} = $rules + static::${$var}; @@ -263,7 +263,7 @@ public static function rules(string $type, array $rules, bool $reset = false): v * * @param string $word Word in singular * @return string Word in plural - * @link https://book.cakephp.org/4/en/core-libraries/inflector.html#creating-plural-singular-forms + * @link https://book.cakephp.org/5/en/core-libraries/inflector.html#creating-plural-singular-forms */ public static function pluralize(string $word): string { @@ -301,7 +301,7 @@ public static function pluralize(string $word): string foreach (static::$_plural as $rule => $replacement) { if (preg_match($rule, $word)) { - static::$_cache['pluralize'][$word] = preg_replace($rule, $replacement, $word); + static::$_cache['pluralize'][$word] = (string)preg_replace($rule, $replacement, $word); return static::$_cache['pluralize'][$word]; } @@ -315,7 +315,7 @@ public static function pluralize(string $word): string * * @param string $word Word in plural * @return string Word in singular - * @link https://book.cakephp.org/4/en/core-libraries/inflector.html#creating-plural-singular-forms + * @link https://book.cakephp.org/5/en/core-libraries/inflector.html#creating-plural-singular-forms */ public static function singularize(string $word): string { @@ -356,7 +356,7 @@ public static function singularize(string $word): string foreach (static::$_singular as $rule => $replacement) { if (preg_match($rule, $word)) { - static::$_cache['singularize'][$word] = preg_replace($rule, $replacement, $word); + static::$_cache['singularize'][$word] = (string)preg_replace($rule, $replacement, $word); return static::$_cache['singularize'][$word]; } @@ -372,7 +372,7 @@ public static function singularize(string $word): string * @param string $string String to camelize * @param string $delimiter the delimiter in the input string * @return string CamelizedStringLikeThis. - * @link https://book.cakephp.org/4/en/core-libraries/inflector.html#creating-camelcase-and-under-scored-forms + * @link https://book.cakephp.org/5/en/core-libraries/inflector.html#creating-camelcase-and-under-scored-forms */ public static function camelize(string $string, string $delimiter = '_'): string { @@ -395,7 +395,7 @@ public static function camelize(string $string, string $delimiter = '_'): string * * @param string $string CamelCasedString to be "underscorized" * @return string underscore_version of the input string - * @link https://book.cakephp.org/4/en/core-libraries/inflector.html#creating-camelcase-and-under-scored-forms + * @link https://book.cakephp.org/5/en/core-libraries/inflector.html#creating-camelcase-and-under-scored-forms */ public static function underscore(string $string): string { @@ -422,7 +422,7 @@ public static function dasherize(string $string): string * @param string $string String to be humanized * @param string $delimiter the character to replace with a space * @return string Human-readable string - * @link https://book.cakephp.org/4/en/core-libraries/inflector.html#creating-human-readable-forms + * @link https://book.cakephp.org/5/en/core-libraries/inflector.html#creating-human-readable-forms */ public static function humanize(string $string, string $delimiter = '_'): string { @@ -456,7 +456,7 @@ public static function delimit(string $string, string $delimiter = '_'): string $result = static::_cache($cacheKey, $string); if ($result === false) { - $result = mb_strtolower(preg_replace('/(?<=\\w)([A-Z])/', $delimiter . '\\1', $string)); + $result = mb_strtolower((string)preg_replace('/(?<=\\w)([A-Z])/', $delimiter . '\\1', $string)); static::_cache($cacheKey, $string, $result); } @@ -468,7 +468,7 @@ public static function delimit(string $string, string $delimiter = '_'): string * * @param string $className Name of class to get database table name for * @return string Name of the database table for given class - * @link https://book.cakephp.org/4/en/core-libraries/inflector.html#creating-table-and-class-name-forms + * @link https://book.cakephp.org/5/en/core-libraries/inflector.html#creating-table-and-class-name-forms */ public static function tableize(string $className): string { @@ -487,7 +487,7 @@ public static function tableize(string $className): string * * @param string $tableName Name of database table to get class name for * @return string Class name - * @link https://book.cakephp.org/4/en/core-libraries/inflector.html#creating-table-and-class-name-forms + * @link https://book.cakephp.org/5/en/core-libraries/inflector.html#creating-table-and-class-name-forms */ public static function classify(string $tableName): string { @@ -506,7 +506,7 @@ public static function classify(string $tableName): string * * @param string $string String to convert. * @return string in variable form - * @link https://book.cakephp.org/4/en/core-libraries/inflector.html#creating-variable-names + * @link https://book.cakephp.org/5/en/core-libraries/inflector.html#creating-variable-names */ public static function variable(string $string): string { diff --git a/app/vendor/cakephp/cakephp/src/Utility/MergeVariablesTrait.php b/app/vendor/cakephp/cakephp/src/Utility/MergeVariablesTrait.php index 39bafb886..814154edc 100644 --- a/app/vendor/cakephp/cakephp/src/Utility/MergeVariablesTrait.php +++ b/app/vendor/cakephp/cakephp/src/Utility/MergeVariablesTrait.php @@ -101,7 +101,7 @@ protected function _mergeProperty(string $property, array $parentClasses, array * @param bool $isAssoc Whether the merging should be done in associative mode. * @return array The updated value. */ - protected function _mergePropertyData(array $current, array $parent, bool $isAssoc) + protected function _mergePropertyData(array $current, array $parent, bool $isAssoc): array { if (!$isAssoc) { return array_merge($parent, $current); diff --git a/app/vendor/cakephp/cakephp/src/Utility/README.md b/app/vendor/cakephp/cakephp/src/Utility/README.md index 45601b856..a17597606 100644 --- a/app/vendor/cakephp/cakephp/src/Utility/README.md +++ b/app/vendor/cakephp/cakephp/src/Utility/README.md @@ -23,7 +23,7 @@ $bigPeople = Hash::extract($things, '{n}[age>21].name'); // $bigPeople will contain ['Susan', 'Lucy'] ``` -Check the [official Hash class documentation](https://book.cakephp.org/4/en/core-libraries/hash.html) +Check the [official Hash class documentation](https://book.cakephp.org/5/en/core-libraries/hash.html) ### Inflector @@ -36,7 +36,7 @@ echo Inflector::pluralize('Apple'); // echoes Apples echo Inflector::singularize('People'); // echoes Person ``` -Check the [official Inflector class documentation](https://book.cakephp.org/4/en/core-libraries/inflector.html) +Check the [official Inflector class documentation](https://book.cakephp.org/5/en/core-libraries/inflector.html) ### Text @@ -57,7 +57,7 @@ This is the song that never ends. ``` -Check the [official Text class documentation](https://book.cakephp.org/4/en/core-libraries/text.html) +Check the [official Text class documentation](https://book.cakephp.org/5/en/core-libraries/text.html) ### Security @@ -70,7 +70,7 @@ $result = Security::encrypt($value, $key); Security::decrypt($result, $key); ``` -Check the [official Security class documentation](https://book.cakephp.org/4/en/core-libraries/security.html) +Check the [official Security class documentation](https://book.cakephp.org/5/en/core-libraries/security.html) ### Xml @@ -88,4 +88,4 @@ $data = [ $xml = Xml::build($data); ``` -Check the [official Xml class documentation](https://book.cakephp.org/4/en/core-libraries/xml.html) +Check the [official Xml class documentation](https://book.cakephp.org/5/en/core-libraries/xml.html) diff --git a/app/vendor/cakephp/cakephp/src/Utility/Security.php b/app/vendor/cakephp/cakephp/src/Utility/Security.php index a46d3222b..1795ab46e 100644 --- a/app/vendor/cakephp/cakephp/src/Utility/Security.php +++ b/app/vendor/cakephp/cakephp/src/Utility/Security.php @@ -16,9 +16,9 @@ */ namespace Cake\Utility; +use Cake\Core\Exception\CakeException; use Cake\Utility\Crypto\OpenSsl; use InvalidArgumentException; -use RuntimeException; /** * Security Library contains utility methods related to security @@ -31,21 +31,21 @@ class Security * * @var string */ - public static $hashType = 'sha1'; + public static string $hashType = 'sha1'; /** * The HMAC salt to use for encryption and decryption routines * * @var string|null */ - protected static $_salt; + protected static ?string $_salt = null; /** * The crypto implementation to use. * * @var object|null */ - protected static $_instance; + protected static ?object $_instance = null; /** * Create a hash from string using given method. @@ -54,25 +54,25 @@ class Security * @param string|null $algorithm Hashing algo to use (i.e. sha1, sha256 etc.). * Can be any valid algo included in list returned by hash_algos(). * If no value is passed the type specified by `Security::$hashType` is used. - * @param mixed $salt If true, automatically prepends the value returned by + * @param string|bool $salt If true, automatically prepends the value returned by * Security::getSalt() to $string. * @return string Hash - * @throws \RuntimeException - * @link https://book.cakephp.org/4/en/core-libraries/security.html#hashing-data + * @throws \InvalidArgumentException + * @link https://book.cakephp.org/5/en/core-libraries/security.html#hashing-data */ - public static function hash(string $string, ?string $algorithm = null, $salt = false): string + public static function hash(string $string, ?string $algorithm = null, string|bool $salt = false): string { - if (empty($algorithm)) { + if (!$algorithm) { $algorithm = static::$hashType; } $algorithm = strtolower($algorithm); $availableAlgorithms = hash_algos(); if (!in_array($algorithm, $availableAlgorithms, true)) { - throw new RuntimeException(sprintf( - 'The hash type `%s` was not found. Available algorithms are: %s', + throw new InvalidArgumentException(sprintf( + 'The hash type `%s` was not found. Available algorithms are: `%s`.', $algorithm, - implode(', ', $availableAlgorithms) + implode(', ', $availableAlgorithms), )); } @@ -102,7 +102,7 @@ public static function setHash(string $hash): void /** * Get random bytes from a secure source. * - * This method will fall back to an insecure source an trigger a warning + * This method will fall back to an insecure source and trigger a warning * if it cannot find a secure source of random data. * * @param int $length The number of bytes you want. @@ -110,7 +110,10 @@ public static function setHash(string $hash): void */ public static function randomBytes(int $length): string { - /** @psalm-suppress ArgumentTypeCoercion */ + if ($length < 1) { + throw new InvalidArgumentException('Length must be `int<1, max>`'); + } + return random_bytes($length); } @@ -125,7 +128,7 @@ public static function randomString(int $length = 64): string return substr( bin2hex(Security::randomBytes((int)ceil($length / 2))), 0, - $length + $length, ); } @@ -159,15 +162,14 @@ public static function insecureRandomBytes(int $length): string * @param \Cake\Utility\Crypto\OpenSsl|null $instance The crypto instance to use. * @return \Cake\Utility\Crypto\OpenSsl Crypto instance. * @throws \InvalidArgumentException When no compatible crypto extension is available. - * @psalm-suppress MoreSpecificReturnType */ - public static function engine($instance = null) + public static function engine(?object $instance = null): object { if ($instance) { return static::$_instance = $instance; } if (isset(static::$_instance)) { - /** @psalm-suppress LessSpecificReturnStatement */ + /** @var \Cake\Utility\Crypto\OpenSsl */ return static::$_instance; } if (extension_loaded('openssl')) { @@ -175,7 +177,7 @@ public static function engine($instance = null) } throw new InvalidArgumentException( 'No compatible crypto engine available. ' . - 'Load the openssl extension.' + 'Load the openssl extension.', ); } @@ -197,9 +199,7 @@ public static function encrypt(string $plain, string $key, ?string $hmacSalt = n { self::_checkKey($key, 'encrypt()'); - if ($hmacSalt === null) { - $hmacSalt = static::getSalt(); - } + $hmacSalt ??= static::getSalt(); // Generate the encryption and hmac key. $key = mb_substr(hash('sha256', $key . $hmacSalt), 0, 32, '8bit'); @@ -222,7 +222,7 @@ protected static function _checkKey(string $key, string $method): void { if (mb_strlen($key, '8bit') < 32) { throw new InvalidArgumentException( - sprintf('Invalid key for %s, key must be at least 256 bits (32 bytes) long.', $method) + sprintf('Invalid key for %s, key must be at least 256 bits (32 bytes) long.', $method), ); } } @@ -240,12 +240,10 @@ protected static function _checkKey(string $key, string $method): void public static function decrypt(string $cipher, string $key, ?string $hmacSalt = null): ?string { self::_checkKey($key, 'decrypt()'); - if (empty($cipher)) { + if (!$cipher) { throw new InvalidArgumentException('The data to decrypt cannot be empty.'); } - if ($hmacSalt === null) { - $hmacSalt = static::getSalt(); - } + $hmacSalt ??= static::getSalt(); // Generate the encryption and hmac key. $key = mb_substr(hash('sha256', $key . $hmacSalt), 0, 32, '8bit'); @@ -273,7 +271,7 @@ public static function decrypt(string $cipher, string $key, ?string $hmacSalt = * @return bool * @since 3.6.2 */ - public static function constantEquals($original, $compare): bool + public static function constantEquals(mixed $original, mixed $compare): bool { return is_string($original) && is_string($compare) && hash_equals($original, $compare); } @@ -287,8 +285,8 @@ public static function constantEquals($original, $compare): bool public static function getSalt(): string { if (static::$_salt === null) { - throw new RuntimeException( - 'Salt not set. Use Security::setSalt() to set one, ideally in `config/bootstrap.php`.' + throw new CakeException( + 'Salt not set. Use Security::setSalt() to set one, ideally in `config/bootstrap.php`.', ); } diff --git a/app/vendor/cakephp/cakephp/src/Utility/Text.php b/app/vendor/cakephp/cakephp/src/Utility/Text.php index 7116b0dec..a37189112 100644 --- a/app/vendor/cakephp/cakephp/src/Utility/Text.php +++ b/app/vendor/cakephp/cakephp/src/Utility/Text.php @@ -19,7 +19,6 @@ use Cake\Core\Exception\CakeException; use InvalidArgumentException; use Transliterator; -use function Cake\Core\deprecationWarning; use function Cake\I18n\__d; /** @@ -32,25 +31,32 @@ class Text * * @var \Transliterator|null Transliterator instance. */ - protected static $_defaultTransliterator; + protected static ?Transliterator $_defaultTransliterator = null; /** * Default transliterator id string. * - * @var string $_defaultTransliteratorId Transliterator identifier string. + * @var string Transliterator identifier string. */ - protected static $_defaultTransliteratorId = 'Any-Latin; Latin-ASCII; [\u0080-\u7fff] remove'; + protected static string $_defaultTransliteratorId = 'Any-Latin; Latin-ASCII; [\u0080-\u7fff] remove'; /** * Default HTML tags which must not be counted for truncating text. * * @var array */ - protected static $_defaultHtmlNoCount = [ + protected static array $_defaultHtmlNoCount = [ 'style', 'script', ]; + /** + * Whether to use I18n functions for translating default error messages + * + * @var bool + */ + protected static bool $useI18n; + /** * Generate a random UUID version 4 * @@ -82,7 +88,7 @@ public static function uuid(): string // 48 bits for "node" random_int(0, 65535), random_int(0, 65535), - random_int(0, 65535) + random_int(0, 65535), ); } @@ -100,9 +106,9 @@ public static function tokenize( string $data, string $separator = ',', string $leftBound = '(', - string $rightBound = ')' + string $rightBound = ')', ): array { - if (empty($data)) { + if (!$data) { return []; } @@ -141,15 +147,13 @@ public static function tokenize( if ($char === $rightBound) { $depth--; } - } else { - if ($char === $leftBound) { - if (!$open) { - $depth++; - $open = true; - } else { - $depth--; - $open = false; - } + } elseif ($char === $leftBound) { + if (!$open) { + $depth++; + $open = true; + } else { + $depth--; + $open = false; } } $tmpOffset += 1; @@ -159,11 +163,11 @@ public static function tokenize( $offset = $length + 1; } } - if (empty($results) && !empty($buffer)) { + if (!$results && $buffer) { $results[] = $buffer; } - if (!empty($results)) { + if ($results) { return array_map('trim', $results); } @@ -196,59 +200,41 @@ public static function tokenize( */ public static function insert(string $str, array $data, array $options = []): string { - $defaults = [ - 'before' => ':', 'after' => '', 'escape' => '\\', 'format' => null, 'clean' => false, - ]; - $options += $defaults; - if (empty($data)) { - return $options['clean'] ? static::cleanInsert($str, $options) : $str; - } - - if (strpos($str, '?') !== false && is_numeric(key($data))) { - deprecationWarning( - 'Using Text::insert() with `?` placeholders is deprecated. ' . - 'Use sprintf() with `%s` placeholders instead.' - ); - - $offset = 0; - while (($pos = strpos($str, '?', $offset)) !== false) { - $val = array_shift($data); - $offset = $pos + strlen($val); - $str = substr_replace($str, $val, $pos, 1); - } - + $options += ['before' => ':', 'after' => '', 'escape' => '\\', 'format' => null, 'clean' => false]; + if (!$data) { return $options['clean'] ? static::cleanInsert($str, $options) : $str; } $format = $options['format']; - if ($format === null) { - $format = sprintf( - '/(? hash('xxh128', $str), + $dataKeys, + ); /** @var array $tempData */ $tempData = array_combine($dataKeys, $hashKeys); krsort($tempData); foreach ($tempData as $key => $hashVal) { $key = sprintf($format, preg_quote($key, '/')); - $str = preg_replace($key, $hashVal, $str); + $str = (string)preg_replace($key, $hashVal, $str); } /** @var array $dataReplacements */ $dataReplacements = array_combine($hashKeys, array_values($data)); foreach ($dataReplacements as $tmpHash => $tmpValue) { $tmpValue = is_array($tmpValue) ? '' : (string)$tmpValue; - $str = str_replace($tmpHash, $tmpValue, $str); + $str = (string)str_replace($tmpHash, $tmpValue, $str); } - if (!isset($options['format']) && isset($options['before'])) { - $str = str_replace($options['escape'] . $options['before'], $options['before'], $str); + if ($options['format'] === null && $options['before'] !== null) { + $str = (string)str_replace($options['escape'] . $options['before'], $options['before'], $str); } return $options['clean'] ? static::cleanInsert($str, $options) : $str; @@ -288,9 +274,9 @@ public static function cleanInsert(string $str, array $options): string '/[\s]*[a-z]+=(")(%s%s%s[\s]*)+\\1/i', preg_quote($options['before'], '/'), $clean['word'], - preg_quote($options['after'], '/') + preg_quote($options['after'], '/'), ); - $str = preg_replace($kleenex, $clean['replacement'], $str); + $str = (string)preg_replace($kleenex, $clean['replacement'], $str); if ($clean['andText']) { $options['clean'] = ['method' => 'text']; $str = static::cleanInsert($str, $options); @@ -312,9 +298,9 @@ public static function cleanInsert(string $str, array $options): string $clean['gap'], preg_quote($options['before'], '/'), $clean['word'], - preg_quote($options['after'], '/') + preg_quote($options['after'], '/'), ); - $str = preg_replace($kleenex, $clean['replacement'], $str); + $str = (string)preg_replace($kleenex, $clean['replacement'], $str); break; } @@ -335,18 +321,22 @@ public static function cleanInsert(string $str, array $options): string * @param array|int $options Array of options to use, or an integer to wrap the text to. * @return string Formatted text. */ - public static function wrap(string $text, $options = []): string + public static function wrap(string $text, array|int $options = []): string { - if (is_numeric($options)) { + if (is_int($options)) { $options = ['width' => $options]; } $options += ['width' => 72, 'wordWrap' => true, 'indent' => null, 'indentAt' => 0]; if ($options['wordWrap']) { $wrapped = self::wordWrap($text, $options['width'], "\n"); } else { - $wrapped = trim(chunk_split($text, $options['width'] - 1, "\n")); + $length = $options['width'] - 1; + if ($length < 1) { + throw new InvalidArgumentException('Length must be `int<1, max>`.'); + } + $wrapped = trim(chunk_split($text, $length, "\n")); } - if (!empty($options['indent'])) { + if ($options['indent']) { $chunks = explode("\n", $wrapped); for ($i = $options['indentAt'], $len = count($chunks); $i < $len; $i++) { $chunks[$i] = $options['indent'] . $chunks[$i]; @@ -372,16 +362,16 @@ public static function wrap(string $text, $options = []): string * @param array|int $options Array of options to use, or an integer to wrap the text to. * @return string Formatted text. */ - public static function wrapBlock(string $text, $options = []): string + public static function wrapBlock(string $text, array|int $options = []): string { - if (is_numeric($options)) { + if (is_int($options)) { $options = ['width' => $options]; } $options += ['width' => 72, 'wordWrap' => true, 'indent' => null, 'indentAt' => 0]; $wrapped = self::wrap($text, $options); - if (!empty($options['indent'])) { + if ($options['indent']) { $indentationLength = mb_strlen($options['indent']); $chunks = explode("\n", $wrapped); $count = count($chunks); @@ -490,21 +480,20 @@ protected static function _wordWrap(string $text, int $width = 72, string $break * @param array|string $phrase The phrase or phrases that will be searched. * @param array $options An array of HTML attributes and options. * @return string The highlighted text - * @link https://book.cakephp.org/4/en/core-libraries/text.html#highlighting-substrings + * @link https://book.cakephp.org/5/en/core-libraries/text.html#highlighting-substrings */ - public static function highlight(string $text, $phrase, array $options = []): string + public static function highlight(string $text, array|string $phrase, array $options = []): string { - if (empty($phrase)) { + if (!$phrase) { return $text; } - $defaults = [ + $options += [ 'format' => '\1', 'html' => false, 'regex' => '|%s|iu', 'limit' => -1, ]; - $options += $defaults; if (is_array($phrase)) { $replace = []; @@ -513,26 +502,26 @@ public static function highlight(string $text, $phrase, array $options = []): st foreach ($phrase as $key => $segment) { $segment = '(' . preg_quote($segment, '|') . ')'; if ($options['html']) { - $segment = "(?![^<]+>)$segment(?![^<]+>)"; + $segment = "(?![^<]+>){$segment}(?![^<]+>)"; } $with[] = is_array($options['format']) ? $options['format'][$key] : $options['format']; $replace[] = sprintf($options['regex'], $segment); } - return preg_replace($replace, $with, $text, $options['limit']); + return (string)preg_replace($replace, $with, $text, $options['limit']); } $phrase = '(' . preg_quote($phrase, '|') . ')'; if ($options['html']) { - $phrase = "(?![^<]+>)$phrase(?![^<]+>)"; + $phrase = "(?![^<]+>){$phrase}(?![^<]+>)"; } - return preg_replace( + return (string)preg_replace( sprintf($options['regex'], $phrase), $options['format'], $text, - $options['limit'] + $options['limit'], ); } @@ -554,10 +543,7 @@ public static function highlight(string $text, $phrase, array $options = []): st */ public static function tail(string $text, int $length = 100, array $options = []): string { - $default = [ - 'ellipsis' => '...', 'exact' => true, - ]; - $options += $default; + $options += ['ellipsis' => '…', 'exact' => true]; $ellipsis = $options['ellipsis']; if (mb_strlen($text) <= $length) { @@ -590,14 +576,14 @@ public static function tail(string $text, int $length = 100, array $options = [] * @param int $length Length of returned string, including ellipsis. * @param array $options An array of HTML attributes and options. * @return string Trimmed string. - * @link https://book.cakephp.org/4/en/core-libraries/text.html#truncating-text + * @link https://book.cakephp.org/5/en/core-libraries/text.html#truncating-text */ public static function truncate(string $text, int $length = 100, array $options = []): string { $default = [ - 'ellipsis' => '...', 'exact' => true, 'html' => false, 'trimWidth' => false, + 'ellipsis' => '…', 'exact' => true, 'html' => false, 'trimWidth' => false, ]; - if (!empty($options['html']) && strtolower((string)mb_internal_encoding()) === 'utf-8') { + if (!empty($options['html']) && strtolower(mb_internal_encoding()) === 'utf-8') { $default['ellipsis'] = "\xe2\x80\xa6"; } $options += $default; @@ -624,7 +610,7 @@ public static function truncate(string $text, int $length = 100, array $options if ( !preg_match( '/img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param/i', - $tag[2] + $tag[2], ) ) { if (preg_match('/<[\w]+[^>]*>/', $tag[0])) { @@ -725,14 +711,14 @@ protected static function _strlen(string $text, array $options): int } $pattern = '/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i'; - $replace = preg_replace_callback( + $replace = (string)preg_replace_callback( $pattern, function ($match) use ($strlen) { $utf8 = html_entity_decode($match[0], ENT_HTML5 | ENT_QUOTES, 'UTF-8'); return str_repeat(' ', $strlen($utf8, 'UTF-8')); }, - $text + $text, ); return $strlen($replace); @@ -771,9 +757,7 @@ protected static function _substr(string $text, int $start, ?int $length, array return ''; } - if ($length === null) { - $length = self::_strlen($text, $options); - } + $length ??= self::_strlen($text, $options); if ($length < 0) { $text = self::_substr($text, $start, null, $options); @@ -794,7 +778,7 @@ protected static function _substr(string $text, int $start, ?int $length, array $result = ''; $pattern = '/(&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};)/i'; - $parts = preg_split($pattern, $text, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); + $parts = preg_split($pattern, $text, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY) ?: []; foreach ($parts as $part) { $offset = 0; @@ -812,7 +796,7 @@ protected static function _substr(string $text, int $start, ?int $length, array $len = self::_strlen($part, $options); if ($offset !== 0 || $totalLength + $len > $length) { if ( - strpos($part, '&') === 0 + str_starts_with($part, '&') && preg_match($pattern, $part) && $part !== html_entity_decode($part, ENT_HTML5 | ENT_QUOTES, 'UTF-8') ) { @@ -850,7 +834,7 @@ protected static function _removeLastWord(string $text): string // Some languages are written without word separation. // We recognize a string as a word if it doesn't contain any full-width characters. if (mb_strwidth($lastWord) === mb_strlen($lastWord)) { - $text = mb_substr($text, 0, $spacepos); + return mb_substr($text, 0, $spacepos); } return $text; @@ -868,15 +852,15 @@ protected static function _removeLastWord(string $text): string * @param int $radius The amount of characters that will be returned on each side of the founded phrase * @param string $ellipsis Ending that will be appended * @return string Modified string - * @link https://book.cakephp.org/4/en/core-libraries/text.html#extracting-an-excerpt + * @link https://book.cakephp.org/5/en/core-libraries/text.html#extracting-an-excerpt */ - public static function excerpt(string $text, string $phrase, int $radius = 100, string $ellipsis = '...'): string + public static function excerpt(string $text, string $phrase, int $radius = 100, string $ellipsis = '…'): string { - if (empty($text) || empty($phrase)) { + if ($text === '' || $phrase === '') { return static::truncate($text, $radius * 2, ['ellipsis' => $ellipsis]); } - - $append = $prepend = $ellipsis; + $append = $ellipsis; + $prepend = $ellipsis; $phraseLen = mb_strlen($phrase); $textLen = mb_strlen($text); @@ -910,13 +894,13 @@ public static function excerpt(string $text, string $phrase, int $radius = 100, * @param string|null $and The word used to join the last and second last items together with. Defaults to 'and'. * @param string $separator The separator used to join all the other items together. Defaults to ', '. * @return string The glued together string. - * @link https://book.cakephp.org/4/en/core-libraries/text.html#converting-an-array-to-sentence-form + * @link https://book.cakephp.org/5/en/core-libraries/text.html#converting-an-array-to-sentence-form */ public static function toList(array $list, ?string $and = null, string $separator = ', '): string { - if ($and === null) { - $and = __d('cake', 'and'); - } + static::$useI18n ??= function_exists('Cake\I18n\__d'); + $and ??= static::$useI18n ? __d('cake', 'and') : 'and'; + if (count($list) > 1) { return implode($separator, array_slice($list, 0, -1)) . ' ' . $and . ' ' . array_pop($list); } @@ -965,7 +949,7 @@ public static function utf8(string $string): array if ($value < 128) { $map[] = $value; } else { - if (empty($values)) { + if (!$values) { $find = $value < 224 ? 2 : 3; } $values[] = $value; @@ -989,7 +973,7 @@ public static function utf8(string $string): array * Converts the decimal value of a multibyte character string * to a string * - * @param array $array Array + * @param array $array Array * @return string */ public static function ascii(array $array): string @@ -1000,11 +984,11 @@ public static function ascii(array $array): string if ($utf8 < 128) { $ascii .= chr($utf8); } elseif ($utf8 < 2048) { - $ascii .= chr(192 + (($utf8 - ($utf8 % 64)) / 64)); + $ascii .= chr(192 + (int)(($utf8 - ($utf8 % 64)) / 64)); $ascii .= chr(128 + ($utf8 % 64)); } else { - $ascii .= chr(224 + (($utf8 - ($utf8 % 4096)) / 4096)); - $ascii .= chr(128 + ((($utf8 % 4096) - ($utf8 % 64)) / 64)); + $ascii .= chr(224 + (int)(($utf8 - ($utf8 % 4096)) / 4096)); + $ascii .= chr(128 + (int)((($utf8 % 4096) - ($utf8 % 64)) / 64)); $ascii .= chr(128 + ($utf8 % 64)); } } @@ -1019,9 +1003,9 @@ public static function ascii(array $array): string * @param mixed $default Value to be returned when invalid size was used, for example 'Unknown type' * @return mixed Number of bytes as integer on success, `$default` on failure if not false * @throws \InvalidArgumentException On invalid Unit type. - * @link https://book.cakephp.org/4/en/core-libraries/text.html#Cake\Utility\Text::parseFileSize + * @link https://book.cakephp.org/5/en/core-libraries/text.html#Cake\Utility\Text::parseFileSize */ - public static function parseFileSize(string $size, $default = false) + public static function parseFileSize(string $size, mixed $default = false): mixed { if (ctype_digit($size)) { return (int)$size; @@ -1040,7 +1024,7 @@ public static function parseFileSize(string $size, $default = false) return (int)($size * pow(1024, $i + 1)); } - if (substr($size, -1) === 'B' && ctype_digit(substr($size, 0, -1))) { + if (str_ends_with($size, 'B') && ctype_digit(substr($size, 0, -1))) { $size = substr($size, 0, -1); return (int)$size; @@ -1094,7 +1078,7 @@ public static function setTransliteratorId(string $transliteratorId): void { $transliterator = transliterator_create($transliteratorId); if ($transliterator === null) { - throw new CakeException('Unable to create transliterator for id: ' . $transliteratorId); + throw new CakeException(sprintf('Unable to create transliterator for id: %s.', $transliteratorId)); } static::setTransliterator($transliterator); @@ -1112,9 +1096,9 @@ public static function setTransliteratorId(string $transliteratorId): void * @return string * @see https://secure.php.net/manual/en/transliterator.transliterate.php */ - public static function transliterate(string $string, $transliterator = null): string + public static function transliterate(string $string, Transliterator|string|null $transliterator = null): string { - if (empty($transliterator)) { + if (!$transliterator) { $transliterator = static::$_defaultTransliterator ?: static::$_defaultTransliteratorId; } @@ -1147,7 +1131,7 @@ public static function transliterate(string $string, $transliterator = null): st * @see setTransliterator() * @see setTransliteratorId() */ - public static function slug(string $string, $options = []): string + public static function slug(string $string, array|string $options = []): string { if (is_string($options)) { $options = ['replacement' => $options]; @@ -1175,6 +1159,6 @@ public static function slug(string $string, $options = []): string $map[sprintf('/[%s]+/mu', $quotedReplacement)] = $options['replacement']; } - return preg_replace(array_keys($map), $map, $string); + return (string)preg_replace(array_keys($map), $map, $string); } } diff --git a/app/vendor/cakephp/cakephp/src/Utility/Xml.php b/app/vendor/cakephp/cakephp/src/Utility/Xml.php index 53ddfbda7..c49c4d477 100644 --- a/app/vendor/cakephp/cakephp/src/Utility/Xml.php +++ b/app/vendor/cakephp/cakephp/src/Utility/Xml.php @@ -16,13 +16,17 @@ */ namespace Cake\Utility; +use BackedEnum; +use Cake\Core\Exception\CakeException; use Cake\Utility\Exception\XmlException; use Closure; use DOMDocument; +use DOMElement; use DOMNode; use DOMText; use Exception; use SimpleXMLElement; +use UnitEnum; /** * XML handling for CakePHP. @@ -102,7 +106,7 @@ class Xml * @return \SimpleXMLElement|\DOMDocument SimpleXMLElement or DOMDocument * @throws \Cake\Utility\Exception\XmlException */ - public static function build($input, array $options = []) + public static function build(object|array|string $input, array $options = []): SimpleXMLElement|DOMDocument { $defaults = [ 'return' => 'simplexml', @@ -117,15 +121,15 @@ public static function build($input, array $options = []) } if ($options['readFile'] && file_exists($input)) { - return static::_loadXml(file_get_contents($input), $options); - } + $content = file_get_contents($input); + if ($content === false) { + throw new CakeException(sprintf('Cannot read file content of `%s`', $input)); + } - if (!is_string($input)) { - $type = gettype($input); - throw new XmlException("Invalid input. {$type} cannot be parsed as XML."); + return static::_loadXml($content, $options); } - if (strpos($input, '<') !== false) { + if (str_contains($input, '<')) { return static::_loadXml($input, $options); } @@ -140,7 +144,7 @@ public static function build($input, array $options = []) * @return \SimpleXMLElement|\DOMDocument * @throws \Cake\Utility\Exception\XmlException */ - protected static function _loadXml(string $input, array $options) + protected static function _loadXml(string $input, array $options): SimpleXMLElement|DOMDocument { return static::load( $input, @@ -155,7 +159,7 @@ function ($input, $options, $flags) { } return $xml; - } + }, ); } @@ -167,7 +171,7 @@ function ($input, $options, $flags) { * @return \SimpleXMLElement|\DOMDocument * @throws \Cake\Utility\Exception\XmlException */ - public static function loadHtml(string $input, array $options = []) + public static function loadHtml(string $input, array $options = []): SimpleXMLElement|DOMDocument { $defaults = [ 'return' => 'simplexml', @@ -183,11 +187,11 @@ function ($input, $options, $flags) { $xml->loadHTML($input, $flags); if ($options['return'] === 'simplexml' || $options['return'] === 'simplexmlelement') { - $xml = simplexml_import_dom($xml); + return simplexml_import_dom($xml); } return $xml; - } + }, ); } @@ -200,7 +204,7 @@ function ($input, $options, $flags) { * @return \SimpleXMLElement|\DOMDocument * @throws \Cake\Utility\Exception\XmlException */ - protected static function load(string $input, array $options, Closure $callable) + protected static function load(string $input, array $options, Closure $callable): SimpleXMLElement|DOMDocument { $flags = 0; if (!empty($options['parseHuge'])) { @@ -208,9 +212,7 @@ protected static function load(string $input, array $options, Closure $callable) } $internalErrors = libxml_use_internal_errors(true); - if (LIBXML_VERSION < 20900 && !$options['loadEntities']) { - $previousDisabledEntityLoader = libxml_disable_entity_loader(true); - } elseif ($options['loadEntities']) { + if ($options['loadEntities']) { $flags |= LIBXML_NOENT; } @@ -219,9 +221,6 @@ protected static function load(string $input, array $options, Closure $callable) } catch (Exception $e) { throw new XmlException('Xml cannot be read. ' . $e->getMessage(), null, $e); } finally { - if (isset($previousDisabledEntityLoader)) { - libxml_disable_entity_loader($previousDisabledEntityLoader); - } libxml_use_internal_errors($internalErrors); } } @@ -266,13 +265,16 @@ protected static function load(string $input, array $options, Closure $callable) * @return \SimpleXMLElement|\DOMDocument SimpleXMLElement or DOMDocument * @throws \Cake\Utility\Exception\XmlException */ - public static function fromArray($input, array $options = []) + public static function fromArray(object|array $input, array $options = []): SimpleXMLElement|DOMDocument { if (is_object($input) && method_exists($input, 'toArray') && is_callable([$input, 'toArray'])) { $input = $input->toArray(); } if (!is_array($input) || count($input) !== 1) { - throw new XmlException('Invalid input.'); + throw new XmlException( + 'Invalid input of type `' . gettype($input) . '`' + . (is_array($input) ? ' (Count of ' . count($input) . ')' : '') . '.', + ); } $key = key($input); if (is_int($key)) { @@ -296,7 +298,28 @@ public static function fromArray($input, array $options = []) $options['return'] = strtolower($options['return']); if ($options['return'] === 'simplexml' || $options['return'] === 'simplexmlelement') { - return new SimpleXMLElement($dom->saveXML()); + $xmlString = (string)$dom->saveXML(); + $check = new DOMDocument(); + libxml_use_internal_errors(true); + + if (!$check->loadXML($xmlString, LIBXML_NOWARNING | LIBXML_NOERROR)) { + $errors = libxml_get_errors(); + $messages = []; + + foreach ($errors as $error) { + $messages[] = trim(sprintf( + 'File: %s, Line %d, Column %d: %s', + $error->file ?: '[string input]', + $error->line, + $error->column, + $error->message, + )); + } + libxml_clear_errors(); + throw new XmlException("Invalid XML string:\n" . implode("\n", $messages)); + } + + return new SimpleXMLElement($xmlString); } return $dom; @@ -307,14 +330,18 @@ public static function fromArray($input, array $options = []) * * @param \DOMDocument $dom Handler to DOMDocument * @param \DOMDocument|\DOMElement $node Handler to DOMElement (child) - * @param array $data Array of data to append to the $node. + * @param mixed $data Array of data to append to the $node. * @param string $format Either 'attributes' or 'tags'. This determines where nested keys go. * @return void * @throws \Cake\Utility\Exception\XmlException */ - protected static function _fromArray(DOMDocument $dom, $node, &$data, $format): void - { - if (empty($data) || !is_array($data)) { + protected static function _fromArray( + DOMDocument $dom, + DOMDocument|DOMElement $node, + mixed $data, + string $format, + ): void { + if (!$data || !is_array($data)) { return; } foreach ($data as $key => $value) { @@ -329,25 +356,31 @@ protected static function _fromArray(DOMDocument $dom, $node, &$data, $format): } elseif ($value === null) { $value = ''; } - $isNamespace = strpos($key, 'xmlns:'); - if ($isNamespace !== false) { - /** @psalm-suppress PossiblyUndefinedMethod */ + if (str_contains($key, 'xmlns:')) { + assert($node instanceof DOMElement); $node->setAttributeNS('http://www.w3.org/2000/xmlns/', $key, (string)$value); continue; } - if ($key[0] !== '@' && $format === 'tags') { + if (!str_starts_with($key, '@') && $format === 'tags') { if (!is_numeric($value)) { // Escape special characters // https://www.w3.org/TR/REC-xml/#syntax // https://bugs.php.net/bug.php?id=36795 $child = $dom->createElement($key, ''); - $child->appendChild(new DOMText((string)$value)); + if ($value instanceof BackedEnum) { + $value = (string)$value->value; + } elseif ($value instanceof UnitEnum) { + $value = $value->name; + } else { + $value = (string)$value; + } + $child->appendChild(new DOMText($value)); } else { $child = $dom->createElement($key, (string)$value); } $node->appendChild($child); } else { - if ($key[0] === '@') { + if (str_starts_with($key, '@')) { $key = substr($key, 1); } $attribute = $dom->createAttribute($key); @@ -355,7 +388,7 @@ protected static function _fromArray(DOMDocument $dom, $node, &$data, $format): $node->appendChild($attribute); } } else { - if ($key[0] === '@') { + if (str_starts_with($key, '@')) { throw new XmlException('Invalid array'); } if (is_numeric(implode('', array_keys($value)))) { @@ -381,24 +414,21 @@ protected static function _fromArray(DOMDocument $dom, $node, &$data, $format): * * @param array $data Array with information to create children * @return void + * @phpstan-param array{dom: \DOMDocument, node: \DOMNode, key: string, format: string, value?: mixed} $data */ protected static function _createChild(array $data): void { $data += [ - 'dom' => null, - 'node' => null, - 'key' => null, 'value' => null, - 'format' => null, ]; - $value = $data['value']; - $dom = $data['dom']; $key = $data['key']; $format = $data['format']; + $value = $data['value']; + $dom = $data['dom']; $node = $data['node']; - - $childNS = $childValue = null; + $childNS = null; + $childValue = null; if (is_object($value) && method_exists($value, 'toArray') && is_callable([$value, 'toArray'])) { $value = $value->toArray(); } @@ -411,7 +441,7 @@ protected static function _createChild(array $data): void $childNS = $value['xmlns:']; unset($value['xmlns:']); } - } elseif (!empty($value) || $value === 0 || $value === '0') { + } elseif ($value || $value === 0 || $value === '0') { $childValue = (string)$value; } @@ -430,18 +460,20 @@ protected static function _createChild(array $data): void /** * Returns this XML structure as an array. * - * @param \SimpleXMLElement|\DOMDocument|\DOMNode $obj SimpleXMLElement, DOMDocument or DOMNode instance + * @param \SimpleXMLElement|\DOMNode $obj SimpleXMLElement, DOMNode instance * @return array Array representation of the XML structure. * @throws \Cake\Utility\Exception\XmlException */ - public static function toArray($obj): array + public static function toArray(SimpleXMLElement|DOMNode $obj): array { if ($obj instanceof DOMNode) { $obj = simplexml_import_dom($obj); } - if (!($obj instanceof SimpleXMLElement)) { - throw new XmlException('The input is not instance of SimpleXMLElement, DOMDocument or DOMNode.'); + + if ($obj === null) { + throw new XmlException('Failed converting DOMNode to SimpleXMLElement'); } + $result = []; $namespaces = array_merge(['' => ''], $obj->getNamespaces(true)); static::_toArray($obj, $result, '', array_keys($namespaces)); @@ -463,12 +495,9 @@ protected static function _toArray(SimpleXMLElement $xml, array &$parentData, st $data = []; foreach ($namespaces as $namespace) { - /** - * @psalm-suppress PossiblyNullIterator - * @var string $key - */ - foreach ($xml->attributes($namespace, true) as $key => $value) { - if (!empty($namespace)) { + $attributes = $xml->attributes($namespace, true); + foreach ($attributes as $key => $value) { + if ($namespace) { $key = $namespace . ':' . $key; } $data['@' . $key] = (string)$value; @@ -480,13 +509,13 @@ protected static function _toArray(SimpleXMLElement $xml, array &$parentData, st } $asString = trim((string)$xml); - if (empty($data)) { + if (!$data) { $data = $asString; } elseif ($asString !== '') { $data['@'] = $asString; } - if (!empty($ns)) { + if ($ns) { $ns .= ':'; } $name = $ns . $xml->getName(); diff --git a/app/vendor/cakephp/cakephp/src/Utility/composer.json b/app/vendor/cakephp/cakephp/src/Utility/composer.json index 949ca6012..563b1c34d 100644 --- a/app/vendor/cakephp/cakephp/src/Utility/composer.json +++ b/app/vendor/cakephp/cakephp/src/Utility/composer.json @@ -25,12 +25,8 @@ "source": "https://github.com/cakephp/utility" }, "require": { - "php": ">=7.4.0", - "cakephp/core": "^4.0" - }, - "suggest": { - "ext-intl": "To use Text::transliterate() or Text::slug()", - "lib-ICU": "To use Text::transliterate() or Text::slug()" + "php": ">=8.1", + "cakephp/core": "5.2.*@dev" }, "autoload": { "psr-4": { @@ -39,5 +35,15 @@ "files": [ "bootstrap.php" ] + }, + "suggest": { + "ext-intl": "To use Text::transliterate() or Text::slug()", + "lib-ICU": "To use Text::transliterate() or Text::slug()" + }, + "prefer-stable": true, + "extra": { + "branch-alias": { + "dev-5.x": "5.2.x-dev" + } } } diff --git a/app/vendor/cakephp/cakephp/src/Validation/README.md b/app/vendor/cakephp/cakephp/src/Validation/README.md index d3484fac3..9d43d566e 100644 --- a/app/vendor/cakephp/cakephp/src/Validation/README.md +++ b/app/vendor/cakephp/cakephp/src/Validation/README.md @@ -27,11 +27,11 @@ $validator ->notEmptyString('comment', 'You need to give a comment.'); $errors = $validator->validate($_POST); -if (!empty($errors)) { +if ($errors) { // display errors. } ``` ## Documentation -Please make sure you check the [official documentation](https://book.cakephp.org/4/en/core-libraries/validation.html) +Please make sure you check the [official documentation](https://book.cakephp.org/5/en/core-libraries/validation.html) diff --git a/app/vendor/cakephp/cakephp/src/Validation/RulesProvider.php b/app/vendor/cakephp/cakephp/src/Validation/RulesProvider.php index 970b87fd7..077b7cc77 100644 --- a/app/vendor/cakephp/cakephp/src/Validation/RulesProvider.php +++ b/app/vendor/cakephp/cakephp/src/Validation/RulesProvider.php @@ -17,12 +17,15 @@ namespace Cake\Validation; use ReflectionClass; +use function Cake\Core\deprecationWarning; /** * A Proxy class used to remove any extra arguments when the user intended to call * a method in another class that is not aware of validation providers signature * * @method bool extension(mixed $check, array $extensions, array $context = []) + * @deprecated 5.2.0 This class is no longer used. Cake\Validation\Validation + * is now directly used as a provider in Cake\Validation\Validator. */ class RulesProvider { @@ -31,24 +34,33 @@ class RulesProvider * * @var object|string */ - protected $_class; + protected object|string $_class; /** * The proxied class' reflection * - * @var \ReflectionClass + * @var \ReflectionClass */ - protected $_reflection; + protected ReflectionClass $_reflection; /** * Constructor, sets the default class to use for calling methods * * @param object|string $class the default class to proxy * @throws \ReflectionException - * @psalm-param object|class-string $class + * @phpstan-param object|class-string $class */ - public function __construct($class = Validation::class) + public function __construct(object|string $class = Validation::class) { + deprecationWarning( + '5.2.0', + sprintf( + 'The class Cake\Validation\RulesProvider is deprecated. ' + . 'Directly set %s as a validation provider.', + (is_string($class) ? $class : get_class($class)), + ), + ); + $this->_class = $class; $this->_reflection = new ReflectionClass($class); } @@ -65,11 +77,13 @@ public function __construct($class = Validation::class) * @param array $arguments the list of arguments to pass to the method * @return bool Whether the validation rule passed */ - public function __call(string $method, array $arguments) + public function __call(string $method, array $arguments): bool { $method = $this->_reflection->getMethod($method); $argumentList = $method->getParameters(); - if (array_pop($argumentList)->getName() !== 'context') { + /** @var \ReflectionParameter $argument */ + $argument = array_pop($argumentList); + if ($argument->getName() !== 'context') { $arguments = array_slice($arguments, 0, -1); } $object = is_string($this->_class) ? null : $this->_class; diff --git a/app/vendor/cakephp/cakephp/src/Validation/ValidatableInterface.php b/app/vendor/cakephp/cakephp/src/Validation/ValidatableInterface.php deleted file mode 100644 index d37b09c15..000000000 --- a/app/vendor/cakephp/cakephp/src/Validation/ValidatableInterface.php +++ /dev/null @@ -1,34 +0,0 @@ - */ - protected static $_pattern = [ + protected static array $_pattern = [ 'hostname' => '(?:[_\p{L}0-9][-_\p{L}0-9]*\.)*(?:[\p{L}0-9][-\p{L}0-9]{0,62})\.(?:(?:[a-z]{2}\.)?[a-z]{2,})', 'latitude' => '[-+]?([1-8]?\d(\.\d+)?|90(\.0+)?)', 'longitude' => '[-+]?(180(\.0+)?|((1[0-7]\d)|([1-9]?\d))(\.\d+)?)', @@ -131,7 +136,7 @@ class Validation * * @var array */ - public static $errors = []; + public static array $errors = []; /** * Checks that a string contains something other than whitespace @@ -141,9 +146,9 @@ class Validation * @param mixed $check Value to check * @return bool Success */ - public static function notBlank($check): bool + public static function notBlank(mixed $check): bool { - if (empty($check) && !is_bool($check) && !is_numeric($check)) { + if (!$check && !is_bool($check) && !is_numeric($check)) { return false; } @@ -159,7 +164,7 @@ public static function notBlank($check): bool * @param mixed $check Value to check * @return bool Success */ - public static function alphaNumeric($check): bool + public static function alphaNumeric(mixed $check): bool { if ((empty($check) && $check !== '0') || !is_scalar($check)) { return false; @@ -177,7 +182,7 @@ public static function alphaNumeric($check): bool * @param mixed $check Value to check * @return bool Success */ - public static function notAlphaNumeric($check): bool + public static function notAlphaNumeric(mixed $check): bool { return !static::alphaNumeric($check); } @@ -188,7 +193,7 @@ public static function notAlphaNumeric($check): bool * @param mixed $check Value to check * @return bool Success */ - public static function asciiAlphaNumeric($check): bool + public static function asciiAlphaNumeric(mixed $check): bool { if ((empty($check) && $check !== '0') || !is_scalar($check)) { return false; @@ -203,7 +208,7 @@ public static function asciiAlphaNumeric($check): bool * @param mixed $check Value to check * @return bool Success */ - public static function notAsciiAlphaNumeric($check): bool + public static function notAsciiAlphaNumeric(mixed $check): bool { return !static::asciiAlphaNumeric($check); } @@ -218,7 +223,7 @@ public static function notAsciiAlphaNumeric($check): bool * @param int $max Maximum value in range (inclusive) * @return bool Success */ - public static function lengthBetween($check, int $min, int $max): bool + public static function lengthBetween(mixed $check, int $min, int $max): bool { if (!is_scalar($check)) { return false; @@ -241,9 +246,13 @@ public static function lengthBetween($check, int $min, int $max): bool * @return bool Success * @see \Cake\Validation\Validation::luhn() */ - public static function creditCard($check, $type = 'fast', bool $deep = false, ?string $regex = null): bool - { - if (!(is_string($check) || is_int($check))) { + public static function creditCard( + mixed $check, + array|string $type = 'fast', + bool $deep = false, + ?string $regex = null, + ): bool { + if (!is_string($check) && !is_int($check)) { return false; } @@ -313,7 +322,7 @@ public static function creditCard($check, $type = 'fast', bool $deep = false, ?s * @param int $expectedCount The expected count value. * @return bool Success */ - public static function numElements($check, string $operator, int $expectedCount): bool + public static function numElements(mixed $check, string $operator, int $expectedCount): bool { if (!is_array($check) && !$check instanceof Countable) { return false; @@ -325,14 +334,14 @@ public static function numElements($check, string $operator, int $expectedCount) /** * Used to compare 2 numeric values. * - * @param string|int $check1 The left value to compare. + * @param mixed $check1 The left value to compare. * @param string $operator Can be one of following operator strings: * '>', '<', '>=', '<=', '==', '!=', '===' and '!=='. You can use one of * the Validation::COMPARE_* constants. - * @param string|int $check2 The right value to compare. + * @param mixed $check2 The right value to compare. * @return bool Success */ - public static function comparison($check1, string $operator, $check2): bool + public static function comparison(mixed $check1, string $operator, mixed $check2): bool { if ( (!is_numeric($check1) || !is_numeric($check2)) && @@ -341,49 +350,19 @@ public static function comparison($check1, string $operator, $check2): bool return false; } - switch ($operator) { - case static::COMPARE_GREATER: - if ($check1 > $check2) { - return true; - } - break; - case static::COMPARE_LESS: - if ($check1 < $check2) { - return true; - } - break; - case static::COMPARE_GREATER_OR_EQUAL: - if ($check1 >= $check2) { - return true; - } - break; - case static::COMPARE_LESS_OR_EQUAL: - if ($check1 <= $check2) { - return true; - } - break; - case static::COMPARE_EQUAL: - if ($check1 == $check2) { - return true; - } - break; - case static::COMPARE_NOT_EQUAL: - if ($check1 != $check2) { - return true; - } - break; - case static::COMPARE_SAME: - if ($check1 === $check2) { - return true; - } - break; - case static::COMPARE_NOT_SAME: - if ($check1 !== $check2) { - return true; - } - break; - default: - static::$errors[] = 'You must define a valid $operator parameter for Validation::comparison()'; + try { + return match ($operator) { + static::COMPARE_GREATER => $check1 > $check2, + static::COMPARE_LESS => $check1 < $check2, + static::COMPARE_GREATER_OR_EQUAL => $check1 >= $check2, + static::COMPARE_LESS_OR_EQUAL => $check1 <= $check2, + static::COMPARE_EQUAL => $check1 == $check2, + static::COMPARE_NOT_EQUAL => $check1 != $check2, + static::COMPARE_SAME => $check1 === $check2, + static::COMPARE_NOT_SAME => $check1 !== $check2, + }; + } catch (UnhandledMatchError) { + static::$errors[] = 'You must define a valid $operator parameter for Validation::comparison()'; } return false; @@ -399,7 +378,7 @@ public static function comparison($check1, string $operator, $check2): bool * @param array $context The validation context. * @return bool */ - public static function compareWith($check, string $field, array $context): bool + public static function compareWith(mixed $check, string $field, array $context): bool { return self::compareFields($check, $field, static::COMPARE_SAME, $context); } @@ -416,7 +395,7 @@ public static function compareWith($check, string $field, array $context): bool * @return bool * @since 3.6.0 */ - public static function compareFields($check, string $field, string $operator, array $context): bool + public static function compareFields(mixed $check, string $field, string $operator, array $context): bool { if (!isset($context['data']) || !array_key_exists($field, $context['data'])) { return false; @@ -425,28 +404,6 @@ public static function compareFields($check, string $field, string $operator, ar return static::comparison($check, $operator, $context['data'][$field]); } - /** - * Checks if a string contains one or more non-alphanumeric characters. - * - * Returns true if string contains at least the specified number of non-alphanumeric characters - * - * @param mixed $check Value to check - * @param int $count Number of non-alphanumerics to check for - * @return bool Success - * @deprecated 4.0.0 Use {@link notAlphaNumeric()} instead. Will be removed in 5.0 - */ - public static function containsNonAlphaNumeric($check, int $count = 1): bool - { - deprecationWarning('Validation::containsNonAlphaNumeric() is deprecated. Use notAlphaNumeric() instead.'); - if (!is_string($check)) { - return false; - } - - $matches = preg_match_all('/[^a-zA-Z0-9]/', $check); - - return $matches >= $count; - } - /** * Used when a custom regular expression is needed. * @@ -454,7 +411,7 @@ public static function containsNonAlphaNumeric($check, int $count = 1): bool * @param string|null $regex If $check is passed as a string, $regex must also be set to valid regular expression * @return bool Success */ - public static function custom($check, ?string $regex = null): bool + public static function custom(mixed $check, ?string $regex = null): bool { if (!is_scalar($check)) { return false; @@ -476,9 +433,9 @@ public static function custom($check, ?string $regex = null): bool * * ### Formats: * + * - `ymd` 2006-12-27 or 06-12-27 separators can be a space, period, dash, forward slash * - `dmy` 27-12-2006 or 27-12-06 separators can be a space, period, dash, forward slash * - `mdy` 12-27-2006 or 12-27-06 separators can be a space, period, dash, forward slash - * - `ymd` 2006-12-27 or 06-12-27 separators can be a space, period, dash, forward slash * - `dMy` 27 December 2006 or 27 Dec 2006 * - `Mdy` December 27, 2006 or Dec 27, 2006 comma is optional * - `My` December 2006 or Dec 2006 @@ -492,9 +449,12 @@ public static function custom($check, ?string $regex = null): bool * @param string|null $regex If a custom regular expression is used this is the only validation that will occur. * @return bool Success */ - public static function date($check, $format = 'ymd', ?string $regex = null): bool + public static function date(mixed $check, array|string $format = 'ymd', ?string $regex = null): bool { - if ($check instanceof DateTimeInterface) { + if ( + (class_exists(ChronosDate::class) && $check instanceof ChronosDate) + || $check instanceof DateTimeInterface + ) { return true; } if (is_object($check)) { @@ -546,9 +506,9 @@ public static function date($check, $format = 'ymd', ?string $regex = null): boo $regex['ym'] = '%^(' . $year . $separator . $month . ')$%'; $regex['y'] = '%^(' . $fourDigitYear . ')$%'; - $format = is_array($format) ? array_values($format) : [$format]; + $format = (array)$format; foreach ($format as $key) { - if (static::_check($check, $regex[$key]) === true) { + if (static::_check($check, $regex[$key])) { return true; } } @@ -561,6 +521,25 @@ public static function date($check, $format = 'ymd', ?string $regex = null): boo * * All values matching the "date" core validation rule, and the "time" one will be valid * + * Years are valid from 0001 to 2999. + * + * ### Formats: + * + * - `ymd` 2006-12-27 or 06-12-27 separators can be a space, period, dash, forward slash + * - `dmy` 27-12-2006 or 27-12-06 separators can be a space, period, dash, forward slash + * - `mdy` 12-27-2006 or 12-27-06 separators can be a space, period, dash, forward slash + * - `dMy` 27 December 2006 or 27 Dec 2006 + * - `Mdy` December 27, 2006 or Dec 27, 2006 comma is optional + * - `My` December 2006 or Dec 2006 + * - `my` 12/2006 or 12/06 separators can be a space, period, dash, forward slash + * - `ym` 2006/12 or 06/12 separators can be a space, period, dash, forward slash + * - `y` 2006 just the year without any separators + * + * Time is validated as 24hr (HH:MM[:SS][.FFFFFF]) or am/pm ([H]H:MM[a|p]m) + * + * Seconds and fractional seconds (microseconds) are allowed but optional + * in 24hr format. + * * @param mixed $check Value to check * @param array|string $dateFormat Format of the date part. See Validation::date() for more information. * Or `Validation::DATETIME_ISO8601` to validate an ISO8601 datetime value. @@ -570,7 +549,7 @@ public static function date($check, $format = 'ymd', ?string $regex = null): boo * @see \Cake\Validation\Validation::date() * @see \Cake\Validation\Validation::time() */ - public static function datetime($check, $dateFormat = 'ymd', ?string $regex = null): bool + public static function datetime(mixed $check, array|string $dateFormat = 'ymd', ?string $regex = null): bool { if ($check instanceof DateTimeInterface) { return true; @@ -590,13 +569,16 @@ public static function datetime($check, $dateFormat = 'ymd', ?string $regex = nu $check = static::_getDateString($check); $dateFormat = 'ymd'; } + if (!is_string($check)) { + return false; + } $parts = preg_split('/[\sT]+/', $check); - if (!empty($parts) && count($parts) > 1) { + if ($parts && count($parts) > 1) { $date = rtrim(array_shift($parts), ','); $time = implode(' ', $parts); if ($dateFormat === static::DATETIME_ISO8601) { $dateFormat = 'ymd'; - $time = preg_split("/[TZ\-\+\.]/", $time); + $time = preg_split("/[TZ\-\+\.]/", $time) ?: []; $time = array_shift($time); } $valid = static::date($date, $dateFormat, $regex) && static::time($time); @@ -613,7 +595,7 @@ public static function datetime($check, $dateFormat = 'ymd', ?string $regex = nu * @return bool True if the value is valid, false otherwise * @see Regex credits: https://www.myintervals.com/blog/2009/05/20/iso-8601-date-validation-that-doesnt-suck/ */ - public static function iso8601($check): bool + public static function iso8601(mixed $check): bool { if ($check instanceof DateTimeInterface) { return true; @@ -638,9 +620,12 @@ public static function iso8601($check): bool * @param mixed $check a valid time string/object * @return bool Success */ - public static function time($check): bool + public static function time(mixed $check): bool { - if ($check instanceof DateTimeInterface) { + if ( + (class_exists(ChronosTime::class) && $check instanceof ChronosTime) + || $check instanceof DateTimeInterface + ) { return true; } if (is_array($check)) { @@ -670,9 +655,18 @@ public static function time($check): bool * @see \Cake\I18n\Time::parseTime() * @see \Cake\I18n\Time::parseDateTime() */ - public static function localizedTime($check, string $type = 'datetime', $format = null): bool + public static function localizedTime(mixed $check, string $type = 'datetime', string|int|null $format = null): bool { - if ($check instanceof DateTimeInterface) { + if (!class_exists(DateTime::class)) { + throw new CakeException( + 'The Cake\I18n\DateTime class is not available. Install the cakephp/i18n package.', + ); + } + + if ( + (class_exists(ChronosTime::class) && $check instanceof ChronosTime) + || $check instanceof DateTimeInterface + ) { return true; } if (!is_string($check)) { @@ -688,24 +682,20 @@ public static function localizedTime($check, string $type = 'datetime', $format } $method = $methods[$type]; - return FrozenTime::$method($check, $format) !== null; + return DateTime::$method($check, $format) !== null; } /** * Validates if passed value is boolean-like. * - * The list of what is considered to be boolean values, may be set via $booleanValues. + * The list of what is considered to be boolean values may be set via $booleanValues. * - * @param string|int|bool $check Value to check. + * @param mixed $check Value to check. * @param array $booleanValues List of valid boolean values, defaults to `[true, false, 0, 1, '0', '1']`. * @return bool Success. */ - public static function boolean($check, array $booleanValues = []): bool + public static function boolean(mixed $check, array $booleanValues = [true, false, 0, 1, '0', '1']): bool { - if (!$booleanValues) { - $booleanValues = [true, false, 0, 1, '0', '1']; - } - return in_array($check, $booleanValues, true); } @@ -714,16 +704,12 @@ public static function boolean($check, array $booleanValues = []): bool * * The list of what is considered to be truthy values, may be set via $truthyValues. * - * @param string|int|bool $check Value to check. + * @param mixed $check Value to check. * @param array $truthyValues List of valid truthy values, defaults to `[true, 1, '1']`. * @return bool Success. */ - public static function truthy($check, array $truthyValues = []): bool + public static function truthy(mixed $check, array $truthyValues = [true, 1, '1']): bool { - if (!$truthyValues) { - $truthyValues = [true, 1, '1']; - } - return in_array($check, $truthyValues, true); } @@ -732,22 +718,21 @@ public static function truthy($check, array $truthyValues = []): bool * * The list of what is considered to be falsey values, may be set via $falseyValues. * - * @param string|int|bool $check Value to check. + * @param mixed $check Value to check. * @param array $falseyValues List of valid falsey values, defaults to `[false, 0, '0']`. * @return bool Success. */ - public static function falsey($check, array $falseyValues = []): bool + public static function falsey(mixed $check, array $falseyValues = [false, 0, '0']): bool { - if (!$falseyValues) { - $falseyValues = [false, 0, '0']; - } - return in_array($check, $falseyValues, true); } /** * Checks that a value is a valid decimal. Both the sign and exponent are optional. * + * Be aware that the currently set locale is being used to determine + * the decimal and thousands separator of the given number. + * * Valid Places: * * - null => Any number of decimal places, including none. The '.' is not required. @@ -759,7 +744,7 @@ public static function falsey($check, array $falseyValues = []): bool * @param string|null $regex If a custom regular expression is used, this is the only validation that will occur. * @return bool Success */ - public static function decimal($check, $places = null, ?string $regex = null): bool + public static function decimal(mixed $check, int|bool|null $places = null, ?string $regex = null): bool { if (!is_scalar($check)) { return false; @@ -778,12 +763,10 @@ public static function decimal($check, $places = null, ?string $regex = null): b $check = sprintf('%.1f', $check); } $regex = "/^{$sign}{$dnum}{$exp}$/"; - } elseif (is_numeric($places)) { + } else { $places = '[0-9]{' . $places . '}'; $dnum = "(?:[0-9]*[\.]{$places}|{$lnum}[\.]{$places})"; $regex = "/^{$sign}{$dnum}{$exp}$/"; - } else { - return false; } } @@ -794,7 +777,7 @@ public static function decimal($check, $places = null, ?string $regex = null): b $groupingSep = $formatter->getSymbol(NumberFormatter::GROUPING_SEPARATOR_SYMBOL); // There are two types of non-breaking spaces - we inject a space to account for human input - if ($groupingSep == "\xc2\xa0" || $groupingSep == "\xe2\x80\xaf") { + if ($groupingSep === "\xc2\xa0" || $groupingSep === "\xe2\x80\xaf") { $check = str_replace([' ', $groupingSep, $decimalPoint], ['', '', '.'], (string)$check); } else { $check = str_replace([$groupingSep, $decimalPoint], ['', '.'], (string)$check); @@ -810,26 +793,25 @@ public static function decimal($check, $places = null, ?string $regex = null): b * any PHP version on a non-windows distribution * * @param mixed $check Value to check - * @param bool $deep Perform a deeper validation (if true), by also checking availability of host + * @param bool|null $deep Perform a deeper validation (if true), by also checking availability of host * @param string|null $regex Regex to use (if none it will use built in regex) * @return bool Success */ - public static function email($check, ?bool $deep = false, ?string $regex = null): bool + public static function email(mixed $check, ?bool $deep = false, ?string $regex = null): bool { if (!is_string($check)) { return false; } - if ($regex === null) { - // phpcs:ignore Generic.Files.LineLength - $regex = '/^[\p{L}0-9!#$%&\'*+\/=?^_`{|}~-]+(?:\.[\p{L}0-9!#$%&\'*+\/=?^_`{|}~-]+)*@' . self::$_pattern['hostname'] . '$/ui'; - } + // phpcs:ignore Generic.Files.LineLength + $regex ??= '/^[\p{L}0-9!#$%&\'*+\/=?^_`{|}~-]+(?:\.[\p{L}0-9!#$%&\'*+\/=?^_`{|}~-]+)*@' . self::$_pattern['hostname'] . '$/ui'; + $return = static::_check($check, $regex); if ($deep === false || $deep === null) { return $return; } - if ($return === true && preg_match('/@(' . static::$_pattern['hostname'] . ')$/i', $check, $regs)) { + if ($return && preg_match('/@(' . static::$_pattern['hostname'] . ')$/i', $check, $regs)) { if (function_exists('getmxrr') && getmxrr($regs[1], $mxhosts)) { return true; } @@ -843,6 +825,165 @@ public static function email($check, ?bool $deep = false, ?string $regex = null) return false; } + /** + * Checks that the value is a valid backed enum instance or value. + * + * @param mixed $check Value to check + * @param class-string<\BackedEnum> $enumClassName The valid backed enum class name + * @return bool Success + * @since 5.0.3 + */ + public static function enum(mixed $check, string $enumClassName): bool + { + return static::checkEnum($check, $enumClassName); + } + + /** + * Checks that the value is a valid backed enum instance or value. + * + * @param mixed $check Value to check + * @param array<\BackedEnum> $cases Array of enum cases that are valid. + * @return bool Success + * @since 5.1.0 + */ + public static function enumOnly(mixed $check, array $cases): bool + { + if ($cases === []) { + throw new InvalidArgumentException('At least one case needed for `enumOnly()` validation.'); + } + + $firstKey = array_key_first($cases); + $firstValue = $cases[$firstKey]; + $enumClassName = $firstValue::class; + + $options = ['only' => $cases]; + + return static::checkEnum($check, $enumClassName, $options); + } + + /** + * Checks that the value is a valid backed enum instance or value. + * + * @param mixed $check Value to check + * @param array<\BackedEnum> $cases Array of enum cases that are not valid. + * @return bool Success + * @since 5.1.0 + */ + public static function enumExcept(mixed $check, array $cases): bool + { + if ($cases === []) { + throw new InvalidArgumentException('At least one case needed for `enumExcept()` validation.'); + } + + $firstKey = array_key_first($cases); + $firstValue = $cases[$firstKey]; + $enumClassName = $firstValue::class; + + $options = ['except' => $cases]; + + return static::checkEnum($check, $enumClassName, $options); + } + + /** + * @param mixed $check + * @param class-string $enumClassName + * @param array $options + * @return bool + */ + protected static function checkEnum(mixed $check, string $enumClassName, array $options = []): bool + { + if ( + $check instanceof $enumClassName && + $check instanceof BackedEnum + ) { + return static::isValidEnum($check, $options); + } + + $backingType = null; + try { + $reflectionEnum = new ReflectionEnum($enumClassName); + + /** @var \ReflectionNamedType|null $reflectionBackingType */ + $reflectionBackingType = $reflectionEnum->getBackingType(); + if ($reflectionBackingType) { + if (method_exists($reflectionBackingType, 'getName')) { + $backingType = $reflectionBackingType->getName(); + } else { + $backingType = (string)$reflectionBackingType; + } + } + } catch (ReflectionException) { + } + + if ($backingType === null) { + throw new InvalidArgumentException( + 'The `$enumClassName` argument must be the classname of a valid backed enum.', + ); + } + + if (!is_string($check) && !is_int($check)) { + return false; + } + + if ($backingType === 'int') { + if (!is_numeric($check)) { + return false; + } + $check = (int)$check; + } + + if (get_debug_type($check) !== (string)$backingType) { + return false; + } + + $options += [ + 'only' => null, + 'except' => null, + ]; + + /** @var class-string<\BackedEnum> $enumClassName */ + $enum = $enumClassName::tryFrom($check); + if ($enum === null) { + return false; + } + + return static::isValidEnum($enum, $options); + } + + /** + * @param \BackedEnum $enum + * @param array $options + * @return bool + */ + protected static function isValidEnum(BackedEnum $enum, array $options): bool + { + $options += ['only' => null, 'except' => null]; + + if ($options['only']) { + if (!is_array($options['only'])) { + $options['only'] = [$options['only']]; + } + + if (in_array($enum, $options['only'], true)) { + return true; + } + + return false; + } + + if ($options['except']) { + if (!is_array($options['except'])) { + $options['except'] = [$options['except']]; + } + + if (in_array($enum, $options['except'], true)) { + return false; + } + } + + return true; + } + /** * Checks that value is exactly $comparedTo. * @@ -850,7 +991,7 @@ public static function email($check, ?bool $deep = false, ?string $regex = null) * @param mixed $comparedTo Value to compare * @return bool Success */ - public static function equalTo($check, $comparedTo): bool + public static function equalTo(mixed $check, mixed $comparedTo): bool { return $check === $comparedTo; } @@ -858,13 +999,16 @@ public static function equalTo($check, $comparedTo): bool /** * Checks that value has a valid file extension. * - * @param \Psr\Http\Message\UploadedFileInterface|array|string $check Value to check + * Supports checking `\Psr\Http\Message\UploadedFileInterface` instances and + * and arrays with a `name` key. + * + * @param mixed $check Value to check * @param array $extensions file extensions to allow. By default extensions are 'gif', 'jpeg', 'png', 'jpg' * @return bool Success */ - public static function extension($check, array $extensions = ['gif', 'jpeg', 'png', 'jpg']): bool + public static function extension(mixed $check, array $extensions = ['gif', 'jpeg', 'png', 'jpg']): bool { - if ($check instanceof UploadedFileInterface) { + if (interface_exists(UploadedFileInterface::class) && $check instanceof UploadedFileInterface) { $check = $check->getClientFilename(); } elseif (is_array($check) && isset($check['name'])) { $check = $check['name']; @@ -872,7 +1016,7 @@ public static function extension($check, array $extensions = ['gif', 'jpeg', 'pn return static::extension(array_shift($check), $extensions); } - if (empty($check)) { + if (!$check) { return false; } @@ -893,7 +1037,7 @@ public static function extension($check, array $extensions = ['gif', 'jpeg', 'pn * @param string $type The IP Protocol version to validate against * @return bool Success */ - public static function ip($check, string $type = 'both'): bool + public static function ip(mixed $check, string $type = 'both'): bool { if (!is_string($check)) { return false; @@ -918,7 +1062,7 @@ public static function ip($check, string $type = 'both'): bool * @param int $min The minimal string length * @return bool Success */ - public static function minLength($check, int $min): bool + public static function minLength(mixed $check, int $min): bool { if (!is_scalar($check)) { return false; @@ -934,7 +1078,7 @@ public static function minLength($check, int $min): bool * @param int $max The maximal string length * @return bool Success */ - public static function maxLength($check, int $max): bool + public static function maxLength(mixed $check, int $max): bool { if (!is_scalar($check)) { return false; @@ -950,7 +1094,7 @@ public static function maxLength($check, int $max): bool * @param int $min The minimal string length (in bytes) * @return bool Success */ - public static function minLengthBytes($check, int $min): bool + public static function minLengthBytes(mixed $check, int $min): bool { if (!is_scalar($check)) { return false; @@ -966,7 +1110,7 @@ public static function minLengthBytes($check, int $min): bool * @param int $max The maximal string length * @return bool Success */ - public static function maxLengthBytes($check, int $max): bool + public static function maxLengthBytes(mixed $check, int $max): bool { if (!is_scalar($check)) { return false; @@ -982,7 +1126,7 @@ public static function maxLengthBytes($check, int $max): bool * @param string $symbolPosition Where symbol is located (left/right) * @return bool Success */ - public static function money($check, string $symbolPosition = 'left'): bool + public static function money(mixed $check, string $symbolPosition = 'left'): bool { $money = '(?!0,?\d)(?:\d{1,3}(?:([, .])\d{3})?(?:\1\d{3})*|(?:\d+))((?!\1)[,.]\d{1,2})?'; if ($symbolPosition === 'right') { @@ -1008,7 +1152,7 @@ public static function money($check, string $symbolPosition = 'left'): bool * @param bool $caseInsensitive Set to true for case insensitive comparison. * @return bool Success */ - public static function multiple($check, array $options = [], bool $caseInsensitive = false): bool + public static function multiple(mixed $check, array $options = [], bool $caseInsensitive = false): bool { $defaults = ['in' => null, 'max' => null, 'min' => null]; $options += $defaults; @@ -1016,7 +1160,7 @@ public static function multiple($check, array $options = [], bool $caseInsensiti $check = array_filter((array)$check, function ($value) { return $value || is_numeric($value); }); - if (empty($check)) { + if (!$check) { return false; } if ($options['max'] && count($check) > $options['max']) { @@ -1032,7 +1176,7 @@ public static function multiple($check, array $options = [], bool $caseInsensiti foreach ($check as $val) { $strict = !is_numeric($val); if ($caseInsensitive) { - $val = mb_strtolower($val); + $val = mb_strtolower((string)$val); } if (!in_array((string)$val, $options['in'], $strict)) { return false; @@ -1049,7 +1193,7 @@ public static function multiple($check, array $options = [], bool $caseInsensiti * @param mixed $check Value to check * @return bool Success */ - public static function numeric($check): bool + public static function numeric(mixed $check): bool { return is_numeric($check); } @@ -1062,7 +1206,7 @@ public static function numeric($check): bool * @return bool Success * @see https://en.wikipedia.org/wiki/Natural_number */ - public static function naturalNumber($check, bool $allowZero = false): bool + public static function naturalNumber(mixed $check, bool $allowZero = false): bool { $regex = $allowZero ? '/^(?:0|[1-9][0-9]*)$/' : '/^[1-9][0-9]*$/'; @@ -1081,7 +1225,7 @@ public static function naturalNumber($check, bool $allowZero = false): bool * @param float|null $upper Upper limit * @return bool Success */ - public static function range($check, ?float $lower = null, ?float $upper = null): bool + public static function range(mixed $check, ?float $lower = null, ?float $upper = null): bool { if (!is_numeric($check)) { return false; @@ -1114,7 +1258,7 @@ public static function range($check, ?float $lower = null, ?float $upper = null) * @return bool Success * @link https://tools.ietf.org/html/rfc3986 */ - public static function url($check, bool $strict = false): bool + public static function url(mixed $check, bool $strict = false): bool { if (!is_string($check)) { return false; @@ -1140,14 +1284,14 @@ public static function url($check, bool $strict = false): bool } /** - * Checks if a value is in a given list. Comparison is case sensitive by default. + * Checks if a value is in a given list. Comparison is case-sensitive by default. * * @param mixed $check Value to check. * @param array $list List to check against. - * @param bool $caseInsensitive Set to true for case insensitive comparison. + * @param bool $caseInsensitive Set to true for case-insensitive comparison. * @return bool Success. */ - public static function inList($check, array $list, bool $caseInsensitive = false): bool + public static function inList(mixed $check, array $list, bool $caseInsensitive = false): bool { if (!is_scalar($check)) { return false; @@ -1168,9 +1312,9 @@ public static function inList($check, array $list, bool $caseInsensitive = false * @param mixed $check Value to check * @return bool Success */ - public static function uuid($check): bool + public static function uuid(mixed $check): bool { - $regex = '/^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[0-5][a-fA-F0-9]{3}-[089aAbB][a-fA-F0-9]{3}-[a-fA-F0-9]{12}$/'; + $regex = '/^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[0-8][a-fA-F0-9]{3}-[089aAbB][a-fA-F0-9]{3}-[a-fA-F0-9]{12}$/'; return self::_check($check, $regex); } @@ -1182,7 +1326,7 @@ public static function uuid($check): bool * @param string $regex Regular expression * @return bool Success of match */ - protected static function _check($check, string $regex): bool + protected static function _check(mixed $check, string $regex): bool { return is_scalar($check) && preg_match($regex, (string)$check); } @@ -1194,7 +1338,7 @@ protected static function _check($check, string $regex): bool * @return bool Success * @see https://en.wikipedia.org/wiki/Luhn_algorithm */ - public static function luhn($check): bool + public static function luhn(mixed $check): bool { if (!is_scalar($check) || (int)$check === 0) { return false; @@ -1222,32 +1366,31 @@ public static function luhn($check): bool * by checking the using finfo on the file, not relying on the content-type * sent by the client. * - * @param \Psr\Http\Message\UploadedFileInterface|array|string $check Value to check. + * @param mixed $check Value to check. * @param array|string $mimeTypes Array of mime types or regex pattern to check. * @return bool Success - * @throws \RuntimeException when mime type can not be determined. - * @throws \LogicException when ext/fileinfo is missing + * @throws \Cake\Core\Exception\CakeException when mime type can not be determined. */ - public static function mimeType($check, $mimeTypes = []): bool + public static function mimeType(mixed $check, array|string $mimeTypes = []): bool { $file = static::getFilename($check); - if ($file === false) { + if ($file === null) { return false; } if (!function_exists('finfo_open')) { - throw new LogicException('ext/fileinfo is required for validating file mime types'); + throw new CakeException('ext/fileinfo is required for validating file mime types'); } if (!is_file($file)) { - throw new RuntimeException('Cannot validate mimetype for a missing file'); + throw new CakeException('Cannot validate mimetype for a missing file'); } $finfo = finfo_open(FILEINFO_MIME_TYPE); - $mime = finfo_file($finfo, $file); + $mime = $finfo ? finfo_file($finfo, $file) : null; if (!$mime) { - throw new RuntimeException('Can not determine the mimetype.'); + throw new CakeException('Can not determine the mimetype.'); } if (is_string($mimeTypes)) { @@ -1262,13 +1405,12 @@ public static function mimeType($check, $mimeTypes = []): bool } /** - * Helper for reading the file out of the various file implementations - * we accept. + * Helper for reading the file name. * * @param mixed $check The data to read a filename out of. - * @return string|false Either the filename or false on failure. + * @return string|null Either the filename or null on failure. */ - protected static function getFilename($check) + protected static function getFilename(mixed $check): ?string { if ($check instanceof UploadedFileInterface) { // Uploaded files throw exceptions on upload errors. @@ -1278,20 +1420,17 @@ protected static function getFilename($check) return $uri; } - return false; - } catch (RuntimeException $e) { - return false; + return null; + } catch (RuntimeException) { + return null; } } - if (is_array($check) && isset($check['tmp_name'])) { - return $check['tmp_name']; - } if (is_string($check)) { return $check; } - return false; + return null; } /** @@ -1301,15 +1440,15 @@ protected static function getFilename($check) * by checking the filesize() on disk and not relying on the length * reported by the client. * - * @param \Psr\Http\Message\UploadedFileInterface|array|string $check Value to check. + * @param mixed $check Value to check. * @param string $operator See `Validation::comparison()`. * @param string|int $size Size in bytes or human readable string like '5MB'. * @return bool Success */ - public static function fileSize($check, string $operator, $size): bool + public static function fileSize(mixed $check, string $operator, string|int $size): bool { $file = static::getFilename($check); - if ($file === false) { + if ($file === null) { return false; } @@ -1324,12 +1463,15 @@ public static function fileSize($check, string $operator, $size): bool /** * Checking for upload errors * - * @param \Psr\Http\Message\UploadedFileInterface|array|string $check Value to check. + * Supports checking `\Psr\Http\Message\UploadedFileInterface` instances and + * and arrays with a `error` key. + * + * @param mixed $check Value to check. * @param bool $allowNoFile Set to true to allow UPLOAD_ERR_NO_FILE as a pass. * @return bool * @see https://secure.php.net/manual/en/features.file-upload.errors.php */ - public static function uploadError($check, bool $allowNoFile = false): bool + public static function uploadError(mixed $check, bool $allowNoFile = false): bool { if ($check instanceof UploadedFileInterface) { $code = $check->getError(); @@ -1369,55 +1511,46 @@ public static function uploadError($check, bool $allowNoFile = false): bool * @param array $options An array of options for the validation. * @return bool */ - public static function uploadedFile($file, array $options = []): bool + public static function uploadedFile(mixed $file, array $options = []): bool { + if (!($file instanceof UploadedFileInterface)) { + return false; + } + $options += [ 'minSize' => null, 'maxSize' => null, 'types' => null, 'optional' => false, ]; - if (!is_array($file) && !($file instanceof UploadedFileInterface)) { - return false; - } - $error = $isUploaded = false; - if ($file instanceof UploadedFileInterface) { - $error = $file->getError(); - $isUploaded = true; - } - if (is_array($file)) { - $keys = ['error', 'name', 'size', 'tmp_name', 'type']; - ksort($file); - if (array_keys($file) !== $keys) { - return false; - } - $error = (int)$file['error']; - $isUploaded = is_uploaded_file($file['tmp_name']); - } if (!static::uploadError($file, $options['optional'])) { return false; } - if ($options['optional'] && $error === UPLOAD_ERR_NO_FILE) { + + if ($options['optional'] && $file->getError() === UPLOAD_ERR_NO_FILE) { return true; } + if ( isset($options['minSize']) && !static::fileSize($file, static::COMPARE_GREATER_OR_EQUAL, $options['minSize']) ) { return false; } + if ( isset($options['maxSize']) && !static::fileSize($file, static::COMPARE_LESS_OR_EQUAL, $options['maxSize']) ) { return false; } + if (isset($options['types']) && !static::mimeType($file, $options['types'])) { return false; } - return $isUploaded; + return true; } /** @@ -1428,22 +1561,26 @@ public static function uploadedFile($file, array $options = []): bool * @return bool * @throws \InvalidArgumentException */ - public static function imageSize($file, array $options): bool + public static function imageSize(mixed $file, array $options): bool { if (!isset($options['height']) && !isset($options['width'])) { throw new InvalidArgumentException( - 'Invalid image size validation parameters! Missing `width` and / or `height`.' + 'Invalid image size validation parameters! Missing `width` and / or `height`.', ); } $file = static::getFilename($file); - if ($file === false) { + if ($file === null) { return false; } - - [$width, $height] = getimagesize($file); - $validHeight = null; + $width = null; + $height = null; + $imageSize = getimagesize($file); + if ($imageSize) { + [$width, $height] = $imageSize; + } $validWidth = null; + $validHeight = null; if (isset($options['height'])) { $validHeight = self::comparison($height, $options['height'][0], $options['height'][1]); @@ -1472,7 +1609,7 @@ public static function imageSize($file, array $options): bool * @param int $width Min or max width. * @return bool */ - public static function imageWidth($file, string $operator, int $width): bool + public static function imageWidth(mixed $file, string $operator, int $width): bool { return self::imageSize($file, [ 'width' => [ @@ -1490,7 +1627,7 @@ public static function imageWidth($file, string $operator, int $width): bool * @param int $height Min or max height. * @return bool */ - public static function imageHeight($file, string $operator, int $height): bool + public static function imageHeight(mixed $file, string $operator, int $height): bool { return self::imageSize($file, [ 'height' => [ @@ -1517,7 +1654,7 @@ public static function imageHeight($file, string $operator, int $height): bool * @param array $options Options for the validation logic. * @return bool */ - public static function geoCoordinate($value, array $options = []): bool + public static function geoCoordinate(mixed $value, array $options = []): bool { if (!is_scalar($value)) { return false; @@ -1528,9 +1665,9 @@ public static function geoCoordinate($value, array $options = []): bool 'type' => 'latLong', ]; if ($options['type'] !== 'latLong') { - throw new RuntimeException(sprintf( - 'Unsupported coordinate type "%s". Use "latLong" instead.', - $options['type'] + throw new InvalidArgumentException(sprintf( + 'Unsupported coordinate type `%s`. Use `latLong` instead.', + $options['type'], )); } $pattern = '/^' . self::$_pattern['latitude'] . ',\s*' . self::$_pattern['longitude'] . '$/'; @@ -1553,7 +1690,7 @@ public static function geoCoordinate($value, array $options = []): bool * @link https://en.wikipedia.org/wiki/Latitude * @see \Cake\Validation\Validation::geoCoordinate() */ - public static function latitude($value, array $options = []): bool + public static function latitude(mixed $value, array $options = []): bool { $options['format'] = 'lat'; @@ -1569,7 +1706,7 @@ public static function latitude($value, array $options = []): bool * @link https://en.wikipedia.org/wiki/Longitude * @see \Cake\Validation\Validation::geoCoordinate() */ - public static function longitude($value, array $options = []): bool + public static function longitude(mixed $value, array $options = []): bool { $options['format'] = 'long'; @@ -1584,7 +1721,7 @@ public static function longitude($value, array $options = []): bool * @param mixed $value The value to check * @return bool */ - public static function ascii($value): bool + public static function ascii(mixed $value): bool { if (!is_string($value)) { return false; @@ -1608,7 +1745,7 @@ public static function ascii($value): bool * @param array $options An array of options. See above for the supported options. * @return bool */ - public static function utf8($value, array $options = []): bool + public static function utf8(mixed $value, array $options = []): bool { if (!is_string($value)) { return false; @@ -1630,7 +1767,7 @@ public static function utf8($value, array $options = []): bool * @param mixed $value The value to check * @return bool */ - public static function isInteger($value): bool + public static function isInteger(mixed $value): bool { if (is_int($value)) { return true; @@ -1649,7 +1786,7 @@ public static function isInteger($value): bool * @param mixed $value The value to check * @return bool */ - public static function isArray($value): bool + public static function isArray(mixed $value): bool { return is_array($value); } @@ -1663,7 +1800,7 @@ public static function isArray($value): bool * @param mixed $value The value to check * @return bool */ - public static function isScalar($value): bool + public static function isScalar(mixed $value): bool { return is_scalar($value); } @@ -1674,7 +1811,7 @@ public static function isScalar($value): bool * @param mixed $check The value to check * @return bool Success */ - public static function hexColor($check): bool + public static function hexColor(mixed $check): bool { return static::_check($check, '/^#[0-9a-f]{6}$/iD'); } @@ -1687,7 +1824,7 @@ public static function hexColor($check): bool * @param mixed $check The value to check * @return bool Success */ - public static function iban($check): bool + public static function iban(mixed $check): bool { if ( !is_string($check) || @@ -1757,7 +1894,7 @@ protected static function _getDateString(array $value): string $value['hour'], $value['minute'], $value['second'], - $value['microsecond'] + $value['microsecond'], ); } } diff --git a/app/vendor/cakephp/cakephp/src/Validation/ValidationRule.php b/app/vendor/cakephp/cakephp/src/Validation/ValidationRule.php index 6ebd5a749..a71218544 100644 --- a/app/vendor/cakephp/cakephp/src/Validation/ValidationRule.php +++ b/app/vendor/cakephp/cakephp/src/Validation/ValidationRule.php @@ -20,7 +20,9 @@ */ namespace Cake\Validation; -use InvalidArgumentException; +use Cake\ORM\Table; +use Closure; +use ReflectionFunction; /** * ValidationRule object. Represents a validation method, error message and @@ -38,7 +40,7 @@ class ValidationRule /** * The 'on' key * - * @var callable|string + * @var callable|string|null */ protected $_on; @@ -47,14 +49,14 @@ class ValidationRule * * @var bool */ - protected $_last = false; + protected bool $_last = false; /** * The 'message' key * - * @var string + * @var string|null */ - protected $_message; + protected ?string $_message = null; /** * Key under which the object or class where the method to be used for @@ -62,21 +64,21 @@ class ValidationRule * * @var string */ - protected $_provider = 'default'; + protected string $_provider = 'default'; /** * Extra arguments to be passed to the validation method * * @var array */ - protected $_pass = []; + protected array $_pass = []; /** * Constructor * - * @param array $validator [optional] The validator properties + * @param array $validator The validator properties */ - public function __construct(array $validator = []) + public function __construct(array $validator) { $this->_addValidatorProps($validator); } @@ -110,7 +112,7 @@ public function isLast(): bool * @throws \InvalidArgumentException when the supplied rule is not a valid * callable for the configured scope */ - public function process($value, array $providers, array $context = []) + public function process(mixed $value, array $providers, array $context = []): array|string|bool { $context += ['data' => [], 'newRecord' => true, 'providers' => $providers]; @@ -118,33 +120,45 @@ public function process($value, array $providers, array $context = []) return true; } - if (!is_string($this->_rule) && is_callable($this->_rule)) { - $callable = $this->_rule; - $isCallable = true; - } else { + if (is_string($this->_rule)) { $provider = $providers[$this->_provider]; - $callable = [$provider, $this->_rule]; - $isCallable = is_callable($callable); - } + if ( + class_exists(Table::class) + && $provider instanceof Table + && !method_exists($provider, $this->_rule) + && $provider->behaviors()->hasMethod($this->_rule) + ) { + foreach ($provider->behaviors() as $behavior) { + if (in_array($this->_rule, $behavior->implementedMethods(), true)) { + $provider = $behavior; + break; + } + } + } - if (!$isCallable) { - /** @psalm-suppress PossiblyInvalidArgument */ - $message = sprintf( - 'Unable to call method "%s" in "%s" provider for field "%s"', - $this->_rule, - $this->_provider, - $context['field'] - ); - throw new InvalidArgumentException($message); + /** @phpstan-ignore-next-line */ + $callable = [$provider, $this->_rule](...); + } else { + $callable = $this->_rule; + if (!$callable instanceof Closure) { + $callable = $callable(...); + } } + $args = [$value]; + if ($this->_pass) { - $args = array_values(array_merge([$value], $this->_pass, [$context])); - $result = $callable(...$args); - } else { - $result = $callable($value, $context); + $args = array_merge([$value], array_values($this->_pass)); } + $params = (new ReflectionFunction($callable))->getParameters(); + $lastParam = array_pop($params); + if ($lastParam && $lastParam->getName() === 'context') { + $args['context'] = $context; + } + + $result = $callable(...$args); + if ($result === false) { return $this->_message ?: false; } @@ -166,18 +180,19 @@ public function process($value, array $providers, array $context = []) */ protected function _skip(array $context): bool { - if (!is_string($this->_on) && is_callable($this->_on)) { - $function = $this->_on; - - return !$function($context); - } + if (is_string($this->_on)) { + $newRecord = $context['newRecord']; - $newRecord = $context['newRecord']; - if (!empty($this->_on)) { return ($this->_on === Validator::WHEN_CREATE && !$newRecord) || ($this->_on === Validator::WHEN_UPDATE && $newRecord); } + if ($this->_on !== null) { + $function = $this->_on; + + return !$function($context); + } + return false; } @@ -190,7 +205,7 @@ protected function _skip(array $context): bool protected function _addValidatorProps(array $validator = []): void { foreach ($validator as $key => $value) { - if (empty($value)) { + if (!$value) { continue; } if ($key === 'rule' && is_array($value) && !is_callable($value)) { @@ -198,7 +213,7 @@ protected function _addValidatorProps(array $validator = []): void $value = array_shift($value); } if (in_array($key, ['rule', 'on', 'message', 'last', 'provider', 'pass'], true)) { - $this->{"_$key"} = $value; + $this->{"_{$key}"} = $value; } } } @@ -209,7 +224,7 @@ protected function _addValidatorProps(array $validator = []): void * @param string $property The name of the property to retrieve. * @return mixed */ - public function get(string $property) + public function get(string $property): mixed { $property = '_' . $property; diff --git a/app/vendor/cakephp/cakephp/src/Validation/ValidationSet.php b/app/vendor/cakephp/cakephp/src/Validation/ValidationSet.php index 69808d138..6e8d70f53 100644 --- a/app/vendor/cakephp/cakephp/src/Validation/ValidationSet.php +++ b/app/vendor/cakephp/cakephp/src/Validation/ValidationSet.php @@ -18,6 +18,7 @@ use ArrayAccess; use ArrayIterator; +use Cake\Core\Exception\CakeException; use Countable; use IteratorAggregate; use Traversable; @@ -36,10 +37,10 @@ class ValidationSet implements ArrayAccess, IteratorAggregate, Countable * * @var array<\Cake\Validation\ValidationRule> */ - protected $_rules = []; + protected array $_rules = []; /** - * Denotes whether the fieldname key must be present in data array + * Denotes whether the field name key must be present in data array * * @var callable|string|bool */ @@ -57,7 +58,7 @@ class ValidationSet implements ArrayAccess, IteratorAggregate, Countable * * @return callable|string|bool */ - public function isPresenceRequired() + public function isPresenceRequired(): callable|string|bool { return $this->_validatePresent; } @@ -68,7 +69,7 @@ public function isPresenceRequired() * @param callable|string|bool $validatePresent Valid values are true, false, 'create', 'update' or a callable. * @return $this */ - public function requirePresence($validatePresent) + public function requirePresence(callable|string|bool $validatePresent) { $this->_validatePresent = $validatePresent; @@ -80,7 +81,7 @@ public function requirePresence($validatePresent) * * @return callable|string|bool */ - public function isEmptyAllowed() + public function isEmptyAllowed(): callable|string|bool { return $this->_allowEmpty; } @@ -92,7 +93,7 @@ public function isEmptyAllowed() * 'create', 'update' or a callable. * @return $this */ - public function allowEmpty($allowEmpty) + public function allowEmpty(callable|string|bool $allowEmpty) { $this->_allowEmpty = $allowEmpty; @@ -107,11 +108,11 @@ public function allowEmpty($allowEmpty) */ public function rule(string $name): ?ValidationRule { - if (!empty($this->_rules[$name])) { - return $this->_rules[$name]; + if (empty($this->_rules[$name])) { + return null; } - return null; + return $this->_rules[$name]; } /** @@ -124,6 +125,17 @@ public function rules(): array return $this->_rules; } + /** + * Returns whether a validation rule with the given name exists in this set. + * + * @param string $name The name to check + * @return bool + */ + public function has(string $name): bool + { + return array_key_exists($name, $this->_rules); + } + /** * Sets a ValidationRule $rule with a $name * @@ -138,12 +150,16 @@ public function rules(): array * @param string $name The name under which the rule should be set * @param \Cake\Validation\ValidationRule|array $rule The validation rule to be set * @return $this + * @throws \Cake\Core\Exception\CakeException If a rule with the same name already exists */ - public function add(string $name, $rule) + public function add(string $name, ValidationRule|array $rule) { if (!($rule instanceof ValidationRule)) { $rule = new ValidationRule($rule); } + if (array_key_exists($name, $this->_rules)) { + throw new CakeException("A validation rule with the name `{$name}` already exists"); + } $this->_rules[$name] = $rule; return $this; @@ -176,7 +192,7 @@ public function remove(string $name) * @param string $index name of the rule * @return bool */ - public function offsetExists($index): bool + public function offsetExists(mixed $index): bool { return isset($this->_rules[$index]); } @@ -187,7 +203,7 @@ public function offsetExists($index): bool * @param string $index name of the rule * @return \Cake\Validation\ValidationRule */ - public function offsetGet($index): ValidationRule + public function offsetGet(mixed $index): ValidationRule { return $this->_rules[$index]; } @@ -195,13 +211,13 @@ public function offsetGet($index): ValidationRule /** * Sets or replace a validation rule * - * @param string $index name of the rule - * @param \Cake\Validation\ValidationRule|array $rule Rule to add to $index + * @param string $offset name of the rule + * @param \Cake\Validation\ValidationRule|array $value Rule to add to $index * @return void */ - public function offsetSet($index, $rule): void + public function offsetSet(mixed $offset, mixed $value): void { - $this->add($index, $rule); + $this->add($offset, $value); } /** @@ -210,7 +226,7 @@ public function offsetSet($index, $rule): void * @param string $index name of the rule * @return void */ - public function offsetUnset($index): void + public function offsetUnset(mixed $index): void { unset($this->_rules[$index]); } diff --git a/app/vendor/cakephp/cakephp/src/Validation/Validator.php b/app/vendor/cakephp/cakephp/src/Validation/Validator.php index 51764b486..00b08524a 100644 --- a/app/vendor/cakephp/cakephp/src/Validation/Validator.php +++ b/app/vendor/cakephp/cakephp/src/Validation/Validator.php @@ -18,13 +18,13 @@ use ArrayAccess; use ArrayIterator; +use BackedEnum; +use Closure; use Countable; use InvalidArgumentException; use IteratorAggregate; use Psr\Http\Message\UploadedFileInterface; use Traversable; -use function Cake\Core\deprecationWarning; -use function Cake\Core\getTypeName; use function Cake\I18n\__d; /** @@ -33,7 +33,7 @@ * * Implements ArrayAccess to easily modify rules in the set * - * @link https://book.cakephp.org/4/en/core-libraries/validation.html + * @link https://book.cakephp.org/5/en/core-libraries/validation.html * @template-implements \ArrayAccess * @template-implements \IteratorAggregate */ @@ -90,12 +90,8 @@ class Validator implements ArrayAccess, IteratorAggregate, Countable /** * A flag for allowEmptyFor() * - * When an array is given, if it has at least the `name`, `type`, `tmp_name` and `error` keys, - * and the value of `error` is equal to `UPLOAD_ERR_NO_FILE`, the value will be recognized as - * empty. - * - * When an instance of \Psr\Http\Message\UploadedFileInterface is given the - * return value of it's getError() method must be equal to `UPLOAD_ERR_NO_FILE`. + * The return value of \Psr\Http\Message\UploadedFileInterface::getError() + * method must be equal to `UPLOAD_ERR_NO_FILE`. * * @var int */ @@ -137,24 +133,24 @@ class Validator implements ArrayAccess, IteratorAggregate, Countable * * @var array */ - protected $_fields = []; + protected array $_fields = []; /** * An associative array of objects or classes containing methods * used for validation * * @var array - * @psalm-var array + * @phpstan-var array */ - protected $_providers = []; + protected array $_providers = []; /** * An associative array of objects or classes used as a default provider list * * @var array - * @psalm-var array + * @phpstan-var array */ - protected static $_defaultProviders = []; + protected static array $_defaultProviders = []; /** * Contains the validation messages associated with checking the presence @@ -162,14 +158,14 @@ class Validator implements ArrayAccess, IteratorAggregate, Countable * * @var array */ - protected $_presenceMessages = []; + protected array $_presenceMessages = []; /** * Whether to use I18n functions for translating default error messages * * @var bool */ - protected $_useI18n = false; + protected bool $_useI18n; /** * Contains the validation messages associated with checking the emptiness @@ -177,36 +173,37 @@ class Validator implements ArrayAccess, IteratorAggregate, Countable * * @var array */ - protected $_allowEmptyMessages = []; + protected array $_allowEmptyMessages = []; /** * Contains the flags which specify what is empty for each corresponding field. * * @var array */ - protected $_allowEmptyFlags = []; + protected array $_allowEmptyFlags = []; /** * Whether to apply last flag to generated rule(s). * * @var bool */ - protected $_stopOnFailure = false; + protected bool $_stopOnFailure = false; /** * Constructor */ public function __construct() { - $this->_useI18n = function_exists('\Cake\I18n\__d'); + $this->_useI18n ??= function_exists('\Cake\I18n\__d'); $this->_providers = self::$_defaultProviders; + $this->_providers['default'] ??= Validation::class; } /** * Whether to stop validation rule evaluation on the first failed rule. * - * When enabled the first failing rule per field will cause validation to stop. - * When disabled all rules will be run even if there are failures. + * When enabled, the first failing rule per field will cause validation to stop. + * When disabled, all rules will be run even if there are failures. * * @param bool $stopOnFailure If to apply last flag. * @return $this @@ -224,21 +221,6 @@ public function setStopOnFailure(bool $stopOnFailure = true) * @param array $data The data to be checked for errors * @param bool $newRecord whether the data to be validated is new or to be updated. * @return array Array of failed fields - * @deprecated 3.9.0 Renamed to {@link validate()}. - */ - public function errors(array $data, bool $newRecord = true): array - { - deprecationWarning('`Validator::errors()` is deprecated. Use `Validator::validate()` instead.'); - - return $this->validate($data, $newRecord); - } - - /** - * Validates and returns an array of failed fields and their error messages. - * - * @param array $data The data to be checked for errors - * @param bool $newRecord whether the data to be validated is new or to be updated. - * @return array Array of failed fields */ public function validate(array $data, bool $newRecord = true): array { @@ -291,7 +273,7 @@ public function validate(array $data, bool $newRecord = true): array * passed a ValidationSet as second argument, it will replace any other rule set defined * before * - * @param string $name [optional] The fieldname to fetch. + * @param string $name [optional] The field name to fetch. * @param \Cake\Validation\ValidationSet|null $set The set of rules for field * @return \Cake\Validation\ValidationSet */ @@ -324,18 +306,11 @@ public function hasField(string $name): bool * * @param string $name The name under which the provider should be set. * @param object|string $object Provider object or class name. - * @psalm-param object|class-string $object + * @phpstan-param object|class-string $object * @return $this */ - public function setProvider(string $name, $object) + public function setProvider(string $name, object|string $object) { - if (!is_string($object) && !is_object($object)) { - deprecationWarning(sprintf( - 'The provider must be an object or class name string. Got `%s` instead.', - getTypeName($object) - )); - } - $this->_providers[$name] = $object; return $this; @@ -345,31 +320,20 @@ public function setProvider(string $name, $object) * Returns the provider stored under that name if it exists. * * @param string $name The name under which the provider should be set. - * @return object|string|null - * @psalm-return object|class-string|null + * @return object|class-string|null */ - public function getProvider(string $name) + public function getProvider(string $name): object|string|null { - if (isset($this->_providers[$name])) { - return $this->_providers[$name]; - } - if ($name !== 'default') { - return null; - } - - $this->_providers[$name] = new RulesProvider(); - - return $this->_providers[$name]; + return $this->_providers[$name] ?? null; } /** * Returns the default provider stored under that name if it exists. * * @param string $name The name under which the provider should be retrieved. - * @return object|string|null - * @psalm-return object|class-string|null + * @return object|class-string|null */ - public static function getDefaultProvider(string $name) + public static function getDefaultProvider(string $name): object|string|null { return self::$_defaultProviders[$name] ?? null; } @@ -379,18 +343,11 @@ public static function getDefaultProvider(string $name) * * @param string $name The name under which the provider should be set. * @param object|string $object Provider object or class name. - * @psalm-param object|class-string $object + * @phpstan-param object|class-string $object * @return void */ - public static function addDefaultProvider(string $name, $object): void + public static function addDefaultProvider(string $name, object|string $object): void { - if (!is_string($object) && !is_object($object)) { - deprecationWarning(sprintf( - 'The provider must be an object or class name string. Got `%s` instead.', - getTypeName($object) - )); - } - self::$_defaultProviders[$name] = $object; } @@ -420,7 +377,7 @@ public function providers(): array * @param string $field name of the field to check * @return bool */ - public function offsetExists($field): bool + public function offsetExists(mixed $field): bool { return isset($this->_fields[$field]); } @@ -431,7 +388,7 @@ public function offsetExists($field): bool * @param string|int $field name of the field to check * @return \Cake\Validation\ValidationSet */ - public function offsetGet($field): ValidationSet + public function offsetGet(mixed $field): ValidationSet { return $this->field((string)$field); } @@ -439,20 +396,20 @@ public function offsetGet($field): ValidationSet /** * Sets the rule set for a field * - * @param string $field name of the field to set - * @param \Cake\Validation\ValidationSet|array $rules set of rules to apply to field + * @param string $offset name of the field to set + * @param \Cake\Validation\ValidationSet|array $value set of rules to apply to field * @return void */ - public function offsetSet($field, $rules): void + public function offsetSet(mixed $offset, mixed $value): void { - if (!$rules instanceof ValidationSet) { + if (!$value instanceof ValidationSet) { $set = new ValidationSet(); - foreach ($rules as $name => $rule) { + foreach ($value as $name => $rule) { $set->add($name, $rule); } - $rules = $set; + $value = $set; } - $this->_fields[$field] = $rules; + $this->_fields[$offset] = $value; } /** @@ -461,7 +418,7 @@ public function offsetSet($field, $rules): void * @param string $field name of the field to unset * @return void */ - public function offsetUnset($field): void + public function offsetUnset(mixed $field): void { unset($this->_fields[$field]); } @@ -510,7 +467,7 @@ public function count(): int * @throws \InvalidArgumentException If numeric index cannot be resolved to a string one * @return $this */ - public function add(string $field, $name, $rule = []) + public function add(string $field, array|string $name, ValidationRule|array $rule = []) { $validationSet = $this->field($field); @@ -528,19 +485,8 @@ public function add(string $field, $name, $rule = []) ]; } if (!is_string($name)) { - /** @psalm-suppress PossiblyUndefinedMethod */ - $name = $rule['rule']; - if (is_array($name)) { - $name = array_shift($name); - } - - if ($validationSet->offsetExists($name)) { - $message = 'You cannot add a rule without a unique name, already existing rule found: ' . $name; - throw new InvalidArgumentException($message); - } - - deprecationWarning( - 'Adding validation rules without a name key is deprecated. Update rules array to have string keys.' + throw new InvalidArgumentException( + 'You cannot add validation rules without a `name` key. Update rules array to have string keys.', ); } @@ -566,12 +512,16 @@ public function add(string $field, $name, $rule = []) * @param string $field The root field for the nested validator. * @param \Cake\Validation\Validator $validator The nested validator. * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @return $this */ - public function addNested(string $field, Validator $validator, ?string $message = null, $when = null) - { + public function addNested( + string $field, + Validator $validator, + ?string $message = null, + Closure|string|null $when = null, + ) { $extra = array_filter(['message' => $message, 'on' => $when]); $validationSet = $this->field($field); @@ -579,15 +529,16 @@ public function addNested(string $field, Validator $validator, ?string $message if (!is_array($value)) { return false; } - foreach ($this->providers() as $provider) { - /** @psalm-suppress PossiblyNullArgument */ - $validator->setProvider($provider, $this->getProvider($provider)); + foreach ($this->providers() as $name) { + /** @var object|class-string $provider */ + $provider = $this->getProvider($name); + $validator->setProvider($name, $provider); } $errors = $validator->validate($value, $context['newRecord']); $message = $message ? [static::NESTED => $message] : []; - return empty($errors) ? true : $errors + $message; + return $errors === [] ? true : $errors + $message; }]); return $this; @@ -609,12 +560,16 @@ public function addNested(string $field, Validator $validator, ?string $message * @param string $field The root field for the nested validator. * @param \Cake\Validation\Validator $validator The nested validator. * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @return $this */ - public function addNestedMany(string $field, Validator $validator, ?string $message = null, $when = null) - { + public function addNestedMany( + string $field, + Validator $validator, + ?string $message = null, + Closure|string|null $when = null, + ) { $extra = array_filter(['message' => $message, 'on' => $when]); $validationSet = $this->field($field); @@ -622,9 +577,10 @@ public function addNestedMany(string $field, Validator $validator, ?string $mess if (!is_array($value)) { return false; } - foreach ($this->providers() as $provider) { - /** @psalm-suppress PossiblyNullArgument */ - $validator->setProvider($provider, $this->getProvider($provider)); + foreach ($this->providers() as $name) { + /** @var object|class-string $provider */ + $provider = $this->getProvider($name); + $validator->setProvider($name, $provider); } $errors = []; foreach ($value as $i => $row) { @@ -632,14 +588,14 @@ public function addNestedMany(string $field, Validator $validator, ?string $mess return false; } $check = $validator->validate($row, $context['newRecord']); - if (!empty($check)) { + if ($check) { $errors[$i] = $check; } } $message = $message ? [static::NESTED => $message] : []; - return empty($errors) ? true : $errors + $message; + return $errors === [] ? true : $errors + $message; }]); return $this; @@ -683,13 +639,13 @@ public function remove(string $field, ?string $rule = null) * setting takes precedence over group settings. * * @param array|string $field the name of the field or list of fields. - * @param callable|string|bool $mode Valid values are true, false, 'create', 'update'. - * If a callable is passed then the field will be required only when the callback + * @param \Closure|string|bool $mode Valid values are true, false, 'create', 'update'. + * If a Closure is passed then the field will be required only when the callback * returns true. * @param string|null $message The message to show if the field presence validation fails. * @return $this */ - public function requirePresence($field, $mode = true, ?string $message = null) + public function requirePresence(array|string $field, Closure|string|bool $mode = true, ?string $message = null) { $defaults = [ 'mode' => $mode, @@ -697,11 +653,12 @@ public function requirePresence($field, $mode = true, ?string $message = null) ]; if (!is_array($field)) { - $field = $this->_convertValidatorToArray($field, $defaults); + $field = $this->_convertValidatorToArray((string)$field, $defaults); } foreach ($field as $fieldName => $setting) { - $settings = $this->_convertValidatorToArray($fieldName, $defaults, $setting); + $settings = $this->_convertValidatorToArray((string)$fieldName, $defaults, $setting); + /** @var string $fieldName */ $fieldName = current(array_keys($settings)); $this->field((string)$fieldName)->requirePresence($settings[$fieldName]['mode']); @@ -713,108 +670,10 @@ public function requirePresence($field, $mode = true, ?string $message = null) return $this; } - /** - * Allows a field to be empty. You can also pass array. - * Using an array will let you provide the following keys: - * - * - `when` individual when condition for field - * - 'message' individual message for field - * - * You can also set when and message for all passed fields, the individual setting - * takes precedence over group settings. - * - * This is the opposite of notEmpty() which requires a field to not be empty. - * By using $mode equal to 'create' or 'update', you can allow fields to be empty - * when records are first created, or when they are updated. - * - * ### Example: - * - * ``` - * // Email can be empty - * $validator->allowEmpty('email'); - * - * // Email can be empty on create - * $validator->allowEmpty('email', Validator::WHEN_CREATE); - * - * // Email can be empty on update - * $validator->allowEmpty('email', Validator::WHEN_UPDATE); - * - * // Email and subject can be empty on update - * $validator->allowEmpty(['email', 'subject'], Validator::WHEN_UPDATE; - * - * // Email can be always empty, subject and content can be empty on update. - * $validator->allowEmpty( - * [ - * 'email' => [ - * 'when' => true - * ], - * 'content' => [ - * 'message' => 'Content cannot be empty' - * ], - * 'subject' - * ], - * Validator::WHEN_UPDATE - * ); - * ``` - * - * It is possible to conditionally allow emptiness on a field by passing a callback - * as a second argument. The callback will receive the validation context array as - * argument: - * - * ``` - * $validator->allowEmpty('email', function ($context) { - * return !$context['newRecord'] || $context['data']['role'] === 'admin'; - * }); - * ``` - * - * This method will correctly detect empty file uploads and date/time/datetime fields. - * - * Because this and `notEmpty()` modify the same internal state, the last - * method called will take precedence. - * - * @deprecated 3.7.0 Use {@link allowEmptyString()}, {@link allowEmptyArray()}, {@link allowEmptyFile()}, - * {@link allowEmptyDate()}, {@link allowEmptyTime()}, {@link allowEmptyDateTime()} or {@link allowEmptyFor()} instead. - * @param array|string $field the name of the field or a list of fields - * @param callable|string|bool $when Indicates when the field is allowed to be empty - * Valid values are true (always), 'create', 'update'. If a callable is passed then - * the field will allowed to be empty only when the callback returns true. - * @param string|null $message The message to show if the field is not - * @return $this - */ - public function allowEmpty($field, $when = true, $message = null) - { - deprecationWarning( - 'allowEmpty() is deprecated. ' - . 'Use allowEmptyString(), allowEmptyArray(), allowEmptyFile(), allowEmptyDate(), allowEmptyTime(), ' - . 'allowEmptyDateTime() or allowEmptyFor() instead.' - ); - - $defaults = [ - 'when' => $when, - 'message' => $message, - ]; - if (!is_array($field)) { - $field = $this->_convertValidatorToArray($field, $defaults); - } - - foreach ($field as $fieldName => $setting) { - $settings = $this->_convertValidatorToArray($fieldName, $defaults, $setting); - $fieldName = array_keys($settings)[0]; - $this->allowEmptyFor( - $fieldName, - static::EMPTY_ALL, - $settings[$fieldName]['when'], - $settings[$fieldName]['message'] - ); - } - - return $this; - } - /** * Low-level method to indicate that a field can be empty. * - * This method should generally not be used and instead you should + * This method should generally not be used, and instead you should * use: * * - `allowEmptyString()` @@ -873,15 +732,19 @@ public function allowEmpty($field, $when = true, $message = null) * @param string $field The name of the field. * @param int|null $flags A bitmask of EMPTY_* flags which specify what is empty. * If no flags/bitmask is provided only `null` will be allowed as empty value. - * @param callable|string|bool $when Indicates when the field is allowed to be empty - * Valid values are true, false, 'create', 'update'. If a callable is passed then + * @param \Closure|string|bool $when Indicates when the field is allowed to be empty + * Valid values are true, false, 'create', 'update'. If a Closure is passed then * the field will allowed to be empty only when the callback returns true. * @param string|null $message The message to show if the field is not * @since 3.7.0 * @return $this */ - public function allowEmptyFor(string $field, ?int $flags = null, $when = true, ?string $message = null) - { + public function allowEmptyFor( + string $field, + ?int $flags = null, + Closure|string|bool $when = true, + ?string $message = null, + ) { $this->field($field)->allowEmpty($when); if ($message) { $this->_allowEmptyMessages[$field] = $message; @@ -900,13 +763,13 @@ public function allowEmptyFor(string $field, ?int $flags = null, $when = true, ? * * @param string $field The name of the field. * @param string|null $message The message to show if the field is not - * @param callable|string|bool $when Indicates when the field is allowed to be empty - * Valid values are true, false, 'create', 'update'. If a callable is passed then + * @param \Closure|string|bool $when Indicates when the field is allowed to be empty + * Valid values are true, false, 'create', 'update'. If a Closure is passed then * the field will allowed to be empty only when the callback returns true. * @return $this * @see \Cake\Validation\Validator::allowEmptyFor() For detail usage */ - public function allowEmptyString(string $field, ?string $message = null, $when = true) + public function allowEmptyString(string $field, ?string $message = null, Closure|string|bool $when = true) { return $this->allowEmptyFor($field, self::EMPTY_STRING, $when, $message); } @@ -918,15 +781,15 @@ public function allowEmptyString(string $field, ?string $message = null, $when = * * @param string $field The name of the field. * @param string|null $message The message to show if the field is empty. - * @param callable|string|bool $when Indicates when the field is not allowed + * @param \Closure|string|bool $when Indicates when the field is not allowed * to be empty. Valid values are false (never), 'create', 'update'. If a - * callable is passed then the field will be required to be not empty when + * Closure is passed then the field will be required to be not empty when * the callback returns true. * @return $this * @see \Cake\Validation\Validator::allowEmptyString() * @since 3.8.0 */ - public function notEmptyString(string $field, ?string $message = null, $when = false) + public function notEmptyString(string $field, ?string $message = null, Closure|string|bool $when = false) { $when = $this->invertWhenClause($when); @@ -941,14 +804,14 @@ public function notEmptyString(string $field, ?string $message = null, $when = f * * @param string $field The name of the field. * @param string|null $message The message to show if the field is not - * @param callable|string|bool $when Indicates when the field is allowed to be empty - * Valid values are true, false, 'create', 'update'. If a callable is passed then + * @param \Closure|string|bool $when Indicates when the field is allowed to be empty + * Valid values are true, false, 'create', 'update'. If a Closure is passed then * the field will allowed to be empty only when the callback returns true. * @return $this * @since 3.7.0 * @see \Cake\Validation\Validator::allowEmptyFor() for examples. */ - public function allowEmptyArray(string $field, ?string $message = null, $when = true) + public function allowEmptyArray(string $field, ?string $message = null, Closure|string|bool $when = true) { return $this->allowEmptyFor($field, self::EMPTY_STRING | self::EMPTY_ARRAY, $when, $message); } @@ -960,14 +823,14 @@ public function allowEmptyArray(string $field, ?string $message = null, $when = * * @param string $field The name of the field. * @param string|null $message The message to show if the field is empty. - * @param callable|string|bool $when Indicates when the field is not allowed + * @param \Closure|string|bool $when Indicates when the field is not allowed * to be empty. Valid values are false (never), 'create', 'update'. If a - * callable is passed then the field will be required to be not empty when + * Closure is passed then the field will be required to be not empty when * the callback returns true. * @return $this * @see \Cake\Validation\Validator::allowEmptyArray() */ - public function notEmptyArray(string $field, ?string $message = null, $when = false) + public function notEmptyArray(string $field, ?string $message = null, Closure|string|bool $when = false) { $when = $this->invertWhenClause($when); @@ -983,14 +846,14 @@ public function notEmptyArray(string $field, ?string $message = null, $when = fa * * @param string $field The name of the field. * @param string|null $message The message to show if the field is not - * @param callable|string|bool $when Indicates when the field is allowed to be empty - * Valid values are true, 'create', 'update'. If a callable is passed then + * @param \Closure|string|bool $when Indicates when the field is allowed to be empty + * Valid values are true, 'create', 'update'. If a Closure is passed then * the field will allowed to be empty only when the callback returns true. * @return $this * @since 3.7.0 * @see \Cake\Validation\Validator::allowEmptyFor() For detail usage */ - public function allowEmptyFile(string $field, ?string $message = null, $when = true) + public function allowEmptyFile(string $field, ?string $message = null, Closure|string|bool $when = true) { return $this->allowEmptyFor($field, self::EMPTY_FILE, $when, $message); } @@ -1002,15 +865,15 @@ public function allowEmptyFile(string $field, ?string $message = null, $when = t * * @param string $field The name of the field. * @param string|null $message The message to show if the field is empty. - * @param callable|string|bool $when Indicates when the field is not allowed + * @param \Closure|string|bool $when Indicates when the field is not allowed * to be empty. Valid values are false (never), 'create', 'update'. If a - * callable is passed then the field will be required to be not empty when + * Closure is passed then the field will be required to be not empty when * the callback returns true. * @return $this * @since 3.8.0 * @see \Cake\Validation\Validator::allowEmptyFile() */ - public function notEmptyFile(string $field, ?string $message = null, $when = false) + public function notEmptyFile(string $field, ?string $message = null, Closure|string|bool $when = false) { $when = $this->invertWhenClause($when); @@ -1025,13 +888,13 @@ public function notEmptyFile(string $field, ?string $message = null, $when = fal * * @param string $field The name of the field. * @param string|null $message The message to show if the field is not - * @param callable|string|bool $when Indicates when the field is allowed to be empty - * Valid values are true, false, 'create', 'update'. If a callable is passed then + * @param \Closure|string|bool $when Indicates when the field is allowed to be empty + * Valid values are true, false, 'create', 'update'. If a Closure is passed then * the field will allowed to be empty only when the callback returns true. * @return $this * @see \Cake\Validation\Validator::allowEmptyFor() for examples */ - public function allowEmptyDate(string $field, ?string $message = null, $when = true) + public function allowEmptyDate(string $field, ?string $message = null, Closure|string|bool $when = true) { return $this->allowEmptyFor($field, self::EMPTY_STRING | self::EMPTY_DATE, $when, $message); } @@ -1041,14 +904,14 @@ public function allowEmptyDate(string $field, ?string $message = null, $when = t * * @param string $field The name of the field. * @param string|null $message The message to show if the field is empty. - * @param callable|string|bool $when Indicates when the field is not allowed + * @param \Closure|string|bool $when Indicates when the field is not allowed * to be empty. Valid values are false (never), 'create', 'update'. If a - * callable is passed then the field will be required to be not empty when + * Closure is passed then the field will be required to be not empty when * the callback returns true. * @return $this * @see \Cake\Validation\Validator::allowEmptyDate() for examples */ - public function notEmptyDate(string $field, ?string $message = null, $when = false) + public function notEmptyDate(string $field, ?string $message = null, Closure|string|bool $when = false) { $when = $this->invertWhenClause($when); @@ -1066,14 +929,14 @@ public function notEmptyDate(string $field, ?string $message = null, $when = fal * * @param string $field The name of the field. * @param string|null $message The message to show if the field is not - * @param callable|string|bool $when Indicates when the field is allowed to be empty - * Valid values are true, false, 'create', 'update'. If a callable is passed then + * @param \Closure|string|bool $when Indicates when the field is allowed to be empty + * Valid values are true, false, 'create', 'update'. If a Closure is passed then * the field will allowed to be empty only when the callback returns true. * @return $this * @since 3.7.0 * @see \Cake\Validation\Validator::allowEmptyFor() for examples. */ - public function allowEmptyTime(string $field, ?string $message = null, $when = true) + public function allowEmptyTime(string $field, ?string $message = null, Closure|string|bool $when = true) { return $this->allowEmptyFor($field, self::EMPTY_STRING | self::EMPTY_TIME, $when, $message); } @@ -1085,15 +948,15 @@ public function allowEmptyTime(string $field, ?string $message = null, $when = t * * @param string $field The name of the field. * @param string|null $message The message to show if the field is empty. - * @param callable|string|bool $when Indicates when the field is not allowed + * @param \Closure|string|bool $when Indicates when the field is not allowed * to be empty. Valid values are false (never), 'create', 'update'. If a - * callable is passed then the field will be required to be not empty when + * Closure is passed then the field will be required to be not empty when * the callback returns true. * @return $this * @since 3.8.0 * @see \Cake\Validation\Validator::allowEmptyTime() */ - public function notEmptyTime(string $field, ?string $message = null, $when = false) + public function notEmptyTime(string $field, ?string $message = null, Closure|string|bool $when = false) { $when = $this->invertWhenClause($when); @@ -1111,14 +974,14 @@ public function notEmptyTime(string $field, ?string $message = null, $when = fal * * @param string $field The name of the field. * @param string|null $message The message to show if the field is not - * @param callable|string|bool $when Indicates when the field is allowed to be empty - * Valid values are true, false, 'create', 'update'. If a callable is passed then + * @param \Closure|string|bool $when Indicates when the field is allowed to be empty + * Valid values are true, false, 'create', 'update'. If a Closure is passed then * the field will allowed to be empty only when the callback returns false. * @return $this * @since 3.7.0 * @see \Cake\Validation\Validator::allowEmptyFor() for examples. */ - public function allowEmptyDateTime(string $field, ?string $message = null, $when = true) + public function allowEmptyDateTime(string $field, ?string $message = null, Closure|string|bool $when = true) { return $this->allowEmptyFor($field, self::EMPTY_STRING | self::EMPTY_DATE | self::EMPTY_TIME, $when, $message); } @@ -1130,15 +993,15 @@ public function allowEmptyDateTime(string $field, ?string $message = null, $when * * @param string $field The name of the field. * @param string|null $message The message to show if the field is empty. - * @param callable|string|bool $when Indicates when the field is not allowed + * @param \Closure|string|bool $when Indicates when the field is not allowed * to be empty. Valid values are false (never), 'create', 'update'. If a - * callable is passed then the field will be required to be not empty when + * Closure is passed then the field will be required to be not empty when * the callback returns true. * @return $this * @since 3.8.0 * @see \Cake\Validation\Validator::allowEmptyDateTime() */ - public function notEmptyDateTime(string $field, ?string $message = null, $when = false) + public function notEmptyDateTime(string $field, ?string $message = null, Closure|string|bool $when = false) { $when = $this->invertWhenClause($when); @@ -1148,150 +1011,42 @@ public function notEmptyDateTime(string $field, ?string $message = null, $when = /** * Converts validator to fieldName => $settings array * - * @param string|int $fieldName name of field + * @param string $fieldName name of field * @param array $defaults default settings - * @param array|string $settings settings from data - * @return array + * @param array|string|int $settings settings from data + * @return array> * @throws \InvalidArgumentException */ - protected function _convertValidatorToArray($fieldName, array $defaults = [], $settings = []): array - { - if (is_int($settings)) { - $settings = (string)$settings; - } - if (is_string($settings)) { - $fieldName = $settings; - $settings = []; - } + protected function _convertValidatorToArray( + string $fieldName, + array $defaults = [], + array|string|int $settings = [], + ): array { if (!is_array($settings)) { - throw new InvalidArgumentException( - sprintf('Invalid settings for "%s". Settings must be an array.', $fieldName) - ); + $fieldName = (string)$settings; + $settings = []; } $settings += $defaults; return [$fieldName => $settings]; } - /** - * Sets a field to require a non-empty value. You can also pass array. - * Using an array will let you provide the following keys: - * - * - `when` individual when condition for field - * - `message` individual error message for field - * - * You can also set `when` and `message` for all passed fields, the individual setting - * takes precedence over group settings. - * - * This is the opposite of `allowEmpty()` which allows a field to be empty. - * By using $mode equal to 'create' or 'update', you can make fields required - * when records are first created, or when they are updated. - * - * ### Example: - * - * ``` - * $message = 'This field cannot be empty'; - * - * // Email cannot be empty - * $validator->notEmpty('email'); - * - * // Email can be empty on update, but not create - * $validator->notEmpty('email', $message, 'create'); - * - * // Email can be empty on create, but required on update. - * $validator->notEmpty('email', $message, Validator::WHEN_UPDATE); - * - * // Email and title can be empty on create, but are required on update. - * $validator->notEmpty(['email', 'title'], $message, Validator::WHEN_UPDATE); - * - * // Email can be empty on create, title must always be not empty - * $validator->notEmpty( - * [ - * 'email', - * 'title' => [ - * 'when' => true, - * 'message' => 'Title cannot be empty' - * ] - * ], - * $message, - * Validator::WHEN_UPDATE - * ); - * ``` - * - * It is possible to conditionally disallow emptiness on a field by passing a callback - * as the third argument. The callback will receive the validation context array as - * argument: - * - * ``` - * $validator->notEmpty('email', 'Email is required', function ($context) { - * return $context['newRecord'] && $context['data']['role'] !== 'admin'; - * }); - * ``` - * - * Because this and `allowEmpty()` modify the same internal state, the last - * method called will take precedence. - * - * @deprecated 3.7.0 Use {@link notEmptyString()}, {@link notEmptyArray()}, {@link notEmptyFile()}, - * {@link notEmptyDate()}, {@link notEmptyTime()} or {@link notEmptyDateTime()} instead. - * @param array|string $field the name of the field or list of fields - * @param string|null $message The message to show if the field is not - * @param callable|string|bool $when Indicates when the field is not allowed - * to be empty. Valid values are true (always), 'create', 'update'. If a - * callable is passed then the field will allowed to be empty only when - * the callback returns false. - * @return $this - */ - public function notEmpty($field, ?string $message = null, $when = false) - { - deprecationWarning( - 'notEmpty() is deprecated. ' - . 'Use notEmptyString(), notEmptyArray(), notEmptyFile(), notEmptyDate(), notEmptyTime() ' - . 'or notEmptyDateTime() instead.' - ); - - $defaults = [ - 'when' => $when, - 'message' => $message, - ]; - - if (!is_array($field)) { - $field = $this->_convertValidatorToArray($field, $defaults); - } - - foreach ($field as $fieldName => $setting) { - $settings = $this->_convertValidatorToArray($fieldName, $defaults, $setting); - $fieldName = current(array_keys($settings)); - - $whenSetting = $this->invertWhenClause($settings[$fieldName]['when']); - - $this->field((string)$fieldName)->allowEmpty($whenSetting); - $this->_allowEmptyFlags[$fieldName] = static::EMPTY_ALL; - if ($settings[$fieldName]['message']) { - $this->_allowEmptyMessages[$fieldName] = $settings[$fieldName]['message']; - } - } - - return $this; - } - /** * Invert a when clause for creating notEmpty rules * - * @param callable|string|bool $when Indicates when the field is not allowed + * @param \Closure|string|bool $when Indicates when the field is not allowed * to be empty. Valid values are true (always), 'create', 'update'. If a - * callable is passed then the field will allowed to be empty only when + * Closure is passed then the field will allowed to be empty only when * the callback returns false. - * @return callable|string|bool + * @return \Closure|string|bool */ - protected function invertWhenClause($when) + protected function invertWhenClause(Closure|string|bool $when): Closure|string|bool { if ($when === static::WHEN_CREATE || $when === static::WHEN_UPDATE) { return $when === static::WHEN_CREATE ? static::WHEN_UPDATE : static::WHEN_CREATE; } - if (is_callable($when)) { - return function ($context) use ($when) { - return !$when($context); - }; + if ($when instanceof Closure) { + return fn($context) => !$when($context); } return $when; @@ -1302,13 +1057,21 @@ protected function invertWhenClause($when) * * @param string $field The field you want to apply the rule to. * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @see \Cake\Validation\Validation::notBlank() * @return $this */ - public function notBlank(string $field, ?string $message = null, $when = null) + public function notBlank(string $field, ?string $message = null, Closure|string|null $when = null) { + if ($message === null) { + if (!$this->_useI18n) { + $message = 'This field cannot be left empty'; + } else { + $message = __d('cake', 'This field cannot be left empty'); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'notBlank', $extra + [ @@ -1321,13 +1084,21 @@ public function notBlank(string $field, ?string $message = null, $when = null) * * @param string $field The field you want to apply the rule to. * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @see \Cake\Validation\Validation::alphaNumeric() * @return $this */ - public function alphaNumeric(string $field, ?string $message = null, $when = null) + public function alphaNumeric(string $field, ?string $message = null, Closure|string|null $when = null) { + if ($message === null) { + if (!$this->_useI18n) { + $message = 'The provided value must be alphanumeric'; + } else { + $message = __d('cake', 'The provided value must be alphanumeric'); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'alphaNumeric', $extra + [ @@ -1340,13 +1111,21 @@ public function alphaNumeric(string $field, ?string $message = null, $when = nul * * @param string $field The field you want to apply the rule to. * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @see \Cake\Validation\Validation::notAlphaNumeric() * @return $this */ - public function notAlphaNumeric(string $field, ?string $message = null, $when = null) + public function notAlphaNumeric(string $field, ?string $message = null, Closure|string|null $when = null) { + if ($message === null) { + if (!$this->_useI18n) { + $message = 'The provided value must not be alphanumeric'; + } else { + $message = __d('cake', 'The provided value must not be alphanumeric'); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'notAlphaNumeric', $extra + [ @@ -1359,13 +1138,21 @@ public function notAlphaNumeric(string $field, ?string $message = null, $when = * * @param string $field The field you want to apply the rule to. * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @see \Cake\Validation\Validation::asciiAlphaNumeric() * @return $this */ - public function asciiAlphaNumeric(string $field, ?string $message = null, $when = null) + public function asciiAlphaNumeric(string $field, ?string $message = null, Closure|string|null $when = null) { + if ($message === null) { + if (!$this->_useI18n) { + $message = 'The provided value must be ASCII-alphanumeric'; + } else { + $message = __d('cake', 'The provided value must be ASCII-alphanumeric'); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'asciiAlphaNumeric', $extra + [ @@ -1378,13 +1165,21 @@ public function asciiAlphaNumeric(string $field, ?string $message = null, $when * * @param string $field The field you want to apply the rule to. * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @see \Cake\Validation\Validation::notAlphaNumeric() * @return $this */ - public function notAsciiAlphaNumeric(string $field, ?string $message = null, $when = null) + public function notAsciiAlphaNumeric(string $field, ?string $message = null, Closure|string|null $when = null) { + if ($message === null) { + if (!$this->_useI18n) { + $message = 'The provided value must not be ASCII-alphanumeric'; + } else { + $message = __d('cake', 'The provided value must not be ASCII-alphanumeric'); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'notAsciiAlphaNumeric', $extra + [ @@ -1398,21 +1193,45 @@ public function notAsciiAlphaNumeric(string $field, ?string $message = null, $wh * @param string $field The field you want to apply the rule to. * @param array $range The inclusive minimum and maximum length you want permitted. * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @see \Cake\Validation\Validation::alphaNumeric() * @return $this * @throws \InvalidArgumentException */ - public function lengthBetween(string $field, array $range, ?string $message = null, $when = null) - { + public function lengthBetween( + string $field, + array $range, + ?string $message = null, + Closure|string|null $when = null, + ) { if (count($range) !== 2) { throw new InvalidArgumentException('The $range argument requires 2 numbers'); } + $lowerBound = array_shift($range); + $upperBound = array_shift($range); + + if ($message === null) { + if (!$this->_useI18n) { + $message = sprintf( + 'The length of the provided value must be between `%s` and `%s`, inclusively', + $lowerBound, + $upperBound, + ); + } else { + $message = __d( + 'cake', + 'The length of the provided value must be between `{0}` and `{1}`, inclusively', + $lowerBound, + $upperBound, + ); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'lengthBetween', $extra + [ - 'rule' => ['lengthBetween', array_shift($range), array_shift($range)], + 'rule' => ['lengthBetween', $lowerBound, $upperBound], ]); } @@ -1420,16 +1239,50 @@ public function lengthBetween(string $field, array $range, ?string $message = nu * Add a credit card rule to a field. * * @param string $field The field you want to apply the rule to. - * @param string $type The type of cards you want to allow. Defaults to 'all'. + * @param array|string $type The type of cards you want to allow. Defaults to 'all'. * You can also supply an array of accepted card types. e.g `['mastercard', 'visa', 'amex']` * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @see \Cake\Validation\Validation::creditCard() * @return $this */ - public function creditCard(string $field, string $type = 'all', ?string $message = null, $when = null) - { + public function creditCard( + string $field, + array|string $type = 'all', + ?string $message = null, + Closure|string|null $when = null, + ) { + if (is_array($type)) { + $typeEnumeration = implode(', ', $type); + } else { + $typeEnumeration = $type; + } + + if ($message === null) { + if (!$this->_useI18n) { + if ($type === 'all') { + $message = 'The provided value must be a valid credit card number of any type'; + } else { + $message = sprintf( + 'The provided value must be a valid credit card number of these types: `%s`', + $typeEnumeration, + ); + } + } elseif ($type === 'all') { + $message = __d( + 'cake', + 'The provided value must be a valid credit card number of any type', + ); + } else { + $message = __d( + 'cake', + 'The provided value must be a valid credit card number of these types: `{0}`', + $typeEnumeration, + ); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'creditCard', $extra + [ @@ -1443,13 +1296,25 @@ public function creditCard(string $field, string $type = 'all', ?string $message * @param string $field The field you want to apply the rule to. * @param float|int $value The value user data must be greater than. * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @see \Cake\Validation\Validation::comparison() * @return $this */ - public function greaterThan(string $field, $value, ?string $message = null, $when = null) - { + public function greaterThan( + string $field, + float|int $value, + ?string $message = null, + Closure|string|null $when = null, + ) { + if ($message === null) { + if (!$this->_useI18n) { + $message = sprintf('The provided value must be greater than `%s`', $value); + } else { + $message = __d('cake', 'The provided value must be greater than `{0}`', $value); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'greaterThan', $extra + [ @@ -1463,13 +1328,25 @@ public function greaterThan(string $field, $value, ?string $message = null, $whe * @param string $field The field you want to apply the rule to. * @param float|int $value The value user data must be greater than or equal to. * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @see \Cake\Validation\Validation::comparison() * @return $this */ - public function greaterThanOrEqual(string $field, $value, ?string $message = null, $when = null) - { + public function greaterThanOrEqual( + string $field, + float|int $value, + ?string $message = null, + Closure|string|null $when = null, + ) { + if ($message === null) { + if (!$this->_useI18n) { + $message = sprintf('The provided value must be greater than or equal to `%s`', $value); + } else { + $message = __d('cake', 'The provided value must be greater than or equal to `{0}`', $value); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'greaterThanOrEqual', $extra + [ @@ -1483,13 +1360,25 @@ public function greaterThanOrEqual(string $field, $value, ?string $message = nul * @param string $field The field you want to apply the rule to. * @param float|int $value The value user data must be less than. * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @see \Cake\Validation\Validation::comparison() * @return $this */ - public function lessThan(string $field, $value, ?string $message = null, $when = null) - { + public function lessThan( + string $field, + float|int $value, + ?string $message = null, + Closure|string|null $when = null, + ) { + if ($message === null) { + if (!$this->_useI18n) { + $message = sprintf('The provided value must be less than `%s`', $value); + } else { + $message = __d('cake', 'The provided value must be less than `{0}`', $value); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'lessThan', $extra + [ @@ -1503,13 +1392,25 @@ public function lessThan(string $field, $value, ?string $message = null, $when = * @param string $field The field you want to apply the rule to. * @param float|int $value The value user data must be less than or equal to. * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @see \Cake\Validation\Validation::comparison() * @return $this */ - public function lessThanOrEqual(string $field, $value, ?string $message = null, $when = null) - { + public function lessThanOrEqual( + string $field, + float|int $value, + ?string $message = null, + Closure|string|null $when = null, + ) { + if ($message === null) { + if (!$this->_useI18n) { + $message = sprintf('The provided value must be less than or equal to `%s`', $value); + } else { + $message = __d('cake', 'The provided value must be less than or equal to `{0}`', $value); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'lessThanOrEqual', $extra + [ @@ -1521,15 +1422,27 @@ public function lessThanOrEqual(string $field, $value, ?string $message = null, * Add a equal to comparison rule to a field. * * @param string $field The field you want to apply the rule to. - * @param float|int $value The value user data must be equal to. + * @param mixed $value The value user data must be equal to. * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @see \Cake\Validation\Validation::comparison() * @return $this */ - public function equals(string $field, $value, ?string $message = null, $when = null) - { + public function equals( + string $field, + mixed $value, + ?string $message = null, + Closure|string|null $when = null, + ) { + if ($message === null) { + if (!$this->_useI18n) { + $message = sprintf('The provided value must be equal to `%s`', $value); + } else { + $message = __d('cake', 'The provided value must be equal to `{0}`', $value); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'equals', $extra + [ @@ -1541,15 +1454,27 @@ public function equals(string $field, $value, ?string $message = null, $when = n * Add a not equal to comparison rule to a field. * * @param string $field The field you want to apply the rule to. - * @param float|int $value The value user data must be not be equal to. + * @param mixed $value The value user data must be not be equal to. * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @see \Cake\Validation\Validation::comparison() * @return $this */ - public function notEquals(string $field, $value, ?string $message = null, $when = null) - { + public function notEquals( + string $field, + mixed $value, + ?string $message = null, + Closure|string|null $when = null, + ) { + if ($message === null) { + if (!$this->_useI18n) { + $message = sprintf('The provided value must not be equal to `%s`', $value); + } else { + $message = __d('cake', 'The provided value must not be equal to `{0}`', $value); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'notEquals', $extra + [ @@ -1565,13 +1490,25 @@ public function notEquals(string $field, $value, ?string $message = null, $when * @param string $field The field you want to apply the rule to. * @param string $secondField The field you want to compare against. * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @see \Cake\Validation\Validation::compareFields() * @return $this */ - public function sameAs(string $field, string $secondField, ?string $message = null, $when = null) - { + public function sameAs( + string $field, + string $secondField, + ?string $message = null, + Closure|string|null $when = null, + ) { + if ($message === null) { + if (!$this->_useI18n) { + $message = sprintf('The provided value must be same as `%s`', $secondField); + } else { + $message = __d('cake', 'The provided value must be same as `{0}`', $secondField); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'sameAs', $extra + [ @@ -1585,14 +1522,26 @@ public function sameAs(string $field, string $secondField, ?string $message = nu * @param string $field The field you want to apply the rule to. * @param string $secondField The field you want to compare against. * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @see \Cake\Validation\Validation::compareFields() * @return $this * @since 3.6.0 */ - public function notSameAs(string $field, string $secondField, ?string $message = null, $when = null) - { + public function notSameAs( + string $field, + string $secondField, + ?string $message = null, + Closure|string|null $when = null, + ) { + if ($message === null) { + if (!$this->_useI18n) { + $message = sprintf('The provided value must not be same as `%s`', $secondField); + } else { + $message = __d('cake', 'The provided value must not be same as `{0}`', $secondField); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'notSameAs', $extra + [ @@ -1606,14 +1555,30 @@ public function notSameAs(string $field, string $secondField, ?string $message = * @param string $field The field you want to apply the rule to. * @param string $secondField The field you want to compare against. * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @see \Cake\Validation\Validation::compareFields() * @return $this * @since 3.6.0 */ - public function equalToField(string $field, string $secondField, ?string $message = null, $when = null) - { + public function equalToField( + string $field, + string $secondField, + ?string $message = null, + Closure|string|null $when = null, + ) { + if ($message === null) { + if (!$this->_useI18n) { + $message = sprintf('The provided value must be equal to the one of field `%s`', $secondField); + } else { + $message = __d( + 'cake', + 'The provided value must be equal to the one of field `{0}`', + $secondField, + ); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'equalToField', $extra + [ @@ -1627,14 +1592,30 @@ public function equalToField(string $field, string $secondField, ?string $messag * @param string $field The field you want to apply the rule to. * @param string $secondField The field you want to compare against. * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @see \Cake\Validation\Validation::compareFields() * @return $this * @since 3.6.0 */ - public function notEqualToField(string $field, string $secondField, ?string $message = null, $when = null) - { + public function notEqualToField( + string $field, + string $secondField, + ?string $message = null, + Closure|string|null $when = null, + ) { + if ($message === null) { + if (!$this->_useI18n) { + $message = sprintf('The provided value must not be equal to the one of field `%s`', $secondField); + } else { + $message = __d( + 'cake', + 'The provided value must not be equal to the one of field `{0}`', + $secondField, + ); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'notEqualToField', $extra + [ @@ -1648,14 +1629,30 @@ public function notEqualToField(string $field, string $secondField, ?string $mes * @param string $field The field you want to apply the rule to. * @param string $secondField The field you want to compare against. * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @see \Cake\Validation\Validation::compareFields() * @return $this * @since 3.6.0 */ - public function greaterThanField(string $field, string $secondField, ?string $message = null, $when = null) - { + public function greaterThanField( + string $field, + string $secondField, + ?string $message = null, + Closure|string|null $when = null, + ) { + if ($message === null) { + if (!$this->_useI18n) { + $message = sprintf('The provided value must be greater than the one of field `%s`', $secondField); + } else { + $message = __d( + 'cake', + 'The provided value must be greater than the one of field `{0}`', + $secondField, + ); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'greaterThanField', $extra + [ @@ -1669,14 +1666,33 @@ public function greaterThanField(string $field, string $secondField, ?string $me * @param string $field The field you want to apply the rule to. * @param string $secondField The field you want to compare against. * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @see \Cake\Validation\Validation::compareFields() * @return $this * @since 3.6.0 */ - public function greaterThanOrEqualToField(string $field, string $secondField, ?string $message = null, $when = null) - { + public function greaterThanOrEqualToField( + string $field, + string $secondField, + ?string $message = null, + Closure|string|null $when = null, + ) { + if ($message === null) { + if (!$this->_useI18n) { + $message = sprintf( + 'The provided value must be greater than or equal to the one of field `%s`', + $secondField, + ); + } else { + $message = __d( + 'cake', + 'The provided value must be greater than or equal to the one of field `{0}`', + $secondField, + ); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'greaterThanOrEqualToField', $extra + [ @@ -1690,14 +1706,30 @@ public function greaterThanOrEqualToField(string $field, string $secondField, ?s * @param string $field The field you want to apply the rule to. * @param string $secondField The field you want to compare against. * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @see \Cake\Validation\Validation::compareFields() * @return $this * @since 3.6.0 */ - public function lessThanField(string $field, string $secondField, ?string $message = null, $when = null) - { + public function lessThanField( + string $field, + string $secondField, + ?string $message = null, + Closure|string|null $when = null, + ) { + if ($message === null) { + if (!$this->_useI18n) { + $message = sprintf('The provided value must be less than the one of field `%s`', $secondField); + } else { + $message = __d( + 'cake', + 'The provided value must be less than the one of field `{0}`', + $secondField, + ); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'lessThanField', $extra + [ @@ -1711,14 +1743,33 @@ public function lessThanField(string $field, string $secondField, ?string $messa * @param string $field The field you want to apply the rule to. * @param string $secondField The field you want to compare against. * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @see \Cake\Validation\Validation::compareFields() * @return $this * @since 3.6.0 */ - public function lessThanOrEqualToField(string $field, string $secondField, ?string $message = null, $when = null) - { + public function lessThanOrEqualToField( + string $field, + string $secondField, + ?string $message = null, + Closure|string|null $when = null, + ) { + if ($message === null) { + if (!$this->_useI18n) { + $message = sprintf( + 'The provided value must be less than or equal to the one of field `%s`', + $secondField, + ); + } else { + $message = __d( + 'cake', + 'The provided value must be less than or equal to the one of field `{0}`', + $secondField, + ); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'lessThanOrEqualToField', $extra + [ @@ -1726,41 +1777,54 @@ public function lessThanOrEqualToField(string $field, string $secondField, ?stri ]); } - /** - * Add a rule to check if a field contains non alpha numeric characters. - * - * @param string $field The field you want to apply the rule to. - * @param int $limit The minimum number of non-alphanumeric fields required. - * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns - * true when the validation rule should be applied. - * @see \Cake\Validation\Validation::containsNonAlphaNumeric() - * @return $this - * @deprecated 4.0.0 Use {@link notAlphaNumeric()} instead. Will be removed in 5.0 - */ - public function containsNonAlphaNumeric(string $field, int $limit = 1, ?string $message = null, $when = null) - { - deprecationWarning('Validator::containsNonAlphaNumeric() is deprecated. Use notAlphaNumeric() instead.'); - $extra = array_filter(['on' => $when, 'message' => $message]); - - return $this->add($field, 'containsNonAlphaNumeric', $extra + [ - 'rule' => ['containsNonAlphaNumeric', $limit], - ]); - } - /** * Add a date format validation rule to a field. * + * Years are valid from 0001 to 2999. + * + * ### Formats: + * + * - `ymd` 2006-12-27 or 06-12-27 separators can be a space, period, dash, forward slash + * - `dmy` 27-12-2006 or 27-12-06 separators can be a space, period, dash, forward slash + * - `mdy` 12-27-2006 or 12-27-06 separators can be a space, period, dash, forward slash + * - `dMy` 27 December 2006 or 27 Dec 2006 + * - `Mdy` December 27, 2006 or Dec 27, 2006 comma is optional + * - `My` December 2006 or Dec 2006 + * - `my` 12/2006 or 12/06 separators can be a space, period, dash, forward slash + * - `ym` 2006/12 or 06/12 separators can be a space, period, dash, forward slash + * - `y` 2006 just the year without any separators + * * @param string $field The field you want to apply the rule to. * @param array $formats A list of accepted date formats. * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @see \Cake\Validation\Validation::date() * @return $this */ - public function date(string $field, array $formats = ['ymd'], ?string $message = null, $when = null) - { + public function date( + string $field, + array $formats = ['ymd'], + ?string $message = null, + Closure|string|null $when = null, + ) { + $formatEnumeration = implode(', ', $formats); + + if ($message === null) { + if (!$this->_useI18n) { + $message = sprintf( + 'The provided value must be a date of one of these formats: `%s`', + $formatEnumeration, + ); + } else { + $message = __d( + 'cake', + 'The provided value must be a date of one of these formats: `{0}`', + $formatEnumeration, + ); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'date', $extra + [ @@ -1771,16 +1835,58 @@ public function date(string $field, array $formats = ['ymd'], ?string $message = /** * Add a date time format validation rule to a field. * + * All values matching the "date" core validation rule, and the "time" one will be valid + * + * Years are valid from 0001 to 2999. + * + * ### Formats: + * + * - `ymd` 2006-12-27 or 06-12-27 separators can be a space, period, dash, forward slash + * - `dmy` 27-12-2006 or 27-12-06 separators can be a space, period, dash, forward slash + * - `mdy` 12-27-2006 or 12-27-06 separators can be a space, period, dash, forward slash + * - `dMy` 27 December 2006 or 27 Dec 2006 + * - `Mdy` December 27, 2006 or Dec 27, 2006 comma is optional + * - `My` December 2006 or Dec 2006 + * - `my` 12/2006 or 12/06 separators can be a space, period, dash, forward slash + * - `ym` 2006/12 or 06/12 separators can be a space, period, dash, forward slash + * - `y` 2006 just the year without any separators + * + * Time is validated as 24hr (HH:MM[:SS][.FFFFFF]) or am/pm ([H]H:MM[a|p]m) + * + * Seconds and fractional seconds (microseconds) are allowed but optional + * in 24hr format. + * * @param string $field The field you want to apply the rule to. * @param array $formats A list of accepted date formats. * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @see \Cake\Validation\Validation::datetime() * @return $this */ - public function dateTime(string $field, array $formats = ['ymd'], ?string $message = null, $when = null) - { + public function dateTime( + string $field, + array $formats = ['ymd'], + ?string $message = null, + Closure|string|null $when = null, + ) { + $formatEnumeration = implode(', ', $formats); + + if ($message === null) { + if (!$this->_useI18n) { + $message = sprintf( + 'The provided value must be a date and time of one of these formats: `%s`', + $formatEnumeration, + ); + } else { + $message = __d( + 'cake', + 'The provided value must be a date and time of one of these formats: `{0}`', + $formatEnumeration, + ); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'dateTime', $extra + [ @@ -1793,13 +1899,21 @@ public function dateTime(string $field, array $formats = ['ymd'], ?string $messa * * @param string $field The field you want to apply the rule to. * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @see \Cake\Validation\Validation::time() * @return $this */ - public function time(string $field, ?string $message = null, $when = null) + public function time(string $field, ?string $message = null, Closure|string|null $when = null) { + if ($message === null) { + if (!$this->_useI18n) { + $message = 'The provided value must be a time'; + } else { + $message = __d('cake', 'The provided value must be a time'); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'time', $extra + [ @@ -1813,13 +1927,25 @@ public function time(string $field, ?string $message = null, $when = null) * @param string $field The field you want to apply the rule to. * @param string $type Parser type, one out of 'date', 'time', and 'datetime' * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @see \Cake\Validation\Validation::localizedTime() * @return $this */ - public function localizedTime(string $field, string $type = 'datetime', ?string $message = null, $when = null) - { + public function localizedTime( + string $field, + string $type = 'datetime', + ?string $message = null, + Closure|string|null $when = null, + ) { + if ($message === null) { + if (!$this->_useI18n) { + $message = 'The provided value must be a localized time, date or date and time'; + } else { + $message = __d('cake', 'The provided value must be a localized time, date or date and time'); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'localizedTime', $extra + [ @@ -1832,13 +1958,21 @@ public function localizedTime(string $field, string $type = 'datetime', ?string * * @param string $field The field you want to apply the rule to. * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @see \Cake\Validation\Validation::boolean() * @return $this */ - public function boolean(string $field, ?string $message = null, $when = null) + public function boolean(string $field, ?string $message = null, Closure|string|null $when = null) { + if ($message === null) { + if (!$this->_useI18n) { + $message = 'The provided value must be a boolean'; + } else { + $message = __d('cake', 'The provided value must be a boolean'); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'boolean', $extra + [ @@ -1852,13 +1986,38 @@ public function boolean(string $field, ?string $message = null, $when = null) * @param string $field The field you want to apply the rule to. * @param int|null $places The number of decimal places to require. * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @see \Cake\Validation\Validation::decimal() * @return $this */ - public function decimal(string $field, ?int $places = null, ?string $message = null, $when = null) - { + public function decimal( + string $field, + ?int $places = null, + ?string $message = null, + Closure|string|null $when = null, + ) { + if ($message === null) { + if (!$this->_useI18n) { + if ($places === null) { + $message = 'The provided value must be decimal with any number of decimal places, including none'; + } else { + $message = sprintf('The provided value must be decimal with `%s` decimal places', $places); + } + } elseif ($places === null) { + $message = __d( + 'cake', + 'The provided value must be decimal with any number of decimal places, including none', + ); + } else { + $message = __d( + 'cake', + 'The provided value must be decimal with `{0}` decimal places', + $places, + ); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'decimal', $extra + [ @@ -1872,13 +2031,25 @@ public function decimal(string $field, ?int $places = null, ?string $message = n * @param string $field The field you want to apply the rule to. * @param bool $checkMX Whether to check the MX records. * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @see \Cake\Validation\Validation::email() * @return $this */ - public function email(string $field, bool $checkMX = false, ?string $message = null, $when = null) - { + public function email( + string $field, + bool $checkMX = false, + ?string $message = null, + Closure|string|null $when = null, + ) { + if ($message === null) { + if (!$this->_useI18n) { + $message = 'The provided value must be an e-mail address'; + } else { + $message = __d('cake', 'The provided value must be an e-mail address'); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'email', $extra + [ @@ -1886,6 +2057,47 @@ public function email(string $field, bool $checkMX = false, ?string $message = n ]); } + /** + * Add a backed enum validation rule to a field. + * + * @param string $field The field you want to apply the rule to. + * @param class-string<\BackedEnum> $enumClassName The valid backed enum class name. + * @param string|null $message The error message when the rule fails. + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns + * true when the validation rule should be applied. + * @return $this + * @see \Cake\Validation\Validation::enum() + * @since 5.0.3 + */ + public function enum( + string $field, + string $enumClassName, + ?string $message = null, + Closure|string|null $when = null, + ) { + if (!in_array(BackedEnum::class, (array)class_implements($enumClassName), true)) { + throw new InvalidArgumentException( + 'The `$enumClassName` argument must be the classname of a valid backed enum.', + ); + } + + if ($message === null) { + $cases = array_map(fn($case) => $case->value, $enumClassName::cases()); + $caseOptions = implode('`, `', $cases); + if (!$this->_useI18n) { + $message = sprintf('The provided value must be one of `%s`', $caseOptions); + } else { + $message = __d('cake', 'The provided value must be one of `{0}`', $caseOptions); + } + } + + $extra = array_filter(['on' => $when, 'message' => $message]); + + return $this->add($field, 'enum', $extra + [ + 'rule' => ['enum', $enumClassName], + ]); + } + /** * Add an IP validation rule to a field. * @@ -1893,13 +2105,21 @@ public function email(string $field, bool $checkMX = false, ?string $message = n * * @param string $field The field you want to apply the rule to. * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @see \Cake\Validation\Validation::ip() * @return $this */ - public function ip(string $field, ?string $message = null, $when = null) + public function ip(string $field, ?string $message = null, Closure|string|null $when = null) { + if ($message === null) { + if (!$this->_useI18n) { + $message = 'The provided value must be an IP address'; + } else { + $message = __d('cake', 'The provided value must be an IP address'); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'ip', $extra + [ @@ -1912,13 +2132,21 @@ public function ip(string $field, ?string $message = null, $when = null) * * @param string $field The field you want to apply the rule to. * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @see \Cake\Validation\Validation::ip() * @return $this */ - public function ipv4(string $field, ?string $message = null, $when = null) + public function ipv4(string $field, ?string $message = null, Closure|string|null $when = null) { + if ($message === null) { + if (!$this->_useI18n) { + $message = 'The provided value must be an IPv4 address'; + } else { + $message = __d('cake', 'The provided value must be an IPv4 address'); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'ipv4', $extra + [ @@ -1931,13 +2159,21 @@ public function ipv4(string $field, ?string $message = null, $when = null) * * @param string $field The field you want to apply the rule to. * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @see \Cake\Validation\Validation::ip() * @return $this */ - public function ipv6(string $field, ?string $message = null, $when = null) + public function ipv6(string $field, ?string $message = null, Closure|string|null $when = null) { + if ($message === null) { + if (!$this->_useI18n) { + $message = 'The provided value must be an IPv6 address'; + } else { + $message = __d('cake', 'The provided value must be an IPv6 address'); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'ipv6', $extra + [ @@ -1951,13 +2187,21 @@ public function ipv6(string $field, ?string $message = null, $when = null) * @param string $field The field you want to apply the rule to. * @param int $min The minimum length required. * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @see \Cake\Validation\Validation::minLength() * @return $this */ - public function minLength(string $field, int $min, ?string $message = null, $when = null) + public function minLength(string $field, int $min, ?string $message = null, Closure|string|null $when = null) { + if ($message === null) { + if (!$this->_useI18n) { + $message = sprintf('The provided value must be at least `%s` characters long', $min); + } else { + $message = __d('cake', 'The provided value must be at least `{0}` characters long', $min); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'minLength', $extra + [ @@ -1971,13 +2215,21 @@ public function minLength(string $field, int $min, ?string $message = null, $whe * @param string $field The field you want to apply the rule to. * @param int $min The minimum length required. * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @see \Cake\Validation\Validation::minLengthBytes() * @return $this */ - public function minLengthBytes(string $field, int $min, ?string $message = null, $when = null) + public function minLengthBytes(string $field, int $min, ?string $message = null, Closure|string|null $when = null) { + if ($message === null) { + if (!$this->_useI18n) { + $message = sprintf('The provided value must be at least `%s` bytes long', $min); + } else { + $message = __d('cake', 'The provided value must be at least `{0}` bytes long', $min); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'minLengthBytes', $extra + [ @@ -1991,13 +2243,21 @@ public function minLengthBytes(string $field, int $min, ?string $message = null, * @param string $field The field you want to apply the rule to. * @param int $max The maximum length allowed. * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @see \Cake\Validation\Validation::maxLength() * @return $this */ - public function maxLength(string $field, int $max, ?string $message = null, $when = null) + public function maxLength(string $field, int $max, ?string $message = null, Closure|string|null $when = null) { + if ($message === null) { + if (!$this->_useI18n) { + $message = sprintf('The provided value must be at most `%s` characters long', $max); + } else { + $message = __d('cake', 'The provided value must be at most `{0}` characters long', $max); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'maxLength', $extra + [ @@ -2011,13 +2271,21 @@ public function maxLength(string $field, int $max, ?string $message = null, $whe * @param string $field The field you want to apply the rule to. * @param int $max The maximum length allowed. * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @see \Cake\Validation\Validation::maxLengthBytes() * @return $this */ - public function maxLengthBytes(string $field, int $max, ?string $message = null, $when = null) + public function maxLengthBytes(string $field, int $max, ?string $message = null, Closure|string|null $when = null) { + if ($message === null) { + if (!$this->_useI18n) { + $message = sprintf('The provided value must be at most `%s` bytes long', $max); + } else { + $message = __d('cake', 'The provided value must be at most `{0}` bytes long', $max); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'maxLengthBytes', $extra + [ @@ -2030,13 +2298,21 @@ public function maxLengthBytes(string $field, int $max, ?string $message = null, * * @param string $field The field you want to apply the rule to. * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @see \Cake\Validation\Validation::numeric() * @return $this */ - public function numeric(string $field, ?string $message = null, $when = null) + public function numeric(string $field, ?string $message = null, Closure|string|null $when = null) { + if ($message === null) { + if (!$this->_useI18n) { + $message = 'The provided value must be numeric'; + } else { + $message = __d('cake', 'The provided value must be numeric'); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'numeric', $extra + [ @@ -2049,13 +2325,21 @@ public function numeric(string $field, ?string $message = null, $when = null) * * @param string $field The field you want to apply the rule to. * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @see \Cake\Validation\Validation::naturalNumber() * @return $this */ - public function naturalNumber(string $field, ?string $message = null, $when = null) + public function naturalNumber(string $field, ?string $message = null, Closure|string|null $when = null) { + if ($message === null) { + if (!$this->_useI18n) { + $message = 'The provided value must be a natural number'; + } else { + $message = __d('cake', 'The provided value must be a natural number'); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'naturalNumber', $extra + [ @@ -2068,13 +2352,21 @@ public function naturalNumber(string $field, ?string $message = null, $when = nu * * @param string $field The field you want to apply the rule to. * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @see \Cake\Validation\Validation::naturalNumber() * @return $this */ - public function nonNegativeInteger(string $field, ?string $message = null, $when = null) + public function nonNegativeInteger(string $field, ?string $message = null, Closure|string|null $when = null) { + if ($message === null) { + if (!$this->_useI18n) { + $message = 'The provided value must be a non-negative integer'; + } else { + $message = __d('cake', 'The provided value must be a non-negative integer'); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'nonNegativeInteger', $extra + [ @@ -2088,21 +2380,41 @@ public function nonNegativeInteger(string $field, ?string $message = null, $when * @param string $field The field you want to apply the rule to. * @param array $range The inclusive upper and lower bounds of the valid range. * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @see \Cake\Validation\Validation::range() * @return $this * @throws \InvalidArgumentException */ - public function range(string $field, array $range, ?string $message = null, $when = null) + public function range(string $field, array $range, ?string $message = null, Closure|string|null $when = null) { if (count($range) !== 2) { throw new InvalidArgumentException('The $range argument requires 2 numbers'); } + $lowerBound = array_shift($range); + $upperBound = array_shift($range); + + if ($message === null) { + if (!$this->_useI18n) { + $message = sprintf( + 'The provided value must be between `%s` and `%s`, inclusively', + $lowerBound, + $upperBound, + ); + } else { + $message = __d( + 'cake', + 'The provided value must be between `{0}` and `{1}`, inclusively', + $lowerBound, + $upperBound, + ); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'range', $extra + [ - 'rule' => ['range', array_shift($range), array_shift($range)], + 'rule' => ['range', $lowerBound, $upperBound], ]); } @@ -2113,13 +2425,21 @@ public function range(string $field, array $range, ?string $message = null, $whe * * @param string $field The field you want to apply the rule to. * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @see \Cake\Validation\Validation::url() * @return $this */ - public function url(string $field, ?string $message = null, $when = null) + public function url(string $field, ?string $message = null, Closure|string|null $when = null) { + if ($message === null) { + if (!$this->_useI18n) { + $message = 'The provided value must be a URL'; + } else { + $message = __d('cake', 'The provided value must be a URL'); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'url', $extra + [ @@ -2134,13 +2454,21 @@ public function url(string $field, ?string $message = null, $when = null) * * @param string $field The field you want to apply the rule to. * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @see \Cake\Validation\Validation::url() * @return $this */ - public function urlWithProtocol(string $field, ?string $message = null, $when = null) + public function urlWithProtocol(string $field, ?string $message = null, Closure|string|null $when = null) { + if ($message === null) { + if (!$this->_useI18n) { + $message = 'The provided value must be a URL with protocol'; + } else { + $message = __d('cake', 'The provided value must be a URL with protocol'); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'urlWithProtocol', $extra + [ @@ -2154,13 +2482,27 @@ public function urlWithProtocol(string $field, ?string $message = null, $when = * @param string $field The field you want to apply the rule to. * @param array $list The list of valid options. * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @see \Cake\Validation\Validation::inList() * @return $this */ - public function inList(string $field, array $list, ?string $message = null, $when = null) + public function inList(string $field, array $list, ?string $message = null, Closure|string|null $when = null) { + $listEnumeration = implode(', ', $list); + + if ($message === null) { + if (!$this->_useI18n) { + $message = sprintf('The provided value must be one of: `%s`', $listEnumeration); + } else { + $message = __d( + 'cake', + 'The provided value must be one of: `{0}`', + $listEnumeration, + ); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'inList', $extra + [ @@ -2173,13 +2515,21 @@ public function inList(string $field, array $list, ?string $message = null, $whe * * @param string $field The field you want to apply the rule to. * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @see \Cake\Validation\Validation::uuid() * @return $this */ - public function uuid(string $field, ?string $message = null, $when = null) + public function uuid(string $field, ?string $message = null, Closure|string|null $when = null) { + if ($message === null) { + if (!$this->_useI18n) { + $message = 'The provided value must be a UUID'; + } else { + $message = __d('cake', 'The provided value must be a UUID'); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'uuid', $extra + [ @@ -2193,13 +2543,25 @@ public function uuid(string $field, ?string $message = null, $when = null) * @param string $field The field you want to apply the rule to. * @param array $options An array of options. * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @see \Cake\Validation\Validation::uploadedFile() For options * @return $this */ - public function uploadedFile(string $field, array $options, ?string $message = null, $when = null) - { + public function uploadedFile( + string $field, + array $options, + ?string $message = null, + Closure|string|null $when = null, + ) { + if ($message === null) { + if (!$this->_useI18n) { + $message = 'The provided value must be an uploaded file'; + } else { + $message = __d('cake', 'The provided value must be an uploaded file'); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'uploadedFile', $extra + [ @@ -2214,13 +2576,21 @@ public function uploadedFile(string $field, array $options, ?string $message = n * * @param string $field The field you want to apply the rule to. * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. - * @see \Cake\Validation\Validation::uuid() + * @see \Cake\Validation\Validation::geoCoordinate() * @return $this */ - public function latLong(string $field, ?string $message = null, $when = null) + public function latLong(string $field, ?string $message = null, Closure|string|null $when = null) { + if ($message === null) { + if (!$this->_useI18n) { + $message = 'The provided value must be a latitude/longitude coordinate'; + } else { + $message = __d('cake', 'The provided value must be a latitude/longitude coordinate'); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'latLong', $extra + [ @@ -2233,13 +2603,21 @@ public function latLong(string $field, ?string $message = null, $when = null) * * @param string $field The field you want to apply the rule to. * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @see \Cake\Validation\Validation::latitude() * @return $this */ - public function latitude(string $field, ?string $message = null, $when = null) + public function latitude(string $field, ?string $message = null, Closure|string|null $when = null) { + if ($message === null) { + if (!$this->_useI18n) { + $message = 'The provided value must be a latitude'; + } else { + $message = __d('cake', 'The provided value must be a latitude'); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'latitude', $extra + [ @@ -2252,13 +2630,21 @@ public function latitude(string $field, ?string $message = null, $when = null) * * @param string $field The field you want to apply the rule to. * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @see \Cake\Validation\Validation::longitude() * @return $this */ - public function longitude(string $field, ?string $message = null, $when = null) + public function longitude(string $field, ?string $message = null, Closure|string|null $when = null) { + if ($message === null) { + if (!$this->_useI18n) { + $message = 'The provided value must be a longitude'; + } else { + $message = __d('cake', 'The provided value must be a longitude'); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'longitude', $extra + [ @@ -2271,13 +2657,21 @@ public function longitude(string $field, ?string $message = null, $when = null) * * @param string $field The field you want to apply the rule to. * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @see \Cake\Validation\Validation::ascii() * @return $this */ - public function ascii(string $field, ?string $message = null, $when = null) + public function ascii(string $field, ?string $message = null, Closure|string|null $when = null) { + if ($message === null) { + if (!$this->_useI18n) { + $message = 'The provided value must be ASCII bytes only'; + } else { + $message = __d('cake', 'The provided value must be ASCII bytes only'); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'ascii', $extra + [ @@ -2290,13 +2684,21 @@ public function ascii(string $field, ?string $message = null, $when = null) * * @param string $field The field you want to apply the rule to. * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @see \Cake\Validation\Validation::utf8() * @return $this */ - public function utf8(string $field, ?string $message = null, $when = null) + public function utf8(string $field, ?string $message = null, Closure|string|null $when = null) { + if ($message === null) { + if (!$this->_useI18n) { + $message = 'The provided value must be UTF-8 bytes only'; + } else { + $message = __d('cake', 'The provided value must be UTF-8 bytes only'); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'utf8', $extra + [ @@ -2311,13 +2713,21 @@ public function utf8(string $field, ?string $message = null, $when = null) * * @param string $field The field you want to apply the rule to. * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @see \Cake\Validation\Validation::utf8() * @return $this */ - public function utf8Extended(string $field, ?string $message = null, $when = null) + public function utf8Extended(string $field, ?string $message = null, Closure|string|null $when = null) { + if ($message === null) { + if (!$this->_useI18n) { + $message = 'The provided value must be 3 and 4 byte UTF-8 sequences only'; + } else { + $message = __d('cake', 'The provided value must be 3 and 4 byte UTF-8 sequences only'); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'utf8Extended', $extra + [ @@ -2330,13 +2740,21 @@ public function utf8Extended(string $field, ?string $message = null, $when = nul * * @param string $field The field you want to apply the rule to. * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @see \Cake\Validation\Validation::isInteger() * @return $this */ - public function integer(string $field, ?string $message = null, $when = null) + public function integer(string $field, ?string $message = null, Closure|string|null $when = null) { + if ($message === null) { + if (!$this->_useI18n) { + $message = 'The provided value must be an integer'; + } else { + $message = __d('cake', 'The provided value must be an integer'); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'integer', $extra + [ @@ -2349,13 +2767,21 @@ public function integer(string $field, ?string $message = null, $when = null) * * @param string $field The field you want to apply the rule to. * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @see \Cake\Validation\Validation::isArray() * @return $this */ - public function array(string $field, ?string $message = null, $when = null) + public function array(string $field, ?string $message = null, Closure|string|null $when = null) { + if ($message === null) { + if (!$this->_useI18n) { + $message = 'The provided value must be an array'; + } else { + $message = __d('cake', 'The provided value must be an array'); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'array', $extra + [ @@ -2363,40 +2789,25 @@ public function array(string $field, ?string $message = null, $when = null) ]); } - /** - * Add a validation rule to ensure that a field contains an array. - * - * @param string $field The field you want to apply the rule to. - * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns - * true when the validation rule should be applied. - * @see \Cake\Validation\Validation::isArray() - * @return $this - * @deprecated 4.5.0 Use Validator::array() instead. - */ - public function isArray(string $field, ?string $message = null, $when = null) - { - deprecationWarning('`Validator::isArray()` is deprecated, use `Validator::array()` instead'); - - $extra = array_filter(['on' => $when, 'message' => $message]); - - return $this->add($field, 'isArray', $extra + [ - 'rule' => 'isArray', - ]); - } - /** * Add a validation rule to ensure that a field contains a scalar. * * @param string $field The field you want to apply the rule to. * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @see \Cake\Validation\Validation::isScalar() * @return $this */ - public function scalar(string $field, ?string $message = null, $when = null) + public function scalar(string $field, ?string $message = null, Closure|string|null $when = null) { + if ($message === null) { + $message = 'The provided value must be scalar'; + if ($this->_useI18n) { + $message = __d('cake', 'The provided value must be scalar'); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'scalar', $extra + [ @@ -2409,13 +2820,20 @@ public function scalar(string $field, ?string $message = null, $when = null) * * @param string $field The field you want to apply the rule to. * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @see \Cake\Validation\Validation::hexColor() * @return $this */ - public function hexColor(string $field, ?string $message = null, $when = null) + public function hexColor(string $field, ?string $message = null, Closure|string|null $when = null) { + if ($message === null) { + $message = 'The provided value must be a hex color'; + if ($this->_useI18n) { + $message = __d('cake', 'The provided value must be a hex color'); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'hexColor', $extra + [ @@ -2430,13 +2848,24 @@ public function hexColor(string $field, ?string $message = null, $when = null) * @param array $options The options for the validator. Includes the options defined in * \Cake\Validation\Validation::multiple() and the `caseInsensitive` parameter. * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @see \Cake\Validation\Validation::multiple() * @return $this */ - public function multipleOptions(string $field, array $options = [], ?string $message = null, $when = null) - { + public function multipleOptions( + string $field, + array $options = [], + ?string $message = null, + Closure|string|null $when = null, + ) { + if ($message === null) { + $message = 'The provided value must be a set of multiple options'; + if ($this->_useI18n) { + $message = __d('cake', 'The provided value must be a set of multiple options'); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); $caseInsensitive = $options['caseInsensitive'] ?? false; unset($options['caseInsensitive']); @@ -2453,13 +2882,21 @@ public function multipleOptions(string $field, array $options = [], ?string $mes * @param string $field The field you want to apply the rule to. * @param int $count The number of elements the array should at least have * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @see \Cake\Validation\Validation::numElements() * @return $this */ - public function hasAtLeast(string $field, int $count, ?string $message = null, $when = null) + public function hasAtLeast(string $field, int $count, ?string $message = null, Closure|string|null $when = null) { + if ($message === null) { + if (!$this->_useI18n) { + $message = sprintf('The provided value must have at least `%s` elements', $count); + } else { + $message = __d('cake', 'The provided value must have at least `{0}` elements', $count); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'hasAtLeast', $extra + [ @@ -2480,13 +2917,21 @@ public function hasAtLeast(string $field, int $count, ?string $message = null, $ * @param string $field The field you want to apply the rule to. * @param int $count The number maximum amount of elements the field should have * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @see \Cake\Validation\Validation::numElements() * @return $this */ - public function hasAtMost(string $field, int $count, ?string $message = null, $when = null) + public function hasAtMost(string $field, int $count, ?string $message = null, Closure|string|null $when = null) { + if ($message === null) { + if (!$this->_useI18n) { + $message = sprintf('The provided value must have at most `%s` elements', $count); + } else { + $message = __d('cake', 'The provided value must have at most `{0}` elements', $count); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'hasAtMost', $extra + [ @@ -2540,12 +2985,20 @@ public function isPresenceRequired(string $field, bool $newRecord): bool * @param string $field Field name. * @param string $regex Regular expression. * @param string|null $message The error message when the rule fails. - * @param callable|string|null $when Either 'create' or 'update' or a callable that returns + * @param \Closure|string|null $when Either 'create' or 'update' or a Closure that returns * true when the validation rule should be applied. * @return $this */ - public function regex(string $field, string $regex, ?string $message = null, $when = null) + public function regex(string $field, string $regex, ?string $message = null, Closure|string|null $when = null) { + if ($message === null) { + if (!$this->_useI18n) { + $message = sprintf('The provided value must match against the pattern `%s`', $regex); + } else { + $message = __d('cake', 'The provided value must match against the pattern `{0}`', $regex); + } + } + $extra = array_filter(['on' => $when, 'message' => $message]); return $this->add($field, 'regex', $extra + [ @@ -2565,12 +3018,15 @@ public function getRequiredMessage(string $field): ?string return null; } - $defaultMessage = 'This field is required'; - if ($this->_useI18n) { - $defaultMessage = __d('cake', 'This field is required'); + if (isset($this->_presenceMessages[$field])) { + return $this->_presenceMessages[$field]; } - return $this->_presenceMessages[$field] ?? $defaultMessage; + if (!$this->_useI18n) { + return 'This field is required'; + } + + return __d('cake', 'This field is required'); } /** @@ -2585,18 +3041,21 @@ public function getNotEmptyMessage(string $field): ?string return null; } - $defaultMessage = 'This field cannot be left empty'; - if ($this->_useI18n) { - $defaultMessage = __d('cake', 'This field cannot be left empty'); - } - foreach ($this->_fields[$field] as $rule) { if ($rule->get('rule') === 'notBlank' && $rule->get('message')) { return $rule->get('message'); } } - return $this->_allowEmptyMessages[$field] ?? $defaultMessage; + if (isset($this->_allowEmptyMessages[$field])) { + return $this->_allowEmptyMessages[$field]; + } + + if (!$this->_useI18n) { + return 'This field cannot be left empty'; + } + + return __d('cake', 'This field cannot be left empty'); } /** @@ -2611,7 +3070,7 @@ protected function _checkPresence(ValidationSet $field, array $context): bool { $required = $field->isPresenceRequired(); - if (!is_string($required) && is_callable($required)) { + if ($required instanceof Closure) { return !$required($context); } @@ -2635,7 +3094,7 @@ protected function _canBeEmpty(ValidationSet $field, array $context): bool { $allowed = $field->isEmptyAllowed(); - if (!is_string($allowed) && is_callable($allowed)) { + if ($allowed instanceof Closure) { return $allowed($context); } @@ -2648,18 +3107,6 @@ protected function _canBeEmpty(ValidationSet $field, array $context): bool return (bool)$allowed; } - /** - * Returns true if the field is empty in the passed data array - * - * @param mixed $data Value to check against. - * @return bool - * @deprecated 3.7.0 Use {@link isEmpty()} instead - */ - protected function _fieldIsEmpty($data): bool - { - return $this->isEmpty($data, static::EMPTY_ALL); - } - /** * Returns true if the field is empty in the passed data array * @@ -2667,7 +3114,7 @@ protected function _fieldIsEmpty($data): bool * @param int $flags A bitmask of EMPTY_* flags which specify what is empty * @return bool */ - protected function isEmpty($data, int $flags): bool + protected function isEmpty(mixed $data, int $flags): bool { if ($data === null) { return true; @@ -2683,14 +3130,6 @@ protected function isEmpty($data, int $flags): bool } if (is_array($data)) { - if ( - ($flags & self::EMPTY_FILE) - && isset($data['name'], $data['type'], $data['tmp_name'], $data['error']) - && (int)$data['error'] === UPLOAD_ERR_NO_FILE - ) { - return true; - } - $allFieldsAreEmpty = true; foreach ($data as $field) { if ($field !== null && $field !== '') { @@ -2734,17 +3173,13 @@ protected function isEmpty($data, int $flags): bool protected function _processRules(string $field, ValidationSet $rules, array $data, bool $newRecord): array { $errors = []; - // Loading default provider in case there is none - $this->getProvider('default'); - $message = 'The provided value is invalid'; - if ($this->_useI18n) { + if (!$this->_useI18n) { + $message = 'The provided value is invalid'; + } else { $message = __d('cake', 'The provided value is invalid'); } - /** - * @var \Cake\Validation\ValidationRule $rule - */ foreach ($rules as $name => $rule) { $result = $rule->process($data[$field], $this->_providers, compact('newRecord', 'data', 'field')); if ($result === true) { diff --git a/app/vendor/cakephp/cakephp/src/Validation/ValidatorAwareTrait.php b/app/vendor/cakephp/cakephp/src/Validation/ValidatorAwareTrait.php index 610ffcc83..93e10c634 100644 --- a/app/vendor/cakephp/cakephp/src/Validation/ValidatorAwareTrait.php +++ b/app/vendor/cakephp/cakephp/src/Validation/ValidatorAwareTrait.php @@ -17,7 +17,7 @@ namespace Cake\Validation; use Cake\Event\EventDispatcherInterface; -use RuntimeException; +use InvalidArgumentException; /** * A trait that provides methods for building and @@ -32,7 +32,7 @@ * - `DEFAULT_VALIDATOR` - The default validator name. * - `VALIDATOR_PROVIDER_NAME ` - The provider name the including class is assigned * in validators. - * - `BUILD_VALIDATOR_EVENT` - The name of the event to be triggred when validators + * - `BUILD_VALIDATOR_EVENT` - The name of the event to be triggered when validators * are built. * * If the including class also implements events the `Model.buildValidator` event @@ -45,21 +45,21 @@ trait ValidatorAwareTrait * * @var string */ - protected $_validatorClass = Validator::class; + protected string $_validatorClass = Validator::class; /** * A list of validation objects indexed by name * * @var array<\Cake\Validation\Validator> */ - protected $_validators = []; + protected array $_validators = []; /** * Returns the validation rules tagged with $name. It is possible to have * multiple different named validation sets, this is useful when you need * to use varying rules when saving from different routines in your system. * - * If a validator has not been set earlier, this method will build a valiator + * If a validator has not been set earlier, this method will build a validator * using a method inside your class. * * For example, if you wish to create a validation set called 'forSubscription', @@ -107,14 +107,14 @@ public function getValidator(?string $name = null): Validator * * @param string $name The name of the validation set to create. * @return \Cake\Validation\Validator - * @throws \RuntimeException + * @throws \InvalidArgumentException */ protected function createValidator(string $name): Validator { $method = 'validation' . ucfirst($name); if (!$this->validationMethodExists($method)) { - $message = sprintf('The %s::%s() validation method does not exists.', static::class, $method); - throw new RuntimeException($message); + $message = sprintf('The `%s::%s()` validation method does not exists.', static::class, $method); + throw new InvalidArgumentException($message); } $validator = new $this->_validatorClass(); @@ -126,14 +126,15 @@ protected function createValidator(string $name): Validator $this->dispatchEvent($event, compact('validator', 'name')); } - if (!$validator instanceof Validator) { - throw new RuntimeException(sprintf( - 'The %s::%s() validation method must return an instance of %s.', + assert( + $validator instanceof Validator, + sprintf( + 'The `%s::%s()` validation method must return an instance of `%s`.', static::class, $method, - Validator::class - )); - } + Validator::class, + ), + ); return $validator; } diff --git a/app/vendor/cakephp/cakephp/src/Validation/composer.json b/app/vendor/cakephp/cakephp/src/Validation/composer.json index a8998a9c1..c5d6c13e9 100644 --- a/app/vendor/cakephp/cakephp/src/Validation/composer.json +++ b/app/vendor/cakephp/cakephp/src/Validation/composer.json @@ -22,17 +22,26 @@ "source": "https://github.com/cakephp/validation" }, "require": { - "php": ">=7.4.0", - "cakephp/core": "^4.0", - "cakephp/utility": "^4.0", - "psr/http-message": "^1.0.0" + "php": ">=8.1", + "cakephp/core": "5.2.*@dev", + "cakephp/utility": "5.2.*@dev", + "psr/http-message": "^1.1 || ^2.0" }, - "suggest": { - "cakephp/i18n": "If you want to use Validation::localizedTime()" + "require-dev": { + "cakephp/i18n": "5.2.*@dev" }, "autoload": { "psr-4": { "Cake\\Validation\\": "." } + }, + "suggest": { + "cakephp/i18n": "If you want to use Validation::localizedTime()" + }, + "prefer-stable": true, + "extra": { + "branch-alias": { + "dev-5.x": "5.2.x-dev" + } } } diff --git a/app/vendor/cakephp/cakephp/src/View/AjaxView.php b/app/vendor/cakephp/cakephp/src/View/AjaxView.php index a99c14574..d8a800e58 100644 --- a/app/vendor/cakephp/cakephp/src/View/AjaxView.php +++ b/app/vendor/cakephp/cakephp/src/View/AjaxView.php @@ -26,7 +26,7 @@ class AjaxView extends View /** * @inheritDoc */ - protected $layout = 'ajax'; + protected string $layout = 'ajax'; /** * Get content type for this view. diff --git a/app/vendor/cakephp/cakephp/src/View/Cell.php b/app/vendor/cakephp/cakephp/src/View/Cell.php index 9908a74f2..9c94188db 100644 --- a/app/vendor/cakephp/cakephp/src/View/Cell.php +++ b/app/vendor/cakephp/cakephp/src/View/Cell.php @@ -18,7 +18,6 @@ use BadMethodCallException; use Cake\Cache\Cache; -use Cake\Datasource\ModelAwareTrait; use Cake\Event\EventDispatcherInterface; use Cake\Event\EventDispatcherTrait; use Cake\Event\EventManagerInterface; @@ -32,16 +31,20 @@ use Exception; use ReflectionException; use ReflectionMethod; +use Stringable; /** * Cell base. + * + * @implements \Cake\Event\EventDispatcherInterface<\Cake\View\View> */ -#[\AllowDynamicProperties] -abstract class Cell implements EventDispatcherInterface +abstract class Cell implements EventDispatcherInterface, Stringable { + /** + * @use \Cake\Event\EventDispatcherTrait<\Cake\View\View> + */ use EventDispatcherTrait; use LocatorAwareTrait; - use ModelAwareTrait; use ViewVarsTrait; /** @@ -57,7 +60,7 @@ abstract class Cell implements EventDispatcherInterface * * @var \Cake\View\View */ - protected $View; + protected View $View; /** * An instance of a Cake\Http\ServerRequest object that contains information about the current request. @@ -66,28 +69,28 @@ abstract class Cell implements EventDispatcherInterface * * @var \Cake\Http\ServerRequest */ - protected $request; + protected ServerRequest $request; /** * An instance of a Response object that contains information about the impending response * * @var \Cake\Http\Response */ - protected $response; + protected Response $response; /** * The cell's action to invoke. * * @var string */ - protected $action; + protected string $action; /** * Arguments to pass to cell's action. * * @var array */ - protected $args = []; + protected array $args = []; /** * List of valid options (constructor's fourth arguments) @@ -96,14 +99,14 @@ abstract class Cell implements EventDispatcherInterface * * @var array */ - protected $_validCellOptions = []; + protected array $_validCellOptions = []; /** * Caching setup. * * @var array|bool */ - protected $_cache = false; + protected array|bool $_cache = false; /** * Constructor. @@ -117,14 +120,13 @@ public function __construct( ServerRequest $request, Response $response, ?EventManagerInterface $eventManager = null, - array $cellOptions = [] + array $cellOptions = [], ) { if ($eventManager !== null) { $this->setEventManager($eventManager); } $this->request = $request; $this->response = $response; - $this->modelFactory('Table', [$this->getTableLocator(), 'get']); $this->_validCellOptions = array_merge(['action', 'args'], $this->_validCellOptions); foreach ($this->_validCellOptions as $var) { @@ -157,9 +159,7 @@ public function initialize(): void * @param string|null $template Custom template name to render. If not provided (null), the last * value will be used. This value is automatically set by `CellTrait::cell()`. * @return string The rendered cell. - * @throws \Cake\View\Exception\MissingCellTemplateException - * When a MissingTemplateException is raised during rendering. - * @throws \BadMethodCallException + * @throws \Cake\View\Exception\MissingCellTemplateException|\BadMethodCallException */ public function render(?string $template = null): string { @@ -168,15 +168,17 @@ public function render(?string $template = null): string $cache = $this->_cacheConfig($this->action, $template); } - $render = function () use ($template) { + $render = function () use ($template): string { try { + $this->dispatchEvent('Cell.beforeAction', [$this, $this->action, $this->args]); $reflect = new ReflectionMethod($this, $this->action); $reflect->invokeArgs($this, $this->args); - } catch (ReflectionException $e) { + $this->dispatchEvent('Cell.afterAction', [$this, $this->action, $this->args]); + } catch (ReflectionException) { throw new BadMethodCallException(sprintf( - 'Class %s does not have a "%s" method.', + 'Class `%s` does not have a `%s` method.', static::class, - $this->action + $this->action, )); } @@ -188,12 +190,11 @@ public function render(?string $template = null): string $className = static::class; $namePrefix = '\View\Cell\\'; - /** @psalm-suppress PossiblyFalseOperand */ $name = substr($className, strpos($className, $namePrefix) + strlen($namePrefix)); $name = substr($name, 0, -4); if (!$builder->getTemplatePath()) { $builder->setTemplatePath( - static::TEMPLATE_FOLDER . DIRECTORY_SEPARATOR . str_replace('\\', DIRECTORY_SEPARATOR, $name) + static::TEMPLATE_FOLDER . DIRECTORY_SEPARATOR . str_replace('\\', DIRECTORY_SEPARATOR, $name), ); } $template = $builder->getTemplate(); @@ -208,7 +209,7 @@ public function render(?string $template = null): string $attributes['file'], $attributes['paths'], null, - $e + $e, ); } }; @@ -231,7 +232,7 @@ public function render(?string $template = null): string */ protected function _cacheConfig(string $action, ?string $template = null): array { - if (empty($this->_cache)) { + if (!$this->_cache) { return []; } $template = $template ?: 'default'; @@ -268,16 +269,17 @@ public function __toString(): string 'Could not render cell - %s [%s, line %d]', $e->getMessage(), $e->getFile(), - $e->getLine() + $e->getLine(), ), E_USER_WARNING); return ''; + /** @phpstan-ignore-next-line */ } catch (Error $e) { throw new Error(sprintf( 'Could not render cell - %s [%s, line %d]', $e->getMessage(), $e->getFile(), - $e->getLine() + $e->getLine(), ), 0, $e); } } diff --git a/app/vendor/cakephp/cakephp/src/View/CellTrait.php b/app/vendor/cakephp/cakephp/src/View/CellTrait.php index 78d75aacc..630f525c5 100644 --- a/app/vendor/cakephp/cakephp/src/View/CellTrait.php +++ b/app/vendor/cakephp/cakephp/src/View/CellTrait.php @@ -73,9 +73,6 @@ protected function cell(string $cell, array $data = [], array $options = []): Ce throw new MissingCellException(['className' => $pluginAndCell . 'Cell']); } - if (!empty($data)) { - $data = array_values($data); - } $options = ['action' => $action, 'args' => $data] + $options; return $this->_createCell($className, $action, $plugin, $options); @@ -98,21 +95,18 @@ protected function _createCell(string $className, string $action, ?string $plugi $builder = $instance->viewBuilder(); $builder->setTemplate(Inflector::underscore($action)); - if (!empty($plugin)) { + if ($plugin) { $builder->setPlugin($plugin); } - if (!empty($this->helpers)) { - $builder->addHelpers($this->helpers); - } if ($this instanceof View) { - if (!empty($this->theme)) { + $builder->addHelpers($this->helpers); + + if ($this->theme) { $builder->setTheme($this->theme); } - $class = static::class; - $builder->setClassName($class); - $instance->viewBuilder()->setClassName($class); + $builder->setClassName(static::class); return $instance; } diff --git a/app/vendor/cakephp/cakephp/src/View/Exception/MissingCellException.php b/app/vendor/cakephp/cakephp/src/View/Exception/MissingCellException.php index 3f473c831..ab5e2aab2 100644 --- a/app/vendor/cakephp/cakephp/src/View/Exception/MissingCellException.php +++ b/app/vendor/cakephp/cakephp/src/View/Exception/MissingCellException.php @@ -26,5 +26,5 @@ class MissingCellException extends CakeException /** * @inheritDoc */ - protected $_messageTemplate = 'Cell class %s is missing.'; + protected string $_messageTemplate = 'Cell class %s is missing.'; } diff --git a/app/vendor/cakephp/cakephp/src/View/Exception/MissingCellTemplateException.php b/app/vendor/cakephp/cakephp/src/View/Exception/MissingCellTemplateException.php index b276c0f80..ece31e842 100644 --- a/app/vendor/cakephp/cakephp/src/View/Exception/MissingCellTemplateException.php +++ b/app/vendor/cakephp/cakephp/src/View/Exception/MissingCellTemplateException.php @@ -24,12 +24,12 @@ class MissingCellTemplateException extends MissingTemplateException /** * @var string */ - protected $name; + protected string $name; /** * @var string */ - protected $type = 'Cell template'; + protected string $type = 'Cell template'; /** * Constructor @@ -45,7 +45,7 @@ public function __construct( string $file, array $paths = [], ?int $code = null, - ?Throwable $previous = null + ?Throwable $previous = null, ) { $this->name = $name; @@ -55,8 +55,8 @@ public function __construct( /** * Get the passed in attributes * - * @return array - * @psalm-return array{name: string, file: string, paths: array} + * @return array + * @phpstan-return array{name: string, file: string, paths: array} */ public function getAttributes(): array { diff --git a/app/vendor/cakephp/cakephp/src/View/Exception/MissingElementException.php b/app/vendor/cakephp/cakephp/src/View/Exception/MissingElementException.php index 92aa91ccb..a11a5b0db 100644 --- a/app/vendor/cakephp/cakephp/src/View/Exception/MissingElementException.php +++ b/app/vendor/cakephp/cakephp/src/View/Exception/MissingElementException.php @@ -22,5 +22,5 @@ class MissingElementException extends MissingTemplateException /** * @var string */ - protected $type = 'Element'; + protected string $type = 'Element'; } diff --git a/app/vendor/cakephp/cakephp/src/View/Exception/MissingHelperException.php b/app/vendor/cakephp/cakephp/src/View/Exception/MissingHelperException.php index 76d6c5685..7fe31db8a 100644 --- a/app/vendor/cakephp/cakephp/src/View/Exception/MissingHelperException.php +++ b/app/vendor/cakephp/cakephp/src/View/Exception/MissingHelperException.php @@ -24,5 +24,5 @@ class MissingHelperException extends CakeException /** * @inheritDoc */ - protected $_messageTemplate = 'Helper class %s could not be found.'; + protected string $_messageTemplate = 'Helper class `%s` could not be found.'; } diff --git a/app/vendor/cakephp/cakephp/src/View/Exception/MissingLayoutException.php b/app/vendor/cakephp/cakephp/src/View/Exception/MissingLayoutException.php index 7d29e1326..224d2b652 100644 --- a/app/vendor/cakephp/cakephp/src/View/Exception/MissingLayoutException.php +++ b/app/vendor/cakephp/cakephp/src/View/Exception/MissingLayoutException.php @@ -22,5 +22,5 @@ class MissingLayoutException extends MissingTemplateException /** * @var string */ - protected $type = 'Layout'; + protected string $type = 'Layout'; } diff --git a/app/vendor/cakephp/cakephp/src/View/Exception/MissingTemplateException.php b/app/vendor/cakephp/cakephp/src/View/Exception/MissingTemplateException.php index cf50c7a22..7b7fd8dc2 100644 --- a/app/vendor/cakephp/cakephp/src/View/Exception/MissingTemplateException.php +++ b/app/vendor/cakephp/cakephp/src/View/Exception/MissingTemplateException.php @@ -25,22 +25,22 @@ class MissingTemplateException extends CakeException /** * @var string|null */ - protected $templateName; + protected ?string $templateName = null; /** * @var string */ - protected $filename; + protected string $filename; /** * @var array */ - protected $paths; + protected array $paths; /** * @var string */ - protected $type = 'Template'; + protected string $type = 'Template'; /** * Constructor @@ -50,10 +50,10 @@ class MissingTemplateException extends CakeException * @param int|null $code The code of the error. * @param \Throwable|null $previous the previous exception. */ - public function __construct($file, array $paths = [], ?int $code = null, ?Throwable $previous = null) + public function __construct(array|string $file, array $paths = [], ?int $code = null, ?Throwable $previous = null) { if (is_array($file)) { - $this->filename = array_pop($file); + $this->filename = (string)array_pop($file); $this->templateName = array_pop($file); } else { $this->filename = $file; @@ -86,8 +86,8 @@ public function formatMessage(): string /** * Get the passed in attributes * - * @return array - * @psalm-return array{file: string, paths: array} + * @return array + * @phpstan-return array{file: string, paths: array} */ public function getAttributes(): array { diff --git a/app/vendor/cakephp/cakephp/src/View/Exception/MissingViewException.php b/app/vendor/cakephp/cakephp/src/View/Exception/MissingViewException.php index da0a3774d..9f587dda4 100644 --- a/app/vendor/cakephp/cakephp/src/View/Exception/MissingViewException.php +++ b/app/vendor/cakephp/cakephp/src/View/Exception/MissingViewException.php @@ -26,5 +26,5 @@ class MissingViewException extends CakeException /** * @inheritDoc */ - protected $_messageTemplate = 'View class "%s" is missing.'; + protected string $_messageTemplate = 'View class `%s` is missing.'; } diff --git a/app/vendor/cakephp/cakephp/src/View/Form/ArrayContext.php b/app/vendor/cakephp/cakephp/src/View/Form/ArrayContext.php index 1e197569e..39c201fa4 100644 --- a/app/vendor/cakephp/cakephp/src/View/Form/ArrayContext.php +++ b/app/vendor/cakephp/cakephp/src/View/Form/ArrayContext.php @@ -17,7 +17,6 @@ namespace Cake\View\Form; use Cake\Utility\Hash; -use function Cake\Core\deprecationWarning; use function Cake\I18n\__d; /** @@ -36,7 +35,7 @@ * flags to indicate a field is required. The value can also be a string to be used * as the required error message * - `schema` An array of data that emulate the column structures that - * Cake\Database\Schema\Schema uses. This array allows you to control + * {@link \Cake\Database\Schema\TableSchema} uses. This array allows you to control * the inferred type for fields and allows auto generation of attributes * like maxlength, step and other HTML attributes. If you want * primary key/id detection to work. Make sure you have provided a `_constraints` @@ -77,7 +76,7 @@ class ArrayContext implements ContextInterface * * @var array */ - protected $_context; + protected array $_context; /** * Constructor. @@ -96,19 +95,6 @@ public function __construct(array $context) $this->_context = $context; } - /** - * Get the fields used in the context as a primary key. - * - * @return array - * @deprecated 4.0.0 Renamed to {@link getPrimaryKey()}. - */ - public function primaryKey(): array - { - deprecationWarning('`ArrayContext::primaryKey()` is deprecated. Use `ArrayContext::getPrimaryKey()`.'); - - return $this->getPrimaryKey(); - } - /** * Get the fields used in the context as a primary key. * @@ -177,7 +163,7 @@ public function isCreate(): bool * context's schema should be used if it's not explicitly provided. * @return mixed */ - public function val(string $field, array $options = []) + public function val(string $field, array $options = []): mixed { $options += [ 'default' => null, @@ -195,7 +181,7 @@ public function val(string $field, array $options = []) return null; } - // Using Hash::check here incase the default value is actually null + // Using Hash::check here in case the default value is actually null if (Hash::check($this->_context['defaults'], $field)) { return Hash::get($this->_context['defaults'], $field); } @@ -217,17 +203,14 @@ public function isRequired(string $field): ?bool return null; } - $required = Hash::get($this->_context['required'], $field); - - if ($required === null) { - $required = Hash::get($this->_context['required'], $this->stripNesting($field)); - } + $required = Hash::get($this->_context['required'], $field) + ?? Hash::get($this->_context['required'], $this->stripNesting($field)); - if (!empty($required) || $required === '0') { + if ($required || $required === '0') { return true; } - return $required; + return $required !== null ? (bool)$required : null; } /** @@ -238,17 +221,15 @@ public function getRequiredMessage(string $field): ?string if (!is_array($this->_context['required'])) { return null; } - $required = Hash::get($this->_context['required'], $field); - if ($required === null) { - $required = Hash::get($this->_context['required'], $this->stripNesting($field)); - } + $required = Hash::get($this->_context['required'], $field) + ?? Hash::get($this->_context['required'], $this->stripNesting($field)); if ($required === false) { return null; } if ($required === true) { - $required = __d('cake', 'This field cannot be left empty'); + return __d('cake', 'This field cannot be left empty'); } return $required; @@ -268,7 +249,7 @@ public function getMaxLength(string $field): ?int return null; } - return Hash::get($this->_context['schema'], "$field.length"); + return Hash::get($this->_context['schema'], "{$field}.length"); } /** @@ -279,6 +260,7 @@ public function fieldNames(): array $schema = $this->_context['schema']; unset($schema['_constraints'], $schema['_indexes']); + /** @var array */ return array_keys($schema); } @@ -295,10 +277,8 @@ public function type(string $field): ?string return null; } - $schema = Hash::get($this->_context['schema'], $field); - if ($schema === null) { - $schema = Hash::get($this->_context['schema'], $this->stripNesting($field)); - } + $schema = Hash::get($this->_context['schema'], $field) + ?? Hash::get($this->_context['schema'], $this->stripNesting($field)); return $schema['type'] ?? null; } @@ -314,14 +294,12 @@ public function attributes(string $field): array if (!is_array($this->_context['schema'])) { return []; } - $schema = Hash::get($this->_context['schema'], $field); - if ($schema === null) { - $schema = Hash::get($this->_context['schema'], $this->stripNesting($field)); - } + $schema = Hash::get($this->_context['schema'], $field) + ?? Hash::get($this->_context['schema'], $this->stripNesting($field)); return array_intersect_key( (array)$schema, - array_flip(static::VALID_ATTRIBUTES) + array_flip(static::VALID_ATTRIBUTES), ); } @@ -366,6 +344,6 @@ public function error(string $field): array */ protected function stripNesting(string $field): string { - return preg_replace('/\.\d*\./', '.', $field); + return (string)preg_replace('/\.\d*\./', '.', $field); } } diff --git a/app/vendor/cakephp/cakephp/src/View/Form/ContextFactory.php b/app/vendor/cakephp/cakephp/src/View/Form/ContextFactory.php index 482fa85d2..a34989bb3 100644 --- a/app/vendor/cakephp/cakephp/src/View/Form/ContextFactory.php +++ b/app/vendor/cakephp/cakephp/src/View/Form/ContextFactory.php @@ -17,11 +17,10 @@ namespace Cake\View\Form; use Cake\Collection\Collection; +use Cake\Core\Exception\CakeException; use Cake\Datasource\EntityInterface; use Cake\Form\Form; use Cake\Http\ServerRequest; -use RuntimeException; -use function Cake\Core\getTypeName; /** * Factory for getting form context instance based on provided data. @@ -33,7 +32,7 @@ class ContextFactory * * @var array */ - protected $providers = []; + protected array $providers = []; /** * Constructor. @@ -55,7 +54,7 @@ public function __construct(array $providers = []) * be of form `['type' => 'a-string', 'callable' => ..]` * @return static */ - public static function createWithDefaults(array $providers = []) + public static function createWithDefaults(array $providers = []): static { $providers = [ [ @@ -71,9 +70,9 @@ public static function createWithDefaults(array $providers = []) $pass = (new Collection($data['entity']))->first() !== null; if ($pass) { return new EntityContext($data); - } else { - return new NullContext($data); } + + return new NullContext($data); } }, ], @@ -137,12 +136,13 @@ public function addProvider(string $type, callable $check) * @param \Cake\Http\ServerRequest $request Request instance. * @param array $data The data to get a context provider for. * @return \Cake\View\Form\ContextInterface Context provider. - * @throws \RuntimeException When a context instance cannot be generated for given entity. + * @throws \Cake\Core\Exception\CakeException When a context instance cannot be generated for given entity. */ public function get(ServerRequest $request, array $data = []): ContextInterface { $data += ['entity' => null]; + $context = null; foreach ($this->providers as $provider) { $check = $provider['callable']; $context = $check($request, $data); @@ -151,11 +151,11 @@ public function get(ServerRequest $request, array $data = []): ContextInterface } } - if (!isset($context)) { - throw new RuntimeException(sprintf( + if ($context === null) { + throw new CakeException(sprintf( 'No context provider found for value of type `%s`.' . ' Use `null` as 1st argument of FormHelper::create() to create a context-less form.', - getTypeName($data['entity']) + get_debug_type($data['entity']), )); } diff --git a/app/vendor/cakephp/cakephp/src/View/Form/ContextInterface.php b/app/vendor/cakephp/cakephp/src/View/Form/ContextInterface.php index db22e26c7..b1eeaacd5 100644 --- a/app/vendor/cakephp/cakephp/src/View/Form/ContextInterface.php +++ b/app/vendor/cakephp/cakephp/src/View/Form/ContextInterface.php @@ -65,7 +65,7 @@ public function isCreate(): bool; * is needed for. * @return mixed */ - public function val(string $field, array $options = []); + public function val(string $field, array $options = []): mixed; /** * Check if a given field is 'required'. diff --git a/app/vendor/cakephp/cakephp/src/View/Form/EntityContext.php b/app/vendor/cakephp/cakephp/src/View/Form/EntityContext.php index 728ade06e..2ce89ffdf 100644 --- a/app/vendor/cakephp/cakephp/src/View/Form/EntityContext.php +++ b/app/vendor/cakephp/cakephp/src/View/Form/EntityContext.php @@ -18,16 +18,17 @@ use ArrayAccess; use Cake\Collection\Collection; +use Cake\Core\Exception\CakeException; use Cake\Datasource\EntityInterface; use Cake\Datasource\InvalidPropertyInterface; +use Cake\ORM\Association\BelongsToMany; use Cake\ORM\Entity; use Cake\ORM\Locator\LocatorAwareTrait; use Cake\ORM\Table; use Cake\Utility\Inflector; use Cake\Validation\Validator; -use RuntimeException; +use InvalidArgumentException; use Traversable; -use function Cake\Core\deprecationWarning; use function Cake\Core\namespaceSplit; /** @@ -59,14 +60,14 @@ class EntityContext implements ContextInterface * * @var array */ - protected $_context; + protected array $_context; /** * The name of the top level entity/table object. * * @var string */ - protected $_rootName; + protected string $_rootName; /** * Boolean to track whether the entity is a @@ -74,21 +75,21 @@ class EntityContext implements ContextInterface * * @var bool */ - protected $_isCollection = false; + protected bool $_isCollection = false; /** * A dictionary of tables * * @var array<\Cake\ORM\Table> */ - protected $_tables = []; + protected array $_tables = []; /** * Dictionary of validators. * * @var array<\Cake\Validation\Validator> */ - protected $_validator = []; + protected array $_validator = []; /** * Constructor. @@ -119,18 +120,17 @@ public function __construct(array $context) * like arrays, Collection objects and ResultSets. * * @return void - * @throws \RuntimeException When a table object cannot be located/inferred. + * @throws \Cake\Core\Exception\CakeException When a table object cannot be located/inferred. */ protected function _prepare(): void { - /** @var \Cake\ORM\Table|null $table */ $table = $this->_context['table']; + /** @var \Cake\Datasource\EntityInterface|iterable<\Cake\Datasource\EntityInterface|array> $entity */ $entity = $this->_context['entity']; - $this->_isCollection = is_iterable($entity); - if (empty($table)) { + if (!$table) { if ($this->_isCollection) { /** @var iterable<\Cake\Datasource\EntityInterface|array> $entity */ foreach ($entity as $e) { @@ -138,15 +138,12 @@ protected function _prepare(): void break; } } - $isEntity = $entity instanceof EntityInterface; - if ($isEntity) { - /** @psalm-suppress PossiblyInvalidMethodCall */ + if ($entity instanceof EntityInterface) { $table = $entity->getSource(); } - /** @psalm-suppress PossiblyInvalidArgument */ - if (!$table && $isEntity && get_class($entity) !== Entity::class) { - [, $entityClass] = namespaceSplit(get_class($entity)); + if (!$table && $entity instanceof EntityInterface && $entity::class !== Entity::class) { + [, $entityClass] = namespaceSplit($entity::class); $table = Inflector::pluralize($entityClass); } } @@ -155,30 +152,13 @@ protected function _prepare(): void } if (!($table instanceof Table)) { - throw new RuntimeException( - 'Unable to find table class for current entity.' - ); + throw new CakeException('Unable to find table class for current entity.'); } - - $alias = $this->_rootName = $table->getAlias(); + $alias = $table->getAlias(); + $this->_rootName = $alias; $this->_tables[$alias] = $table; } - /** - * Get the primary key data for the context. - * - * Gets the primary key columns from the root entity's schema. - * - * @return array - * @deprecated 4.0.0 Renamed to {@link getPrimaryKey()}. - */ - public function primaryKey(): array - { - deprecationWarning('`EntityContext::primaryKey()` is deprecated. Use `EntityContext::getPrimaryKey()`.'); - - return (array)$this->_tables[$this->_rootName]->getPrimaryKey(); - } - /** * Get the primary key data for the context. * @@ -227,7 +207,7 @@ public function isCreate(): bool } } if ($entity instanceof EntityInterface) { - return $entity->isNew() !== false; + return $entity->isNew(); } return true; @@ -247,7 +227,7 @@ public function isCreate(): bool * schema should be used if it's not explicitly provided. * @return mixed The value of the field or null on a miss. */ - public function val(string $field, array $options = []) + public function val(string $field, array $options = []): mixed { $options += [ 'default' => null, @@ -274,7 +254,7 @@ public function val(string $field, array $options = []) } } - $val = $entity->get($part); + $val = $entity->has($part) ? $entity->get($part) : null; if ($val !== null) { return $val; } @@ -303,7 +283,7 @@ public function val(string $field, array $options = []) * @param array $parts Each one of the parts in a path for a field name * @return mixed */ - protected function _schemaDefault(array $parts) + protected function _schemaDefault(array $parts): mixed { $table = $this->_getTable($parts); if ($table === null) { @@ -311,7 +291,7 @@ protected function _schemaDefault(array $parts) } $field = end($parts); $defaults = $table->getSchema()->defaultValues(); - if (!array_key_exists($field, $defaults)) { + if ($field === false || !array_key_exists($field, $defaults)) { return null; } @@ -326,7 +306,7 @@ protected function _schemaDefault(array $parts) * @param array $path Each one of the parts in a path for a field name * @return array|null */ - protected function _extractMultiple($values, array $path): ?array + protected function _extractMultiple(mixed $values, array $path): ?array { if (!is_iterable($values)) { return null; @@ -345,12 +325,12 @@ protected function _extractMultiple($values, array $path): ?array * * If you only want the terminal Entity for a path use `leafEntity` instead. * - * @param array|null $path Each one of the parts in a path for a field name + * @param array|null $path Each one of the parts in a path for a field name * or null to get the entity passed in constructor context. * @return \Cake\Datasource\EntityInterface|iterable|null - * @throws \RuntimeException When properties cannot be read. + * @throws \Cake\Core\Exception\CakeException When properties cannot be read. */ - public function entity(?array $path = null) + public function entity(?array $path = null): EntityInterface|iterable|null { if ($path === null) { return $this->_context['entity']; @@ -391,9 +371,9 @@ public function entity(?array $path = null) } $entity = $next; } - throw new RuntimeException(sprintf( - 'Unable to fetch property "%s"', - implode('.', $path) + throw new CakeException(sprintf( + 'Unable to fetch property `%s`.', + implode('.', $path), )); } @@ -407,9 +387,9 @@ public function entity(?array $path = null) * @param array|null $path Each one of the parts in a path for a field name * or null to get the entity passed in constructor context. * @return array Containing the found entity, and remaining un-matched path. - * @throws \RuntimeException When properties cannot be read. + * @throws \Cake\Core\Exception\CakeException When properties cannot be read. */ - protected function leafEntity($path = null) + protected function leafEntity(?array $path = null): array { if ($path === null) { return $this->_context['entity']; @@ -417,9 +397,9 @@ protected function leafEntity($path = null) $oneElement = count($path) === 1; if ($oneElement && $this->_isCollection) { - throw new RuntimeException(sprintf( - 'Unable to fetch property "%s"', - implode('.', $path) + throw new CakeException(sprintf( + 'Unable to fetch property `%s`.', + implode('.', $path), )); } $entity = $this->_context['entity']; @@ -438,7 +418,7 @@ protected function leafEntity($path = null) $next = $this->_getProp($entity, $prop); // Did not dig into an entity, return the current one. - if (is_array($entity) && !($next instanceof EntityInterface || $next instanceof Traversable)) { + if (is_array($entity) && (!$next instanceof EntityInterface && !$next instanceof Traversable)) { return [$leafEntity, array_slice($path, $i - 1)]; } @@ -449,8 +429,7 @@ protected function leafEntity($path = null) // If we are at the end of traversable elements // return the last entity found. $isTraversable = ( - is_array($next) || - $next instanceof Traversable || + is_iterable($next) || $next instanceof EntityInterface ); if (!$isTraversable) { @@ -458,9 +437,9 @@ protected function leafEntity($path = null) } $entity = $next; } - throw new RuntimeException(sprintf( - 'Unable to fetch property "%s"', - implode('.', $path) + throw new CakeException(sprintf( + 'Unable to fetch property `%s`.', + implode('.', $path), )); } @@ -471,7 +450,7 @@ protected function leafEntity($path = null) * @param string $field The next field to fetch. * @return mixed */ - protected function _getProp($target, $field) + protected function _getProp(mixed $target, string $field): mixed { if (is_array($target) && isset($target[$field])) { return $target[$field]; @@ -534,11 +513,11 @@ public function getRequiredMessage(string $field): ?string } $ruleset = $validator->field($fieldName); - if (!$ruleset->isEmptyAllowed()) { - return $validator->getNotEmptyMessage($fieldName); + if ($ruleset->isEmptyAllowed()) { + return null; } - return null; + return $validator->getNotEmptyMessage($fieldName); } /** @@ -562,11 +541,11 @@ public function getMaxLength(string $field): ?int } $attributes = $this->attributes($field); - if (!empty($attributes['length'])) { - return (int)$attributes['length']; + if (empty($attributes['length'])) { + return null; } - return null; + return (int)$attributes['length']; } /** @@ -590,9 +569,9 @@ public function fieldNames(): array * Get the validator associated to an entity based on naming * conventions. * - * @param array $parts Each one of the parts in a path for a field name + * @param array $parts Each one of the parts in a path for a field name * @return \Cake\Validation\Validator - * @throws \RuntimeException If validator cannot be retrieved based on the parts. + * @throws \Cake\Core\Exception\CakeException If validator cannot be retrieved based on the parts. */ protected function _getValidator(array $parts): Validator { @@ -600,7 +579,7 @@ protected function _getValidator(array $parts): Validator return !is_numeric($part); }); $key = implode('.', $keyParts); - $entity = $this->entity($parts) ?: null; + $entity = $this->entity($parts); if (isset($this->_validator[$key])) { if (is_object($entity)) { @@ -612,7 +591,7 @@ protected function _getValidator(array $parts): Validator $table = $this->_getTable($parts); if (!$table) { - throw new RuntimeException('Validator not found: ' . $key); + throw new InvalidArgumentException(sprintf('Validator not found: `%s`.', $key)); } $alias = $table->getAlias(); @@ -640,7 +619,7 @@ protected function _getValidator(array $parts): Validator * when a nonexistent field/property is being encountered. * @return \Cake\ORM\Table|null Table instance or null */ - protected function _getTable($parts, $fallback = true): ?Table + protected function _getTable(EntityInterface|array|string $parts, bool $fallback = true): ?Table { if (!is_array($parts) || count($parts) === 1) { return $this->_tables[$this->_rootName]; @@ -662,12 +641,10 @@ protected function _getTable($parts, $fallback = true): ?Table $table = $this->_tables[$this->_rootName]; $assoc = null; foreach ($normalized as $part) { - if ($part === '_joinData') { - if ($assoc !== null) { - $table = $assoc->junction(); - $assoc = null; - continue; - } + if ($assoc instanceof BelongsToMany && $part === $assoc->getJunctionProperty()) { + $table = $assoc->junction(); + $assoc = null; + continue; } else { $associationCollection = $table->associations(); $assoc = $associationCollection->getByProperty($part); @@ -698,11 +675,8 @@ public function type(string $field): ?string { $parts = explode('.', $field); $table = $this->_getTable($parts); - if (!$table) { - return null; - } - return $table->getSchema()->baseColumnType(array_pop($parts)); + return $table?->getSchema()->baseColumnType(array_pop($parts)); } /** @@ -721,7 +695,7 @@ public function attributes(string $field): array return array_intersect_key( (array)$table->getSchema()->getColumn(array_pop($parts)), - array_flip(static::VALID_ATTRIBUTES) + array_flip(static::VALID_ATTRIBUTES), ); } @@ -746,11 +720,15 @@ public function error(string $field): array { $parts = explode('.', $field); try { + /** + * @var \Cake\Datasource\EntityInterface|null $entity + * @var array $remainingParts + */ [$entity, $remainingParts] = $this->leafEntity($parts); - } catch (RuntimeException $e) { + } catch (CakeException) { return []; } - if (count($remainingParts) === 0) { + if ($entity instanceof EntityInterface && count($remainingParts) === 0) { return $entity->getErrors(); } diff --git a/app/vendor/cakephp/cakephp/src/View/Form/FormContext.php b/app/vendor/cakephp/cakephp/src/View/Form/FormContext.php index 731cc045e..d487704f0 100644 --- a/app/vendor/cakephp/cakephp/src/View/Form/FormContext.php +++ b/app/vendor/cakephp/cakephp/src/View/Form/FormContext.php @@ -16,10 +16,8 @@ */ namespace Cake\View\Form; -use Cake\Core\Exception\CakeException; use Cake\Form\Form; use Cake\Utility\Hash; -use function Cake\Core\deprecationWarning; /** * Provides a context provider for {@link \Cake\Form\Form} instances. @@ -34,14 +32,14 @@ class FormContext implements ContextInterface * * @var \Cake\Form\Form */ - protected $_form; + protected Form $_form; /** * Validator name. * * @var string|null */ - protected $_validator = null; + protected ?string $_validator = null; /** * Constructor. @@ -55,27 +53,15 @@ class FormContext implements ContextInterface */ public function __construct(array $context) { - if (!isset($context['entity']) || !$context['entity'] instanceof Form) { - throw new CakeException('`$context[\'entity\']` must be an instance of Cake\Form\Form'); - } + assert( + isset($context['entity']) && $context['entity'] instanceof Form, + "`\$context['entity']` must be an instance of " . Form::class, + ); $this->_form = $context['entity']; $this->_validator = $context['validator'] ?? null; } - /** - * Get the fields used in the context as a primary key. - * - * @return array - * @deprecated 4.0.0 Renamed to {@link getPrimaryKey()}. - */ - public function primaryKey(): array - { - deprecationWarning('`FormContext::primaryKey()` is deprecated. Use `FormContext::getPrimaryKey()`.'); - - return []; - } - /** * @inheritDoc */ @@ -103,7 +89,7 @@ public function isCreate(): bool /** * @inheritDoc */ - public function val(string $field, array $options = []) + public function val(string $field, array $options = []): mixed { $options += [ 'default' => null, @@ -128,14 +114,14 @@ public function val(string $field, array $options = []) * @param string $field Field name. * @return mixed */ - protected function _schemaDefault(string $field) + protected function _schemaDefault(string $field): mixed { $field = $this->_form->getSchema()->field($field); - if ($field) { - return $field['default']; + if (!$field) { + return null; } - return null; + return $field['default']; } /** @@ -191,11 +177,11 @@ public function getMaxLength(string $field): ?int } $attributes = $this->attributes($field); - if (!empty($attributes['length'])) { - return $attributes['length']; + if (empty($attributes['length'])) { + return null; } - return null; + return $attributes['length']; } /** @@ -221,7 +207,7 @@ public function attributes(string $field): array { return array_intersect_key( (array)$this->_form->getSchema()->field($field), - array_flip(static::VALID_ATTRIBUTES) + array_flip(static::VALID_ATTRIBUTES), ); } @@ -232,7 +218,7 @@ public function hasError(string $field): bool { $errors = $this->error($field); - return count($errors) > 0; + return $errors !== []; } /** diff --git a/app/vendor/cakephp/cakephp/src/View/Form/NullContext.php b/app/vendor/cakephp/cakephp/src/View/Form/NullContext.php index 8defaa128..ce6351637 100644 --- a/app/vendor/cakephp/cakephp/src/View/Form/NullContext.php +++ b/app/vendor/cakephp/cakephp/src/View/Form/NullContext.php @@ -16,8 +16,6 @@ */ namespace Cake\View\Form; -use function Cake\Core\deprecationWarning; - /** * Provides a context provider that does nothing. * @@ -35,19 +33,6 @@ public function __construct(array $context) { } - /** - * Get the fields used in the context as a primary key. - * - * @return array - * @deprecated 4.0.0 Renamed to {@link getPrimaryKey()}. - */ - public function primaryKey(): array - { - deprecationWarning('`NullContext::primaryKey()` is deprecated. Use `NullContext::getPrimaryKey()`.'); - - return []; - } - /** * @inheritDoc */ @@ -75,7 +60,7 @@ public function isCreate(): bool /** * @inheritDoc */ - public function val(string $field, array $options = []) + public function val(string $field, array $options = []): mixed { return null; } diff --git a/app/vendor/cakephp/cakephp/src/View/Helper.php b/app/vendor/cakephp/cakephp/src/View/Helper.php index 35ca7dbbe..764c89c49 100644 --- a/app/vendor/cakephp/cakephp/src/View/Helper.php +++ b/app/vendor/cakephp/cakephp/src/View/Helper.php @@ -40,7 +40,6 @@ * - `afterRenderFile(EventInterface $event, $viewFile, $content)` - Called after any view fragment is rendered. * If a listener returns a non-null value, the output of the rendered file will be set to that. */ -#[\AllowDynamicProperties] class Helper implements EventListenerInterface { use InstanceConfigTrait; @@ -50,28 +49,28 @@ class Helper implements EventListenerInterface * * @var array */ - protected $helpers = []; + protected array $helpers = []; /** * Default config for this helper. * * @var array */ - protected $_defaultConfig = []; + protected array $_defaultConfig = []; /** - * A helper lookup table used to lazy load helper objects. + * Loaded helper instances. * - * @var array + * @var array */ - protected $_helperMap = []; + protected array $helperInstances = []; /** * The View instance this helper is attached to * * @var \Cake\View\View */ - protected $_View; + protected View $_View; /** * Default Constructor @@ -84,39 +83,32 @@ public function __construct(View $view, array $config = []) $this->_View = $view; $this->setConfig($config); - if (!empty($this->helpers)) { - $this->_helperMap = $view->helpers()->normalizeArray($this->helpers); + if ($this->helpers) { + $this->helpers = $view->helpers()->normalizeArray($this->helpers); } $this->initialize($config); } - /** - * Provide non fatal errors on missing method calls. - * - * @param string $method Method to invoke - * @param array $params Array of params for the method. - * @return mixed|void - */ - public function __call(string $method, array $params) - { - trigger_error(sprintf('Method %1$s::%2$s does not exist', static::class, $method), E_USER_WARNING); - } - /** * Lazy loads helpers. * * @param string $name Name of the property being accessed. - * @return \Cake\View\Helper|null|void Helper instance if helper with provided name exists + * @return \Cake\View\Helper|null Helper instance if helper with provided name exists */ - public function __get(string $name) + public function __get(string $name): ?Helper { - if (isset($this->_helperMap[$name]) && !isset($this->{$name})) { - $config = ['enabled' => false] + (array)$this->_helperMap[$name]['config']; - $this->{$name} = $this->_View->loadHelper($this->_helperMap[$name]['class'], $config); + if (isset($this->helperInstances[$name])) { + return $this->helperInstances[$name]; + } - return $this->{$name}; + if (isset($this->helpers[$name])) { + $config = ['enabled' => false] + $this->helpers[$name]; + + return $this->helperInstances[$name] = $this->_View->loadHelper($name, $config); } + + return null; } /** diff --git a/app/vendor/cakephp/cakephp/src/View/Helper/BreadcrumbsHelper.php b/app/vendor/cakephp/cakephp/src/View/Helper/BreadcrumbsHelper.php index cc930d210..6d1e25ce7 100644 --- a/app/vendor/cakephp/cakephp/src/View/Helper/BreadcrumbsHelper.php +++ b/app/vendor/cakephp/cakephp/src/View/Helper/BreadcrumbsHelper.php @@ -34,14 +34,14 @@ class BreadcrumbsHelper extends Helper * * @var array */ - protected $helpers = ['Url']; + protected array $helpers = ['Url']; /** * Default config for the helper. * * @var array */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'templates' => [ 'wrapper' => '{{content}}', 'item' => '{{title}}{{separator}}', @@ -55,7 +55,7 @@ class BreadcrumbsHelper extends Helper * * @var array */ - protected $crumbs = []; + protected array $crumbs = []; /** * Add a crumb to the end of the trail. @@ -78,7 +78,7 @@ class BreadcrumbsHelper extends Helper * - *templateVars*: Specific template vars in case you override the templates provided. * @return $this */ - public function add($title, $url = null, array $options = []) + public function add(array|string $title, array|string|null $url = null, array $options = []) { if (is_array($title)) { foreach ($title as $crumb) { @@ -114,7 +114,7 @@ public function add($title, $url = null, array $options = []) * - *templateVars*: Specific template vars in case you override the templates provided. * @return $this */ - public function prepend($title, $url = null, array $options = []) + public function prepend(array|string $title, array|string|null $url = null, array $options = []) { if (is_array($title)) { $crumbs = []; @@ -154,10 +154,10 @@ public function prepend($title, $url = null, array $options = []) * @return $this * @throws \LogicException In case the index is out of bound */ - public function insertAt(int $index, string $title, $url = null, array $options = []) + public function insertAt(int $index, string $title, array|string|null $url = null, array $options = []) { if (!isset($this->crumbs[$index]) && $index !== count($this->crumbs)) { - throw new LogicException(sprintf("No crumb could be found at index '%s'", $index)); + throw new LogicException(sprintf('No crumb could be found at index `%s`.', $index)); } array_splice($this->crumbs, $index, 0, [compact('title', 'url', 'options')]); @@ -184,12 +184,16 @@ public function insertAt(int $index, string $title, $url = null, array $options * @return $this * @throws \LogicException In case the matching crumb can not be found */ - public function insertBefore(string $matchingTitle, string $title, $url = null, array $options = []) - { + public function insertBefore( + string $matchingTitle, + string $title, + array|string|null $url = null, + array $options = [], + ) { $key = $this->findCrumb($matchingTitle); if ($key === null) { - throw new LogicException(sprintf("No crumb matching '%s' could be found.", $matchingTitle)); + throw new LogicException(sprintf('No crumb matching `%s` could be found.', $matchingTitle)); } return $this->insertAt($key, $title, $url, $options); @@ -214,12 +218,16 @@ public function insertBefore(string $matchingTitle, string $title, $url = null, * @return $this * @throws \LogicException In case the matching crumb can not be found. */ - public function insertAfter(string $matchingTitle, string $title, $url = null, array $options = []) - { + public function insertAfter( + string $matchingTitle, + string $title, + array|string|null $url = null, + array $options = [], + ) { $key = $this->findCrumb($matchingTitle); if ($key === null) { - throw new LogicException(sprintf("No crumb matching '%s' could be found.", $matchingTitle)); + throw new LogicException(sprintf('No crumb matching `%s` could be found.', $matchingTitle)); } return $this->insertAt($key + 1, $title, $url, $options); @@ -281,7 +289,7 @@ public function render(array $attributes = [], array $separator = []): string $separator['attrs'] = $templater->formatAttributes( $separator, - ['innerAttrs', 'separator'] + ['innerAttrs', 'separator'], ); $separatorString = $this->formatTemplate('separator', $separator); diff --git a/app/vendor/cakephp/cakephp/src/View/Helper/FormHelper.php b/app/vendor/cakephp/cakephp/src/View/Helper/FormHelper.php index 77bc7451c..89044d05c 100644 --- a/app/vendor/cakephp/cakephp/src/View/Helper/FormHelper.php +++ b/app/vendor/cakephp/cakephp/src/View/Helper/FormHelper.php @@ -16,8 +16,12 @@ */ namespace Cake\View\Helper; +use BackedEnum; use Cake\Core\Configure; use Cake\Core\Exception\CakeException; +use Cake\Database\Type\EnumLabelInterface; +use Cake\Database\Type\EnumType; +use Cake\Database\TypeFactory; use Cake\Form\FormProtector; use Cake\Routing\Router; use Cake\Utility\Hash; @@ -27,9 +31,9 @@ use Cake\View\Helper; use Cake\View\StringTemplateTrait; use Cake\View\View; +use Cake\View\Widget\WidgetInterface; use Cake\View\Widget\WidgetLocator; use InvalidArgumentException; -use RuntimeException; use function Cake\Core\deprecationWarning; use function Cake\Core\h; use function Cake\I18n\__; @@ -45,9 +49,13 @@ * @method string email(string $fieldName, array $options = []) Creates input of type email. * @method string password(string $fieldName, array $options = []) Creates input of type password. * @method string search(string $fieldName, array $options = []) Creates input of type search. + * @method string day(string $fieldName, array $options = []) Creates input of type day. + * @method string hour(string $fieldName, array $options = []) Creates input of type hour. + * @method string minute(string $fieldName, array $options = []) Creates input of type minute. + * @method string meridian(string $fieldName, array $options = []) Creates input of type meridian. * @property \Cake\View\Helper\HtmlHelper $Html * @property \Cake\View\Helper\UrlHelper $Url - * @link https://book.cakephp.org/4/en/views/helpers/form.html + * @link https://book.cakephp.org/5/en/views/helpers/form.html */ class FormHelper extends Helper { @@ -59,16 +67,18 @@ class FormHelper extends Helper * * @var array */ - protected $helpers = ['Url', 'Html']; + protected array $helpers = ['Url', 'Html']; /** * Default config for the helper. * * @var array */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'idPrefix' => null, - 'errorClass' => 'form-error', + // Deprecated option, use templates.errorClass intead. + 'errorClass' => null, + 'defaultPostLinkBlock' => null, 'typeMap' => [ 'string' => 'text', 'text' => 'textarea', @@ -96,7 +106,7 @@ class FormHelper extends Helper 'checkbox' => '', // Input group wrapper for checkboxes created via control(). 'checkboxFormGroup' => '{{label}}', - // Wrapper container for checkboxes. + // Wrapper container for checkboxes in a multicheckbox input 'checkboxWrapper' => '
{{label}}
', // Error message wrapper elements. 'error' => '
{{content}}
', @@ -115,15 +125,16 @@ class FormHelper extends Helper // General grouping container for control(). Defines input/label ordering. 'formGroup' => '{{label}}{{input}}', // Wrapper content used to hide other content. - 'hiddenBlock' => '
{{content}}
', + 'hiddenBlock' => '{{content}}', // Generic input element. 'input' => '', // Submit input element. 'inputSubmit' => '', // Container element used by control(). - 'inputContainer' => '
{{content}}
', + 'inputContainer' => '
{{content}}
', // Container element used by control() when a field has an error. - 'inputContainerError' => '
{{content}}{{error}}
', + // phpcs:ignore + 'inputContainerError' => '
{{content}}{{error}}
', // Label element when inputs are not nested inside the label. 'label' => '{{text}}', // Label element used for radio and multi-checkbox inputs. @@ -152,10 +163,19 @@ class FormHelper extends Helper 'submitContainer' => '
{{content}}
', // Confirm javascript template for postLink() 'confirmJs' => '{{confirm}}', + // Templates for postLink() JS for - - - + + + diff --git a/app/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/dashboard_branch.html.dist b/app/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/dashboard_branch.html.dist index 60e66d5be..eb003154a 100644 --- a/app/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/dashboard_branch.html.dist +++ b/app/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/dashboard_branch.html.dist @@ -5,7 +5,7 @@ Dashboard for {{full_path}} - + @@ -32,7 +32,7 @@

Coverage Distribution

-
+
@@ -45,7 +45,7 @@
-

Insufficient Coverage

+

Insufficient Coverage

has('{{ details.property }}') ? $this->Html->link(${{ singularVar }}->{{ details.property }}->{{ details.displayField }}, ['controller' => '{{ details.controller }}', 'action' => 'view', ${{ singularVar }}->{{ details.property }}->{{ details.primaryKey[0] }}]) : '' ?>hasValue('{{ details.property }}') ? $this->Html->link(${{ singularVar }}->{{ details.property }}->{{ details.displayField }}, ['controller' => '{{ details.controller }}', 'action' => 'view', ${{ singularVar }}->{{ details.property }}->{{ details.primaryKey[0] }}]) : '' ?>
has('{{ details.property }}') ? $this->Html->link(${{ singularVar }}->{{ details.property }}->{{ details.displayField }}, ['controller' => '{{ details.controller }}', 'action' => 'view', ${{ singularVar }}->{{ details.property }}->{{ details.primaryKey[0] }}]) : '' ?>hasValue('{{ details.property }}') ? $this->Html->link(${{ singularVar }}->{{ details.property }}->{{ details.displayField }}, ['controller' => '{{ details.controller }}', 'action' => 'view', ${{ singularVar }}->{{ details.property }}->{{ details.primaryKey[0] }}]) : '' ?>
{{ field }} === null ? '' : h(${{ singularVar }}->{{ field }}->{% if supportsLabel %}label(){% else %}value{% endif %}) ?>{{ field }}->{% if supportsLabel %}label(){% else %}value{% endif %}) ?>
Html->link(__('View'), ['controller' => '{{ details.controller }}', 'action' => 'view', {{ otherPk|raw }}]) ?> Html->link(__('Edit'), ['controller' => '{{ details.controller }}', 'action' => 'edit', {{ otherPk|raw }}]) ?> - Form->postLink(__('Delete'), ['controller' => '{{ details.controller }}', 'action' => 'delete', {{ otherPk|raw }}], ['confirm' => __('Are you sure you want to delete # {0}?', {{ otherPk|raw }})]) ?> + Form->postLink( + __('Delete'), + ['controller' => '{{ details.controller }}', 'action' => 'delete', {{ otherPk|raw }}], + [ + 'method' => 'delete', + 'confirm' => __('Are you sure you want to delete # {0}?', {{ otherPk|raw }}), + ] + ) ?>
@@ -61,7 +61,7 @@
-

Project Risks

+

Project Risks

@@ -79,13 +79,13 @@
-

Methods

+

Methods

Coverage Distribution

-
+
@@ -98,7 +98,7 @@
-

Insufficient Coverage

+

Insufficient Coverage

@@ -114,7 +114,7 @@
-

Project Risks

+

Project Risks

@@ -137,145 +137,157 @@

- - - - + + + diff --git a/app/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/file.html.dist b/app/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/file.html.dist index a022f5c2c..d29103481 100644 --- a/app/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/file.html.dist +++ b/app/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/file.html.dist @@ -58,8 +58,7 @@ - - + diff --git a/app/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/file_branch.html.dist b/app/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/file_branch.html.dist index f48ebf128..b8bcf3747 100644 --- a/app/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/file_branch.html.dist +++ b/app/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/file_branch.html.dist @@ -60,8 +60,7 @@ - - + diff --git a/app/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/icons/file-code.svg b/app/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/icons/file-code.svg new file mode 100644 index 000000000..5b4b19953 --- /dev/null +++ b/app/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/icons/file-code.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/icons/file-directory.svg b/app/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/icons/file-directory.svg new file mode 100644 index 000000000..4bf1f1caa --- /dev/null +++ b/app/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/icons/file-directory.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/js/billboard.pkgd.min.js b/app/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/js/billboard.pkgd.min.js new file mode 100644 index 000000000..52128a864 --- /dev/null +++ b/app/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/js/billboard.pkgd.min.js @@ -0,0 +1,57 @@ +/*! + * Copyright (c) 2017 ~ present NAVER Corp. + * billboard.js project is licensed under the MIT license + * + * billboard.js, JavaScript chart library + * https://naver.github.io/billboard.js/ + * + * @version 3.15.1 + * + * All-in-one packaged file for ease use of 'billboard.js' with dependant d3.js modules & polyfills. + * - @types/d3-selection ^3.0.11 + * - @types/d3-transition ^3.0.9 + * - d3-axis ^3.0.0 + * - d3-brush ^3.0.0 + * - d3-drag ^3.0.0 + * - d3-dsv ^3.0.1 + * - d3-ease ^3.0.1 + * - d3-hierarchy ^3.1.2 + * - d3-interpolate ^3.0.1 + * - d3-scale ^4.0.2 + * - d3-selection ^3.0.0 + * - d3-shape ^3.2.0 + * - d3-time-format ^4.1.0 + * - d3-transition ^3.0.1 + * - d3-zoom ^3.0.0 + */(function(Xa,zn){if(typeof exports=="object"&&typeof module=="object")module.exports=zn();else if(typeof define=="function"&&define.amd)define([],zn);else{var Ha=zn();for(var x in Ha)(typeof exports=="object"?exports:Xa)[x]=Ha[x]}})(this,function(){return function(){"use strict";var Cs=[function(x,b,r){r(1),r(97),r(98),r(99),r(100),r(101),r(102),r(103),r(104),r(105),r(106),r(107),r(108),r(109),r(110),r(111),r(124),r(126),r(136),r(137),r(139),r(143),r(146),r(148),r(150),r(151),r(152),r(153),r(155),r(156),r(158),r(159),r(161),r(165),r(166),r(167),r(168),r(173),r(174),r(176),r(177),r(178),r(180),r(184),r(185),r(186),r(187),r(188),r(193),r(195),r(196),r(198),r(201),r(202),r(203),r(204),r(205),r(207),r(218),r(220),r(221),r(223),r(224),r(227),r(230),r(236),r(237),r(238),r(239),r(240),r(241),r(245),r(246),r(248),r(249),r(250),r(252),r(253),r(254),r(255),r(256),r(261),r(262),r(263),r(264),r(266),r(267),r(268),r(270),r(271),r(272),r(273),r(93),r(274),r(275),r(283),r(285),r(287),r(288),r(289),r(290),r(291),r(293),r(294),r(295),r(296),r(297),r(298),r(300),r(301),r(302),r(303),r(304),r(305),r(306),r(307),r(311),r(312),r(314),r(316),r(317),r(318),r(319),r(320),r(322),r(324),r(325),r(326),r(327),r(329),r(330),r(332),r(333),r(334),r(335),r(337),r(338),r(339),r(340),r(341),r(342),r(343),r(344),r(345),r(347),r(348),r(349),r(350),r(351),r(352),r(353),r(354),r(355),r(356),r(357),r(359),r(360),r(361),r(362),r(386),r(387),r(388),r(389),r(390),r(391),r(392),r(393),r(394),r(395),r(397),r(398),r(399),r(400),r(401),r(402),r(403),r(404),r(405),r(406),r(413),r(415),r(416),r(418),r(419),r(420),r(421),r(422),r(424),r(434),r(436),r(438),r(440),r(442),r(444),r(446),r(447),r(449),r(452),r(453),r(454),r(455),r(456),r(460),r(461),r(463),r(464),r(465),r(466),r(468),r(469),r(470),r(471),r(472),r(473),r(474),r(476),r(479),r(482),r(485),r(486),r(487),r(488),r(489),r(490),r(491),r(492),r(493),r(494),r(495),r(496),r(497),r(505),r(506),r(507),r(508),r(509),r(510),r(511),r(512),r(513),r(514),r(515),r(516),r(517),r(519),r(520),r(521),r(522),r(523),r(524),r(525),r(526),r(527),r(528),r(529),r(530),r(531),r(532),r(533),r(534),r(535),r(536),r(537),r(538),r(539),r(540),r(541),r(542),r(543),r(544),r(545),r(546),r(549),r(551),r(553),r(554),r(557),r(558),r(560),r(561),r(562),r(566),r(567),r(568),r(569),r(572),r(577),r(578),r(579),r(580),r(581),r(582),r(583),r(80)},function(x,b,r){r(2),r(90),r(92),r(93),r(96)},function(x,b,r){var u=r(3),d=r(4),h=r(8),p=r(14),y=r(36),T=r(6),$=r(26),A=r(7),E=r(38),R=r(24),I=r(46),O=r(12),C=r(18),D=r(68),M=r(11),F=r(71),z=r(73),U=r(57),j=r(75),G=r(66),B=r(5),V=r(44),Y=r(72),Z=r(10),J=r(47),q=r(77),nt=r(34),rt=r(53),_=r(54),tt=r(40),et=r(33),lt=r(78),mt=r(79),gt=r(81),xt=r(82),yt=r(51),Ut=r(83).forEach,Dt=rt("hidden"),Xt="Symbol",Qt="prototype",kt=yt.set,me=yt.getterFor(Xt),ge=Object[Qt],ae=d.Symbol,Mt=ae&&ae[Qt],Ht=d.RangeError,re=d.TypeError,se=d.QObject,ee=B.f,fe=V.f,Pe=j.f,Me=Z.f,$e=p([].push),ce=nt("symbols"),Ae=nt("op-symbols"),Te=nt("wks"),de=!se||!se[Qt]||!se[Qt].findChild,bt=function(It,Pt,Ct){var Nt=ee(ge,Pt);Nt&&delete ge[Pt],fe(It,Pt,Ct),Nt&&It!==ge&&fe(ge,Pt,Nt)},Ft=T&&A(function(){return F(fe({},"a",{get:function(){return fe(this,"a",{value:7}).a}})).a!==7})?bt:fe,Tt=function(It,Pt){var Ct=ce[It]=F(Mt);return kt(Ct,{type:Xt,tag:It,description:Pt}),T||(Ct.description=Pt),Ct},qt=function(Pt,Ct,Nt){Pt===ge&&qt(Ae,Ct,Nt),I(Pt);var Et=C(Ct);return I(Nt),E(ce,Et)?(Nt.enumerable?(E(Pt,Dt)&&Pt[Dt][Et]&&(Pt[Dt][Et]=!1),Nt=F(Nt,{enumerable:M(0,!1)})):(E(Pt,Dt)||fe(Pt,Dt,M(1,F(null))),Pt[Dt][Et]=!0),Ft(Pt,Et,Nt)):fe(Pt,Et,Nt)},te=function(Pt,Ct){I(Pt);var Nt=O(Ct),Et=z(Nt).concat(ut(Nt));return Ut(Et,function(ie){(!T||h(Yt,Nt,ie))&&qt(Pt,ie,Nt[ie])}),Pt},Zt=function(Pt,Ct){return Ct===void 0?F(Pt):te(F(Pt),Ct)},Yt=function(Pt){var Ct=C(Pt),Nt=h(Me,this,Ct);return this===ge&&E(ce,Ct)&&!E(Ae,Ct)?!1:Nt||!E(this,Ct)||!E(ce,Ct)||E(this,Dt)&&this[Dt][Ct]?Nt:!0},Ye=function(Pt,Ct){var Nt=O(Pt),Et=C(Ct);if(!(Nt===ge&&E(ce,Et)&&!E(Ae,Et))){var ie=ee(Nt,Et);return ie&&E(ce,Et)&&!(E(Nt,Dt)&&Nt[Dt][Et])&&(ie.enumerable=!0),ie}},Ze=function(Pt){var Ct=Pe(O(Pt)),Nt=[];return Ut(Ct,function(Et){!E(ce,Et)&&!E(_,Et)&&$e(Nt,Et)}),Nt},ut=function(It){var Pt=It===ge,Ct=Pe(Pt?Ae:O(It)),Nt=[];return Ut(Ct,function(Et){E(ce,Et)&&(!Pt||E(ge,Et))&&$e(Nt,ce[Et])}),Nt};$||(ae=function(){if(R(Mt,this))throw new re("Symbol is not a constructor");var Pt=!arguments.length||arguments[0]===void 0?void 0:D(arguments[0]),Ct=tt(Pt),Nt=function(Et){var ie=this===void 0?d:this;ie===ge&&h(Nt,Ae,Et),E(ie,Dt)&&E(ie[Dt],Ct)&&(ie[Dt][Ct]=!1);var we=M(1,Et);try{Ft(ie,Ct,we)}catch(Rt){if(!(Rt instanceof Ht))throw Rt;bt(ie,Ct,we)}};return T&&de&&Ft(ge,Ct,{configurable:!0,set:Nt}),Tt(Ct,Pt)},Mt=ae[Qt],J(Mt,"toString",function(){return me(this).tag}),J(ae,"withoutSetter",function(It){return Tt(tt(It),It)}),Z.f=Yt,V.f=qt,Y.f=te,B.f=Ye,U.f=j.f=Ze,G.f=ut,lt.f=function(It){return Tt(et(It),It)},T&&(q(Mt,"description",{configurable:!0,get:function(){return me(this).description}}),y||J(ge,"propertyIsEnumerable",Yt,{unsafe:!0}))),u({global:!0,constructor:!0,wrap:!0,forced:!$,sham:!$},{Symbol:ae}),Ut(z(Te),function(It){mt(It)}),u({target:Xt,stat:!0,forced:!$},{useSetter:function(){de=!0},useSimple:function(){de=!1}}),u({target:"Object",stat:!0,forced:!$,sham:!T},{create:Zt,defineProperty:qt,defineProperties:te,getOwnPropertyDescriptor:Ye}),u({target:"Object",stat:!0,forced:!$},{getOwnPropertyNames:Ze}),gt(),xt(ae,Xt),_[Dt]=!0},function(x,b,r){var u=r(4),d=r(5).f,h=r(43),p=r(47),y=r(37),T=r(55),$=r(67);x.exports=function(A,E){var R=A.target,I=A.global,O=A.stat,C,D,M,F,z,U;if(I?D=u:O?D=u[R]||y(R,{}):D=u[R]&&u[R].prototype,D)for(M in E){if(z=E[M],A.dontCallGetSet?(U=d(D,M),F=U&&U.value):F=D[M],C=$(I?M:R+(O?".":"#")+M,A.forced),!C&&F!==void 0){if(typeof z==typeof F)continue;T(z,F)}(A.sham||F&&F.sham)&&h(z,"sham",!0),p(D,M,z,A)}}},function(x){var b=function(r){return r&&r.Math===Math&&r};x.exports=b(typeof globalThis=="object"&&globalThis)||b(typeof window=="object"&&window)||b(typeof self=="object"&&self)||b(typeof global=="object"&&global)||b(typeof this=="object"&&this)||function(){return this}()||Function("return this")()},function(x,b,r){var u=r(6),d=r(8),h=r(10),p=r(11),y=r(12),T=r(18),$=r(38),A=r(41),E=Object.getOwnPropertyDescriptor;b.f=u?E:function(I,O){if(I=y(I),O=T(O),A)try{return E(I,O)}catch(C){}if($(I,O))return p(!d(h.f,I,O),I[O])}},function(x,b,r){var u=r(7);x.exports=!u(function(){return Object.defineProperty({},1,{get:function(){return 7}})[1]!==7})},function(x){x.exports=function(b){try{return!!b()}catch(r){return!0}}},function(x,b,r){var u=r(9),d=Function.prototype.call;x.exports=u?d.bind(d):function(){return d.apply(d,arguments)}},function(x,b,r){var u=r(7);x.exports=!u(function(){var d=function(){}.bind();return typeof d!="function"||d.hasOwnProperty("prototype")})},function(x,b){var r={}.propertyIsEnumerable,u=Object.getOwnPropertyDescriptor,d=u&&!r.call({1:2},1);b.f=d?function(p){var y=u(this,p);return!!y&&y.enumerable}:r},function(x){x.exports=function(b,r){return{enumerable:!(b&1),configurable:!(b&2),writable:!(b&4),value:r}}},function(x,b,r){var u=r(13),d=r(16);x.exports=function(h){return u(d(h))}},function(x,b,r){var u=r(14),d=r(7),h=r(15),p=Object,y=u("".split);x.exports=d(function(){return!p("z").propertyIsEnumerable(0)})?function(T){return h(T)==="String"?y(T,""):p(T)}:p},function(x,b,r){var u=r(9),d=Function.prototype,h=d.call,p=u&&d.bind.bind(h,h);x.exports=u?p:function(y){return function(){return h.apply(y,arguments)}}},function(x,b,r){var u=r(14),d=u({}.toString),h=u("".slice);x.exports=function(p){return h(d(p),8,-1)}},function(x,b,r){var u=r(17),d=TypeError;x.exports=function(h){if(u(h))throw new d("Can't call method on "+h);return h}},function(x){x.exports=function(b){return b==null}},function(x,b,r){var u=r(19),d=r(22);x.exports=function(h){var p=u(h,"string");return d(p)?p:p+""}},function(x,b,r){var u=r(8),d=r(20),h=r(22),p=r(29),y=r(32),T=r(33),$=TypeError,A=T("toPrimitive");x.exports=function(E,R){if(!d(E)||h(E))return E;var I=p(E,A),O;if(I){if(R===void 0&&(R="default"),O=u(I,E,R),!d(O)||h(O))return O;throw new $("Can't convert object to primitive value")}return R===void 0&&(R="number"),y(E,R)}},function(x,b,r){var u=r(21);x.exports=function(d){return typeof d=="object"?d!==null:u(d)}},function(x){var b=typeof document=="object"&&document.all;x.exports=typeof b=="undefined"&&b!==void 0?function(r){return typeof r=="function"||r===b}:function(r){return typeof r=="function"}},function(x,b,r){var u=r(23),d=r(21),h=r(24),p=r(25),y=Object;x.exports=p?function(T){return typeof T=="symbol"}:function(T){var $=u("Symbol");return d($)&&h($.prototype,y(T))}},function(x,b,r){var u=r(4),d=r(21),h=function(p){return d(p)?p:void 0};x.exports=function(p,y){return arguments.length<2?h(u[p]):u[p]&&u[p][y]}},function(x,b,r){var u=r(14);x.exports=u({}.isPrototypeOf)},function(x,b,r){var u=r(26);x.exports=u&&!Symbol.sham&&typeof Symbol.iterator=="symbol"},function(x,b,r){var u=r(27),d=r(7),h=r(4),p=h.String;x.exports=!!Object.getOwnPropertySymbols&&!d(function(){var y=Symbol("symbol detection");return!p(y)||!(Object(y)instanceof Symbol)||!Symbol.sham&&u&&u<41})},function(x,b,r){var u=r(4),d=r(28),h=u.process,p=u.Deno,y=h&&h.versions||p&&p.version,T=y&&y.v8,$,A;T&&($=T.split("."),A=$[0]>0&&$[0]<4?1:+($[0]+$[1])),!A&&d&&($=d.match(/Edge\/(\d+)/),(!$||$[1]>=74)&&($=d.match(/Chrome\/(\d+)/),$&&(A=+$[1]))),x.exports=A},function(x,b,r){var u=r(4),d=u.navigator,h=d&&d.userAgent;x.exports=h?String(h):""},function(x,b,r){var u=r(30),d=r(17);x.exports=function(h,p){var y=h[p];return d(y)?void 0:u(y)}},function(x,b,r){var u=r(21),d=r(31),h=TypeError;x.exports=function(p){if(u(p))return p;throw new h(d(p)+" is not a function")}},function(x){var b=String;x.exports=function(r){try{return b(r)}catch(u){return"Object"}}},function(x,b,r){var u=r(8),d=r(21),h=r(20),p=TypeError;x.exports=function(y,T){var $,A;if(T==="string"&&d($=y.toString)&&!h(A=u($,y))||d($=y.valueOf)&&!h(A=u($,y))||T!=="string"&&d($=y.toString)&&!h(A=u($,y)))return A;throw new p("Can't convert object to primitive value")}},function(x,b,r){var u=r(4),d=r(34),h=r(38),p=r(40),y=r(26),T=r(25),$=u.Symbol,A=d("wks"),E=T?$.for||$:$&&$.withoutSetter||p;x.exports=function(R){return h(A,R)||(A[R]=y&&h($,R)?$[R]:E("Symbol."+R)),A[R]}},function(x,b,r){var u=r(35);x.exports=function(d,h){return u[d]||(u[d]=h||{})}},function(x,b,r){var u=r(36),d=r(4),h=r(37),p="__core-js_shared__",y=x.exports=d[p]||h(p,{});(y.versions||(y.versions=[])).push({version:"3.41.0",mode:u?"pure":"global",copyright:"\xA9 2014-2025 Denis Pushkarev (zloirock.ru)",license:"https://github.com/zloirock/core-js/blob/v3.41.0/LICENSE",source:"https://github.com/zloirock/core-js"})},function(x){x.exports=!1},function(x,b,r){var u=r(4),d=Object.defineProperty;x.exports=function(h,p){try{d(u,h,{value:p,configurable:!0,writable:!0})}catch(y){u[h]=p}return p}},function(x,b,r){var u=r(14),d=r(39),h=u({}.hasOwnProperty);x.exports=Object.hasOwn||function(y,T){return h(d(y),T)}},function(x,b,r){var u=r(16),d=Object;x.exports=function(h){return d(u(h))}},function(x,b,r){var u=r(14),d=0,h=Math.random(),p=u(1 .toString);x.exports=function(y){return"Symbol("+(y===void 0?"":y)+")_"+p(++d+h,36)}},function(x,b,r){var u=r(6),d=r(7),h=r(42);x.exports=!u&&!d(function(){return Object.defineProperty(h("div"),"a",{get:function(){return 7}}).a!==7})},function(x,b,r){var u=r(4),d=r(20),h=u.document,p=d(h)&&d(h.createElement);x.exports=function(y){return p?h.createElement(y):{}}},function(x,b,r){var u=r(6),d=r(44),h=r(11);x.exports=u?function(p,y,T){return d.f(p,y,h(1,T))}:function(p,y,T){return p[y]=T,p}},function(x,b,r){var u=r(6),d=r(41),h=r(45),p=r(46),y=r(18),T=TypeError,$=Object.defineProperty,A=Object.getOwnPropertyDescriptor,E="enumerable",R="configurable",I="writable";b.f=u?h?function(C,D,M){if(p(C),D=y(D),p(M),typeof C=="function"&&D==="prototype"&&"value"in M&&I in M&&!M[I]){var F=A(C,D);F&&F[I]&&(C[D]=M.value,M={configurable:R in M?M[R]:F[R],enumerable:E in M?M[E]:F[E],writable:!1})}return $(C,D,M)}:$:function(C,D,M){if(p(C),D=y(D),p(M),d)try{return $(C,D,M)}catch(F){}if("get"in M||"set"in M)throw new T("Accessors not supported");return"value"in M&&(C[D]=M.value),C}},function(x,b,r){var u=r(6),d=r(7);x.exports=u&&d(function(){return Object.defineProperty(function(){},"prototype",{value:42,writable:!1}).prototype!==42})},function(x,b,r){var u=r(20),d=String,h=TypeError;x.exports=function(p){if(u(p))return p;throw new h(d(p)+" is not an object")}},function(x,b,r){var u=r(21),d=r(44),h=r(48),p=r(37);x.exports=function(y,T,$,A){A||(A={});var E=A.enumerable,R=A.name!==void 0?A.name:T;if(u($)&&h($,R,A),A.global)E?y[T]=$:p(T,$);else{try{A.unsafe?y[T]&&(E=!0):delete y[T]}catch(I){}E?y[T]=$:d.f(y,T,{value:$,enumerable:!1,configurable:!A.nonConfigurable,writable:!A.nonWritable})}return y}},function(x,b,r){var u=r(14),d=r(7),h=r(21),p=r(38),y=r(6),T=r(49).CONFIGURABLE,$=r(50),A=r(51),E=A.enforce,R=A.get,I=String,O=Object.defineProperty,C=u("".slice),D=u("".replace),M=u([].join),F=y&&!d(function(){return O(function(){},"length",{value:8}).length!==8}),z=String(String).split("String"),U=x.exports=function(j,G,B){C(I(G),0,7)==="Symbol("&&(G="["+D(I(G),/^Symbol\(([^)]*)\).*$/,"$1")+"]"),B&&B.getter&&(G="get "+G),B&&B.setter&&(G="set "+G),(!p(j,"name")||T&&j.name!==G)&&(y?O(j,"name",{value:G,configurable:!0}):j.name=G),F&&B&&p(B,"arity")&&j.length!==B.arity&&O(j,"length",{value:B.arity});try{B&&p(B,"constructor")&&B.constructor?y&&O(j,"prototype",{writable:!1}):j.prototype&&(j.prototype=void 0)}catch(Y){}var V=E(j);return p(V,"source")||(V.source=M(z,typeof G=="string"?G:"")),j};Function.prototype.toString=U(function(){return h(this)&&R(this).source||$(this)},"toString")},function(x,b,r){var u=r(6),d=r(38),h=Function.prototype,p=u&&Object.getOwnPropertyDescriptor,y=d(h,"name"),T=y&&function(){}.name==="something",$=y&&(!u||u&&p(h,"name").configurable);x.exports={EXISTS:y,PROPER:T,CONFIGURABLE:$}},function(x,b,r){var u=r(14),d=r(21),h=r(35),p=u(Function.toString);d(h.inspectSource)||(h.inspectSource=function(y){return p(y)}),x.exports=h.inspectSource},function(x,b,r){var u=r(52),d=r(4),h=r(20),p=r(43),y=r(38),T=r(35),$=r(53),A=r(54),E="Object already initialized",R=d.TypeError,I=d.WeakMap,O,C,D,M=function(j){return D(j)?C(j):O(j,{})},F=function(j){return function(G){var B;if(!h(G)||(B=C(G)).type!==j)throw new R("Incompatible receiver, "+j+" required");return B}};if(u||T.state){var z=T.state||(T.state=new I);z.get=z.get,z.has=z.has,z.set=z.set,O=function(j,G){if(z.has(j))throw new R(E);return G.facade=j,z.set(j,G),G},C=function(j){return z.get(j)||{}},D=function(j){return z.has(j)}}else{var U=$("state");A[U]=!0,O=function(j,G){if(y(j,U))throw new R(E);return G.facade=j,p(j,U,G),G},C=function(j){return y(j,U)?j[U]:{}},D=function(j){return y(j,U)}}x.exports={set:O,get:C,has:D,enforce:M,getterFor:F}},function(x,b,r){var u=r(4),d=r(21),h=u.WeakMap;x.exports=d(h)&&/native code/.test(String(h))},function(x,b,r){var u=r(34),d=r(40),h=u("keys");x.exports=function(p){return h[p]||(h[p]=d(p))}},function(x){x.exports={}},function(x,b,r){var u=r(38),d=r(56),h=r(5),p=r(44);x.exports=function(y,T,$){for(var A=d(T),E=p.f,R=h.f,I=0;IR;)d(E,O=A[R++])&&(~p(I,O)||T(I,O));return I}},function(x,b,r){var u=r(12),d=r(60),h=r(63),p=function(y){return function(T,$,A){var E=u(T),R=h(E);if(R===0)return!y&&-1;var I=d(A,R),O;if(y&&$!==$){for(;R>I;)if(O=E[I++],O!==O)return!0}else for(;R>I;I++)if((y||I in E)&&E[I]===$)return y||I||0;return!y&&-1}};x.exports={includes:p(!0),indexOf:p(!1)}},function(x,b,r){var u=r(61),d=Math.max,h=Math.min;x.exports=function(p,y){var T=u(p);return T<0?d(T+y,0):h(T,y)}},function(x,b,r){var u=r(62);x.exports=function(d){var h=+d;return h!==h||h===0?0:u(h)}},function(x){var b=Math.ceil,r=Math.floor;x.exports=Math.trunc||function(d){var h=+d;return(h>0?r:b)(h)}},function(x,b,r){var u=r(64);x.exports=function(d){return u(d.length)}},function(x,b,r){var u=r(61),d=Math.min;x.exports=function(h){var p=u(h);return p>0?d(p,9007199254740991):0}},function(x){x.exports=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"]},function(x,b){b.f=Object.getOwnPropertySymbols},function(x,b,r){var u=r(7),d=r(21),h=/#|\.prototype\./,p=function(E,R){var I=T[y(E)];return I===A?!0:I===$?!1:d(R)?u(R):!!R},y=p.normalize=function(E){return String(E).replace(h,".").toLowerCase()},T=p.data={},$=p.NATIVE="N",A=p.POLYFILL="P";x.exports=p},function(x,b,r){var u=r(69),d=String;x.exports=function(h){if(u(h)==="Symbol")throw new TypeError("Cannot convert a Symbol value to a string");return d(h)}},function(x,b,r){var u=r(70),d=r(21),h=r(15),p=r(33),y=p("toStringTag"),T=Object,$=h(function(){return arguments}())==="Arguments",A=function(E,R){try{return E[R]}catch(I){}};x.exports=u?h:function(E){var R,I,O;return E===void 0?"Undefined":E===null?"Null":typeof(I=A(R=T(E),y))=="string"?I:$?h(R):(O=h(R))==="Object"&&d(R.callee)?"Arguments":O}},function(x,b,r){var u=r(33),d=u("toStringTag"),h={};h[d]="z",x.exports=String(h)==="[object z]"},function(x,b,r){var u=r(46),d=r(72),h=r(65),p=r(54),y=r(74),T=r(42),$=r(53),A=">",E="<",R="prototype",I="script",O=$("IE_PROTO"),C=function(){},D=function(j){return E+I+A+j+E+"/"+I+A},M=function(j){j.write(D("")),j.close();var G=j.parentWindow.Object;return j=null,G},F=function(){var j=T("iframe"),G="java"+I+":",B;return j.style.display="none",y.appendChild(j),j.src=String(G),B=j.contentWindow.document,B.open(),B.write(D("document.F=Object")),B.close(),B.F},z,U=function(){try{z=new ActiveXObject("htmlfile")}catch(G){}U=typeof document!="undefined"?document.domain&&z?M(z):F():M(z);for(var j=h.length;j--;)delete U[R][h[j]];return U()};p[O]=!0,x.exports=Object.create||function(G,B){var V;return G!==null?(C[R]=u(G),V=new C,C[R]=null,V[O]=G):V=U(),B===void 0?V:d.f(V,B)}},function(x,b,r){var u=r(6),d=r(45),h=r(44),p=r(46),y=r(12),T=r(73);b.f=u&&!d?Object.defineProperties:function(A,E){p(A);for(var R=y(E),I=T(E),O=I.length,C=0,D;O>C;)h.f(A,D=I[C++],R[D]);return A}},function(x,b,r){var u=r(58),d=r(65);x.exports=Object.keys||function(p){return u(p,d)}},function(x,b,r){var u=r(23);x.exports=u("document","documentElement")},function(x,b,r){var u=r(15),d=r(12),h=r(57).f,p=r(76),y=typeof window=="object"&&window&&Object.getOwnPropertyNames?Object.getOwnPropertyNames(window):[],T=function($){try{return h($)}catch(A){return p(y)}};x.exports.f=function(A){return y&&u(A)==="Window"?T(A):h(d(A))}},function(x,b,r){var u=r(14);x.exports=u([].slice)},function(x,b,r){var u=r(48),d=r(44);x.exports=function(h,p,y){return y.get&&u(y.get,p,{getter:!0}),y.set&&u(y.set,p,{setter:!0}),d.f(h,p,y)}},function(x,b,r){var u=r(33);b.f=u},function(x,b,r){var u=r(80),d=r(38),h=r(78),p=r(44).f;x.exports=function(y){var T=u.Symbol||(u.Symbol={});d(T,y)||p(T,y,{value:h.f(y)})}},function(x,b,r){var u=r(4);x.exports=u},function(x,b,r){var u=r(8),d=r(23),h=r(33),p=r(47);x.exports=function(){var y=d("Symbol"),T=y&&y.prototype,$=T&&T.valueOf,A=h("toPrimitive");T&&!T[A]&&p(T,A,function(E){return u($,this)},{arity:1})}},function(x,b,r){var u=r(44).f,d=r(38),h=r(33),p=h("toStringTag");x.exports=function(y,T,$){y&&!$&&(y=y.prototype),y&&!d(y,p)&&u(y,p,{configurable:!0,value:T})}},function(x,b,r){var u=r(84),d=r(14),h=r(13),p=r(39),y=r(63),T=r(86),$=d([].push),A=function(E){var R=E===1,I=E===2,O=E===3,C=E===4,D=E===6,M=E===7,F=E===5||D;return function(z,U,j,G){for(var B=p(z),V=h(B),Y=y(V),Z=u(U,j),J=0,q=G||T,nt=R?q(z,Y):I||M?q(z,0):void 0,rt,_;Y>J;J++)if((F||J in V)&&(rt=V[J],_=Z(rt,J,B),E))if(R)nt[J]=_;else if(_)switch(E){case 3:return!0;case 5:return rt;case 6:return J;case 2:$(nt,rt)}else switch(E){case 4:return!1;case 7:$(nt,rt)}return D?-1:O||C?C:nt}};x.exports={forEach:A(0),map:A(1),filter:A(2),some:A(3),every:A(4),find:A(5),findIndex:A(6),filterReject:A(7)}},function(x,b,r){var u=r(85),d=r(30),h=r(9),p=u(u.bind);x.exports=function(y,T){return d(y),T===void 0?y:h?p(y,T):function(){return y.apply(T,arguments)}}},function(x,b,r){var u=r(15),d=r(14);x.exports=function(h){if(u(h)==="Function")return d(h)}},function(x,b,r){var u=r(87);x.exports=function(d,h){return new(u(d))(h===0?0:h)}},function(x,b,r){var u=r(88),d=r(89),h=r(20),p=r(33),y=p("species"),T=Array;x.exports=function($){var A;return u($)&&(A=$.constructor,d(A)&&(A===T||u(A.prototype))?A=void 0:h(A)&&(A=A[y],A===null&&(A=void 0))),A===void 0?T:A}},function(x,b,r){var u=r(15);x.exports=Array.isArray||function(h){return u(h)==="Array"}},function(x,b,r){var u=r(14),d=r(7),h=r(21),p=r(69),y=r(23),T=r(50),$=function(){},A=y("Reflect","construct"),E=/^\s*(?:class|function)\b/,R=u(E.exec),I=!E.test($),O=function(M){if(!h(M))return!1;try{return A($,[],M),!0}catch(F){return!1}},C=function(M){if(!h(M))return!1;switch(p(M)){case"AsyncFunction":case"GeneratorFunction":case"AsyncGeneratorFunction":return!1}try{return I||!!R(E,T(M))}catch(F){return!0}};C.sham=!0,x.exports=!A||d(function(){var D;return O(O.call)||!O(Object)||!O(function(){D=!0})||D})?C:O},function(x,b,r){var u=r(3),d=r(23),h=r(38),p=r(68),y=r(34),T=r(91),$=y("string-to-symbol-registry"),A=y("symbol-to-string-registry");u({target:"Symbol",stat:!0,forced:!T},{for:function(E){var R=p(E);if(h($,R))return $[R];var I=d("Symbol")(R);return $[R]=I,A[I]=R,I}})},function(x,b,r){var u=r(26);x.exports=u&&!!Symbol.for&&!!Symbol.keyFor},function(x,b,r){var u=r(3),d=r(38),h=r(22),p=r(31),y=r(34),T=r(91),$=y("symbol-to-string-registry");u({target:"Symbol",stat:!0,forced:!T},{keyFor:function(E){if(!h(E))throw new TypeError(p(E)+" is not a symbol");if(d($,E))return $[E]}})},function(x,b,r){var u=r(3),d=r(23),h=r(94),p=r(8),y=r(14),T=r(7),$=r(21),A=r(22),E=r(76),R=r(95),I=r(26),O=String,C=d("JSON","stringify"),D=y(/./.exec),M=y("".charAt),F=y("".charCodeAt),z=y("".replace),U=y(1 .toString),j=/[\uD800-\uDFFF]/g,G=/^[\uD800-\uDBFF]$/,B=/^[\uDC00-\uDFFF]$/,V=!I||T(function(){var q=d("Symbol")("stringify detection");return C([q])!=="[null]"||C({a:q})!=="{}"||C(Object(q))!=="{}"}),Y=T(function(){return C("\uDF06\uD834")!=='"\\udf06\\ud834"'||C("\uDEAD")!=='"\\udead"'}),Z=function(q,nt){var rt=E(arguments),_=R(nt);if(!(!$(_)&&(q===void 0||A(q))))return rt[1]=function(tt,et){if($(_)&&(et=p(_,this,O(tt),et)),!A(et))return et},h(C,null,rt)},J=function(q,nt,rt){var _=M(rt,nt-1),tt=M(rt,nt+1);return D(G,q)&&!D(B,tt)||D(B,q)&&!D(G,_)?"\\u"+U(F(q,0),16):q};C&&u({target:"JSON",stat:!0,arity:3,forced:V||Y},{stringify:function(nt,rt,_){var tt=E(arguments),et=h(V?Z:C,null,tt);return Y&&typeof et=="string"?z(et,j,J):et}})},function(x,b,r){var u=r(9),d=Function.prototype,h=d.apply,p=d.call;x.exports=typeof Reflect=="object"&&Reflect.apply||(u?p.bind(h):function(){return p.apply(h,arguments)})},function(x,b,r){var u=r(14),d=r(88),h=r(21),p=r(15),y=r(68),T=u([].push);x.exports=function($){if(h($))return $;if(d($)){for(var A=$.length,E=[],R=0;Rj&&R(_,arguments[j]),_});if(J.prototype=Y,B!=="Error"?y?y(J,Z):T(J,Z,{name:!0}):O&&U in V&&($(J,V,U),$(J,V,"prepareStackTrace")),T(J,V),!C)try{Y.name!==B&&h(Y,"name",B),Y.constructor=J}catch(q){}return J}}},function(x,b,r){var u=r(114),d=r(20),h=r(16),p=r(115);x.exports=Object.setPrototypeOf||("__proto__"in{}?function(){var y=!1,T={},$;try{$=u(Object.prototype,"__proto__","set"),$(T,[]),y=T instanceof Array}catch(A){}return function(E,R){return h(E),p(R),d(E)&&(y?$(E,R):E.__proto__=R),E}}():void 0)},function(x,b,r){var u=r(14),d=r(30);x.exports=function(h,p,y){try{return u(d(Object.getOwnPropertyDescriptor(h,p)[y]))}catch(T){}}},function(x,b,r){var u=r(116),d=String,h=TypeError;x.exports=function(p){if(u(p))return p;throw new h("Can't set "+d(p)+" as a prototype")}},function(x,b,r){var u=r(20);x.exports=function(d){return u(d)||d===null}},function(x,b,r){var u=r(44).f;x.exports=function(d,h,p){p in d||u(d,p,{configurable:!0,get:function(){return h[p]},set:function(y){h[p]=y}})}},function(x,b,r){var u=r(21),d=r(20),h=r(113);x.exports=function(p,y,T){var $,A;return h&&u($=y.constructor)&&$!==T&&d(A=$.prototype)&&A!==T.prototype&&h(p,A),p}},function(x,b,r){var u=r(68);x.exports=function(d,h){return d===void 0?arguments.length<2?"":h:u(d)}},function(x,b,r){var u=r(20),d=r(43);x.exports=function(h,p){u(p)&&"cause"in p&&d(h,"cause",p.cause)}},function(x,b,r){var u=r(43),d=r(122),h=r(123),p=Error.captureStackTrace;x.exports=function(y,T,$,A){h&&(p?p(y,T):u(y,"stack",d($,A)))}},function(x,b,r){var u=r(14),d=Error,h=u("".replace),p=function($){return String(new d($).stack)}("zxcasd"),y=/\n\s*at [^:]*:[^\n]*/,T=y.test(p);x.exports=function($,A){if(T&&typeof $=="string"&&!d.prepareStackTrace)for(;A--;)$=h($,y,"");return $}},function(x,b,r){var u=r(7),d=r(11);x.exports=!u(function(){var h=new Error("a");return"stack"in h?(Object.defineProperty(h,"stack",d(1,7)),h.stack!==7):!0})},function(x,b,r){var u=r(47),d=r(125),h=Error.prototype;h.toString!==d&&u(h,"toString",d)},function(x,b,r){var u=r(6),d=r(7),h=r(46),p=r(119),y=Error.prototype.toString,T=d(function(){if(u){var $=Object.create(Object.defineProperty({},"name",{get:function(){return this===$}}));if(y.call($)!=="true")return!0}return y.call({message:1,name:2})!=="2: 1"||y.call({})!=="Error"});x.exports=T?function(){var A=h(this),E=p(A.name,"Error"),R=p(A.message);return E?R?E+": "+R:E:R}:y},function(x,b,r){r(127)},function(x,b,r){var u=r(3),d=r(24),h=r(128),p=r(113),y=r(55),T=r(71),$=r(43),A=r(11),E=r(120),R=r(121),I=r(130),O=r(119),C=r(33),D=C("toStringTag"),M=Error,F=[].push,z=function(G,B){var V=d(U,this),Y;p?Y=p(new M,V?h(this):U):(Y=V?this:T(U),$(Y,D,"Error")),B!==void 0&&$(Y,"message",O(B)),R(Y,z,Y.stack,1),arguments.length>2&&E(Y,arguments[2]);var Z=[];return I(G,F,{that:Z}),$(Y,"errors",Z),Y};p?p(z,M):y(z,M,{name:!0});var U=z.prototype=T(M.prototype,{constructor:A(1,z),message:A(1,""),name:A(1,"AggregateError")});u({global:!0,constructor:!0,arity:2},{AggregateError:z})},function(x,b,r){var u=r(38),d=r(21),h=r(39),p=r(53),y=r(129),T=p("IE_PROTO"),$=Object,A=$.prototype;x.exports=y?$.getPrototypeOf:function(E){var R=h(E);if(u(R,T))return R[T];var I=R.constructor;return d(I)&&R instanceof I?I.prototype:R instanceof $?A:null}},function(x,b,r){var u=r(7);x.exports=!u(function(){function d(){}return d.prototype.constructor=null,Object.getPrototypeOf(new d)!==d.prototype})},function(x,b,r){var u=r(84),d=r(8),h=r(46),p=r(31),y=r(131),T=r(63),$=r(24),A=r(133),E=r(134),R=r(135),I=TypeError,O=function(D,M){this.stopped=D,this.result=M},C=O.prototype;x.exports=function(D,M,F){var z=F&&F.that,U=!!(F&&F.AS_ENTRIES),j=!!(F&&F.IS_RECORD),G=!!(F&&F.IS_ITERATOR),B=!!(F&&F.INTERRUPTED),V=u(M,z),Y,Z,J,q,nt,rt,_,tt=function(lt){return Y&&R(Y,"normal",lt),new O(!0,lt)},et=function(lt){return U?(h(lt),B?V(lt[0],lt[1],tt):V(lt[0],lt[1])):B?V(lt,tt):V(lt)};if(j)Y=D.iterator;else if(G)Y=D;else{if(Z=E(D),!Z)throw new I(p(D)+" is not iterable");if(y(Z)){for(J=0,q=T(D);q>J;J++)if(nt=et(D[J]),nt&&$(C,nt))return nt;return new O(!1)}Y=A(D,Z)}for(rt=j?D.next:Y.next;!(_=d(rt,Y)).done;){try{nt=et(_.value)}catch(lt){R(Y,"throw",lt)}if(typeof nt=="object"&&nt&&$(C,nt))return nt}return new O(!1)}},function(x,b,r){var u=r(33),d=r(132),h=u("iterator"),p=Array.prototype;x.exports=function(y){return y!==void 0&&(d.Array===y||p[h]===y)}},function(x){x.exports={}},function(x,b,r){var u=r(8),d=r(30),h=r(46),p=r(31),y=r(134),T=TypeError;x.exports=function($,A){var E=arguments.length<2?y($):A;if(d(E))return h(u(E,$));throw new T(p($)+" is not iterable")}},function(x,b,r){var u=r(69),d=r(29),h=r(17),p=r(132),y=r(33),T=y("iterator");x.exports=function($){if(!h($))return d($,T)||d($,"@@iterator")||p[u($)]}},function(x,b,r){var u=r(8),d=r(46),h=r(29);x.exports=function(p,y,T){var $,A;d(p);try{if($=h(p,"return"),!$){if(y==="throw")throw T;return T}$=u($,p)}catch(E){A=!0,$=E}if(y==="throw")throw T;if(A)throw $;return d($),T}},function(x,b,r){var u=r(3),d=r(23),h=r(94),p=r(7),y=r(112),T="AggregateError",$=d(T),A=!p(function(){return $([1]).errors[0]!==1})&&p(function(){return $([1],T,{cause:7}).cause!==7});u({global:!0,constructor:!0,arity:2,forced:A},{AggregateError:y(T,function(E){return function(I,O){return h(E,this,arguments)}},A,!0)})},function(x,b,r){var u=r(3),d=r(39),h=r(63),p=r(61),y=r(138);u({target:"Array",proto:!0},{at:function($){var A=d(this),E=h(A),R=p($),I=R>=0?R:E+R;return I<0||I>=E?void 0:A[I]}}),y("at")},function(x,b,r){var u=r(33),d=r(71),h=r(44).f,p=u("unscopables"),y=Array.prototype;y[p]===void 0&&h(y,p,{configurable:!0,value:d(null)}),x.exports=function(T){y[p][T]=!0}},function(x,b,r){var u=r(3),d=r(7),h=r(88),p=r(20),y=r(39),T=r(63),$=r(140),A=r(141),E=r(86),R=r(142),I=r(33),O=r(27),C=I("isConcatSpreadable"),D=O>=51||!d(function(){var z=[];return z[C]=!1,z.concat()[0]!==z}),M=function(z){if(!p(z))return!1;var U=z[C];return U!==void 0?!!U:h(z)},F=!D||!R("concat");u({target:"Array",proto:!0,arity:1,forced:F},{concat:function(U){var j=y(this),G=E(j,0),B=0,V,Y,Z,J,q;for(V=-1,Z=arguments.length;Vr)throw b("Maximum allowed index exceeded");return u}},function(x,b,r){var u=r(6),d=r(44),h=r(11);x.exports=function(p,y,T){u?d.f(p,y,h(0,T)):p[y]=T}},function(x,b,r){var u=r(7),d=r(33),h=r(27),p=d("species");x.exports=function(y){return h>=51||!u(function(){var T=[],$=T.constructor={};return $[p]=function(){return{foo:1}},T[y](Boolean).foo!==1})}},function(x,b,r){var u=r(3),d=r(144),h=r(138);u({target:"Array",proto:!0},{copyWithin:d}),h("copyWithin")},function(x,b,r){var u=r(39),d=r(60),h=r(63),p=r(145),y=Math.min;x.exports=[].copyWithin||function($,A){var E=u(this),R=h(E),I=d($,R),O=d(A,R),C=arguments.length>2?arguments[2]:void 0,D=y((C===void 0?R:d(C,R))-O,R-I),M=1;for(O0;)O in E?E[I]=E[O]:p(E,I),I+=M,O+=M;return E}},function(x,b,r){var u=r(31),d=TypeError;x.exports=function(h,p){if(!delete h[p])throw new d("Cannot delete property "+u(p)+" of "+u(h))}},function(x,b,r){var u=r(3),d=r(83).every,h=r(147),p=h("every");u({target:"Array",proto:!0,forced:!p},{every:function(T){return d(this,T,arguments.length>1?arguments[1]:void 0)}})},function(x,b,r){var u=r(7);x.exports=function(d,h){var p=[][d];return!!p&&u(function(){p.call(null,h||function(){return 1},1)})}},function(x,b,r){var u=r(3),d=r(149),h=r(138);u({target:"Array",proto:!0},{fill:d}),h("fill")},function(x,b,r){var u=r(39),d=r(60),h=r(63);x.exports=function(y){for(var T=u(this),$=h(T),A=arguments.length,E=d(A>1?arguments[1]:void 0,$),R=A>2?arguments[2]:void 0,I=R===void 0?$:d(R,$);I>E;)T[E++]=y;return T}},function(x,b,r){var u=r(3),d=r(83).filter,h=r(142),p=h("filter");u({target:"Array",proto:!0,forced:!p},{filter:function(T){return d(this,T,arguments.length>1?arguments[1]:void 0)}})},function(x,b,r){var u=r(3),d=r(83).find,h=r(138),p="find",y=!0;p in[]&&Array(1)[p](function(){y=!1}),u({target:"Array",proto:!0,forced:y},{find:function($){return d(this,$,arguments.length>1?arguments[1]:void 0)}}),h(p)},function(x,b,r){var u=r(3),d=r(83).findIndex,h=r(138),p="findIndex",y=!0;p in[]&&Array(1)[p](function(){y=!1}),u({target:"Array",proto:!0,forced:y},{findIndex:function($){return d(this,$,arguments.length>1?arguments[1]:void 0)}}),h(p)},function(x,b,r){var u=r(3),d=r(154).findLast,h=r(138);u({target:"Array",proto:!0},{findLast:function(y){return d(this,y,arguments.length>1?arguments[1]:void 0)}}),h("findLast")},function(x,b,r){var u=r(84),d=r(13),h=r(39),p=r(63),y=function(T){var $=T===1;return function(A,E,R){for(var I=h(A),O=d(I),C=p(O),D=u(E,R),M,F;C-- >0;)if(M=O[C],F=D(M,C,I),F)switch(T){case 0:return M;case 1:return C}return $?-1:void 0}};x.exports={findLast:y(0),findLastIndex:y(1)}},function(x,b,r){var u=r(3),d=r(154).findLastIndex,h=r(138);u({target:"Array",proto:!0},{findLastIndex:function(y){return d(this,y,arguments.length>1?arguments[1]:void 0)}}),h("findLastIndex")},function(x,b,r){var u=r(3),d=r(157),h=r(39),p=r(63),y=r(61),T=r(86);u({target:"Array",proto:!0},{flat:function(){var A=arguments.length?arguments[0]:void 0,E=h(this),R=p(E),I=T(E,0);return I.length=d(I,E,E,R,0,A===void 0?1:y(A)),I}})},function(x,b,r){var u=r(88),d=r(63),h=r(140),p=r(84),y=function(T,$,A,E,R,I,O,C){for(var D=R,M=0,F=O?p(O,C):!1,z,U;M0&&u(z)?(U=d(z),D=y(T,$,z,U,D,I-1)-1):(h(D+1),T[D]=z),D++),M++;return D};x.exports=y},function(x,b,r){var u=r(3),d=r(157),h=r(30),p=r(39),y=r(63),T=r(86);u({target:"Array",proto:!0},{flatMap:function(A){var E=p(this),R=y(E),I;return h(A),I=T(E,0),I.length=d(I,E,E,R,0,1,A,arguments.length>1?arguments[1]:void 0),I}})},function(x,b,r){var u=r(3),d=r(160);u({target:"Array",proto:!0,forced:[].forEach!==d},{forEach:d})},function(x,b,r){var u=r(83).forEach,d=r(147),h=d("forEach");x.exports=h?[].forEach:function(y){return u(this,y,arguments.length>1?arguments[1]:void 0)}},function(x,b,r){var u=r(3),d=r(162),h=r(164),p=!h(function(y){Array.from(y)});u({target:"Array",stat:!0,forced:p},{from:d})},function(x,b,r){var u=r(84),d=r(8),h=r(39),p=r(163),y=r(131),T=r(89),$=r(63),A=r(141),E=r(133),R=r(134),I=Array;x.exports=function(C){var D=h(C),M=T(this),F=arguments.length,z=F>1?arguments[1]:void 0,U=z!==void 0;U&&(z=u(z,F>2?arguments[2]:void 0));var j=R(D),G=0,B,V,Y,Z,J,q;if(j&&!(this===I&&y(j)))for(V=M?new this:[],Z=E(D,j),J=Z.next;!(Y=d(J,Z)).done;G++)q=U?p(Z,z,[Y.value,G],!0):Y.value,A(V,G,q);else for(B=$(D),V=M?new this(B):I(B);B>G;G++)q=U?z(D[G],G):D[G],A(V,G,q);return V.length=G,V}},function(x,b,r){var u=r(46),d=r(135);x.exports=function(h,p,y,T){try{return T?p(u(y)[0],y[1]):p(y)}catch($){d(h,"throw",$)}}},function(x,b,r){var u=r(33),d=u("iterator"),h=!1;try{var p=0,y={next:function(){return{done:!!p++}},return:function(){h=!0}};y[d]=function(){return this},Array.from(y,function(){throw 2})}catch(T){}x.exports=function(T,$){try{if(!$&&!h)return!1}catch(R){return!1}var A=!1;try{var E={};E[d]=function(){return{next:function(){return{done:A=!0}}}},T(E)}catch(R){}return A}},function(x,b,r){var u=r(3),d=r(59).includes,h=r(7),p=r(138),y=h(function(){return!Array(1).includes()});u({target:"Array",proto:!0,forced:y},{includes:function($){return d(this,$,arguments.length>1?arguments[1]:void 0)}}),p("includes")},function(x,b,r){var u=r(3),d=r(85),h=r(59).indexOf,p=r(147),y=d([].indexOf),T=!!y&&1/y([1],1,-0)<0,$=T||!p("indexOf");u({target:"Array",proto:!0,forced:$},{indexOf:function(E){var R=arguments.length>1?arguments[1]:void 0;return T?y(this,E,R)||0:h(this,E,R)}})},function(x,b,r){var u=r(3),d=r(88);u({target:"Array",stat:!0},{isArray:d})},function(x,b,r){var u=r(12),d=r(138),h=r(132),p=r(51),y=r(44).f,T=r(169),$=r(172),A=r(36),E=r(6),R="Array Iterator",I=p.set,O=p.getterFor(R);x.exports=T(Array,"Array",function(D,M){I(this,{type:R,target:u(D),index:0,kind:M})},function(){var D=O(this),M=D.target,F=D.index++;if(!M||F>=M.length)return D.target=null,$(void 0,!0);switch(D.kind){case"keys":return $(F,!1);case"values":return $(M[F],!1)}return $([F,M[F]],!1)},"values");var C=h.Arguments=h.Array;if(d("keys"),d("values"),d("entries"),!A&&E&&C.name!=="values")try{y(C,"name",{value:"values"})}catch(D){}},function(x,b,r){var u=r(3),d=r(8),h=r(36),p=r(49),y=r(21),T=r(170),$=r(128),A=r(113),E=r(82),R=r(43),I=r(47),O=r(33),C=r(132),D=r(171),M=p.PROPER,F=p.CONFIGURABLE,z=D.IteratorPrototype,U=D.BUGGY_SAFARI_ITERATORS,j=O("iterator"),G="keys",B="values",V="entries",Y=function(){return this};x.exports=function(Z,J,q,nt,rt,_,tt){T(q,J,nt);var et=function(kt){if(kt===rt&&yt)return yt;if(!U&&kt&&kt in gt)return gt[kt];switch(kt){case G:return function(){return new q(this,kt)};case B:return function(){return new q(this,kt)};case V:return function(){return new q(this,kt)}}return function(){return new q(this)}},lt=J+" Iterator",mt=!1,gt=Z.prototype,xt=gt[j]||gt["@@iterator"]||rt&>[rt],yt=!U&&xt||et(rt),Ut=J==="Array"&>.entries||xt,Dt,Xt,Qt;if(Ut&&(Dt=$(Ut.call(new Z)),Dt!==Object.prototype&&Dt.next&&(!h&&$(Dt)!==z&&(A?A(Dt,z):y(Dt[j])||I(Dt,j,Y)),E(Dt,lt,!0,!0),h&&(C[lt]=Y))),M&&rt===B&&xt&&xt.name!==B&&(!h&&F?R(gt,"name",B):(mt=!0,yt=function(){return d(xt,this)})),rt)if(Xt={values:et(B),keys:_?yt:et(G),entries:et(V)},tt)for(Qt in Xt)(U||mt||!(Qt in gt))&&I(gt,Qt,Xt[Qt]);else u({target:J,proto:!0,forced:U||mt},Xt);return(!h||tt)&>[j]!==yt&&I(gt,j,yt,{name:rt}),C[J]=yt,Xt}},function(x,b,r){var u=r(171).IteratorPrototype,d=r(71),h=r(11),p=r(82),y=r(132),T=function(){return this};x.exports=function($,A,E,R){var I=A+" Iterator";return $.prototype=d(u,{next:h(+!R,E)}),p($,I,!1,!0),y[I]=T,$}},function(x,b,r){var u=r(7),d=r(21),h=r(20),p=r(71),y=r(128),T=r(47),$=r(33),A=r(36),E=$("iterator"),R=!1,I,O,C;[].keys&&(C=[].keys(),"next"in C?(O=y(y(C)),O!==Object.prototype&&(I=O)):R=!0);var D=!h(I)||u(function(){var M={};return I[E].call(M)!==M});D?I={}:A&&(I=p(I)),d(I[E])||T(I,E,function(){return this}),x.exports={IteratorPrototype:I,BUGGY_SAFARI_ITERATORS:R}},function(x){x.exports=function(b,r){return{value:b,done:r}}},function(x,b,r){var u=r(3),d=r(14),h=r(13),p=r(12),y=r(147),T=d([].join),$=h!==Object,A=$||!y("join",",");u({target:"Array",proto:!0,forced:A},{join:function(R){return T(p(this),R===void 0?",":R)}})},function(x,b,r){var u=r(3),d=r(175);u({target:"Array",proto:!0,forced:d!==[].lastIndexOf},{lastIndexOf:d})},function(x,b,r){var u=r(94),d=r(12),h=r(61),p=r(63),y=r(147),T=Math.min,$=[].lastIndexOf,A=!!$&&1/[1].lastIndexOf(1,-0)<0,E=y("lastIndexOf"),R=A||!E;x.exports=R?function(O){if(A)return u($,this,arguments)||0;var C=d(this),D=p(C);if(D===0)return-1;var M=D-1;for(arguments.length>1&&(M=T(M,h(arguments[1]))),M<0&&(M=D+M);M>=0;M--)if(M in C&&C[M]===O)return M||0;return-1}:$},function(x,b,r){var u=r(3),d=r(83).map,h=r(142),p=h("map");u({target:"Array",proto:!0,forced:!p},{map:function(T){return d(this,T,arguments.length>1?arguments[1]:void 0)}})},function(x,b,r){var u=r(3),d=r(7),h=r(89),p=r(141),y=Array,T=d(function(){function $(){}return!(y.of.call($)instanceof $)});u({target:"Array",stat:!0,forced:T},{of:function(){for(var A=0,E=arguments.length,R=new(h(this)?this:y)(E);E>A;)p(R,A,arguments[A++]);return R.length=E,R}})},function(x,b,r){var u=r(3),d=r(39),h=r(63),p=r(179),y=r(140),T=r(7),$=T(function(){return[].push.call({length:4294967296},1)!==4294967297}),A=function(){try{Object.defineProperty([],"length",{writable:!1}).push()}catch(R){return R instanceof TypeError}},E=$||!A();u({target:"Array",proto:!0,arity:1,forced:E},{push:function(I){var O=d(this),C=h(O),D=arguments.length;y(C+D);for(var M=0;M79&&p<83,$=T||!h("reduce");u({target:"Array",proto:!0,forced:$},{reduce:function(E){var R=arguments.length;return d(this,E,R,R>1?arguments[1]:void 0)}})},function(x,b,r){var u=r(30),d=r(39),h=r(13),p=r(63),y=TypeError,T="Reduce of empty array with no initial value",$=function(A){return function(E,R,I,O){var C=d(E),D=h(C),M=p(C);if(u(R),M===0&&I<2)throw new y(T);var F=A?M-1:0,z=A?-1:1;if(I<2)for(;;){if(F in D){O=D[F],F+=z;break}if(F+=z,A?F<0:M<=F)throw new y(T)}for(;A?F>=0:M>F;F+=z)F in D&&(O=R(O,D[F],F,C));return O}};x.exports={left:$(!1),right:$(!0)}},function(x,b,r){var u=r(183);x.exports=u==="NODE"},function(x,b,r){var u=r(4),d=r(28),h=r(15),p=function(y){return d.slice(0,y.length)===y};x.exports=function(){return p("Bun/")?"BUN":p("Cloudflare-Workers")?"CLOUDFLARE":p("Deno/")?"DENO":p("Node.js/")?"NODE":u.Bun&&typeof Bun.version=="string"?"BUN":u.Deno&&typeof Deno.version=="object"?"DENO":h(u.process)==="process"?"NODE":u.window&&u.document?"BROWSER":"REST"}()},function(x,b,r){var u=r(3),d=r(181).right,h=r(147),p=r(27),y=r(182),T=!y&&p>79&&p<83,$=T||!h("reduceRight");u({target:"Array",proto:!0,forced:$},{reduceRight:function(E){return d(this,E,arguments.length,arguments.length>1?arguments[1]:void 0)}})},function(x,b,r){var u=r(3),d=r(14),h=r(88),p=d([].reverse),y=[1,2];u({target:"Array",proto:!0,forced:String(y)===String(y.reverse())},{reverse:function(){return h(this)&&(this.length=this.length),p(this)}})},function(x,b,r){var u=r(3),d=r(88),h=r(89),p=r(20),y=r(60),T=r(63),$=r(12),A=r(141),E=r(33),R=r(142),I=r(76),O=R("slice"),C=E("species"),D=Array,M=Math.max;u({target:"Array",proto:!0,forced:!O},{slice:function(z,U){var j=$(this),G=T(j),B=y(z,G),V=y(U===void 0?G:U,G),Y,Z,J;if(d(j)&&(Y=j.constructor,h(Y)&&(Y===D||d(Y.prototype))?Y=void 0:p(Y)&&(Y=Y[C],Y===null&&(Y=void 0)),Y===D||Y===void 0))return I(j,B,V);for(Z=new(Y===void 0?D:Y)(M(V-B,0)),J=0;B1?arguments[1]:void 0)}})},function(x,b,r){var u=r(3),d=r(14),h=r(30),p=r(39),y=r(63),T=r(145),$=r(68),A=r(7),E=r(189),R=r(147),I=r(190),O=r(191),C=r(27),D=r(192),M=[],F=d(M.sort),z=d(M.push),U=A(function(){M.sort(void 0)}),j=A(function(){M.sort(null)}),G=R("sort"),B=!A(function(){if(C)return C<70;if(!(I&&I>3)){if(O)return!0;if(D)return D<603;var Z="",J,q,nt,rt;for(J=65;J<76;J++){switch(q=String.fromCharCode(J),J){case 66:case 69:case 70:case 72:nt=3;break;case 68:case 71:nt=4;break;default:nt=2}for(rt=0;rt<47;rt++)M.push({k:q+rt,v:nt})}for(M.sort(function(_,tt){return tt.v-_.v}),rt=0;rt$(q)?1:-1}};u({target:"Array",proto:!0,forced:V},{sort:function(J){J!==void 0&&h(J);var q=p(this);if(B)return J===void 0?F(q):F(q,J);var nt=[],rt=y(q),_,tt;for(tt=0;tt0;)p[E]=p[--E];E!==$++&&(p[E]=A)}else for(var R=d(T/2),I=h(u(p,0,R),y),O=h(u(p,R),y),C=I.length,D=O.length,M=0,F=0;Mj-Y+V;J--)R(U,J-1)}else if(V>Y)for(J=j-Y;J>G;J--)q=J+Y-1,nt=J+V-1,q in U?U[nt]=U[q]:R(U,nt);for(J=0;J2?p:u(h),$=new d(T);T>y;)$[y]=h[y++];return $}},function(x,b,r){var u=r(4);x.exports=function(d,h){var p=u[d],y=p&&p.prototype;return y&&y[h]}},function(x,b,r){var u=r(3),d=r(138),h=r(140),p=r(63),y=r(60),T=r(12),$=r(61),A=Array,E=Math.max,R=Math.min;u({target:"Array",proto:!0},{toSpliced:function(O,C){var D=T(this),M=p(D),F=y(O,M),z=arguments.length,U=0,j,G,B,V;for(z===0?j=G=0:z===1?(j=0,G=M-F):(j=z-2,G=R(E($(C),0),M-F)),B=h(M+j-G),V=A(B);U=A||R<0)throw new h("Incorrect index");for(var I=new y(A),O=0;O>8&255]},se=function(bt){return[bt&255,bt>>8&255,bt>>16&255,bt>>24&255]},ee=function(bt){return bt[3]<<24|bt[2]<<16|bt[1]<<8|bt[0]},fe=function(bt){return ae(D(bt),23,4)},Pe=function(bt){return ae(bt,52,8)},Me=function(bt,Ft,Tt){$(bt[rt],Ft,{configurable:!0,get:function(){return Tt(this)[Ft]}})},$e=function(bt,Ft,Tt,qt){var te=lt(bt),Zt=C(Tt),Yt=!!qt;if(Zt+Ft>te.byteLength)throw new kt(tt);var Ye=te.bytes,Ze=Zt+te.byteOffset,ut=j(Ye,Ze,Ze+Ft);return Yt?ut:ge(ut)},ce=function(bt,Ft,Tt,qt,te,Zt){var Yt=lt(bt),Ye=C(Tt),Ze=qt(+te),ut=!!Zt;if(Ye+Ft>Yt.byteLength)throw new kt(tt);for(var It=Yt.bytes,Pt=Ye+Yt.byteOffset,Ct=0;CtZt)throw new kt("Wrong offset");if(qt=qt===void 0?Zt-Yt:O(qt),Yt+qt>Zt)throw new kt(_);mt(this,{type:nt,buffer:Ft,byteLength:qt,byteOffset:Yt,bytes:te.bytes}),h||(this.buffer=Ft,this.byteLength=qt,this.byteOffset=Yt)},Dt=Ut[rt],h&&(Me(xt,"byteLength",et),Me(Ut,"buffer",lt),Me(Ut,"byteLength",lt),Me(Ut,"byteOffset",lt)),A(Dt,{getInt8:function(Ft){return $e(this,1,Ft)[0]<<24>>24},getUint8:function(Ft){return $e(this,1,Ft)[0]},getInt16:function(Ft){var Tt=$e(this,2,Ft,arguments.length>1?arguments[1]:!1);return(Tt[1]<<8|Tt[0])<<16>>16},getUint16:function(Ft){var Tt=$e(this,2,Ft,arguments.length>1?arguments[1]:!1);return Tt[1]<<8|Tt[0]},getInt32:function(Ft){return ee($e(this,4,Ft,arguments.length>1?arguments[1]:!1))},getUint32:function(Ft){return ee($e(this,4,Ft,arguments.length>1?arguments[1]:!1))>>>0},getFloat32:function(Ft){return Mt($e(this,4,Ft,arguments.length>1?arguments[1]:!1),23)},getFloat64:function(Ft){return Mt($e(this,8,Ft,arguments.length>1?arguments[1]:!1),52)},setInt8:function(Ft,Tt){ce(this,1,Ft,Ht,Tt)},setUint8:function(Ft,Tt){ce(this,1,Ft,Ht,Tt)},setInt16:function(Ft,Tt){ce(this,2,Ft,re,Tt,arguments.length>2?arguments[2]:!1)},setUint16:function(Ft,Tt){ce(this,2,Ft,re,Tt,arguments.length>2?arguments[2]:!1)},setInt32:function(Ft,Tt){ce(this,4,Ft,se,Tt,arguments.length>2?arguments[2]:!1)},setUint32:function(Ft,Tt){ce(this,4,Ft,se,Tt,arguments.length>2?arguments[2]:!1)},setFloat32:function(Ft,Tt){ce(this,4,Ft,fe,Tt,arguments.length>2?arguments[2]:!1)},setFloat64:function(Ft,Tt){ce(this,8,Ft,Pe,Tt,arguments.length>2?arguments[2]:!1)}});else{var Ae=Z&>.name!==q;!E(function(){gt(1)})||!E(function(){new gt(-1)})||E(function(){return new gt,new gt(1.5),new gt(NaN),gt.length!==1||Ae&&!J})?(xt=function(Ft){return R(this,yt),G(new gt(C(Ft)),this,xt)},xt[rt]=yt,yt.constructor=xt,B(xt,gt)):Ae&&J&&T(gt,"name",q),z&&F(Dt)!==Xt&&z(Dt,Xt);var Te=new Ut(new xt(2)),de=d(Dt.setInt8);Te.setInt8(0,2147483648),Te.setInt8(1,2147483649),(Te.getInt8(0)||!Te.getInt8(1))&&A(Dt,{setInt8:function(Ft,Tt){de(this,Ft,Tt<<24>>24)},setUint8:function(Ft,Tt){de(this,Ft,Tt<<24>>24)}},{unsafe:!0})}V(xt,q),V(Ut,nt),x.exports={ArrayBuffer:xt,DataView:Ut}},function(x){x.exports=typeof ArrayBuffer!="undefined"&&typeof DataView!="undefined"},function(x,b,r){var u=r(47);x.exports=function(d,h,p){for(var y in h)u(d,y,h[y],p);return d}},function(x,b,r){var u=r(24),d=TypeError;x.exports=function(h,p){if(u(p,h))return h;throw new d("Incorrect invocation")}},function(x,b,r){var u=r(61),d=r(64),h=RangeError;x.exports=function(p){if(p===void 0)return 0;var y=u(p),T=d(y);if(y!==T)throw new h("Wrong length or index");return T}},function(x,b,r){var u=r(214),d=11920928955078125e-23,h=34028234663852886e22,p=11754943508222875e-54;x.exports=Math.fround||function(T){return u(T,d,h,p)}},function(x,b,r){var u=r(215),d=r(216),h=Math.abs,p=2220446049250313e-31;x.exports=function(y,T,$,A){var E=+y,R=h(E),I=u(E);if(R$||C!==C?I*(1/0):I*C}},function(x){x.exports=Math.sign||function(r){var u=+r;return u===0||u!==u?u:u<0?-1:1}},function(x){var b=2220446049250313e-31,r=1/b;x.exports=function(u){return u+r-r}},function(x){var b=Array,r=Math.abs,u=Math.pow,d=Math.floor,h=Math.log,p=Math.LN2,y=function($,A,E){var R=b(E),I=E*8-A-1,O=(1<>1,D=A===23?u(2,-24)-u(2,-77):0,M=$<0||$===0&&1/$<0?1:0,F=0,z,U,j;for($=r($),$!==$||$===1/0?(U=$!==$?1:0,z=O):(z=d(h($)/p),j=u(2,-z),$*j<1&&(z--,j*=2),z+C>=1?$+=D/j:$+=D*u(2,1-C),$*j>=2&&(z++,j/=2),z+C>=O?(U=0,z=O):z+C>=1?(U=($*j-1)*u(2,A),z+=C):(U=$*u(2,C-1)*u(2,A),z=0));A>=8;)R[F++]=U&255,U/=256,A-=8;for(z=z<0;)R[F++]=z&255,z/=256,I-=8;return R[F-1]|=M*128,R},T=function($,A){var E=$.length,R=E*8-A-1,I=(1<>1,C=R-7,D=E-1,M=$[D--],F=M&127,z;for(M>>=7;C>0;)F=F*256+$[D--],C-=8;for(z=F&(1<<-C)-1,F>>=-C,C+=A;C>0;)z=z*256+$[D--],C-=8;if(F===0)F=1-O;else{if(F===I)return z?NaN:M?-1/0:1/0;z+=u(2,A),F-=O}return(M?-1:1)*z*u(2,F-A)};x.exports={pack:y,unpack:T}},function(x,b,r){var u=r(3),d=r(219),h=d.NATIVE_ARRAY_BUFFER_VIEWS;u({target:"ArrayBuffer",stat:!0,forced:!h},{isView:d.isView})},function(x,b,r){var u=r(209),d=r(6),h=r(4),p=r(21),y=r(20),T=r(38),$=r(69),A=r(31),E=r(43),R=r(47),I=r(77),O=r(24),C=r(128),D=r(113),M=r(33),F=r(40),z=r(51),U=z.enforce,j=z.get,G=h.Int8Array,B=G&&G.prototype,V=h.Uint8ClampedArray,Y=V&&V.prototype,Z=G&&C(G),J=B&&C(B),q=Object.prototype,nt=h.TypeError,rt=M("toStringTag"),_=F("TYPED_ARRAY_TAG"),tt="TypedArrayConstructor",et=u&&!!D&&$(h.opera)!=="Opera",lt=!1,mt,gt,xt,yt={Int8Array:1,Uint8Array:1,Uint8ClampedArray:1,Int16Array:2,Uint16Array:2,Int32Array:4,Uint32Array:4,Float32Array:4,Float64Array:8},Ut={BigInt64Array:8,BigUint64Array:8},Dt=function(Ht){if(!y(Ht))return!1;var re=$(Ht);return re==="DataView"||T(yt,re)||T(Ut,re)},Xt=function(Mt){var Ht=C(Mt);if(y(Ht)){var re=j(Ht);return re&&T(re,tt)?re[tt]:Xt(Ht)}},Qt=function(Mt){if(!y(Mt))return!1;var Ht=$(Mt);return T(yt,Ht)||T(Ut,Ht)},kt=function(Mt){if(Qt(Mt))return Mt;throw new nt("Target is not a typed array")},me=function(Mt){if(p(Mt)&&(!D||O(Z,Mt)))return Mt;throw new nt(A(Mt)+" is not a typed array constructor")},ge=function(Mt,Ht,re,se){if(d){if(re)for(var ee in yt){var fe=h[ee];if(fe&&T(fe.prototype,Mt))try{delete fe.prototype[Mt]}catch(Pe){try{fe.prototype[Mt]=Ht}catch(Me){}}}(!J[Mt]||re)&&R(J,Mt,re?Ht:et&&B[Mt]||Ht,se)}},ae=function(Mt,Ht,re){var se,ee;if(d){if(D){if(re){for(se in yt)if(ee=h[se],ee&&T(ee,Mt))try{delete ee[Mt]}catch(fe){}}if(!Z[Mt]||re)try{return R(Z,Mt,re?Ht:et&&Z[Mt]||Ht)}catch(fe){}else return}for(se in yt)ee=h[se],ee&&(!ee[Mt]||re)&&R(ee,Mt,Ht)}};for(mt in yt)gt=h[mt],xt=gt&>.prototype,xt?U(xt)[tt]=gt:et=!1;for(mt in Ut)gt=h[mt],xt=gt&>.prototype,xt&&(U(xt)[tt]=gt);if((!et||!p(Z)||Z===Function.prototype)&&(Z=function(){throw new nt("Incorrect invocation")},et))for(mt in yt)h[mt]&&D(h[mt],Z);if((!et||!J||J===q)&&(J=Z.prototype,et))for(mt in yt)h[mt]&&D(h[mt].prototype,J);if(et&&C(Y)!==J&&D(Y,J),d&&!T(J,rt)){lt=!0,I(J,rt,{configurable:!0,get:function(){return y(this)?this[_]:void 0}});for(mt in yt)h[mt]&&E(h[mt],_,mt)}x.exports={NATIVE_ARRAY_BUFFER_VIEWS:et,TYPED_ARRAY_TAG:lt&&_,aTypedArray:kt,aTypedArrayConstructor:me,exportTypedArrayMethod:ge,exportTypedArrayStaticMethod:ae,getTypedArrayConstructor:Xt,isView:Dt,isTypedArray:Qt,TypedArray:Z,TypedArrayPrototype:J}},function(x,b,r){var u=r(3),d=r(85),h=r(7),p=r(208),y=r(46),T=r(60),$=r(64),A=p.ArrayBuffer,E=p.DataView,R=E.prototype,I=d(A.prototype.slice),O=d(R.getUint8),C=d(R.setUint8),D=h(function(){return!new A(2).slice(1,void 0).byteLength});u({target:"ArrayBuffer",proto:!0,unsafe:!0,forced:D},{slice:function(F,z){if(I&&z===void 0)return I(y(this),F);for(var U=y(this).byteLength,j=T(F,U),G=T(z===void 0?U:z,U),B=new A($(G-j)),V=new E(this),Y=new E(B),Z=0;j>>15,O=R>>>10&p,C=R&y;return O===p?C===0?I===0?1/0:-1/0:NaN:O===0?C*(I===0?T:-T):h(2,O-15)*(I===0?1+C*$:-1-C*$)},E=d(DataView.prototype.getUint16);u({target:"DataView",proto:!0},{getFloat16:function(I){var O=E(this,I,arguments.length>1?arguments[1]:!1);return A(O)}})},function(x,b,r){var u=r(3),d=r(14),h=r(225),p=r(212),y=r(226),T=r(216),$=Math.pow,A=65520,E=61005353927612305e-21,R=16777216,I=1024,O=function(D){if(D!==D)return 32256;if(D===0)return(1/D===-1/0)<<15;var M=D<0;if(M&&(D=-D),D>=A)return M<<15|31744;if(D2?arguments[2]:!1)}})},function(x,b,r){var u=r(69),d=TypeError;x.exports=function(h){if(u(h)==="DataView")return h;throw new d("Argument is not a DataView")}},function(x){var b=Math.log,r=Math.LN2;x.exports=Math.log2||function(d){return b(d)/r}},function(x,b,r){var u=r(6),d=r(77),h=r(228),p=ArrayBuffer.prototype;u&&!("detached"in p)&&d(p,"detached",{configurable:!0,get:function(){return h(this)}})},function(x,b,r){var u=r(4),d=r(209),h=r(229),p=u.DataView;x.exports=function(y){if(!d||h(y)!==0)return!1;try{return new p(y),!1}catch(T){return!0}}},function(x,b,r){var u=r(4),d=r(114),h=r(15),p=u.ArrayBuffer,y=u.TypeError;x.exports=p&&d(p.prototype,"byteLength","get")||function(T){if(h(T)!=="ArrayBuffer")throw new y("ArrayBuffer expected");return T.byteLength}},function(x,b,r){var u=r(3),d=r(231);d&&u({target:"ArrayBuffer",proto:!0},{transfer:function(){return d(this,arguments.length?arguments[0]:void 0,!0)}})},function(x,b,r){var u=r(4),d=r(14),h=r(114),p=r(212),y=r(232),T=r(229),$=r(233),A=r(235),E=u.structuredClone,R=u.ArrayBuffer,I=u.DataView,O=Math.min,C=R.prototype,D=I.prototype,M=d(C.slice),F=h(C,"resizable","get"),z=h(C,"maxByteLength","get"),U=d(D.getInt8),j=d(D.setInt8);x.exports=(A||$)&&function(G,B,V){var Y=T(G),Z=B===void 0?Y:p(B),J=!F||!F(G),q;if(y(G),A&&(G=E(G,{transfer:[G]}),Y===Z&&(V||J)))return G;if(Y>=Z&&(!V||J))q=M(G,0,Z);else{var nt=V&&!J&&z?{maxByteLength:z(G)}:void 0;q=new R(Z,nt);for(var rt=new I(G),_=new I(q),tt=O(Z,Y),et=0;et92||p==="NODE"&&h>94||p==="BROWSER"&&h>97)return!1;var T=new ArrayBuffer(8),$=y(T,{transfer:[T]});return T.byteLength!==0||$.byteLength!==8})},function(x,b,r){var u=r(3),d=r(231);d&&u({target:"ArrayBuffer",proto:!0},{transferToFixedLength:function(){return d(this,arguments.length?arguments[0]:void 0,!1)}})},function(x,b,r){var u=r(3),d=r(14),h=r(7),p=h(function(){return new Date(16e11).getYear()!==120}),y=d(Date.prototype.getFullYear);u({target:"Date",proto:!0,forced:p},{getYear:function(){return y(this)-1900}})},function(x,b,r){var u=r(3),d=r(14),h=Date,p=d(h.prototype.getTime);u({target:"Date",stat:!0},{now:function(){return p(new h)}})},function(x,b,r){var u=r(3),d=r(14),h=r(61),p=Date.prototype,y=d(p.getTime),T=d(p.setFullYear);u({target:"Date",proto:!0},{setYear:function(A){y(this);var E=h(A),R=E>=0&&E<=99?E+1900:E;return T(this,R)}})},function(x,b,r){var u=r(3);u({target:"Date",proto:!0},{toGMTString:Date.prototype.toUTCString})},function(x,b,r){var u=r(3),d=r(242);u({target:"Date",proto:!0,forced:Date.prototype.toISOString!==d},{toISOString:d})},function(x,b,r){var u=r(14),d=r(7),h=r(243).start,p=RangeError,y=isFinite,T=Math.abs,$=Date.prototype,A=$.toISOString,E=u($.getTime),R=u($.getUTCDate),I=u($.getUTCFullYear),O=u($.getUTCHours),C=u($.getUTCMilliseconds),D=u($.getUTCMinutes),M=u($.getUTCMonth),F=u($.getUTCSeconds);x.exports=d(function(){return A.call(new Date(-50000000000001))!=="0385-07-25T07:06:39.999Z"})||!d(function(){A.call(new Date(NaN))})?function(){if(!y(E(this)))throw new p("Invalid time value");var U=this,j=I(U),G=C(U),B=j<0?"-":j>9999?"+":"";return B+h(T(j),B?6:4,0)+"-"+h(M(U)+1,2,0)+"-"+h(R(U),2,0)+"T"+h(O(U),2,0)+":"+h(D(U),2,0)+":"+h(F(U),2,0)+"."+h(G,3,0)+"Z"}:A},function(x,b,r){var u=r(14),d=r(64),h=r(68),p=r(244),y=r(16),T=u(p),$=u("".slice),A=Math.ceil,E=function(R){return function(I,O,C){var D=h(y(I)),M=d(O),F=D.length,z=C===void 0?" ":h(C),U,j;return M<=F||z===""?D:(U=M-F,j=T(z,A(U/z.length)),j.length>U&&(j=$(j,0,U)),R?D+j:j+D)}};x.exports={start:E(!1),end:E(!0)}},function(x,b,r){var u=r(61),d=r(68),h=r(16),p=RangeError;x.exports=function(T){var $=d(h(this)),A="",E=u(T);if(E<0||E===1/0)throw new p("Wrong number of repetitions");for(;E>0;(E>>>=1)&&($+=$))E&1&&(A+=$);return A}},function(x,b,r){var u=r(3),d=r(7),h=r(39),p=r(19),y=d(function(){return new Date(NaN).toJSON()!==null||Date.prototype.toJSON.call({toISOString:function(){return 1}})!==1});u({target:"Date",proto:!0,arity:1,forced:y},{toJSON:function($){var A=h(this),E=p(A,"number");return typeof E=="number"&&!isFinite(E)?null:A.toISOString()}})},function(x,b,r){var u=r(38),d=r(47),h=r(247),p=r(33),y=p("toPrimitive"),T=Date.prototype;u(T,y)||d(T,y,h)},function(x,b,r){var u=r(46),d=r(32),h=TypeError;x.exports=function(p){if(u(this),p==="string"||p==="default")p="string";else if(p!=="number")throw new h("Incorrect hint");return d(this,p)}},function(x,b,r){var u=r(14),d=r(47),h=Date.prototype,p="Invalid Date",y="toString",T=u(h[y]),$=u(h.getTime);String(new Date(NaN))!==p&&d(h,y,function(){var E=$(this);return E===E?T(this):p})},function(x,b,r){var u=r(3),d=r(14),h=r(68),p=d("".charAt),y=d("".charCodeAt),T=d(/./.exec),$=d(1 .toString),A=d("".toUpperCase),E=/[\w*+\-./@]/,R=function(I,O){for(var C=$(I,16);C.length1?arguments[1]:void 0),_;_=_?_.next:nt.first;)for(rt(_.value,_.key,this);_&&_.removed;)_=_.previous},has:function(q){return!!Z(this,q)}}),h(B,U?{get:function(q){var nt=Z(this,q);return nt&&nt.value},set:function(q,nt){return Y(this,q===0?0:q,nt)}}:{add:function(q){return Y(this,q=q===0?0:q,q)}}),I&&d(B,"size",{configurable:!0,get:function(){return V(this).size}}),G},setStrong:function(F,z,U){var j=z+" Iterator",G=M(z),B=M(j);A(F,z,function(V,Y){D(this,{type:j,target:V,state:G(V),kind:Y,last:null})},function(){for(var V=B(this),Y=V.kind,Z=V.last;Z&&Z.removed;)Z=Z.previous;return!V.target||!(V.last=Z=Z?Z.next:V.state.first)?(V.target=null,E(void 0,!0)):E(Y==="keys"?Z.key:Y==="values"?Z.value:[Z.key,Z.value],!1)},U?"entries":"values",!U,!0),R(z)}}},function(x,b,r){var u=r(3),d=r(14),h=r(30),p=r(16),y=r(130),T=r(284),$=r(36),A=r(7),E=T.Map,R=T.has,I=T.get,O=T.set,C=d([].push),D=$||A(function(){return E.groupBy("ab",function(M){return M}).get("a").length!==1});u({target:"Map",stat:!0,forced:$||D},{groupBy:function(F,z){p(F),h(z);var U=new E,j=0;return y(F,function(G){var B=z(G,j++);R(U,B)?C(I(U,B),G):O(U,B,[G])}),U}})},function(x,b,r){var u=r(14),d=Map.prototype;x.exports={Map,set:u(d.set),get:u(d.get),has:u(d.has),remove:u(d.delete),proto:d}},function(x,b,r){var u=r(3),d=r(286),h=Math.acosh,p=Math.log,y=Math.sqrt,T=Math.LN2,$=!h||Math.floor(h(Number.MAX_VALUE))!==710||h(1/0)!==1/0;u({target:"Math",stat:!0,forced:$},{acosh:function(E){var R=+E;return R<1?NaN:R>9490626562425156e-8?p(R)+T:d(R-1+y(R-1)*y(R+1))}})},function(x){var b=Math.log;x.exports=Math.log1p||function(u){var d=+u;return d>-1e-8&&d<1e-8?d-d*d/2:b(1+d)}},function(x,b,r){var u=r(3),d=Math.asinh,h=Math.log,p=Math.sqrt;function y($){var A=+$;return!isFinite(A)||A===0?A:A<0?-y(-A):h(A+p(A*A+1))}var T=!(d&&1/d(0)>0);u({target:"Math",stat:!0,forced:T},{asinh:y})},function(x,b,r){var u=r(3),d=Math.atanh,h=Math.log,p=!(d&&1/d(-0)<0);u({target:"Math",stat:!0,forced:p},{atanh:function(T){var $=+T;return $===0?$:h((1+$)/(1-$))/2}})},function(x,b,r){var u=r(3),d=r(215),h=Math.abs,p=Math.pow;u({target:"Math",stat:!0},{cbrt:function(T){var $=+T;return d($)*p(h($),.3333333333333333)}})},function(x,b,r){var u=r(3),d=Math.floor,h=Math.log,p=Math.LOG2E;u({target:"Math",stat:!0},{clz32:function(T){var $=T>>>0;return $?31-d(h($+.5)*p):32}})},function(x,b,r){var u=r(3),d=r(292),h=Math.cosh,p=Math.abs,y=Math.E,T=!h||h(710)===1/0;u({target:"Math",stat:!0,forced:T},{cosh:function(A){var E=d(p(A)-1)+1;return(E+1/(E*y*y))*(y/2)}})},function(x){var b=Math.expm1,r=Math.exp;x.exports=!b||b(10)>22025.465794806718||b(10)<22025.465794806718||b(-2e-17)!==-2e-17?function(d){var h=+d;return h===0?h:h>-1e-6&&h<1e-6?h+h*h/2:r(h)-1}:b},function(x,b,r){var u=r(3),d=r(292);u({target:"Math",stat:!0,forced:d!==Math.expm1},{expm1:d})},function(x,b,r){var u=r(3),d=r(213);u({target:"Math",stat:!0},{fround:d})},function(x,b,r){var u=r(3),d=r(214),h=.0009765625,p=65504,y=6103515625e-14;u({target:"Math",stat:!0},{f16round:function($){return d($,h,p,y)}})},function(x,b,r){var u=r(3),d=Math.hypot,h=Math.abs,p=Math.sqrt,y=!!d&&d(1/0,NaN)!==1/0;u({target:"Math",stat:!0,arity:2,forced:y},{hypot:function($,A){for(var E=0,R=0,I=arguments.length,O=0,C,D;R0?(D=C/O,E+=D*D):E+=C;return O===1/0?1/0:O*p(E)}})},function(x,b,r){var u=r(3),d=r(7),h=Math.imul,p=d(function(){return h(4294967295,5)!==-5||h.length!==2});u({target:"Math",stat:!0,forced:p},{imul:function(T,$){var A=65535,E=+T,R=+$,I=A&E,O=A&R;return 0|I*O+((A&E>>>16)*O+I*(A&R>>>16)<<16>>>0)}})},function(x,b,r){var u=r(3),d=r(299);u({target:"Math",stat:!0},{log10:d})},function(x){var b=Math.log,r=Math.LOG10E;x.exports=Math.log10||function(d){return b(d)*r}},function(x,b,r){var u=r(3),d=r(286);u({target:"Math",stat:!0},{log1p:d})},function(x,b,r){var u=r(3),d=r(226);u({target:"Math",stat:!0},{log2:d})},function(x,b,r){var u=r(3),d=r(215);u({target:"Math",stat:!0},{sign:d})},function(x,b,r){var u=r(3),d=r(7),h=r(292),p=Math.abs,y=Math.exp,T=Math.E,$=d(function(){return Math.sinh(-2e-17)!==-2e-17});u({target:"Math",stat:!0,forced:$},{sinh:function(E){var R=+E;return p(R)<1?(h(R)-h(-R))/2:(y(R-1)-y(-R-1))*(T/2)}})},function(x,b,r){var u=r(3),d=r(292),h=Math.exp;u({target:"Math",stat:!0},{tanh:function(y){var T=+y,$=d(T),A=d(-T);return $===1/0?1:A===1/0?-1:($-A)/(h(T)+h(-T))}})},function(x,b,r){var u=r(82);u(Math,"Math",!0)},function(x,b,r){var u=r(3),d=r(62);u({target:"Math",stat:!0},{trunc:d})},function(x,b,r){var u=r(3),d=r(36),h=r(6),p=r(4),y=r(80),T=r(14),$=r(67),A=r(38),E=r(118),R=r(24),I=r(22),O=r(19),C=r(7),D=r(57).f,M=r(5).f,F=r(44).f,z=r(308),U=r(309).trim,j="Number",G=p[j],B=y[j],V=G.prototype,Y=p.TypeError,Z=T("".slice),J=T("".charCodeAt),q=function(lt){var mt=O(lt,"number");return typeof mt=="bigint"?mt:nt(mt)},nt=function(lt){var mt=O(lt,"number"),gt,xt,yt,Ut,Dt,Xt,Qt,kt;if(I(mt))throw new Y("Cannot convert a Symbol value to a number");if(typeof mt=="string"&&mt.length>2){if(mt=U(mt),gt=J(mt,0),gt===43||gt===45){if(xt=J(mt,2),xt===88||xt===120)return NaN}else if(gt===48){switch(J(mt,1)){case 66:case 98:yt=2,Ut=49;break;case 79:case 111:yt=8,Ut=55;break;default:return+mt}for(Dt=Z(mt,2),Xt=Dt.length,Qt=0;QtUt)return NaN;return parseInt(Dt,yt)}}return+mt},rt=$(j,!G(" 0o1")||!G("0b1")||G("+0x1")),_=function(lt){return R(V,lt)&&C(function(){z(lt)})},tt=function(mt){var gt=arguments.length<1?0:G(q(mt));return _(this)?E(Object(gt),this,tt):gt};tt.prototype=V,rt&&!d&&(V.constructor=tt),u({global:!0,constructor:!0,wrap:!0,forced:rt},{Number:tt});var et=function(lt,mt){for(var gt=h?D(mt):"MAX_VALUE,MIN_VALUE,NaN,NEGATIVE_INFINITY,POSITIVE_INFINITY,EPSILON,MAX_SAFE_INTEGER,MIN_SAFE_INTEGER,isFinite,isInteger,isNaN,isSafeInteger,parseFloat,parseInt,fromString,range".split(","),xt=0,yt;gt.length>xt;xt++)A(mt,yt=gt[xt])&&!A(lt,yt)&&F(lt,yt,M(mt,yt))};d&&B&&et(y[j],B),(rt||d)&&et(y[j],G)},function(x,b,r){var u=r(14);x.exports=u(1 .valueOf)},function(x,b,r){var u=r(14),d=r(16),h=r(68),p=r(310),y=u("".replace),T=RegExp("^["+p+"]+"),$=RegExp("(^|[^"+p+"])["+p+"]+$"),A=function(E){return function(R){var I=h(d(R));return E&1&&(I=y(I,T,"")),E&2&&(I=y(I,$,"$1")),I}};x.exports={start:A(1),end:A(2),trim:A(3)}},function(x){x.exports=` +\v\f\r \xA0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028\u2029\uFEFF`},function(x,b,r){var u=r(3);u({target:"Number",stat:!0,nonConfigurable:!0,nonWritable:!0},{EPSILON:Math.pow(2,-52)})},function(x,b,r){var u=r(3),d=r(313);u({target:"Number",stat:!0},{isFinite:d})},function(x,b,r){var u=r(4),d=u.isFinite;x.exports=Number.isFinite||function(p){return typeof p=="number"&&d(p)}},function(x,b,r){var u=r(3),d=r(315);u({target:"Number",stat:!0},{isInteger:d})},function(x,b,r){var u=r(20),d=Math.floor;x.exports=Number.isInteger||function(p){return!u(p)&&isFinite(p)&&d(p)===p}},function(x,b,r){var u=r(3);u({target:"Number",stat:!0},{isNaN:function(h){return h!==h}})},function(x,b,r){var u=r(3),d=r(315),h=Math.abs;u({target:"Number",stat:!0},{isSafeInteger:function(y){return d(y)&&h(y)<=9007199254740991}})},function(x,b,r){var u=r(3);u({target:"Number",stat:!0,nonConfigurable:!0,nonWritable:!0},{MAX_SAFE_INTEGER:9007199254740991})},function(x,b,r){var u=r(3);u({target:"Number",stat:!0,nonConfigurable:!0,nonWritable:!0},{MIN_SAFE_INTEGER:-9007199254740991})},function(x,b,r){var u=r(3),d=r(321);u({target:"Number",stat:!0,forced:Number.parseFloat!==d},{parseFloat:d})},function(x,b,r){var u=r(4),d=r(7),h=r(14),p=r(68),y=r(309).trim,T=r(310),$=h("".charAt),A=u.parseFloat,E=u.Symbol,R=E&&E.iterator,I=1/A(T+"-0")!==-1/0||R&&!d(function(){A(Object(R))});x.exports=I?function(C){var D=y(p(C)),M=A(D);return M===0&&$(D,0)==="-"?-0:M}:A},function(x,b,r){var u=r(3),d=r(323);u({target:"Number",stat:!0,forced:Number.parseInt!==d},{parseInt:d})},function(x,b,r){var u=r(4),d=r(7),h=r(14),p=r(68),y=r(309).trim,T=r(310),$=u.parseInt,A=u.Symbol,E=A&&A.iterator,R=/^[+-]?0x/i,I=h(R.exec),O=$(T+"08")!==8||$(T+"0x16")!==22||E&&!d(function(){$(Object(E))});x.exports=O?function(D,M){var F=y(p(D));return $(F,M>>>0||(I(R,F)?16:10))}:$},function(x,b,r){var u=r(3),d=r(14),h=r(61),p=r(308),y=r(244),T=r(299),$=r(7),A=RangeError,E=String,R=isFinite,I=Math.abs,O=Math.floor,C=Math.pow,D=Math.round,M=d(1 .toExponential),F=d(y),z=d("".slice),U=M(-69e-12,4)==="-6.9000e-11"&&M(1.255,2)==="1.25e+0"&&M(12345,3)==="1.235e+4"&&M(25,0)==="3e+1",j=function(){return $(function(){M(1,1/0)})&&$(function(){M(1,-1/0)})},G=function(){return!$(function(){M(1/0,1/0),M(NaN,1/0)})},B=!U||!j()||!G();u({target:"Number",proto:!0,forced:B},{toExponential:function(Y){var Z=p(this);if(Y===void 0)return M(Z);var J=h(Y);if(!R(Z))return String(Z);if(J<0||J>20)throw new A("Incorrect fraction digits");if(U)return M(Z,J);var q="",nt,rt,_,tt;if(Z<0&&(q="-",Z=-Z),Z===0)rt=0,nt=F("0",J+1);else{var et=T(Z);rt=O(et);var lt=C(10,rt-J),mt=D(Z/lt);2*Z>=(2*mt+1)*lt&&(mt+=1),mt>=C(10,J+1)&&(mt/=10,rt+=1),nt=E(mt)}return J!==0&&(nt=z(nt,0,1)+"."+z(nt,1)),rt===0?(_="+",tt="0"):(_=rt>0?"+":"-",tt=E(I(rt))),nt+="e"+_+tt,q+nt}})},function(x,b,r){var u=r(3),d=r(14),h=r(61),p=r(308),y=r(244),T=r(7),$=RangeError,A=String,E=Math.floor,R=d(y),I=d("".slice),O=d(1 .toFixed),C=function(j,G,B){return G===0?B:G%2===1?C(j,G-1,B*j):C(j*j,G/2,B)},D=function(j){for(var G=0,B=j;B>=4096;)G+=12,B/=4096;for(;B>=2;)G+=1,B/=2;return G},M=function(j,G,B){for(var V=-1,Y=B;++V<6;)Y+=G*j[V],j[V]=Y%1e7,Y=E(Y/1e7)},F=function(j,G){for(var B=6,V=0;--B>=0;)V+=j[B],j[B]=E(V/G),V=V%G*1e7},z=function(j){for(var G=6,B="";--G>=0;)if(B!==""||G===0||j[G]!==0){var V=A(j[G]);B=B===""?V:B+R("0",7-V.length)+V}return B},U=T(function(){return O(8e-5,3)!=="0.000"||O(.9,0)!=="1"||O(1.255,2)!=="1.25"||O(0xde0b6b3a7640080,0)!=="1000000000000000128"})||!T(function(){O({})});u({target:"Number",proto:!0,forced:U},{toFixed:function(G){var B=p(this),V=h(G),Y=[0,0,0,0,0,0],Z="",J="0",q,nt,rt,_;if(V<0||V>20)throw new $("Incorrect fraction digits");if(B!==B)return"NaN";if(B<=-1e21||B>=1e21)return A(B);if(B<0&&(Z="-",B=-B),B>1e-21)if(q=D(B*C(2,69,1))-69,nt=q<0?B*C(2,-q,1):B/C(2,q,1),nt*=4503599627370496,q=52-q,q>0){for(M(Y,0,nt),rt=V;rt>=7;)M(Y,1e7,0),rt-=7;for(M(Y,C(10,rt,1),0),rt=q-1;rt>=23;)F(Y,8388608),rt-=23;F(Y,1<0?(_=J.length,J=Z+(_<=V?"0."+R("0",V-_)+J:I(J,0,_-V)+"."+I(J,_-V))):J=Z+J,J}})},function(x,b,r){var u=r(3),d=r(14),h=r(7),p=r(308),y=d(1 .toPrecision),T=h(function(){return y(1,void 0)!=="1"})||!h(function(){y({})});u({target:"Number",proto:!0,forced:T},{toPrecision:function(A){return A===void 0?y(p(this)):y(p(this),A)}})},function(x,b,r){var u=r(3),d=r(328);u({target:"Object",stat:!0,arity:2,forced:Object.assign!==d},{assign:d})},function(x,b,r){var u=r(6),d=r(14),h=r(8),p=r(7),y=r(73),T=r(66),$=r(10),A=r(39),E=r(13),R=Object.assign,I=Object.defineProperty,O=d([].concat);x.exports=!R||p(function(){if(u&&R({b:1},R(I({},"a",{enumerable:!0,get:function(){I(this,"b",{value:3,enumerable:!1})}}),{b:2})).b!==1)return!0;var C={},D={},M=Symbol("assign detection"),F="abcdefghijklmnopqrst";return C[M]=7,F.split("").forEach(function(z){D[z]=z}),R({},C)[M]!==7||y(R({},D)).join("")!==F})?function(D,M){for(var F=A(D),z=arguments.length,U=1,j=T.f,G=$.f;z>U;)for(var B=E(arguments[U++]),V=j?O(y(B),j(B)):y(B),Y=V.length,Z=0,J;Y>Z;)J=V[Z++],(!u||h(G,B,J))&&(F[J]=B[J]);return F}:R},function(x,b,r){var u=r(3),d=r(6),h=r(71);u({target:"Object",stat:!0,sham:!d},{create:h})},function(x,b,r){var u=r(3),d=r(6),h=r(331),p=r(30),y=r(39),T=r(44);d&&u({target:"Object",proto:!0,forced:h},{__defineGetter__:function(A,E){T.f(y(this),A,{get:p(E),enumerable:!0,configurable:!0})}})},function(x,b,r){var u=r(36),d=r(4),h=r(7),p=r(192);x.exports=u||!h(function(){if(!(p&&p<535)){var y=Math.random();__defineSetter__.call(null,y,function(){}),delete d[y]}})},function(x,b,r){var u=r(3),d=r(6),h=r(72).f;u({target:"Object",stat:!0,forced:Object.defineProperties!==h,sham:!d},{defineProperties:h})},function(x,b,r){var u=r(3),d=r(6),h=r(44).f;u({target:"Object",stat:!0,forced:Object.defineProperty!==h,sham:!d},{defineProperty:h})},function(x,b,r){var u=r(3),d=r(6),h=r(331),p=r(30),y=r(39),T=r(44);d&&u({target:"Object",proto:!0,forced:h},{__defineSetter__:function(A,E){T.f(y(this),A,{set:p(E),enumerable:!0,configurable:!0})}})},function(x,b,r){var u=r(3),d=r(336).entries;u({target:"Object",stat:!0},{entries:function(p){return d(p)}})},function(x,b,r){var u=r(6),d=r(7),h=r(14),p=r(128),y=r(73),T=r(12),$=r(10).f,A=h($),E=h([].push),R=u&&d(function(){var O=Object.create(null);return O[2]=2,!A(O,2)}),I=function(O){return function(C){for(var D=T(C),M=y(D),F=R&&p(D)===null,z=M.length,U=0,j=[],G;z>U;)G=M[U++],(!u||(F?G in D:A(D,G)))&&E(j,O?[G,D[G]]:D[G]);return j}};x.exports={entries:I(!0),values:I(!1)}},function(x,b,r){var u=r(3),d=r(281),h=r(7),p=r(20),y=r(278).onFreeze,T=Object.freeze,$=h(function(){T(1)});u({target:"Object",stat:!0,forced:$,sham:!d},{freeze:function(E){return T&&p(E)?T(y(E)):E}})},function(x,b,r){var u=r(3),d=r(130),h=r(141);u({target:"Object",stat:!0},{fromEntries:function(y){var T={};return d(y,function($,A){h(T,$,A)},{AS_ENTRIES:!0}),T}})},function(x,b,r){var u=r(3),d=r(7),h=r(12),p=r(5).f,y=r(6),T=!y||d(function(){p(1)});u({target:"Object",stat:!0,forced:T,sham:!y},{getOwnPropertyDescriptor:function(A,E){return p(h(A),E)}})},function(x,b,r){var u=r(3),d=r(6),h=r(56),p=r(12),y=r(5),T=r(141);u({target:"Object",stat:!0,sham:!d},{getOwnPropertyDescriptors:function(A){for(var E=p(A),R=y.f,I=h(E),O={},C=0,D,M;I.length>C;)M=R(E,D=I[C++]),M!==void 0&&T(O,D,M);return O}})},function(x,b,r){var u=r(3),d=r(7),h=r(75).f,p=d(function(){return!Object.getOwnPropertyNames(1)});u({target:"Object",stat:!0,forced:p},{getOwnPropertyNames:h})},function(x,b,r){var u=r(3),d=r(7),h=r(39),p=r(128),y=r(129),T=d(function(){p(1)});u({target:"Object",stat:!0,forced:T,sham:!y},{getPrototypeOf:function(A){return p(h(A))}})},function(x,b,r){var u=r(3),d=r(23),h=r(14),p=r(30),y=r(16),T=r(18),$=r(130),A=r(7),E=Object.groupBy,R=d("Object","create"),I=h([].push),O=!E||A(function(){return E("ab",function(C){return C}).a.length!==1});u({target:"Object",stat:!0,forced:O},{groupBy:function(D,M){y(D),p(M);var F=R(null),z=0;return $(D,function(U){var j=T(M(U,z++));j in F?I(F[j],U):F[j]=[U]}),F}})},function(x,b,r){var u=r(3),d=r(38);u({target:"Object",stat:!0},{hasOwn:d})},function(x,b,r){var u=r(3),d=r(346);u({target:"Object",stat:!0},{is:d})},function(x){x.exports=Object.is||function(r,u){return r===u?r!==0||1/r===1/u:r!==r&&u!==u}},function(x,b,r){var u=r(3),d=r(279);u({target:"Object",stat:!0,forced:Object.isExtensible!==d},{isExtensible:d})},function(x,b,r){var u=r(3),d=r(7),h=r(20),p=r(15),y=r(280),T=Object.isFrozen,$=y||d(function(){T(1)});u({target:"Object",stat:!0,forced:$},{isFrozen:function(E){return!h(E)||y&&p(E)==="ArrayBuffer"?!0:T?T(E):!1}})},function(x,b,r){var u=r(3),d=r(7),h=r(20),p=r(15),y=r(280),T=Object.isSealed,$=y||d(function(){T(1)});u({target:"Object",stat:!0,forced:$},{isSealed:function(E){return!h(E)||y&&p(E)==="ArrayBuffer"?!0:T?T(E):!1}})},function(x,b,r){var u=r(3),d=r(39),h=r(73),p=r(7),y=p(function(){h(1)});u({target:"Object",stat:!0,forced:y},{keys:function($){return h(d($))}})},function(x,b,r){var u=r(3),d=r(6),h=r(331),p=r(39),y=r(18),T=r(128),$=r(5).f;d&&u({target:"Object",proto:!0,forced:h},{__lookupGetter__:function(E){var R=p(this),I=y(E),O;do if(O=$(R,I))return O.get;while(R=T(R))}})},function(x,b,r){var u=r(3),d=r(6),h=r(331),p=r(39),y=r(18),T=r(128),$=r(5).f;d&&u({target:"Object",proto:!0,forced:h},{__lookupSetter__:function(E){var R=p(this),I=y(E),O;do if(O=$(R,I))return O.set;while(R=T(R))}})},function(x,b,r){var u=r(3),d=r(20),h=r(278).onFreeze,p=r(281),y=r(7),T=Object.preventExtensions,$=y(function(){T(1)});u({target:"Object",stat:!0,forced:$,sham:!p},{preventExtensions:function(E){return T&&d(E)?T(h(E)):E}})},function(x,b,r){var u=r(6),d=r(77),h=r(20),p=r(116),y=r(39),T=r(16),$=Object.getPrototypeOf,A=Object.setPrototypeOf,E=Object.prototype,R="__proto__";if(u&&$&&A&&!(R in E))try{d(E,R,{configurable:!0,get:function(){return $(y(this))},set:function(O){var C=T(this);p(O)&&h(C)&&A(C,O)}})}catch(I){}},function(x,b,r){var u=r(3),d=r(20),h=r(278).onFreeze,p=r(281),y=r(7),T=Object.seal,$=y(function(){T(1)});u({target:"Object",stat:!0,forced:$,sham:!p},{seal:function(E){return T&&d(E)?T(h(E)):E}})},function(x,b,r){var u=r(3),d=r(113);u({target:"Object",stat:!0},{setPrototypeOf:d})},function(x,b,r){var u=r(70),d=r(47),h=r(358);u||d(Object.prototype,"toString",h,{unsafe:!0})},function(x,b,r){var u=r(70),d=r(69);x.exports=u?{}.toString:function(){return"[object "+d(this)+"]"}},function(x,b,r){var u=r(3),d=r(336).values;u({target:"Object",stat:!0},{values:function(p){return d(p)}})},function(x,b,r){var u=r(3),d=r(321);u({global:!0,forced:parseFloat!==d},{parseFloat:d})},function(x,b,r){var u=r(3),d=r(323);u({global:!0,forced:parseInt!==d},{parseInt:d})},function(x,b,r){r(363),r(379),r(381),r(382),r(383),r(384)},function(x,b,r){var u=r(3),d=r(36),h=r(182),p=r(4),y=r(8),T=r(47),$=r(113),A=r(82),E=r(194),R=r(30),I=r(21),O=r(20),C=r(211),D=r(364),M=r(366).set,F=r(369),z=r(374),U=r(375),j=r(371),G=r(51),B=r(376),V=r(377),Y=r(378),Z="Promise",J=V.CONSTRUCTOR,q=V.REJECTION_EVENT,nt=V.SUBCLASSING,rt=G.getterFor(Z),_=G.set,tt=B&&B.prototype,et=B,lt=tt,mt=p.TypeError,gt=p.document,xt=p.process,yt=Y.f,Ut=yt,Dt=!!(gt&>.createEvent&&p.dispatchEvent),Xt="unhandledrejection",Qt="rejectionhandled",kt=0,me=1,ge=2,ae=1,Mt=2,Ht,re,se,ee,fe=function(Tt){var qt;return O(Tt)&&I(qt=Tt.then)?qt:!1},Pe=function(Tt,qt){var te=qt.value,Zt=qt.state===me,Yt=Zt?Tt.ok:Tt.fail,Ye=Tt.resolve,Ze=Tt.reject,ut=Tt.domain,It,Pt,Ct;try{Yt?(Zt||(qt.rejection===Mt&&Te(qt),qt.rejection=ae),Yt===!0?It=te:(ut&&ut.enter(),It=Yt(te),ut&&(ut.exit(),Ct=!0)),It===Tt.promise?Ze(new mt("Promise-chain cycle")):(Pt=fe(It))?y(Pt,It,Ye,Ze):Ye(It)):Ze(te)}catch(Nt){ut&&!Ct&&ut.exit(),Ze(Nt)}},Me=function(Tt,qt){Tt.notified||(Tt.notified=!0,F(function(){for(var te=Tt.reactions,Zt;Zt=te.get();)Pe(Zt,Tt);Tt.notified=!1,qt&&!Tt.rejection&&ce(Tt)}))},$e=function(Tt,qt,te){var Zt,Yt;Dt?(Zt=gt.createEvent("Event"),Zt.promise=qt,Zt.reason=te,Zt.initEvent(Tt,!1,!0),p.dispatchEvent(Zt)):Zt={promise:qt,reason:te},!q&&(Yt=p["on"+Tt])?Yt(Zt):Tt===Xt&&z("Unhandled promise rejection",te)},ce=function(Tt){y(M,p,function(){var qt=Tt.facade,te=Tt.value,Zt=Ae(Tt),Yt;if(Zt&&(Yt=U(function(){h?xt.emit("unhandledRejection",te,qt):$e(Xt,qt,te)}),Tt.rejection=h||Ae(Tt)?Mt:ae,Yt.error))throw Yt.value})},Ae=function(Tt){return Tt.rejection!==ae&&!Tt.parent},Te=function(Tt){y(M,p,function(){var qt=Tt.facade;h?xt.emit("rejectionHandled",qt):$e(Qt,qt,Tt.value)})},de=function(Tt,qt,te){return function(Zt){Tt(qt,Zt,te)}},bt=function(Tt,qt,te){Tt.done||(Tt.done=!0,te&&(Tt=te),Tt.value=qt,Tt.state=ge,Me(Tt,!0))},Ft=function(Tt,qt,te){if(!Tt.done){Tt.done=!0,te&&(Tt=te);try{if(Tt.facade===qt)throw new mt("Promise can't be resolved itself");var Zt=fe(qt);Zt?F(function(){var Yt={done:!1};try{y(Zt,qt,de(Ft,Yt,Tt),de(bt,Yt,Tt))}catch(Ye){bt(Yt,Ye,Tt)}}):(Tt.value=qt,Tt.state=me,Me(Tt,!1))}catch(Yt){bt({done:!1},Yt,Tt)}}};if(J&&(et=function(qt){C(this,lt),R(qt),y(Ht,this);var te=rt(this);try{qt(de(Ft,te),de(bt,te))}catch(Zt){bt(te,Zt)}},lt=et.prototype,Ht=function(qt){_(this,{type:Z,done:!1,notified:!1,parent:!1,reactions:new j,rejection:!1,state:kt,value:null})},Ht.prototype=T(lt,"then",function(qt,te){var Zt=rt(this),Yt=yt(D(this,et));return Zt.parent=!0,Yt.ok=I(qt)?qt:!0,Yt.fail=I(te)&&te,Yt.domain=h?xt.domain:void 0,Zt.state===kt?Zt.reactions.add(Yt):F(function(){Pe(Yt,Zt)}),Yt.promise}),re=function(){var Tt=new Ht,qt=rt(Tt);this.promise=Tt,this.resolve=de(Ft,qt),this.reject=de(bt,qt)},Y.f=yt=function(Tt){return Tt===et||Tt===se?new re(Tt):Ut(Tt)},!d&&I(B)&&tt!==Object.prototype)){ee=tt.then,nt||T(tt,"then",function(qt,te){var Zt=this;return new et(function(Yt,Ye){y(ee,Zt,Yt,Ye)}).then(qt,te)},{unsafe:!0});try{delete tt.constructor}catch(Tt){}$&&$(tt,lt)}u({global:!0,constructor:!0,wrap:!0,forced:J},{Promise:et}),A(et,Z,!1,!0),E(Z)},function(x,b,r){var u=r(46),d=r(365),h=r(17),p=r(33),y=p("species");x.exports=function(T,$){var A=u(T).constructor,E;return A===void 0||h(E=u(A)[y])?$:d(E)}},function(x,b,r){var u=r(89),d=r(31),h=TypeError;x.exports=function(p){if(u(p))return p;throw new h(d(p)+" is not a constructor")}},function(x,b,r){var u=r(4),d=r(94),h=r(84),p=r(21),y=r(38),T=r(7),$=r(74),A=r(76),E=r(42),R=r(367),I=r(368),O=r(182),C=u.setImmediate,D=u.clearImmediate,M=u.process,F=u.Dispatch,z=u.Function,U=u.MessageChannel,j=u.String,G=0,B={},V="onreadystatechange",Y,Z,J,q;T(function(){Y=u.location});var nt=function(et){if(y(B,et)){var lt=B[et];delete B[et],lt()}},rt=function(et){return function(){nt(et)}},_=function(et){nt(et.data)},tt=function(et){u.postMessage(j(et),Y.protocol+"//"+Y.host)};(!C||!D)&&(C=function(lt){R(arguments.length,1);var mt=p(lt)?lt:z(lt),gt=A(arguments,1);return B[++G]=function(){d(mt,void 0,gt)},Z(G),G},D=function(lt){delete B[lt]},O?Z=function(et){M.nextTick(rt(et))}:F&&F.now?Z=function(et){F.now(rt(et))}:U&&!I?(J=new U,q=J.port2,J.port1.onmessage=_,Z=h(q.postMessage,q)):u.addEventListener&&p(u.postMessage)&&!u.importScripts&&Y&&Y.protocol!=="file:"&&!T(tt)?(Z=tt,u.addEventListener("message",_,!1)):V in E("script")?Z=function(et){$.appendChild(E("script"))[V]=function(){$.removeChild(this),nt(et)}}:Z=function(et){setTimeout(rt(et),0)}),x.exports={set:C,clear:D}},function(x){var b=TypeError;x.exports=function(r,u){if(r1?p(arguments,1):[],C=y.f(this),D=$(function(){return h(T(I),void 0,O)});return(D.error?C.reject:C.resolve)(D.value),C.promise}})},function(x,b,r){var u=r(3),d=r(378);u({target:"Promise",stat:!0},{withResolvers:function(){var p=d.f(this);return{promise:p.promise,resolve:p.resolve,reject:p.reject}}})},function(x,b,r){var u=r(3),d=r(94),h=r(30),p=r(46),y=r(7),T=!y(function(){Reflect.apply(function(){})});u({target:"Reflect",stat:!0,forced:T},{apply:function(A,E,R){return d(h(A),E,p(R))}})},function(x,b,r){var u=r(3),d=r(23),h=r(94),p=r(251),y=r(365),T=r(46),$=r(20),A=r(71),E=r(7),R=d("Reflect","construct"),I=Object.prototype,O=[].push,C=E(function(){function F(){}return!(R(function(){},[],F)instanceof F)}),D=!E(function(){R(function(){})}),M=C||D;u({target:"Reflect",stat:!0,forced:M,sham:M},{construct:function(z,U){y(z),T(U);var j=arguments.length<3?z:y(arguments[2]);if(D&&!C)return R(z,U,j);if(z===j){switch(U.length){case 0:return new z;case 1:return new z(U[0]);case 2:return new z(U[0],U[1]);case 3:return new z(U[0],U[1],U[2]);case 4:return new z(U[0],U[1],U[2],U[3])}var G=[null];return h(O,G,U),new(h(p,z,G))}var B=j.prototype,V=A($(B)?B:I),Y=h(z,V,U);return $(Y)?Y:V}})},function(x,b,r){var u=r(3),d=r(6),h=r(46),p=r(18),y=r(44),T=r(7),$=T(function(){Reflect.defineProperty(y.f({},1,{value:1}),1,{value:2})});u({target:"Reflect",stat:!0,forced:$,sham:!d},{defineProperty:function(E,R,I){h(E);var O=p(R);h(I);try{return y.f(E,O,I),!0}catch(C){return!1}}})},function(x,b,r){var u=r(3),d=r(46),h=r(5).f;u({target:"Reflect",stat:!0},{deleteProperty:function(y,T){var $=h(d(y),T);return $&&!$.configurable?!1:delete y[T]}})},function(x,b,r){var u=r(3),d=r(8),h=r(20),p=r(46),y=r(396),T=r(5),$=r(128);function A(E,R){var I=arguments.length<3?E:arguments[2],O,C;if(p(E)===I)return E[R];if(O=T.f(E,R),O)return y(O)?O.value:O.get===void 0?void 0:d(O.get,I);if(h(C=$(E)))return A(C,R,I)}u({target:"Reflect",stat:!0},{get:A})},function(x,b,r){var u=r(38);x.exports=function(d){return d!==void 0&&(u(d,"value")||u(d,"writable"))}},function(x,b,r){var u=r(3),d=r(6),h=r(46),p=r(5);u({target:"Reflect",stat:!0,sham:!d},{getOwnPropertyDescriptor:function(T,$){return p.f(h(T),$)}})},function(x,b,r){var u=r(3),d=r(46),h=r(128),p=r(129);u({target:"Reflect",stat:!0,sham:!p},{getPrototypeOf:function(T){return h(d(T))}})},function(x,b,r){var u=r(3);u({target:"Reflect",stat:!0},{has:function(h,p){return p in h}})},function(x,b,r){var u=r(3),d=r(46),h=r(279);u({target:"Reflect",stat:!0},{isExtensible:function(y){return d(y),h(y)}})},function(x,b,r){var u=r(3),d=r(56);u({target:"Reflect",stat:!0},{ownKeys:d})},function(x,b,r){var u=r(3),d=r(23),h=r(46),p=r(281);u({target:"Reflect",stat:!0,sham:!p},{preventExtensions:function(T){h(T);try{var $=d("Object","preventExtensions");return $&&$(T),!0}catch(A){return!1}}})},function(x,b,r){var u=r(3),d=r(8),h=r(46),p=r(20),y=r(396),T=r(7),$=r(44),A=r(5),E=r(128),R=r(11);function I(C,D,M){var F=arguments.length<4?C:arguments[3],z=A.f(h(C),D),U,j,G;if(!z){if(p(j=E(C)))return I(j,D,M,F);z=R(0)}if(y(z)){if(z.writable===!1||!p(F))return!1;if(U=A.f(F,D)){if(U.get||U.set||U.writable===!1)return!1;U.value=M,$.f(F,D,U)}else $.f(F,D,R(0,M))}else{if(G=z.set,G===void 0)return!1;d(G,F,M)}return!0}var O=T(function(){var C=function(){},D=$.f(new C,"a",{configurable:!0});return Reflect.set(C.prototype,"a",1,D)!==!1});u({target:"Reflect",stat:!0,forced:O},{set:I})},function(x,b,r){var u=r(3),d=r(46),h=r(115),p=r(113);p&&u({target:"Reflect",stat:!0},{setPrototypeOf:function(T,$){d(T),h($);try{return p(T,$),!0}catch(A){return!1}}})},function(x,b,r){var u=r(3),d=r(4),h=r(82);u({global:!0},{Reflect:{}}),h(d.Reflect,"Reflect",!0)},function(x,b,r){var u=r(6),d=r(4),h=r(14),p=r(67),y=r(118),T=r(43),$=r(71),A=r(57).f,E=r(24),R=r(407),I=r(68),O=r(408),C=r(410),D=r(117),M=r(47),F=r(7),z=r(38),U=r(51).enforce,j=r(194),G=r(33),B=r(411),V=r(412),Y=G("match"),Z=d.RegExp,J=Z.prototype,q=d.SyntaxError,nt=h(J.exec),rt=h("".charAt),_=h("".replace),tt=h("".indexOf),et=h("".slice),lt=/^\?<[^\s\d!#%&*+<=>@^][^\s!#%&*+<=>@^]*>/,mt=/a/g,gt=/a/g,xt=new Z(mt)!==mt,yt=C.MISSED_STICKY,Ut=C.UNSUPPORTED_Y,Dt=u&&(!xt||yt||B||V||F(function(){return gt[Y]=!1,Z(mt)!==mt||Z(gt)===gt||String(Z(mt,"i"))!=="/a/i"})),Xt=function(ae){for(var Mt=ae.length,Ht=0,re="",se=!1,ee;Ht<=Mt;Ht++){if(ee=rt(ae,Ht),ee==="\\"){re+=ee+rt(ae,++Ht);continue}!se&&ee==="."?re+="[\\s\\S]":(ee==="["?se=!0:ee==="]"&&(se=!1),re+=ee)}return re},Qt=function(ae){for(var Mt=ae.length,Ht=0,re="",se=[],ee=$(null),fe=!1,Pe=!1,Me=0,$e="",ce;Ht<=Mt;Ht++){if(ce=rt(ae,Ht),ce==="\\")ce+=rt(ae,++Ht);else if(ce==="]")fe=!1;else if(!fe)switch(!0){case ce==="[":fe=!0;break;case ce==="(":if(re+=ce,et(ae,Ht+1,Ht+3)==="?:")continue;nt(lt,et(ae,Ht+1))&&(Ht+=2,Pe=!0),Me++;continue;case(ce===">"&&Pe):if($e===""||z(ee,$e))throw new q("Invalid capture group name");ee[$e]=!0,se[se.length]=[$e,Me],Pe=!1,$e="";continue}Pe?$e+=ce:re+=ce}return[re,se]};if(p("RegExp",Dt)){for(var kt=function(Mt,Ht){var re=E(J,this),se=R(Mt),ee=Ht===void 0,fe=[],Pe=Mt,Me,$e,ce,Ae,Te,de;if(!re&&se&&ee&&Mt.constructor===kt)return Mt;if((se||E(J,Mt))&&(Mt=Mt.source,ee&&(Ht=O(Pe))),Mt=Mt===void 0?"":I(Mt),Ht=Ht===void 0?"":I(Ht),Pe=Mt,B&&"dotAll"in mt&&($e=!!Ht&&tt(Ht,"s")>-1,$e&&(Ht=_(Ht,/s/g,""))),Me=Ht,yt&&"sticky"in mt&&(ce=!!Ht&&tt(Ht,"y")>-1,ce&&Ut&&(Ht=_(Ht,/y/g,""))),V&&(Ae=Qt(Mt),Mt=Ae[0],fe=Ae[1]),Te=y(Z(Mt,Ht),re?this:J,kt),($e||ce||fe.length)&&(de=U(Te),$e&&(de.dotAll=!0,de.raw=kt(Xt(Mt),Me)),ce&&(de.sticky=!0),fe.length&&(de.groups=fe)),Mt!==Pe)try{T(Te,"source",Pe===""?"(?:)":Pe)}catch(bt){}return Te},me=A(Z),ge=0;me.length>ge;)D(kt,Z,me[ge++]);J.constructor=kt,kt.prototype=J,M(d,"RegExp",kt,{constructor:!0})}j("RegExp")},function(x,b,r){var u=r(20),d=r(15),h=r(33),p=h("match");x.exports=function(y){var T;return u(y)&&((T=y[p])!==void 0?!!T:d(y)==="RegExp")}},function(x,b,r){var u=r(8),d=r(38),h=r(24),p=r(409),y=RegExp.prototype;x.exports=function(T){var $=T.flags;return $===void 0&&!("flags"in y)&&!d(T,"flags")&&h(y,T)?u(p,T):$}},function(x,b,r){var u=r(46);x.exports=function(){var d=u(this),h="";return d.hasIndices&&(h+="d"),d.global&&(h+="g"),d.ignoreCase&&(h+="i"),d.multiline&&(h+="m"),d.dotAll&&(h+="s"),d.unicode&&(h+="u"),d.unicodeSets&&(h+="v"),d.sticky&&(h+="y"),h}},function(x,b,r){var u=r(7),d=r(4),h=d.RegExp,p=u(function(){var $=h("a","y");return $.lastIndex=2,$.exec("abcd")!==null}),y=p||u(function(){return!h("a","y").sticky}),T=p||u(function(){var $=h("^r","gy");return $.lastIndex=2,$.exec("str")!==null});x.exports={BROKEN_CARET:T,MISSED_STICKY:y,UNSUPPORTED_Y:p}},function(x,b,r){var u=r(7),d=r(4),h=d.RegExp;x.exports=u(function(){var p=h(".","s");return!(p.dotAll&&p.test(` +`)&&p.flags==="s")})},function(x,b,r){var u=r(7),d=r(4),h=d.RegExp;x.exports=u(function(){var p=h("(?b)","g");return p.exec("b").groups.a!=="b"||"b".replace(p,"$c")!=="bc"})},function(x,b,r){var u=r(3),d=r(14),h=r(414),p=r(38),y=r(243).start,T=r(310),$=Array,A=RegExp.escape,E=d("".charAt),R=d("".charCodeAt),I=d(1.1.toString),O=d([].join),C=/^[0-9a-z]/i,D=/^[$()*+./?[\\\]^{|}]/,M=RegExp("^[!\"#%&',\\-:;<=>@`~"+T+"]"),F=d(C.exec),z={" ":"t","\n":"n","\v":"v","\f":"f","\r":"r"},U=function(G){var B=I(R(G,0),16);return B.length<3?"\\x"+y(B,2,"0"):"\\u"+y(B,4,"0")},j=!A||A("ab")!=="\\x61b";u({target:"RegExp",stat:!0,forced:j},{escape:function(B){h(B);for(var V=B.length,Y=$(V),Z=0;Z=56320||Z+1>=V||(R(B,Z+1)&64512)!==56320?Y[Z]=U(J):(Y[Z]=J,Y[++Z]=E(B,Z))}}return O(Y,"")}})},function(x){var b=TypeError;x.exports=function(r){if(typeof r=="string")return r;throw new b("Argument is not a string")}},function(x,b,r){var u=r(6),d=r(411),h=r(15),p=r(77),y=r(51).get,T=RegExp.prototype,$=TypeError;u&&d&&p(T,"dotAll",{configurable:!0,get:function(){if(this!==T){if(h(this)==="RegExp")return!!y(this).dotAll;throw new $("Incompatible receiver, RegExp required")}}})},function(x,b,r){var u=r(3),d=r(417);u({target:"RegExp",proto:!0,forced:/./.exec!==d},{exec:d})},function(x,b,r){var u=r(8),d=r(14),h=r(68),p=r(409),y=r(410),T=r(34),$=r(71),A=r(51).get,E=r(411),R=r(412),I=T("native-string-replace",String.prototype.replace),O=RegExp.prototype.exec,C=O,D=d("".charAt),M=d("".indexOf),F=d("".replace),z=d("".slice),U=function(){var V=/a/,Y=/b*/g;return u(O,V,"a"),u(O,Y,"a"),V.lastIndex!==0||Y.lastIndex!==0}(),j=y.BROKEN_CARET,G=/()??/.exec("")[1]!==void 0,B=U||G||j||E||R;B&&(C=function(Y){var Z=this,J=A(Z),q=h(Y),nt=J.raw,rt,_,tt,et,lt,mt,gt;if(nt)return nt.lastIndex=Z.lastIndex,rt=u(C,nt,q),Z.lastIndex=nt.lastIndex,rt;var xt=J.groups,yt=j&&Z.sticky,Ut=u(p,Z),Dt=Z.source,Xt=0,Qt=q;if(yt&&(Ut=F(Ut,"y",""),M(Ut,"g")===-1&&(Ut+="g"),Qt=z(q,Z.lastIndex),Z.lastIndex>0&&(!Z.multiline||Z.multiline&&D(q,Z.lastIndex-1)!==` +`)&&(Dt="(?: "+Dt+")",Qt=" "+Qt,Xt++),_=new RegExp("^(?:"+Dt+")",Ut)),G&&(_=new RegExp("^"+Dt+"$(?!\\s)",Ut)),U&&(tt=Z.lastIndex),et=u(O,yt?_:Z,Qt),yt?et?(et.input=z(et.input,Xt),et[0]=z(et[0],Xt),et.index=Z.lastIndex,Z.lastIndex+=et[0].length):Z.lastIndex=0:U&&et&&(Z.lastIndex=Z.global?et.index+et[0].length:tt),G&&et&&et.length>1&&u(I,et[0],_,function(){for(lt=1;ltC.size?T(C.getIterator(),function(M){E(O,M)&&A(D,M)}):y(O,function(M){C.includes(M)&&A(D,M)}),D}},function(x,b,r){var u=r(3),d=r(437),h=r(433),p=!h("isDisjointFrom",function(y){return!y});u({target:"Set",proto:!0,real:!0,forced:p},{isDisjointFrom:d})},function(x,b,r){var u=r(426),d=r(427).has,h=r(431),p=r(432),y=r(429),T=r(430),$=r(135);x.exports=function(E){var R=u(this),I=p(E);if(h(R)<=I.size)return y(R,function(C){if(I.includes(C))return!1},!0)!==!1;var O=I.getIterator();return T(O,function(C){if(d(R,C))return $(O,"normal",!1)})!==!1}},function(x,b,r){var u=r(3),d=r(439),h=r(433),p=!h("isSubsetOf",function(y){return y});u({target:"Set",proto:!0,real:!0,forced:p},{isSubsetOf:d})},function(x,b,r){var u=r(426),d=r(431),h=r(429),p=r(432);x.exports=function(T){var $=u(this),A=p(T);return d($)>A.size?!1:h($,function(E){if(!A.includes(E))return!1},!0)!==!1}},function(x,b,r){var u=r(3),d=r(441),h=r(433),p=!h("isSupersetOf",function(y){return!y});u({target:"Set",proto:!0,real:!0,forced:p},{isSupersetOf:d})},function(x,b,r){var u=r(426),d=r(427).has,h=r(431),p=r(432),y=r(430),T=r(135);x.exports=function(A){var E=u(this),R=p(A);if(h(E)=0?C:O+C;return D<0||D>=O?void 0:$(I,D)}})},function(x,b,r){var u=r(3),d=r(448).codeAt;u({target:"String",proto:!0},{codePointAt:function(p){return d(this,p)}})},function(x,b,r){var u=r(14),d=r(61),h=r(68),p=r(16),y=u("".charAt),T=u("".charCodeAt),$=u("".slice),A=function(E){return function(R,I){var O=h(p(R)),C=d(I),D=O.length,M,F;return C<0||C>=D?E?"":void 0:(M=T(O,C),M<55296||M>56319||C+1===D||(F=T(O,C+1))<56320||F>57343?E?y(O,C):M:E?$(O,C,C+2):(M-55296<<10)+(F-56320)+65536)}};x.exports={codeAt:A(!1),charAt:A(!0)}},function(x,b,r){var u=r(3),d=r(85),h=r(5).f,p=r(64),y=r(68),T=r(450),$=r(16),A=r(451),E=r(36),R=d("".slice),I=Math.min,O=A("endsWith"),C=!E&&!O&&!!function(){var D=h(String.prototype,"endsWith");return D&&!D.writable}();u({target:"String",proto:!0,forced:!C&&!O},{endsWith:function(M){var F=y($(this));T(M);var z=arguments.length>1?arguments[1]:void 0,U=F.length,j=z===void 0?U:I(p(z),U),G=y(M);return R(F,j-G.length,j)===G}})},function(x,b,r){var u=r(407),d=TypeError;x.exports=function(h){if(u(h))throw new d("The method doesn't accept regular expressions");return h}},function(x,b,r){var u=r(33),d=u("match");x.exports=function(h){var p=/./;try{"/./"[h](p)}catch(y){try{return p[d]=!1,"/./"[h](p)}catch(T){}}return!1}},function(x,b,r){var u=r(3),d=r(14),h=r(60),p=RangeError,y=String.fromCharCode,T=String.fromCodePoint,$=d([].join),A=!!T&&T.length!==1;u({target:"String",stat:!0,arity:1,forced:A},{fromCodePoint:function(R){for(var I=[],O=arguments.length,C=0,D;O>C;){if(D=+arguments[C++],h(D,1114111)!==D)throw new p(D+" is not a valid code point");I[C]=D<65536?y(D):y(((D-=65536)>>10)+55296,D%1024+56320)}return $(I,"")}})},function(x,b,r){var u=r(3),d=r(14),h=r(450),p=r(16),y=r(68),T=r(451),$=d("".indexOf);u({target:"String",proto:!0,forced:!T("includes")},{includes:function(E){return!!~$(y(p(this)),y(h(E)),arguments.length>1?arguments[1]:void 0)}})},function(x,b,r){var u=r(3),d=r(14),h=r(16),p=r(68),y=d("".charCodeAt);u({target:"String",proto:!0},{isWellFormed:function(){for(var $=p(h(this)),A=$.length,E=0;E=56320||++E>=A||(y($,E)&64512)!==56320))return!1}return!0}})},function(x,b,r){var u=r(448).charAt,d=r(68),h=r(51),p=r(169),y=r(172),T="String Iterator",$=h.set,A=h.getterFor(T);p(String,"String",function(E){$(this,{type:T,string:d(E),index:0})},function(){var R=A(this),I=R.string,O=R.index,C;return O>=I.length?y(void 0,!0):(C=u(I,O),R.index+=C.length,y(C,!1))})},function(x,b,r){var u=r(8),d=r(457),h=r(46),p=r(17),y=r(64),T=r(68),$=r(16),A=r(29),E=r(458),R=r(459);d("match",function(I,O,C){return[function(M){var F=$(this),z=p(M)?void 0:A(M,I);return z?u(z,M,F):new RegExp(M)[I](T(F))},function(D){var M=h(this),F=T(D),z=C(O,M,F);if(z.done)return z.value;if(!M.global)return R(M,F);var U=M.unicode;M.lastIndex=0;for(var j=[],G=0,B;(B=R(M,F))!==null;){var V=T(B[0]);j[G]=V,V===""&&(M.lastIndex=E(F,y(M.lastIndex),U)),G++}return G===0?null:j}]})},function(x,b,r){r(416);var u=r(8),d=r(47),h=r(417),p=r(7),y=r(33),T=r(43),$=y("species"),A=RegExp.prototype;x.exports=function(E,R,I,O){var C=y(E),D=!p(function(){var U={};return U[C]=function(){return 7},""[E](U)!==7}),M=D&&!p(function(){var U=!1,j=/a/;return E==="split"&&(j={},j.constructor={},j.constructor[$]=function(){return j},j.flags="",j[C]=/./[C]),j.exec=function(){return U=!0,null},j[C](""),!U});if(!D||!M||I){var F=/./[C],z=R(C,""[E],function(U,j,G,B,V){var Y=j.exec;return Y===h||Y===A.exec?D&&!V?{done:!0,value:u(F,j,G,B)}:{done:!0,value:u(U,G,j,B)}:{done:!1}});d(String.prototype,E,z[0]),d(A,C,z[1])}O&&T(A[C],"sham",!0)}},function(x,b,r){var u=r(448).charAt;x.exports=function(d,h,p){return h+(p?u(d,h).length:1)}},function(x,b,r){var u=r(8),d=r(46),h=r(21),p=r(15),y=r(417),T=TypeError;x.exports=function($,A){var E=$.exec;if(h(E)){var R=u(E,$,A);return R!==null&&d(R),R}if(p($)==="RegExp")return u(y,$,A);throw new T("RegExp#exec called on incompatible receiver")}},function(x,b,r){var u=r(3),d=r(8),h=r(85),p=r(170),y=r(172),T=r(16),$=r(64),A=r(68),E=r(46),R=r(17),I=r(15),O=r(407),C=r(408),D=r(29),M=r(47),F=r(7),z=r(33),U=r(364),j=r(458),G=r(459),B=r(51),V=r(36),Y=z("matchAll"),Z="RegExp String",J=Z+" Iterator",q=B.set,nt=B.getterFor(J),rt=RegExp.prototype,_=TypeError,tt=h("".indexOf),et=h("".matchAll),lt=!!et&&!F(function(){et("a",/./)}),mt=p(function(yt,Ut,Dt,Xt){q(this,{type:J,regexp:yt,string:Ut,global:Dt,unicode:Xt,done:!1})},Z,function(){var yt=nt(this);if(yt.done)return y(void 0,!0);var Ut=yt.regexp,Dt=yt.string,Xt=G(Ut,Dt);return Xt===null?(yt.done=!0,y(void 0,!0)):yt.global?(A(Xt[0])===""&&(Ut.lastIndex=j(Dt,$(Ut.lastIndex),yt.unicode)),y(Xt,!1)):(yt.done=!0,y(Xt,!1))}),gt=function(xt){var yt=E(this),Ut=A(xt),Dt=U(yt,RegExp),Xt=A(C(yt)),Qt,kt,me;return Qt=new Dt(Dt===RegExp?yt.source:yt,Xt),kt=!!~tt(Xt,"g"),me=!!~tt(Xt,"u"),Qt.lastIndex=$(yt.lastIndex),new mt(Qt,Ut,kt,me)};u({target:"String",proto:!0,forced:lt},{matchAll:function(yt){var Ut=T(this),Dt,Xt,Qt,kt;if(R(yt)){if(lt)return et(Ut,yt)}else{if(O(yt)&&(Dt=A(T(C(yt))),!~tt(Dt,"g")))throw new _("`.matchAll` does not allow non-global regexes");if(lt)return et(Ut,yt);if(Qt=D(yt,Y),Qt===void 0&&V&&I(yt)==="RegExp"&&(Qt=gt),Qt)return d(Qt,yt,Ut)}return Xt=A(Ut),kt=new RegExp(yt,"g"),V?d(gt,kt,Xt):kt[Y](Xt)}}),V||Y in rt||M(rt,Y,gt)},function(x,b,r){var u=r(3),d=r(243).end,h=r(462);u({target:"String",proto:!0,forced:h},{padEnd:function(y){return d(this,y,arguments.length>1?arguments[1]:void 0)}})},function(x,b,r){var u=r(28);x.exports=/Version\/10(?:\.\d+){1,2}(?: [\w./]+)?(?: Mobile\/\w+)? Safari\//.test(u)},function(x,b,r){var u=r(3),d=r(243).start,h=r(462);u({target:"String",proto:!0,forced:h},{padStart:function(y){return d(this,y,arguments.length>1?arguments[1]:void 0)}})},function(x,b,r){var u=r(3),d=r(14),h=r(12),p=r(39),y=r(68),T=r(63),$=d([].push),A=d([].join);u({target:"String",stat:!0},{raw:function(R){var I=h(p(R).raw),O=T(I);if(!O)return"";for(var C=arguments.length,D=[],M=0;;){if($(D,y(I[M++])),M===O)return A(D,"");M")!=="7"});p("replace",function(_,tt,et){var lt=nt?"$":"$0";return[function(gt,xt){var yt=O(this),Ut=A(gt)?void 0:D(gt,U);return Ut?d(Ut,gt,yt,xt):d(tt,I(yt),gt,xt)},function(mt,gt){var xt=T(this),yt=I(mt);if(typeof gt=="string"&&Y(gt,lt)===-1&&Y(gt,"$<")===-1){var Ut=et(tt,xt,yt,gt);if(Ut.done)return Ut.value}var Dt=$(gt);Dt||(gt=I(gt));var Xt=xt.global,Qt;Xt&&(Qt=xt.unicode,xt.lastIndex=0);for(var kt=[],me;me=F(xt,yt),!(me===null||(V(kt,me),!Xt));){var ge=I(me[0]);ge===""&&(xt.lastIndex=C(yt,R(xt.lastIndex),Qt))}for(var ae="",Mt=0,Ht=0;Ht=Mt&&(ae+=Z(yt,Mt,se)+fe,Mt=se+re.length)}return ae+Z(yt,Mt)}]},!rt||!q||nt)},function(x,b,r){var u=r(14),d=r(39),h=Math.floor,p=u("".charAt),y=u("".replace),T=u("".slice),$=/\$([$&'`]|\d{1,2}|<[^>]*>)/g,A=/\$([$&'`]|\d{1,2})/g;x.exports=function(E,R,I,O,C,D){var M=I+E.length,F=O.length,z=A;return C!==void 0&&(C=d(C),z=$),y(D,z,function(U,j){var G;switch(p(j,0)){case"$":return"$";case"&":return E;case"`":return T(R,0,I);case"'":return T(R,M);case"<":G=C[T(j,1,-1)];break;default:var B=+j;if(B===0)return U;if(B>F){var V=h(B/10);return V===0?U:V<=F?O[V-1]===void 0?p(j,1):O[V-1]+p(j,1):U}G=O[B-1]}return G===void 0?"":G})}},function(x,b,r){var u=r(3),d=r(8),h=r(14),p=r(16),y=r(21),T=r(17),$=r(407),A=r(68),E=r(29),R=r(408),I=r(467),O=r(33),C=r(36),D=O("replace"),M=TypeError,F=h("".indexOf),z=h("".replace),U=h("".slice),j=Math.max;u({target:"String",proto:!0},{replaceAll:function(B,V){var Y=p(this),Z,J,q,nt,rt,_,tt,et,lt,mt,gt=0,xt="";if(!T(B)){if(Z=$(B),Z&&(J=A(p(R(B))),!~F(J,"g")))throw new M("`.replaceAll` does not allow non-global regexes");if(q=E(B,D),q)return d(q,B,Y,V);if(C&&Z)return z(A(Y),B,V)}for(nt=A(Y),rt=A(B),_=y(V),_||(V=A(V)),tt=rt.length,et=j(1,tt),lt=F(nt,rt);lt!==-1;)mt=_?A(V(rt,lt,nt)):I(rt,nt,lt,[],void 0,V),xt+=U(nt,gt,lt)+mt,gt=lt+tt,lt=lt+et>nt.length?-1:F(nt,rt,lt+et);return gt1||"".split(/.?/).length;h("split",function(V,Y,Z){var J="0".split(void 0,0).length?function(q,nt){return q===void 0&&nt===0?[]:u(Y,this,q,nt)}:Y;return[function(nt,rt){var _=T(this),tt=y(nt)?void 0:I(nt,V);return tt?u(tt,nt,_,rt):u(J,R(_),nt,rt)},function(q,nt){var rt=p(this),_=R(q);if(!B){var tt=Z(J,rt,_,nt,J!==Y);if(tt.done)return tt.value}var et=$(rt,RegExp),lt=rt.unicode,mt=(rt.ignoreCase?"i":"")+(rt.multiline?"m":"")+(rt.unicode?"u":"")+(M?"g":"y"),gt=new et(M?"^(?:"+rt.source+")":rt,mt),xt=nt===void 0?F:nt>>>0;if(xt===0)return[];if(_.length===0)return O(gt,_)===null?[_]:[];for(var yt=0,Ut=0,Dt=[];Ut<_.length;){gt.lastIndex=M?0:Ut;var Xt=O(gt,M?j(_,Ut):_),Qt;if(Xt===null||(Qt=z(E(gt.lastIndex+(M?Ut:0)),_.length))===yt)Ut=A(_,Ut,lt);else{if(U(Dt,j(_,yt,Ut)),Dt.length===xt)return Dt;for(var kt=1;kt<=Xt.length-1;kt++)if(U(Dt,Xt[kt]),Dt.length===xt)return Dt;Ut=yt=Qt}}return U(Dt,j(_,yt)),Dt}]},B||!G,M)},function(x,b,r){var u=r(3),d=r(85),h=r(5).f,p=r(64),y=r(68),T=r(450),$=r(16),A=r(451),E=r(36),R=d("".slice),I=Math.min,O=A("startsWith"),C=!E&&!O&&!!function(){var D=h(String.prototype,"startsWith");return D&&!D.writable}();u({target:"String",proto:!0,forced:!C&&!O},{startsWith:function(M){var F=y($(this));T(M);var z=p(I(arguments.length>1?arguments[1]:void 0,F.length)),U=y(M);return R(F,z,z+U.length)===U}})},function(x,b,r){var u=r(3),d=r(14),h=r(16),p=r(61),y=r(68),T=d("".slice),$=Math.max,A=Math.min,E=!"".substr||"ab".substr(-1)!=="b";u({target:"String",proto:!0,forced:E},{substr:function(I,O){var C=y(h(this)),D=C.length,M=p(I),F,z;return M===1/0&&(M=0),M<0&&(M=$(D+M,0)),F=O===void 0?D:p(O),F<=0||F===1/0?"":(z=A(M+F,D),M>=z?"":T(C,M,z))}})},function(x,b,r){var u=r(3),d=r(8),h=r(14),p=r(16),y=r(68),T=r(7),$=Array,A=h("".charAt),E=h("".charCodeAt),R=h([].join),I="".toWellFormed,O="\uFFFD",C=I&&T(function(){return d(I,1)!=="1"});u({target:"String",proto:!0,forced:C},{toWellFormed:function(){var M=y(p(this));if(C)return d(I,M);for(var F=M.length,z=$(F),U=0;U=56320||U+1>=F||(E(M,U+1)&64512)!==56320?z[U]=O:(z[U]=A(M,U),z[++U]=A(M,U))}return R(z,"")}})},function(x,b,r){var u=r(3),d=r(309).trim,h=r(475);u({target:"String",proto:!0,forced:h("trim")},{trim:function(){return d(this)}})},function(x,b,r){var u=r(49).PROPER,d=r(7),h=r(310),p="\u200B\x85\u180E";x.exports=function(y){return d(function(){return!!h[y]()||p[y]()!==p||u&&h[y].name!==y})}},function(x,b,r){r(477);var u=r(3),d=r(478);u({target:"String",proto:!0,name:"trimEnd",forced:"".trimEnd!==d},{trimEnd:d})},function(x,b,r){var u=r(3),d=r(478);u({target:"String",proto:!0,name:"trimEnd",forced:"".trimRight!==d},{trimRight:d})},function(x,b,r){var u=r(309).end,d=r(475);x.exports=d("trimEnd")?function(){return u(this)}:"".trimEnd},function(x,b,r){r(480);var u=r(3),d=r(481);u({target:"String",proto:!0,name:"trimStart",forced:"".trimStart!==d},{trimStart:d})},function(x,b,r){var u=r(3),d=r(481);u({target:"String",proto:!0,name:"trimStart",forced:"".trimLeft!==d},{trimLeft:d})},function(x,b,r){var u=r(309).start,d=r(475);x.exports=d("trimStart")?function(){return u(this)}:"".trimStart},function(x,b,r){var u=r(3),d=r(483),h=r(484);u({target:"String",proto:!0,forced:h("anchor")},{anchor:function(y){return d(this,"a","name",y)}})},function(x,b,r){var u=r(14),d=r(16),h=r(68),p=/"/g,y=u("".replace);x.exports=function(T,$,A,E){var R=h(d(T)),I="<"+$;return A!==""&&(I+=" "+A+'="'+y(h(E),p,""")+'"'),I+">"+R+""}},function(x,b,r){var u=r(7);x.exports=function(d){return u(function(){var h=""[d]('"');return h!==h.toLowerCase()||h.split('"').length>3})}},function(x,b,r){var u=r(3),d=r(483),h=r(484);u({target:"String",proto:!0,forced:h("big")},{big:function(){return d(this,"big","","")}})},function(x,b,r){var u=r(3),d=r(483),h=r(484);u({target:"String",proto:!0,forced:h("blink")},{blink:function(){return d(this,"blink","","")}})},function(x,b,r){var u=r(3),d=r(483),h=r(484);u({target:"String",proto:!0,forced:h("bold")},{bold:function(){return d(this,"b","","")}})},function(x,b,r){var u=r(3),d=r(483),h=r(484);u({target:"String",proto:!0,forced:h("fixed")},{fixed:function(){return d(this,"tt","","")}})},function(x,b,r){var u=r(3),d=r(483),h=r(484);u({target:"String",proto:!0,forced:h("fontcolor")},{fontcolor:function(y){return d(this,"font","color",y)}})},function(x,b,r){var u=r(3),d=r(483),h=r(484);u({target:"String",proto:!0,forced:h("fontsize")},{fontsize:function(y){return d(this,"font","size",y)}})},function(x,b,r){var u=r(3),d=r(483),h=r(484);u({target:"String",proto:!0,forced:h("italics")},{italics:function(){return d(this,"i","","")}})},function(x,b,r){var u=r(3),d=r(483),h=r(484);u({target:"String",proto:!0,forced:h("link")},{link:function(y){return d(this,"a","href",y)}})},function(x,b,r){var u=r(3),d=r(483),h=r(484);u({target:"String",proto:!0,forced:h("small")},{small:function(){return d(this,"small","","")}})},function(x,b,r){var u=r(3),d=r(483),h=r(484);u({target:"String",proto:!0,forced:h("strike")},{strike:function(){return d(this,"strike","","")}})},function(x,b,r){var u=r(3),d=r(483),h=r(484);u({target:"String",proto:!0,forced:h("sub")},{sub:function(){return d(this,"sub","","")}})},function(x,b,r){var u=r(3),d=r(483),h=r(484);u({target:"String",proto:!0,forced:h("sup")},{sup:function(){return d(this,"sup","","")}})},function(x,b,r){var u=r(498);u("Float32",function(d){return function(p,y,T){return d(this,p,y,T)}})},function(x,b,r){var u=r(3),d=r(4),h=r(8),p=r(6),y=r(499),T=r(219),$=r(208),A=r(211),E=r(11),R=r(43),I=r(315),O=r(64),C=r(212),D=r(500),M=r(501),F=r(18),z=r(38),U=r(69),j=r(20),G=r(22),B=r(71),V=r(24),Y=r(113),Z=r(57).f,J=r(502),q=r(83).forEach,nt=r(194),rt=r(77),_=r(44),tt=r(5),et=r(199),lt=r(51),mt=r(118),gt=lt.get,xt=lt.set,yt=lt.enforce,Ut=_.f,Dt=tt.f,Xt=d.RangeError,Qt=$.ArrayBuffer,kt=Qt.prototype,me=$.DataView,ge=T.NATIVE_ARRAY_BUFFER_VIEWS,ae=T.TYPED_ARRAY_TAG,Mt=T.TypedArray,Ht=T.TypedArrayPrototype,re=T.isTypedArray,se="BYTES_PER_ELEMENT",ee="Wrong length",fe=function(Ae,Te){rt(Ae,Te,{configurable:!0,get:function(){return gt(this)[Te]}})},Pe=function(Ae){var Te;return V(kt,Ae)||(Te=U(Ae))==="ArrayBuffer"||Te==="SharedArrayBuffer"},Me=function(Ae,Te){return re(Ae)&&!G(Te)&&Te in Ae&&I(+Te)&&Te>=0},$e=function(Te,de){return de=F(de),Me(Te,de)?E(2,Te[de]):Dt(Te,de)},ce=function(Te,de,bt){return de=F(de),Me(Te,de)&&j(bt)&&z(bt,"value")&&!z(bt,"get")&&!z(bt,"set")&&!bt.configurable&&(!z(bt,"writable")||bt.writable)&&(!z(bt,"enumerable")||bt.enumerable)?(Te[de]=bt.value,Te):Ut(Te,de,bt)};p?(ge||(tt.f=$e,_.f=ce,fe(Ht,"buffer"),fe(Ht,"byteOffset"),fe(Ht,"byteLength"),fe(Ht,"length")),u({target:"Object",stat:!0,forced:!ge},{getOwnPropertyDescriptor:$e,defineProperty:ce}),x.exports=function(Ae,Te,de){var bt=Ae.match(/\d+/)[0]/8,Ft=Ae+(de?"Clamped":"")+"Array",Tt="get"+Ae,qt="set"+Ae,te=d[Ft],Zt=te,Yt=Zt&&Zt.prototype,Ye={},Ze=function(Ct,Nt){var Et=gt(Ct);return Et.view[Tt](Nt*bt+Et.byteOffset,!0)},ut=function(Ct,Nt,Et){var ie=gt(Ct);ie.view[qt](Nt*bt+ie.byteOffset,de?M(Et):Et,!0)},It=function(Ct,Nt){Ut(Ct,Nt,{get:function(){return Ze(this,Nt)},set:function(Et){return ut(this,Nt,Et)},enumerable:!0})};ge?y&&(Zt=Te(function(Ct,Nt,Et,ie){return A(Ct,Yt),mt(function(){return j(Nt)?Pe(Nt)?ie!==void 0?new te(Nt,D(Et,bt),ie):Et!==void 0?new te(Nt,D(Et,bt)):new te(Nt):re(Nt)?et(Zt,Nt):h(J,Zt,Nt):new te(C(Nt))}(),Ct,Zt)}),Y&&Y(Zt,Mt),q(Z(te),function(Ct){Ct in Zt||R(Zt,Ct,te[Ct])}),Zt.prototype=Yt):(Zt=Te(function(Ct,Nt,Et,ie){A(Ct,Yt);var we=0,Rt=0,zt,jt,Wt;if(!j(Nt))Wt=C(Nt),jt=Wt*bt,zt=new Qt(jt);else if(Pe(Nt)){zt=Nt,Rt=D(Et,bt);var ue=Nt.byteLength;if(ie===void 0){if(ue%bt)throw new Xt(ee);if(jt=ue-Rt,jt<0)throw new Xt(ee)}else if(jt=O(ie)*bt,jt+Rt>ue)throw new Xt(ee);Wt=jt/bt}else return re(Nt)?et(Zt,Nt):h(J,Zt,Nt);for(xt(Ct,{buffer:zt,byteOffset:Rt,byteLength:jt,length:Wt,view:new me(zt)});we255?255:u&255}},function(x,b,r){var u=r(84),d=r(8),h=r(365),p=r(39),y=r(63),T=r(133),$=r(134),A=r(131),E=r(503),R=r(219).aTypedArrayConstructor,I=r(504);x.exports=function(C){var D=h(this),M=p(C),F=arguments.length,z=F>1?arguments[1]:void 0,U=z!==void 0,j=$(M),G,B,V,Y,Z,J,q,nt;if(j&&!A(j))for(q=T(M,j),nt=q.next,M=[];!(J=d(nt,q)).done;)M.push(J.value);for(U&&F>2&&(z=u(z,arguments[2])),B=y(M),V=new(R(D))(B),Y=E(V),G=0;B>G;G++)Z=U?z(M[G],G):M[G],V[G]=Y?I(Z):+Z;return V}},function(x,b,r){var u=r(69);x.exports=function(d){var h=u(d);return h==="BigInt64Array"||h==="BigUint64Array"}},function(x,b,r){var u=r(19),d=TypeError;x.exports=function(h){var p=u(h,"number");if(typeof p=="number")throw new d("Can't convert number to bigint");return BigInt(p)}},function(x,b,r){var u=r(498);u("Float64",function(d){return function(p,y,T){return d(this,p,y,T)}})},function(x,b,r){var u=r(498);u("Int8",function(d){return function(p,y,T){return d(this,p,y,T)}})},function(x,b,r){var u=r(498);u("Int16",function(d){return function(p,y,T){return d(this,p,y,T)}})},function(x,b,r){var u=r(498);u("Int32",function(d){return function(p,y,T){return d(this,p,y,T)}})},function(x,b,r){var u=r(498);u("Uint8",function(d){return function(p,y,T){return d(this,p,y,T)}})},function(x,b,r){var u=r(498);u("Uint8",function(d){return function(p,y,T){return d(this,p,y,T)}},!0)},function(x,b,r){var u=r(498);u("Uint16",function(d){return function(p,y,T){return d(this,p,y,T)}})},function(x,b,r){var u=r(498);u("Uint32",function(d){return function(p,y,T){return d(this,p,y,T)}})},function(x,b,r){var u=r(219),d=r(63),h=r(61),p=u.aTypedArray,y=u.exportTypedArrayMethod;y("at",function($){var A=p(this),E=d(A),R=h($),I=R>=0?R:E+R;return I<0||I>=E?void 0:A[I]})},function(x,b,r){var u=r(14),d=r(219),h=r(144),p=u(h),y=d.aTypedArray,T=d.exportTypedArrayMethod;T("copyWithin",function(A,E){return p(y(this),A,E,arguments.length>2?arguments[2]:void 0)})},function(x,b,r){var u=r(219),d=r(83).every,h=u.aTypedArray,p=u.exportTypedArrayMethod;p("every",function(T){return d(h(this),T,arguments.length>1?arguments[1]:void 0)})},function(x,b,r){var u=r(219),d=r(149),h=r(504),p=r(69),y=r(8),T=r(14),$=r(7),A=u.aTypedArray,E=u.exportTypedArrayMethod,R=T("".slice),I=$(function(){var O=0;return new Int8Array(2).fill({valueOf:function(){return O++}}),O!==1});E("fill",function(C){var D=arguments.length;A(this);var M=R(p(this),0,3)==="Big"?h(C):+C;return y(d,this,M,D>1?arguments[1]:void 0,D>2?arguments[2]:void 0)},I)},function(x,b,r){var u=r(219),d=r(83).filter,h=r(518),p=u.aTypedArray,y=u.exportTypedArrayMethod;y("filter",function($){var A=d(p(this),$,arguments.length>1?arguments[1]:void 0);return h(this,A)})},function(x,b,r){var u=r(199),d=r(219).getTypedArrayConstructor;x.exports=function(h,p){return u(d(h),p)}},function(x,b,r){var u=r(219),d=r(83).find,h=u.aTypedArray,p=u.exportTypedArrayMethod;p("find",function(T){return d(h(this),T,arguments.length>1?arguments[1]:void 0)})},function(x,b,r){var u=r(219),d=r(83).findIndex,h=u.aTypedArray,p=u.exportTypedArrayMethod;p("findIndex",function(T){return d(h(this),T,arguments.length>1?arguments[1]:void 0)})},function(x,b,r){var u=r(219),d=r(154).findLast,h=u.aTypedArray,p=u.exportTypedArrayMethod;p("findLast",function(T){return d(h(this),T,arguments.length>1?arguments[1]:void 0)})},function(x,b,r){var u=r(219),d=r(154).findLastIndex,h=u.aTypedArray,p=u.exportTypedArrayMethod;p("findLastIndex",function(T){return d(h(this),T,arguments.length>1?arguments[1]:void 0)})},function(x,b,r){var u=r(219),d=r(83).forEach,h=u.aTypedArray,p=u.exportTypedArrayMethod;p("forEach",function(T){d(h(this),T,arguments.length>1?arguments[1]:void 0)})},function(x,b,r){var u=r(499),d=r(219).exportTypedArrayStaticMethod,h=r(502);d("from",h,u)},function(x,b,r){var u=r(219),d=r(59).includes,h=u.aTypedArray,p=u.exportTypedArrayMethod;p("includes",function(T){return d(h(this),T,arguments.length>1?arguments[1]:void 0)})},function(x,b,r){var u=r(219),d=r(59).indexOf,h=u.aTypedArray,p=u.exportTypedArrayMethod;p("indexOf",function(T){return d(h(this),T,arguments.length>1?arguments[1]:void 0)})},function(x,b,r){var u=r(4),d=r(7),h=r(14),p=r(219),y=r(168),T=r(33),$=T("iterator"),A=u.Uint8Array,E=h(y.values),R=h(y.keys),I=h(y.entries),O=p.aTypedArray,C=p.exportTypedArrayMethod,D=A&&A.prototype,M=!d(function(){D[$].call([1])}),F=!!D&&D.values&&D[$]===D.values&&D.values.name==="values",z=function(){return E(O(this))};C("entries",function(){return I(O(this))},M),C("keys",function(){return R(O(this))},M),C("values",z,M||!F,{name:"values"}),C($,z,M||!F,{name:"values"})},function(x,b,r){var u=r(219),d=r(14),h=u.aTypedArray,p=u.exportTypedArrayMethod,y=d([].join);p("join",function($){return y(h(this),$)})},function(x,b,r){var u=r(219),d=r(94),h=r(175),p=u.aTypedArray,y=u.exportTypedArrayMethod;y("lastIndexOf",function($){var A=arguments.length;return d(h,p(this),A>1?[$,arguments[1]]:[$])})},function(x,b,r){var u=r(219),d=r(83).map,h=u.aTypedArray,p=u.getTypedArrayConstructor,y=u.exportTypedArrayMethod;y("map",function($){return d(h(this),$,arguments.length>1?arguments[1]:void 0,function(A,E){return new(p(A))(E)})})},function(x,b,r){var u=r(219),d=r(499),h=u.aTypedArrayConstructor,p=u.exportTypedArrayStaticMethod;p("of",function(){for(var T=0,$=arguments.length,A=new(h(this))($);$>T;)A[T]=arguments[T++];return A},d)},function(x,b,r){var u=r(219),d=r(181).left,h=u.aTypedArray,p=u.exportTypedArrayMethod;p("reduce",function(T){var $=arguments.length;return d(h(this),T,$,$>1?arguments[1]:void 0)})},function(x,b,r){var u=r(219),d=r(181).right,h=u.aTypedArray,p=u.exportTypedArrayMethod;p("reduceRight",function(T){var $=arguments.length;return d(h(this),T,$,$>1?arguments[1]:void 0)})},function(x,b,r){var u=r(219),d=u.aTypedArray,h=u.exportTypedArrayMethod,p=Math.floor;h("reverse",function(){for(var T=this,$=d(T).length,A=p($/2),E=0,R;E1?arguments[1]:void 0,1),j=T(z);if(D)return d(I,this,j,U);var G=this.length,B=p(j),V=0;if(B+U>G)throw new A("Wrong length");for(;VC;)M[C]=I[C++];return M},$)},function(x,b,r){var u=r(219),d=r(83).some,h=u.aTypedArray,p=u.exportTypedArrayMethod;p("some",function(T){return d(h(this),T,arguments.length>1?arguments[1]:void 0)})},function(x,b,r){var u=r(4),d=r(85),h=r(7),p=r(30),y=r(189),T=r(219),$=r(190),A=r(191),E=r(27),R=r(192),I=T.aTypedArray,O=T.exportTypedArrayMethod,C=u.Uint16Array,D=C&&d(C.prototype.sort),M=!!D&&!(h(function(){D(new C(2),null)})&&h(function(){D(new C(2),{})})),F=!!D&&!h(function(){if(E)return E<74;if($)return $<67;if(A)return!0;if(R)return R<602;var U=new C(516),j=Array(516),G,B;for(G=0;G<516;G++)B=G%4,U[G]=515-G,j[G]=G-2*B+3;for(D(U,function(V,Y){return(V/4|0)-(Y/4|0)}),G=0;G<516;G++)if(U[G]!==j[G])return!0}),z=function(U){return function(j,G){return U!==void 0?+U(j,G)||0:G!==G?-1:j!==j?1:j===0&&G===0?1/j>0&&1/G<0?1:-1:j>G}};O("sort",function(j){return j!==void 0&&p(j),F?D(this,j):y(I(this),z(j))},!F||M)},function(x,b,r){var u=r(219),d=r(64),h=r(60),p=u.aTypedArray,y=u.getTypedArrayConstructor,T=u.exportTypedArrayMethod;T("subarray",function(A,E){var R=p(this),I=R.length,O=h(A,I),C=y(R);return new C(R.buffer,R.byteOffset+O*R.BYTES_PER_ELEMENT,d((E===void 0?I:h(E,I))-O))})},function(x,b,r){var u=r(4),d=r(94),h=r(219),p=r(7),y=r(76),T=u.Int8Array,$=h.aTypedArray,A=h.exportTypedArrayMethod,E=[].toLocaleString,R=!!T&&p(function(){E.call(new T(1))}),I=p(function(){return[1,2].toLocaleString()!==new T([1,2]).toLocaleString()})||!p(function(){T.prototype.toLocaleString.call([1,2])});A("toLocaleString",function(){return d(E,R?y($(this)):$(this),y(arguments))},I)},function(x,b,r){var u=r(197),d=r(219),h=d.aTypedArray,p=d.exportTypedArrayMethod,y=d.getTypedArrayConstructor;p("toReversed",function(){return u(h(this),y(this))})},function(x,b,r){var u=r(219),d=r(14),h=r(30),p=r(199),y=u.aTypedArray,T=u.getTypedArrayConstructor,$=u.exportTypedArrayMethod,A=d(u.TypedArrayPrototype.sort);$("toSorted",function(R){R!==void 0&&h(R);var I=y(this),O=p(T(I),I);return A(O,R)})},function(x,b,r){var u=r(219).exportTypedArrayMethod,d=r(7),h=r(4),p=r(14),y=h.Uint8Array,T=y&&y.prototype||{},$=[].toString,A=p([].join);d(function(){$.call({})})&&($=function(){return A(this)});var E=T.toString!==$;u("toString",$,E)},function(x,b,r){var u=r(206),d=r(219),h=r(503),p=r(61),y=r(504),T=d.aTypedArray,$=d.getTypedArrayConstructor,A=d.exportTypedArrayMethod,E=!!function(){try{new Int8Array(1).with(2,{valueOf:function(){throw 8}})}catch(R){return R===8}}();A("with",function(R,I){var O=T(this),C=p(R),D=h(O)?y(I):+I;return u(O,$(O),C,D)},!E)},function(x,b,r){var u=r(3),d=r(14),h=r(68),p=String.fromCharCode,y=d("".charAt),T=d(/./.exec),$=d("".slice),A=/^[\da-f]{2}$/i,E=/^[\da-f]{4}$/i;u({global:!0},{unescape:function(I){for(var O=h(I),C="",D=O.length,M=0,F,z;M>(-2*_&6)));return nt}})},function(x){var b="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",r=b+"+/",u=b+"-_",d=function(h){for(var p={},y=0;y<64;y++)p[h.charAt(y)]=y;return p};x.exports={i2c:r,c2i:d(r),i2cUrl:u,c2iUrl:d(u)}},function(x,b,r){var u=r(3),d=r(4),h=r(23),p=r(14),y=r(8),T=r(7),$=r(68),A=r(367),E=r(552).i2c,R=h("btoa"),I=p("".charAt),O=p("".charCodeAt),C=!!R&&!T(function(){return R("hi")!=="aGk="}),D=C&&!T(function(){R()}),M=C&&T(function(){return R(null)!=="bnVsbA=="}),F=C&&R.length!==1;u({global:!0,bind:!0,enumerable:!0,forced:!C||D||M||F},{btoa:function(U){if(A(arguments.length,1),C)return y(R,d,$(U));for(var j=$(U),G="",B=0,V=E,Y,Z;I(j,B)||(V="=",B%1);){if(Z=O(j,B+=.75),Z>255)throw new(h("DOMException"))("The string contains characters outside of the Latin1 range","InvalidCharacterError");Y=Y<<8|Z,G+=I(V,63&Y>>8-B%1*8)}return G}})},function(x,b,r){var u=r(4),d=r(555),h=r(556),p=r(160),y=r(43),T=function(A){if(A&&A.forEach!==p)try{y(A,"forEach",p)}catch(E){A.forEach=p}};for(var $ in d)d[$]&&T(u[$]&&u[$].prototype);T(h)},function(x){x.exports={CSSRuleList:0,CSSStyleDeclaration:0,CSSValueList:0,ClientRectList:0,DOMRectList:0,DOMStringList:0,DOMTokenList:1,DataTransferItemList:0,FileList:0,HTMLAllCollection:0,HTMLCollection:0,HTMLFormElement:0,HTMLSelectElement:0,MediaList:0,MimeTypeArray:0,NamedNodeMap:0,NodeList:1,PaintRequestList:0,Plugin:0,PluginArray:0,SVGLengthList:0,SVGNumberList:0,SVGPathSegList:0,SVGPointList:0,SVGStringList:0,SVGTransformList:0,SourceBufferList:0,StyleSheetList:0,TextTrackCueList:0,TextTrackList:0,TouchList:0}},function(x,b,r){var u=r(42),d=u("span").classList,h=d&&d.constructor&&d.constructor.prototype;x.exports=h===Object.prototype?void 0:h},function(x,b,r){var u=r(4),d=r(555),h=r(556),p=r(168),y=r(43),T=r(82),$=r(33),A=$("iterator"),E=p.values,R=function(O,C){if(O){if(O[A]!==E)try{y(O,A,E)}catch(M){O[A]=E}if(T(O,C,!0),d[C]){for(var D in p)if(O[D]!==p[D])try{y(O,D,p[D])}catch(M){O[D]=p[D]}}}};for(var I in d)R(u[I]&&u[I].prototype,I);R(h,"DOMTokenList")},function(x,b,r){var u=r(3),d=r(23),h=r(234),p=r(7),y=r(71),T=r(11),$=r(44).f,A=r(47),E=r(77),R=r(38),I=r(211),O=r(46),C=r(125),D=r(119),M=r(559),F=r(122),z=r(51),U=r(6),j=r(36),G="DOMException",B="DATA_CLONE_ERR",V=d("Error"),Y=d(G)||function(){try{var Mt=d("MessageChannel")||h("worker_threads").MessageChannel;new Mt().port1.postMessage(new WeakMap)}catch(Ht){if(Ht.name===B&&Ht.code===25)return Ht.constructor}}(),Z=Y&&Y.prototype,J=V.prototype,q=z.set,nt=z.getterFor(G),rt="stack"in new V(G),_=function(Mt){return R(M,Mt)&&M[Mt].m?M[Mt].c:0},tt=function(){I(this,et);var Ht=arguments.length,re=D(Ht<1?void 0:arguments[0]),se=D(Ht<2?void 0:arguments[1],"Error"),ee=_(se);if(q(this,{type:G,name:se,message:re,code:ee}),U||(this.name=se,this.message=re,this.code=ee),rt){var fe=new V(re);fe.name=G,$(this,"stack",T(1,F(fe.stack,1)))}},et=tt.prototype=y(J),lt=function(Mt){return{enumerable:!0,configurable:!0,get:Mt}},mt=function(Mt){return lt(function(){return nt(this)[Mt]})};U&&(E(et,"code",mt("code")),E(et,"message",mt("message")),E(et,"name",mt("name"))),$(et,"constructor",T(1,tt));var gt=p(function(){return!(new Y instanceof V)}),xt=gt||p(function(){return J.toString!==C||String(new Y(1,2))!=="2: 1"}),yt=gt||p(function(){return new Y(1,"DataCloneError").code!==25}),Ut=gt||Y[B]!==25||Z[B]!==25,Dt=j?xt||yt||Ut:gt;u({global:!0,constructor:!0,forced:Dt},{DOMException:Dt?tt:Y});var Xt=d(G),Qt=Xt.prototype;xt&&(j||Y===Xt)&&A(Qt,"toString",C),yt&&U&&Y===Xt&&E(Qt,"code",lt(function(){return _(O(this).name)}));for(var kt in M)if(R(M,kt)){var me=M[kt],ge=me.s,ae=T(6,me.c);R(Xt,ge)||$(Xt,ge,ae),R(Qt,ge)||$(Qt,ge,ae)}},function(x){x.exports={IndexSizeError:{s:"INDEX_SIZE_ERR",c:1,m:1},DOMStringSizeError:{s:"DOMSTRING_SIZE_ERR",c:2,m:0},HierarchyRequestError:{s:"HIERARCHY_REQUEST_ERR",c:3,m:1},WrongDocumentError:{s:"WRONG_DOCUMENT_ERR",c:4,m:1},InvalidCharacterError:{s:"INVALID_CHARACTER_ERR",c:5,m:1},NoDataAllowedError:{s:"NO_DATA_ALLOWED_ERR",c:6,m:0},NoModificationAllowedError:{s:"NO_MODIFICATION_ALLOWED_ERR",c:7,m:1},NotFoundError:{s:"NOT_FOUND_ERR",c:8,m:1},NotSupportedError:{s:"NOT_SUPPORTED_ERR",c:9,m:1},InUseAttributeError:{s:"INUSE_ATTRIBUTE_ERR",c:10,m:1},InvalidStateError:{s:"INVALID_STATE_ERR",c:11,m:1},SyntaxError:{s:"SYNTAX_ERR",c:12,m:1},InvalidModificationError:{s:"INVALID_MODIFICATION_ERR",c:13,m:1},NamespaceError:{s:"NAMESPACE_ERR",c:14,m:1},InvalidAccessError:{s:"INVALID_ACCESS_ERR",c:15,m:1},ValidationError:{s:"VALIDATION_ERR",c:16,m:0},TypeMismatchError:{s:"TYPE_MISMATCH_ERR",c:17,m:1},SecurityError:{s:"SECURITY_ERR",c:18,m:1},NetworkError:{s:"NETWORK_ERR",c:19,m:1},AbortError:{s:"ABORT_ERR",c:20,m:1},URLMismatchError:{s:"URL_MISMATCH_ERR",c:21,m:1},QuotaExceededError:{s:"QUOTA_EXCEEDED_ERR",c:22,m:1},TimeoutError:{s:"TIMEOUT_ERR",c:23,m:1},InvalidNodeTypeError:{s:"INVALID_NODE_TYPE_ERR",c:24,m:1},DataCloneError:{s:"DATA_CLONE_ERR",c:25,m:1}}},function(x,b,r){var u=r(3),d=r(4),h=r(23),p=r(11),y=r(44).f,T=r(38),$=r(211),A=r(118),E=r(119),R=r(559),I=r(122),O=r(6),C=r(36),D="DOMException",M=h("Error"),F=h(D),z=function(){$(this,U);var tt=arguments.length,et=E(tt<1?void 0:arguments[0]),lt=E(tt<2?void 0:arguments[1],"Error"),mt=new F(et,lt),gt=new M(et);return gt.name=D,y(mt,"stack",p(1,I(gt.stack,1))),A(mt,this,z),mt},U=z.prototype=F.prototype,j="stack"in new M(D),G="stack"in new F(1,2),B=F&&O&&Object.getOwnPropertyDescriptor(d,D),V=!!B&&!(B.writable&&B.configurable),Y=j&&!V&&!G;u({global:!0,constructor:!0,forced:C||Y},{DOMException:Y?z:F});var Z=h(D),J=Z.prototype;if(J.constructor!==Z){C||y(J,"constructor",p(1,Z));for(var q in R)if(T(R,q)){var nt=R[q],rt=nt.s;T(Z,rt)||y(Z,rt,p(6,nt.c))}}},function(x,b,r){var u=r(23),d=r(82),h="DOMException";d(u(h),h)},function(x,b,r){r(563),r(564)},function(x,b,r){var u=r(3),d=r(4),h=r(366).clear;u({global:!0,bind:!0,enumerable:!0,forced:d.clearImmediate!==h},{clearImmediate:h})},function(x,b,r){var u=r(3),d=r(4),h=r(366).set,p=r(565),y=d.setImmediate?p(h,!1):h;u({global:!0,bind:!0,enumerable:!0,forced:d.setImmediate!==y},{setImmediate:y})},function(x,b,r){var u=r(4),d=r(94),h=r(21),p=r(183),y=r(28),T=r(76),$=r(367),A=u.Function,E=/MSIE .\./.test(y)||p==="BUN"&&function(){var R=u.Bun.version.split(".");return R.length<3||R[0]==="0"&&(R[1]<3||R[1]==="3"&&R[2]==="0")}();x.exports=function(R,I){var O=I?2:1;return E?function(C,D){var M=$(arguments.length,1)>O,F=h(C)?C:A(C),z=M?T(arguments,O):[],U=M?function(){d(F,this,z)}:F;return I?R(U,D):R(U)}:R}},function(x,b,r){var u=r(3),d=r(4),h=r(369),p=r(30),y=r(367),T=r(7),$=r(6),A=T(function(){return $&&Object.getOwnPropertyDescriptor(d,"queueMicrotask").value.length!==1});u({global:!0,enumerable:!0,dontCallGetSet:!0,forced:A},{queueMicrotask:function(R){y(arguments.length,1),h(p(R))}})},function(x,b,r){var u=r(3),d=r(4),h=r(77),p=r(6),y=TypeError,T=Object.defineProperty,$=d.self!==d;try{if(p){var A=Object.getOwnPropertyDescriptor(d,"self");($||!A||!A.get||!A.enumerable)&&h(d,"self",{get:function(){return d},set:function(R){if(this!==d)throw new y("Illegal invocation");T(d,"self",{value:R,writable:!0,configurable:!0,enumerable:!0})},configurable:!0,enumerable:!0})}else u({global:!0,simple:!0,forced:$},{self:d})}catch(E){}},function(x,b,r){var u=r(36),d=r(3),h=r(4),p=r(23),y=r(14),T=r(7),$=r(40),A=r(21),E=r(89),R=r(17),I=r(20),O=r(22),C=r(130),D=r(46),M=r(69),F=r(38),z=r(141),U=r(43),j=r(63),G=r(367),B=r(408),V=r(284),Y=r(427),Z=r(429),J=r(233),q=r(123),nt=r(235),rt=h.Object,_=h.Array,tt=h.Date,et=h.Error,lt=h.TypeError,mt=h.PerformanceMark,gt=p("DOMException"),xt=V.Map,yt=V.has,Ut=V.get,Dt=V.set,Xt=Y.Set,Qt=Y.add,kt=Y.has,me=p("Object","keys"),ge=y([].push),ae=y((!0).valueOf),Mt=y(1 .valueOf),Ht=y("".valueOf),re=y(tt.prototype.getTime),se=$("structuredClone"),ee="DataCloneError",fe="Transferring",Pe=function(ut){return!T(function(){var It=new h.Set([7]),Pt=ut(It),Ct=ut(rt(7));return Pt===It||!Pt.has(7)||!I(Ct)||+Ct!=7})&&ut},Me=function(ut,It){return!T(function(){var Pt=new It,Ct=ut({a:Pt,b:Pt});return!(Ct&&Ct.a===Ct.b&&Ct.a instanceof It&&Ct.a.stack===Pt.stack)})},$e=function(ut){return!T(function(){var It=ut(new h.AggregateError([1],se,{cause:3}));return It.name!=="AggregateError"||It.errors[0]!==1||It.message!==se||It.cause!==3})},ce=h.structuredClone,Ae=u||!Me(ce,et)||!Me(ce,gt)||!$e(ce),Te=!ce&&Pe(function(ut){return new mt(se,{detail:ut}).detail}),de=Pe(ce)||Te,bt=function(ut){throw new gt("Uncloneable type: "+ut,ee)},Ft=function(ut,It){throw new gt((It||"Cloning")+" of "+ut+" cannot be properly polyfilled in this engine",ee)},Tt=function(ut,It){return de||Ft(It),de(ut)},qt=function(){var ut;try{ut=new h.DataTransfer}catch(It){try{ut=new h.ClipboardEvent("").clipboardData}catch(Pt){}}return ut&&ut.items&&ut.files?ut:null},te=function(ut,It,Pt){if(yt(It,ut))return Ut(It,ut);var Ct=Pt||M(ut),Nt,Et,ie,we,Rt,zt;if(Ct==="SharedArrayBuffer")de?Nt=de(ut):Nt=ut;else{var jt=h.DataView;!jt&&!A(ut.slice)&&Ft("ArrayBuffer");try{if(A(ut.slice)&&!ut.resizable)Nt=ut.slice(0);else for(Et=ut.byteLength,ie=("maxByteLength"in ut)?{maxByteLength:ut.maxByteLength}:void 0,Nt=new ArrayBuffer(Et,ie),we=new jt(ut),Rt=new jt(Nt),zt=0;zt1&&!R(arguments[1])?D(arguments[1]):void 0,Ct=Pt?Pt.transfer:void 0,Nt,Et;Ct!==void 0&&(Nt=new xt,Et=Ye(Ct,Nt));var ie=Yt(It,Nt);return Et&&Ze(Et),ie}})},function(x,b,r){r(570),r(571)},function(x,b,r){var u=r(3),d=r(4),h=r(565),p=h(d.setInterval,!0);u({global:!0,bind:!0,forced:d.setInterval!==p},{setInterval:p})},function(x,b,r){var u=r(3),d=r(4),h=r(565),p=h(d.setTimeout,!0);u({global:!0,bind:!0,forced:d.setTimeout!==p},{setTimeout:p})},function(x,b,r){r(573)},function(x,b,r){r(455);var u=r(3),d=r(6),h=r(574),p=r(4),y=r(84),T=r(14),$=r(47),A=r(77),E=r(211),R=r(38),I=r(328),O=r(162),C=r(76),D=r(448).codeAt,M=r(575),F=r(68),z=r(82),U=r(367),j=r(576),G=r(51),B=G.set,V=G.getterFor("URL"),Y=j.URLSearchParams,Z=j.getState,J=p.URL,q=p.TypeError,nt=p.parseInt,rt=Math.floor,_=Math.pow,tt=T("".charAt),et=T(/./.exec),lt=T([].join),mt=T(1 .toString),gt=T([].pop),xt=T([].push),yt=T("".replace),Ut=T([].shift),Dt=T("".split),Xt=T("".slice),Qt=T("".toLowerCase),kt=T([].unshift),me="Invalid authority",ge="Invalid scheme",ae="Invalid host",Mt="Invalid port",Ht=/[a-z]/i,re=/[\d+-.a-z]/i,se=/\d/,ee=/^0x/i,fe=/^[0-7]+$/,Pe=/^\d+$/,Me=/^[\da-f]+$/i,$e=/[\0\t\n\r #%/:<>?@[\\\]^|]/,ce=/[\0\t\n\r #/:<>?@[\\\]^|]/,Ae=/^[\u0000-\u0020]+/,Te=/(^|[^\u0000-\u0020])[\u0000-\u0020]+$/,de=/[\t\n\r]/g,bt,Ft=function(ft){var wt=Dt(ft,"."),pt,it,Ot,ye,_t,Ie,rn;if(wt.length&&wt[wt.length-1]===""&&wt.length--,pt=wt.length,pt>4)return ft;for(it=[],Ot=0;Ot1&&tt(ye,0)==="0"&&(_t=et(ee,ye)?16:8,ye=Xt(ye,_t===8?1:2)),ye==="")Ie=0;else{if(!et(_t===10?Pe:_t===8?fe:Me,ye))return ft;Ie=nt(ye,_t)}xt(it,Ie)}for(Ot=0;Ot=_(256,5-pt))return null}else if(Ie>255)return null;for(rn=gt(it),Ot=0;Ot6))return;for(Ie=0;an();){if(rn=null,Ie>0)if(an()==="."&&Ie<4)Ot++;else return;if(!et(se,an()))return;for(;et(se,an());){if(hn=nt(an(),10),rn===null)rn=hn;else{if(rn===0)return;rn=rn*10+hn}if(rn>255)return;Ot++}wt[pt]=wt[pt]*256+rn,Ie++,(Ie===2||Ie===4)&&pt++}if(Ie!==4)return;break}else if(an()===":"){if(Ot++,!an())return}else if(an())return;wt[pt++]=ye}if(it!==null)for(pn=pt-it,pt=7;pt!==0&&pn>0;)ot=wt[pt],wt[pt--]=wt[it+pn-1],wt[it+--pn]=ot;else if(pt!==8)return;return wt},qt=function(ft){for(var wt=null,pt=1,it=null,Ot=0,ye=0;ye<8;ye++)ft[ye]!==0?(Ot>pt&&(wt=it,pt=Ot),it=null,Ot=0):(it===null&&(it=ye),++Ot);return Ot>pt?it:wt},te=function(ft){var wt,pt,it,Ot;if(typeof ft=="number"){for(wt=[],pt=0;pt<4;pt++)kt(wt,ft%256),ft=rt(ft/256);return lt(wt,".")}if(typeof ft=="object"){for(wt="",it=qt(ft),pt=0;pt<8;pt++)Ot&&ft[pt]===0||(Ot&&(Ot=!1),it===pt?(wt+=pt?":":"::",Ot=!0):(wt+=mt(ft[pt],16),pt<7&&(wt+=":")));return"["+wt+"]"}return ft},Zt={},Yt=I({},Zt,{" ":1,'"':1,"<":1,">":1,"`":1}),Ye=I({},Yt,{"#":1,"?":1,"{":1,"}":1}),Ze=I({},Ye,{"/":1,":":1,";":1,"=":1,"@":1,"[":1,"\\":1,"]":1,"^":1,"|":1}),ut=function(ft,wt){var pt=D(ft,0);return pt>32&&pt<127&&!R(wt,ft)?ft:encodeURIComponent(ft)},It={ftp:21,file:null,http:80,https:443,ws:80,wss:443},Pt=function(ft,wt){var pt;return ft.length===2&&et(Ht,tt(ft,0))&&((pt=tt(ft,1))===":"||!wt&&pt==="|")},Ct=function(ft){var wt;return ft.length>1&&Pt(Xt(ft,0,2))&&(ft.length===2||(wt=tt(ft,2))==="/"||wt==="\\"||wt==="?"||wt==="#")},Nt=function(ft){return ft==="."||Qt(ft)==="%2e"},Et=function(ft){return ft=Qt(ft),ft===".."||ft==="%2e."||ft===".%2e"||ft==="%2e%2e"},ie={},we={},Rt={},zt={},jt={},Wt={},ue={},Ee={},Xe={},Je={},nn={},vn={},jn={},Hr={},Ya={},ga={},yr={},Vn={},Wa={},ir={},Wn={},va=function(ft,wt,pt){var it=F(ft),Ot,ye,_t;if(wt){if(ye=this.parse(it),ye)throw new q(ye);this.searchParams=null}else{if(pt!==void 0&&(Ot=new va(pt,!0)),ye=this.parse(it,null,Ot),ye)throw new q(ye);_t=Z(new Y),_t.bindURL(this),this.searchParams=_t}};va.prototype={type:"URL",parse:function(ft,wt,pt){var it=this,Ot=wt||ie,ye=0,_t="",Ie=!1,rn=!1,hn=!1,pn,ot,an,Fn;for(ft=F(ft),wt||(it.scheme="",it.username="",it.password="",it.host=null,it.port=null,it.path=[],it.query=null,it.fragment=null,it.cannotBeABaseURL=!1,ft=yt(ft,Ae,""),ft=yt(ft,Te,"$1")),ft=yt(ft,de,""),pn=O(ft);ye<=pn.length;){switch(ot=pn[ye],Ot){case ie:if(ot&&et(Ht,ot))_t+=Qt(ot),Ot=we;else{if(wt)return ge;Ot=Rt;continue}break;case we:if(ot&&(et(re,ot)||ot==="+"||ot==="-"||ot==="."))_t+=Qt(ot);else if(ot===":"){if(wt&&(it.isSpecial()!==R(It,_t)||_t==="file"&&(it.includesCredentials()||it.port!==null)||it.scheme==="file"&&!it.host))return;if(it.scheme=_t,wt){it.isSpecial()&&It[it.scheme]===it.port&&(it.port=null);return}_t="",it.scheme==="file"?Ot=Hr:it.isSpecial()&&pt&&pt.scheme===it.scheme?Ot=zt:it.isSpecial()?Ot=Ee:pn[ye+1]==="/"?(Ot=jt,ye++):(it.cannotBeABaseURL=!0,xt(it.path,""),Ot=Wa)}else{if(wt)return ge;_t="",Ot=Rt,ye=0;continue}break;case Rt:if(!pt||pt.cannotBeABaseURL&&ot!=="#")return ge;if(pt.cannotBeABaseURL&&ot==="#"){it.scheme=pt.scheme,it.path=C(pt.path),it.query=pt.query,it.fragment="",it.cannotBeABaseURL=!0,Ot=Wn;break}Ot=pt.scheme==="file"?Hr:Wt;continue;case zt:if(ot==="/"&&pn[ye+1]==="/")Ot=Xe,ye++;else{Ot=Wt;continue}break;case jt:if(ot==="/"){Ot=Je;break}else{Ot=Vn;continue}case Wt:if(it.scheme=pt.scheme,ot===bt)it.username=pt.username,it.password=pt.password,it.host=pt.host,it.port=pt.port,it.path=C(pt.path),it.query=pt.query;else if(ot==="/"||ot==="\\"&&it.isSpecial())Ot=ue;else if(ot==="?")it.username=pt.username,it.password=pt.password,it.host=pt.host,it.port=pt.port,it.path=C(pt.path),it.query="",Ot=ir;else if(ot==="#")it.username=pt.username,it.password=pt.password,it.host=pt.host,it.port=pt.port,it.path=C(pt.path),it.query=pt.query,it.fragment="",Ot=Wn;else{it.username=pt.username,it.password=pt.password,it.host=pt.host,it.port=pt.port,it.path=C(pt.path),it.path.length--,Ot=Vn;continue}break;case ue:if(it.isSpecial()&&(ot==="/"||ot==="\\"))Ot=Xe;else if(ot==="/")Ot=Je;else{it.username=pt.username,it.password=pt.password,it.host=pt.host,it.port=pt.port,Ot=Vn;continue}break;case Ee:if(Ot=Xe,ot!=="/"||tt(_t,ye+1)!=="/")continue;ye++;break;case Xe:if(ot!=="/"&&ot!=="\\"){Ot=Je;continue}break;case Je:if(ot==="@"){Ie&&(_t="%40"+_t),Ie=!0,an=O(_t);for(var en=0;en65535)return Mt;it.port=it.isSpecial()&&Bn===It[it.scheme]?null:Bn,_t=""}if(wt)return;Ot=yr;continue}else return Mt;break;case Hr:if(it.scheme="file",ot==="/"||ot==="\\")Ot=Ya;else if(pt&&pt.scheme==="file")switch(ot){case bt:it.host=pt.host,it.path=C(pt.path),it.query=pt.query;break;case"?":it.host=pt.host,it.path=C(pt.path),it.query="",Ot=ir;break;case"#":it.host=pt.host,it.path=C(pt.path),it.query=pt.query,it.fragment="",Ot=Wn;break;default:Ct(lt(C(pn,ye),""))||(it.host=pt.host,it.path=C(pt.path),it.shortenPath()),Ot=Vn;continue}else{Ot=Vn;continue}break;case Ya:if(ot==="/"||ot==="\\"){Ot=ga;break}pt&&pt.scheme==="file"&&!Ct(lt(C(pn,ye),""))&&(Pt(pt.path[0],!0)?xt(it.path,pt.path[0]):it.host=pt.host),Ot=Vn;continue;case ga:if(ot===bt||ot==="/"||ot==="\\"||ot==="?"||ot==="#"){if(!wt&&Pt(_t))Ot=Vn;else if(_t===""){if(it.host="",wt)return;Ot=yr}else{if(Fn=it.parseHost(_t),Fn)return Fn;if(it.host==="localhost"&&(it.host=""),wt)return;_t="",Ot=yr}continue}else _t+=ot;break;case yr:if(it.isSpecial()){if(Ot=Vn,ot!=="/"&&ot!=="\\")continue}else if(!wt&&ot==="?")it.query="",Ot=ir;else if(!wt&&ot==="#")it.fragment="",Ot=Wn;else if(ot!==bt&&(Ot=Vn,ot!=="/"))continue;break;case Vn:if(ot===bt||ot==="/"||ot==="\\"&&it.isSpecial()||!wt&&(ot==="?"||ot==="#")){if(Et(_t)?(it.shortenPath(),ot!=="/"&&!(ot==="\\"&&it.isSpecial())&&xt(it.path,"")):Nt(_t)?ot!=="/"&&!(ot==="\\"&&it.isSpecial())&&xt(it.path,""):(it.scheme==="file"&&!it.path.length&&Pt(_t)&&(it.host&&(it.host=""),_t=tt(_t,0)+":"),xt(it.path,_t)),_t="",it.scheme==="file"&&(ot===bt||ot==="?"||ot==="#"))for(;it.path.length>1&&it.path[0]==="";)Ut(it.path);ot==="?"?(it.query="",Ot=ir):ot==="#"&&(it.fragment="",Ot=Wn)}else _t+=ut(ot,Ye);break;case Wa:ot==="?"?(it.query="",Ot=ir):ot==="#"?(it.fragment="",Ot=Wn):ot!==bt&&(it.path[0]+=ut(ot,Zt));break;case ir:!wt&&ot==="#"?(it.fragment="",Ot=Wn):ot!==bt&&(ot==="'"&&it.isSpecial()?it.query+="%27":ot==="#"?it.query+="%23":it.query+=ut(ot,Zt));break;case Wn:ot!==bt&&(it.fragment+=ut(ot,Yt));break}ye++}},parseHost:function(ft){var wt,pt,it;if(tt(ft,0)==="["){if(tt(ft,ft.length-1)!=="]"||(wt=Tt(Xt(ft,1,-1)),!wt))return ae;this.host=wt}else if(this.isSpecial()){if(ft=M(ft),et($e,ft)||(wt=Ft(ft),wt===null))return ae;this.host=wt}else{if(et(ce,ft))return ae;for(wt="",pt=O(ft),it=0;it1?arguments[1]:void 0,Ot=B(pt,new va(wt,!1,it));d||(pt.href=Ot.serialize(),pt.origin=Ot.getOrigin(),pt.protocol=Ot.getProtocol(),pt.username=Ot.getUsername(),pt.password=Ot.getPassword(),pt.host=Ot.getHost(),pt.hostname=Ot.getHostname(),pt.port=Ot.getPort(),pt.pathname=Ot.getPathname(),pt.search=Ot.getSearch(),pt.searchParams=Ot.getSearchParams(),pt.hash=Ot.getHash())},xn=xr.prototype,Mn=function(ft,wt){return{get:function(){return V(this)[ft]()},set:wt&&function(pt){return V(this)[wt](pt)},configurable:!0,enumerable:!0}};if(d&&(A(xn,"href",Mn("serialize","setHref")),A(xn,"origin",Mn("getOrigin")),A(xn,"protocol",Mn("getProtocol","setProtocol")),A(xn,"username",Mn("getUsername","setUsername")),A(xn,"password",Mn("getPassword","setPassword")),A(xn,"host",Mn("getHost","setHost")),A(xn,"hostname",Mn("getHostname","setHostname")),A(xn,"port",Mn("getPort","setPort")),A(xn,"pathname",Mn("getPathname","setPathname")),A(xn,"search",Mn("getSearch","setSearch")),A(xn,"searchParams",Mn("getSearchParams")),A(xn,"hash",Mn("getHash","setHash"))),$(xn,"toJSON",function(){return V(this).serialize()},{enumerable:!0}),$(xn,"toString",function(){return V(this).serialize()},{enumerable:!0}),J){var Ka=J.createObjectURL,Za=J.revokeObjectURL;Ka&&$(xr,"createObjectURL",y(Ka,J)),Za&&$(xr,"revokeObjectURL",y(Za,J))}z(xr,"URL"),u({global:!0,constructor:!0,forced:!h,sham:!d},{URL:xr})},function(x,b,r){var u=r(7),d=r(33),h=r(6),p=r(36),y=d("iterator");x.exports=!u(function(){var T=new URL("b?a=1&b=2&c=3","https://a"),$=T.searchParams,A=new URLSearchParams("a=1&a=2&b=3"),E="";return T.pathname="c%20d",$.forEach(function(R,I){$.delete("b"),E+=I+R}),A.delete("a",2),A.delete("b",void 0),p&&(!T.toJSON||!A.has("a",1)||A.has("a",2)||!A.has("a",void 0)||A.has("b"))||!$.size&&(p||!h)||!$.sort||T.href!=="https://a/c%20d?a=1&c=3"||$.get("c")!=="3"||String(new URLSearchParams("?a=1"))!=="a=1"||!$[y]||new URL("https://a@b").username!=="a"||new URLSearchParams(new URLSearchParams("a=b")).get("a")!=="b"||new URL("https://\u0442\u0435\u0441\u0442").host!=="xn--e1aybc"||new URL("https://a#\u0431").hash!=="#%D0%B1"||E!=="a1c3"||new URL("https://x",void 0).host!=="x"})},function(x,b,r){var u=r(14),d=2147483647,h=36,p=1,y=26,T=38,$=700,A=72,E=128,R="-",I=/[^\0-\u007E]/,O=/[.\u3002\uFF0E\uFF61]/g,C="Overflow: input needs wider integers to process",D=h-p,M=RangeError,F=u(O.exec),z=Math.floor,U=String.fromCharCode,j=u("".charCodeAt),G=u([].join),B=u([].push),V=u("".replace),Y=u("".split),Z=u("".toLowerCase),J=function(_){for(var tt=[],et=0,lt=_.length;et=55296&&mt<=56319&&et>1,_+=z(_/tt);_>D*y>>1;)_=z(_/D),lt+=h;return z(lt+(D+1)*_/(_+T))},rt=function(_){var tt=[];_=J(_);var et=_.length,lt=E,mt=0,gt=A,xt,yt;for(xt=0;xt<_.length;xt++)yt=_[xt],yt<128&&B(tt,U(yt));var Ut=tt.length,Dt=Ut;for(Ut&&B(tt,R);Dt=lt&&ytz((d-mt)/Qt))throw new M(C);for(mt+=(Xt-lt)*Qt,lt=Xt,xt=0;xt<_.length;xt++){if(yt=_[xt],ytd)throw new M(C);if(yt===lt){for(var kt=mt,me=h;;){var ge=me<=gt?p:me>=gt+y?y:me-gt;if(kt0&&(Rt&jt)!==0;jt>>=1)zt++;return zt},qt=function(Rt){var zt=null;switch(Rt.length){case 1:zt=Rt[0];break;case 2:zt=(Rt[0]&31)<<6|Rt[1]&63;break;case 3:zt=(Rt[0]&15)<<12|(Rt[1]&63)<<6|Rt[2]&63;break;case 4:zt=(Rt[0]&7)<<18|(Rt[1]&63)<<12|(Rt[2]&63)<<6|Rt[3]&63;break}return zt>1114111?null:zt},te=function(Rt){Rt=fe(Rt,Te," ");for(var zt=Rt.length,jt="",Wt=0;Wtzt){jt+="%",Wt++;continue}var Ee=Ft(Rt,Wt+1);if(Ee!==Ee){jt+=ue,Wt++;continue}Wt+=2;var Xe=Tt(Ee);if(Xe===0)ue=ae(Ee);else{if(Xe===1||Xe>4){jt+=de,Wt++;continue}for(var Je=[Ee],nn=1;nnzt||re(Rt,Wt)!=="%"));){var vn=Ft(Rt,Wt+1);if(vn!==vn){Wt+=3;break}if(vn>191||vn<128)break;ee(Je,vn),Wt+=2,nn++}if(Je.length!==Xe){jt+=de;continue}var jn=qt(Je);jn===null?jt+=de:ue=Mt(jn)}}jt+=ue,Wt++}return jt},Zt=/[!'()~]|%20/g,Yt={"!":"%21","'":"%27","(":"%28",")":"%29","~":"%7E","%20":"+"},Ye=function(Rt){return Yt[Rt]},Ze=function(Rt){return fe(ge(Rt),Zt,Ye)},ut=C(function(zt,jt){gt(this,{type:mt,target:xt(zt).entries,index:0,kind:jt})},lt,function(){var zt=yt(this),jt=zt.target,Wt=zt.index++;if(!jt||Wt>=jt.length)return zt.target=null,nt(void 0,!0);var ue=jt[Wt];switch(zt.kind){case"keys":return nt(ue.key,!1);case"values":return nt(ue.value,!1)}return nt([ue.key,ue.value],!1)},!0),It=function(Rt){this.entries=[],this.url=null,Rt!==void 0&&(B(Rt)?this.parseObject(Rt):this.parseQuery(typeof Rt=="string"?re(Rt,0)==="?"?ce(Rt,1):Rt:V(Rt)))};It.prototype={type:lt,bindURL:function(Rt){this.url=Rt,this.update()},parseObject:function(Rt){var zt=this.entries,jt=q(Rt),Wt,ue,Ee,Xe,Je,nn,vn;if(jt)for(Wt=J(Rt,jt),ue=Wt.next;!(Ee=y(ue,Wt)).done;){if(Xe=J(G(Ee.value)),Je=Xe.next,(nn=y(Je,Xe)).done||(vn=y(Je,Xe)).done||!y(Je,Xe).done)throw new me("Expected sequence with length 2");ee(zt,{key:V(nn.value),value:V(vn.value)})}else for(var jn in Rt)z(Rt,jn)&&ee(zt,{key:jn,value:V(Rt[jn])})},parseQuery:function(Rt){if(Rt)for(var zt=this.entries,jt=$e(Rt,"&"),Wt=0,ue,Ee;Wt0?arguments[0]:void 0,jt=gt(this,new It(zt));$||(this.size=jt.entries.length)},Ct=Pt.prototype;if(I(Ct,{append:function(zt,jt){var Wt=xt(this);rt(arguments.length,2),ee(Wt.entries,{key:V(zt),value:V(jt)}),$||this.length++,Wt.updateURL()},delete:function(Rt){for(var zt=xt(this),jt=rt(arguments.length,1),Wt=zt.entries,ue=V(Rt),Ee=jt<2?void 0:arguments[1],Xe=Ee===void 0?Ee:V(Ee),Je=0;JeWt.key?1:-1}),zt.updateURL()},forEach:function(zt){for(var jt=xt(this).entries,Wt=U(zt,arguments.length>1?arguments[1]:void 0),ue=0,Ee;ue1?ie(arguments[1]):{})}}),F(Dt)){var we=function(zt){return M(this,Qt),new Dt(zt,arguments.length>1?ie(arguments[1]):{})};Qt.constructor=we,we.prototype=Qt,u({global:!0,constructor:!0,dontCallGetSet:!0,forced:!0},{Request:we})}}x.exports={URLSearchParams:Pt,getState:xt}},function(x,b,r){var u=r(3),d=r(23),h=r(7),p=r(367),y=r(68),T=r(574),$=d("URL"),A=T&&h(function(){$.canParse()}),E=h(function(){return $.canParse.length!==1});u({target:"URL",stat:!0,forced:!A||E},{canParse:function(I){var O=p(arguments.length,1),C=y(I),D=O<2||arguments[1]===void 0?void 0:y(arguments[1]);try{return!!new $(C,D)}catch(M){return!1}}})},function(x,b,r){var u=r(3),d=r(23),h=r(367),p=r(68),y=r(574),T=d("URL");u({target:"URL",stat:!0,forced:!y},{parse:function(A){var E=h(arguments.length,1),R=p(A),I=E<2||arguments[1]===void 0?void 0:p(arguments[1]);try{return new T(R,I)}catch(O){return null}}})},function(x,b,r){var u=r(3),d=r(8);u({target:"URL",proto:!0,enumerable:!0},{toJSON:function(){return d(URL.prototype.toString,this)}})},function(x,b,r){r(576)},function(x,b,r){var u=r(47),d=r(14),h=r(68),p=r(367),y=URLSearchParams,T=y.prototype,$=d(T.append),A=d(T.delete),E=d(T.forEach),R=d([].push),I=new y("a=1&a=2&b=3");I.delete("a",1),I.delete("b",void 0),I+""!="a=2"&&u(T,"delete",function(O){var C=arguments.length,D=C<2?void 0:arguments[1];if(C&&D===void 0)return A(this,O);var M=[];E(this,function(Y,Z){R(M,{key:Z,value:Y})}),p(C,1);for(var F=h(O),z=h(D),U=0,j=0,G=!1,B=M.length,V;U=W&&(W=X+1);!(k=L[W])&&++W=0;)(s=a[i])&&(o&&s.compareDocumentPosition(o)^4&&o.parentNode.insertBefore(s,o),o=s);return this}function xt(t){t||(t=yt);function e(v,m){return v&&m?t(v.__data__,m.__data__):!v-!m}for(var n=this._groups,a=n.length,i=new Array(a),o=0;oe?1:t>=e?0:NaN}function Ut(){var t=arguments[0];return arguments[0]=this,t.apply(null,arguments),this}function Dt(){return Array.from(this)}function Xt(){for(var t=this._groups,e=0,n=t.length;e=0&&(e=t.slice(0,n))!=="xmlns"&&(t=t.slice(n+1)),ae.hasOwnProperty(e)?{space:ae[e],local:t}:t}function Ht(t){return function(){this.removeAttribute(t)}}function re(t){return function(){this.removeAttributeNS(t.space,t.local)}}function se(t,e){return function(){this.setAttribute(t,e)}}function ee(t,e){return function(){this.setAttributeNS(t.space,t.local,e)}}function fe(t,e){return function(){var n=e.apply(this,arguments);n==null?this.removeAttribute(t):this.setAttribute(t,n)}}function Pe(t,e){return function(){var n=e.apply(this,arguments);n==null?this.removeAttributeNS(t.space,t.local):this.setAttributeNS(t.space,t.local,n)}}function Me(t,e){var n=Mt(t);if(arguments.length<2){var a=this.node();return n.local?a.getAttributeNS(n.space,n.local):a.getAttribute(n)}return this.each((e==null?n.local?re:Ht:typeof e=="function"?n.local?Pe:fe:n.local?ee:se)(n,e))}function $e(t){return t.ownerDocument&&t.ownerDocument.defaultView||t.document&&t||t.defaultView}function ce(t){return function(){this.style.removeProperty(t)}}function Ae(t,e,n){return function(){this.style.setProperty(t,e,n)}}function Te(t,e,n){return function(){var a=e.apply(this,arguments);a==null?this.style.removeProperty(t):this.style.setProperty(t,a,n)}}function de(t,e,n){return arguments.length>1?this.each((e==null?ce:typeof e=="function"?Te:Ae)(t,e,n==null?"":n)):bt(this.node(),t)}function bt(t,e){return t.style.getPropertyValue(e)||$e(t).getComputedStyle(t,null).getPropertyValue(e)}function Ft(t){return function(){delete this[t]}}function Tt(t,e){return function(){this[t]=e}}function qt(t,e){return function(){var n=e.apply(this,arguments);n==null?delete this[t]:this[t]=n}}function te(t,e){return arguments.length>1?this.each((e==null?Ft:typeof e=="function"?qt:Tt)(t,e)):this.node()[t]}function Zt(t){return t.trim().split(/^|\s+/)}function Yt(t){return t.classList||new Ye(t)}function Ye(t){this._node=t,this._names=Zt(t.getAttribute("class")||"")}Ye.prototype={add:function(t){var e=this._names.indexOf(t);e<0&&(this._names.push(t),this._node.setAttribute("class",this._names.join(" ")))},remove:function(t){var e=this._names.indexOf(t);e>=0&&(this._names.splice(e,1),this._node.setAttribute("class",this._names.join(" ")))},contains:function(t){return this._names.indexOf(t)>=0}};function Ze(t,e){for(var n=Yt(t),a=-1,i=e.length;++a=0&&(n=e.slice(a+1),e=e.slice(0,a)),{type:e,name:n}})}function Ka(t){return function(){var e=this.__on;if(e){for(var n=0,a=-1,i=e.length,o;n(t(o=new Date(+o)),o),i.ceil=o=>(t(o=new Date(o-1)),e(o,1),t(o),o),i.round=o=>{const s=i(o),l=i.ceil(o);return o-s(e(o=new Date(+o),s==null?1:Math.floor(s)),o),i.range=(o,s,l)=>{const c=[];if(o=i.ceil(o),l=l==null?1:Math.floor(l),!(o0))return c;let f;do c.push(f=new Date(+o)),e(o,l),t(o);while(fen(s=>{if(s>=s)for(;t(s),!o(s);)s.setTime(s-1)},(s,l)=>{if(s>=s)if(l<0)for(;++l<=0;)for(;e(s,-1),!o(s););else for(;--l>=0;)for(;e(s,1),!o(s););}),n&&(i.count=(o,s)=>(an.setTime(+o),Fn.setTime(+s),t(an),t(Fn),Math.floor(n(an,Fn))),i.every=o=>(o=Math.floor(o),!isFinite(o)||!(o>0)?null:o>1?i.filter(a?s=>a(s)%o===0:s=>i.count(0,s)%o===0):i)),i}const Gn=1e3,In=Gn*60,Bn=In*60,or=Bn*24,to=or*7,Ps=or*30,eo=or*365;function Rr(t){return en(e=>{e.setDate(e.getDate()-(e.getDay()+7-t)%7),e.setHours(0,0,0,0)},(e,n)=>{e.setDate(e.getDate()+n*7)},(e,n)=>(n-e-(n.getTimezoneOffset()-e.getTimezoneOffset())*In)/to)}const Ja=Rr(0),Qa=Rr(1),Rf=Rr(2),If=Rr(3),Yr=Rr(4),Of=Rr(5),Cf=Rr(6),I0=Ja.range,O0=Qa.range,C0=Rf.range,P0=If.range,w0=Yr.range,M0=Of.range,D0=Cf.range;function Ir(t){return en(e=>{e.setUTCDate(e.getUTCDate()-(e.getUTCDay()+7-t)%7),e.setUTCHours(0,0,0,0)},(e,n)=>{e.setUTCDate(e.getUTCDate()+n*7)},(e,n)=>(n-e)/to)}const ka=Ir(0),qa=Ir(1),Pf=Ir(2),wf=Ir(3),Wr=Ir(4),Mf=Ir(5),Df=Ir(6),L0=ka.range,N0=qa.range,F0=Pf.range,B0=wf.range,U0=Wr.range,z0=Mf.range,j0=Df.range,pa=en(t=>t.setHours(0,0,0,0),(t,e)=>t.setDate(t.getDate()+e),(t,e)=>(e-t-(e.getTimezoneOffset()-t.getTimezoneOffset())*In)/or,t=>t.getDate()-1),V0=pa.range,_a=en(t=>{t.setUTCHours(0,0,0,0)},(t,e)=>{t.setUTCDate(t.getUTCDate()+e)},(t,e)=>(e-t)/or,t=>t.getUTCDate()-1),G0=_a.range,ws=en(t=>{t.setUTCHours(0,0,0,0)},(t,e)=>{t.setUTCDate(t.getUTCDate()+e)},(t,e)=>(e-t)/or,t=>Math.floor(t/or)),X0=ws.range,sr=en(t=>{t.setMonth(0,1),t.setHours(0,0,0,0)},(t,e)=>{t.setFullYear(t.getFullYear()+e)},(t,e)=>e.getFullYear()-t.getFullYear(),t=>t.getFullYear());sr.every=t=>!isFinite(t=Math.floor(t))||!(t>0)?null:en(e=>{e.setFullYear(Math.floor(e.getFullYear()/t)*t),e.setMonth(0,1),e.setHours(0,0,0,0)},(e,n)=>{e.setFullYear(e.getFullYear()+n*t)});const H0=sr.range,lr=en(t=>{t.setUTCMonth(0,1),t.setUTCHours(0,0,0,0)},(t,e)=>{t.setUTCFullYear(t.getUTCFullYear()+e)},(t,e)=>e.getUTCFullYear()-t.getUTCFullYear(),t=>t.getUTCFullYear());lr.every=t=>!isFinite(t=Math.floor(t))||!(t>0)?null:en(e=>{e.setUTCFullYear(Math.floor(e.getUTCFullYear()/t)*t),e.setUTCMonth(0,1),e.setUTCHours(0,0,0,0)},(e,n)=>{e.setUTCFullYear(e.getUTCFullYear()+n*t)});const Y0=lr.range;function no(t){if(0<=t.y&&t.y<100){var e=new Date(-1,t.m,t.d,t.H,t.M,t.S,t.L);return e.setFullYear(t.y),e}return new Date(t.y,t.m,t.d,t.H,t.M,t.S,t.L)}function ro(t){if(0<=t.y&&t.y<100){var e=new Date(Date.UTC(-1,t.m,t.d,t.H,t.M,t.S,t.L));return e.setUTCFullYear(t.y),e}return new Date(Date.UTC(t.y,t.m,t.d,t.H,t.M,t.S,t.L))}function ma(t,e,n){return{y:t,m:e,d:n,H:0,M:0,S:0,L:0}}function Lf(t){var e=t.dateTime,n=t.date,a=t.time,i=t.periods,o=t.days,s=t.shortDays,l=t.months,c=t.shortMonths,f=ya(i),g=xa(i),v=ya(o),m=xa(o),S=ya(s),P=xa(s),N=ya(l),L=xa(l),w=ya(c),X=xa(c),W={a:At,A:Gt,b:Bt,B:Kt,c:null,d:Bs,e:Bs,f:rd,g:hd,G:vd,H:td,I:ed,j:nd,L:Us,m:ad,M:id,p:ne,q:le,Q:Hs,s:Ys,S:od,u:sd,U:ld,V:cd,w:ud,W:fd,x:null,X:null,y:dd,Y:gd,Z:pd,"%":Xs},H={a:be,A:Oe,b:Ce,B:He,c:null,d:js,e:js,f:Td,g:Pd,G:Md,H:md,I:yd,j:xd,L:Vs,m:$d,M:Sd,p:Fe,q:dn,Q:Hs,s:Ys,S:Ad,u:Ed,U:bd,V:Rd,w:Id,W:Od,x:null,X:null,y:Cd,Y:wd,Z:Dd,"%":Xs},k={a:dt,A:st,b:Vt,B:vt,c:Q,d:Ns,e:Ns,f:Qf,g:Ls,G:Ds,H:Fs,I:Fs,j:Wf,L:Jf,m:Yf,M:Kf,p:$t,q:Hf,Q:qf,s:_f,S:Zf,u:zf,U:jf,V:Vf,w:Uf,W:Gf,x:St,X:ct,y:Ls,Y:Ds,Z:Xf,"%":kf};W.x=K(n,W),W.X=K(a,W),W.c=K(e,W),H.x=K(n,H),H.X=K(a,H),H.c=K(e,H);function K(Jt,xe){return function(Re){var Lt=[],un=-1,Ge=0,Pn=Jt.length,wn,pe,fn;for(Re instanceof Date||(Re=new Date(+Re));++un53)return null;"w"in Lt||(Lt.w=1),"Z"in Lt?(Ge=ro(ma(Lt.y,0,1)),Pn=Ge.getUTCDay(),Ge=Pn>4||Pn===0?qa.ceil(Ge):qa(Ge),Ge=_a.offset(Ge,(Lt.V-1)*7),Lt.y=Ge.getUTCFullYear(),Lt.m=Ge.getUTCMonth(),Lt.d=Ge.getUTCDate()+(Lt.w+6)%7):(Ge=no(ma(Lt.y,0,1)),Pn=Ge.getDay(),Ge=Pn>4||Pn===0?Qa.ceil(Ge):Qa(Ge),Ge=pa.offset(Ge,(Lt.V-1)*7),Lt.y=Ge.getFullYear(),Lt.m=Ge.getMonth(),Lt.d=Ge.getDate()+(Lt.w+6)%7)}else("W"in Lt||"U"in Lt)&&("w"in Lt||(Lt.w="u"in Lt?Lt.u%7:"W"in Lt?1:0),Pn="Z"in Lt?ro(ma(Lt.y,0,1)).getUTCDay():no(ma(Lt.y,0,1)).getDay(),Lt.m=0,Lt.d="W"in Lt?(Lt.w+6)%7+Lt.W*7-(Pn+5)%7:Lt.w+Lt.U*7-(Pn+6)%7);return"Z"in Lt?(Lt.H+=Lt.Z/100|0,Lt.M+=Lt.Z%100,ro(Lt)):no(Lt)}}function ht(Jt,xe,Re,Lt){for(var un=0,Ge=xe.length,Pn=Re.length,wn,pe;un=Pn)return-1;if(wn=xe.charCodeAt(un++),wn===37){if(wn=xe.charAt(un++),pe=k[wn in Ms?xe.charAt(un++):wn],!pe||(Lt=pe(Jt,Re,Lt))<0)return-1}else if(wn!=Re.charCodeAt(Lt++))return-1}return Lt}function $t(Jt,xe,Re){var Lt=f.exec(xe.slice(Re));return Lt?(Jt.p=g.get(Lt[0].toLowerCase()),Re+Lt[0].length):-1}function dt(Jt,xe,Re){var Lt=S.exec(xe.slice(Re));return Lt?(Jt.w=P.get(Lt[0].toLowerCase()),Re+Lt[0].length):-1}function st(Jt,xe,Re){var Lt=v.exec(xe.slice(Re));return Lt?(Jt.w=m.get(Lt[0].toLowerCase()),Re+Lt[0].length):-1}function Vt(Jt,xe,Re){var Lt=w.exec(xe.slice(Re));return Lt?(Jt.m=X.get(Lt[0].toLowerCase()),Re+Lt[0].length):-1}function vt(Jt,xe,Re){var Lt=N.exec(xe.slice(Re));return Lt?(Jt.m=L.get(Lt[0].toLowerCase()),Re+Lt[0].length):-1}function Q(Jt,xe,Re){return ht(Jt,e,xe,Re)}function St(Jt,xe,Re){return ht(Jt,n,xe,Re)}function ct(Jt,xe,Re){return ht(Jt,a,xe,Re)}function At(Jt){return s[Jt.getDay()]}function Gt(Jt){return o[Jt.getDay()]}function Bt(Jt){return c[Jt.getMonth()]}function Kt(Jt){return l[Jt.getMonth()]}function ne(Jt){return i[+(Jt.getHours()>=12)]}function le(Jt){return 1+~~(Jt.getMonth()/3)}function be(Jt){return s[Jt.getUTCDay()]}function Oe(Jt){return o[Jt.getUTCDay()]}function Ce(Jt){return c[Jt.getUTCMonth()]}function He(Jt){return l[Jt.getUTCMonth()]}function Fe(Jt){return i[+(Jt.getUTCHours()>=12)]}function dn(Jt){return 1+~~(Jt.getUTCMonth()/3)}return{format:function(Jt){var xe=K(Jt+="",W);return xe.toString=function(){return Jt},xe},parse:function(Jt){var xe=at(Jt+="",!1);return xe.toString=function(){return Jt},xe},utcFormat:function(Jt){var xe=K(Jt+="",H);return xe.toString=function(){return Jt},xe},utcParse:function(Jt){var xe=at(Jt+="",!0);return xe.toString=function(){return Jt},xe}}}var Ms={"-":"",_:" ",0:"0"},mn=/^\s*\d+/,Nf=/^%/,Ff=/[\\^$*+?|[\]().{}]/g;function Ne(t,e,n){var a=t<0?"-":"",i=(a?-t:t)+"",o=i.length;return a+(o[e.toLowerCase(),n]))}function Uf(t,e,n){var a=mn.exec(e.slice(n,n+1));return a?(t.w=+a[0],n+a[0].length):-1}function zf(t,e,n){var a=mn.exec(e.slice(n,n+1));return a?(t.u=+a[0],n+a[0].length):-1}function jf(t,e,n){var a=mn.exec(e.slice(n,n+2));return a?(t.U=+a[0],n+a[0].length):-1}function Vf(t,e,n){var a=mn.exec(e.slice(n,n+2));return a?(t.V=+a[0],n+a[0].length):-1}function Gf(t,e,n){var a=mn.exec(e.slice(n,n+2));return a?(t.W=+a[0],n+a[0].length):-1}function Ds(t,e,n){var a=mn.exec(e.slice(n,n+4));return a?(t.y=+a[0],n+a[0].length):-1}function Ls(t,e,n){var a=mn.exec(e.slice(n,n+2));return a?(t.y=+a[0]+(+a[0]>68?1900:2e3),n+a[0].length):-1}function Xf(t,e,n){var a=/^(Z)|([+-]\d\d)(?::?(\d\d))?/.exec(e.slice(n,n+6));return a?(t.Z=a[1]?0:-(a[2]+(a[3]||"00")),n+a[0].length):-1}function Hf(t,e,n){var a=mn.exec(e.slice(n,n+1));return a?(t.q=a[0]*3-3,n+a[0].length):-1}function Yf(t,e,n){var a=mn.exec(e.slice(n,n+2));return a?(t.m=a[0]-1,n+a[0].length):-1}function Ns(t,e,n){var a=mn.exec(e.slice(n,n+2));return a?(t.d=+a[0],n+a[0].length):-1}function Wf(t,e,n){var a=mn.exec(e.slice(n,n+3));return a?(t.m=0,t.d=+a[0],n+a[0].length):-1}function Fs(t,e,n){var a=mn.exec(e.slice(n,n+2));return a?(t.H=+a[0],n+a[0].length):-1}function Kf(t,e,n){var a=mn.exec(e.slice(n,n+2));return a?(t.M=+a[0],n+a[0].length):-1}function Zf(t,e,n){var a=mn.exec(e.slice(n,n+2));return a?(t.S=+a[0],n+a[0].length):-1}function Jf(t,e,n){var a=mn.exec(e.slice(n,n+3));return a?(t.L=+a[0],n+a[0].length):-1}function Qf(t,e,n){var a=mn.exec(e.slice(n,n+6));return a?(t.L=Math.floor(a[0]/1e3),n+a[0].length):-1}function kf(t,e,n){var a=Nf.exec(e.slice(n,n+1));return a?n+a[0].length:-1}function qf(t,e,n){var a=mn.exec(e.slice(n));return a?(t.Q=+a[0],n+a[0].length):-1}function _f(t,e,n){var a=mn.exec(e.slice(n));return a?(t.s=+a[0],n+a[0].length):-1}function Bs(t,e){return Ne(t.getDate(),e,2)}function td(t,e){return Ne(t.getHours(),e,2)}function ed(t,e){return Ne(t.getHours()%12||12,e,2)}function nd(t,e){return Ne(1+pa.count(sr(t),t),e,3)}function Us(t,e){return Ne(t.getMilliseconds(),e,3)}function rd(t,e){return Us(t,e)+"000"}function ad(t,e){return Ne(t.getMonth()+1,e,2)}function id(t,e){return Ne(t.getMinutes(),e,2)}function od(t,e){return Ne(t.getSeconds(),e,2)}function sd(t){var e=t.getDay();return e===0?7:e}function ld(t,e){return Ne(Ja.count(sr(t)-1,t),e,2)}function zs(t){var e=t.getDay();return e>=4||e===0?Yr(t):Yr.ceil(t)}function cd(t,e){return t=zs(t),Ne(Yr.count(sr(t),t)+(sr(t).getDay()===4),e,2)}function ud(t){return t.getDay()}function fd(t,e){return Ne(Qa.count(sr(t)-1,t),e,2)}function dd(t,e){return Ne(t.getFullYear()%100,e,2)}function hd(t,e){return t=zs(t),Ne(t.getFullYear()%100,e,2)}function gd(t,e){return Ne(t.getFullYear()%1e4,e,4)}function vd(t,e){var n=t.getDay();return t=n>=4||n===0?Yr(t):Yr.ceil(t),Ne(t.getFullYear()%1e4,e,4)}function pd(t){var e=t.getTimezoneOffset();return(e>0?"-":(e*=-1,"+"))+Ne(e/60|0,"0",2)+Ne(e%60,"0",2)}function js(t,e){return Ne(t.getUTCDate(),e,2)}function md(t,e){return Ne(t.getUTCHours(),e,2)}function yd(t,e){return Ne(t.getUTCHours()%12||12,e,2)}function xd(t,e){return Ne(1+_a.count(lr(t),t),e,3)}function Vs(t,e){return Ne(t.getUTCMilliseconds(),e,3)}function Td(t,e){return Vs(t,e)+"000"}function $d(t,e){return Ne(t.getUTCMonth()+1,e,2)}function Sd(t,e){return Ne(t.getUTCMinutes(),e,2)}function Ad(t,e){return Ne(t.getUTCSeconds(),e,2)}function Ed(t){var e=t.getUTCDay();return e===0?7:e}function bd(t,e){return Ne(ka.count(lr(t)-1,t),e,2)}function Gs(t){var e=t.getUTCDay();return e>=4||e===0?Wr(t):Wr.ceil(t)}function Rd(t,e){return t=Gs(t),Ne(Wr.count(lr(t),t)+(lr(t).getUTCDay()===4),e,2)}function Id(t){return t.getUTCDay()}function Od(t,e){return Ne(qa.count(lr(t)-1,t),e,2)}function Cd(t,e){return Ne(t.getUTCFullYear()%100,e,2)}function Pd(t,e){return t=Gs(t),Ne(t.getUTCFullYear()%100,e,2)}function wd(t,e){return Ne(t.getUTCFullYear()%1e4,e,4)}function Md(t,e){var n=t.getUTCDay();return t=n>=4||n===0?Wr(t):Wr.ceil(t),Ne(t.getUTCFullYear()%1e4,e,4)}function Dd(){return"+0000"}function Xs(){return"%"}function Hs(t){return+t}function Ys(t){return Math.floor(+t/1e3)}var Kr,ao,Ws,io,Ks;Ld({dateTime:"%x, %X",date:"%-m/%-d/%Y",time:"%-I:%M:%S %p",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});function Ld(t){return Kr=Lf(t),ao=Kr.format,Ws=Kr.parse,io=Kr.utcFormat,Ks=Kr.utcParse,Kr}var Nd=Object.defineProperty,Zs=Object.getOwnPropertySymbols,Fd=Object.prototype.hasOwnProperty,Bd=Object.prototype.propertyIsEnumerable,Js=(t,e,n)=>e in t?Nd(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n,ke=(t,e)=>{for(var n in e||(e={}))Fd.call(e,n)&&Js(t,n,e[n]);if(Zs)for(var n of Zs(e))Bd.call(e,n)&&Js(t,n,e[n]);return t};const Se={button:"bb-button",chart:"bb-chart",empty:"bb-empty",main:"bb-main",target:"bb-target",EXPANDED:"_expanded_"},Ve={arc:"bb-arc",arcLabelLine:"bb-arc-label-line",arcRange:"bb-arc-range",arcs:"bb-arcs",chartArc:"bb-chart-arc",chartArcs:"bb-chart-arcs",chartArcsBackground:"bb-chart-arcs-background",chartArcsTitle:"bb-chart-arcs-title",needle:"bb-needle"},ti={area:"bb-area",areas:"bb-areas"},Tn={axis:"bb-axis",axisX:"bb-axis-x",axisXLabel:"bb-axis-x-label",axisY:"bb-axis-y",axisY2:"bb-axis-y2",axisY2Label:"bb-axis-y2-label",axisYLabel:"bb-axis-y-label",axisXTooltip:"bb-axis-x-tooltip",axisYTooltip:"bb-axis-y-tooltip",axisY2Tooltip:"bb-axis-y2-tooltip"},Kn={bar:"bb-bar",bars:"bb-bars",chartBar:"bb-chart-bar",chartBars:"bb-chart-bars"},cr={candlestick:"bb-candlestick",candlesticks:"bb-candlesticks",chartCandlestick:"bb-chart-candlestick",chartCandlesticks:"bb-chart-candlesticks",valueDown:"bb-value-down",valueUp:"bb-value-up"},$n={chartCircles:"bb-chart-circles",circle:"bb-circle",circles:"bb-circles"},oo={colorPattern:"bb-color-pattern",colorScale:"bb-colorscale"},Or={dragarea:"bb-dragarea",INCLUDED:"_included_"},Ta={funnel:"bb-funnel",chartFunnel:"bb-chart-funnel",chartFunnels:"bb-chart-funnels",funnelBackground:"bb-funnel-background"},Un={chartArcsGaugeMax:"bb-chart-arcs-gauge-max",chartArcsGaugeMin:"bb-chart-arcs-gauge-min",chartArcsGaugeUnit:"bb-chart-arcs-gauge-unit",chartArcsGaugeTitle:"bb-chart-arcs-gauge-title",gaugeValue:"bb-gauge-value"},We={legend:"bb-legend",legendBackground:"bb-legend-background",legendItem:"bb-legend-item",legendItemEvent:"bb-legend-item-event",legendItemHidden:"bb-legend-item-hidden",legendItemPoint:"bb-legend-item-point",legendItemTile:"bb-legend-item-tile"},ur={chartLine:"bb-chart-line",chartLines:"bb-chart-lines",line:"bb-line",lines:"bb-lines"},Zn={eventRect:"bb-event-rect",eventRects:"bb-event-rects",eventRectsMultiple:"bb-event-rects-multiple",eventRectsSingle:"bb-event-rects-single"},qe={focused:"bb-focused",defocused:"bb-defocused",legendItemFocused:"bb-legend-item-focused",xgridFocus:"bb-xgrid-focus",ygridFocus:"bb-ygrid-focus"},on={grid:"bb-grid",gridLines:"bb-grid-lines",xgrid:"bb-xgrid",xgridLine:"bb-xgrid-line",xgridLines:"bb-xgrid-lines",xgrids:"bb-xgrids",ygrid:"bb-ygrid",ygridLine:"bb-ygrid-line",ygridLines:"bb-ygrid-lines",ygrids:"bb-ygrids"},Tr={level:"bb-level",levels:"bb-levels"},Qs={chartRadar:"bb-chart-radar",chartRadars:"bb-chart-radars"},$a={region:"bb-region",regions:"bb-regions"},tn={selectedCircle:"bb-selected-circle",selectedCircles:"bb-selected-circles",SELECTED:"_selected_"},sn={shape:"bb-shape",shapes:"bb-shapes"},ks={brush:"bb-brush",subchart:"bb-subchart"},On={chartText:"bb-chart-text",chartTexts:"bb-chart-texts",text:"bb-text",texts:"bb-texts",title:"bb-title",TextOverlapping:"text-overlapping"},ei={tooltip:"bb-tooltip",tooltipContainer:"bb-tooltip-container",tooltipName:"bb-tooltip-name"},qs={treemap:"bb-treemap",chartTreemap:"bb-chart-treemap",chartTreemaps:"bb-chart-treemaps"},so={buttonZoomReset:"bb-zoom-reset",zoomBrush:"bb-zoom-brush"};var Ue=ke(ke(ke(ke(ke(ke(ke(ke(ke(ke(ke(ke(ke(ke(ke(ke(ke(ke(ke(ke(ke(ke(ke(ke(ke({},Se),Ve),ti),Tn),Kn),cr),$n),oo),Or),Un),We),ur),Zn),qe),Ta),on),Qs),$a),tn),sn),ks),On),ei),qs),so),Ud={boost_useCssRule:!1,boost_useWorker:!1},zd={color_pattern:[],color_tiles:void 0,color_threshold:{},color_onover:void 0},jd={legend_contents_bindto:void 0,legend_contents_template:"{=TITLE}",legend_equally:!1,legend_hide:!1,legend_inset_anchor:"top-left",legend_inset_x:10,legend_inset_y:0,legend_inset_step:void 0,legend_item_interaction:!0,legend_item_dblclick:!1,legend_item_onclick:void 0,legend_item_onover:void 0,legend_item_onout:void 0,legend_item_tile_width:10,legend_item_tile_height:10,legend_item_tile_r:5,legend_item_tile_type:"rectangle",legend_format:void 0,legend_padding:0,legend_position:"bottom",legend_show:!0,legend_tooltip:!1,legend_usePoint:!1},Vd={bindto:"#chart",background:{},clipPath:!0,svg_classname:void 0,size_width:void 0,size_height:void 0,padding:!0,padding_mode:void 0,padding_left:void 0,padding_right:void 0,padding_top:void 0,padding_bottom:void 0,resize_auto:!0,resize_timer:!0,onclick:void 0,onover:void 0,onout:void 0,onresize:void 0,onresized:void 0,onbeforeinit:void 0,oninit:void 0,onafterinit:void 0,onrendered:void 0,transition_duration:250,plugins:[],render:{},regions:[]},Gd={title_text:void 0,title_padding:{top:0,right:0,bottom:0,left:0},title_position:"center"},Xd={tooltip_show:!0,tooltip_doNotHide:!1,tooltip_grouped:!0,tooltip_format_title:void 0,tooltip_format_name:void 0,tooltip_format_value:void 0,tooltip_position:void 0,tooltip_contents:{},tooltip_init_show:!1,tooltip_init_x:0,tooltip_init_position:void 0,tooltip_linked:!1,tooltip_linked_name:"",tooltip_onshow:()=>{},tooltip_onhide:()=>{},tooltip_onshown:()=>{},tooltip_onhidden:()=>{},tooltip_order:null},Hd={data_x:void 0,data_idConverter:t=>t,data_names:{},data_classes:{},data_type:void 0,data_types:{},data_order:"desc",data_groups:[],data_groupsZeroAs:"positive",data_color:void 0,data_colors:{},data_labels:{},data_labels_backgroundColors:void 0,data_labels_colors:void 0,data_labels_position:{},data_hide:!1,data_filter:void 0,data_onclick:()=>{},data_onover:()=>{},data_onout:()=>{},data_onshown:void 0,data_onhidden:void 0,data_onmin:void 0,data_onmax:void 0,data_url:void 0,data_headers:void 0,data_json:void 0,data_rows:void 0,data_columns:void 0,data_mimeType:"csv",data_keys:void 0,data_empty_label_text:""},Yd={interaction_enabled:!0,interaction_brighten:!0,interaction_inputType_mouse:!0,interaction_inputType_touch:{},interaction_onout:!0},Wd={value:()=>{}};function _s(){for(var t=0,e=arguments.length,n={},a;t=0&&(a=n.slice(i+1),n=n.slice(0,i)),n&&!e.hasOwnProperty(n))throw new Error("unknown type: "+n);return{type:n,name:a}})}ni.prototype=_s.prototype={constructor:ni,on:function(t,e){var n=this._,a=Kd(t+"",n),i,o=-1,s=a.length;if(arguments.length<2){for(;++o0)for(var n=new Array(i),a=0,i,o;a>8&15|e>>4&240,e>>4&15|e&240,(e&15)<<4|e&15,1):n===8?ii(e>>24&255,e>>16&255,e>>8&255,(e&255)/255):n===4?ii(e>>12&15|e>>8&240,e>>8&15|e>>4&240,e>>4&15|e&240,((e&15)<<4|e&15)/255):null):(e=kd.exec(t))?new Dn(e[1],e[2],e[3],1):(e=qd.exec(t))?new Dn(e[1]*255/100,e[2]*255/100,e[3]*255/100,1):(e=_d.exec(t))?ii(e[1],e[2],e[3],e[4]):(e=th.exec(t))?ii(e[1]*255/100,e[2]*255/100,e[3]*255/100,e[4]):(e=eh.exec(t))?ll(e[1],e[2]/100,e[3]/100,1):(e=nh.exec(t))?ll(e[1],e[2]/100,e[3]/100,e[4]):nl.hasOwnProperty(t)?il(nl[t]):t==="transparent"?new Dn(NaN,NaN,NaN,0):null}function il(t){return new Dn(t>>16&255,t>>8&255,t&255,1)}function ii(t,e,n,a){return a<=0&&(t=e=n=NaN),new Dn(t,e,n,a)}function ih(t){return t instanceof Aa||(t=Cr(t)),t?(t=t.rgb(),new Dn(t.r,t.g,t.b,t.opacity)):new Dn}function oi(t,e,n,a){return arguments.length===1?ih(t):new Dn(t,e,n,a==null?1:a)}function Dn(t,e,n,a){this.r=+t,this.g=+e,this.b=+n,this.opacity=+a}fo(Dn,oi,el(Aa,{brighter(t){return t=t==null?ai:Math.pow(ai,t),new Dn(this.r*t,this.g*t,this.b*t,this.opacity)},darker(t){return t=t==null?Ea:Math.pow(Ea,t),new Dn(this.r*t,this.g*t,this.b*t,this.opacity)},rgb(){return this},clamp(){return new Dn(Pr(this.r),Pr(this.g),Pr(this.b),si(this.opacity))},displayable(){return-.5<=this.r&&this.r<255.5&&-.5<=this.g&&this.g<255.5&&-.5<=this.b&&this.b<255.5&&0<=this.opacity&&this.opacity<=1},hex:ol,formatHex:ol,formatHex8:oh,formatRgb:sl,toString:sl}));function ol(){return`#${wr(this.r)}${wr(this.g)}${wr(this.b)}`}function oh(){return`#${wr(this.r)}${wr(this.g)}${wr(this.b)}${wr((isNaN(this.opacity)?1:this.opacity)*255)}`}function sl(){const t=si(this.opacity);return`${t===1?"rgb(":"rgba("}${Pr(this.r)}, ${Pr(this.g)}, ${Pr(this.b)}${t===1?")":`, ${t})`}`}function si(t){return isNaN(t)?1:Math.max(0,Math.min(1,t))}function Pr(t){return Math.max(0,Math.min(255,Math.round(t)||0))}function wr(t){return t=Pr(t),(t<16?"0":"")+t.toString(16)}function ll(t,e,n,a){return a<=0?t=e=n=NaN:n<=0||n>=1?t=e=NaN:e<=0&&(t=NaN),new Jn(t,e,n,a)}function cl(t){if(t instanceof Jn)return new Jn(t.h,t.s,t.l,t.opacity);if(t instanceof Aa||(t=Cr(t)),!t)return new Jn;if(t instanceof Jn)return t;t=t.rgb();var e=t.r/255,n=t.g/255,a=t.b/255,i=Math.min(e,n,a),o=Math.max(e,n,a),s=NaN,l=o-i,c=(o+i)/2;return l?(e===o?s=(n-a)/l+(n0&&c<1?0:s,new Jn(s,l,c,t.opacity)}function sh(t,e,n,a){return arguments.length===1?cl(t):new Jn(t,e,n,a==null?1:a)}function Jn(t,e,n,a){this.h=+t,this.s=+e,this.l=+n,this.opacity=+a}fo(Jn,sh,el(Aa,{brighter(t){return t=t==null?ai:Math.pow(ai,t),new Jn(this.h,this.s,this.l*t,this.opacity)},darker(t){return t=t==null?Ea:Math.pow(Ea,t),new Jn(this.h,this.s,this.l*t,this.opacity)},rgb(){var t=this.h%360+(this.h<0)*360,e=isNaN(t)||isNaN(this.s)?0:this.s,n=this.l,a=n+(n<.5?n:1-n)*e,i=2*n-a;return new Dn(ho(t>=240?t-240:t+120,i,a),ho(t,i,a),ho(t<120?t+240:t-120,i,a),this.opacity)},clamp(){return new Jn(ul(this.h),li(this.s),li(this.l),si(this.opacity))},displayable(){return(0<=this.s&&this.s<=1||isNaN(this.s))&&0<=this.l&&this.l<=1&&0<=this.opacity&&this.opacity<=1},formatHsl(){const t=si(this.opacity);return`${t===1?"hsl(":"hsla("}${ul(this.h)}, ${li(this.s)*100}%, ${li(this.l)*100}%${t===1?")":`, ${t})`}`}}));function ul(t){return t=(t||0)%360,t<0?t+360:t}function li(t){return Math.max(0,Math.min(1,t||0))}function ho(t,e,n){return(t<60?e+(n-e)*t/60:t<180?n:t<240?e+(n-e)*(240-t)/60:e)*255}function fl(t,e,n,a,i){var o=t*t,s=o*t;return((1-3*t+3*o-s)*e+(4-6*o+3*s)*n+(1+3*t+3*o-3*s)*a+s*i)/6}function lh(t){var e=t.length-1;return function(n){var a=n<=0?n=0:n>=1?(n=1,e-1):Math.floor(n*e),i=t[a],o=t[a+1],s=a>0?t[a-1]:2*i-o,l=a()=>t;function dl(t,e){return function(n){return t+n*e}}function uh(t,e,n){return t=Math.pow(t,n),e=Math.pow(e,n)-t,n=1/n,function(a){return Math.pow(t+a*e,n)}}function W0(t,e){var n=e-t;return n?dl(t,n>180||n<-180?n-360*Math.round(n/360):n):ci(isNaN(t)?e:t)}function fh(t){return(t=+t)==1?hl:function(e,n){return n-e?uh(e,n,t):ci(isNaN(e)?n:e)}}function hl(t,e){var n=e-t;return n?dl(t,n):ci(isNaN(t)?e:t)}var ui=function t(e){var n=fh(e);function a(i,o){var s=n((i=oi(i)).r,(o=oi(o)).r),l=n(i.g,o.g),c=n(i.b,o.b),f=hl(i.opacity,o.opacity);return function(g){return i.r=s(g),i.g=l(g),i.b=c(g),i.opacity=f(g),i+""}}return a.gamma=t,a}(1);function gl(t){return function(e){var n=e.length,a=new Array(n),i=new Array(n),o=new Array(n),s,l;for(s=0;sn&&(o=e.slice(n,o),l[s]?l[s]+=o:l[++s]=o),(a=a[0])===(i=i[0])?l[s]?l[s]+=i:l[++s]=i:(l[++s]=null,c.push({i:s,x:Qn(a,i)})),n=vo.lastIndex;return n=0&&t._call.call(void 0,e),t=t._next;--kr}function Sl(){Mr=(di=Ca.now())+hi,kr=Ra=0;try{yh()}finally{kr=0,Th(),Mr=0}}function xh(){var t=Ca.now(),e=t-di;e>xl&&(hi-=e,di=t)}function Th(){for(var t,e=fi,n,a=1/0;e;)e._call?(a>e._time&&(a=e._time),t=e,e=e._next):(n=e._next,e._next=null,e=t?t._next=n:fi=n);Oa=t,mo(a)}function mo(t){if(!kr){Ra&&(Ra=clearTimeout(Ra));var e=t-Mr;e>24?(t<1/0&&(Ra=setTimeout(Sl,t-Ca.now()-hi)),Ia&&(Ia=clearInterval(Ia))):(Ia||(di=Ca.now(),Ia=setInterval(xh,xl)),kr=1,Tl(Sl))}}function Al(t,e,n){var a=new gi;return e=e==null?0:+e,a.restart(i=>{a.stop(),t(i+e)},e,n),a}var $h=ri("start","end","cancel","interrupt"),Sh=[],El=0,bl=1,yo=2,vi=3,Rl=4,xo=5,pi=6;function mi(t,e,n,a,i,o){var s=t.__transition;if(!s)t.__transition={};else if(n in s)return;Ah(t,n,{name:e,index:a,group:i,on:$h,tween:Sh,time:o.time,delay:o.delay,duration:o.duration,ease:o.ease,timer:null,state:El})}function To(t,e){var n=kn(t,e);if(n.state>El)throw new Error("too late; already scheduled");return n}function er(t,e){var n=kn(t,e);if(n.state>vi)throw new Error("too late; already running");return n}function kn(t,e){var n=t.__transition;if(!n||!(n=n[e]))throw new Error("transition not found");return n}function Ah(t,e,n){var a=t.__transition,i;a[e]=n,n.timer=$l(o,0,n.time);function o(f){n.state=bl,n.timer.restart(s,n.delay,n.time),n.delay<=f&&s(f-n.delay)}function s(f){var g,v,m,S;if(n.state!==bl)return c();for(g in a)if(S=a[g],S.name===n.name){if(S.state===vi)return Al(s);S.state===Rl?(S.state=pi,S.timer.stop(),S.on.call("interrupt",t,t.__data__,S.index,S.group),delete a[g]):+gyo&&a.state180?g+=360:g-f>180&&(f+=360),m.push({i:v.push(i(v)+"rotate(",null,a)-2,x:Qn(f,g)})):g&&v.push(i(v)+"rotate("+g+a)}function l(f,g,v,m){f!==g?m.push({i:v.push(i(v)+"skewX(",null,a)-2,x:Qn(f,g)}):g&&v.push(i(v)+"skewX("+g+a)}function c(f,g,v,m,S,P){if(f!==v||g!==m){var N=S.push(i(S)+"scale(",null,",",null,")");P.push({i:N-4,x:Qn(f,v)},{i:N-2,x:Qn(g,m)})}else(v!==1||m!==1)&&S.push(i(S)+"scale("+v+","+m+")")}return function(f,g){var v=[],m=[];return f=t(f),g=t(g),o(f.translateX,f.translateY,g.translateX,g.translateY,v,m),s(f.rotate,g.rotate,v,m),l(f.skewX,g.skewX,v,m),c(f.scaleX,f.scaleY,g.scaleX,g.scaleY,v,m),f=g=null,function(S){for(var P=-1,N=m.length,L;++P=0&&(e=e.slice(0,n)),!e||e==="start"})}function rg(t,e,n){var a,i,o=ng(e)?To:er;return function(){var s=o(this,t),l=s.on;l!==a&&(i=(a=l).copy()).on(e,n),s.on=i}}function ag(t,e){var n=this._id;return arguments.length<2?kn(this.node(),n).on.on(t):this.each(rg(n,t,e))}function ig(t){return function(){var e=this.parentNode;for(var n in this.__transition)if(+n!==t)return;e&&e.removeChild(this)}}function og(){return this.on("end.remove",ig(this._id))}function sg(t){var e=this._name,n=this._id;typeof t!="function"&&(t=p(t));for(var a=this._groups,i=a.length,o=new Array(i),s=0;s()=>t;function Mg(t,{sourceEvent:e,target:n,selection:a,mode:i,dispatch:o}){Object.defineProperties(this,{type:{value:t,enumerable:!0,configurable:!0},sourceEvent:{value:e,enumerable:!0,configurable:!0},target:{value:n,enumerable:!0,configurable:!0},selection:{value:a,enumerable:!0,configurable:!0},mode:{value:i,enumerable:!0,configurable:!0},_:{value:o}})}function Dg(t){t.stopImmediatePropagation()}function Eo(t){t.preventDefault(),t.stopImmediatePropagation()}var Ll={name:"drag"},bo={name:"space"},_r={name:"handle"},ta={name:"center"};const{abs:Nl,max:Sn,min:An}=Math;function Fl(t){return[+t[0],+t[1]]}function Ro(t){return[Fl(t[0]),Fl(t[1])]}var xi={name:"x",handles:["w","e"].map(Pa),input:function(t,e){return t==null?null:[[+t[0],e[0][1]],[+t[1],e[1][1]]]},output:function(t){return t&&[t[0][0],t[1][0]]}},Ti={name:"y",handles:["n","s"].map(Pa),input:function(t,e){return t==null?null:[[e[0][0],+t[0]],[e[1][0],+t[1]]]},output:function(t){return t&&[t[0][1],t[1][1]]}},Lg={name:"xy",handles:["n","w","e","s","nw","ne","sw","se"].map(Pa),input:function(t){return t==null?null:Ro(t)},output:function(t){return t}},hr={overlay:"crosshair",selection:"move",n:"ns-resize",e:"ew-resize",s:"ns-resize",w:"ew-resize",nw:"nwse-resize",ne:"nesw-resize",se:"nwse-resize",sw:"nesw-resize"},Bl={e:"w",w:"e",nw:"ne",ne:"nw",se:"sw",sw:"se"},Ul={n:"s",s:"n",nw:"sw",ne:"se",se:"ne",sw:"nw"},Ng={overlay:1,selection:1,n:null,e:1,s:null,w:-1,nw:-1,ne:1,se:1,sw:-1},Fg={overlay:1,selection:1,n:-1,e:null,s:1,w:null,nw:-1,ne:-1,se:1,sw:1};function Pa(t){return{type:t}}function Bg(t){return!t.ctrlKey&&!t.button}function Ug(){var t=this.ownerSVGElement||this;return t.hasAttribute("viewBox")?(t=t.viewBox.baseVal,[[t.x,t.y],[t.x+t.width,t.y+t.height]]):[[0,0],[t.width.baseVal.value,t.height.baseVal.value]]}function zg(){return navigator.maxTouchPoints||"ontouchstart"in this}function Io(t){for(;!t.__brush;)if(!(t=t.parentNode))return;return t.__brush}function jg(t){return t[0][0]===t[1][0]||t[0][1]===t[1][1]}function zl(t){var e=t.__brush;return e?e.dim.output(e.selection):null}function Vg(){return Oo(xi)}function Gg(){return Oo(Ti)}function q0(){return Oo(Lg)}function Oo(t){var e=Ug,n=Bg,a=zg,i=!0,o=ri("start","brush","end"),s=6,l;function c(L){var w=L.property("__brush",N).selectAll(".overlay").data([Pa("overlay")]);w.enter().append("rect").attr("class","overlay").attr("pointer-events","all").attr("cursor",hr.overlay).merge(w).each(function(){var W=Io(this).extent;ot(this).attr("x",W[0][0]).attr("y",W[0][1]).attr("width",W[1][0]-W[0][0]).attr("height",W[1][1]-W[0][1])}),L.selectAll(".selection").data([Pa("selection")]).enter().append("rect").attr("class","selection").attr("cursor",hr.selection).attr("fill","#777").attr("fill-opacity",.3).attr("stroke","#fff").attr("shape-rendering","crispEdges");var X=L.selectAll(".handle").data(t.handles,function(W){return W.type});X.exit().remove(),X.enter().append("rect").attr("class",function(W){return"handle handle--"+W.type}).attr("cursor",function(W){return hr[W.type]}),L.each(f).attr("fill","none").attr("pointer-events","all").on("mousedown.brush",m).filter(a).on("touchstart.brush",m).on("touchmove.brush",S).on("touchend.brush touchcancel.brush",P).style("touch-action","none").style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}c.move=function(L,w,X){L.tween?L.on("start.brush",function(W){g(this,arguments).beforestart().start(W)}).on("interrupt.brush end.brush",function(W){g(this,arguments).end(W)}).tween("brush",function(){var W=this,H=W.__brush,k=g(W,arguments),K=H.selection,at=t.input(typeof w=="function"?w.apply(this,arguments):w,H.extent),ht=Qr(K,at);function $t(dt){H.selection=dt===1&&at===null?null:ht(dt),f.call(W),k.brush()}return K!==null&&at!==null?$t:$t(1)}):L.each(function(){var W=this,H=arguments,k=W.__brush,K=t.input(typeof w=="function"?w.apply(W,H):w,k.extent),at=g(W,H).beforestart();qr(W),k.selection=K===null?null:K,f.call(W),at.start(X).brush(X).end(X)})},c.clear=function(L,w){c.move(L,null,w)};function f(){var L=ot(this),w=Io(this).selection;w?(L.selectAll(".selection").style("display",null).attr("x",w[0][0]).attr("y",w[0][1]).attr("width",w[1][0]-w[0][0]).attr("height",w[1][1]-w[0][1]),L.selectAll(".handle").style("display",null).attr("x",function(X){return X.type[X.type.length-1]==="e"?w[1][0]-s/2:w[0][0]-s/2}).attr("y",function(X){return X.type[0]==="s"?w[1][1]-s/2:w[0][1]-s/2}).attr("width",function(X){return X.type==="n"||X.type==="s"?w[1][0]-w[0][0]+s:s}).attr("height",function(X){return X.type==="e"||X.type==="w"?w[1][1]-w[0][1]+s:s})):L.selectAll(".selection,.handle").style("display","none").attr("x",null).attr("y",null).attr("width",null).attr("height",null)}function g(L,w,X){var W=L.__brush.emitter;return W&&(!X||!W.clean)?W:new v(L,w,X)}function v(L,w,X){this.that=L,this.args=w,this.state=L.__brush,this.active=0,this.clean=X}v.prototype={beforestart:function(){return++this.active===1&&(this.state.emitter=this,this.starting=!0),this},start:function(L,w){return this.starting?(this.starting=!1,this.emit("start",L,w)):this.emit("brush",L),this},brush:function(L,w){return this.emit("brush",L,w),this},end:function(L,w){return--this.active===0&&(delete this.state.emitter,this.emit("end",L,w)),this},emit:function(L,w,X){var W=ot(this.that).datum();o.call(L,this.that,new Mg(L,{sourceEvent:w,target:c,selection:t.output(this.state.selection),mode:X,dispatch:o}),W)}};function m(L){if(l&&!L.touches||!n.apply(this,arguments))return;var w=this,X=L.target.__data__.type,W=(i&&L.metaKey?X="overlay":X)==="selection"?Ll:i&&L.altKey?ta:_r,H=t===Ti?null:Ng[X],k=t===xi?null:Fg[X],K=Io(w),at=K.extent,ht=K.selection,$t=at[0][0],dt,st,Vt=at[0][1],vt,Q,St=at[1][0],ct,At,Gt=at[1][1],Bt,Kt,ne=0,le=0,be,Oe=H&&k&&i&&L.shiftKey,Ce,He,Fe=Array.from(L.touches||[L],pe=>{const fn=pe.identifier;return pe=Xn(pe,w),pe.point0=pe.slice(),pe.identifier=fn,pe});qr(w);var dn=g(w,arguments,!0).beforestart();if(X==="overlay"){ht&&(be=!0);const pe=[Fe[0],Fe[1]||Fe[0]];K.selection=ht=[[dt=t===Ti?$t:An(pe[0][0],pe[1][0]),vt=t===xi?Vt:An(pe[0][1],pe[1][1])],[ct=t===Ti?St:Sn(pe[0][0],pe[1][0]),Bt=t===xi?Gt:Sn(pe[0][1],pe[1][1])]],Fe.length>1&&un(L)}else dt=ht[0][0],vt=ht[0][1],ct=ht[1][0],Bt=ht[1][1];st=dt,Q=vt,At=ct,Kt=Bt;var Jt=ot(w).attr("pointer-events","none"),xe=Jt.selectAll(".overlay").attr("cursor",hr[X]);if(L.touches)dn.moved=Lt,dn.ended=Ge;else{var Re=ot(L.view).on("mousemove.brush",Lt,!0).on("mouseup.brush",Ge,!0);i&&Re.on("keydown.brush",Pn,!0).on("keyup.brush",wn,!0),co(L.view)}f.call(w),dn.start(L,W.name);function Lt(pe){for(const fn of pe.changedTouches||[pe])for(const Ga of Fe)Ga.identifier===fn.identifier&&(Ga.cur=Xn(fn,w));if(Oe&&!Ce&&!He&&Fe.length===1){const fn=Fe[0];Nl(fn.cur[0]-fn[0])>Nl(fn.cur[1]-fn[1])?He=!0:Ce=!0}for(const fn of Fe)fn.cur&&(fn[0]=fn.cur[0],fn[1]=fn.cur[1]);be=!0,Eo(pe),un(pe)}function un(pe){const fn=Fe[0],Ga=fn.point0;var br;switch(ne=fn[0]-Ga[0],le=fn[1]-Ga[1],W){case bo:case Ll:{H&&(ne=Sn($t-dt,An(St-ct,ne)),st=dt+ne,At=ct+ne),k&&(le=Sn(Vt-vt,An(Gt-Bt,le)),Q=vt+le,Kt=Bt+le);break}case _r:{Fe[1]?(H&&(st=Sn($t,An(St,Fe[0][0])),At=Sn($t,An(St,Fe[1][0])),H=1),k&&(Q=Sn(Vt,An(Gt,Fe[0][1])),Kt=Sn(Vt,An(Gt,Fe[1][1])),k=1)):(H<0?(ne=Sn($t-dt,An(St-dt,ne)),st=dt+ne,At=ct):H>0&&(ne=Sn($t-ct,An(St-ct,ne)),st=dt,At=ct+ne),k<0?(le=Sn(Vt-vt,An(Gt-vt,le)),Q=vt+le,Kt=Bt):k>0&&(le=Sn(Vt-Bt,An(Gt-Bt,le)),Q=vt,Kt=Bt+le));break}case ta:{H&&(st=Sn($t,An(St,dt-ne*H)),At=Sn($t,An(St,ct+ne*H))),k&&(Q=Sn(Vt,An(Gt,vt-le*k)),Kt=Sn(Vt,An(Gt,Bt+le*k)));break}}At0&&(dt=st-ne),k<0?Bt=Kt-le:k>0&&(vt=Q-le),W=bo,xe.attr("cursor",hr.selection),un(pe));break}default:return}Eo(pe)}function wn(pe){switch(pe.keyCode){case 16:{Oe&&(Ce=He=Oe=!1,un(pe));break}case 18:{W===ta&&(H<0?ct=At:H>0&&(dt=st),k<0?Bt=Kt:k>0&&(vt=Q),W=_r,un(pe));break}case 32:{W===bo&&(pe.altKey?(H&&(ct=At-ne*H,dt=st+ne*H),k&&(Bt=Kt-le*k,vt=Q+le*k),W=ta):(H<0?ct=At:H>0&&(dt=st),k<0?Bt=Kt:k>0&&(vt=Q),W=_r),xe.attr("cursor",hr[X]),un(pe));break}default:return}Eo(pe)}}function S(L){g(this,arguments).moved(L)}function P(L){g(this,arguments).ended(L)}function N(){var L=this.__brush||{selection:null};return L.extent=Ro(e.apply(this,arguments)),L.dim=t,L}return c.extent=function(L){return arguments.length?(e=typeof L=="function"?L:Ao(Ro(L)),c):e},c.filter=function(L){return arguments.length?(n=typeof L=="function"?L:Ao(!!L),c):n},c.touchable=function(L){return arguments.length?(a=typeof L=="function"?L:Ao(!!L),c):a},c.handleSize=function(L){return arguments.length?(s=+L,c):s},c.keyModifiers=function(L){return arguments.length?(i=!!L,c):i},c.on=function(){var L=o.on.apply(o,arguments);return L===o?c:L},c}function Xg(){return typeof globalThis=="object"&&globalThis!==null&&globalThis.Object===Object&&globalThis||typeof global=="object"&&global!==null&&global.Object===Object&&global||typeof self=="object"&&self!==null&&self.Object===Object&&self||Function("return this")()}function Hg(t){const e=typeof(t==null?void 0:t.requestAnimationFrame)=="function"&&typeof(t==null?void 0:t.cancelAnimationFrame)=="function",n=typeof(t==null?void 0:t.requestIdleCallback)=="function"&&typeof(t==null?void 0:t.cancelIdleCallback)=="function",a=o=>setTimeout(o,1),i=o=>clearTimeout(o);return[e?t.requestAnimationFrame:a,e?t.cancelAnimationFrame:i,n?t.requestIdleCallback:a,n?t.cancelIdleCallback:i]}const Ke=Xg(),gn=Ke==null?void 0:Ke.document,[Yg,_0,jl,t1]=Hg(Ke);var Wg=Object.defineProperty,Vl=Object.getOwnPropertySymbols,Kg=Object.prototype.hasOwnProperty,Zg=Object.prototype.propertyIsEnumerable,Gl=(t,e,n)=>e in t?Wg(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n,Xl=(t,e)=>{for(var n in e||(e={}))Kg.call(e,n)&&Gl(t,n,e[n]);if(Vl)for(var n of Vl(e))Zg.call(e,n)&&Gl(t,n,e[n]);return t};const De=t=>t||t===0,ve=t=>typeof t=="function",ze=t=>typeof t=="string",he=t=>typeof t=="number",ln=t=>typeof t=="undefined",Qe=t=>typeof t!="undefined",Co=t=>typeof t=="boolean",Jg=t=>Math.ceil(t/10)*10,$i=t=>Math.ceil(t)+.5,Dr=t=>t[1]-t[0],nr=t=>typeof t=="object",qn=t=>ln(t)||t===null||ze(t)&&t.length===0||nr(t)&&!(t instanceof Date)&&Object.keys(t).length===0||he(t)&&isNaN(t),cn=t=>!qn(t),je=t=>Array.isArray(t),Be=t=>t&&!(t!=null&&t.nodeType)&&nr(t)&&!je(t);function $r(t,e,n){return Qe(t[e])?t[e]:n}function Qg(t,e){let n=!1;return Object.keys(t).forEach(a=>t[a]===e&&(n=!0)),n}function _e(t,e,...n){const a=ve(t);return a&&t.call(e,...n),a}function Si(t,e){let n=0;const a=function(...i){!--n&&e.apply(this,...i)};"duration"in t?t.each(()=>++n).on("end",a):(++n,t.call(a))}function Po(t){return ze(t)?t.replace(/<(script|img)?/ig,"<").replace(/(script)?>/ig,">"):t}function wa(t,e,n=[-1,1],a=!1){if(!(!t||!ze(e)))if(e.indexOf(` +`)===-1)t.text(e);else{const i=[t.text(),e].map(o=>o.replace(/[\s\n]/g,""));if(i[0]!==i[1]){const o=e.split(` +`),s=a?o.length-1:1;t.html(""),o.forEach((l,c)=>{t.append("tspan").attr("x",0).attr("dy",`${c===0?n[0]*s:n[1]}em`).text(l)})}}}function Hl(t){const{x:e,y:n,width:a,height:i}=t.getBBox();return[{x:e,y:n+i},{x:e,y:n},{x:e+a,y:n},{x:e+a,y:n+i}]}function Yl(t){const{width:e,height:n}=t.getBoundingClientRect(),a=Hl(t),i=a[0].x,o=Math.min(a[0].y,a[1].y);return{x:i,y:o,width:e,height:n}}function Hn(t,e){var n;const a=t&&((n=t.touches||t.sourceEvent&&t.sourceEvent.touches)==null?void 0:n[0]);let i=[0,0];try{i=Xn(a||t,e)}catch(o){}return i.map(o=>isNaN(o)?0:o)}function Wl(t){const{event:e,$el:n}=t,a=n.subchart.main||n.main;let i;return e&&e.type==="brush"?i=e.selection:a&&(i=a.select(".bb-brush").node())&&(i=zl(i)),i}function Ma(t){return!("rect"in t)||"rect"in t&&t.hasAttribute("width")&&t.rect.width!==+t.getAttribute("width")?t.rect=t.getBoundingClientRect():t.rect}function gr(t=!0,e=0,n=1e4){const a=Ke.crypto||Ke.msCrypto,i=a?e+a.getRandomValues(new Uint32Array(1))[0]%(n-e+1):Math.floor(Math.random()*(n-e)+e);return t?String(i):i}function wo(t,e,n,a,i){if(n>a)return-1;const o=Math.floor((n+a)/2);let{x:s,w:l=0}=t[o];return i&&(s=t[o].y,l=t[o].h),e>=s&&e<=s+l?o:e{if(Be(n)&&n.constructor){const a=new n.constructor;for(const i in n)a[i]=e(n[i]);return a}return n};return t.map(n=>e(n)).reduce((n,a)=>Xl(Xl({},n),a))}function yn(t={},e){je(e)&&e.forEach(n=>yn(t,n));for(const n in e)/^\d+$/.test(n)||n in t||(t[n]=e[n]);return t}const Cn=t=>t.charAt(0).toUpperCase()+t.slice(1);function qg(t,e="-"){return t.split(e).map((n,a)=>a?n.charAt(0).toUpperCase()+n.slice(1).toLowerCase():n.toLowerCase()).join("")}const Lr=t=>[].slice.call(t);function _g(t,e,n){const{rootSelector:a="",sheet:i}=t,s=`${a} ${(l=>l.replace(/\s?(bb-)/g,".$1").replace(/\.+/g,"."))(e)} {${n.join(";")}}`;return i[i.insertRule?"insertRule":"addRule"](s,i.cssRules.length)}function tv(t){let e=[];return t.forEach(n=>{var a;try{n.cssRules&&n.cssRules.length&&(e=e.concat(Lr(n.cssRules)))}catch(i){(a=Ke.console)==null||a.warn(`Error while reading rules from ${n.href}: ${i.toString()}`)}}),e}function Zl(t){var e,n,a,i,o,s;return{x:((n=(e=Ke.pageXOffset)!=null?e:Ke.scrollX)!=null?n:0)+((a=t.scrollLeft)!=null?a:0),y:((o=(i=Ke.pageYOffset)!=null?i:Ke.scrollY)!=null?o:0)+((s=t.scrollTop)!=null?s:0)}}function Ai(t,e=0,n=0,a=!0){const i=new DOMPoint(e,n),o=t.getScreenCTM(),s=i.matrixTransform(a?o==null?void 0:o.inverse():o);if(a===!1){const l=t.getBoundingClientRect();s.x-=l.x,s.y-=l.y}return s}function Jl(t){const e=t?t.transform:null,n=e&&e.baseVal;return n&&n.numberOfItems?n.getItem(0).matrix:{a:0,b:0,c:0,d:0,e:0,f:0}}function Mo(t){const e=t[0]instanceof Date,n=(e?t.map(Number):t).filter((a,i,o)=>o.indexOf(a)===i);return e?n.map(a=>new Date(a)):n}function Do(t){return t&&t.length?t.reduce((e,n)=>e.concat(n)):[]}function ea(t,...e){if(!e.length||e.length===1&&!e[0])return t;const n=e.shift();return Be(t)&&Be(n)&&Object.keys(n).forEach(a=>{if(!/^(__proto__|constructor|prototype)$/i.test(a)){const i=n[a];Be(i)?(!t[a]&&(t[a]={}),t[a]=ea(t[a],i)):t[a]=je(i)?i.concat():i}}),ea(t,...e)}function na(t,e=!0){let n;return t[0]instanceof Date?n=e?(a,i)=>a-i:(a,i)=>i-a:e&&!t.every(isNaN)?n=(a,i)=>a-i:e||(n=(a,i)=>a>i&&-1||acn(a));return n.length?he(n[0])?n=Math[t](...n):n[0]instanceof Date&&(n=na(n,t==="min")[0]):n=void 0,n}const Ei=(t,e,n=1)=>{const a=[],i=Math.max(0,Math.ceil((e-t)/n))|0;for(let o=t;o{const t=()=>({bubbles:!1,cancelable:!1,screenX:0,screenY:0,clientX:0,clientY:0});try{return new MouseEvent("t"),(e,n,a=t())=>{e.dispatchEvent(new MouseEvent(n,a))}}catch(e){return(n,a,i=t())=>{const o=gn.createEvent("MouseEvent");o.initMouseEvent(a,i.bubbles,i.cancelable,Ke,0,i.screenX,i.screenY,i.clientX,i.clientY,!1,!1,!1,!1,0,null),n.dispatchEvent(o)}}})(),touch:(t,e,n)=>{const a=new Touch(ea({identifier:Date.now(),target:t,radiusX:2.5,radiusY:2.5,rotationAngle:10,force:.5},n));t.dispatchEvent(new TouchEvent(e,{cancelable:!0,bubbles:!0,shiftKey:!0,touches:[a],targetTouches:[],changedTouches:[a]}))}};function bi(t,e){let n=t;for(const a in e)n=n.replace(new RegExp(`{=${a}}`,"g"),e[a]);return n}function Yn(t){var e;let n;if(t instanceof Date)n=t;else if(ze(t)){const{config:a,format:i}=this;n=(e=i.dataTime(a.data_xFormat)(t))!=null?e:new Date(t)}else he(t)&&!isNaN(t)&&(n=new Date(+t));return(!n||isNaN(+n))&&console&&console.error&&console.error(`Failed to parse x '${t}' to Date object`),n}function Lo(t){const e=t.attr("viewBox");return e?/(\d+(\.\d+)?){3}/.test(e):!1}function nv(t,e,n=!1){const a=!!t.node;let i=!1;for(const[o,s]of Object.entries(e))if(i=a?t.style(o)===s:t.style[o]===s,n===!1&&i)break;return i}function Da(){var t,e;return((t=gn)==null?void 0:t.hidden)===!1||((e=gn)==null?void 0:e.visibilityState)==="visible"}function rv(t,e){const{DocumentTouch:n,matchMedia:a,navigator:i}=Ke,o=a==null?void 0:a("(pointer:coarse)").matches;let s=!1;if(e)if(i&&"maxTouchPoints"in i)s=i.maxTouchPoints>0;else if("ontouchmove"in Ke||n&&gn instanceof n)s=!0;else if(o)s=!0;else{const c=i.userAgent;s=/\b(BlackBerry|webOS|iPhone|IEMobile)\b/i.test(c)||/\b(Android|Windows Phone|iPad|iPod)\b/i.test(c)}return t&&!o&&(a==null?void 0:a("(pointer:fine)").matches)&&"mouse"||s&&"touch"||"mouse"}function Ql(t,e){e()===!1?Yg(()=>Ql(t,e)):t()}var av=Object.defineProperty,kl=Object.getOwnPropertySymbols,iv=Object.prototype.hasOwnProperty,ov=Object.prototype.propertyIsEnumerable,No=(t,e,n)=>e in t?av(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n,ql=(t,e)=>{for(var n in e||(e={}))iv.call(e,n)&&No(t,n,e[n]);if(kl)for(var n of kl(e))ov.call(e,n)&&No(t,n,e[n]);return t},sv=(t,e,n)=>No(t,typeof e!="symbol"?e+"":e,n);const _l=class bf{static setOptions(e){this.data=e.reduce((n,a)=>ql(ql({},n),a),this.data)}constructor(){return kg(Vd,Ud,Hd,zd,Yd,jd,Gd,Xd,bf.data)}};sv(_l,"data",{});let Nr=_l;class lv{constructor(){return{chart:null,main:null,svg:null,axis:{x:null,y:null,y2:null,subX:null},axisTooltip:{x:null,y:null,y2:null},defs:null,tooltip:null,legend:null,title:null,subchart:{main:null,bar:null,line:null,area:null},arcs:null,bar:null,candlestick:null,line:null,area:null,circle:null,radar:null,text:null,grid:{main:null,x:null,y:null},gridLines:{main:null,x:null,y:null},region:{main:null,list:null},eventRect:null,zoomResetBtn:null}}}class cv{constructor(){return{width:0,width2:0,height:0,height2:0,margin:{top:0,bottom:0,left:0,right:0},margin2:{top:0,bottom:0,left:0,right:0},margin3:{top:0,bottom:0,left:0,right:0},arcWidth:0,arcHeight:0,xAxisHeight:0,hasAxis:!1,hasFunnel:!1,hasRadar:!1,hasTreemap:!1,cssRule:{},current:{domain:void 0,width:0,height:0,dataMax:0,maxTickSize:{x:{width:0,height:0,ticks:[],clipPath:0,domain:""},y:{width:0,height:0,domain:""},y2:{width:0,height:0,domain:""}},types:[],needle:void 0},isLegendRight:!1,isLegendInset:!1,isLegendTop:!1,isLegendLeft:!1,legendStep:0,legendItemWidth:0,legendItemHeight:0,legendHasRendered:!1,eventReceiver:{currentIdx:-1,rect:{},data:[],coords:[]},axis:{x:{padding:{left:0,right:0},tickCount:0}},rotatedPadding:{left:30,right:0,top:5},withoutFadeIn:{},inputType:"",datetimeId:"",clip:{id:"",idXAxis:"",idYAxis:"",idXAxisTickTexts:"",idGrid:"",idSubchart:"",path:"",pathXAxis:"",pathYAxis:"",pathXAxisTickTexts:"",pathGrid:""},event:null,dragStart:null,dragging:!1,flowing:!1,cancelClick:!1,mouseover:!1,rendered:!1,transiting:!1,redrawing:!1,resizing:!1,toggling:!1,zooming:!1,hasNegativeValue:!1,hasPositiveValue:!0,orgAreaOpacity:"0.2",orgConfig:{},hiddenTargetIds:[],hiddenLegendIds:[],focusedTargetIds:[],defocusedTargetIds:[],radius:0,innerRadius:0,outerRadius:void 0,innerRadiusRatio:0,gaugeArcWidth:0,radiusExpanded:0,xgridAttr:{x1:null,x2:null,y1:null,y2:null}}}}const tc={element:lv,state:cv};class uv{constructor(){Object.keys(tc).forEach(e=>{this[e]=new tc[e]})}getStore(e){return this[e]}}var fv=Object.defineProperty,dv=(t,e,n)=>e in t?fv(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n,hv=(t,e,n)=>dv(t,typeof e!="symbol"?e+"":e,n);const Ln={bubbleBaseLength:"$baseLength",colorPattern:"__colorPattern__",dataMinMax:"$dataMinMax",dataTotalSum:"$dataTotalSum",dataTotalPerIndex:"$totalPerIndex",legendItemTextBox:"legendItemTextBox",radarPoints:"$radarPoints",radarTextWidth:"$radarTextWidth",setOverOut:"setOverOut",callOverOutForTouch:"callOverOutForTouch",textRect:"textRect"};class gv{constructor(){hv(this,"cache",{})}add(e,n,a=!1){return this.cache[e]=a?this.cloneTarget(n):n,this.cache[e]}remove(e){(ze(e)?[e]:e).forEach(n=>delete this.cache[n])}get(e,n=!1){if(n&&Array.isArray(e)){const a=[];for(let i=0,o;o=e[i];i++)o in this.cache&&a.push(this.cloneTarget(this.cache[o]));return a}else{const a=this.cache[e];return De(a)?a:null}}reset(e){const n=this;for(const a in n.cache)(e||/^\$/.test(a))&&(n.cache[a]=null)}cloneTarget(e){return{id:e.id,id_org:e.id_org,values:e.values.map(n=>({x:n.x,value:n.value,id:n.id}))}}}const oe={AREA:"area",AREA_LINE_RANGE:"area-line-range",AREA_SPLINE:"area-spline",AREA_SPLINE_RANGE:"area-spline-range",AREA_STEP:"area-step",AREA_STEP_RANGE:"area-step-range",BAR:"bar",BUBBLE:"bubble",CANDLESTICK:"candlestick",DONUT:"donut",FUNNEL:"funnel",GAUGE:"gauge",LINE:"line",PIE:"pie",POLAR:"polar",RADAR:"radar",SCATTER:"scatter",SPLINE:"spline",STEP:"step",TREEMAP:"treemap"},Fo={AREA:"initArea",AREA_LINE_RANGE:"initArea",AREA_SPLINE:"initArea",AREA_SPLINE_RANGE:"initArea",AREA_STEP:"initArea",AREA_STEP_RANGE:"initArea",BAR:"initBar",BUBBLE:"initCircle",CANDLESTICK:"initCandlestick",DONUT:"initArc",FUNNEL:"initFunnel",GAUGE:"initArc",LINE:"initLine",PIE:"initArc",POLAR:"initPolar",RADAR:"initCircle",SCATTER:"initCircle",SPLINE:"initLine",STEP:"initLine",TREEMAP:"initTreemap"},Sr={Area:[oe.AREA,oe.AREA_SPLINE,oe.AREA_SPLINE_RANGE,oe.AREA_LINE_RANGE,oe.AREA_STEP,oe.AREA_STEP_RANGE],AreaRange:[oe.AREA_SPLINE_RANGE,oe.AREA_LINE_RANGE,oe.AREA_STEP_RANGE],Arc:[oe.PIE,oe.DONUT,oe.GAUGE,oe.POLAR,oe.RADAR],Line:[oe.LINE,oe.SPLINE,oe.AREA,oe.AREA_SPLINE,oe.AREA_SPLINE_RANGE,oe.AREA_LINE_RANGE,oe.STEP,oe.AREA_STEP,oe.AREA_STEP_RANGE],Step:[oe.STEP,oe.AREA_STEP,oe.AREA_STEP_RANGE],Spline:[oe.SPLINE,oe.AREA_SPLINE,oe.AREA_SPLINE_RANGE]};function vv(t){const e=t,{config:n}=e;let a="";if(qn(n.data_type||n.data_types)&&!e[Fo.LINE])a="line";else for(const i in Fo){const o=oe[i];if(e.hasType(o)&&!e[Fo[i]]){a=o;break}}a&&pv(`Please, make sure if %c${qg(a)}`,"module has been imported and specified correctly.","https://github.com/naver/billboard.js/wiki/CHANGELOG-v2#modularization-by-its-functionality")}function pv(t,e,n){var a;const i="[billboard.js]";if((a=Ke.console)==null?void 0:a.error){const s=e?["background:red;color:white;display:block;font-size:15px",e]:[];console.error(`\u274C ${i} ${t}`,"background:red;color:white;display:block;font-size:15px",...s),n&&console.info("%c\u2139\uFE0F","font-size:15px",n)}throw Error(`${i} ${t.replace(/\%c([a-z-]+)/i,"'$1' ")} ${e!=null?e:""}`)}const{setTimeout:mv,clearTimeout:yv}=Ke;function xv(t){const e=[];let n;const a=function(){a.clear(),t===!1?jl(()=>{e.forEach(i=>i())},{timeout:200}):n=mv(()=>{e.forEach(i=>i())},he(t)?t:200)};return a.clear=()=>{n&&(yv(n),n=null)},a.add=i=>e.push(i),a.remove=i=>e.splice(e.indexOf(i),1),a}function ec(){let t=[];const e=function(n,a){function i(){var o;let s=0;for(let l=0,c;c=t[l];l++){if(c===!0||(o=c.empty)!=null&&o.call(c)){s++;continue}if(Da()===!1){s=t.length;break}try{c.transition()}catch(f){s++}}return s===t.length}Ql(()=>{a==null||a()},i)};return e.add=function(n){je(n)?t=t.concat(n):t.push(n)},e}const Bo={};function Tv(t,e){var n;const a=t.toString(),i=a.replace(/(function|[\s\W\n])/g,"").substring(0,15);return i in Bo||(Bo[i]=new Ke.Blob([`${(n=e==null?void 0:e.map(String).join(";"))!=null?n:""} + + self.onmessage=function({data}) { + const result = (${a}).apply(null, data); + self.postMessage(result); + };`],{type:"text/javascript"})),Ke.URL.createObjectURL(Bo[i])}function $v(t){const e=new Ke.Worker(t);return e.onerror=function(n){console.error?console.error(n):console.log(n)},e}function Uo(t=!0,e,n,a){let i=function(...o){const s=e(...o);n(s)};if(Ke.Worker&&t){const o=Tv(e,a),s=$v(o);i=function(...l){s.postMessage(l),s.onmessage=function(c){return Ke.URL.revokeObjectURL(o),n(c.data)}}}return i}var nc={},zo={},jo=34,La=10,Vo=13;function rc(t){return new Function("d","return {"+t.map(function(e,n){return JSON.stringify(e)+": d["+n+'] || ""'}).join(",")+"}")}function Sv(t,e){var n=rc(t);return function(a,i){return e(n(a),i,t)}}function ac(t){var e=Object.create(null),n=[];return t.forEach(function(a){for(var i in a)i in e||n.push(e[i]=i)}),n}function Nn(t,e){var n=t+"",a=n.length;return a9999?"+"+Nn(t,6):Nn(t,4)}function Ev(t){var e=t.getUTCHours(),n=t.getUTCMinutes(),a=t.getUTCSeconds(),i=t.getUTCMilliseconds();return isNaN(t)?"Invalid Date":Av(t.getUTCFullYear(),4)+"-"+Nn(t.getUTCMonth()+1,2)+"-"+Nn(t.getUTCDate(),2)+(i?"T"+Nn(e,2)+":"+Nn(n,2)+":"+Nn(a,2)+"."+Nn(i,3)+"Z":a?"T"+Nn(e,2)+":"+Nn(n,2)+":"+Nn(a,2)+"Z":n||e?"T"+Nn(e,2)+":"+Nn(n,2)+"Z":"")}function ic(t){var e=new RegExp('["'+t+` +\r]`),n=t.charCodeAt(0);function a(v,m){var S,P,N=i(v,function(L,w){if(S)return S(L,w-1);P=L,S=m?Sv(L,m):rc(L)});return N.columns=P||[],N}function i(v,m){var S=[],P=v.length,N=0,L=0,w,X=P<=0,W=!1;v.charCodeAt(P-1)===La&&--P,v.charCodeAt(P-1)===Vo&&--P;function H(){if(X)return zo;if(W)return W=!1,nc;var K,at=N,ht;if(v.charCodeAt(at)===jo){for(;N++=P?X=!0:(ht=v.charCodeAt(N++))===La?W=!0:ht===Vo&&(W=!0,v.charCodeAt(N)===La&&++N),v.slice(at+1,K-1).replace(/""/g,'"')}for(;N0){if(typeof e[s-1]=="undefined"&&(e[s-1]={}),typeof o=="undefined")throw new Error(`Source data is missing a component at (${a}, ${s})!`);e[s-1][i]=o}})}),e}function Xo(t){const e=t[0],n=[];return t.forEach(function(a,i){if(i>0){const o={};a.forEach(function(s,l){if(typeof s=="undefined")throw new Error(`Source data is missing a component at (${i}, ${l})!`);o[e[l]]=s}),n.push(o)}}),n}function oc(t,e){const n=[];let a,i;if(Array.isArray(t)){const o=function(s,l){if(s[l]!==void 0)return s[l];const f=l.replace(/\[(\w+)\]/g,".$1").replace(/^\./,"").split(".");let g=s;return f.some(function(v){return!(g=g&&v in g?g[v]:void 0)}),g};e.x?a=e.value.concat(e.x):a=e.value,n.push(a),t.forEach(function(s){const l=a.map(function(c){let f=o(s,c);return typeof f=="undefined"&&(f=null),f});n.push(l)}),i=Xo(n)}else Object.keys(t).forEach(function(o){var s;const l=t[o].concat();(s=l.unshift)==null||s.call(l,o),n.push(l)}),i=Go(n);return i}function Cv(t,e="csv",n,a,i){const o=new XMLHttpRequest,s={csv:Pv,tsv:wv,json:oc};o.open("GET",t),n&&Object.keys(n).forEach(function(l){o.setRequestHeader(l,n[l])}),o.onreadystatechange=function(){if(o.readyState===4)if(o.status===200){const l=o.responseText;l&&i.call(this,s[e](e==="json"?JSON.parse(l):l,a))}else throw new Error(`${t}: Something went wrong loading!`)},o.send()}function sc(t,e){const n=t.rows(e);let a;return n.length===1?(a=[{}],n[0].forEach(i=>{a[0][i]=null})):a=t.parse(e),a}function Pv(t){return sc({rows:Rv,parse:bv},t)}function wv(t){return sc({rows:Ov,parse:Iv},t)}function lc(t,e){const n=t||(e==null?void 0:e.data_keys);return n!=null&&n.x&&(e.data_x=n.x),n}var Mv={convertData(t,e){const{config:n}=this,a=n.boost_useWorker;let i=t;if(t.bindto&&(i={},["url","mimeType","headers","keys","json","keys","rows","columns"].forEach(o=>{const s=`data_${o}`;s in t&&(i[o]=t[s])})),i.url&&e)Cv(i.url,i.mimeType,i.headers,lc(i.keys,n),e);else if(i.json)Uo(a,oc,e,[Go,Xo])(i.json,lc(i.keys,n));else if(i.rows)Uo(a,Xo,e)(i.rows);else if(i.columns)Uo(a,Go,e)(i.columns);else if(t.bindto)throw Error("url or json or rows or columns is required.")},convertDataToTargets(t,e){const n=this,{axis:a,config:i,state:o}=n,s=i.data_type;let l=!1,c=!1,f=!1;a&&(l=a.isCategorized(),c=a.isTimeSeries(),f=a.isCustomX());const g=Object.keys(t[0]||{}),v=g.length?g.filter(n.isNotX,n):[],m=g.length?g.filter(n.isX,n):[];let S;v.forEach(N=>{const L=this.getXKey(N);f||c?m.indexOf(L)>=0?S=(e&&n.data.xs[N]||[]).concat(t.map(w=>w[L]).filter(De).map((w,X)=>n.generateTargetX(w,N,X))):i.data_x?S=this.getOtherTargetXs():cn(i.data_xs)&&(S=n.getXValuesOfXKey(L,n.data.targets)):S=t.map((w,X)=>X),S&&(this.data.xs[N]=S)}),v.forEach(N=>{if(!this.data.xs[N])throw new Error(`x is not defined for id = "${N}".`)});const P=v.map((N,L)=>{const w=i.data_idConverter.bind(n.api)(N),X=n.getXKey(N),W=f&&l,H=W&&t.map(at=>at.x).every(at=>i.axis_x_categories.indexOf(at)>-1),k=t.__append__,K=X===null&&k?n.api.data.values(N).length:0;return{id:w,id_org:N,values:t.map((at,ht)=>{const $t=at[X];let dt=at[N],st;return dt=dt!==null&&!isNaN(dt)&&!Be(dt)?+dt:je(dt)||Be(dt)?dt:null,(W||o.hasRadar)&&L===0&&!ln($t)?(!H&&L===0&&ht===0&&!k&&(i.axis_x_categories=[]),st=i.axis_x_categories.indexOf($t),st===-1&&(st=i.axis_x_categories.length,i.axis_x_categories.push($t))):st=n.generateTargetX($t,N,K+ht),(ln(dt)||n.data.xs[N].length<=ht)&&(st=void 0),{x:st,value:dt,id:w,index:-1}}).filter(at=>Qe(at.x))}});if(P.forEach(N=>{var L;i.data_xSort&&(N.values=N.values.sort((w,X)=>{const W=w.x||w.x===0?w.x:1/0,H=X.x||X.x===0?X.x:1/0;return W-H})),N.values.forEach((w,X)=>w.index=X),(L=n.data.xs[N.id])==null||L.sort((w,X)=>w-X)}),o.hasNegativeValue=n.hasNegativeValueInTargets(P),o.hasPositiveValue=n.hasPositiveValueInTargets(P),s&&n.isValidChartType(s)){const N=n.mapToIds(P).filter(L=>!(L in i.data_types)||!n.isValidChartType(i.data_types[L]));n.setTargetType(N,s)}return P.forEach(N=>n.cache.add(N.id_org,N,!0)),P}},Dv={isX(t){const e=this,{config:n}=e,a=n.data_x&&t===n.data_x,i=cn(n.data_xs)&&Qg(n.data_xs,t);return a||i},isNotX(t){return!this.isX(t)},isStackNormalized(){const{config:t}=this;return!!(t.data_stack_normalize&&t.data_groups.length)},isGrouped(t){const e=this.config.data_groups;return t?e.some(n=>n.indexOf(t)>=0&&n.length>1):e.length>0},getXKey(t){const e=this,{config:n}=e;return n.data_x?n.data_x:cn(n.data_xs)?n.data_xs[t]:null},getXValuesOfXKey(t,e){const n=this,a=e&&cn(e)?n.mapToIds(e):[];let i;return a.forEach(o=>{n.getXKey(o)===t&&(i=n.data.xs[o])}),i},getIndexByX(t,e){const n=this;return e?e.indexOf(ze(t)?t:+t):(n.filterByX(n.data.targets,t)[0]||{index:null}).index},getXValue(t,e){const n=this;return t in n.data.xs&&n.data.xs[t]&&De(n.data.xs[t][e])?n.data.xs[t][e]:e},getOtherTargetXs(){const t=this,e=Object.keys(t.data.xs);return e.length?t.data.xs[e[0]]:null},getOtherTargetX(t){const e=this.getOtherTargetXs();return e&&t{n.data_xs[a]=t[a]})},isMultipleX(){return!this.config.axis_x_forceAsSingle&&(cn(this.config.data_xs)||this.hasType("bubble")||this.hasType("scatter"))},addName(t){const e=this,{config:n}=e;let a;return t&&(a=n.data_names[t.id],t.name=a!==void 0?a:t.id),t},getAllValuesOnIndex(t,e=!1){const n=this;let a=n.filterTargetsToShow(n.data.targets).map(i=>n.addName(n.getValueOnIndex(i.values,t)));return e&&(a=a.filter(i=>i&&"value"in i&&De(i.value))),a},getValueOnIndex(t,e){const n=t.filter(a=>a.index===e);return n.length?n[0]:null},updateTargetX(t,e){const n=this;t.forEach(a=>{a.values.forEach((i,o)=>{i.x=n.generateTargetX(e[o],a.id,o)}),n.data.xs[a.id]=e})},updateTargetXs(t,e){const n=this;t.forEach(a=>{e[a.id]&&n.updateTargetX([a],e[a.id])})},generateTargetX(t,e,n){const a=this,{axis:i}=a;let o=i!=null&&i.isCategorized()?n:t||n;if(i!=null&&i.isTimeSeries()){const s=Yn.bind(a);o=s(t||a.getXValue(e,n))}else i!=null&&i.isCustomX()&&!(i!=null&&i.isCategorized())&&(o=De(t)?+t:a.getXValue(e,n));return o},updateXs(t){t.length&&(this.axis.xs=t.map(e=>e.x))},getPrevX(t){const e=this.axis.xs[t-1];return Qe(e)?e:null},getNextX(t){const e=this.axis.xs[t+1];return Qe(e)?e:null},getBaseValue(t){const e=this,{hasAxis:n}=e.state;let{value:a}=t;return a&&n&&(e.isAreaRangeType(t)?a=e.getRangedData(t,"mid"):e.isBubbleZType(t)&&(a=e.getBubbleZData(a,"y"))),a},getMinMaxValue(t){const e=this.getBaseValue.bind(this);let n,a;return(t||this.data.targets.map(i=>i.values)).forEach((i,o)=>{const s=i.map(e).filter(he);n=Math.min(o?n:1/0,...s),a=Math.max(o?a:-1/0,...s)}),{min:n,max:a}},getMinMaxData(){const t=this,e=Ln.dataMinMax;let n=t.cache.get(e);if(!n){const a=t.data.targets.map(l=>l.values),i=t.getMinMaxValue(a);let o=[],s=[];a.forEach(l=>{const c=t.getFilteredDataByValue(l,i.min),f=t.getFilteredDataByValue(l,i.max);c.length&&(o=o.concat(c)),f.length&&(s=s.concat(f))}),t.cache.add(e,n={min:o,max:s})}return n},getTotalPerIndex(){const t=this,e=Ln.dataTotalPerIndex;let n=t.cache.get(e);return(t.config.data_groups.length||t.isStackNormalized())&&!n&&(n=[],t.data.targets.forEach(a=>{a.values.forEach((i,o)=>{n[o]||(n[o]=0),n[o]+=he(i.value)?i.value:0})})),n},getTotalDataSum(t){const e=this,n=Ln.dataTotalSum;let a=e.cache.get(n);if(!he(a)){const i=Do(e.data.targets.map(o=>o.values)).map(o=>o.value);a=i.length?i.reduce((o,s)=>o+s):0,e.cache.add(n,a)}return t&&(a-=e.getHiddenTotalDataSum()),a},getHiddenTotalDataSum(){const t=this,{api:e,state:{hiddenTargetIds:n}}=t;let a=0;return n.length&&(a=e.data.values.bind(e)(n).reduce((i,o)=>i+o)),a},getFilteredDataByValue(t,e){return t.filter(n=>this.getBaseValue(n)===e)},getMaxDataCount(){return Math.max(...this.data.targets.map(t=>t.values.length),0)},getMaxDataCountTarget(){let t=this.filterTargetsToShow()||[];const e=t.length,n=this.config.axis_x_inverted;return e>1?(t=t.map(a=>a.values).reduce((a,i)=>a.concat(i)).map(a=>a.x),t=na(Mo(t)).map((a,i,o)=>({x:a,index:n?o.length-i-1:i}))):e&&(t=t[0].values.concat()),t},mapToIds(t){return t.map(e=>e.id)},mapToTargetIds(t){const e=this;return t?je(t)?t.concat():[t]:e.mapToIds(e.data.targets)},hasTarget(t,e){const n=this.mapToIds(t);for(let a=0,i;i=n[a];a++)if(i===e)return!0;return!1},isTargetToShow(t){return this.state.hiddenTargetIds.indexOf(t)<0},isLegendToShow(t){return this.state.hiddenLegendIds.indexOf(t)<0},filterTargetsToShow(t){const e=this;return(t||e.data.targets).filter(n=>e.isTargetToShow(n.id))},mapTargetsToUniqueXs(t){const e=this,{axis:n}=e;let a=[];return t!=null&&t.length&&(a=Mo(Do(t.map(i=>i.values.map(o=>+o.x)))),a=n!=null&&n.isTimeSeries()?a.map(i=>new Date(+i)):a.map(Number)),na(a)},addTargetIds(t,e){const{state:n}=this;(je(e)?e:[e]).forEach(i=>{n[t].indexOf(i)<0&&n[t].push(i)})},removeTargetIds(t,e){const{state:n}=this;(je(e)?e:[e]).forEach(i=>{const o=n[t].indexOf(i);o>=0&&n[t].splice(o,1)})},addHiddenTargetIds(t){this.addTargetIds("hiddenTargetIds",t)},removeHiddenTargetIds(t){this.removeTargetIds("hiddenTargetIds",t)},addHiddenLegendIds(t){this.addTargetIds("hiddenLegendIds",t)},removeHiddenLegendIds(t){this.removeTargetIds("hiddenLegendIds",t)},getValuesAsIdKeyed(t){const e=this,{hasAxis:n}=e.state,a={},i=e.isMultipleX(),o=i?e.mapTargetsToUniqueXs(t).map(s=>ze(s)?s:+s):null;return t.forEach(s=>{const l=[];s.values.filter(({value:c})=>De(c)||c===null).forEach(c=>{let{value:f}=c;f!==null&&e.isCandlestickType(c)&&(f=je(f)?f.slice(0,4):[f.open,f.high,f.low,f.close]),je(f)?l.push(...f):Be(f)&&"high"in f?l.push(...Object.values(f)):e.isBubbleZType(c)?l.push(n&&e.getBubbleZData(f,"y")):i?l[e.getIndexByX(c.x,o)]=f:l.push(f)}),a[s.id]=l}),a},checkValueInTargets(t,e){const n=Object.keys(t);let a;for(let i=0;i1},hasNegativeValueInTargets(t){return this.checkValueInTargets(t,e=>e<0)},hasPositiveValueInTargets(t){return this.checkValueInTargets(t,e=>e>0)},orderTargets(t){const e=this,n=[...t],a=e.getSortCompareFn();return a&&n.sort(a),n},getSortCompareFn(t=!1){const e=this,{config:n}=e,a=n.data_order,i=/asc/i.test(a),o=/desc/i.test(a);let s;if(i||o){const l=(f,g)=>f+Math.abs(g.value),c=f=>he(f)?f:"values"in f?f.values.reduce(l,0):f.value;s=(f,g)=>{const v=c(f),m=c(g);return t?i?v-m:m-v:i?m-v:v-m}}else ve(a)&&(s=a.bind(e.api));return s||null},filterByX(t,e){return Do(t.map(n=>n.values)).filter(n=>n.x-e===0)},filterRemoveNull(t){return t.filter(e=>De(this.getBaseValue(e)))},filterByXDomain(t,e){return t.map(n=>({id:n.id,id_org:n.id_org,values:n.values.filter(a=>e[0]<=a.x&&a.x<=e[1])}))},hasDataLabel(){const t=this.config.data_labels;return Co(t)&&t||nr(t)&&cn(t)},hasNullDataValue(t){return t.some(({value:e})=>e===null)},getDataIndexFromEvent(t){const e=this,{$el:n,config:a,state:{hasRadar:i,inputType:o,eventReceiver:{coords:s,rect:l}}}=e;let c;if(i){let f=t.target;/tspan/i.test(f.tagName)&&(f=f.parentNode);const g=ot(f).datum();c=g&&Object.keys(g).length===1?g.index:void 0}else{const f=a.axis_rotated,g=Zl(n.chart.node()),v=o==="touch"&&t.changedTouches?t.changedTouches[0]:t;let m=f?v.clientY+g.y:v.clientX+g.x;if(Lo(n.svg)){const S=[m,0];f&&S.reverse(),m=Ai(n.eventRect.node(),...S)[f?"y":"x"]}else m-=f?l.top:l.left;c=wo(s,m,0,s.length-1,f)}return c},getDataLabelLength(t,e,n){const a=this,i=[0,0],o=1.3;return a.$el.chart.select("svg").selectAll(".dummy").data([t,e]).enter().append("text").text(s=>a.dataLabelFormat(s.id)(s)).each(function(s,l){i[l]=this.getBoundingClientRect()[n]*o}).remove(),i},isNoneArc(t){return this.hasTarget(this.data.targets,t.id)},isArc(t){return"data"in t&&this.hasTarget(this.data.targets,t.data.id)},findSameXOfValues(t,e){const n=t[e].x,a=[];let i;for(i=e-1;i>=0&&n===t[i].x;i--)a.push(t[i]);for(i=e;in.findClosest(i.values,e));return n.findClosest(a,e)},findClosest(t,e){const n=this,{$el:{main:a}}=n,i=t.filter(l=>l&&De(l.value));let o,s;return i.filter(l=>n.isBarType(l.id)||n.isCandlestickType(l.id)).forEach(l=>{const c=n.isBarType(l.id)?`.${Kn.chartBar}.${Se.target}${n.getTargetSelectorSuffix(l.id)} .${Kn.bar}-${l.index}`:`.${cr.chartCandlestick}.${Se.target}${n.getTargetSelectorSuffix(l.id)} .${cr.candlestick}-${l.index} path`;!s&&n.isWithinBar(a.select(c).node())&&(s=l)}),i.filter(l=>!n.isBarType(l.id)&&!n.isCandlestickType(l.id)).forEach(l=>{const c=n.dist(l,e);o=n.getPointSensitivity(l),c{const{x:i,id:o}=a;n.push({x:i,id:o,value:a.value[0]}),n.push({x:i,id:o,value:a.value[2]})}),n},updateDataAttributes(t,e){const n=this,{config:a}=n,i=a[`data_${t}`];return ln(e)||(Object.keys(e).forEach(o=>{i[o]=e[o]}),n.redraw({withLegend:!0})),i},getRangedData(t,e="",n="areaRange"){const a=t==null?void 0:t.value;if(je(a)){if(n==="bar")return a.reduce((i,o)=>o-i);{const i={areaRange:["high","mid","low"],candlestick:["open","high","low","close","volume"]}[n].indexOf(e);return i>=0&&a?a[i]:void 0}}else if(a&&e)return a[e];return a},setRatioForGroupedData(t){const e=this,{config:n}=e;if(n.data_groups.length&&t.some(a=>e.isGrouped(a.id))){const a=i=>e.getRatio("index",i,!0);t.forEach(i=>{"values"in i?i.values.forEach(a):a(i)})}},getRatio(t,e,n=!1){const a=this,{config:i,state:o}=a,s=a.api;let l=0;if(e&&s.data.shown().length)if(l=e.ratio||e.value,t==="arc")if(a.pie.padAngle()())l=e.value/a.getTotalDataSum(!0);else{const c=i.gauge_fullCircle?a.getArcLength():a.getStartingAngle()*-2,f=a.hasType("gauge")?c:Math.PI*2;l=(e.endAngle-e.startAngle)/f}else if(t==="index"){const c=s.data.values.bind(s);let f=this.getTotalPerIndex();if(o.hiddenTargetIds.length){let v=c(o.hiddenTargetIds,!1);v.length&&(v=v.reduce((m,S)=>m.map((P,N)=>(he(P)?P:0)+S[N])),f=f.map((m,S)=>m-v[S]))}const g=f[e.index];e.ratio=he(e.value)&&f&&g?e.value/g:0,l=e.ratio}else if(t==="radar")l=parseFloat(String(Math.max(e.value,0)))/o.current.dataMax*i.radar_size_ratio;else if(t==="bar"){const f=a.getYScaleById.bind(a)(e.id).domain().reduce((g,v)=>v-g);l=f===0?0:Math.abs(a.getRangedData(e,null,t)/f)}else t==="treemap"&&(l/=a.getTotalDataSum(!0));return n&&l?l*100:l},updateDataIndexByX(t){const e=this,n=t.reduce((a,i,o)=>(a[Number(i.x)]=o,a),{});e.data.targets.forEach(a=>{a.values.forEach((i,o)=>{let s=n[Number(i.x)];s===void 0&&(s=o),i.index=s})})},isBubbleZType(t){return this.isBubbleType(t)&&(Be(t.value)&&("z"in t.value||"y"in t.value)||je(t.value)&&t.value.length>=2)},isBarRangeType(t){const e=this,{value:n}=t;return e.isBarType(t)&&je(n)&&n.length>=2&&n.every(a=>he(a))},getDataById(t){var e;const n=this.cache.get(t)||this.api.data(t);return(e=n==null?void 0:n[0])!=null?e:n}};function cc(t,e=!1){const n=this,{api:a}=n;e&&n.api.flush(!0),t==null||t.call(a)}var Lv={load(t,e){const n=this,{axis:a,data:i,org:o,scale:s}=n,{append:l}=e,c={domain:null,currentDomain:null,x:null};let f=t;f&&(e.filter&&(f=f.filter(e.filter)),(e.type||e.types)&&f.forEach(g=>{var v;const m=((v=e.types)==null?void 0:v[g.id])||e.type;n.setTargetType(g.id,m)}),i.targets.forEach(g=>{for(let v=0;v{const a=t.data||n;t.append&&(a.__append__=!0),a&&e.load(e.convertDataToTargets(a),t)}))},unload(t,e){var n;const a=this,{state:i,$el:o,$T:s}=a,l=!!((n=a.hasLegendDefsPoint)!=null&&n.call(a));let c=e,f=t;if(a.cache.reset(),c||(c=()=>{}),f=f.filter(v=>a.hasTarget(a.data.targets,v)),!f||f.length===0){c();return}const g=o.svg.selectAll(f.map(v=>a.selectorTarget(v)));s(g).style("opacity","0").remove().call(Si,c),f.forEach(v=>{var m;const S=a.getTargetSelectorSuffix(v);i.withoutFadeIn[v]=!1,o.legend&&o.legend.selectAll(`.${We.legendItem}${S}`).remove(),a.data.targets=a.data.targets.filter(P=>P.id!==v),l&&((m=o.defs)==null||m.select(`#${a.getDefsPointId(S)}`).remove())}),i.hasFunnel&&a.updateFunnel(a.data.targets),i.hasTreemap&&a.updateTargetsForTreemap(a.data.targets),a.updateTypesElements()}},Ri=t=>()=>t;function Ho(t,{sourceEvent:e,subject:n,target:a,identifier:i,active:o,x:s,y:l,dx:c,dy:f,dispatch:g}){Object.defineProperties(this,{type:{value:t,enumerable:!0,configurable:!0},sourceEvent:{value:e,enumerable:!0,configurable:!0},subject:{value:n,enumerable:!0,configurable:!0},target:{value:a,enumerable:!0,configurable:!0},identifier:{value:i,enumerable:!0,configurable:!0},active:{value:o,enumerable:!0,configurable:!0},x:{value:s,enumerable:!0,configurable:!0},y:{value:l,enumerable:!0,configurable:!0},dx:{value:c,enumerable:!0,configurable:!0},dy:{value:f,enumerable:!0,configurable:!0},_:{value:g}})}Ho.prototype.on=function(){var t=this._.on.apply(this._,arguments);return t===this._?this:t};function Nv(t){return!t.ctrlKey&&!t.button}function Fv(){return this.parentNode}function Bv(t,e){return e==null?{x:t.x,y:t.y}:e}function Uv(){return navigator.maxTouchPoints||"ontouchstart"in this}function uc(){var t=Nv,e=Fv,n=Bv,a=Uv,i={},o=ri("start","drag","end"),s=0,l,c,f,g,v=0;function m(H){H.on("mousedown.drag",S).filter(a).on("touchstart.drag",L).on("touchmove.drag",w,Jd).on("touchend.drag touchcancel.drag",X).style("touch-action","none").style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}function S(H,k){if(!(g||!t.call(this,H,k))){var K=W(this,e.call(this,H,k),H,k,"mouse");K&&(ot(H.view).on("mousemove.drag",P,Sa).on("mouseup.drag",N,Sa),co(H.view),lo(H),f=!1,l=H.clientX,c=H.clientY,K("start",H))}}function P(H){if(Zr(H),!f){var k=H.clientX-l,K=H.clientY-c;f=k*k+K*K>v}i.mouse("drag",H)}function N(H){ot(H.view).on("mousemove.drag mouseup.drag",null),uo(H.view,f),Zr(H),i.mouse("end",H)}function L(H,k){if(t.call(this,H,k)){var K=H.changedTouches,at=e.call(this,H,k),ht=K.length,$t,dt;for($t=0;$ti.$el[o]).forEach(o=>{a&&i.$el[o].classed(Se.EXPANDED,!1),i.getShapeByIndex(o,e,n).classed(Se.EXPANDED,t)})},setOverOut(t,e){const n=this,{config:a,state:{hasFunnel:i,hasRadar:o,hasTreemap:s},$el:{main:l}}=n,c=Be(e);if(c||e!==-1){const f=a[t?"data_onover":"data_onout"].bind(n.api);if(a.color_onover&&n.setOverColor(t,e,c),c){const g=n.getTargetSelectorSuffix(e.id),v=i||s?`${Se.target+g} .${sn.shape}`:Ve.arc+g;f(e,l.select(`.${v}`).node())}else if(a.tooltip_grouped)t&&(o&&n.isPointFocusOnly()?n.showCircleFocus(n.getAllValuesOnIndex(e,!0)):n.setExpand(e,null,!0)),!n.isMultipleX()&&l.selectAll(`.${sn.shape}-${e}`).each(function(g){f(g,this)});else{const g=n.cache.get(Ln.setOverOut)||[],v=l.selectAll(`.${sn.shape}-${e}`).filter(function(S){return n.isWithinShape(this,S)}),m=v.filter(function(){return g.every(S=>S!==this)});if(!t||v.empty()||g.length===m.size()&&m.nodes().every((S,P)=>S!==g[P]))for(;g.length;){const S=g.pop();a.data_onout.bind(n.api)(ot(S).datum(),S)}m.each(function(){t&&(f(ot(this).datum(),this),g.push(this))}),n.cache.add(Ln.setOverOut,g)}}},callOverOutForTouch(t){const e=this,n=e.cache.get(Ln.callOverOutForTouch);(Be(t)&&n?t.id!==n.id:t!==n)&&((n||he(n))&&e.setOverOut(!1,n),(t||he(t))&&e.setOverOut(!0,t),e.cache.add(Ln.callOverOutForTouch,t))},getDraggableSelection(){const t=this,{config:e,state:n}=t;return e.interaction_enabled&&e.data_selection_draggable&&t.drag?uc().on("drag",function(a){n.event=a,t.drag(Hn(a,this))}).on("start",function(a){n.event=a,t.dragstart(Hn(a,this))}).on("end",a=>{n.event=a,t.dragend()}):()=>{}},dispatchEvent(t,e,n){var a,i,o;const s=this,{config:l,state:{eventReceiver:c,hasAxis:f,hasFunnel:g,hasRadar:v,hasTreemap:m},$el:{eventRect:S,funnel:P,radar:N,svg:L,treemap:w}}=s;let X=(o=(i=(g||m)&&c.rect||v&&N.axes.select(`.${Tn.axis}-${e} text`)||S||((a=s.getArcElementByIdOrIndex)==null?void 0:a.call(s,e)))==null?void 0:i.node)==null?void 0:o.call(i);if(X){const W=s.isMultipleX(),H=l.axis_rotated;let{width:k,left:K,top:at}=X.getBoundingClientRect();if(f&&!v&&!W){const st=c.coords[e];st?(k=st.w,K+=st.x,at+=st.y):(k=0,K=0,at=0)}let ht=K+(n?n[0]:0)+(W||H?0:k/2),$t=at+(n?n[1]:0)+(H?4:0);if(Lo(L)){const st=Ai(s.$el.eventRect.node(),ht,$t,!1);ht=st.x,$t=st.y}const dt={screenX:ht,screenY:$t,clientX:ht,clientY:$t,bubbles:v};(g||m)&&(X=(P!=null?P:w).node()),ev[/^(mouse|click)/.test(t)?"mouse":"touch"](X,t,dt)}},setDragStatus(t){this.state.dragging=t},unbindZoomEvent(){const t=this,{$el:{eventRect:e,zoomResetBtn:n}}=t;e==null||e.on(".zoom wheel.zoom .drag",null),n==null||n.on("click",null).style("display","none")},unbindAllEvents(){var t;const e=this,{$el:{arcs:n,eventRect:a,legend:i,region:o,svg:s,treemap:l},brush:c}=e,f=["wheel","click","mouseover","mousemove","mouseout","touchstart","touchmove","touchend","touchstart.eventRect","touchmove.eventRect","touchend.eventRect",".brush",".drag",".zoom","wheel.zoom","dblclick.zoom"].join(" ");[s,a,o==null?void 0:o.list,c==null?void 0:c.getSelection(),n==null?void 0:n.selectAll("path"),i==null?void 0:i.selectAll("g"),l].forEach(g=>g==null?void 0:g.on(f,null)),(t=e.unbindZoomEvent)==null||t.call(e)}},jv={categoryName(t){var e;const{axis_x_categories:n}=this.config;return(e=n==null?void 0:n[t])!=null?e:t}},Vv={generateClass(t,e){return` ${t} ${t+this.getTargetSelectorSuffix(e)}`},getClass(t,e){const n=/s$/.test(t),a=/^(area|arc|line|funnel|treemap)s?$/.test(t),i=n?"id":"index";return o=>{const s=o.data||o;return((e?this.generateClass(Ue[n?"shapes":"shape"],s[i]):"")+this.generateClass(Ue[t],s[a?"id":i])).trim()}},getChartClass(t){return e=>Ue[`chart${t}`]+this.classTarget((e.data?e.data:e).id)},generateExtraLineClass(){const e=this.config.line_classes||[],n=[];return function(a){var i;const o=a.id||((i=a.data)==null?void 0:i.id)||a;return n.indexOf(o)<0&&n.push(o),e[n.indexOf(o)%e.length]}},classRegion(t,e){return`${this.generateClass(Ue.region,e)} ${"class"in t?t.class:""}`},classTarget(t){const e=this.config.data_classes[t];let n="";return e&&(n=` ${Ue.target}-${e}`),this.generateClass(Ue.target,t)+n},classFocus(t){return this.classFocused(t)+this.classDefocused(t)},classFocused(t){return` ${this.state.focusedTargetIds.indexOf(t.id)>=0?Ue.focused:""}`},classDefocused(t){return` ${this.state.defocusedTargetIds.indexOf(t.id)>=0?Ue.defocused:""}`},getTargetSelectorSuffix(t){return(t||t===0?`-${t}`:"").replace(/[\x00-\x20\x7F-\xA0\s?!@#$%^&*()_=+,.<>'":;\[\]\/|~`{}\\]/g,"-")},selectorTarget(t,e="",n=""){const a=this.getTargetSelectorSuffix(t);return`${e}.${Ue.target+a} ${n}, ${e}.${Ue.circles+a} ${n}`},selectorTargets(t,e){const n=t||[];return n.length?n.map(a=>this.selectorTarget(a,e)):null},selectorLegend(t){return`.${Ue.legendItem+this.getTargetSelectorSuffix(t)}`},selectorLegends(t){return t!=null&&t.length?t.map(e=>this.selectorLegend(e)):null}};class fc extends Map{constructor(e,n=gc){if(super(),Object.defineProperties(this,{_intern:{value:new Map},_key:{value:n}}),e!=null)for(const[a,i]of e)this.set(a,i)}get(e){return super.get(Yo(this,e))}has(e){return super.has(Yo(this,e))}set(e,n){return super.set(dc(this,e),n)}delete(e){return super.delete(hc(this,e))}}class f1 extends Set{constructor(e,n=gc){if(super(),Object.defineProperties(this,{_intern:{value:new Map},_key:{value:n}}),e!=null)for(const a of e)this.add(a)}has(e){return super.has(Yo(this,e))}add(e){return super.add(dc(this,e))}delete(e){return super.delete(hc(this,e))}}function Yo({_intern:t,_key:e},n){const a=e(n);return t.has(a)?t.get(a):n}function dc({_intern:t,_key:e},n){const a=e(n);return t.has(a)?t.get(a):(t.set(a,n),n)}function hc({_intern:t,_key:e},n){const a=e(n);return t.has(a)&&(n=t.get(a),t.delete(a)),n}function gc(t){return t!==null&&typeof t=="object"?t.valueOf():t}function ra(t,e){switch(arguments.length){case 0:break;case 1:this.range(t);break;default:this.range(e).domain(t);break}return this}function d1(t,e){switch(arguments.length){case 0:break;case 1:{typeof t=="function"?this.interpolator(t):this.range(t);break}default:{this.domain(t),typeof e=="function"?this.interpolator(e):this.range(e);break}}return this}const vc=Symbol("implicit");function pc(){var t=new fc,e=[],n=[],a=vc;function i(o){let s=t.get(o);if(s===void 0){if(a!==vc)return a;t.set(o,s=e.push(o)-1)}return n[s%n.length]}return i.domain=function(o){if(!arguments.length)return e.slice();e=[],t=new fc;for(const s of o)t.has(s)||t.set(s,e.push(s)-1);return i},i.range=function(o){return arguments.length?(n=Array.from(o),i):n.slice()},i.unknown=function(o){return arguments.length?(a=o,i):a},i.copy=function(){return pc(e,n).unknown(a)},ra.apply(i,arguments),i}const Gv=(t,e,n)=>{const a=ot(t.cloneNode(!0));return a.attr("id",n).insert("rect",":first-child").attr("width",a.attr("width")).attr("height",a.attr("height")).style("fill",e),{id:n,node:a.node()}};function Xv(t){const e=Ln.colorPattern,{body:n}=gn;let a=n[e];if(!a){const i=";",o=t.classed(oo.colorPattern,!0).style("background-image");t.classed(oo.colorPattern,!1),o.indexOf(i)>-1&&(a=o.replace(/url[^#]*|["'()]|(\s|%20)/g,"").split(i).map(s=>s.trim().replace(/[\"'\s]/g,"")).filter(Boolean),n[e]=a)}return a}const Hv=["#1f77b4","#ff7f0e","#2ca02c","#d62728","#9467bd","#8c564b","#e377c2","#7f7f7f","#bcbd22","#17becf"];var Yv={generateColor(){const t=this,{$el:e,config:n}=t,a=n.data_colors,i=n.data_color,o=[];let s=cn(n.color_pattern)?n.color_pattern:pc(Xv(e.chart)||Hv).range();const l=s;if(ve(n.color_tiles)){const c=n.color_tiles.bind(t.api)(),f=s.map((g,v)=>{const m=g.replace(/[#\(\)\s,]/g,""),S=`${t.state.datetimeId}-pattern-${m}-${v}`;return Gv(c[v%c.length],g,S)});s=f.map(g=>`url(#${g.id})`),t.patterns=f}return function(c){var f;const g=c.id||((f=c.data)==null?void 0:f.id)||c,v=t.isTypeOf(g,["line","spline","step"])||!n.data_types[g];let m;return ve(a[g])?m=a[g].bind(t.api)(c):a[g]?m=a[g]:(o.indexOf(g)<0&&o.push(g),m=v?l[o.indexOf(g)%l.length]:s[o.indexOf(g)%s.length],a[g]=m),ve(i)?i.bind(t.api)(m,c):m}},generateLevelColor(){const t=this,{config:e}=t,n=e.color_pattern,a=e.color_threshold,i=a.unit==="value",o=a.max||100,s=a.values&&a.values.length?a.values:[];return cn(a)?function(l){const c=i?l:l*100/o;let f=n[n.length-1];for(let g=0,v=s.length;g{const l=`${i.datetimeId}-labels-bg${n.getTargetSelectorSuffix(s)}${ze(t)?n.getTargetSelectorSuffix(t):""}`;a.defs.append("filter").attr("x",e.x).attr("y",e.y).attr("width",e.width).attr("height",e.height).attr("id",l).html(` + `)})}},getGradienColortUrl(t){return`url(#${this.state.datetimeId}-gradient${this.getTargetSelectorSuffix(t)})`},updateLinearGradient(){const t=this,{config:e,data:{targets:n},state:{datetimeId:a},$el:{defs:i}}=t;n.forEach(o=>{const s=`${a}-gradient${t.getTargetSelectorSuffix(o.id)}`,l=t.hasPointType()&&e.point_radialGradient,c=t.isAreaType(o)&&"area"||t.isBarType(o)&&"bar";if((l||c)&&i.select(`#${s}`).empty()){const f=t.color(o),g={defs:null,stops:[]};if(l){const{cx:v=.3,cy:m=.3,r:S=.7,stops:P=[[.1,f,0],[.9,f,1]]}=l;g.stops=P,g.defs=i.append("radialGradient").attr("id",`${s}`).attr("cx",v).attr("cy",m).attr("r",S)}else{const v=e.axis_rotated,{x:m=v?[1,0]:[0,0],y:S=v?[0,0]:[0,1],stops:P=[[0,f,1],[1,f,0]]}=e[`${c}_linearGradient`];g.stops=P,g.defs=i.append("linearGradient").attr("id",`${s}`).attr("x1",m[0]).attr("x2",m[1]).attr("y1",S[0]).attr("y2",S[1])}g.stops.forEach(v=>{const[m,S,P]=v,N=ve(S)?S.bind(t.api)(o.id):S;g.defs&&g.defs.append("stop").attr("offset",m).attr("stop-color",N||f).attr("stop-opacity",P)})}})},setOverColor(t,e){const n=this,{config:a,$el:{main:i}}=n,o=a.color_onover;let s=t?o:n.color;Be(s)?s=({id:l})=>l in o?o[l]:n.color(l):ze(s)?s=()=>o:ve(o)&&(s=s.bind(n.api)),i.selectAll(Be(e)?`.${Ve.arc}${n.getTargetSelectorSuffix(e.id)}`:`.${sn.shape}-${e}`).style("fill",s)}},Wv={getYDomainMinMax(t,e){const n=this,{axis:a,config:i}=n,o=e==="min",s=i.data_groups,l=n.mapToIds(t),c=n.getValuesAsIdKeyed(t);if(s.length>0){const f=n[`has${o?"Negative":"Positive"}ValueInTargets`](t);s.forEach(g=>{const v=g.filter(m=>l.indexOf(m)>=0);if(v.length){const m=v[0],S=a.getId(m);f&&c[m]&&(c[m]=c[m].map(P=>(o?P<0:P>0)?P:0)),v.filter((P,N)=>N>0).forEach(P=>{if(c[P]){const N=a.getId(P);c[P].forEach((L,w)=>{const X=+L,W=o?X>0:X<0;N===S&&!(f&&W)&&(c[m][w]+=X)})}})}})}return _n(e,Object.keys(c).map(f=>_n(e,c[f])))},isHiddenTargetWithYDomain(t){const e=this;return e.state.hiddenTargetIds.some(n=>e.axis.getId(n)===t)},getYDomain(t,e,n){const a=this,{axis:i,config:o,scale:s}=a,l=`axis_${e}`;if(a.isStackNormalized())return[0,100];const c=(s==null?void 0:s[e])&&s[e].type==="log",f=t.filter(dt=>i.getId(dt.id)===e),g=n?a.filterByXDomain(f,n):f;if(g.length===0)return a.isHiddenTargetWithYDomain(e)?s[e].domain():e==="y2"?s.y.domain():a.getYDomain(t,"y2",n);const v=o[`${l}_min`],m=o[`${l}_max`],S=o[`${l}_center`],P=o[`${l}_inverted`],N=a.hasDataLabel()&&o.axis_rotated,L=a.hasDataLabel()&&!o.axis_rotated;let w=a.getYDomainMinMax(g,"min"),X=a.getYDomainMinMax(g,"max"),W=[oe.BAR,oe.BUBBLE,oe.SCATTER,...Sr.Line].some(dt=>{const st=dt.indexOf("area")>-1?"area":dt;return a.hasType(dt,g,!0)&&o[`${st}_zerobased`]});w=De(v)?v:De(m)?w<=m?w:m-10:w,X=De(m)?m:De(v)?v<=X?X:v+10:X,isNaN(w)&&(w=0),isNaN(X)&&(X=w),w===X&&(w<0?X=0:w=0);const H=w>=0&&X>=0,k=w<=0&&X<=0;(De(v)&&H||De(m)&&k)&&(W=!1),W&&(H&&(w=0),k&&(X=0));const K=Math.abs(X-w);let at={top:K*.1,bottom:K*.1};if(Qe(S)){const dt=Math.max(Math.abs(w),Math.abs(X));X=S+dt,w=S-dt}if(N){const dt=Dr(s.y.range()),st=a.getDataLabelLength(w,X,"width").map(Vt=>Vt/dt);["bottom","top"].forEach((Vt,vt)=>{at[Vt]+=K*(st[vt]/(1-st[0]-st[1]))})}else if(L){const dt=a.getDataLabelLength(w,X,"height");["bottom","top"].forEach((st,Vt)=>{at[st]+=a.convertPixelToScale("y",dt[Vt],K)})}at=a.getResettedPadding(at);const ht=o[`${l}_padding`];cn(ht)&&["bottom","top"].forEach(dt=>{at[dt]=i.getPadding(ht,dt,at[dt],K)}),W&&(H&&(at.bottom=w),k&&(at.top=-X));const $t=c?[w,X].map(dt=>dt<0?0:dt):[w-at.bottom,X+at.top];return P?$t.reverse():$t},getXDomainMinMax(t,e){var n;const a=this,i=a.config[`axis_x_${e}`],o=_n(e,t.map(l=>_n(e,l.values.map(c=>c.x))));let s=Be(i)?i.value:i;return s=Qe(s)&&((n=a.axis)!=null&&n.isTimeSeries())?Yn.bind(this)(s):s,Be(i)&&i.fit&&(e==="min"&&so)&&(s=void 0),Qe(s)?s:o},getXDomainPadding(t,e){const n=this,{axis:a,config:i}=n,o=i.axis_x_padding,s=a.isTimeSeries()&&e,l=Dr(t);let c;if(a.isCategorized()||s)c=0;else if(n.hasType("bar")){const v=n.getMaxDataCount();c=v>1?l/(v-1)/2:.5}else c=n.getResettedPadding(l*.01);let{left:f=c,right:g=c}=he(o)?{left:o,right:o}:o;if(o.unit==="px"){const v=Math.abs(l+l*.2);f=a.getPadding(o,"left",c,v),g=a.getPadding(o,"right",c,v)}else{const v=l+f+g;if(s&&v){const m=l/e/v;f=f/v/m,g=g/v/m}}return{left:f,right:g}},getXDomain(t){const e=this,{axis:n,config:a,scale:{x:i}}=e,o=a.axis_x_inverted,s=[e.getXDomainMinMax(t,"min"),e.getXDomainMinMax(t,"max")];let[l=0,c=0]=s;if(i.type!=="log"){const f=n.isCategorized(),g=n.isTimeSeries(),v=e.getXDomainPadding(s);let[m,S]=s;m-S===0&&!f&&(g?(m=new Date(m.getTime()*.5),S=new Date(S.getTime()*1.5)):(m=m===0?1:m*.5,S=S===0?-1:S*1.5)),(m||m===0)&&(l=g?new Date(m.getTime()-v.left):m-v.left),(S||S===0)&&(c=g?new Date(S.getTime()+v.right):S+v.right)}return o?[c,l]:[l,c]},updateXDomain(t,e,n,a,i){var o;const s=this,{config:l,org:c,scale:{x:f,subX:g}}=s,v=l.zoom_enabled;if(n&&(f.domain(i||na(s.getXDomain(t),!l.axis_x_inverted)),c.xDomain=f.domain(),g.domain(f.domain()),(o=s.brush)==null||o.scale(g)),e){const m=i||!s.brush||Kl(s)?c.xDomain:Wl(s).map(g.invert);f.domain(m)}return(n||e)&&v&&s.zoom.updateScaleExtent(),a&&f.domain(s.trimXDomain(f.orgDomain())),f.domain()},trimXDomain(t){const e=this,n=e.config.axis_x_inverted,a=e.getZoomDomain(),[i,o]=a;return(n?t[0]>=i:t[0]<=i)&&(t[1]=+t[1]+(i-t[0]),t[0]=i),(n?t[1]<=o:t[1]>=o)&&(t[0]=+t[0]-(t[1]-o),t[1]=o),t},getZoomDomain(t="zoom",e=!1){const n=this,{config:a,scale:i,org:o}=n;let[s,l]=e&&i[t]?i[t].domain():o.xDomain;return t==="zoom"&&(Qe(a.zoom_x_min)&&(s=_n("min",[s,a.zoom_x_min])),Qe(a.zoom_x_max)&&(l=_n("max",[l,a.zoom_x_max]))),[s,l]},getZoomDomainValue(t){const e=this,{config:n,axis:a}=e;if(a.isCategorized()&&Array.isArray(t)){const i=n.axis_x_inverted;return t.map((s,l)=>Number(s)+(l===0?+i:+!i))}return t},convertPixelToScale(t,e,n){const a=this,{config:i,state:o}=a,s=i.axis_rotated;let l;return t==="x"?l=s?"height":"width":l=s?"width":"height",n*(e/o[l])},withinRange(t,e=[0,0],n){const i=this.config.axis_x_inverted,[o,s]=n;if(Array.isArray(t)){const l=[...t];if(i&&l.reverse(),l[0](f===0?i?+c<=o:+c>=o:i?+c>=s:+c<=s)&&!t.every((g,v)=>g===e[v]))}return!1}};function mc(t,e,n){const{config:a}=t,i=`axis_${e}_tick_format`;return(a[i]?a[i]:t.defaultValueFormat).call(t.api,n)}var Kv={yFormat(t){return mc(this,"y",t)},y2Format(t){return mc(this,"y2",t)},getDefaultValueFormat(){const t=this,{defaultArcValueFormat:e,yFormat:n,y2Format:a}=t,i=t.hasArcType(null,["gauge","polar","radar"]);return function(o,s,l){return(i?e:t.axis&&t.axis.getId(l)==="y2"?a:n).call(t,o,s)}},defaultValueFormat(t){return je(t)?t.join("~"):De(t)?+t:""},defaultArcValueFormat(t,e){return`${(e*100).toFixed(1)}%`},defaultPolarValueFormat(t){return`${t}`},dataLabelFormat(t){const e=this,n=e.config.data_labels,a=o=>{const s="~";let l=o;return je(o)?l=o.join(s):Be(o)&&(l=Object.values(o).join(s)),l};let i=a;return ve(n.format)?i=n.format:nr(n.format)&&(n.format[t]?i=n.format[t]===!0?a:n.format[t]:i=()=>""),i.bind(e.api)}};function Ii(t){const e=this,n=e.getDataById(t);return e.levelColor?e.levelColor(n.values[0].value):e.color(n)}function Wo(t,e=!0){var n;const{config:a}=this;let i=(n=a.data_names[t])!=null?n:t;return e&&ve(a.legend_format)&&(i=a.legend_format(i,t!==i?t:void 0)),i}var Zv={initLegend(){const t=this,{config:e,$el:n}=t;t.legendItemTextBox={},t.state.legendHasRendered=!1,e.legend_show?(e.legend_contents_bindto||(n.legend=t.$el.svg.append("g").classed(We.legend,!0).attr("transform",t.getTranslate("legend"))),t.updateLegend()):t.state.hiddenLegendIds=t.mapToIds(t.data.targets)},updateLegend(t,e,n){var a;const i=this,{config:o,state:s,scale:l,$el:c}=i,f=e||{withTransform:!1,withTransitionForTransform:!1,withTransition:!1};f.withTransition=$r(f,"withTransition",!0),f.withTransitionForTransform=$r(f,"withTransitionForTransform",!0),o.legend_contents_bindto&&o.legend_contents_template?i.updateLegendTemplate():s.hasTreemap||i.updateLegendElement(t||i.mapToIds(i.data.targets),f,n),(a=c.legend)==null||a.selectAll(`.${We.legendItem}`).classed(We.legendItemHidden,function(g){const v=!i.isTargetToShow(g);return v&&(this.style.opacity=null),v}),i.updateScales(!1,!l.zoom),i.updateSvgSize(),i.transformAll(f.withTransitionForTransform,n),s.legendHasRendered=!0},updateLegendTemplate(){const t=this,{config:e,$el:n}=t,a=ot(e.legend_contents_bindto),i=e.legend_contents_template;if(!a.empty()){const o=t.mapToIds(t.data.targets),s=[];let l="";o.forEach(f=>{const g=ve(i)?i.bind(t.api)(f,t.color(f),t.api.data(f)[0].values):bi(i,{COLOR:t.color(f),TITLE:f});g&&(s.push(f),l+=g)});const c=a.html(l).selectAll(function(){return this.childNodes}).data(s);t.setLegendItem(c),n.legend=a}},updateSizeForLegend(t){const e=this,{config:n,state:{isLegendTop:a,isLegendLeft:i,isLegendRight:o,isLegendInset:s,current:l}}=e,{width:c,height:f}=t,g={top:a?e.getCurrentPaddingByDirection("top")+n.legend_inset_y+5.5:l.height-f-e.getCurrentPaddingByDirection("bottom")-n.legend_inset_y,left:i?e.getCurrentPaddingByDirection("left")+n.legend_inset_x+.5:l.width-c-e.getCurrentPaddingByDirection("right")-n.legend_inset_x+.5};e.state.margin3={top:o?0:s?g.top:l.height-f,right:NaN,bottom:0,left:o?l.width-c:s?g.left:0}},transformLegend(t){const e=this,{$el:{legend:n},$T:a}=e;a(n,t).attr("transform",e.getTranslate("legend"))},updateLegendStep(t){this.state.legendStep=t},updateLegendItemWidth(t){this.state.legendItemWidth=t},updateLegendItemHeight(t){this.state.legendItemHeight=t},updateLegendItemColor(t,e){const{legend:n}=this.$el;n&&n.select(`.${We.legendItem}-${t} line`).style("stroke",e)},getLegendWidth(){const t=this,{current:{width:e},isLegendRight:n,isLegendInset:a,legendItemWidth:i,legendStep:o}=t.state;return t.config.legend_show?n||a?i*(o+1):e:0},getLegendHeight(){var t;const e=this,{current:n,isLegendRight:a,legendItemHeight:i,legendStep:o}=e.state,s=((t=e.config.padding)==null?void 0:t.mode)==="fit";return e.config.legend_show?a?n.height:Math.max(s?10:20,i)*(o+1):0},opacityForUnfocusedLegend(t){return t.classed(We.legendItemHidden)?null:"0.3"},toggleFocusLegend(t,e){const n=this,{$el:{legend:a},$T:i}=n,o=n.mapToTargetIds(t);a&&i(a.selectAll(`.${We.legendItem}`).filter(s=>o.indexOf(s)>=0).classed(qe.legendItemFocused,e)).style("opacity",function(){return e?null:n.opacityForUnfocusedLegend.call(n,ot(this))})},revertLegend(){const t=this,{$el:{legend:e},$T:n}=t;e&&n(e.selectAll(`.${We.legendItem}`).classed(qe.legendItemFocused,!1)).style("opacity",null)},showLegend(t){const e=this,{config:n,$el:a,$T:i}=e;n.legend_show||(n.legend_show=!0,a.legend?a.legend.style("visibility",null):e.initLegend(),!e.state.legendHasRendered&&e.updateLegend()),e.removeHiddenLegendIds(t),i(a.legend.selectAll(e.selectorLegends(t)).style("visibility",null)).style("opacity",null)},hideLegend(t){const e=this,{config:n,$el:{legend:a}}=e;n.legend_show&&qn(t)&&(n.legend_show=!1,a.style("visibility","hidden")),e.addHiddenLegendIds(t),a.selectAll(e.selectorLegends(t)).style("opacity","0").style("visibility","hidden")},getLegendItemTextBox(t,e){const n=this,{cache:a,state:i}=n;let o;const s=Ln.legendItemTextBox;return t&&(o=!i.redrawing&&a.get(s)||{},o[t]||(o[t]=n.getTextRect(e,We.legendItem),a.add(s,o)),o=o[t]),o},setLegendItem(t){const e=this,{$el:n,api:a,config:i,state:o}=e,s=o.inputType==="touch",l=e.hasType("gauge"),c=i.boost_useCssRule,f=i.legend_item_interaction;t.attr("class",function(g){const v=ot(this);return(!v.empty()&&v.attr("class")||"")+e.generateClass(We.legendItem,g)}).style("visibility",g=>e.isLegendToShow(g)?null:"hidden"),i.interaction_enabled&&(c&&[[`.${We.legendItem}`,"cursor:pointer"],[`.${We.legendItem} text`,"pointer-events:none"],[`.${We.legendItemPoint} text`,"pointer-events:none"],[`.${We.legendItemTile}`,"pointer-events:none"],[`.${We.legendItemEvent}`,"fill-opacity:0"]].forEach(g=>{const[v,m]=g;e.setCssRule(!1,v,[m])(n.legend)}),t.on(f.dblclick?"dblclick":"click",f||ve(i.legend_item_onclick)?function(g,v){if(!_e(i.legend_item_onclick,a,v,!o.hiddenTargetIds.includes(v))){const{altKey:m,target:S,type:P}=g;P==="dblclick"||m?o.hiddenTargetIds.length&&S.parentNode.getAttribute("class").indexOf(We.legendItemHidden)===-1?a.show():(a.hide(),a.show(v)):(a.toggle(v),ot(this).classed(qe.legendItemFocused,!1))}s&&e.hideTooltip()}:null),!s&&t.on("mouseout",f||ve(i.legend_item_onout)?function(g,v){_e(i.legend_item_onout,a,v,!o.hiddenTargetIds.includes(v))||(ot(this).classed(qe.legendItemFocused,!1),l&&e.undoMarkOverlapped(e,`.${Un.gaugeValue}`),e.api.revert())}:null).on("mouseover",f||ve(i.legend_item_onover)?function(g,v){_e(i.legend_item_onover,a,v,!o.hiddenTargetIds.includes(v))||(ot(this).classed(qe.legendItemFocused,!0),l&&e.markOverlapped(v,e,`.${Un.gaugeValue}`),!o.transiting&&e.isTargetToShow(v)&&a.focus(v))}:null),!t.empty()&&t.on("click mouseout mouseover")&&t.style("cursor",e.getStylePropValue("pointer")))},updateLegendElement(t,e){const n=this,{config:a,state:i,$el:{legend:o},$T:s}=n,c=a.legend_item_tile_type!=="circle",f=a.legend_item_tile_r,g={width:c?a.legend_item_tile_width:f*2,height:c?a.legend_item_tile_height:f*2},v={padding:{top:4,right:10},max:{width:0,height:0},posMin:10,step:0,tileWidth:g.width+5,totalLength:0},m={offsets:{},widths:{},heights:{},margins:[0],steps:{}};let S,P,N;const L=t.filter(K=>!Qe(a.data_names[K])||a.data_names[K]!==null),w=e.withTransition,X=n.getUpdateLegendPositions(L,v,m);i.isLegendInset&&(v.step=a.legend_inset_step?a.legend_inset_step:L.length,n.updateLegendStep(v.step)),i.isLegendRight?(S=K=>v.max.width*m.steps[K],P=K=>m.margins[m.steps[K]]+m.offsets[K]):i.isLegendInset?(S=K=>v.max.width*m.steps[K]+10,P=K=>m.margins[m.steps[K]]+m.offsets[K]):(S=K=>m.margins[m.steps[K]]+m.offsets[K],P=K=>v.max.height*m.steps[K]);const W={xText:(K,at)=>S(K,at)+4+g.width,xRect:(K,at)=>S(K,at),x1Tile:(K,at)=>S(K,at)-2,x2Tile:(K,at)=>S(K,at)-2+g.width,yText:(K,at)=>P(K,at)+9,yRect:(K,at)=>P(K,at)-5,yTile:(K,at)=>P(K,at)+4};n.generateLegendItem(L,g,X,W),N=o.select(`.${We.legendBackground} rect`),i.isLegendInset&&v.max.width>0&&N.size()===0&&(N=o.insert("g",`.${We.legendItem}`).attr("class",We.legendBackground).append("rect")),a.legend_tooltip&&o.selectAll("title").data(L).text(K=>Wo.bind(n)(K,!1));const H=o.selectAll("text").data(L).text(K=>Wo.bind(n)(K)).each(function(K,at){X(this,K,at)});s(H,w).attr("x",W.xText).attr("y",W.yText);const k=o.selectAll(`rect.${We.legendItemEvent}`).data(L);s(k,w).attr("width",K=>m.widths[K]).attr("height",K=>m.heights[K]).attr("x",W.xRect).attr("y",W.yRect),n.updateLegendItemPos(L,w,W),N&&s(N,w).attr("height",n.getLegendHeight()-12).attr("width",v.max.width*(v.step+1)+10),n.updateLegendItemWidth(v.max.width),n.updateLegendItemHeight(v.max.height),n.updateLegendStep(v.step)},getUpdateLegendPositions(t,e,n){const a=this,{config:i,state:o}=a,s=o.isLegendRight||o.isLegendInset;return function(l,c,f){const g=f===0,v=f===t.length-1,m=a.getLegendItemTextBox(c,l),S=m.width+e.tileWidth+(v&&!s?0:e.padding.right)+i.legend_padding,P=m.height+e.padding.top,N=s?P:S,L=s?a.getLegendHeight():a.getLegendWidth();let w;const X=function(H,k){k||(w=(L-e.totalLength-N)/2,w=e.max.width)&&(e.max.width=S),(!e.max.height||P>=e.max.height)&&(e.max.height=P);const W=s?e.max.height:e.max.width;i.legend_equally?(Object.keys(n.widths).forEach(H=>n.widths[H]=e.max.width),Object.keys(n.heights).forEach(H=>n.heights[H]=e.max.height),w=(L-W*t.length)/2,wX(H))):X(c,!0)):X(c)}},generateLegendItem(t,e,n,a){const i=this,{config:o,state:s,$el:{legend:l}}=i,c=o.legend_usePoint,f=o.legend_item_tile_r,g=o.legend_item_tile_type,v=g!=="circle",m=s.isLegendRight||s.isLegendInset,S=-200,P=l.selectAll(`.${We.legendItem}`).data(t).enter().append("g");if(i.setLegendItem(P),o.legend_tooltip&&P.append("title").text(N=>N),P.append("text").text(N=>Wo.bind(i)(N)).each(function(N,L){n(this,N,L)}).style("pointer-events",i.getStylePropValue("none")).attr("x",m?a.xText:S).attr("y",m?S:a.yText),P.append("rect").attr("class",We.legendItemEvent).style("fill-opacity",i.getStylePropValue("0")).attr("x",m?a.xRect:S).attr("y",m?S:a.yRect),c){const N=[];P.append(L=>{const w=cn(o.point_pattern)?o.point_pattern:[o.point_type];N.indexOf(L)===-1&&N.push(L);let X=w[N.indexOf(L)%w.length];return X==="rectangle"&&(X="rect"),gn.createElementNS(ae.svg,"hasValidPointType"in i&&i.hasValidPointType(X)?X:"use")}).attr("class",We.legendItemPoint).style("fill",Ii.bind(i)).style("pointer-events",i.getStylePropValue("none")).attr("href",(L,w,X)=>{const H=X[w].nodeName.toLowerCase(),k=i.getTargetSelectorSuffix(L);return H==="use"?`#${s.datetimeId}-point${k}`:void 0})}else P.append(v?"line":g).attr("class",We.legendItemTile).style("stroke",Ii.bind(i)).style("pointer-events",i.getStylePropValue("none")).call(N=>{g==="circle"?N.attr("r",f).style("fill",Ii.bind(i)).attr("cx",m?a.x2Tile:S).attr("cy",m?S:a.yTile):v&&N.attr("stroke-width",e.height).attr("x1",m?a.x1Tile:S).attr("y1",m?S:a.yTile).attr("x2",m?a.x2Tile:S).attr("y2",m?S:a.yTile)})},updateLegendItemPos(t,e,n){const a=this,{config:i,$el:{legend:o},$T:s}=a,l=i.legend_usePoint,c=i.legend_item_tile_type,f=c!=="circle";if(l){const g=o.selectAll(`.${We.legendItemPoint}`).data(t);s(g,e).each(function(){const v=this.nodeName.toLowerCase(),m=i.point_r;let S="x",P="y",N=2,L=2.5,w=null,X=null,W=null;if(v==="circle"){const H=m*.2;S="cx",P="cy",w=m+H,N=m*2,L=-H}else if(v==="rect"){const H=m*2.5;X=H,W=H,L=3}ot(this).attr(S,H=>n.x1Tile(H)+N).attr(P,H=>n.yTile(H)-L).attr("r",w).attr("width",X).attr("height",W)})}else{const g=o.selectAll(`.${We.legendItemTile}`).data(t);s(g,e).style("stroke",Ii.bind(a)).call(v=>{c==="circle"?v.attr("cx",m=>{const S=n.x2Tile(m);return S-(S-n.x1Tile(m))/2}).attr("cy",n.yTile):f&&v.attr("x1",n.x1Tile).attr("y1",n.yTile).attr("x2",n.x2Tile).attr("y2",n.yTile)})}}},Jv={redraw(t={}){var e,n,a,i;const o=this,{config:s,state:l,$el:c}=o,{main:f,treemap:g}=c;l.redrawing=!0;const v=o.filterTargetsToShow(o.data.targets),{flow:m,initializing:S}=t,P=o.getWithOption(t),N=P.Transition?s.transition_duration:0,L=P.TransitionForExit?N:0,w=P.TransitionForAxis?N:0,X=(e=o.axis)==null?void 0:e.generateTransitions(w);o.updateSizes(S),P.Legend&&s.legend_show?(t.withTransition=!!N,!g&&o.updateLegend(o.mapToIds(o.data.targets),t,X)):P.Dimension&&o.updateDimension(!0),s.data_empty_label_text&&f.select(`text.${On.text}.${Se.empty}`).attr("x",l.width/2).attr("y",l.height/2).text(s.data_empty_label_text).style("display",v.length?"none":null),l.hasAxis?(o.axis.redrawAxis(v,P,X,m,S),o.hasGrid()&&o.updateGrid(),s.regions.length&&o.updateRegion(),["bar","candlestick","line","area"].forEach(W=>{const H=Cn(W);(/^(line|area)$/.test(W)&&o.hasTypeOf(H)||o.hasType(W))&&o[`update${H}`](P.TransitionForExit)}),c.text&&f.selectAll(`.${tn.selectedCircles}`).filter(o.isBarType.bind(o)).selectAll("circle").remove(),s.interaction_enabled&&!m&&P.EventRect&&(o.redrawEventRect(),(n=o.bindZoomEvent)==null||n.call(o))):(c.arcs&&o.redrawArc(N,L,P.Transform),c.radar&&o.redrawRadar(),c.polar&&o.redrawPolar(),c.funnel&&o.redrawFunnel(),g&&o.updateTreemap(L)),!l.resizing&&!g&&(o.hasPointType()||l.hasRadar)?o.updateCircle():(a=o.hasLegendDefsPoint)!=null&&a.call(o)&&o.data.targets.forEach(o.point("create",this)),o.hasDataLabel()&&!o.hasArcType(null,["radar"])&&o.updateText(),(i=o.redrawTitle)==null||i.call(o),S&&o.updateTypesElements(),o.generateRedrawList(v,m,N,P.Subchart),o.updateTooltipOnRedraw(),o.callPluginHook("$redraw",t,N)},generateRedrawList(t,e,n,a){const i=this,{config:o,state:s}=i,l=i.getDrawShape();s.hasAxis&&o.subchart_show&&i.redrawSubchart(a,n,l);const c=e&&i.generateFlow({targets:t,flow:e,duration:e.duration,shape:l,xv:i.xv.bind(i)}),f=(n||c)&&Da(),g=i.getRedrawList(l,e,c,f),v=()=>{c&&c(),s.redrawing=!1,_e(o.onrendered,i.api)};if(v)if(f&&g.length){const m=ec();Ml().duration(n).each(()=>{g.reduce((S,P)=>S.concat(P),[]).forEach(S=>m.add(S))}).call(m,v)}else s.transiting||v();i.mapToIds(i.data.targets).forEach(m=>{s.withoutFadeIn[m]=!0})},getRedrawList(t,e,n,a){const i=this,{config:o,state:{hasAxis:s,hasRadar:l,hasTreemap:c},$el:{grid:f}}=i,{cx:g,cy:v,xForText:m,yForText:S}=t.pos,P=[];return s&&((o.grid_x_lines.length||o.grid_y_lines.length)&&P.push(i.redrawGrid(a)),o.regions.length&&P.push(i.redrawRegion(a)),Object.keys(t.type).forEach(N=>{const L=Cn(N),w=t.type[N];(/^(area|line)$/.test(N)&&i.hasTypeOf(L)||i.hasType(N))&&P.push(i[`redraw${L}`](w,a))}),!e&&f.main&&P.push(i.updateGridFocus())),(!i.hasArcType()||l)&&cn(o.data_labels)&&o.data_labels!==!1&&P.push(i.redrawText(m,S,e,a)),(i.hasPointType()||l)&&!i.isPointFocusOnly()&&i.redrawCircle&&P.push(i.redrawCircle(g,v,a,n)),c&&P.push(i.redrawTreemap(a)),P},updateAndRedraw(t={}){const e=this,{config:n,state:a}=e;let i;t.withTransition=$r(t,"withTransition",!0),t.withTransform=$r(t,"withTransform",!1),t.withLegend=$r(t,"withLegend",!1),t.withUpdateXDomain=!0,t.withUpdateOrgXDomain=!0,t.withTransitionForExit=!1,t.withTransitionForTransform=$r(t,"withTransitionForTransform",t.withTransition),t.withLegend&&n.legend_show||(a.hasAxis&&(i=e.axis.generateTransitions(t.withTransitionForAxis?n.transition_duration:0)),e.updateScales(),e.updateSvgSize(),e.transformAll(t.withTransitionForTransform,i)),e.redraw(t,i)}};const Qv=Math.sqrt(50),kv=Math.sqrt(10),qv=Math.sqrt(2);function Oi(t,e,n){const a=(e-t)/Math.max(0,n),i=Math.floor(Math.log10(a)),o=a/Math.pow(10,i),s=o>=Qv?10:o>=kv?5:o>=qv?2:1;let l,c,f;return i<0?(f=Math.pow(10,-i)/s,l=Math.round(t*f),c=Math.round(e*f),l/fe&&--c,f=-f):(f=Math.pow(10,i)*s,l=Math.round(t/f),c=Math.round(e/f),l*fe&&--c),c0))return[];if(t===e)return[t];const a=e=i))return[];const l=o-i+1,c=new Array(l);if(a)if(s<0)for(let f=0;fe?1:t>=e?0:NaN}function _v(t,e){return t==null||e==null?NaN:et?1:e>=t?0:NaN}function Qo(t){let e,n,a;t.length!==2?(e=Ci,n=(l,c)=>Ci(t(l),c),a=(l,c)=>t(l)-c):(e=t===Ci||t===_v?t:tp,n=t,a=t);function i(l,c,f=0,g=l.length){if(f>>1;n(l[v],c)<0?f=v+1:g=v}while(f>>1;n(l[v],c)<=0?f=v+1:g=v}while(ff&&a(l[v-1],c)>-a(l[v],c)?v-1:v}return{left:i,center:s,right:o}}function tp(){return 0}function ep(t){return t===null?NaN:+t}function*h1(t,e){if(e===void 0)for(let n of t)n!=null&&(n=+n)>=n&&(yield n);else{let n=-1;for(let a of t)(a=e(a,++n,t))!=null&&(a=+a)>=a&&(yield a)}}const yc=Qo(Ci),np=yc.right,g1=yc.left,v1=Qo(ep).center;var rp=np;function ap(t,e){return t=+t,e=+e,function(n){return Math.round(t*(1-n)+e*n)}}function ip(t){return function(){return t}}function op(t){return+t}var xc=[0,1];function aa(t){return t}function ko(t,e){return(e-=t=+t)?function(n){return(n-t)/e}:ip(isNaN(e)?NaN:.5)}function sp(t,e){var n;return t>e&&(n=t,t=e,e=n),function(a){return Math.max(t,Math.min(e,a))}}function lp(t,e,n){var a=t[0],i=t[1],o=e[0],s=e[1];return i2?cp:lp,c=f=null,v}function v(m){return m==null||isNaN(m=+m)?o:(c||(c=l(t.map(a),e,n)))(a(s(m)))}return v.invert=function(m){return s(i((f||(f=l(e,t.map(a),Qn)))(m)))},v.domain=function(m){return arguments.length?(t=Array.from(m,op),g()):t.slice()},v.range=function(m){return arguments.length?(e=Array.from(m),g()):e.slice()},v.rangeRound=function(m){return e=Array.from(m),n=ap,g()},v.clamp=function(m){return arguments.length?(s=m?!0:aa,g()):s!==aa},v.interpolate=function(m){return arguments.length?(n=m,g()):n},v.unknown=function(m){return arguments.length?(o=m,v):o},function(m,S){return a=m,i=S,g()}}function Tc(){return qo()(aa,aa)}var up=/^(?:(.)?([<>=^]))?([+\-( ])?([$#])?(0)?(\d+)?(,)?(\.\d+)?(~)?([a-z%])?$/i;function Na(t){if(!(e=up.exec(t)))throw new Error("invalid format: "+t);var e;return new _o({fill:e[1],align:e[2],sign:e[3],symbol:e[4],zero:e[5],width:e[6],comma:e[7],precision:e[8]&&e[8].slice(1),trim:e[9],type:e[10]})}Na.prototype=_o.prototype;function _o(t){this.fill=t.fill===void 0?" ":t.fill+"",this.align=t.align===void 0?">":t.align+"",this.sign=t.sign===void 0?"-":t.sign+"",this.symbol=t.symbol===void 0?"":t.symbol+"",this.zero=!!t.zero,this.width=t.width===void 0?void 0:+t.width,this.comma=!!t.comma,this.precision=t.precision===void 0?void 0:+t.precision,this.trim=!!t.trim,this.type=t.type===void 0?"":t.type+""}_o.prototype.toString=function(){return this.fill+this.align+this.sign+this.symbol+(this.zero?"0":"")+(this.width===void 0?"":Math.max(1,this.width|0))+(this.comma?",":"")+(this.precision===void 0?"":"."+Math.max(0,this.precision|0))+(this.trim?"~":"")+this.type};function fp(t){return Math.abs(t=Math.round(t))>=1e21?t.toLocaleString("en").replace(/,/g,""):t.toString(10)}function wi(t,e){if((n=(t=e?t.toExponential(e-1):t.toExponential()).indexOf("e"))<0)return null;var n,a=t.slice(0,n);return[a.length>1?a[0]+a.slice(2):a,+t.slice(n+1)]}function ia(t){return t=wi(Math.abs(t)),t?t[1]:NaN}function dp(t,e){return Math.max(0,Math.max(-8,Math.min(8,Math.floor(ia(e)/3)))*3-ia(Math.abs(t)))}function hp(t,e){return function(n,a){for(var i=n.length,o=[],s=0,l=t[0],c=0;i>0&&l>0&&(c+l+1>a&&(l=Math.max(1,a-c)),o.push(n.substring(i-=l,i+l)),!((c+=l+1)>a));)l=t[s=(s+1)%t.length];return o.reverse().join(e)}}function gp(t){return function(e){return e.replace(/[0-9]/g,function(n){return t[+n]})}}function vp(t){t:for(var e=t.length,n=1,a=-1,i;n0&&(a=0);break}return a>0?t.slice(0,a)+t.slice(i+1):t}var $c;function pp(t,e){var n=wi(t,e);if(!n)return t+"";var a=n[0],i=n[1],o=i-($c=Math.max(-8,Math.min(8,Math.floor(i/3)))*3)+1,s=a.length;return o===s?a:o>s?a+new Array(o-s+1).join("0"):o>0?a.slice(0,o)+"."+a.slice(o):"0."+new Array(1-o).join("0")+wi(t,Math.max(0,e+o-1))[0]}function Sc(t,e){var n=wi(t,e);if(!n)return t+"";var a=n[0],i=n[1];return i<0?"0."+new Array(-i).join("0")+a:a.length>i+1?a.slice(0,i+1)+"."+a.slice(i+1):a+new Array(i-a.length+2).join("0")}var Ac={"%":(t,e)=>(t*100).toFixed(e),b:t=>Math.round(t).toString(2),c:t=>t+"",d:fp,e:(t,e)=>t.toExponential(e),f:(t,e)=>t.toFixed(e),g:(t,e)=>t.toPrecision(e),o:t=>Math.round(t).toString(8),p:(t,e)=>Sc(t*100,e),r:Sc,s:pp,X:t=>Math.round(t).toString(16).toUpperCase(),x:t=>Math.round(t).toString(16)};function Ec(t){return t}var bc=Array.prototype.map,Rc=["y","z","a","f","p","n","\xB5","m","","k","M","G","T","P","E","Z","Y"];function mp(t){var e=t.grouping===void 0||t.thousands===void 0?Ec:hp(bc.call(t.grouping,Number),t.thousands+""),n=t.currency===void 0?"":t.currency[0]+"",a=t.currency===void 0?"":t.currency[1]+"",i=t.decimal===void 0?".":t.decimal+"",o=t.numerals===void 0?Ec:gp(bc.call(t.numerals,String)),s=t.percent===void 0?"%":t.percent+"",l=t.minus===void 0?"\u2212":t.minus+"",c=t.nan===void 0?"NaN":t.nan+"";function f(v){v=Na(v);var m=v.fill,S=v.align,P=v.sign,N=v.symbol,L=v.zero,w=v.width,X=v.comma,W=v.precision,H=v.trim,k=v.type;k==="n"?(X=!0,k="g"):Ac[k]||(W===void 0&&(W=12),H=!0,k="g"),(L||m==="0"&&S==="=")&&(L=!0,m="0",S="=");var K=N==="$"?n:N==="#"&&/[boxX]/.test(k)?"0"+k.toLowerCase():"",at=N==="$"?a:/[%p]/.test(k)?s:"",ht=Ac[k],$t=/[defgprs%]/.test(k);W=W===void 0?6:/[gprs]/.test(k)?Math.max(1,Math.min(21,W)):Math.max(0,Math.min(20,W));function dt(st){var Vt=K,vt=at,Q,St,ct;if(k==="c")vt=ht(st)+vt,st="";else{st=+st;var At=st<0||1/st<0;if(st=isNaN(st)?c:ht(Math.abs(st),W),H&&(st=vp(st)),At&&+st==0&&P!=="+"&&(At=!1),Vt=(At?P==="("?P:l:P==="-"||P==="("?"":P)+Vt,vt=(k==="s"?Rc[8+$c/3]:"")+vt+(At&&P==="("?")":""),$t){for(Q=-1,St=st.length;++Qct||ct>57){vt=(ct===46?i+st.slice(Q+1):st.slice(Q))+vt,st=st.slice(0,Q);break}}}X&&!L&&(st=e(st,1/0));var Gt=Vt.length+st.length+vt.length,Bt=Gt>1)+Vt+st+vt+Bt.slice(Gt);break;default:st=Bt+Vt+st+vt;break}return o(st)}return dt.toString=function(){return v+""},dt}function g(v,m){var S=f((v=Na(v),v.type="f",v)),P=Math.max(-8,Math.min(8,Math.floor(ia(m)/3)))*3,N=Math.pow(10,-P),L=Rc[8+P/3];return function(w){return S(N*w)+L}}return{format:f,formatPrefix:g}}var Mi,ts,Ic;yp({thousands:",",grouping:[3],currency:["$",""]});function yp(t){return Mi=mp(t),ts=Mi.format,Ic=Mi.formatPrefix,Mi}function xp(t,e){return t=Math.abs(t),e=Math.abs(e)-t,Math.max(0,ia(e)-ia(t))+1}function Tp(t){return Math.max(0,-ia(Math.abs(t)))}function $p(t,e,n,a){var i=Jo(t,e,n),o;switch(a=Na(a==null?",f":a),a.type){case"s":{var s=Math.max(Math.abs(t),Math.abs(e));return a.precision==null&&!isNaN(o=dp(i,s))&&(a.precision=o),Ic(a,s)}case"":case"e":case"g":case"p":case"r":{a.precision==null&&!isNaN(o=xp(i,Math.max(Math.abs(t),Math.abs(e))))&&(a.precision=o-(a.type==="e"));break}case"f":case"%":{a.precision==null&&!isNaN(o=Tp(i))&&(a.precision=o-(a.type==="%")*2);break}}return ts(a)}function Oc(t){var e=t.domain;return t.ticks=function(n){var a=e();return Ko(a[0],a[a.length-1],n==null?10:n)},t.tickFormat=function(n,a){var i=e();return $p(i[0],i[i.length-1],n==null?10:n,a)},t.nice=function(n){n==null&&(n=10);var a=e(),i=0,o=a.length-1,s=a[i],l=a[o],c,f,g=10;for(l0;){if(f=Zo(s,l,n),f===c)return a[i]=s,a[o]=l,e(a);if(f>0)s=Math.floor(s/f)*f,l=Math.ceil(l/f)*f;else if(f<0)s=Math.ceil(s*f)/f,l=Math.floor(l*f)/f;else break;c=f}return t},t}function Di(){var t=Tc();return t.copy=function(){return Pi(t,Di())},ra.apply(t,arguments),Oc(t)}function Cc(t){return function(e){return Math.sign(e)*Math.log1p(Math.abs(e/t))}}function Pc(t){return function(e){return Math.sign(e)*Math.expm1(Math.abs(e))*t}}function Sp(t){var e=1,n=t(Cc(e),Pc(e));return n.constant=function(a){return arguments.length?t(Cc(e=+a),Pc(e)):e},Oc(n)}function wc(){var t=Sp(qo());return t.copy=function(){return Pi(t,wc()).constant(t.constant())},ra.apply(t,arguments)}function Mc(t,e){t=t.slice();var n=0,a=t.length-1,i=t[n],o=t[a],s;return oMath.pow(t,e)}function Ip(t){return t===Math.E?Math.log:t===10&&Math.log10||t===2&&Math.log2||(t=Math.log(t),e=>Math.log(e)/t)}function Nc(t){return(e,n)=>-t(-e,n)}function Op(t){const e=t(Dc,Lc),n=e.domain;let a=10,i,o;function s(){return i=Ip(a),o=Rp(a),n()[0]<0?(i=Nc(i),o=Nc(o),t(Ap,Ep)):t(Dc,Lc),e}return e.base=function(l){return arguments.length?(a=+l,s()):a},e.domain=function(l){return arguments.length?(n(l),s()):n()},e.ticks=l=>{const c=n();let f=c[0],g=c[c.length-1];const v=g0){for(;m<=S;++m)for(P=1;Pg)break;w.push(N)}}else for(;m<=S;++m)for(P=a-1;P>=1;--P)if(N=m>0?P/o(-m):P*o(m),!(Ng)break;w.push(N)}w.length*2{if(l==null&&(l=10),c==null&&(c=a===10?"s":","),typeof c!="function"&&(!(a%1)&&(c=Na(c)).precision==null&&(c.trim=!0),c=ts(c)),l===1/0)return c;const f=Math.max(1,a*l/e.ticks().length);return g=>{let v=g/o(Math.round(i(g)));return v*an(Mc(n(),{floor:l=>o(Math.floor(i(l))),ceil:l=>o(Math.ceil(i(l)))})),e}function Fc(){const t=Op(qo()).domain([1,10]);return t.copy=()=>Pi(t,Fc()).base(t.base()),ra.apply(t,arguments),t}const Li=en(()=>{},(t,e)=>{t.setTime(+t+e)},(t,e)=>e-t);Li.every=t=>(t=Math.floor(t),!isFinite(t)||!(t>0)?null:t>1?en(e=>{e.setTime(Math.floor(e/t)*t)},(e,n)=>{e.setTime(+e+n*t)},(e,n)=>(n-e)/t):Li);const p1=Li.range,Ur=en(t=>{t.setTime(t-t.getMilliseconds())},(t,e)=>{t.setTime(+t+e*Gn)},(t,e)=>(e-t)/Gn,t=>t.getUTCSeconds()),m1=Ur.range,es=en(t=>{t.setTime(t-t.getMilliseconds()-t.getSeconds()*Gn)},(t,e)=>{t.setTime(+t+e*In)},(t,e)=>(e-t)/In,t=>t.getMinutes()),y1=es.range,ns=en(t=>{t.setUTCSeconds(0,0)},(t,e)=>{t.setTime(+t+e*In)},(t,e)=>(e-t)/In,t=>t.getUTCMinutes()),x1=ns.range,rs=en(t=>{t.setTime(t-t.getMilliseconds()-t.getSeconds()*Gn-t.getMinutes()*In)},(t,e)=>{t.setTime(+t+e*Bn)},(t,e)=>(e-t)/Bn,t=>t.getHours()),T1=rs.range,as=en(t=>{t.setUTCMinutes(0,0,0)},(t,e)=>{t.setTime(+t+e*Bn)},(t,e)=>(e-t)/Bn,t=>t.getUTCHours()),$1=as.range,is=en(t=>{t.setDate(1),t.setHours(0,0,0,0)},(t,e)=>{t.setMonth(t.getMonth()+e)},(t,e)=>e.getMonth()-t.getMonth()+(e.getFullYear()-t.getFullYear())*12,t=>t.getMonth()),S1=is.range,os=en(t=>{t.setUTCDate(1),t.setUTCHours(0,0,0,0)},(t,e)=>{t.setUTCMonth(t.getUTCMonth()+e)},(t,e)=>e.getUTCMonth()-t.getUTCMonth()+(e.getUTCFullYear()-t.getUTCFullYear())*12,t=>t.getUTCMonth()),A1=os.range;function Bc(t,e,n,a,i,o){const s=[[Ur,1,Gn],[Ur,5,5*Gn],[Ur,15,15*Gn],[Ur,30,30*Gn],[o,1,In],[o,5,5*In],[o,15,15*In],[o,30,30*In],[i,1,Bn],[i,3,3*Bn],[i,6,6*Bn],[i,12,12*Bn],[a,1,or],[a,2,2*or],[n,1,to],[e,1,Ps],[e,3,3*Ps],[t,1,eo]];function l(f,g,v){const m=gL).right(s,m);if(S===s.length)return t.every(Jo(f/eo,g/eo,v));if(S===0)return Li.every(Math.max(Jo(f,g,v),1));const[P,N]=s[m/s[S-1][2]n.axis.x.tickOffset()),i=n.config.axis_x_inverted,o=function(s){return t(s)+a()};for(const s in t)o[s]=t[s];return o.orgDomain=()=>t.domain(),o.orgScale=()=>t,n.axis.isCategorized()&&(o.domain=function(s){let l=s;return arguments.length?(t.domain(l),o):(l=this.orgDomain(),i?[l[0]+1,l[1]]:[l[0],l[1]+1])}),o},updateScales(t,e=!0){var n,a;const i=this,{axis:o,config:s,format:l,org:c,scale:f,state:{current:g,width:v,height:m,width2:S,height2:P,hasAxis:N,hasTreemap:L}}=i;if(N){const w=s.axis_rotated,X=i.getResettedPadding(1),W={x:w?X:0,y:w?0:m,subX:w?1:0,subY:w?0:P},H={x:w?m:v,y:w?v:X,subX:w?m:v,subY:w?S:1},k=e&&((n=f.x)==null?void 0:n.orgDomain()),K=e&&c.xDomain;f.x=i.getXScale(W.x,H.x,k,()=>o.x.tickOffset()),f.subX=i.getXScale(W.x,H.x,K,at=>{var ht;return at%1?0:((ht=o.subX)!=null?ht:o.x).tickOffset()}),l.xAxisTick=o.getXAxisTickFormat(),l.subXAxisTick=o.getXAxisTickFormat(!0),o.setAxis("x",f.x,s.axis_x_tick_outer,t),s.subchart_show&&o.setAxis("subX",f.subX,s.axis_x_tick_outer,t),f.y=i.getYScale("y",W.y,H.y,f.y?f.y.domain():s.axis_y_default),f.subY=i.getYScale("y",W.subY,H.subY,f.subY?f.subY.domain():s.axis_y_default),o.setAxis("y",f.y,s.axis_y_tick_outer,t),s.axis_y2_show&&(f.y2=i.getYScale("y2",W.y,H.y,f.y2?f.y2.domain():s.axis_y2_default),f.subY2=i.getYScale("y2",W.subY,H.subY,f.subY2?f.subY2.domain():s.axis_y2_default),o.setAxis("y2",f.y2,s.axis_y2_tick_outer,t))}else if(L){const w=i.getCurrentPadding();f.x=Di().rangeRound([w.left,g.width-w.right]),f.y=Di().rangeRound([w.top,g.height-w.bottom])}else(a=i.updateArc)==null||a.call(i)},xx(t){const e=this,{config:n,scale:{x:a,zoom:i}}=e,o=n.zoom_enabled&&i?i:a;return t?o(De(t.x)?t.x:t):null},xv(t){const e=this,{axis:n,config:a,scale:{x:i,zoom:o}}=e,s=a.zoom_enabled&&o?o:i;let l=e.getBaseValue(t);return n.isTimeSeries()?l=Yn.call(e,l):n.isCategorized()&&ze(l)&&(l=a.axis_x_categories.indexOf(l)),s(l)},yv(t){const e=this,{scale:{y:n,y2:a}}=e;return(t.axis&&t.axis==="y2"?a:n)(e.getBaseValue(t))},subxx(t){return t?this.scale.subX(t.x):null}},Up={setContainerSize(){const t=this,{state:e}=t;e.current.width=t.getCurrentWidth(),e.current.height=t.getCurrentHeight()},getCurrentWidth(){const t=this;return t.config.size_width||t.getParentWidth()},getCurrentHeight(){const t=this,{config:e}=t,n=e.size_height||t.getParentHeight();return n>0?n:320/(t.hasType("gauge")&&!e.gauge_fullCircle?2:1)},getParentRectValue(t){const e=`offset${Cn(t)}`;let n=this.$el.chart.node(),a=0;for(;a<30&&n&&n.tagName!=="BODY";){try{a=n.getBoundingClientRect()[t]}catch(o){e in n&&(a=n[e])}n=n.parentNode}const i=gn.body[e];return a>i&&(a=i),a},getParentWidth(){return this.getParentRectValue("width")},getParentHeight(){const t=this.$el.chart.style("height");let e=0;return t&&(e=/px$/.test(t)?parseInt(t,10):this.getParentRectValue("height")),e},getSvgLeft(t){const e=this,{config:n,state:{hasAxis:a},$el:i}=e,o=n.axis_rotated,s=o||!o&&!n.axis_y_inner,l=o?Tn.axisX:Tn.axisY,c=i.main.select(`.${l}`).node(),f=a&&n[`axis_${o?"x":"y"}_label`];let g=0;if(a&&(ze(f)||ze(f.text)||/^inner-/.test(f==null?void 0:f.position))){const N=i.main.select(`.${l}-label`);N.empty()||(g=N.node().getBoundingClientRect().left)}const v=c&&s?c.getBoundingClientRect():{right:0},m=i.chart.node().getBoundingClientRect().left+g,S=e.hasArcType(),P=v.right-m-(S?0:e.getCurrentPaddingByDirection("left",t));return P>0?P:0},updateDimension(t){var e;const n=this,{config:a,state:{hasAxis:i},$el:o}=n;i&&!t&&n.axis.x&&a.axis_rotated&&((e=n.axis.subX)==null||e.create(o.axis.subX)),n.updateScales(t),n.updateSvgSize(),n.transformAll(!1)},updateSvgSize(){const t=this,{config:e,state:{clip:n,current:a,hasAxis:i,width:o,height:s},$el:{svg:l}}=t;if(e.resize_auto==="viewBox"?l.attr("viewBox",`0 0 ${a.width} ${a.height}`):l.attr("width",a.width).attr("height",a.height),i){const c=l.select(`.${ks.brush} .overlay`),f={width:0,height:0};c.size()&&(f.width=+c.attr("width"),f.height=+c.attr("height")),l.selectAll([`#${n.id}`,`#${n.idGrid}`]).select("rect").attr("width",o).attr("height",s),l.select(`#${n.idXAxis}`).select("rect").call(t.setXAxisClipPath.bind(t)),l.select(`#${n.idYAxis}`).select("rect").call(t.setYAxisClipPath.bind(t)),n.idSubchart&&l.select(`#${n.idSubchart}`).select("rect").attr("width",o).attr("height",f.height)}},getCurrentPaddingByDirection(t,e=!1,n=!1){var a;const i=this,{config:o,$el:s,state:{hasAxis:l}}=i,c=o.axis_rotated,f=((a=o.padding)==null?void 0:a.mode)==="fit",g=he(o[`padding_${t}`])?o[`padding_${t}`]:void 0,v=l?{top:c?"y2":null,bottom:c?"y":"x",left:c?"x":"y",right:c?null:"y2"}[t]:null,m=/^(left|right)$/.test(t),S=v&&o[`axis_${v}_inner`],P=v&&o[`axis_${v}_show`],N=v?o[`axis_${v}_axes`].length:0;let L=v?m?i.getAxisWidthByAxisId(v,e):i.getHorizontalAxisHeight(v):0;const w=20;let X=0;!f&&m&&(L=Jg(L));let W=l&&m&&(S||ln(g)&&!P)?0:f?(P?L:0)+(g!=null?g:0):ln(g)?L:g;return m&&l?(v&&(f||S)&&o[`axis_${v}_label`].text&&(W+=i.axis.getAxisLabelPosition(v).isOuter?w:0),t==="right"?(W+=c?!f&&ln(g)?10:2:!P||S?f?2:1:0,W+=n?i.axis.getXAxisTickTextY2Overflow(w):0):t==="left"&&c&&ln(g)&&(W=o.axis_x_show?f?L:Math.max(L,40):1)):t==="top"?(s.title&&s.title.node()&&(W+=i.getTitlePadding()),X=c&&!S?N:0):t==="bottom"&&l&&c&&!P&&(W+=1),W+L*N-X},getCurrentPadding(t=!1){const e=this,[n,a,i,o]=["top","bottom","left","right"].map(s=>e.getCurrentPaddingByDirection(s,null,t));return{top:n,bottom:a,left:i,right:o}},getResettedPadding(t){const e=this,{config:n}=e,a=he(t);let i=a?0:{};return n.padding===!1?!a&&Object.keys(t).forEach(o=>{i[o]=!qn(n.data_labels)&&n.data_labels!==!1&&o==="top"?t[o]:0}):i=t,i},updateSizes(t){var e,n,a,i,o;const s=this,{config:l,state:c,$el:{legend:f}}=s,g=l.axis_rotated,v=s.hasArcType()||c.hasFunnel||c.hasTreemap,m=((e=l.padding)==null?void 0:e.mode)==="fit";!t&&s.setContainerSize();const S={width:f?s.getLegendWidth():0,height:f?s.getLegendHeight():0};!v&&l.axis_x_show&&l.axis_x_tick_autorotate&&s.updateXAxisTickClip();const P={right:l.legend_show&&c.isLegendRight?s.getLegendWidth()+(m?0:20):0,bottom:!l.legend_show||c.isLegendRight||c.isLegendInset?0:S.height},N=g||v?0:s.getHorizontalAxisHeight("x"),L=l.subchart_axis_x_show&&l.subchart_axis_x_tick_text_show?N:30,w=l.subchart_show&&!v?l.subchart_size_height+L:0,X=s.hasType("gauge")&&l.arc_needle_show&&!l.gauge_fullCircle&&!l.gauge_label_show?10:0,W=s.getCurrentPadding(!0);if(c.margin=!v&&g?{top:W.top,right:v?0:W.right+P.right,bottom:P.bottom+W.bottom,left:w+(v?0:W.left)}:{top:(m?0:4)+W.top,right:v?0:W.right+P.right,bottom:X+w+P.bottom+W.bottom,left:v?0:W.left},c.margin=s.getResettedPadding(c.margin),c.margin2=g?{top:c.margin.top,right:NaN,bottom:20+P.bottom,left:s.state.rotatedPadding.left}:{top:c.current.height-w-P.bottom,right:NaN,bottom:L+P.bottom,left:c.margin.left},c.margin3={top:0,right:NaN,bottom:0,left:0},(n=s.updateSizeForLegend)==null||n.call(s,S),c.width=c.current.width-c.margin.left-c.margin.right,c.height=c.current.height-c.margin.top-c.margin.bottom,c.width<0&&(c.width=0),c.height<0&&(c.height=0),c.width2=g?c.margin.left-c.rotatedPadding.left-c.rotatedPadding.right:c.width,c.height2=g?c.height:c.current.height-c.margin2.top-c.margin2.bottom,c.width2<0&&(c.width2=0),c.height2<0&&(c.height2=0),s.hasArcType()){const H=s.hasType("gauge"),k=l.legend_show&&c.isLegendRight,K=(a=c.hasRadar&&s.cache.get(Ln.radarTextWidth))!=null?a:0;c.arcWidth=c.width-(k?S.width+10:0)-K,c.arcHeight=c.height-(k&&!H?0:10),(i=l.arc_rangeText_values)!=null&&i.length&&(H?(c.arcWidth-=25,c.arcHeight-=10,c.margin.left+=10):(c.arcHeight-=20,c.margin.top+=10)),H&&!l.gauge_fullCircle&&(c.arcHeight+=c.height-s.getPaddingBottomForGauge()),(o=s.updateRadius)==null||o.call(s)}c.isLegendRight&&v&&(c.margin3.left=c.arcWidth/2+c.radiusExpanded*1.1)}},zp={setCssRule(t,e,n,a){const i=this,{config:o,state:{cssRule:s,style:l}}=i;return o.boost_useCssRule?c=>{c.each(f=>{const g=a&&(a==null?void 0:a.call(i,f)),v=`${t?`.${sn.shapes+i.getTargetSelectorSuffix(f.id)}`:""}${e}`;e in s&&l.sheet.deleteRule(s[v]),i.state.cssRule[v]=_g(l,v,n.filter(Boolean).map(m=>ze(g)&&m.indexOf(":")===-1?`${m}: ${g}`:m||""))})}:()=>{}},getStylePropValue(t){const{config:{boost_useCssRule:e}}=this;return e?null:ve(t)?t.bind(this):t}};function Uc(t){return typeof t=="string"?new Ie([document.querySelectorAll(t)],[document.documentElement]):new Ie([T(t)],_t)}function jp(t){let e="middle";return t>0&&t<=170?e="end":t>190&&t<=360&&(e="start"),e}function Vp(t,e,n,a,i){var o;const s=this,{value:l}=t,c=s.isCandlestickType(t),f=he(l)&&l<0||c&&!((o=s.getCandlestickData(t))!=null&&o._isUp);let{x:g,y:v}=e;const m=4,S=m*2;return a?n==="start"?(g+=f?0:S,v+=m):n==="middle"?(g+=S,v-=S):n==="end"&&(f&&(g-=S),v+=m):(n==="start"?(g+=m,f&&(v+=S*2)):n==="middle"?v-=S:n==="end"&&(g-=m,f&&(v+=S*2)),i&&(v+=f?-17:c?13:7)),{x:g,y:v}}function zc(t,e){var n;const a=this.config.data_labels_position,{id:i,index:o,value:s}=t;return(n=ve(a)?a.bind(this.api)(e,s,i,o,this.$el.text):(i in a?a[i]:a)[e])!=null?n:0}var Gp={opacityForText(t){const e=this;return e.isBarType(t)&&!e.meetsLabelThreshold(Math.abs(e.getRatio("bar",t)),"bar")?"0":e.hasDataLabel?null:"0"},initText(){const{$el:t}=this;t.main.select(`.${Se.chart}`).append("g").attr("class",On.chartTexts).style("pointer-events",t.funnel||t.treemap?"none":null)},updateTargetsForText(t){const e=this,n=e.getChartClass("Text"),a=e.getClass("texts","id"),i=e.classFocus.bind(e);e.$el.main.select(`.${On.chartTexts}`).selectAll(`.${On.chartText}`).data(t).attr("class",l=>`${n(l)}${i(l)}`.trim()).enter().append("g").style("opacity","0").attr("class",n).call(e.setCssRule(!0,` .${On.text}`,["fill","pointer-events:none"],e.updateTextColor)).append("g").attr("class",a)},updateText(){const t=this,{$el:e,$T:n,config:a,axis:i}=t,o=t.getClass("text","index"),s=a.data_labels.centered,l=e.main.selectAll(`.${On.texts}`).selectAll(`.${On.text}`).data(t.labelishData.bind(t));n(l.exit()).style("fill-opacity","0").remove(),e.text=l.enter().append("text").merge(l).attr("class",o).attr("text-anchor",c=>{let g=a[`axis_${i==null?void 0:i.getId(c.id)}_inverted`]?c.value>0:c.value<0;if(t.isCandlestickType(c)){const v=t.getCandlestickData(c);g=!(v!=null&&v._isUp)}else if(t.isTreemapType(c))return s?"middle":"start";return a.axis_rotated?g?"end":"start":"middle"}).style("fill",t.getStylePropValue(t.updateTextColor)).style("fill-opacity","0").each(function(c,f,g){const v=ot(this);let{value:m}=c;if(t.isBubbleZType(c))m=t.getBubbleZData(m,"z");else if(t.isCandlestickType(c)){const S=t.getCandlestickData(c);S&&(m=S.close)}m=t.isTreemapType(c)?t.treemapDataLabelFormat(c)(v):t.dataLabelFormat(c.id)(m,c.id,c.index,g),he(m)?this.textContent=m:wa(v,m)})},updateTextColor(t){const e=this,{config:n}=e,a=n.data_labels_colors,i=e.isArcType(t)&&!e.isRadarType(t)||e.isFunnelType(t)||e.isTreemapType(t)?null:e.color(t);let o;if(ze(a))o=a;else if(Be(a)){const{id:s}=t.data||t;o=a[s]}else ve(a)&&(o=a.bind(e.api)(i,t));if(e.isCandlestickType(t)&&!ve(a)){const s=e.getCandlestickData(t);if(!(s!=null&&s._isUp)){const l=n.candlestick_color_down;o=Be(l)?l[t.id]:l}}return o||i},updateTextBGColor(t,e){const n=this,{$el:a}=n;let i="";if(ze(e)||Be(e)){const o=ze(e)?"":n.getTargetSelectorSuffix("id"in t?t.id:t.data.id),s=a.defs.select(["filter[id*='labels-bg","']"].join(o));s.size()&&(i=`url(#${s.attr("id")})`)}return i||null},redrawText(t,e,n,a){const i=this,{$T:o,axis:s,config:l,state:{hasTreemap:c}}=i,f=gr(!0),g=l.axis_rotated,v=l.data_labels.rotate,m=jp(v),S=v?`rotate(${v})`:"";return i.$el.text.style("fill",i.getStylePropValue(i.updateTextColor)).attr("filter",P=>i.updateTextBGColor.bind(i)(P,l.data_labels_backgroundColors)).style("fill-opacity",n?0:i.opacityForText.bind(i)).each(function(P,N){const L=o(c&&this.childElementCount?this.parentNode:this,!!(a&&this.getAttribute("x")),f),w=l[`axis_${s==null?void 0:s.getId(P.id)}_inverted`];let X={x:t.bind(this)(P,N),y:e.bind(this)(P,N)};v&&(X=Vp.bind(i)(P,X,m,g,w),L.attr("text-anchor",m)),this.childElementCount||v?L.attr("transform",`translate(${X.x} ${X.y}) ${S}`):L.attr("x",X.x).attr("y",X.y)}),!0},getTextRect(t,e){const n=this;let a=t.node?t.node():t;/text/i.test(a.tagName)||(a=a.querySelector("text"));const i=a.textContent,o=`${Ln.textRect}-${i.replace(/\W/g,"_")}`;let s=n.cache.get(o);return s||(n.$el.svg.append("text").style("visibility","hidden").style("font",ot(a).style("font")).classed(e,!0).text(i).call(l=>{s=Ma(l.node())}).remove(),n.cache.add(o,s)),s},generateXYForText(t,e){const n=this,{state:{hasRadar:a,hasFunnel:i,hasTreemap:o}}=n,s=Object.keys(t),l={},c=e?n.getXForText:n.getYForText;return i&&s.push("funnel"),a&&s.push("radar"),o&&s.push("treemap"),s.forEach(f=>{l[f]=n[`generateGet${Cn(f)}Points`](t[f],!1)}),function(f,g){const v=n.isAreaType(f)&&"area"||n.isBarType(f)&&"bar"||n.isCandlestickType(f)&&"candlestick"||n.isFunnelType(f)&&"funnel"||n.isRadarType(f)&&"radar"||n.isTreemapType(f)&&"treemap"||"line";return c.call(n,l[v](f,g),f,this)}},getCenteredTextPos(t,e,n,a){const i=this,{config:o}=i,s=o.axis_rotated,l=i.isBarType(t),c=i.isTreemapType(t);if(o.data_labels.centered&&(l||c)){const f=Ma(n);if(l){const g=i.getRangedData(t,null,"bar")>=0;if(s){const v=(g?e[1][1]-e[0][1]:e[0][1]-e[1][1])/2+f.width/2;return g?-v-3:v+2}else{const v=(g?e[0][1]-e[1][1]:e[1][1]-e[0][1])/2+f.height/2;return g?v:-v-2}}else if(c)return a==="x"?(e[1][0]-e[0][0])/2:(e[1][1]-e[0][1])/2+f.height/2}return 0},getXForText(t,e,n){var a;const i=this,{config:o}=i,s=o.axis_rotated,l=i.isFunnelType(e),c=i.isTreemapType(e);let f=t?t[0][0]:0;if(i.isCandlestickType(e))s?f=(a=i.getCandlestickData(e))!=null&&a._isUp?t[2][2]+4:t[2][1]-4:f+=(t[1][0]-f)/2;else if(l)f+=i.state.current.width/2;else if(c)f+=o.data_labels.centered?0:5;else if(s){const g=o[`axis_${i.axis.getId(e.id)}_inverted`],v=i.isBarType(e)?4:6,m=e.value;f=t[2][1],g?f-=v*(m>0?1:-1):f+=v*(m<0?-1:1)}else f=i.hasType("bar")?(t[2][0]+t[0][0])/2:f;return(s||c)&&(f+=i.getCenteredTextPos(e,t,n,"x")),f+zc.call(this,e,"x")},getYForText(t,e,n){const a=this,{axis:i,config:o,state:s}=a,l=o.axis_rotated,c=o[`axis_${i==null?void 0:i.getId(e.id)}_inverted`],f=a.isBarType(e),g=a.isFunnelType(e),v=a.isTreemapType(e),m=o.point_r,S=Ma(n);let{value:P}=e,N=3,L;if(a.isCandlestickType(e))P=a.getCandlestickData(e),l?(L=t[0][0],L+=(t[1][0]-L)/2+N):(L=P&&P._isUp?t[2][2]-N:t[2][1]+N*4,c&&(L+=15*(P._isUp?1:-1)));else if(g)L=t?t[0][1]+(t[1][1]-t[0][1])/2+S.height/2-3:0;else if(v)L=t[0][1]+(o.data_labels.centered?0:S.height+5);else if(l)L=(t[0][0]+t[2][0]+S.height*.6)/2;else if(L=t[2][1],he(m)&&m>5&&(a.isLineType(e)||a.isScatterType(e))&&(N+=o.point_r/2.3),P<0||P===0&&!s.hasPositiveValue&&s.hasNegativeValue)L+=c?f?-3:-5:S.height+(f?-N:N);else{let w=-N*2;f?w=-N:a.isBubbleType(e)&&(w=N),c&&(w=f?10:15),L+=w}return(!l||v)&&(L+=a.getCenteredTextPos(e,t,n,"y")),L+zc.call(this,e,"y")},markOverlapped(t,e,n){const a=e.$el.arcs.selectAll(n),i=a.filter(c=>c.data.id!==t),o=a.filter(c=>c.data.id===t),s=Jl(o.node()),l=(c,f)=>Math.sqrt(Math.pow(c,2)+Math.pow(f,2));o.node()&&i.each(function(){const c=Jl(this),f=ot(this),g=l(s.e,s.f)>l(c.e,c.f)?o:f,v=Math.ceil(Math.abs(s.e-c.e))=i}};function jc(t="left",e){const n=he(e);let a;return t.indexOf("center")>-1?a=n?e/2:"middle":t.indexOf("right")>-1?a=n?e:"end":a=n?0:"start",a}var Xp={initTitle(){const t=this,{config:e,$el:n}=t;if(e.title_text){n.title=n.svg.append("g");const a=n.title.append("text").style("text-anchor",jc(e.title_position)).attr("class",On.title);wa(a,e.title_text,[.3,1.5])}},redrawTitle(){const t=this,{config:e,state:{current:n},$el:{title:a}}=t;if(a){const i=jc(e.title_position,n.width),o=(e.title_padding.top||0)+t.getTextRect(t.$el.title,On.title).height;a.attr("transform",`translate(${i}, ${o})`)}},getTitlePadding(){const t=this,{$el:{title:e},config:n}=t;return(n.title_padding.top||0)+(e?t.getTextRect(e,On.title).height:0)+(n.title_padding.bottom||0)}},Hp={initTooltip(){const t=this,{config:e,$el:n}=t;n.tooltip=ot(e.tooltip_contents.bindto),n.tooltip.empty()&&(n.tooltip=n.chart.append("div").attr("class",ei.tooltipContainer).style("position","absolute").style("pointer-events","none").style("display","none")),t.bindTooltipResizePos()},initShowTooltip(){var t;const e=this,{config:n,$el:a,state:{hasAxis:i,hasRadar:o}}=e;if(n.tooltip_init_show){const s=!(i||o);(t=e.axis)!=null&&t.isTimeSeries()&&ze(n.tooltip_init_x)&&(n.tooltip_init_x=Yn.call(e,n.tooltip_init_x)),e.api.tooltip.show({data:{[s?"index":"x"]:n.tooltip_init_x}});const l=n.tooltip_init_position;if(!n.tooltip_contents.bindto&&!qn(l)){const{top:c=0,left:f=50}=l;a.tooltip.style("top",ze(c)?c:`${c}px`).style("left",ze(f)?f:`${f}px`).style("display",null)}}},getTooltipHTML(...t){const e=this,{api:n,config:a}=e;return ve(a.tooltip_contents)?a.tooltip_contents.bind(n)(...t):e.getTooltipContent(...t)},getTooltipContent(t,e,n,a){var i;const o=this,{api:s,config:l,state:c,$el:f}=o,[g,v,m]=["title","name","value"].map(vt=>{const Q=l[`tooltip_format_${vt}`];return ve(Q)?Q.bind(s):Q}),S=(...vt)=>Po((g||e)(...vt)),P=(...vt)=>Po((v||(Q=>Q))(...vt)),N=(...vt)=>{const Q=m||(c.hasTreemap||o.isStackNormalized()?(St,ct)=>`${(ct*100).toFixed(2)}%`:n);return Po(Q(...vt))},L=l.tooltip_order,w=vt=>o.axis&&o.isBubbleZType(vt)?o.getBubbleZData(vt.value,"z"):o.getBaseValue(vt),X=o.levelColor?vt=>o.levelColor(vt.value):vt=>a(vt),W=l.tooltip_contents,H=W.template,k=o.mapToTargetIds();if(L===null&&l.data_groups.length){const vt=o.orderTargets(o.data.targets).map(Q=>Q.id).reverse();t.sort((Q,St)=>{let ct=Q?Q.value:null,At=St?St.value:null;return ct>0&&At>0&&(ct=Q.id?vt.indexOf(Q.id):null,At=St.id?vt.indexOf(St.id):null),ct-At})}else if(/^(asc|desc)$/.test(L)){const vt=L==="asc";t.sort((Q,St)=>{const ct=Q?w(Q):null,At=St?w(St):null;return vt?ct-At:At-ct})}else ve(L)&&t.sort(L.bind(s));const K=o.getTooltipContentTemplate(H),at=t.length;let ht,$t,dt,st,Vt;for(Vt=0;Vt`:""})}if(!$t.ratio&&f.arcs&&(dt=["arc",o.$el.arcs.select(`path.${Ve.arc}-${$t.id}`).data()[0]],$t.ratio=o.getRatio(...dt)),dt=[$t.ratio,$t.id,$t.index],o.isAreaRangeType($t)){const[vt,Q]=["high","low"].map(ct=>N(o.getRangedData($t,ct),...dt));st=`Mid: ${N(w($t),...dt)} High: ${vt} Low: ${Q}`}else if(o.isCandlestickType($t)){const[vt,Q,St,ct,At]=["open","high","low","close","volume"].map(Gt=>o.getRangedData($t,Gt,"candlestick")?N(o.getRangedData($t,Gt,"candlestick"),...dt):void 0);st=`Open: ${vt} High: ${Q} Low: ${St} Close: ${ct}${At?` Volume: ${At}`:""}`}else if(o.isBarRangeType($t)){const{value:vt,id:Q,index:St}=$t;st=`${N(vt,void 0,Q,St)}`}else st=N(w($t),...dt);if(st!==void 0){if($t.name===null)continue;const vt=P((i=$t.name)!=null?i:$t.id,...dt),Q=X($t),St={CLASS_TOOLTIP_NAME:ei.tooltipName+o.getTargetSelectorSuffix($t.id),COLOR:H||!o.patterns?Q:``,NAME:vt,VALUE:st};if(H&&Be(W.text)){const ct=k.indexOf($t.id);Object.keys(W.text).forEach(At=>{St[At]=W.text[At][ct]})}ht+=bi(K[1],St)}}return`${ht}
${vt}
`},getTooltipContentTemplate(t){return(t||` + {=TITLE} + {{ + + + }} +
${this.patterns?"{=COLOR}":''}{=NAME}{=VALUE}
`).replace(/(\r?\n|\t)/g,"").split(/{{(.*)}}/)},setTooltipPosition(t,e){var n,a;const i=this,{config:o,scale:s,state:l,$el:{eventRect:c,tooltip:f,svg:g}}=i,{bindto:v}=o.tooltip_contents,m=o.axis_rotated,S=f==null?void 0:f.datum();if(!v&&S){const P=t!=null?t:JSON.parse(S.current),[N,L]=Hn(l.event,e!=null?e:c==null?void 0:c.node()),w={x:N,y:L};if(l.hasAxis&&s.x&&S&&"x"in S){const k=(K=0,at,ht="y")=>{var $t;const dt=s[at?($t=i.axis)==null?void 0:$t.getId(at):ht];return dt?dt(K)+(m?l.margin.left:l.margin.top):0};w.xAxis=s.x(S.x)+(o.tooltip_position?m?l.margin.top:l.margin.left:0),P.length===1?w.yAxis=k(P[0].value,P[0].id):w.yAxis=k}const{width:X=0,height:W=0}=S,H=(a=(n=o.tooltip_position)==null?void 0:n.bind(i.api)(P,X,W,c==null?void 0:c.node(),w))!=null?a:Lo(g)?i.getTooltipPositionViewBox.bind(i)(X,W,w):i.getTooltipPosition.bind(i)(X,W,w);["top","left"].forEach(k=>{const K=H[k];f.style(k,`${K}px`),k==="left"&&!S.xPosInPercent&&(S.xPosInPercent=K/l.current.width*100)})}},getTooltipPositionViewBox(t,e,n){var a,i;const o=this,{$el:{eventRect:s,svg:l},config:c,state:f}=o,g=c.axis_rotated,v=o.hasArcType()||f.hasFunnel||f.hasTreemap,m=(i=(a=v?l:s)==null?void 0:a.node())!=null?i:f.event.target;let{x:S,y:P}=n;f.hasAxis&&(S=g?S:n.xAxis,P=g?n.xAxis:P);const N=Ai(m,S,P,!1),L=m.getBoundingClientRect(),w=Ai(m,20,0,!1).x;let X=N.y,W=N.x+t/2+w;return v&&(f.hasFunnel||f.hasTreemap||f.hasRadar?(W-=t/2+w,X+=e):(X+=L.height/2,W+=L.width/2-(t-w))),W+t>L.width&&(W=L.width-t-w),X+e>L.height&&(X-=e*2),{top:X,left:W}},getTooltipPosition(t,e,n){var a,i,o;const s=this,{config:l,scale:c,state:f}=s,{width:g,height:v,current:m,hasFunnel:S,hasRadar:P,hasTreemap:N,isLegendRight:L,inputType:w}=f,X=s.hasType("gauge")&&!l.gauge_fullCircle,W=l.axis_rotated,H=s.hasArcType(),k=s.getSvgLeft(!0);let K=k+m.width-s.getCurrentPaddingByDirection("right");const at=20;let{x:ht,y:$t}=n;if(P)ht+=ht>=g/2?15:-(t+15),$t+=15;else if(H){if(w!=="touch"){let Vt=(i=(a=s.getTitlePadding)==null?void 0:a.call(s))!=null?i:0;Vt&&X&&((o=l.arc_rangeText_values)!=null&&o.length)&&(Vt+=10),ht+=(g-(L?s.getLegendWidth():0))/2,$t+=(X?v:v/2+e)+Vt}}else if(S||N)$t+=e;else{const st={top:s.getCurrentPaddingByDirection("top",!0),left:s.getCurrentPaddingByDirection("left",!0)};W?(ht+=k+st.left+at,$t=st.top+n.xAxis+at,K-=k):(ht=k+st.left+at+(c.zoom?ht:n.xAxis),$t+=st.top-5)}if(ht+t+15>K&&(ht-=t+(S||N||H?0:W?at*2:38)),$t+e>m.height){const st=N?e+10:30;$t-=X?e*1.5:e+st}const dt={top:$t,left:ht};return Object.keys(dt).forEach(st=>{dt[st]<0&&(dt[st]=0)}),dt},showTooltip(t,e){const n=this,{config:a,$el:{tooltip:i}}=n,o=t.filter(c=>c&&De(n.getBaseValue(c)));if(!i||o.length===0||!a.tooltip_show)return;let s=i.datum();const l=JSON.stringify(t);if(!s||s.current!==l){const{index:c,x:f}=t.concat().sort()[0];_e(a.tooltip_onshow,n.api,t),i.html(n.getTooltipHTML(t,n.axis?n.axis.getXAxisTickFormat():n.categoryName.bind(n),n.getDefaultValueFormat(),n.color)).style("display",null).style("visibility",null).datum(s={index:c,x:f,current:l,width:i.property("offsetWidth"),height:i.property("offsetHeight")}),_e(a.tooltip_onshown,n.api,t),n._handleLinkedCharts(!0,c)}n.setTooltipPosition(o,e)},bindTooltipResizePos(){const t=this,{resizeFunction:e,state:n,$el:{tooltip:a}}=t;e.add(()=>{if(a.style("display")==="block"){const{current:i}=n,{width:o,xPosInPercent:s}=a.datum();let l=i.width/100*s;const c=i.width-(l+o);c<0&&(l+=c),a.style("left",`${l}px`)}})},hideTooltip(t){var e;const n=this,{api:a,config:i,$el:{tooltip:o}}=n;if(o&&o.style("display")!=="none"&&(!i.tooltip_doNotHide||t)){const s=JSON.parse((e=o.datum().current)!=null?e:{});_e(i.tooltip_onhide,a,s),o.style("display","none").style("visibility","hidden").datum(null),_e(i.tooltip_onhidden,a,s)}},_handleLinkedCharts(t,e){const n=this,{charts:a,config:i,state:{event:o}}=n;if(o!=null&&o.isTrusted&&i.tooltip_linked&&a.length>1){const s=i.tooltip_linked_name;a.filter(l=>l!==n.api).forEach(l=>{const{config:c,$el:f}=l.internal,g=c.tooltip_linked,v=c.tooltip_linked_name,m=gn.body.contains(f.chart.node());if(g&&s===v&&m){const S=f.tooltip.data()[0],P=e!==(S==null?void 0:S.index);try{l.tooltip[t&&P?"show":"hide"]({index:e})}catch(N){}}})}},updateTooltipOnRedraw(t,e){var n;const a=this,{config:i,$el:{eventRect:o,svg:s,tooltip:l},state:{event:c,hasAxis:f,hasRadar:g,hasTreemap:v}}=a;if((l==null?void 0:l.style("display"))==="block"&&c){const m=t!=null?t:(n=g?s:o)==null?void 0:n.node();if(f||g)if(a.isMultipleX())a.selectRectForMultipleXs(m,!1);else{const S=e!=null?e:a.getDataIndexFromEvent(c);e===-1?a.api.tooltip.hide():(a.selectRectForSingle(m,S),a.setExpand(S,null,!0))}else{const{clientX:S,clientY:P}=c;setTimeout(()=>{let N=[S,P].every(Number.isFinite)&&gn.elementFromPoint(S,P);const L=N&&ot(N).datum();if(L){const w=a.hasArcType()?a.convertToArcData(a.updateAngle(L)):L==null?void 0:L.data;v&&(N=s.node()),w&&a.showTooltip([w],N)}else a.api.tooltip.hide()},i.transition_duration)}}}},Yp={getTranslate(t,e=0){var n;const a=this,{config:i,state:o}=a,s=i.axis_rotated;let l=0,c,f;if(e&&/^(x|y2?)$/.test(t)&&(l=a.getAxisSize(t)*e),t==="main")c=$i(o.margin.left),f=$i(o.margin.top);else if(t==="context")c=$i(o.margin2.left),f=$i(o.margin2.top);else if(t==="legend")c=o.margin3.left,f=o.margin3.top;else if(t==="x")c=s?-l:0,f=s?0:o.height+l;else if(t==="y")c=s?0:-l,f=s?o.height+l:0;else if(t==="y2")c=s?0:o.width+l,f=s?-l-1:0;else if(t==="subX")c=0,f=s?0:o.height2;else if(t==="arc")c=o.arcWidth/2,f=o.arcHeight/2,(n=i.arc_rangeText_values)!=null&&n.length&&(f+=5+(a.hasType("gauge")&&i.title_text?10:0));else if(t==="polar")c=o.arcWidth/2,f=o.arcHeight/2;else if(t==="radar"){const[g,v]=a.getRadarSize();c=o.width/2-g,f=o.height/2-v}return`translate(${c}, ${f})`},transformMain(t,e){const n=this,{$el:{main:a},$T:i}=n,o=e!=null&&e.axisX?e.axisX:i(a.select(`.${Tn.axisX}`),t),s=e!=null&&e.axisY?e.axisY:i(a.select(`.${Tn.axisY}`),t),l=e!=null&&e.axisY2?e.axisY2:i(a.select(`.${Tn.axisY2}`),t);i(a,t).attr("transform",n.getTranslate("main")),o.attr("transform",n.getTranslate("x")),s.attr("transform",n.getTranslate("y")),l.attr("transform",n.getTranslate("y2")),a.select(`.${Ve.chartArcs}`).attr("transform",n.getTranslate("arc"))},transformAll(t,e){const n=this,{config:a,state:{hasAxis:i,hasFunnel:o,hasTreemap:s},$el:l}=n;!o&&!s&&n.transformMain(t,e),i&&a.subchart_show&&n.transformContext(t,e),l.legend&&n.transformLegend(t)}},Wp={isValidChartType(t){return!!(t&&Object.values(oe).indexOf(t)>-1)},setTargetType(t,e){const n=this,{config:a,state:{withoutFadeIn:i}}=n;n.mapToTargetIds(t).forEach(o=>{i[o]=e===a.data_types[o],a.data_types[o]=e}),t||(a.data_type=e)},updateTypesElements(){const t=this,{state:{current:e}}=t;Object.keys(oe).forEach(n=>{const a=oe[n],i=t.hasType(a,null,!0),o=e.types.indexOf(a);o===-1&&i?e.types.push(a):o>-1&&!i&&e.types.splice(o,1)}),t.setChartElements()},hasType(t,e,n=!1){var a;const i=this,{config:o,state:{current:s}}=i,l=o.data_types,c=e||i.data.targets;let f=!1;return!n&&((a=s.types)==null?void 0:a.indexOf(t))>-1?f=!0:c!=null&&c.length?c.forEach(g=>{const v=l[g.id];(v===t||!v&&t==="line")&&(f=!0)}):Object.keys(l).length?Object.keys(l).forEach(g=>{l[g]===t&&(f=!0)}):f=o.data_type===t,f},hasTypeOf(t,e,n=[]){return t in Sr?!Sr[t].filter(a=>n.indexOf(a)===-1).every(a=>!this.hasType(a,e)):!1},isTypeOf(t,e){var n;const a=ze(t)?t:t.id,i=this.config&&(((n=this.config.data_types)==null?void 0:n[a])||this.config.data_type);return je(e)?e.indexOf(i)>=0:i===e},hasPointType(){const t=this;return t.hasTypeOf("Line")||t.hasType("bubble")||t.hasType("scatter")},hasArcType(t,e){return this.hasTypeOf("Arc",t,e)},hasMultiArcGauge(){return this.hasType("gauge")&&this.config.gauge_type==="multi"},isLineType(t){const e=ze(t)?t:t.id;return!this.config.data_types[e]||this.isTypeOf(e,Sr.Line)},isStepType(t){return this.isTypeOf(t,Sr.Step)},isSplineType(t){return this.isTypeOf(t,Sr.Spline)},isAreaType(t){return this.isTypeOf(t,Sr.Area)},isAreaRangeType(t){return this.isTypeOf(t,Sr.AreaRange)},isBarType(t){return this.isTypeOf(t,"bar")},isBubbleType(t){return this.isTypeOf(t,"bubble")},isCandlestickType(t){return this.isTypeOf(t,"candlestick")},isScatterType(t){return this.isTypeOf(t,"scatter")},isTreemapType(t){return this.isTypeOf(t,"treemap")},isPieType(t){return this.isTypeOf(t,"pie")},isFunnelType(t){return this.isTypeOf(t,"funnel")},isGaugeType(t){return this.isTypeOf(t,"gauge")},isDonutType(t){return this.isTypeOf(t,"donut")},isPolarType(t){return this.isTypeOf(t,"polar")},isRadarType(t){return this.isTypeOf(t,"radar")},isArcType(t){return this.isPieType(t)||this.isDonutType(t)||this.isGaugeType(t)||this.isPolarType(t)||this.isRadarType(t)},isCirclePoint(t){const{config:e}=this,n=e.point_pattern;let a=!1;return(t==null?void 0:t.tagName)==="circle"?a=!0:a=e.point_type==="circle"&&(!n||je(n)&&n.length===0),a},lineData(t){return this.isLineType(t)?[t]:[]},arcData(t){return this.isArcType(t.data)?[t]:[]},labelishData(t){return this.isBarType(t)||this.isLineType(t)||this.isScatterType(t)||this.isBubbleType(t)||this.isCandlestickType(t)||this.isFunnelType(t)||this.isRadarType(t)||this.isTreemapType(t)?t.values.filter(e=>he(e.value)||!!e.value):[]},barLineBubbleData(t){return this.isBarType(t)||this.isLineType(t)||this.isBubbleType(t)?t.values:[]},isInterpolationType(t){return["basis","basis-closed","basis-open","bundle","cardinal","cardinal-closed","cardinal-open","catmull-rom","catmull-rom-closed","catmull-rom-open","linear","linear-closed","monotone-x","monotone-y","natural"].indexOf(t)>=0}};function Ni(t,e,n){t._context.bezierCurveTo((2*t._x0+t._x1)/3,(2*t._y0+t._y1)/3,(t._x0+2*t._x1)/3,(t._y0+2*t._y1)/3,(t._x0+4*t._x1+e)/6,(t._y0+4*t._y1+n)/6)}function Fi(t){this._context=t}Fi.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},lineEnd:function(){switch(this._point){case 3:Ni(this,this._x1,this._y1);case 2:this._context.lineTo(this._x1,this._y1);break}(this._line||this._line!==0&&this._point===1)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2;break;case 2:this._point=3,this._context.lineTo((5*this._x0+this._x1)/6,(5*this._y0+this._y1)/6);default:Ni(this,t,e);break}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=e}};function Kp(t){return new Fi(t)}function Ar(){}function Vc(t){this._context=t}Vc.prototype={areaStart:Ar,areaEnd:Ar,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._y0=this._y1=this._y2=this._y3=this._y4=NaN,this._point=0},lineEnd:function(){switch(this._point){case 1:{this._context.moveTo(this._x2,this._y2),this._context.closePath();break}case 2:{this._context.moveTo((this._x2+2*this._x3)/3,(this._y2+2*this._y3)/3),this._context.lineTo((this._x3+2*this._x2)/3,(this._y3+2*this._y2)/3),this._context.closePath();break}case 3:{this.point(this._x2,this._y2),this.point(this._x3,this._y3),this.point(this._x4,this._y4);break}}},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._x2=t,this._y2=e;break;case 1:this._point=2,this._x3=t,this._y3=e;break;case 2:this._point=3,this._x4=t,this._y4=e,this._context.moveTo((this._x0+4*this._x1+t)/6,(this._y0+4*this._y1+e)/6);break;default:Ni(this,t,e);break}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=e}};function Zp(t){return new Vc(t)}function Gc(t){this._context=t}Gc.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},lineEnd:function(){(this._line||this._line!==0&&this._point===3)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3;var n=(this._x0+4*this._x1+t)/6,a=(this._y0+4*this._y1+e)/6;this._line?this._context.lineTo(n,a):this._context.moveTo(n,a);break;case 3:this._point=4;default:Ni(this,t,e);break}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=e}};function Jp(t){return new Gc(t)}function Xc(t,e){this._basis=new Fi(t),this._beta=e}Xc.prototype={lineStart:function(){this._x=[],this._y=[],this._basis.lineStart()},lineEnd:function(){var t=this._x,e=this._y,n=t.length-1;if(n>0)for(var a=t[0],i=e[0],o=t[n]-a,s=e[n]-i,l=-1,c;++l<=n;)c=l/n,this._basis.point(this._beta*t[l]+(1-this._beta)*(a+c*o),this._beta*e[l]+(1-this._beta)*(i+c*s));this._x=this._y=null,this._basis.lineEnd()},point:function(t,e){this._x.push(+t),this._y.push(+e)}};var Qp=function t(e){function n(a){return e===1?new Fi(a):new Xc(a,e)}return n.beta=function(a){return t(+a)},n}(.85);function Bi(t,e,n){t._context.bezierCurveTo(t._x1+t._k*(t._x2-t._x0),t._y1+t._k*(t._y2-t._y0),t._x2+t._k*(t._x1-e),t._y2+t._k*(t._y1-n),t._x2,t._y2)}function ls(t,e){this._context=t,this._k=(1-e)/6}ls.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._point=0},lineEnd:function(){switch(this._point){case 2:this._context.lineTo(this._x2,this._y2);break;case 3:Bi(this,this._x1,this._y1);break}(this._line||this._line!==0&&this._point===1)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2,this._x1=t,this._y1=e;break;case 2:this._point=3;default:Bi(this,t,e);break}this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e}};var kp=function t(e){function n(a){return new ls(a,e)}return n.tension=function(a){return t(+a)},n}(0);function cs(t,e){this._context=t,this._k=(1-e)/6}cs.prototype={areaStart:Ar,areaEnd:Ar,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._x5=this._y0=this._y1=this._y2=this._y3=this._y4=this._y5=NaN,this._point=0},lineEnd:function(){switch(this._point){case 1:{this._context.moveTo(this._x3,this._y3),this._context.closePath();break}case 2:{this._context.lineTo(this._x3,this._y3),this._context.closePath();break}case 3:{this.point(this._x3,this._y3),this.point(this._x4,this._y4),this.point(this._x5,this._y5);break}}},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._x3=t,this._y3=e;break;case 1:this._point=2,this._context.moveTo(this._x4=t,this._y4=e);break;case 2:this._point=3,this._x5=t,this._y5=e;break;default:Bi(this,t,e);break}this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e}};var qp=function t(e){function n(a){return new cs(a,e)}return n.tension=function(a){return t(+a)},n}(0);function us(t,e){this._context=t,this._k=(1-e)/6}us.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._point=0},lineEnd:function(){(this._line||this._line!==0&&this._point===3)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3,this._line?this._context.lineTo(this._x2,this._y2):this._context.moveTo(this._x2,this._y2);break;case 3:this._point=4;default:Bi(this,t,e);break}this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e}};var _p=function t(e){function n(a){return new us(a,e)}return n.tension=function(a){return t(+a)},n}(0);const Hc=Math.abs,En=Math.atan2,jr=Math.cos,tm=Math.max,fs=Math.min,rr=Math.sin,oa=Math.sqrt,bn=1e-12,Fa=Math.PI,Ui=Fa/2,zi=2*Fa;function em(t){return t>1?0:t<-1?Fa:Math.acos(t)}function Yc(t){return t>=1?Ui:t<=-1?-Ui:Math.asin(t)}function ds(t,e,n){var a=t._x1,i=t._y1,o=t._x2,s=t._y2;if(t._l01_a>bn){var l=2*t._l01_2a+3*t._l01_a*t._l12_a+t._l12_2a,c=3*t._l01_a*(t._l01_a+t._l12_a);a=(a*l-t._x0*t._l12_2a+t._x2*t._l01_2a)/c,i=(i*l-t._y0*t._l12_2a+t._y2*t._l01_2a)/c}if(t._l23_a>bn){var f=2*t._l23_2a+3*t._l23_a*t._l12_a+t._l12_2a,g=3*t._l23_a*(t._l23_a+t._l12_a);o=(o*f+t._x1*t._l23_2a-e*t._l12_2a)/g,s=(s*f+t._y1*t._l23_2a-n*t._l12_2a)/g}t._context.bezierCurveTo(a,i,o,s,t._x2,t._y2)}function Wc(t,e){this._context=t,this._alpha=e}Wc.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){switch(this._point){case 2:this._context.lineTo(this._x2,this._y2);break;case 3:this.point(this._x2,this._y2);break}(this._line||this._line!==0&&this._point===1)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){if(t=+t,e=+e,this._point){var n=this._x2-t,a=this._y2-e;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(n*n+a*a,this._alpha))}switch(this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2;break;case 2:this._point=3;default:ds(this,t,e);break}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e}};var nm=function t(e){function n(a){return e?new Wc(a,e):new ls(a,0)}return n.alpha=function(a){return t(+a)},n}(.5);function Kc(t,e){this._context=t,this._alpha=e}Kc.prototype={areaStart:Ar,areaEnd:Ar,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._x5=this._y0=this._y1=this._y2=this._y3=this._y4=this._y5=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){switch(this._point){case 1:{this._context.moveTo(this._x3,this._y3),this._context.closePath();break}case 2:{this._context.lineTo(this._x3,this._y3),this._context.closePath();break}case 3:{this.point(this._x3,this._y3),this.point(this._x4,this._y4),this.point(this._x5,this._y5);break}}},point:function(t,e){if(t=+t,e=+e,this._point){var n=this._x2-t,a=this._y2-e;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(n*n+a*a,this._alpha))}switch(this._point){case 0:this._point=1,this._x3=t,this._y3=e;break;case 1:this._point=2,this._context.moveTo(this._x4=t,this._y4=e);break;case 2:this._point=3,this._x5=t,this._y5=e;break;default:ds(this,t,e);break}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e}};var rm=function t(e){function n(a){return e?new Kc(a,e):new cs(a,0)}return n.alpha=function(a){return t(+a)},n}(.5);function Zc(t,e){this._context=t,this._alpha=e}Zc.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){(this._line||this._line!==0&&this._point===3)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){if(t=+t,e=+e,this._point){var n=this._x2-t,a=this._y2-e;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(n*n+a*a,this._alpha))}switch(this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3,this._line?this._context.lineTo(this._x2,this._y2):this._context.moveTo(this._x2,this._y2);break;case 3:this._point=4;default:ds(this,t,e);break}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e}};var am=function t(e){function n(a){return e?new Zc(a,e):new us(a,0)}return n.alpha=function(a){return t(+a)},n}(.5);function Jc(t){return t<0?-1:1}function Qc(t,e,n){var a=t._x1-t._x0,i=e-t._x1,o=(t._y1-t._y0)/(a||i<0&&-0),s=(n-t._y1)/(i||a<0&&-0),l=(o*i+s*a)/(a+i);return(Jc(o)+Jc(s))*Math.min(Math.abs(o),Math.abs(s),.5*Math.abs(l))||0}function kc(t,e){var n=t._x1-t._x0;return n?(3*(t._y1-t._y0)/n-e)/2:e}function hs(t,e,n){var a=t._x0,i=t._y0,o=t._x1,s=t._y1,l=(o-a)/3;t._context.bezierCurveTo(a+l,i+l*e,o-l,s-l*n,o,s)}function ji(t){this._context=t}ji.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=this._t0=NaN,this._point=0},lineEnd:function(){switch(this._point){case 2:this._context.lineTo(this._x1,this._y1);break;case 3:hs(this,this._t0,kc(this,this._t0));break}(this._line||this._line!==0&&this._point===1)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){var n=NaN;if(t=+t,e=+e,!(t===this._x1&&e===this._y1)){switch(this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2;break;case 2:this._point=3,hs(this,kc(this,n=Qc(this,t,e)),n);break;default:hs(this,this._t0,n=Qc(this,t,e));break}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=e,this._t0=n}}};function qc(t){this._context=new _c(t)}(qc.prototype=Object.create(ji.prototype)).point=function(t,e){ji.prototype.point.call(this,e,t)};function _c(t){this._context=t}_c.prototype={moveTo:function(t,e){this._context.moveTo(e,t)},closePath:function(){this._context.closePath()},lineTo:function(t,e){this._context.lineTo(e,t)},bezierCurveTo:function(t,e,n,a,i,o){this._context.bezierCurveTo(e,t,a,n,o,i)}};function im(t){return new ji(t)}function om(t){return new qc(t)}function tu(t){this._context=t}tu.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x=[],this._y=[]},lineEnd:function(){var t=this._x,e=this._y,n=t.length;if(n)if(this._line?this._context.lineTo(t[0],e[0]):this._context.moveTo(t[0],e[0]),n===2)this._context.lineTo(t[1],e[1]);else for(var a=eu(t),i=eu(e),o=0,s=1;s=0;--e)i[e]=(s[e]-i[e+1])/o[e];for(o[n-1]=(t[n]+i[n-1])/2,e=0;e=0&&(this._t=1-this._t,this._line=1-this._line)},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2;default:{if(this._t<=0)this._context.lineTo(this._x,e),this._context.lineTo(t,e);else{var n=this._x*(1-this._t)+t*this._t;this._context.lineTo(n,this._y),this._context.lineTo(n,e)}break}}this._x=t,this._y=e}};function cm(t){return new Vi(t,.5)}function um(t){return new Vi(t,0)}function fm(t){return new Vi(t,1)}function dm(t){const e=this;let n;return e.isLineType(t)?n=e.generateGetLinePoints(e.getShapeIndices(e.isLineType)):e.isBarType(t)&&(n=e.generateGetBarPoints(e.getShapeIndices(e.isBarType))),n}var hm={getDrawShape(){const t=this,e=t.config.axis_rotated,{hasRadar:n,hasTreemap:a}=t.state,i={type:{},indices:{},pos:{}};if(!a&&["bar","candlestick","line","area"].forEach(o=>{const s=Cn(/^(bubble|scatter)$/.test(o)?"line":o);if(t.hasType(o)||t.hasTypeOf(s)||o==="line"&&(t.hasType("bubble")||t.hasType("scatter"))){const l=t.getShapeIndices(t[`is${s}Type`]),c=t[`generateDraw${s}`];i.indices[o]=l,i.type[o]=c?c.bind(t)(l,!1):void 0}}),!t.hasArcType()||n||a){let o,s;a||(o=n?t.radarCircleX:e?t.circleY:t.circleX,s=n?t.radarCircleY:e?t.circleX:t.circleY),i.pos={xForText:t.generateXYForText(i.indices,!0),yForText:t.generateXYForText(i.indices,!1),cx:(o||function(){}).bind(t),cy:(s||function(){}).bind(t)}}return i},getShapeIndices(t){const e=this,{config:n}=e,a=n.data_xs,i=cn(a),o={};let s=i?{}:0;return i&&Mo(Object.keys(a).map(l=>a[l])).forEach(l=>{s[l]=0,o[l]={}}),e.filterTargetsToShow(e.data.targets.filter(t,e)).forEach(l=>{var c;const f=l.id in a?a[l.id]:"",g=f?o[f]:o;for(let v=0,m;m=n.data_groups[v];v++)if(!(m.indexOf(l.id)<0))for(let S=0,P;P=m[S];S++){if(P in g){g[l.id]=g[P];break}l.id!==P&&f&&(g[P]=(c=g[l.id])!=null?c:s[f])}ln(g[l.id])&&(g[l.id]=f?s[f]++:s++,g.__max__=(f?s[f]:s)-1)}),o},getIndices(t,e,n){const a=this,{data_xs:i,bar_indices_removeNull:o}=a.config,{id:s,index:l}=e;if(a.isBarType(s)&&o){const c={};return a.getAllValuesOnIndex(l,!0).forEach((f,g)=>{c[f.id]=g,c.__max__=g}),c}return cn(i)?t[i[s]]:t},getIndicesMax(t){return cn(this.config.data_xs)?Object.keys(t).map(e=>t[e].__max__||0).reduce((e,n)=>e+n):t.__max__},getShapeX(t,e,n){const a=this,{config:i,scale:o}=a,s=n?o.subX:o.zoom||o.x,l=i.bar_overlap,c=i.bar_padding,f=(v,m)=>v+m,g=nr(t)&&(t._$total.length?t._$total.reduce(f)/2:0);return v=>{const m=a.getIndices(e,v,"getShapeX"),S=v.id in m?m[v.id]:0,P=(m.__max__||0)+1;let N=0;if(cn(v.x)){const L=s(v.x,!0);if(g){const w=t[v.id]||t._$width;N=l?L-w/2:L-w+t._$total.slice(0,S+1).reduce(f)-g}else N=L-(he(t)?t:t._$width)*(P/2-(l?1:S))}return t&&N&&P>1&&c&&(S&&(N+=c*S),P>2?N-=(P-1)*c/2:P===2&&(N-=c/2)),N}},getShapeY(t){const e=this,n=e.isStackNormalized();return a=>{let{value:i}=a;return he(a)?i=a:e.isAreaRangeType(a)?i=e.getBaseValue(a,"mid"):n?i=e.getRatio("index",a,!0):e.isBubbleZType(a)?i=e.getBubbleZData(a.value,"y"):e.isBarRangeType(a)&&(i=i[1]),e.getYScaleById(a.id,t)(i)}},getShapeYMin(t){const e=this,n=e.axis.getId(t),a=e.scale[n],[i]=a.domain(),o=e.config[`axis_${n}_inverted`];return!e.isGrouped(t)&&!o&&i>0?i:0},getShapeOffsetData(t){const e=this,n=e.orderTargets(e.filterTargetsToShow(e.data.targets.filter(t,e))),a=e.isStackNormalized(),i=n.map(s=>{let l=s.values;const c={};e.isStepType(s)&&(l=e.convertValuesToStep(l));const f=l.reduce((g,v)=>{const m=Number(v.x);return g[m]=v,c[m]=a?e.getRatio("index",v,!0):v.value,g},{});return{id:s.id,rowValues:l,rowValueMapByXValue:f,values:c}});return{indexMapByTargetId:n.reduce((s,{id:l},c)=>(s[l]=c,s),{}),shapeOffsetTargets:i}},getShapeOffset(t,e,n){const a=this,{shapeOffsetTargets:i,indexMapByTargetId:o}=a.getShapeOffsetData(t),s=a.config.data_groupsZeroAs;return(l,c)=>{const{id:f,value:g,x:v}=l,m=a.getIndices(e,l),S=a.getYScaleById(f,n);if(a.isBarRangeType(l))return S(g[0]);const P=Number(v),N=S(s==="zero"?0:a.getShapeYMin(f));let L=N;return i.filter(w=>w.id!==f&&m[w.id]===m[f]).forEach(w=>{const{id:X,rowValueMapByXValue:W,rowValues:H,values:k}=w;if(o[X]=0&&he(K)&&(g!==0||s==="positive"&&K>0||s==="negative"&&K<0)&&(L+=S(K)-N)}}),L}},circleY(t,e){const n=this,a=t.id;let i;return n.isGrouped(a)&&(i=dm.bind(n)(t)),i?i(t,e)[0][1]:n.getYScaleById(a)(n.getBaseValue(t))},getBarW(t,e,n){var a,i,o,s,l;const c=this,{config:f,org:g,scale:v,state:m}=c,S=c.getMaxDataCount(),P=t==="bar"&&((a=f.data_groups)==null?void 0:a.length),N=`${t}_width`,{k:L}=(o=(i=c.getZoomTransform)==null?void 0:i.call(c))!=null?o:{k:1},w=[(s=f.axis_x_min)!=null?s:g.xDomain[0],(l=f.axis_x_max)!=null?l:g.xDomain[1]].map(c.axis.isTimeSeries()?Yn.bind(c):Number);let X=e.tickInterval(S);if(v.zoom&&!c.axis.isCategorized()&&L>1){const k=w.every((K,at)=>K===g.xDomain[at]);X=g.xDomain.map((K,at)=>{const ht=k?K:K-Math.abs(w[at]);return v.zoom(ht)}).reduce((K,at)=>Math.abs(K)+at)/S}const W=k=>{const K=k?f[N][k]:f[N],at=k?K.ratio:f[`${N}_ratio`],ht=k?K.max:f[`${N}_max`],$t=he(K)?K:ve(K)?K.call(c,m.width,n,S):n?X*at/n:0;return ht&&$t>ht?ht:$t};let H=W();return!P&&nr(f[N])&&(H={_$width:H,_$total:[]},c.filterTargetsToShow(c.data.targets).forEach(k=>{f[N][k.id]&&(H[k.id]=W(k.id),H._$total.push(H[k.id]||H._$width))})),H},getShapeByIndex(t,e,n){const a=this,{$el:i}=a,o=De(e)?`-${e}`:"";let s=i[t];return s&&!s.empty()?s=s.filter(l=>n?l.id===n:!0).filter(l=>De(e)?l.index===e:!0):s=(n?i.main.selectAll(`.${Ue[`${t}s`]}${a.getTargetSelectorSuffix(n)}`):i.main).selectAll(`.${Ue[t]}${o}`),s},isWithinShape(t,e){var n;const a=this,i=ot(t);let o;return a.isTargetToShow(e.id)?(n=a.hasValidPointType)!=null&&n.call(a,t.nodeName)?o=a.isStepType(e)?a.isWithinStep(t,a.getYScaleById(e.id)(a.getBaseValue(e))):a.isWithinCircle(t,a.isBubbleType(e)?a.pointSelectR(e)*1.5:0):t.nodeName==="path"&&(o=i.classed(Ue.bar)?a.isWithinBar(t):!0):o=!1,o},getInterpolate(t){const n=this.getInterpolateType(t);return{basis:Kp,"basis-closed":Zp,"basis-open":Jp,bundle:Qp,cardinal:kp,"cardinal-closed":qp,"cardinal-open":_p,"catmull-rom":nm,"catmull-rom-closed":rm,"catmull-rom-open":am,"monotone-x":im,"monotone-y":om,natural:sm,"linear-closed":lm,linear:gs,step:cm,"step-after":fm,"step-before":um}[n]},getInterpolateType(t){const e=this,{config:n}=e,a=n.spline_interpolation_type,i=e.isInterpolationType(a)?a:"cardinal";return e.isSplineType(t)?i:e.isStepType(t)?n.line_step_type:"linear"},isWithinBar(t){const e=Hn(this.state.event,t),n=Hl(t),[a,i]=n,o=Math.min(a.x,i.x),s=Math.min(a.y,i.y),l=this.config.bar_sensitivity,{width:c,height:f}=t.getBBox(),g=o-l,v=o+c+l,m=s+f+l,S=s-l;return ge in t?gm(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n,Rn=(t,e,n)=>vm(t,typeof e!="symbol"?e+"":e,n);class Vr{constructor(e){Rn(this,"api"),Rn(this,"config"),Rn(this,"cache"),Rn(this,"$el"),Rn(this,"state"),Rn(this,"charts"),Rn(this,"data",{xs:{},targets:[]}),Rn(this,"axis"),Rn(this,"scale",{x:null,y:null,y2:null,subX:null,subY:null,subY2:null,zoom:null}),Rn(this,"org",{xScale:null,xDomain:null}),Rn(this,"color"),Rn(this,"patterns"),Rn(this,"levelColor"),Rn(this,"point"),Rn(this,"brush"),Rn(this,"format",{extraLineClasses:null,xAxisTick:null,dataTime:null,defaultAxisTime:null,axisTime:null});const n=this;n.api=e,n.config=new Nr,n.cache=new gv;const a=new uv;n.$el=a.getStore("element"),n.state=a.getStore("state"),n.$T=n.$T.bind(n)}$T(e,n,a){const{config:i,state:o}=this,s=i.transition_duration,l=i.subchart_show;let c=e;return c&&("tagName"in c&&(c=ot(c)),c=(n!==!1&&s||n)&&(!o.zooming||o.dragging)&&!o.resizing&&o.rendered&&!l?c.transition(a).duration(s):c),c}beforeInit(){const e=this;e.callPluginHook("$beforeInit"),_e(e.config.onbeforeinit,e.api)}afterInit(){const e=this;e.callPluginHook("$afterInit"),_e(e.config.onafterinit,e.api)}init(){const e=this,{config:n,state:a,$el:i}=e,o=n.boost_useCssRule;if(vv(e),a.hasRadar=!a.hasAxis&&e.hasType("radar"),a.hasFunnel=!a.hasAxis&&e.hasType("funnel"),a.hasTreemap=!a.hasAxis&&e.hasType("treemap"),a.hasAxis=!e.hasArcType()&&!a.hasFunnel&&!a.hasTreemap,a.datetimeId=`bb-${+new Date*gr()}`,o){const l=gn.createElement("style");l.type="text/css",gn.head.appendChild(l),a.style={rootSelctor:`.${a.datetimeId}`,sheet:l.sheet},i.style=l}const s={element:n.bindto,classname:"bb"};Be(n.bindto)&&(s.element=n.bindto.element||"#chart",s.classname=n.bindto.classname||s.classname),i.chart=ve(s.element.node)?n.bindto.element:ot(s.element||[]),i.chart.empty()&&(i.chart=ot(gn.body.appendChild(gn.createElement("div")))),i.chart.html("").classed(s.classname,!0).classed(a.datetimeId,o).style("position","relative"),e.initParams(),e.initToRender()}initToRender(e){const n=this,{config:a,state:i,$el:{chart:o}}=n,s=()=>nv(o,{display:"none",visibility:"hidden"}),l=a.render.lazy===!1?!1:a.render.lazy||s(),c=Ke.MutationObserver;l&&c&&a.render.observe!==!1&&!e&&new c((f,g)=>{s()||(g.disconnect(),!i.rendered&&n.initToRender(!0))}).observe(o.node(),{attributes:!0,attributeFilter:["class","style"]}),(!l||e)&&n.convertData(a,f=>{n.initWithData(f),n.afterInit()})}initParams(){var e;const n=this,{config:a,format:i,state:o}=n,s=a.axis_rotated;if(n.color=n.generateColor(),n.levelColor=n.generateLevelColor(),a.padding===!1&&(a.axis_x_show=!1,a.axis_y_show=!1,a.axis_y2_show=!1,a.subchart_show=!1),(n.hasPointType()||(e=n.hasLegendDefsPoint)!=null&&e.call(n))&&(n.point=n.generatePoint()),o.hasAxis){n.initClip(),i.extraLineClasses=n.generateExtraLineClass(),i.dataTime=a.data_xLocaltime?Ws:Ks,i.axisTime=a.axis_x_localtime?ao:io;const l=n.config.zoom_enabled&&n.config.zoom_type==="drag";i.defaultAxisTime=c=>{const{x:f,zoom:g}=n.scale,v=l?g:g&&f.orgDomain().toString()!==g.domain().toString(),m=c.getMilliseconds()&&".%L"||c.getSeconds()&&".:%S"||c.getMinutes()&&"%I:%M"||c.getHours()&&"%I %p"||c.getDate()!==1&&"%b %d"||v&&c.getDate()===1&&"%b'%y"||c.getMonth()&&"%-m/%-d"||"%Y";return i.axisTime(m)(c)}}o.isLegendRight=a.legend_position==="right",o.isLegendInset=a.legend_position==="inset",o.isLegendTop=a.legend_inset_anchor==="top-left"||a.legend_inset_anchor==="top-right",o.isLegendLeft=a.legend_inset_anchor==="top-left"||a.legend_inset_anchor==="bottom-left",o.rotatedPadding.top=n.getResettedPadding(o.rotatedPadding.top),o.rotatedPadding.right=s&&!a.axis_x_show?0:30,o.inputType=rv(a.interaction_inputType_mouse,a.interaction_inputType_touch)}initWithData(e){var n,a,i;const o=this,{config:s,scale:l,state:c,$el:f,org:g}=o,{hasAxis:v,hasFunnel:m,hasTreemap:S}=c,P=s.interaction_enabled,N=o.hasType("polar"),L=s.data_labels_backgroundColors;if(v&&(o.axis=o.getAxisInstance(),s.zoom_enabled&&o.initZoom()),o.data.xs={},o.data.targets=o.convertDataToTargets(e),s.data_filter&&(o.data.targets=o.data.targets.filter(s.data_filter.bind(o.api))),s.data_hide&&o.addHiddenTargetIds(s.data_hide===!0?o.mapToIds(o.data.targets):s.data_hide),s.legend_hide&&o.addHiddenLegendIds(s.legend_hide===!0?o.mapToIds(o.data.targets):s.legend_hide),o.updateSizes(),o.updateScales(!0),v){const{x:W,y:H,y2:k,subX:K,subY:at,subY2:ht}=l;W&&(W.domain(na(o.getXDomain(o.data.targets),!s.axis_x_inverted)),K.domain(W.domain()),g.xDomain=W.domain()),H&&(H.domain(o.getYDomain(o.data.targets,"y")),at.domain(H.domain())),k&&(k.domain(o.getYDomain(o.data.targets,"y2")),ht&&ht.domain(k.domain()))}if(f.svg=f.chart.append("svg").style("overflow","hidden").style("display","block"),P&&c.inputType){const W=c.inputType==="touch",{onclick:H,onover:k,onout:K}=s;f.svg.on("click",(H==null?void 0:H.bind(o.api))||null).on(W?"touchstart":"mouseenter",(k==null?void 0:k.bind(o.api))||null).on(W?"touchend":"mouseleave",(K==null?void 0:K.bind(o.api))||null)}s.svg_classname&&f.svg.attr("class",s.svg_classname);const w=ve(s.color_tiles)&&o.patterns;(v||w||N||S||L||(n=o.hasLegendDefsPoint)!=null&&n.call(o))&&(f.defs=f.svg.append("defs"),v&&["id","idXAxis","idYAxis","idGrid"].forEach(W=>{o.appendClip(f.defs,c.clip[W])}),o.generateTextBGColorFilter(L),w&&o.patterns.forEach(W=>f.defs.append(()=>W.node))),o.updateSvgSize(),o.bindResize();const X=f.svg.append("g").classed(Se.main,!0).attr("transform",m||S?null:o.getTranslate("main"));if(f.main=X,s.subchart_show&&o.initSubchart(),s.tooltip_show&&o.initTooltip(),s.title_text&&o.initTitle(),!S&&s.legend_show&&o.initLegend(),s.data_empty_label_text&&X.append("text").attr("class",`${On.text} ${Se.empty}`).attr("text-anchor","middle").attr("dominant-baseline","middle"),v&&(s.regions.length&&o.initRegion(),!s.clipPath&&o.axis.init()),X.append("g").classed(Se.chart,!0).attr("clip-path",v?c.clip.path:null),o.callPluginHook("$init"),o.initChartElements(),v&&(P&&((a=o.initEventRect)==null||a.call(o)),o.initGrid(),s.clipPath&&((i=o.axis)==null||i.init())),o.updateTargets(o.data.targets),o.updateDimension(),_e(s.oninit,o.api),o.setBackground(),o.redraw({withTransition:!1,withTransform:!0,withUpdateXDomain:!0,withUpdateOrgXDomain:!0,withTransitionForAxis:!1,initializing:!0}),s.data_onmin||s.data_onmax){const W=o.getMinMaxData();_e(s.data_onmin,o.api,W.min),_e(s.data_onmax,o.api,W.max)}s.tooltip_show&&o.initShowTooltip(),c.rendered=!0}initChartElements(){const e=this,{hasAxis:n,hasRadar:a,hasTreemap:i}=e.state,o=[];if(n){const s=["bar","bubble","candlestick","line"];e.config.bar_front&&s.push(s.shift()),s.forEach(l=>{const c=Cn(l);(l==="line"&&e.hasTypeOf(c)||e.hasType(l))&&o.push(c)})}else if(i)o.push("Treemap");else if(e.hasType("funnel"))o.push("Funnel");else{const s=e.hasType("polar");a||o.push("Arc","Pie"),e.hasType("gauge")?o.push("Gauge"):a?o.push("Radar"):s&&o.push("Polar")}o.forEach(s=>{e[`init${s}`]()}),cn(e.config.data_labels)&&!e.hasArcType(null,["radar"])&&e.initText()}setChartElements(){const e=this,{$el:{chart:n,svg:a,defs:i,main:o,tooltip:s,legend:l,title:c,grid:f,needle:g,arcs:v,circle:m,bar:S,candlestick:P,line:N,area:L,text:w}}=e;e.api.$={chart:n,svg:a,defs:i,main:o,tooltip:s,legend:l,title:c,grid:f,arc:v,circles:m,bar:{bars:S},candlestick:P,line:{lines:N,areas:L},needle:g,text:{texts:w}}}setBackground(){const e=this,{config:{background:n},state:a,$el:{svg:i}}=e;if(cn(n)){const o=i.select("g").insert(n.imgUrl?"image":"rect",":first-child");n.imgUrl?o.attr("href",n.imgUrl):n.color&&o.style("fill",n.color).attr("clip-path",a.clip.path),o.attr("class",n.class||null).attr("width","100%").attr("height","100%")}}updateTargets(e){var n;const a=this,{hasAxis:i,hasFunnel:o,hasRadar:s,hasTreemap:l}=a.state,c=g=>a[`updateTargetsFor${g}`](e.filter(a[`is${g}Type`].bind(a)));if(a.updateTargetsForText(e),i)["bar","candlestick","line"].forEach(g=>{const v=Cn(g);(g==="line"&&a.hasTypeOf(v)||a.hasType(g))&&c(v)}),a.updateTargetsForSubchart&&a.updateTargetsForSubchart(e);else if(a.hasArcType(e)){let g="Arc";s?g="Radar":a.hasType("polar")&&(g="Polar"),c(g)}else o?c("Funnel"):l&&c("Treemap");const f=a.hasType("bubble")||a.hasType("scatter");f&&((n=a.updateTargetForCircle)==null||n.call(a)),a.filterTargetsToShowAtInit(f)}filterTargetsToShowAtInit(e=!1){const n=this,{$el:{svg:a},$T:i}=n;let o=`.${Se.target}`;e&&(o+=`, .${$n.chartCircles} > .${$n.circles}`),i(a.selectAll(o).filter(s=>n.isTargetToShow(s.id))).style("opacity",null)}getWithOption(e){const n={Dimension:!0,EventRect:!0,Legend:!1,Subchart:!0,Transform:!1,Transition:!0,TrimXDomain:!0,UpdateXAxis:"UpdateXDomain",UpdateXDomain:!1,UpdateOrgXDomain:!1,TransitionForExit:"Transition",TransitionForAxis:"Transition",Y:!0};return Object.keys(n).forEach(a=>{let i=n[a];ze(i)&&(i=n[i]),n[a]=$r(e,`with${a}`,i)}),n}initialOpacity(e){const n=this,{withoutFadeIn:a}=n.state;return n.getBaseValue(e)!==null&&a[e.id]?null:"0"}bindResize(){const e=this,{$el:n,config:a,state:i}=e,o=xv(a.resize_timer),s=[];s.push(()=>_e(a.onresize,e.api)),/^(true|parent)$/.test(a.resize_auto)&&s.push(()=>{i.resizing=!0,a.legend_show&&(e.updateSizes(),e.updateLegend()),e.api.flush(!1)}),s.push(()=>{_e(a.onresized,e.api),i.resizing=!1}),s.forEach(l=>o.add(l)),e.resizeFunction=o,a.resize_auto==="parent"?(e.resizeFunction.resizeObserver=new ResizeObserver(e.resizeFunction.bind(e))).observe(n.chart.node().parentNode):Ke.addEventListener("resize",e.resizeFunction)}callPluginHook(e,...n){this.config.plugins.forEach(a=>{e==="$beforeInit"&&(a.$$=this,this.api.plugins.push(a)),a[e](...n)})}}yn(Vr.prototype,[Mv,Dv,Lv,jv,Vv,Yv,Wv,zv,Kv,Zv,Jv,Bp,hm,Up,zp,Gp,Xp,Hp,Yp,Wp]);function pm(t){const e=this.config;let n,a,i;const o=()=>{const s=a.shift();if(s&&n&&nr(n)&&s in n)return n=n[s],o();if(!s)return n};Object.keys(e).forEach(s=>{n=t,a=s.split("_"),i=o(),Qe(i)&&(e[s]=i)}),this.api&&(this.state.orgConfig=t)}var mm={resize(t){const e=this.internal,{config:n,state:a}=e;a.rendered&&(n.size_width=t?t.width:null,n.size_height=t?t.height:null,a.resizing=!0,this.flush(!1),e.resizeFunction())},flush(t){var e,n;const a=this.internal,{state:i,$el:{zoomResetBtn:o}}=a;i.rendered?(i.resizing?(e=a.brush)==null||e.updateResize():(n=a.axis)==null||n.setOrient(),o==null||o.style("display","none"),a.scale.zoom=null,t?a.redraw({withTransform:!0,withUpdateXDomain:!0,withUpdateOrgXDomain:!0,withLegend:!0}):a.updateAndRedraw({withLegend:!0,withTransition:!1,withTransitionForTransform:!1}),!i.resizing&&a.brush&&(a.brush.getSelection().call(a.brush.move),a.unselectRect())):a.initToRender(!0)},destroy(){var t;const e=this.internal,{$el:{chart:n,style:a,svg:i}}=e;if(cn(e)){e.callPluginHook("$willDestroy"),e.charts.splice(e.charts.indexOf(this),1),e.unbindAllEvents(),i.select("*").interrupt(),e.resizeFunction.clear(),(t=e.resizeFunction.resizeObserver)==null||t.disconnect(),Ke.removeEventListener("resize",e.resizeFunction),n.classed("bb",!1).style("position",null).selectChildren().remove(),a&&a.parentNode.removeChild(a),Object.keys(this).forEach(o=>{o==="internal"&&Object.keys(e).forEach(s=>{e[s]=null}),this[o]=null,delete this[o]});for(const o in this)this[o]=()=>{}}return null},config(t,e,n){const a=this.internal,{config:i,state:o}=a,s=t==null?void 0:t.replace(/\./g,"_");let l;return t&&s in i?Qe(e)?(i[s]=e,l=e,n&&this.flush()):l=i[s]:(arguments.length===0||qn(t))&&(l=o.orgConfig),l}},ym={color(t){return this.internal.color(t)}};const au=function(t){const{targets:e}=this.internal.data;if(!ln(t)){const n=je(t)?t:[t];return e.filter(a=>n.some(i=>i===a.id))}return e};yn(au,{shown:function(t){return this.internal.filterTargetsToShow(this.data(t))},values:function(t,e=!0){let n=null;if(t){const a=this.data(t);je(a)&&(n=[],a.forEach(i=>{const o=i.values.map(s=>s.value);e?n=n.concat(o):n.push(o)}))}return n},names:function(t){return this.internal.updateDataAttributes("names",t)},colors:function(t){return this.internal.updateDataAttributes("colors",t)},axes:function(t){return this.internal.updateDataAttributes("axes",t)},min:function(){return this.internal.getMinMaxData().min},max:function(){return this.internal.getMinMaxData().max}});var xm={data:au};const Tm=t=>{var e,n;return(n=(e=Ke).btoa)==null?void 0:n.call(e,encodeURIComponent(t).replace(/%([0-9A-F]{2})/g,(a,i)=>String.fromCharCode(+`0x${i}`)))};function $m(t,e,n){const{width:a,height:i}=e||n,o=new XMLSerializer,s=t.cloneNode(!0),l=tv(Lr(gn.styleSheets)).filter(m=>m.cssText).map(m=>m.cssText);s.setAttribute("xmlns",ae.xhtml),s.style.margin="0",s.style.padding="0",e.preserveFontStyle&&s.querySelectorAll("text").forEach(m=>{m.innerHTML=""});const c=o.serializeToString(s),f=gn.createElement("style");f.appendChild(gn.createTextNode(l.join(` +`)));const g=o.serializeToString(f),v=` + + ${g} + ${c.replace(/(url\()[^#]+/g,"$1")} + `;return`data:image/svg+xml;base64,${Tm(v)}`}function Sm(t,e){const{top:n,left:a}=e,{x:i,y:o}=t.getBBox(),{a:s,b:l,c,d:f,e:g,f:v}=t.getScreenCTM(),{width:m,height:S}=t.getBoundingClientRect();return{x:s*i+c*o+g-a,y:l*i+f*o+v-n+(S-Math.round(S/4)),width:m,height:S}}function Am(t){const{left:e,top:n}=t.getBoundingClientRect(),a=o=>o.textContent||o.childElementCount,i=[];return Lr(t.querySelectorAll("text")).filter(a).forEach(o=>{const s=l=>{const{fill:c,fontFamily:f,fontSize:g,textAnchor:v,transform:m}=Ke.getComputedStyle(l),{x:S,y:P,width:N,height:L}=Sm(l,{left:e,top:n});return{[l.textContent]:{x:S,y:P,width:N,height:L,fill:c,fontFamily:f,fontSize:g,textAnchor:v,transform:m}}};if(o.childElementCount>1){const l=[];return Lr(o.querySelectorAll("tspan")).filter(a).forEach(c=>{i.push(s(c))}),l}else i.push(s(o))}),i}function Em(t,e){e.forEach(n=>{Object.keys(n).forEach(a=>{const{x:i,y:o,width:s,height:l,fill:c,fontFamily:f,fontSize:g,transform:v}=n[a];if(t.save(),t.font=`${g} ${f}`,t.fillStyle=c,v==="none")t.fillText(a,i,o);else{const m=v.replace(/(matrix|\(|\))/g,"").split(",");m.splice(4).every(S=>+S==0)?(m.push(i+s-s/4),m.push(o-l+l/3)):(m.push(i),m.push(o)),t.transform(...m),t.fillText(a,0,0)}t.restore()})})}var bm={export(t,e){const n=this.internal,{state:a,$el:{chart:i,svg:o}}=n,{width:s,height:l}=a.current,c=ea(Object.create(null),{width:s,height:l,preserveAspectRatio:!0,preserveFontStyle:!1,mimeType:"image/png"},t),f=$m(i.node(),c,{width:s,height:l}),g=c.preserveFontStyle?Am(o.node()):[];if(e&&ve(e)){const v=new Image;v.crossOrigin="Anonymous",v.onload=()=>{const m=gn.createElement("canvas"),S=m.getContext("2d");m.width=c.width||s,m.height=c.height||l,S.drawImage(v,0,0),g.length&&(Em(S,g),g.length=0),e.bind(this)(m.toDataURL(c.mimeType))},v.src=f}return f}},Rm={focus(t){const e=this.internal,{state:n}=e,a=e.mapToTargetIds(t),i=e.$el.svg.selectAll(e.selectorTargets(a.filter(e.isTargetToShow,e)));this.revert(),this.defocus(),i.classed(qe.focused,!0).classed(qe.defocused,!1),e.hasArcType()&&!n.hasRadar&&(e.expandArc(a),e.hasType("gauge")&&e.markOverlapped(t,e,`.${Un.gaugeValue}`)),e.toggleFocusLegend(a,!0),n.focusedTargetIds=a,n.defocusedTargetIds=n.defocusedTargetIds.filter(o=>a.indexOf(o)<0)},defocus(t){const e=this.internal,{state:n}=e,a=e.mapToTargetIds(t);e.$el.svg.selectAll(e.selectorTargets(a.filter(e.isTargetToShow,e))).classed(qe.focused,!1).classed(qe.defocused,!0),e.hasArcType(null,["polar"])&&(e.unexpandArc(a),e.hasType("gauge")&&e.undoMarkOverlapped(e,`.${Un.gaugeValue}`)),e.toggleFocusLegend(a,!1),n.focusedTargetIds=n.focusedTargetIds.filter(o=>a.indexOf(o)<0),n.defocusedTargetIds=a},revert(t){const e=this.internal,{config:n,state:a,$el:i}=e,o=e.mapToTargetIds(t);i.svg.selectAll(e.selectorTargets(o)).classed(qe.focused,!1).classed(qe.defocused,!1),e.hasArcType(null,["polar"])&&e.unexpandArc(o),n.legend_show&&(e.showLegend(o.filter(e.isLegendToShow.bind(e))),i.legend.selectAll(e.selectorLegends(o)).filter(function(){return ot(this).classed(qe.legendItemFocused)}).classed(qe.legendItemFocused,!1)),a.focusedTargetIds=[],a.defocusedTargetIds=[]}},Im={legend:{show:function(t){const e=this.internal;e.showLegend(e.mapToTargetIds(t)),e.updateAndRedraw({withLegend:!0})},hide:function(t){const e=this.internal;e.hideLegend(e.mapToTargetIds(t)),e.updateAndRedraw({withLegend:!0})}}},Om={load(t){const e=this.internal,{config:n}=e;t.xs&&e.addXs(t.xs),"names"in t&&this.data.names(t.names),"classes"in t&&Object.keys(t.classes).forEach(a=>{n.data_classes[a]=t.classes[a]}),"categories"in t&&e.axis.isCategorized()&&(n.axis_x_categories=t.categories),"axes"in t&&Object.keys(t.axes).forEach(a=>{n.data_axes[a]=t.axes[a]}),"colors"in t&&Object.keys(t.colors).forEach(a=>{n.data_colors[a]=t.colors[a]}),"unload"in t&&t.unload!==!1?e.unload(e.mapToTargetIds(t.unload===!0?null:t.unload),()=>{jl(()=>e.loadFromArgs(t))}):e.loadFromArgs(t)},unload(t){const e=this.internal;let n=t||{};qn(n)&&this.tooltip.hide(),je(n)?n={ids:n}:ze(n)&&(n={ids:[n]});const a=e.mapToTargetIds(n.ids);e.unload(a,()=>{e.redraw({withUpdateOrgXDomain:!0,withUpdateXDomain:!0,withLegend:!0}),e.cache.remove(a),cc.call(e,n.done,n.resizeAfter)})}};function iu(t,e,n){const a=this.internal,i=a.mapToTargetIds(e),o=a.state.hiddenTargetIds.map(c=>i.indexOf(c)>-1&&c).filter(Boolean);a.state.toggling=!0,a[`${t?"remove":"add"}HiddenTargetIds`](i);const s=a.$el.svg.selectAll(a.selectorTargets(i)),l=t?null:"0";t&&o.length&&(s.style("display",null),_e(a.config.data_onshown,this,o)),a.$T(s).style("opacity",l,"important").call(Si,()=>{var c;!t&&o.length===0&&(s.style("display","none"),_e((c=a.config)==null?void 0:c.data_onhidden,this,i)),s.style("opacity",l)}),n.withLegend&&a[`${t?"show":"hide"}Legend`](i),a.redraw({withUpdateOrgXDomain:!0,withUpdateXDomain:!0,withLegend:!0}),a.state.toggling=!1}var Cm={show(t,e={}){iu.call(this,!0,t,e)},hide(t,e={}){iu.call(this,!1,t,e)},toggle(t,e={}){const n=this.internal,a={show:[],hide:[]};n.mapToTargetIds(t).forEach(i=>a[n.isTargetToShow(i)?"hide":"show"].push(i)),a.show.length&&this.show(a.show,e),a.hide.length&&setTimeout(()=>this.hide(a.hide,e),0)}},Pm={tooltip:{show:function(t){var e,n,a;const i=this.internal,{$el:o,config:s,state:{eventReceiver:l,hasFunnel:c,hasTreemap:f,inputType:g}}=i;let v,m;if(t.mouse&&(m=t.mouse),t.data){const{data:S}=t,P=(e=i.getYScaleById(S.id))==null?void 0:e(S.value);if((c||f)&&S.id){const N=i.selectorTarget(S.id,void 0,`.${sn.shape}`);l.rect=o.main.select(N)}else i.isMultipleX()?m=[i.xx(S),P]:(s.tooltip_grouped||(m=[0,P]),v=(a=S.index)!=null?a:i.hasArcType()&&S.id?(n=i.getArcElementByIdOrIndex(S.id))==null?void 0:n.datum().index:i.getIndexByX(S.x))}else Qe(t.x)?v=i.getIndexByX(t.x):Qe(t.index)&&(v=t.index);(g==="mouse"?["mouseover","mousemove"]:["touchstart"]).forEach(S=>{i.dispatchEvent(S,v,m)})},hide:function(){var t,e,n;const a=this.internal,{state:{inputType:i},$el:{tooltip:o}}=a,s=o==null?void 0:o.datum();if(s){const{index:l}=JSON.parse(s.current)[0];(i==="mouse"?["mouseout"]:["touchend"]).forEach(c=>{a.dispatchEvent(c,l)})}i==="touch"&&a.callOverOutForTouch(),a.hideTooltip(!0),(t=a.hideGridFocus)==null||t.call(a),(e=a.unexpandCircles)==null||e.call(a),(n=a.expandBarTypeShapes)==null||n.call(a,!1)}}},wm=Object.defineProperty,Mm=(t,e,n)=>e in t?wm(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n,ou=(t,e,n)=>Mm(t,typeof e!="symbol"?e+"":e,n);class Er{constructor(e){ou(this,"plugins",[]),ou(this,"internal");const n=new Vr(this);this.internal=n,function a(i,o,s){Object.keys(i).forEach(l=>{const c=ve(i[l]),f=o!==s,g=cn(i[l]),v=g&&Object.keys(i[l]).length>0;c&&(!f&&v||f)?o[l]=i[l].bind(s):g&&!c?o[l]={}:o[l]=i[l],v&&a(i[l],o[l],s)})}(Er.prototype,this,this),pm.call(n,e),n.beforeInit(),n.init()}}yn(Er.prototype,[mm,ym,xm,bm,Rm,Im,Om,Cm,Pm]);function su(t=!1,e,n,a){const i=this,{config:o,$el:{main:s}}=i,l=o.data_selection_grouped,c=o.data_selection_isselectable.bind(i.api);o.data_selection_enabled&&s.selectAll(`.${sn.shapes}`).selectAll(`.${sn.shape}`).each(function(f){const g=ot(this),{id:v,index:m}=f.data?f.data:f,S=i.getToggle(this,f).bind(i),P=l||!e||e.indexOf(v)>=0,N=!n||n.indexOf(m)>=0,L=g.classed(tn.SELECTED);g.classed(ur.line)||g.classed(ti.area)||(t?P&&N&&c(f)&&!L?S(!0,g.classed(tn.SELECTED,!0),f,m):Qe(a)&&a&&L&&S(!1,g.classed(tn.SELECTED,!1),f,m):P&&N&&c(f)&&L&&S(!1,g.classed(tn.SELECTED,!1),f,m))})}var Dm={selected(t){const e=this.internal,n=[];return e.$el.main.selectAll(`.${sn.shapes+e.getTargetSelectorSuffix(t)}`).selectAll(`.${sn.shape}`).filter(function(){return ot(this).classed(tn.SELECTED)}).each(a=>n.push(a)),n},select(t,e,n){const a=this.internal;su.bind(a)(!0,t,e,n)},unselect(t,e){const n=this.internal;su.bind(n)(!1,t,e)}};const lu=function(t){var e;const n=this.internal,{axis:a,brush:i,config:o,scale:{x:s,subX:l},state:c}=n;let f;return o.subchart_show&&(f=t,Array.isArray(f)?(a.isTimeSeries()&&(f=f.map(v=>Yn.bind(n)(v))),n.withinRange(f,n.getZoomDomain("subX",!0),n.getZoomDomain("subX"))&&(c.domain=f,i.move(i.getSelection(),f.map(l)))):f=(e=c.domain)!=null?e:s.orgDomain()),f};yn(lu,{show(){var t,e;const n=this.internal,{$el:{subchart:a},config:i}=n,o=i.subchart_show;if(!o){n.unbindZoomEvent(),i.subchart_show=!o,!a.main&&n.initSubchart();let s=a.main.selectAll(`.${Se.target}`);n.data.targets.length!==s.size()&&(n.updateSizes(),n.updateTargetsForSubchart(n.data.targets),s=(t=a.main)==null?void 0:t.selectAll(`.${Se.target}`)),s==null||s.style("opacity",null),(e=a.main)==null||e.style("display",null),this.resize()}},hide(){const t=this.internal,{$el:{subchart:{main:e}},config:n}=t;n.subchart_show&&(e==null?void 0:e.style("display"))!=="none"&&(n.subchart_show=!1,e.style("display","none"),this.resize())},toggle(){const t=this.internal,{config:e}=t;this.subchart[e.subchart_show?"hide":"show"]()},reset(){const t=this.internal,{brush:e}=t;e.clear(e.getSelection())}});var Lm={subchart:lu},Nm=1e-12;function cu(t){return((t=Math.exp(t))+1/t)/2}function Fm(t){return((t=Math.exp(t))-1/t)/2}function Bm(t){return((t=Math.exp(2*t))-1)/(t+1)}var Um=function t(e,n,a){function i(o,s){var l=o[0],c=o[1],f=o[2],g=s[0],v=s[1],m=s[2],S=g-l,P=v-c,N=S*S+P*P,L,w;if(N()=>t;function zm(t,{sourceEvent:e,target:n,transform:a,dispatch:i}){Object.defineProperties(this,{type:{value:t,enumerable:!0,configurable:!0},sourceEvent:{value:e,enumerable:!0,configurable:!0},target:{value:n,enumerable:!0,configurable:!0},transform:{value:a,enumerable:!0,configurable:!0},_:{value:i}})}function vr(t,e,n){this.k=t,this.x=e,this.y=n}vr.prototype={constructor:vr,scale:function(t){return t===1?this:new vr(this.k*t,this.x,this.y)},translate:function(t,e){return t===0&e===0?this:new vr(this.k,this.x+this.k*t,this.y+this.k*e)},apply:function(t){return[t[0]*this.k+this.x,t[1]*this.k+this.y]},applyX:function(t){return t*this.k+this.x},applyY:function(t){return t*this.k+this.y},invert:function(t){return[(t[0]-this.x)/this.k,(t[1]-this.y)/this.k]},invertX:function(t){return(t-this.x)/this.k},invertY:function(t){return(t-this.y)/this.k},rescaleX:function(t){return t.copy().domain(t.range().map(this.invertX,this).map(t.invert,t))},rescaleY:function(t){return t.copy().domain(t.range().map(this.invertY,this).map(t.invert,t))},toString:function(){return"translate("+this.x+","+this.y+") scale("+this.k+")"}};var ar=new vr(1,0,0);vs.prototype=vr.prototype;function vs(t){for(;!t.__zoom;)if(!(t=t.parentNode))return ar;return t.__zoom}function ps(t){t.stopImmediatePropagation()}function Ba(t){t.preventDefault(),t.stopImmediatePropagation()}function jm(t){return(!t.ctrlKey||t.type==="wheel")&&!t.button}function Vm(){var t=this;return t instanceof SVGElement?(t=t.ownerSVGElement||t,t.hasAttribute("viewBox")?(t=t.viewBox.baseVal,[[t.x,t.y],[t.x+t.width,t.y+t.height]]):[[0,0],[t.width.baseVal.value,t.height.baseVal.value]]):[[0,0],[t.clientWidth,t.clientHeight]]}function uu(){return this.__zoom||ar}function Gm(t){return-t.deltaY*(t.deltaMode===1?.05:t.deltaMode?1:.002)*(t.ctrlKey?10:1)}function Xm(){return navigator.maxTouchPoints||"ontouchstart"in this}function Hm(t,e,n){var a=t.invertX(e[0][0])-n[0][0],i=t.invertX(e[1][0])-n[1][0],o=t.invertY(e[0][1])-n[0][1],s=t.invertY(e[1][1])-n[1][1];return t.translate(i>a?(a+i)/2:Math.min(0,a)||Math.max(0,i),s>o?(o+s)/2:Math.min(0,o)||Math.max(0,s))}function Ym(){var t=jm,e=Vm,n=Hm,a=Gm,i=Xm,o=[0,1/0],s=[[-1/0,-1/0],[1/0,1/0]],l=250,c=Um,f=ri("start","zoom","end"),g,v,m,S=500,P=150,N=0,L=10;function w(Q){Q.property("__zoom",uu).on("wheel.zoom",ht,{passive:!1}).on("mousedown.zoom",$t).on("dblclick.zoom",dt).filter(i).on("touchstart.zoom",st).on("touchmove.zoom",Vt).on("touchend.zoom touchcancel.zoom",vt).style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}w.transform=function(Q,St,ct,At){var Gt=Q.selection?Q.selection():Q;Gt.property("__zoom",uu),Q!==Gt?k(Q,St,ct,At):Gt.interrupt().each(function(){K(this,arguments).event(At).start().zoom(null,typeof St=="function"?St.apply(this,arguments):St).end()})},w.scaleBy=function(Q,St,ct,At){w.scaleTo(Q,function(){var Gt=this.__zoom.k,Bt=typeof St=="function"?St.apply(this,arguments):St;return Gt*Bt},ct,At)},w.scaleTo=function(Q,St,ct,At){w.transform(Q,function(){var Gt=e.apply(this,arguments),Bt=this.__zoom,Kt=ct==null?H(Gt):typeof ct=="function"?ct.apply(this,arguments):ct,ne=Bt.invert(Kt),le=typeof St=="function"?St.apply(this,arguments):St;return n(W(X(Bt,le),Kt,ne),Gt,s)},ct,At)},w.translateBy=function(Q,St,ct,At){w.transform(Q,function(){return n(this.__zoom.translate(typeof St=="function"?St.apply(this,arguments):St,typeof ct=="function"?ct.apply(this,arguments):ct),e.apply(this,arguments),s)},null,At)},w.translateTo=function(Q,St,ct,At,Gt){w.transform(Q,function(){var Bt=e.apply(this,arguments),Kt=this.__zoom,ne=At==null?H(Bt):typeof At=="function"?At.apply(this,arguments):At;return n(ar.translate(ne[0],ne[1]).scale(Kt.k).translate(typeof St=="function"?-St.apply(this,arguments):-St,typeof ct=="function"?-ct.apply(this,arguments):-ct),Bt,s)},At,Gt)};function X(Q,St){return St=Math.max(o[0],Math.min(o[1],St)),St===Q.k?Q:new vr(St,Q.x,Q.y)}function W(Q,St,ct){var At=St[0]-ct[0]*Q.k,Gt=St[1]-ct[1]*Q.k;return At===Q.x&&Gt===Q.y?Q:new vr(Q.k,At,Gt)}function H(Q){return[(+Q[0][0]+ +Q[1][0])/2,(+Q[0][1]+ +Q[1][1])/2]}function k(Q,St,ct,At){Q.on("start.zoom",function(){K(this,arguments).event(At).start()}).on("interrupt.zoom end.zoom",function(){K(this,arguments).event(At).end()}).tween("zoom",function(){var Gt=this,Bt=arguments,Kt=K(Gt,Bt).event(At),ne=e.apply(Gt,Bt),le=ct==null?H(ne):typeof ct=="function"?ct.apply(Gt,Bt):ct,be=Math.max(ne[1][0]-ne[0][0],ne[1][1]-ne[0][1]),Oe=Gt.__zoom,Ce=typeof St=="function"?St.apply(Gt,Bt):St,He=c(Oe.invert(le).concat(be/Oe.k),Ce.invert(le).concat(be/Ce.k));return function(Fe){if(Fe===1)Fe=Ce;else{var dn=He(Fe),Jt=be/dn[2];Fe=new vr(Jt,le[0]-dn[0]*Jt,le[1]-dn[1]*Jt)}Kt.zoom(null,Fe)}})}function K(Q,St,ct){return!ct&&Q.__zooming||new at(Q,St)}function at(Q,St){this.that=Q,this.args=St,this.active=0,this.sourceEvent=null,this.extent=e.apply(Q,St),this.taps=0}at.prototype={event:function(Q){return Q&&(this.sourceEvent=Q),this},start:function(){return++this.active===1&&(this.that.__zooming=this,this.emit("start")),this},zoom:function(Q,St){return this.mouse&&Q!=="mouse"&&(this.mouse[1]=St.invert(this.mouse[0])),this.touch0&&Q!=="touch"&&(this.touch0[1]=St.invert(this.touch0[0])),this.touch1&&Q!=="touch"&&(this.touch1[1]=St.invert(this.touch1[0])),this.that.__zoom=St,this.emit("zoom"),this},end:function(){return--this.active===0&&(delete this.that.__zooming,this.emit("end")),this},emit:function(Q){var St=ot(this.that).datum();f.call(Q,this.that,new zm(Q,{sourceEvent:this.sourceEvent,target:w,type:Q,transform:this.that.__zoom,dispatch:f}),St)}};function ht(Q,...St){if(!t.apply(this,arguments))return;var ct=K(this,St).event(Q),At=this.__zoom,Gt=Math.max(o[0],Math.min(o[1],At.k*Math.pow(2,a.apply(this,arguments)))),Bt=Xn(Q);if(ct.wheel)(ct.mouse[0][0]!==Bt[0]||ct.mouse[0][1]!==Bt[1])&&(ct.mouse[1]=At.invert(ct.mouse[0]=Bt)),clearTimeout(ct.wheel);else{if(At.k===Gt)return;ct.mouse=[Bt,At.invert(Bt)],qr(this),ct.start()}Ba(Q),ct.wheel=setTimeout(Kt,P),ct.zoom("mouse",n(W(X(At,Gt),ct.mouse[0],ct.mouse[1]),ct.extent,s));function Kt(){ct.wheel=null,ct.end()}}function $t(Q,...St){if(m||!t.apply(this,arguments))return;var ct=Q.currentTarget,At=K(this,St,!0).event(Q),Gt=ot(Q.view).on("mousemove.zoom",le,!0).on("mouseup.zoom",be,!0),Bt=Xn(Q,ct),Kt=Q.clientX,ne=Q.clientY;co(Q.view),ps(Q),At.mouse=[Bt,this.__zoom.invert(Bt)],qr(this),At.start();function le(Oe){if(Ba(Oe),!At.moved){var Ce=Oe.clientX-Kt,He=Oe.clientY-ne;At.moved=Ce*Ce+He*He>N}At.event(Oe).zoom("mouse",n(W(At.that.__zoom,At.mouse[0]=Xn(Oe,ct),At.mouse[1]),At.extent,s))}function be(Oe){Gt.on("mousemove.zoom mouseup.zoom",null),uo(Oe.view,At.moved),Ba(Oe),At.event(Oe).end()}}function dt(Q,...St){if(t.apply(this,arguments)){var ct=this.__zoom,At=Xn(Q.changedTouches?Q.changedTouches[0]:Q,this),Gt=ct.invert(At),Bt=ct.k*(Q.shiftKey?.5:2),Kt=n(W(X(ct,Bt),At,Gt),e.apply(this,St),s);Ba(Q),l>0?ot(this).transition().duration(l).call(k,Kt,At,Q):ot(this).call(w.transform,Kt,At,Q)}}function st(Q,...St){if(t.apply(this,arguments)){var ct=Q.touches,At=ct.length,Gt=K(this,St,Q.changedTouches.length===At).event(Q),Bt,Kt,ne,le;for(ps(Q),Kt=0;KtYn.bind(n)(v))),n.withinRange(f,n.getZoomDomain("zoom",!0),n.getZoomDomain("zoom"))){if(l.domain=f,f=n.getZoomDomainValue(f),n.api.tooltip.hide(),i.subchart_show){const v=s.zoom||s.x;n.brush.getSelection().call(n.brush.move,f.map(v))}else{const v=c?s.x.orgScale():o.xScale||s.x;n.updateCurrentZoomTransform(v,f)}n.setZoomResetButton()}}else f=n.zoom.getDomain();return(e=l.domain)!=null?e:f};yn(fu,{enable(t){const e=this.internal,{config:n}=e;/^(drag|wheel)$/.test(t)&&(n.zoom_type=t),n.zoom_enabled=!!t,e.zoom?t===!1&&e.bindZoomEvent(!1):(e.initZoom(),e.bindZoomEvent()),e.updateAndRedraw()},max(t){const e=this.internal,{config:n,org:{xDomain:a}}=e;return(t===0||t)&&(n.zoom_x_max=_n("max",[a[1],t])),n.zoom_x_max},min(t){const e=this.internal,{config:n,org:{xDomain:a}}=e;return(t===0||t)&&(n.zoom_x_min=_n("min",[a[0],t])),n.zoom_x_min},range(t){const e=this.zoom;if(Be(t)){const{min:n,max:a}=t;Qe(n)&&e.min(n),Qe(a)&&e.max(a)}return{min:e.min(),max:e.max()}}});var Wm={zoom:fu,unzoom(){const t=this.internal,{config:e,$el:{eventRect:n,zoomResetBtn:a},scale:{zoom:i},state:o}=t;i&&(e.subchart_show?t.brush.getSelection().call(t.brush.move,null):t.zoom.updateTransformScale(ar),t.updateZoom(!0),a==null||a.style("display","none"),vs(n.node())!==ar&&t.zoom.transform(n,ar),o.domain=void 0)}},Km={initBrush(){const t=this,{config:e,scale:n,$el:{subchart:a},state:i}=t,o=e.axis_rotated,s=e.subchart_size_height;let l,c,f;t.brush=(o?Gg():Vg()).handleSize(5),t.brush.on("start brush end",g=>{const{selection:v,sourceEvent:m,target:S,type:P}=g;P==="start"&&(t.state.inputType==="touch"&&t.hideTooltip(),c=m?v:null),/(start|brush)/.test(P)&&(P==="brush"&&m&&i.domain&&(c==null||c.forEach((N,L)=>{N!==v[L]&&(i.domain[L]=n.x.orgDomain()[L])})),t.redrawForBrush(P!=="start")),P==="end"&&(l=n.x.orgDomain()),S!=null&&S.handle&&(v===null?t.brush.handle.attr("display","none"):t.brush.handle.attr("display",null).attr("transform",(N,L)=>{const w=[v[L],s/2];return`translate(${o?w.reverse():w})`}))}),t.brush.updateResize=function(){f&&clearTimeout(f),f=setTimeout(()=>{const g=this.getSelection();l&&zl(g.node())&&this.move(g,l.map(n.subX.orgScale()))},0)},t.brush.update=function(){var g;return this.extent()()[1].filter(m=>isNaN(m)).length===0&&((g=a.main)==null||g.select(`.${Ue.brush}`).call(this)),this},t.brush.scale=function(g){const v=e.subchart_size_height;let m=t.axis.getExtent();!m&&g.range?m=[[0,0],[g.range()[1],v]]:je(m)&&(m=m.map((S,P)=>[S,P>0?v:P])),o&&m[1].reverse(),this.extent(m),this.update()},t.brush.getSelection=()=>a.main?a.main.select(`.${Ue.brush}`):ot([])},initSubchart(){const t=this,{config:e,state:{clip:n,hasAxis:a},$el:{defs:i,svg:o,subchart:s,axis:l}}=t;if(!a)return;const c=e.subchart_show?null:"hidden",f=`${n.id}-subchart`,g=t.getClipPath(f);n.idSubchart=f,t.appendClip(i,f),t.initBrush(),s.main=o.append("g").classed(Ue.subchart,!0).attr("transform",t.getTranslate("context"));const{main:v}=s;v.style("visibility",c),v.append("g").attr("clip-path",g).attr("class",Ue.chart),["bar","line","bubble","candlestick","scatter"].forEach(S=>{const P=Cn(/^(bubble|scatter)$/.test(S)?"circle":S);if(t.hasType(S)||t.hasTypeOf(P)){const N=v.select(`.${Ue.chart}`),L=Ue[`chart${P}s`];N.select(`.${L}`).empty()&&N.append("g").attr("class",L)}});const m=v.append("g").attr("clip-path",g).attr("class",Ue.brush).call(t.brush);e.subchart_showHandle&&t.addBrushHandle(m),l.subX=v.append("g").attr("class",Ue.axisX).attr("transform",t.getTranslate("subX")).attr("clip-path",e.axis_rotated?"":n.pathXAxis).style("visibility",e.subchart_axis_x_show?c:"hidden")},addBrushHandle(t){const e=this,{config:n}=e,a=n.axis_rotated,i=n.subchart_init_range,o="handle--custom",s=a?["M8.5 0 a6 6 0 0 0 -6 -6.5 H-2.5 a 6 6 0 0 0 -6 6.5 z m-5 -2 H-3.5 m7 -2 H-3.5z","M8.5 0 a6 -6 0 0 1 -6 6.5 H-2.5 a 6 -6 0 0 1 -6 -6.5z m-5 2 H-3.5 m7 2 H-3.5z"]:["M0 -8.5 A6 6 0 0 0 -6.5 -3.5 V2.5 A6 6 0 0 0 0 8.5 Z M-2 -3.5 V3.5 M-4 -3.5 V3.5z","M0 -8.5 A6 6 0 0 1 6.5 -3.5 V2.5 A6 6 0 0 1 0 8.5 Z M2 -3.5 V3.5 M4 -3.5 V3.5z"];e.brush.handle=t.selectAll(`.${o}`).data(a?[{type:"n"},{type:"s"}]:[{type:"w"},{type:"e"}]).enter().append("path").attr("class",o).attr("cursor",`${a?"ns":"ew"}-resize`).attr("d",l=>s[+/[se]/.test(l.type)]).attr("display",i?null:"none")},updateTargetsForSubchart(t){const e=this,{config:n,state:a,$el:{subchart:{main:i}}}=e;n.subchart_show&&(["bar","line","bubble","candlestick","scatter"].filter(o=>e.hasType(o)||e.hasTypeOf(Cn(o))).forEach(o=>{const s=/^(bubble|scatter)$/.test(o),l=Cn(s?"circle":o),c=e.getChartClass(l,!0),f=e.getClass(s?"circles":`${o}s`,!0),g=i.select(`.${Ue[`chart${`${l}s`}`]}`);if(s){const v=g.selectAll(`.${Ue.circles}`).data(t.filter(e[`is${Cn(o)}Type`].bind(e))).attr("class",f);v.exit().remove(),v.enter().append("g").attr("class",f)}else{const v=g.selectAll(`.${Ue[`chart${l}`]}`).attr("class",c).data(t.filter(e[`is${l}Type`].bind(e))),m=v.enter().append("g").style("opacity","0").attr("class",c).append("g").attr("class",f);v.exit().remove(),o==="line"&&e.hasTypeOf("Area")&&m.append("g").attr("class",e.getClass("areas",!0))}}),i.selectAll(`.${Ue.brush} rect`).attr(n.axis_rotated?"width":"height",n.axis_rotated?a.width2:a.height2))},redrawSubchart(t,e,n){var a;const i=this,{config:o,$el:{subchart:{main:s}},state:l}=i,c=!!e;if(s.style("visibility",o.subchart_show?null:"hidden"),o.subchart_show&&(((a=l.event)==null?void 0:a.type)==="zoom"&&i.brush.update(),t)){const f=o.subchart_init_range;if(!Kl(i)&&i.brush.update(),Object.keys(n.type).forEach(g=>{const v=Cn(g),m=i[`generateDraw${v}`](n.indices[g],!0);i[`update${v}`](c,!0),i[`redraw${v}`](m,c,!0)}),i.hasType("bubble")||i.hasType("scatter")){const{cx:g}=n.pos,v=i.updateCircleY(!0);i.updateCircle(!0),i.redrawCircle(g,v,c,void 0,!0)}!l.rendered&&f&&(l.domain=f,i.brush.move(i.brush.getSelection(),f.map(i.scale.x)))}},redrawForBrush(t=!0){var e;const n=this,{config:{subchart_onbrush:a,zoom_rescale:i},scale:o,state:s}=n;n.redraw({withTransition:!1,withY:i,withSubchart:!1,withUpdateXDomain:!0,withDimension:!1}),t&&s.rendered&&a.bind(n.api)((e=s.domain)!=null?e:o.x.orgDomain())},transformContext(t,e){const n=this,{$el:{subchart:a},$T:i}=n,o=e!=null&&e.axisSubX?e.axisSubX:i(a.main.select(`.${Ue.axisX}`),t);a.main.attr("transform",n.getTranslate("context")),o.attr("transform",n.getTranslate("subX"))}},Zm={initZoom(){const t=this;t.scale.zoom=null,t.generateZoom(),t.config.zoom_type==="drag"&&t.initZoomBehaviour()},bindZoomEvent(t=!0){const e=this,{config:n}=e;n.zoom_enabled&&t?!n.subchart_show&&e.bindZoomOnEventRect():t===!1&&(e.api.unzoom(),e.unbindZoomEvent())},generateZoom(){const t=this,{config:e,org:n,scale:a}=t,i=Ym().duration(0).on("start",t.onZoomStart.bind(t)).on("zoom",t.onZoom.bind(t)).on("end",t.onZoomEnd.bind(t));i.orgScaleExtent=()=>{const o=e.zoom_extent||[1,10];return[o[0],Math.max(t.getMaxDataCount()/o[1],o[1])]},i.updateScaleExtent=function(){const o=Dr(t.scale.x.orgDomain())/Dr(t.getZoomDomain()),s=this.orgScaleExtent();return this.scaleExtent([s[0]*o,s[1]*o]),this},i.updateTransformScale=(o,s)=>{var l;const c=e.axis_rotated;(l=n.xScale)==null||l.range(a.x.range());const f=o[c?"rescaleY":"rescaleX"](n.xScale||a.x);if(f.domain().some(m=>/(Invalid Date|NaN)/.test(m.toString())))return;const g=t.trimXDomain(f.domain()),v=e.zoom_rescale;if(f.domain(g,n.xDomain),s){const m=f(a.x.domain()[0]),S=c?o.x:m,P=c?m:o.y;t.$el.eventRect.property("__zoom",ar.translate(S,P).scale(o.k))}t.state.xTickOffset||(t.state.xTickOffset=t.axis.x.tickOffset()),a.zoom=t.getCustomizedXScale(f),t.axis.x.scale(a.zoom),v?(!n.xScale&&(n.xScale=a.x.copy()),a.x.domain(g)):n.xScale&&(a.x.domain(n.xScale.domain()),n.xScale=null)},i.getDomain=()=>{const o=a[a.zoom?"zoom":"subX"].domain();return t.axis.isCategorized()&&(o[1]-=2),o},t.zoom=i},onZoomStart(t){const e=this,{sourceEvent:n}=t;n&&(e.zoom.startEvent=n,e.state.zooming=!0,_e(e.config.zoom_onzoomstart,e.api,t))},onZoom(t){var e;const n=this,{config:a,scale:i,state:o,org:s}=n,{sourceEvent:l}=t,c=(t==null?void 0:t.transform)===ar;if(!a.zoom_enabled||n.filterTargetsToShow(n.data.targets).length===0||!i.zoom&&(l==null?void 0:l.type.indexOf("touch"))>-1&&(l==null?void 0:l.touches.length)===1)return;t.sourceEvent&&(o.zooming=!0,o.domain=void 0);const f=(l==null?void 0:l.type)==="mousemove",g=(l==null?void 0:l.wheelDelta)<0,{transform:v}=t;!f&&g&&i.x.domain().every((S,P)=>S!==s.xDomain[P])&&i.x.domain(s.xDomain),n.zoom.updateTransformScale(v,a.zoom_type==="wheel"&&l);const m=a.transition_duration>0&&!a.subchart_show&&(o.dragging||c||!t.sourceEvent);n.redraw({withTransition:m,withY:a.zoom_rescale,withSubchart:!1,withEventRect:!1,withDimension:!1}),n.state.cancelClick=f,!c&&_e(a.zoom_onzoom,n.api,(e=n.state.domain)!=null?e:n.zoom.getDomain())},onZoomEnd(t){var e,n;const a=this,{config:i,state:o}=a;let{startEvent:s}=a.zoom,l=t==null?void 0:t.sourceEvent;const c=(t==null?void 0:t.transform)===ar;(s==null?void 0:s.type.indexOf("touch"))>-1&&(s=s.changedTouches[0],l=(e=l==null?void 0:l.changedTouches)==null?void 0:e[0]),!(i.zoom_type==="drag"&&l&&s.clientX===l.clientX&&s.clientY===l.clientY)&&(o.zooming=!1,a.redrawEventRect(),a.updateZoom(),!c&&(l||o.dragging)&&_e(i.zoom_onzoomend,a.api,(n=a.state.domain)!=null?n:a.zoom.getDomain()))},updateZoom(t){const e=this,{subX:n,x:a,zoom:i}=e.scale;if(i){const o=i.domain(),s=n.domain(),l=.015,c=e.config.axis_x_inverted?(o[0]>=s[0]||o[0]+l>=s[0])&&(s[1]>=o[1]||s[1]>=o[1]+l):(o[0]<=s[0]||o[0]-l<=s[0])&&(s[1]<=o[1]||s[1]<=o[1]-l);(t||c)&&(e.axis.x.scale(n),a.domain(n.orgDomain()),e.scale.zoom=null)}},updateCurrentZoomTransform(t,e){const n=this,{$el:{eventRect:a},config:i}=n,o=i.axis_rotated,s=[-t(e[0]),0],l=ar.scale(t.range()[1]/(t(e[1])-t(e[0]))).translate(...o?s.reverse():s);a.call(n.zoom.transform,l)},bindZoomOnEventRect(){var t;const e=this,{config:n,$el:{eventRect:a,svg:i}}=e,o=n.zoom_type==="drag"?e.zoomBehaviour:e.zoom;Ke.GestureEvent&&/^((?!chrome|android|mobile).)*safari/i.test((t=Ke.navigator)==null?void 0:t.userAgent)&&i.on("wheel",()=>{}),a==null||a.call(o).on("dblclick.zoom",null)},initZoomBehaviour(){const t=this,{config:e,state:n}=t,a=e.axis_rotated;let i=0,o=0,s,l;const c={axis:a?"y":"x",attr:a?"height":"width",index:a?1:0};t.zoomBehaviour=uc().clickDistance(4).on("start",function(f){l=t.scale.zoom?null:t.axis.getExtent(),n.event=f,t.setDragStatus(!0),t.unselectRect(),s||(s=t.$el.main.append("rect").attr("clip-path",n.clip.path).attr("class",so.zoomBrush).attr("width",a?n.width:0).attr("height",a?0:n.height)),i=Hn(f,this)[c.index],l&&(il[1]&&(i=l[1])),o=i,s.attr(c.axis,i).attr(c.attr,0),t.onZoomStart(f)}).on("drag",function(f){o=Hn(f,this)[c.index],l&&(o>l[1]?o=l[1]:o{const g=t.scale.zoom||t.scale.x;n.event=f,s.attr(c.axis,0).attr(c.attr,0),i>o&&([i,o]=[o,i]),i<0&&(o+=Math.abs(i),i=0),i!==o&&t.api.zoom([i,o].map(v=>g.invert(v))),t.setDragStatus(!1)})},setZoomResetButton(){const t=this,{config:e,$el:n}=t,a=e.zoom_resetButton;a&&e.zoom_type==="drag"&&(n.zoomResetBtn?n.zoomResetBtn.style("display",null):n.zoomResetBtn=t.$el.chart.append("div").classed(Se.button,!0).append("span").on("click",function(){ve(a.onclick)&&a.onclick.bind(t.api)(this),t.api.unzoom()}).classed(so.buttonZoomReset,!0).text(a.text||"Reset Zoom"))},getZoomTransform(){const t=this,{$el:{eventRect:e}}=t;return e!=null&&e.node()?vs(e.node()):{k:1}}},Jm={drag(t){const e=this,{config:n,state:a,$el:{main:i}}=e,o=n.data_selection_grouped,s=n.interaction_enabled&&n.data_selection_isselectable;if(e.hasArcType()||!n.data_selection_enabled||n.zoom_enabled&&!e.zoom.altDomain||!n.data_selection_multiple)return;const[l,c]=a.dragStart||[0,0],[f,g]=t,v=Math.min(l,f),m=Math.max(l,f),S=o?a.margin.top:Math.min(c,g),P=o?a.height:Math.max(c,g);i.select(`.${Or.dragarea}`).attr("x",v).attr("y",S).attr("width",m-v).attr("height",P-S),i.selectAll(`.${sn.shapes}`).selectAll(`.${sn.shape}`).filter(N=>s==null?void 0:s.bind(e.api)(N)).each(function(N,L){const w=ot(this),X=w.classed(tn.SELECTED),W=w.classed(Or.INCLUDED);let H=!1,k;if(w.classed($n.circle)){const K=+w.attr("cx")*1,at=+w.attr("cy")*1;k=e.togglePoint,H=ve in t?Qm(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n,ey=(t,e)=>{for(var n in e||(e={}))_m.call(e,n)&&hu(t,n,e[n]);if(du)for(var n of du(e))ty.call(e,n)&&hu(t,n,e[n]);return t},ny=(t,e)=>km(t,qm(e)),ry=ny(ey({},Jm),{selectPoint(t,e,n){const a=this,{config:i,$el:{main:o},$T:s}=a,l=i.axis_rotated,c=(l?a.circleY:a.circleX).bind(a),f=(l?a.circleX:a.circleY).bind(a),g=a.pointSelectR.bind(a);_e(i.data_onselected,a.api,e,t.node()),s(o.select(`.${tn.selectedCircles}${a.getTargetSelectorSuffix(e.id)}`).selectAll(`.${tn.selectedCircle}-${n}`).data([e]).enter().append("circle").attr("class",()=>a.generateClass(tn.selectedCircle,n)).attr("cx",c).attr("cy",f).attr("stroke",a.color).attr("r",v=>a.pointSelectR(v)*1.4)).attr("r",g)},unselectPoint(t,e,n){const a=this,{config:i,$el:{main:o},$T:s}=a;_e(i.data_onunselected,a.api,e,t==null?void 0:t.node()),s(o.select(`.${tn.selectedCircles}${a.getTargetSelectorSuffix(e.id)}`).selectAll(`.${tn.selectedCircle}-${n}`)).attr("r",0).remove()},togglePoint(t,e,n,a){this[`${t?"":"un"}selectPoint`](e,n,a)},selectPath(t,e){const n=this,{config:a}=n;_e(a.data_onselected,n.api,e,t.node()),a.interaction_brighten&&t.style("filter","brightness(1.25)")},unselectPath(t,e){const n=this,{config:a}=n;_e(a.data_onunselected,n.api,e,t.node()),a.interaction_brighten&&t.style("filter",null)},togglePath(t,e,n,a){this[`${t?"":"un"}selectPath`](e,n,a)},getToggle(t,e){const n=this;return t.nodeName==="path"?n.togglePath:n.isStepType(e)?()=>{}:n.togglePoint},toggleShape(t,e,n){var a;const i=this,{config:o,$el:{main:s}}=i;if(o.data_selection_enabled&&o.data_selection_isselectable.bind(i.api)(e)){const l=ot(t),c=l.classed(tn.SELECTED),f=i.getToggle(t,e).bind(i);let g;if(!o.data_selection_multiple){const v=(a=i.isPointFocusOnly)==null?void 0:a.call(i);let m=`.${v?tn.selectedCircles:sn.shapes}`;o.data_selection_grouped&&(m+=i.getTargetSelectorSuffix(e.id)),s.selectAll(m).selectAll(v?`.${tn.selectedCircle}`:`.${sn.shape}.${tn.SELECTED}`).classed(tn.SELECTED,!1).each(function(S){const P=ot(this);g=P,f(!1,P,S,S.index)})}(!g||g.node()!==l.node())&&(l.classed(tn.SELECTED,!c),f(!c,l,e,n))}}}),ay={data_selection_enabled:!1,data_selection_grouped:!1,data_selection_isselectable:()=>!0,data_selection_multiple:!0,data_selection_draggable:!1,data_onselected:()=>{},data_onunselected:()=>{}},iy={subchart_show:!1,subchart_showHandle:!1,subchart_size_height:60,subchart_axis_x_show:!0,subchart_axis_x_tick_show:!0,subchart_axis_x_tick_format:void 0,subchart_axis_x_tick_text_show:!0,subchart_init_range:void 0,subchart_onbrush:()=>{}},oy={zoom_enabled:!1,zoom_type:"wheel",zoom_extent:void 0,zoom_privileged:!1,zoom_rescale:!1,zoom_onzoom:void 0,zoom_onzoomstart:void 0,zoom_onzoomend:void 0,zoom_resetButton:!0,zoom_x_min:void 0,zoom_x_max:void 0};let gu=()=>(yn(Vr.prototype,ry),yn(Er.prototype,Dm),Nr.setOptions([ay]),(gu=()=>!0)()),vu=()=>(yn(Vr.prototype,Km),yn(Er.prototype,Lm),Nr.setOptions([iy]),(vu=()=>!0)()),pu=()=>(yn(Vr.prototype,Zm),yn(Er.prototype,Wm),Nr.setOptions([oy]),(pu=()=>!0)());function mu(t,e,n){const{config:a}=t,i=(o,s)=>{const l=he(s)?s:s===!1?void 0:null;l!==null&&(a[`axis_${o}_${e}`]=l)};Qe(n)&&(nr(n)?Object.keys(n).forEach(o=>{i(o,n[o])}):(he(n)||n===!1)&&["y","y2"].forEach(o=>{i(o,n)}),t.redraw({withUpdateOrgXDomain:!0,withUpdateXDomain:!0}))}function yu(t,e){const{config:n}=t;return{x:n[`axis_x_${e}`],y:n[`axis_y_${e}`],y2:n[`axis_y2_${e}`]}}var sy={axis:{labels:function(t){const e=this.internal;let n;return t&&(Object.keys(t).forEach(a=>{e.axis.setLabelText(a,t[a])}),e.axis.updateLabels()),["x","y","y2"].forEach(a=>{const i=e.axis.getLabelText(a);i&&(!n&&(n={}),n[a]=i)}),n},min:function(t){const e=this.internal;return De(t)||t===!1?mu(e,"min",t):yu(e,"min")},max:function(t){const e=this.internal;return De(t)||t===!1?mu(e,"max",t):yu(e,"max")},range:function(t){const{axis:e}=this;if(arguments.length){const{min:n,max:a}=t;Qe(a)&&e.max(a),Qe(n)&&e.min(n)}else return{max:e.max(),min:e.min()}}}},ly={category(t,e){const n=this.internal,{config:a}=n;return arguments.length>1&&(a.axis_x_categories[t]=e,n.redraw()),a.axis_x_categories[t]},categories(t){const e=this.internal,{config:n}=e;if(!t||!Array.isArray(t)){const a=n.axis_x_categories;return qn(a)?Object.values(e.data.xs)[0]:a}return n.axis_x_categories=t,e.redraw(),n.axis_x_categories}},cy={flow(t){const e=this.internal;let n;(t.json||t.rows||t.columns)&&e.convertData(t,i=>{n=i,a()});function a(){let i,o=0,s=0,l,c;if(e.state.redrawing||!n||!Da())return;const f=[],g=e.getMaxDataCount(),v=e.convertDataToTargets(n,!0),m=e.axis.isTimeSeries();e.data.targets.forEach(N=>{let L=!1;for(let w=0;w{for(let L=0;L{const L=[];for(let w=e.data.targets[0].values[0].index;w{w.index+=s,m||(w.x+=s)}),N.values=L.concat(N.values)}),e.data.targets=e.data.targets.concat(v);const S=e.data.targets[0],P=S.values[0];Qe(t.to)?(o=0,c=m?Yn.call(e,t.to):t.to,S.values.forEach(N=>{N.x1?S.values[S.values.length-1].x-P.x:P.x-e.getXDomain(e.data.targets)[0]:l=1,i=[P.x-l,P.x]),i&&e.updateXDomain(null,!0,!0,!1,i),e.updateTargets(e.data.targets),e.redraw({flow:{index:P.index,length:o,duration:De(t.duration)?t.duration:e.config.transition_duration,done:t.done,orgDataCount:g},withLegend:!0,withTransition:g>1,withTrimXDomain:!1,withUpdateXAxis:!0})}}};function ms(t,e){const n=this.internal,{config:a}=n,i=a.transition_duration&&Da(),o=`grid_${e}_lines`;return t&&(a[o]=t,n.updateGrid(),n.redrawGrid(i)),a[o]}function xu(t,e){const n=`grid_${e}_lines`;return ms.bind(this)(this.internal.config[n].concat(t||[]),e)}function Tu(t,e){this.internal.removeGridLines(t,e)}const $u=function(t){return ms.bind(this)(t,"x")};yn($u,{add(t){return xu.bind(this)(t,"x")},remove(t){return Tu.bind(this)(t,!0)}});const Su=function(t){return ms.bind(this)(t,"y")};yn(Su,{add(t){return xu.bind(this)(t,"y")},remove(t){return Tu.bind(this)(t,!1)}});var uy={xgrids:$u,ygrids:Su},fy={groups(t){const e=this.internal,{config:n}=e;return ln(t)||(n.data_groups=t,e.redraw()),n.data_groups}};function Au(t,e=!1){const n=this.internal,{config:a}=n,i=a.transition_duration&&Da();return t?(a.regions=e?a.regions.concat(t):t,n.updateRegion(),n.redrawRegion(i),e?a.regions:t):a.regions}const Eu=function(t){return Au.bind(this)(t)};yn(Eu,{add:function(t){return Au.bind(this)(t,!0)},remove:function(t){const e=this.internal,{config:n,$T:a}=e,i=t||{},o=$r(i,"classes",[$a.region]);let s=e.$el.main.select(`.${$a.regions}`).selectAll(o.map(l=>`.${l}`));return a(s).style("opacity","0").remove(),s=n.regions,Object.keys(i).length?(s=s.filter(l=>{let c=!1;return l.class?(l.class.split(" ").forEach(f=>{o.indexOf(f)>=0&&(c=!0)}),!c):!0}),n.regions=s):n.regions=[],s}});var dy={regions:Eu},hy={x(t){const e=this.internal,{axis:n,data:a}=e,i=n.isCustomX()&&n.isCategorized();return je(t)&&(i?this.categories(t):(e.updateTargetX(a.targets,t),e.redraw({withUpdateOrgXDomain:!0,withUpdateXDomain:!0}))),i?this.categories():a.xs},xs(t){const e=this.internal;return Be(t)&&(e.updateTargetXs(e.data.targets,t),e.redraw({withUpdateOrgXDomain:!0,withUpdateXDomain:!0})),e.data.xs}};function gy(t){return t}var Xi=1,Hi=2,ys=3,Ua=4,bu=1e-6;function vy(t){return"translate("+t+",0)"}function py(t){return"translate(0,"+t+")"}function my(t){return e=>+t(e)}function yy(t,e){return e=Math.max(0,t.bandwidth()-e*2)/2,t.round()&&(e=Math.round(e)),n=>+t(n)+e}function xy(){return!this.__axis}function Yi(t,e){var n=[],a=null,i=null,o=6,s=6,l=3,c=typeof window!="undefined"&&window.devicePixelRatio>1?0:.5,f=t===Xi||t===Ua?-1:1,g=t===Ua||t===Hi?"x":"y",v=t===Xi||t===ys?vy:py;function m(S){var P=a==null?e.ticks?e.ticks.apply(e,n):e.domain():a,N=i==null?e.tickFormat?e.tickFormat.apply(e,n):gy:i,L=Math.max(o,0)+l,w=e.range(),X=+w[0]+c,W=+w[w.length-1]+c,H=(e.bandwidth?yy:my)(e.copy(),c),k=S.selection?S.selection():S,K=k.selectAll(".domain").data([null]),at=k.selectAll(".tick").data(P,e).order(),ht=at.exit(),$t=at.enter().append("g").attr("class","tick"),dt=at.select("line"),st=at.select("text");K=K.merge(K.enter().insert("path",".tick").attr("class","domain").attr("stroke","currentColor")),at=at.merge($t),dt=dt.merge($t.append("line").attr("stroke","currentColor").attr(g+"2",f*o)),st=st.merge($t.append("text").attr("fill","currentColor").attr(g,f*L).attr("dy",t===Xi?"0em":t===ys?"0.71em":"0.32em")),S!==k&&(K=K.transition(S),at=at.transition(S),dt=dt.transition(S),st=st.transition(S),ht=ht.transition(S).attr("opacity",bu).attr("transform",function(Vt){return isFinite(Vt=H(Vt))?v(Vt+c):this.getAttribute("transform")}),$t.attr("opacity",bu).attr("transform",function(Vt){var vt=this.parentNode.__axis;return v((vt&&isFinite(vt=vt(Vt))?vt:H(Vt))+c)})),ht.remove(),K.attr("d",t===Ua||t===Hi?s?"M"+f*s+","+X+"H"+c+"V"+W+"H"+f*s:"M"+c+","+X+"V"+W:s?"M"+X+","+f*s+"V"+c+"H"+W+"V"+f*s:"M"+X+","+c+"H"+W),at.attr("opacity",1).attr("transform",function(Vt){return v(H(Vt)+c)}),dt.attr(g+"2",f*o),st.attr(g,f*L).text(N),k.filter(xy).attr("fill","none").attr("font-size",10).attr("font-family","sans-serif").attr("text-anchor",t===Hi?"start":t===Ua?"end":"middle"),k.each(function(){this.__axis=H})}return m.scale=function(S){return arguments.length?(e=S,m):e},m.ticks=function(){return n=Array.from(arguments),m},m.tickArguments=function(S){return arguments.length?(n=S==null?[]:Array.from(S),m):n.slice()},m.tickValues=function(S){return arguments.length?(a=S==null?null:Array.from(S),m):a&&a.slice()},m.tickFormat=function(S){return arguments.length?(i=S,m):i},m.tickSize=function(S){return arguments.length?(o=s=+S,m):o},m.tickSizeInner=function(S){return arguments.length?(o=+S,m):o},m.tickSizeOuter=function(S){return arguments.length?(s=+S,m):s},m.tickPadding=function(S){return arguments.length?(l=+S,m):l},m.offset=function(S){return arguments.length?(c=+S,m):c},m}function Ty(t){return Yi(Xi,t)}function $y(t){return Yi(Hi,t)}function Ru(t){return Yi(ys,t)}function Iu(t){return Yi(Ua,t)}var Sy=Object.defineProperty,Ay=(t,e,n)=>e in t?Sy(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n,xs=(t,e,n)=>Ay(t,typeof e!="symbol"?e+"":e,n);class Ou{constructor(e){xs(this,"owner"),xs(this,"config"),xs(this,"scale");const n=zr(),{config:a,params:i}=e;this.owner=e,this.config=a,this.scale=n,(a.noTransition||!i.config.transition_duration)&&(a.withoutTransition=!0),a.range=this.scaleExtent((i.orgXScale||n).range())}static getSizeFor1Char(e,n=!0){const a={w:5.5,h:11.5};return!e.empty()&&e.text("0").call(i=>{try{const{width:o,height:s}=i.node().getBBox();o&&s&&(a.w=o,a.h=s)}finally{i.text("")}}),n&&(this.getSizeFor1Char=()=>a),a}getTickTransformSetter(e){const{config:n}=this,a=e==="x"?i=>`translate(${i+n.tickOffset},0)`:i=>`translate(0,${i})`;return(i,o)=>{i.attr("transform",s=>{const l=o(s);return De(s)?a(l):null})}}scaleExtent(e){const n=e[0],a=e[e.length-1];return n0?i:1,o]).range(e.range());s=c.ticks();for(let f=o.toFixed().length;s.length>15;f--)s=c.ticks(f);s.splice(0,1,i),s.splice(s.length-1,1,o)}else s=e.ticks(...this.config.tickArguments||[]);s=s.map(c=>ze(c)&&he(c)&&!isNaN(c)&&Math.round(c*10)/10||c)}return s}copyScale(){const e=this.scale.copy();return e.domain().length||e.domain(this.scale.domain()),e.type=this.scale.type,e}textFormatted(e){const n=this.config.tickFormat,a=/\d+\.\d+0{5,}\d$/.test(e)?+String(e).replace(/0+\d$/,""):e,i=n?n(a):a;return Qe(i)?i:""}transitionise(e){const{config:n}=this;let a=e;if(n.withoutTransition)a=e.interrupt();else if(n.transition||!this.owner.params.noTransition)try{a=e.transition(n.transition)}catch(i){}return a}}var Ey=Object.defineProperty,by=(t,e,n)=>e in t?Ey(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n,za=(t,e,n)=>by(t,typeof e!="symbol"?e+"":e,n);class Ry{constructor(e={}){za(this,"helper"),za(this,"config"),za(this,"params"),za(this,"g"),za(this,"generatedTicks");const n={innerTickSize:6,outerTickSize:e.outerTick?6:0,orient:"bottom",range:[],tickArguments:null,tickCentered:null,tickCulling:!0,tickFormat:null,tickLength:9,tickOffset:0,tickPadding:3,tickValues:null,transition:null,noTransition:e.noTransition};n.tickLength=Math.max(n.innerTickSize,0)+n.tickPadding,this.config=n,this.params=e,this.helper=new Ou(this)}create(e){const n=this,{config:a,helper:i,params:o}=n,{scale:s}=i,{orient:l}=a,c=this.splitTickText.bind(n),f=/^(left|right)$/.test(l),g=/^(top|bottom)$/.test(l),v=i.getTickTransformSetter(g?"x":"y"),m=v===i.axisX?"y":"x",S=/^(top|left)$/.test(l)?-1:1,P=o.tickTextRotate;this.config.range=s.rangeExtent?s.rangeExtent():i.scaleExtent((o.orgXScale||s).range());const{innerTickSize:N,tickLength:L,range:w}=a,X=o.id,W=X&&/^(x|y|y2)$/.test(X)?o.config[`axis_${X}_tick_text_position`]:{x:0,y:0},H=X==="subX"?"subchart_axis_x":`axis_${X}`,k=o.config[`${H}_show`],K={tick:k?o.config[`${H}_tick_show`]:!1,text:k?o.config[`${H}_tick_text_show`]:!1},at=o.config.axis_evalTextSize;let ht;e.each(function(){const $t=ot(this);let dt=this.__chart__||s,st=i.copyScale();ht=$t,this.__chart__=st,a.tickOffset=o.isCategory?(st(1)-st(0))/2:0;const Vt=$t.selectAll(".domain").data([0]);if(Vt.enter().append("path").attr("class","domain").merge(Vt).attr("d",()=>{const vt=a.outerTickSize*S;return g?`M${w[0]},${vt}V0H${w[1]}V${vt}`:`M${vt},${w[0]}H0V${w[1]}H${vt}`}),K.tick||K.text){const vt=a.tickValues||i.generateTicks(st,f);n.generatedTicks=vt;let Q=$t.selectAll(".tick").data(vt,st);const St=Q.enter().insert("g",".domain").attr("class","tick"),ct=Q.exit().remove();Q=St.merge(Q),K.tick&&St.append("line"),K.text&&St.append("text");const At=Q.select("text"),Gt=ve(at)?at.bind(n.params.owner.api)(At.node()):Ou.getSizeFor1Char(At,at),Bt=[];let Kt=At.selectAll("tspan").data((be,Oe)=>{const Ce=o.tickMultiline?c(be,st,vt,f,Gt.w):je(i.textFormatted(be))?i.textFormatted(be).concat():[i.textFormatted(be)];return Bt[Oe]=Ce.length,Ce.map(He=>({index:Oe,splitted:He}))});Kt.exit().remove(),Kt=Kt.enter().append("tspan").merge(Kt).text(be=>be.splitted),Kt.attr("x",g?0:L*S).attr("dx",(()=>{let be=0;return/(top|bottom)/.test(l)&&P&&(be=8*Math.sin(Math.PI*(P/180))*(l==="top"?-1:1)),be+(W.x||0)})()).attr("dy",(be,Oe)=>{const Ce=".71em";let He=0;return l!=="top"&&(He=Gt.h,Oe===0&&(He=f?-((Bt[be.index]-1)*(Gt.h/2)-3):W.y===0?Ce:0)),he(He)&&W.y?He+W.y:He||Ce});const ne=Q.select("line"),le=Q.select("text");if(St.select("line").attr(`${m}2`,N*S),St.select("text").attr(m,L*S),n.setTickLineTextPosition(ne,le),o.tickTitle){const be=le.select("title");(be.empty()?le.append("title"):be).text(Oe=>o.tickTitle[Oe])}if(st.bandwidth){const be=st,Oe=be.bandwidth()/2;dt=Ce=>be(Ce)+Oe,st=dt}else dt.bandwidth?dt=st:v(ct,st);Q=o.owner.state.flowing?i.transitionise(Q):o.owner.$T(Q),v(St,dt),v(Q.style("opacity",null),st)}}),this.g=ht}getGeneratedTicks(e){var n;const a=((n=this.generatedTicks)==null?void 0:n.length)-1;let i=this.generatedTicks;if(a>e){const o=Math.round(a/e+.1);i=this.generatedTicks.map((s,l)=>l%o===0?s:null).filter(s=>s!==null).splice(0,e)}return i}getTickXY(){const{config:e}=this,n={x:0,y:0};return this.params.isCategory&&(n.x=e.tickCentered?0:e.tickOffset,n.y=e.tickCentered?e.tickOffset:0),n}getTickSize(e){const{scale:n}=this.helper,{config:a}=this,{innerTickSize:i,range:o}=a,s=n(e)+(a.tickCentered?0:a.tickOffset);return o[0]{const N=["start","end"];return o==="top"&&N.reverse(),P?N[P>0?0:1]:"middle"},g=P=>P?`rotate(${P})`:null,v=P=>{const N=P/(o==="bottom"?15:23);return P?11.5-2.5*N*(P>0?1:-1):s},{config:{axis_rotated:m,axis_x_tick_text_inner:S}}=this.params.owner;switch(o){case"bottom":e.attr("x1",a.x).attr("x2",a.x).attr("y2",this.getTickSize.bind(this)),n.attr("x",0).attr("y",v(c)).style("text-anchor",f(c)).style("text-anchor",(P,N,{length:L})=>!m&&N===0&&(S===!0||S.first)?"start":!m&&N===L-1&&(S===!0||S.last)?"end":f(c)).attr("transform",g(c));break;case"top":e.attr("x2",0).attr("y2",-i),n.attr("x",0).attr("y",-v(c)*2).style("text-anchor",f(c)).attr("transform",g(c));break;case"left":e.attr("x2",-i).attr("y1",a.y).attr("y2",a.y),n.attr("x",-s).attr("y",l).style("text-anchor","end");break;case"right":e.attr("x2",i).attr("y2",0),n.attr("x",s).attr("y",0).style("text-anchor","start")}}splitTickText(e,n,a,i,o){const{params:s}=this,l=this.helper.textFormatted(e),c=ze(l)&&l.indexOf(` +`)>-1?l.split(` +`):[];if(c.length)return c;if(je(l))return l;let f=s.tickWidth;(!f||f<=0)&&(f=i?95:s.isCategory?(s.isInverted?n(a[0])-n(a[1]):n(a[1])-n(a[0]))-12:110);function g(v,m){let S,P,N;for(let L=1;L{const S=v+1;return Se(this.helper.scale.domain());else{if(!arguments.length)return n.tickValues;n.tickValues=e}return this}setTransition(e){return this.config.transition=e,this}}var Iy=Object.defineProperty,Oy=(t,e,n)=>e in t?Iy(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n,pr=(t,e,n)=>Oy(t,typeof e!="symbol"?e+"":e,n),Cy={getAxisInstance:function(){return this.axis||new Py(this)}};class Py{constructor(e){pr(this,"owner"),pr(this,"x"),pr(this,"subX"),pr(this,"y"),pr(this,"y2"),pr(this,"axesList",{}),pr(this,"tick",{x:null,y:null,y2:null}),pr(this,"xs",[]),pr(this,"orient",{x:"bottom",y:"left",y2:"right",subX:"bottom"}),this.owner=e,this.setOrient()}getAxisClassName(e){return`${Tn.axis} ${Tn[`axis${Cn(e)}`]}`}isHorizontal(e,n){const a=e.config.axis_rotated;return n?a:!a}isCategorized(){const{config:e,state:n}=this.owner;return e.axis_x_type.indexOf("category")>=0||n.hasRadar}isCustomX(){const{config:e}=this.owner;return!this.isTimeSeries()&&(e.data_x||cn(e.data_xs))}isTimeSeries(e="x"){return this.owner.config[`axis_${e}_type`]==="timeseries"}isLog(e="x"){return this.owner.config[`axis_${e}_type`]==="log"}isTimeSeriesY(){return this.isTimeSeries("y")}getAxisType(e="x"){let n="linear";return this.isTimeSeries(e)?n=this.owner.config.axis_x_localtime?"time":"utc":this.isLog(e)&&(n="log"),n}getExtent(){const e=this.owner,{config:n,scale:a}=e;let i=n.axis_x_extent;if(i){if(ve(i))i=i.bind(e.api)(e.getXDomain(e.data.targets),a.subX);else if(this.isTimeSeries()&&i.every(isNaN)){const o=Yn.bind(e);i=i.map(s=>a.subX(o(s)))}}return i}init(){const e=this.owner,{config:n,$el:{main:a,axis:i},state:{clip:o}}=e,s=["x","y"];n.axis_y2_show&&s.push("y2"),s.forEach(l=>{const c=this.getAxisClassName(l);i[l]=a.append("g").attr("class",c).attr("clip-path",()=>{let f=null;return l==="x"?f=o.pathXAxis:l==="y"&&(f=o.pathYAxis),f}).attr("transform",e.getTranslate(l)).style("visibility",n[`axis_${l}_show`]?null:"hidden"),this.generateAxes(l)})}setOrient(){const e=this.owner,{axis_rotated:n,axis_y_inner:a,axis_y2_inner:i}=e.config;this.orient={x:n?"left":"bottom",y:n?a?"top":"bottom":a?"right":"left",y2:n?i?"bottom":"top":i?"left":"right",subX:n?"left":"bottom"}}generateAxes(e){const n=this.owner,{config:a}=n,i=[],o=a[`axis_${e}_axes`],s=a.axis_rotated;let l;e==="x"?l=s?Iu:Ru:e==="y"?l=s?Ru:Iu:e==="y2"&&(l=s?Ty:$y),o.length&&o.forEach(c=>{const f=c.tick||{},g=n.scale[e].copy();c.domain&&g.domain(c.domain),i.push(l(g).ticks(f.count).tickFormat(ve(f.format)?f.format.bind(n.api):v=>v).tickValues(f.values).tickSizeOuter(f.outer===!1?0:6))}),this.axesList[e]=i}updateAxes(){const e=this.owner,{config:n,$el:{main:a},$T:i}=e;Object.keys(this.axesList).forEach(o=>{const s=n[`axis_${o}_axes`],l=e.scale[o].copy(),c=l.range();this.axesList[o].forEach((f,g)=>{const v=f.scale().range();c.every((P,N)=>P===v[N])||f.scale().range(c);const m=`${this.getAxisClassName(o)}-${g+1}`;let S=a.select(`.${m.replace(/\s/,".")}`);S.empty()?S=a.append("g").attr("class",m).style("visibility",n[`axis_${o}_show`]?null:"hidden").call(f):(s[g].domain&&l.domain(s[g].domain),i(S).call(f.scale(l))),S.attr("transform",e.getTranslate(o,g+1))})})}setAxis(e,n,a,i){const o=this.owner;e!=="subX"&&(this.tick[e]=this.getTickValues(e)),this[e]=this.getAxis(e,n,a,e==="x"&&(o.scale.zoom||o.config.subchart_show||o.state.resizing)?!0:i)}getAxis(e,n,a,i,o){const s=this.owner,{config:l}=s,c=/^(x|subX)$/.test(e),f=c?"x":e,g=c&&this.isCategorized(),v=this.orient[e],m=o?0:s.getAxisTickRotate(f);let S;if(c)S=e==="subX"?s.format.subXAxisTick:s.format.xAxisTick;else{const X=l[`axis_${e}_tick_format`];ve(X)&&(S=X.bind(s.api))}let P=this.tick[f];const N=ea({outerTick:a,noTransition:i,config:l,id:e,tickTextRotate:m,owner:s},c&&{isCategory:g,isInverted:l.axis_x_inverted,tickMultiline:l.axis_x_tick_multiline,tickWidth:l.axis_x_tick_width,tickTitle:g&&l.axis_x_tick_tooltip&&s.api.categories(),orgXScale:s.scale.x});c||(N.tickStepSize=l[`axis_${f}_tick_stepSize`]);const L=new Ry(N).scale(c&&s.scale.zoom||n).orient(v);if(c&&this.isTimeSeries()&&P&&!ve(P)){const X=Yn.bind(s);P=P.map(W=>X(W))}else!c&&this.isTimeSeriesY()&&(L.ticks(l.axis_y_tick_time_value),P=null);P&&L.tickValues(P),L.tickFormat(S||!c&&s.isStackNormalized()&&(X=>`${X}%`)),g&&(L.tickCentered(l.axis_x_tick_centered),qn(l.axis_x_tick_culling)&&(l.axis_x_tick_culling=!1));const w=l[`axis_${f}_tick_count`];return w&&L.ticks(w),L}updateXAxisTickValues(e,n){var a;const i=this.owner,{config:o}=i,s=o.axis_x_tick_fit;let l=o.axis_x_tick_count,c;return(s||l&&s)&&(c=i.mapTargetsToUniqueXs(e),this.isCategorized()&&l>c.length&&(l=c.length),c=this.generateTickValues(c,l,this.isTimeSeries())),n?n.tickValues(c):this.x&&(this.x.tickValues(c),(a=this.subX)==null||a.tickValues(c)),c}getId(e){const{config:n,scale:a}=this.owner;let i=n.data_axes[e];return(!i||!a[i])&&(i="y"),i}getXAxisTickFormat(e){const n=this.owner,{config:a,format:i}=n,o=e&&a.subchart_axis_x_tick_format||a.axis_x_tick_format,s=this.isTimeSeries(),l=this.isCategorized();let c;return o?ve(o)?c=o.bind(n.api):s&&(c=f=>f?i.axisTime(o)(f):""):c=s?i.defaultAxisTime:l?n.categoryName:f=>f<0?f.toFixed(0):f,ve(c)?f=>c.apply(n,l?[f,n.categoryName(f)]:[f]):c}getTickValues(e){const n=this.owner,a=n.config[`axis_${e}_tick_values`],i=n[`${e}Axis`];return(ve(a)?a.call(n.api):a)||(i?i.tickValues():void 0)}getLabelOptionByAxisId(e){return this.owner.config[`axis_${e}_label`]}getLabelText(e){const n=this.getLabelOptionByAxisId(e);return ze(n)?n:n?n.text:null}setLabelText(e,n){const a=this.owner,{config:i}=a,o=this.getLabelOptionByAxisId(e);ze(o)?i[`axis_${e}_label`]=n:o&&(o.text=n)}getLabelPosition(e,n){const a=this.owner.config.axis_rotated,i=this.getLabelOptionByAxisId(e),o=nr(i)&&i.position?i.position:n[+!a],s=l=>!!~o.indexOf(l);return{isInner:s("inner"),isOuter:s("outer"),isLeft:s("left"),isCenter:s("center"),isRight:s("right"),isTop:s("top"),isMiddle:s("middle"),isBottom:s("bottom")}}getAxisLabelPosition(e){return this.getLabelPosition(e,e==="x"?["inner-top","inner-right"]:["inner-right","inner-top"])}getLabelPositionById(e){return this.getAxisLabelPosition(e)}xForAxisLabel(e){const n=this.owner,{state:{width:a,height:i}}=n,o=this.getAxisLabelPosition(e);let s=o.isMiddle?-i/2:0;return this.isHorizontal(n,e!=="x")?s=o.isLeft?0:o.isCenter?a/2:a:o.isBottom&&(s=-i),s}textAnchorForAxisLabel(e){const n=this.owner,a=this.getAxisLabelPosition(e);let i=a.isMiddle?"middle":"end";return this.isHorizontal(n,e!=="x")?i=a.isLeft?"start":a.isCenter?"middle":"end":a.isBottom&&(i="start"),i}dxForAxisLabel(e){const n=this.owner,a=this.getAxisLabelPosition(e);let i=a.isBottom?"0.5em":"0";return this.isHorizontal(n,e!=="x")?i=a.isLeft?"0.5em":a.isRight?"-0.5em":"0":a.isTop&&(i="-0.5em"),i}dyForAxisLabel(e){const n=this.owner,{config:a}=n,i=a.axis_rotated,o=this.getAxisLabelPosition(e).isInner,s=a[`axis_${e}_tick_rotate`]?n.getHorizontalAxisHeight(e):0,{width:l}=this.getMaxTickSize(e);let c;if(e==="x"){const f=a.axis_x_height;i?c=o?"1.2em":-25-l:o?c="-0.5em":f?c=f-10:s?c=s-10:c="3em"}else c={y:["-0.5em",10,"3em","1.2em",10],y2:["1.2em",-20,"-2.2em","-0.5em",15]}[e],i?o?c=c[0]:s?c=s*(e==="y2"?-1:1)-c[1]:c=c[2]:c=o?c[3]:(c[4]+(a[`axis_${e}_inner`]?0:l+c[4]))*(e==="y"?-1:1);return c}getMaxTickSize(e,n){const a=this.owner,{config:i,state:{current:o},$el:{svg:s,chart:l}}=a,c=o.maxTickSize[e],f=`axis_${e}`,g={width:0,height:0};if(n||!i[`${f}_show`]||c.width>0&&a.filterTargetsToShow().length===0)return c;if(s){const v=/^y2?$/.test(e),m=a.filterTargetsToShow(a.data.targets),S=a.scale[e].copy().domain(a[`get${v?"Y":"X"}Domain`](m,e)),P=S.domain(),N=P[0]===P[1]&&P.every(K=>K>0),L=je(c.domain)&&c.domain[0]===c.domain[1]&&c.domain.every(K=>K>0);if(N||L)return c.size;c.domain=P,v||c.ticks.splice(0);const w=this.getAxis(e,S,!1,!1,!0),X=i[`${f}_tick_rotate`],W=i[`${f}_tick_count`];!i[`${f}_tick_values`]&&W&&w.tickValues(this.generateTickValues(P,W,v?this.isTimeSeriesY():this.isTimeSeries())),!v&&this.updateXAxisTickValues(m,w);const k=l.append("svg").style("visibility","hidden").style("position","fixed").style("top","0").style("left","0");w.create(k),k.selectAll("text").attr("transform",he(X)?`rotate(${X})`:null).each(function(K,at){const{width:ht,height:$t}=this.getBoundingClientRect();g.width=Math.max(g.width,ht),g.height=Math.max(g.height,$t),v||(c.ticks[at]=ht)}),k.remove()}return Object.keys(g).forEach(v=>{g[v]>0&&(c[v]=g[v])}),c}getXAxisTickTextY2Overflow(e){const n=this.owner,{axis:a,config:i,state:{current:o,isLegendRight:s,legendItemWidth:l}}=n,c=n.getAxisTickRotate("x"),f=c>0&&c<90;if((a.isCategorized()||a.isTimeSeries())&&i.axis_x_tick_fit&&(!i.axis_x_tick_culling||qn(i.axis_x_tick_culling))&&!i.axis_x_tick_multiline&&f){const g=i.axis_y2_show&&o.maxTickSize.y2.width||0,v=s&&l||0,m=o.width-n.getCurrentPaddingByDirection("left"),S=this.getXAxisTickMaxOverflow(c,m-e)-g-v,P=Math.max(0,S)+e;return Math.min(P,m/2)}return 0}getXAxisTickMaxOverflow(e,n){const a=this.owner,{axis:i,config:o,state:s}=a,l=i.isTimeSeries(),c=s.current.maxTickSize.x.ticks,f=c.length,{left:g,right:v}=s.axis.x.padding;let m=0;const S=f-(l&&o.axis_x_tick_fit?.5:0);for(let L=0;L{const c=this.getLabelText(l),f=`axis${Cn(l)}`,g=Tn[`${f}Label`];if(c){let v=i.select(`text.${g}`);v.empty()&&(v=i.select(`g.${Tn[f]}`).insert("text",":first-child").attr("class",g).attr("transform",["rotate(-90)",null][l==="x"?+!s:+s]).style("text-anchor",()=>this.textAnchorForAxisLabel(l))),o(v,e).attr("x",()=>this.xForAxisLabel(l)).attr("dx",()=>this.dxForAxisLabel(l)).attr("dy",()=>this.dyForAxisLabel(l)).text(c)}})}getPadding(e,n,a,i){const o=he(e)?e:e[n];return De(o)?this.owner.convertPixelToScale(/(bottom|top)/.test(n)?"y":"x",o,i):a}generateTickValues(e,n,a){let i=e;if(n){const o=ve(n)?n():n;if(o===1)i=[e[0]];else if(o===2)i=[e[0],e[e.length-1]];else if(o>2){const s=this.isCategorized(),l=o-2,c=e[0],f=e[e.length-1],g=(f-c)/(l+1);let v;i=[c];for(let m=0;mo-s)),i}generateTransitions(e){const n=this.owner,{$el:{axis:a},$T:i}=n,[o,s,l,c]=["x","y","y2","subX"].map(f=>i(a[f],e));return{axisX:o,axisY:s,axisY2:l,axisSubX:c}}redraw(e,n,a){const i=this.owner,{config:o,state:s,$el:l}=i,c=n?"0":null;["x","y","y2","subX"].forEach(f=>{const g=this[f],v=l.axis[f];g&&v&&(!a&&!o.transition_duration&&(g.config.withoutTransition=!0),v.style("opacity",c),g.create(e[`axis${Cn(f)}`]))}),this.updateAxes(),!s.rendered&&o.axis_tooltip&&this.setAxisTooltip()}redrawAxis(e,n,a,i,o){var s,l,c;const f=this.owner,{config:g,scale:v,$el:m}=f,S=!!v.zoom;let P;!S&&this.isCategorized()&&e.length===0&&v.x.domain([0,m.axis.x.selectAll(".tick").size()]),v.x&&e.length?(!S&&f.updateXDomain(e,n.UpdateXDomain,n.UpdateOrgXDomain,n.TrimXDomain),g.axis_x_tick_values||this.updateXAxisTickValues(e)):this.x&&(this.x.tickValues([]),(s=this.subX)==null||s.tickValues([])),g.zoom_rescale&&!i&&(P=v.x.orgDomain()),["y","y2"].forEach(N=>{const L=`axis_${N}_`,w=v[N];if(w){const X=g[`${L}tick_values`],W=g[`${L}tick_count`];if(w.domain(f.getYDomain(e,N,P)),!X&&W){const H=f.axis[N],k=w.domain();H.tickValues(this.generateTickValues(k,k.every(K=>K===0)?1:W,this.isTimeSeriesY()))}}}),this.redraw(a,f.hasArcType(),o),this.updateLabels(n.Transition),(n.UpdateXDomain||n.UpdateXAxis||n.Y)&&e.length&&this.setCulling(),n.Y&&((l=v.subY)==null||l.domain(f.getYDomain(e,"y")),(c=v.subY2)==null||c.domain(f.getYDomain(e,"y2")))}setCulling(){const e=this.owner,{config:n,state:{clip:a,current:i},$el:o}=e;["subX","x","y","y2"].forEach(s=>{const l=o.axis[s],f=`axis_${s==="subX"?"x":s}_tick_culling`,g=n[f];if(l&&g){const v=l.selectAll(".tick"),m=na(v.data()),S=m.length,P=n[`${f}_max`],N=n[`${f}_lines`];let L;if(S){for(let w=1;w{var f,g,v;if(ze(l)||l[c])if(s[c]=(f=o[c])==null?void 0:f.append("text").classed(Tn[`axis${c.toUpperCase()}Tooltip`],!0).attr("filter",n.updateTextBGColor({id:c},l)),a){const m=c==="x"?"x":"y",S=c==="y"?"1.15em":c==="x"?"-0.3em":"-0.4em";(g=s[c])==null||g.attr(m,S).attr(`d${c==="x"?"y":"x"}`,c==="x"?"0.4em":"-1.3em").style("text-anchor",c==="x"?"end":null)}else{const m=c==="x"?"y":"x",S=c==="x"?"1.15em":`${c==="y"?"-":""}0.4em`;(v=s[c])==null||v.attr(m,S).attr(`d${c==="x"?"x":"y"}`,c==="x"?"-1em":"0.3em").style("text-anchor",c==="y"?"end":null)}})}}var wy={initEventRect(){this.$el.main.select(`.${Se.chart}`).append("g").attr("class",Zn.eventRects).style("fill-opacity","0")},redrawEventRect(){var t;const e=this,{config:n,state:a,$el:i}=e,o=e.isMultipleX(),s=n.axis_x_inverted;if(i.eventRect)e.updateEventRect(i.eventRect,!0);else if(e.data.targets.length){const c=e.$el.main.select(`.${Zn.eventRects}`).style("cursor",n.zoom_enabled&&n.zoom_type!=="drag"?n.axis_rotated?"ns-resize":"ew-resize":null).classed(Zn.eventRectsMultiple,o).classed(Zn.eventRectsSingle,!o).selectAll(`.${Zn.eventRect}`).data([0]).enter().append("rect");e.updateEventRect(c),e.updateEventType(c),c.call(e.getDraggableSelection()),i.eventRect=c,e.state.inputType==="touch"&&!i.svg.on("touchstart.eventRect")&&!e.hasArcType()&&e.bindTouchOnEventRect(),a.rendered&&e.updateEventRect(i.eventRect,!0)}if(!o){const l=e.getMaxDataCountTarget();(!n.data_xSort||s)&&l.sort((c,f)=>s?f.x-c.x:c.x-f.x),e.updateDataIndexByX(l),e.updateXs(l),(t=e.updatePointClass)==null||t.call(e,!0),a.eventReceiver.data=l}e.updateEventRectData()},bindTouchOnEventRect(){const t=this,{config:e,state:n,$el:{eventRect:a,svg:i}}=t,o=m=>{if(t.isMultipleX())t.selectRectForMultipleXs(m);else{const S=t.getDataIndexFromEvent(n.event);t.callOverOutForTouch(S),S===-1?t.unselectRect():t.selectRectForSingle(m,S)}},s=()=>{t.unselectRect(),t.callOverOutForTouch()},l=e.interaction_inputType_touch.preventDefault,c=Co(l)&&l||!1,f=!isNaN(l)&&l||null;let g;const v=m=>{const S=m.type,N=m.changedTouches[0][`client${e.axis_rotated?"Y":"X"}`];S==="touchstart"?c?m.preventDefault():f!==null&&(g=N):S==="touchmove"&&(c||g===!0||f!==null&&Math.abs(g-N)>=f)&&(g=!0,m.preventDefault())};a.on("touchstart",m=>{n.event=m,t.updateEventRect()}).on("touchstart.eventRect touchmove.eventRect",m=>{if(n.event=m,!a.empty()&&a.classed(Zn.eventRect)){if(n.dragging||n.flowing||t.hasArcType()||m.touches.length>1)return;v(m),o(a.node())}else s()},!0).on("touchend.eventRect",m=>{n.event=m,!a.empty()&&a.classed(Zn.eventRect)&&(t.hasArcType()||!t.toggleShape||n.cancelClick)&&n.cancelClick&&(n.cancelClick=!1)},!0),i.on("touchstart",m=>{n.event=m;const{target:S}=m;S&&S!==a.node()&&s()})},updateEventRect(t,e=!1){const n=this,{state:a,$el:i}=n,{eventReceiver:o,width:s,height:l,rendered:c,resizing:f}=a,g=t||i.eventRect,v=()=>{if(o){const m=Zl(i.chart.node());o.rect=g.node().getBoundingClientRect().toJSON(),o.rect.top+=m.y,o.rect.left+=m.x}};(!c||f||e)&&(g.attr("x",0).attr("y",0).attr("width",s).attr("height",l),(!c||e)&&g.classed(Zn.eventRect,!0)),v()},updateEventType(t){const e=this,n=Co(t),a=n?e.$el.eventRect:t,i=n?t!==(a==null?void 0:a.datum().multipleX):!1;a&&(i&&(a==null||a.on("mouseover mousemove mouseout click",null)),e.isMultipleX()?e.generateEventRectsForMultipleXs(a):e.generateEventRectsForSingleX(a))},updateEventRectData(){const t=this,{config:e,scale:n,state:a}=t,i=n.zoom||n.x,o=e.axis_rotated,s=t.isMultipleX();let l,c,f,g;if(t.updateEventType(s),s)l=0,c=0,f=a.width,g=a.height;else{let S,P;if(t.axis.isCategorized())S=t.getEventRectWidth(),P=N=>i(N.x)-S/2;else{const N=({index:L})=>({prev:t.getPrevX(L),next:t.getNextX(L)});S=L=>{const w=N(L),X=i.domain();let W;return w.prev===null&&w.next===null?W=o?a.height:a.width:w.prev===null?W=(i(w.next)+i(L.x))/2:w.next===null?W=i(X[1])-(i(w.prev)+i(L.x))/2:(Object.keys(w).forEach((H,k)=>{var K;w[H]=(K=w[H])!=null?K:X[k]}),W=Math.max(0,(i(w.next)-i(w.prev))/2)),W},P=L=>{const w=N(L);let X;return w.prev===null&&w.next===null?X=0:w.prev===null?X=i(i.domain()[0]):X=(i(L.x)+i(w.prev))/2,X}}l=o?0:P,c=o?P:0,f=o?a.width:S,g=o?S:a.height}const{eventReceiver:v}=a,m=(S,P)=>ve(S)?S(P):S;v.coords.splice(v.data.length),v.data.forEach((S,P)=>{v.coords[P]={x:m(l,S),y:m(c,S),w:m(f,S),h:m(g,S)}})},selectRectForSingle(t,e){var n,a;const i=this,{config:o,$el:{main:s,circle:l}}=i,c=o.data_selection_enabled,f=o.data_selection_grouped,g=o.data_selection_isselectable,v=o.tooltip_grouped,m=i.getAllValuesOnIndex(e);if(v&&(i.showTooltip(m,t),(n=i.showGridFocus)==null||n.call(i,m),!c||f))return;!l&&s.selectAll(`.${Se.EXPANDED}:not(.${sn.shape}-${e})`).classed(Se.EXPANDED,!1);const S=s.selectAll(`.${sn.shape}-${e}`).classed(Se.EXPANDED,!0).style("cursor",g?"pointer":null).filter(function(P){return i.isWithinShape(this,P)});S.empty()&&!v&&o.interaction_onout&&((a=i.hideGridFocus)==null||a.call(i),i.hideTooltip(),!f&&i.setExpand(e)),S.call(P=>{var N,L;const w=P.data();c&&(f||g!=null&&g.bind(i.api)(w))&&(t.style.cursor="pointer"),v||(i.showTooltip(w,t),(N=i.showGridFocus)==null||N.call(i,w),(L=i.unexpandCircles)==null||L.call(i),P.each(X=>i.setExpand(e,X.id)))})},selectRectForMultipleXs(t,e=!0){const n=this,{config:a,state:i}=n,o=n.filterTargetsToShow(n.data.targets);if(i.dragging||n.hasArcType(o))return;const s=Hn(i.event,t),l=n.findClosestFromTargets(o,s);if(e&&i.mouseover&&(!l||l.id!==i.mouseover.id)&&(a.data_onout.call(n.api,i.mouseover),i.mouseover=void 0),!l){n.unselectRect();return}const f=(n.isBubbleType(l)||n.isScatterType(l)||!a.tooltip_grouped?[l]:n.filterByX(o,l.x)).map(v=>n.addName(v));n.showTooltip(f,t),n.setExpand(l.index,l.id,!0),n.showGridFocus(f);const g=n.dist(l,s);(n.isBarType(l.id)||g{const c=l?e.getDataIndexFromEvent(l):i.currentIdx;return c>-1?i.data[c]:null};o.on("mouseover",l=>{a.event=l,e.updateEventRect(),Object.values(e.$el.axisTooltip).forEach(c=>c==null?void 0:c.style("display",null))}).on("mousemove",function(l){const c=s(l);if(a.event=l,!c)return;let{index:f}=c;const g=n.line_step_type;if(n.line_step_tooltipMatch&&e.hasType("step")&&/^step\-(before|after)$/.test(g)){const m=e.scale.zoom||e.scale.x,S=e.axis.xs[f],P=m.invert(Hn(l,this)[0]);g==="step-after"&&PS&&(f+=1)}e.showAxisGridFocus();const v=n.tooltip_grouped&&f===i.currentIdx;if(a.dragging||a.flowing||e.hasArcType()||v){n.tooltip_show&&v&&e.setTooltipPosition();return}f!==i.currentIdx&&(e.setOverOut(!1,i.currentIdx),i.currentIdx=f),f===-1?e.unselectRect():e.selectRectForSingle(this,f),e.setOverOut(f!==-1,f)}).on("mouseout",l=>{a.event=l,!(!n||e.hasArcType()||i.currentIdx===-1||!n.interaction_onout)&&(e.hideAxisGridFocus(),e.unselectRect(),e.setOverOut(!1,i.currentIdx),i.currentIdx=-1)})}return o},clickHandlerForSingleX(t,e){const n=e,{config:a,state:i,$el:{main:o}}=n;if(!t||n.hasArcType()||i.cancelClick){i.cancelClick&&(i.cancelClick=!1);return}const{index:s}=t;o.selectAll(`.${sn.shape}-${s}`).each(function(l){var c;(a.data_selection_grouped||n.isWithinShape(this,l))&&((c=n.toggleShape)==null||c.call(n,this,l,s),a.data_onclick.bind(n.api)(l,this))})},generateEventRectsForMultipleXs(t){const e=this,{config:n,state:a}=e;t.on("click",function(i){a.event=i,e.clickHandlerForMultipleXS.bind(this)(e)}).datum({multipleX:!0}),a.inputType==="mouse"&&t.on("mouseover mousemove",function(i){a.event=i,e.selectRectForMultipleXs(this)}).on("mouseout",i=>{a.event=i,!(!e.config||e.hasArcType()||!n.interaction_onout)&&e.unselectRect()})},clickHandlerForMultipleXS(t){const e=t,{config:n,state:a}=e,i=e.filterTargetsToShow(e.data.targets);if(e.hasArcType(i))return;const o=Hn(a.event,this),s=e.findClosestFromTargets(i,o),l=e.getPointSensitivity(s);s&&(e.isBarType(s.id)||e.dist(s,o)+t;var Dy={generateFlow(t){const e=this,{data:n,state:a,$el:i}=e;return function(){const o=t.flow.length;a.flowing=!0,n.targets.forEach(l=>{l.values.splice(0,o)}),e.updateXGrid&&e.updateXGrid(!0);const s={};["axis.x","grid.x","gridLines.x","region.list","text","bar","line","area","circle"].forEach(l=>{const c=l.split(".");let f=i[c[0]];f&&c.length>1&&(f=f[c[1]]),f!=null&&f.size()&&(s[l]=f)}),e.hideGridFocus(),e.setFlowList(s,t)}},setFlowList(t,e){const n=this,{flow:a,targets:i}=e,{duration:o=e.duration,index:s,length:l,orgDataCount:c}=a,f=n.getFlowTransform(i,c,s,l),g=ec();let v;g.add(Object.keys(t).map(m=>(v=t[m].transition().ease(My).duration(o),m==="axis.x"?v=v.call(S=>{n.axis.x.setTransition(S).create(S)}):m==="region.list"?v=v.filter(n.isRegionOnX).attr("transform",f):v=v.attr("transform",f),v))),v.call(g,()=>{n.cleanUpFlow(t,e)})},cleanUpFlow(t,e){const n=this,{config:a,state:i,$el:{svg:o}}=n,s=a.axis_rotated,{flow:l,shape:c,xv:f}=e,{cx:g,cy:v,xForText:m,yForText:S}=c.pos,{done:P=()=>{},length:N}=l;N&&(["circle","text","shape","eventRect"].forEach(L=>{const w=[];for(let X=0;X{const w=t[L];if(L!=="axis.x"&&w.attr("transform",null),L==="grid.x")w.attr(i.xgridAttr);else if(L==="gridLines.x")w.attr("x1",s?0:f).attr("x2",s?i.width:f),w.select("text").attr("x",s?i.width:0).attr("y",f);else if(/^(area|bar|line)$/.test(L))w.attr("d",c.type[L]);else if(L==="text")w.attr("x",m).attr("y",S).style("fill-opacity",n.opacityForText.bind(n));else if(L==="circle")if(n.isCirclePoint())w.attr("cx",g).attr("cy",v);else{const X=H=>g(H)-a.point_r,W=H=>v(H)-a.point_r;w.attr("x",X).attr("y",W)}else L==="region.list"&&w.select("rect").filter(n.isRegionOnX).attr("x",n.regionX.bind(n)).attr("width",n.regionWidth.bind(n))}),a.interaction_enabled&&n.redrawEventRect(),P.call(n.api),i.flowing=!1},getFlowTransform(t,e,n,a){const i=this,{data:o,scale:{x:s}}=i,l=o.targets[0].values;let c=i.getValueOnIndex(l,n),f=i.getValueOnIndex(l,n+a),g;const v=s.domain(),m=i.updateXDomain(t,!0,!0);e?e===1||(c==null?void 0:c.x)===(f==null?void 0:f.x)?g=s(v[0])-s(m[0]):g=i.axis.isTimeSeries()?s(v[0])-s(m[0]):s((c==null?void 0:c.x)||0)-s(f.x):l.length!==1?g=s(v[0])-s(m[0]):i.axis.isTimeSeries()?(c=i.getValueOnIndex(l,0),f=i.getValueOnIndex(l,l.length-1),g=s(c.x)-s(f.x)):g=Dr(m)/2;const S=Dr(v)/Dr(m);return`translate(${g},0) scale(${S},1)`}},Ly={initClip(){const t=this,{clip:e,datetimeId:n}=t.state;e.id=`${n}-clip`,e.idXAxis=`${e.id}-xaxis`,e.idYAxis=`${e.id}-yaxis`,e.idGrid=`${e.id}-grid`,e.path=t.getClipPath(e.id),e.pathXAxis=t.getClipPath(e.idXAxis),e.pathYAxis=t.getClipPath(e.idYAxis),e.pathGrid=t.getClipPath(e.idGrid)},getClipPath(t){const e=this,{config:n}=e;return!n.clipPath&&/-clip$/.test(t)||!n.axis_x_clipPath&&/-clip-xaxis$/.test(t)||!n.axis_y_clipPath&&/-clip-yaxis$/.test(t)?null:`url(#${t})`},appendClip(t,e){e&&t.append("clipPath").attr("id",e).append("rect")},setXAxisClipPath(t){const e=this,{config:n,state:{margin:a,width:i,height:o}}=e,s=n.axis_rotated,l=Math.max(30,a.left)-(s?0:20),c=(s?a.top+o+10:a.bottom)+20,f=s?-(1+l):-(l-1),g=-15,v=s?a.left+20:i+10+l;t.attr("x",f).attr("y",g).attr("width",v).attr("height",c)},setYAxisClipPath(t){const e=this,{config:n,state:{margin:a,width:i,height:o}}=e,s=n.axis_rotated,l=Math.max(30,a.left)-(s?20:0),c=n.axis_y_inner,f=c&&!s?n.axis_y_label.text?-20:-1:s?-(1+l):-(l-1),g=-(s?20:a.top),v=(s?i+15+l:a.left+20)+(c?20:0),m=(s?a.bottom+10:a.top+o)+10;t.attr("x",f).attr("y",g).attr("width",v).attr("height",m)},updateXAxisTickClip(){const t=this,{config:e,state:{clip:n,xAxisHeight:a},$el:{defs:i}}=t,o=t.getHorizontalAxisHeight("x");if(i&&!n.idXAxisTickTexts){const s=`${n.id}-xaxisticktexts`;t.appendClip(i,s),n.pathXAxisTickTexts=t.getClipPath(n.idXAxisTickTexts),n.idXAxisTickTexts=s}!e.axis_x_tick_multiline&&t.getAxisTickRotate("x")&&o!==a&&(t.setXAxisTickClipWidth(),t.setXAxisTickTextClipPathWidth()),t.state.xAxisHeight=o},setXAxisTickClipWidth(){const t=this,{config:e,state:{current:{maxTickSize:n}}}=t,a=t.getAxisTickRotate("x");if(!e.axis_x_tick_multiline&&a){const i=Math.sin(Math.PI/180*Math.abs(a));n.x.clipPath=(t.getHorizontalAxisHeight("x")-20)/i}else n.x.clipPath=null},setXAxisTickTextClipPathWidth(){const t=this,{state:{clip:e,current:n},$el:{svg:a}}=t;a&&a.select(`#${e.idXAxisTickTexts} rect`).attr("width",n.maxTickSize.x.clipPath).attr("height",30)}};const Ny=t=>De(t.position)||"end",Fy=t=>t.position==="start"?4:t.position==="middle"?0:-4;function Cu(t,e,n){return a=>{let i=t?0:e;return a.position==="start"?i=t?-n:0:a.position==="middle"&&(i=(t?-n:e)/2),i}}function Pu(t,e){e==="grid"&&t.each(function(){const n=ot(this);["x1","x2","y1","y2"].forEach(a=>n.attr(a,+n.attr(a)))})}var By={hasGrid(){const{config:t}=this;return["x","y"].some(e=>t[`grid_${e}_show`]||t[`grid_${e}_lines`].length)},initGrid(){const t=this;t.hasGrid()&&t.initGridLines(),t.initFocusGrid()},initGridLines(){const t=this,{config:e,state:{clip:n},$el:a}=t;(e.grid_x_lines.length||e.grid_y_lines.length)&&(a.gridLines.main=a.main.insert("g",`.${Se.chart}${e.grid_lines_front?" + *":""}`).attr("clip-path",n.pathGrid).attr("class",`${on.grid} ${on.gridLines}`),a.gridLines.main.append("g").attr("class",on.xgridLines),a.gridLines.main.append("g").attr("class",on.ygridLines),a.gridLines.x=Uc([]))},updateXGrid(t){const e=this,{config:n,scale:a,state:i,$el:{main:o,grid:s}}=e,l=n.axis_rotated,c=e.generateGridData(n.grid_x_type,a.x),f=e.axis.isCategorized()?e.axis.x.tickOffset():0,g=v=>(a.zoom||a.x)(v)+f*(l?-1:1);i.xgridAttr=l?{x1:0,x2:i.width,y1:g,y2:g}:{x1:g,x2:g,y1:0,y2:i.height},s.x=o.select(`.${on.xgrids}`).selectAll(`.${on.xgrid}`).data(c),s.x.exit().remove(),s.x=s.x.enter().append("line").attr("class",on.xgrid).merge(s.x),t||s.x.each(function(){const v=ot(this);Object.keys(i.xgridAttr).forEach(m=>{v.attr(m,i.xgridAttr[m]).style("opacity",()=>v.attr(l?"y1":"x1")===(l?i.height:0)?"0":null)})})},updateYGrid(){const t=this,{axis:e,config:n,scale:a,state:i,$el:{grid:o,main:s}}=t,l=n.axis_rotated,c=g=>a.y(g),f=e.y.getGeneratedTicks(n.grid_y_ticks)||t.scale.y.ticks(n.grid_y_ticks);o.y=s.select(`.${on.ygrids}`).selectAll(`.${on.ygrid}`).data(f),o.y.exit().remove(),o.y=o.y.enter().append("line").attr("class",on.ygrid).merge(o.y),o.y.attr("x1",l?c:0).attr("x2",l?c:i.width).attr("y1",l?0:c).attr("y2",l?i.height:c),Pu(o.y,"grid")},updateGrid(){const t=this,{$el:{grid:e,gridLines:n}}=t;!n.main&&t.initGridLines(),e.main.style("visibility",t.hasArcType()?"hidden":null),t.hideGridFocus(),t.updateGridLines("x"),t.updateGridLines("y")},updateGridLines(t){const e=this,{config:n,$el:{gridLines:a,main:i},$T:o}=e,s=n.axis_rotated,l=t==="x";n[`grid_${t}_show`]&&e[`update${t.toUpperCase()}Grid`]();let c=i.select(`.${on[`${t}gridLines`]}`).selectAll(`.${on[`${t}gridLine`]}`).data(n[`grid_${t}_lines`]);o(c.exit()).style("opacity","0").remove();const f=c.enter().append("g");f.append("line").style("opacity","0"),c=f.merge(c),c.each(function(g){const v=ot(this);v.select("text").empty()&&g.text&&v.append("text").style("opacity","0")}),o(c.attr("class",g=>`${on[`${t}gridLine`]} ${g.class||""}`.trim()).select("text").attr("text-anchor",Ny).attr("transform",()=>l?s?null:"rotate(-90)":s?"rotate(-90)":null).attr("dx",Fy).attr("dy",-5)).text(function(g){var v;return(v=g.text)!=null?v:this.remove()}),a[t]=c},redrawGrid(t){const e=this,{config:{axis_rotated:n},state:{width:a,height:i},$el:{gridLines:o},$T:s}=e,l=e.xv.bind(e),c=e.yv.bind(e);let f=o.x.select("line"),g=o.x.select("text"),v=o.y.select("line"),m=o.y.select("text");return f=s(f,t).attr("x1",n?0:l).attr("x2",n?a:l).attr("y1",n?l:0).attr("y2",n?l:i),g=s(g,t).attr("x",Cu(!n,a,i)).attr("y",l),v=s(v,t).attr("x1",n?c:0).attr("x2",n?c:a).attr("y1",n?0:c).attr("y2",n?i:c),m=s(m,t).attr("x",Cu(n,a,i)).attr("y",c),[f.style("opacity",null),g.style("opacity",null),v.style("opacity",null),m.style("opacity",null)]},initFocusGrid(){const t=this,{config:e,state:{clip:n},$el:a}=t,i=e.grid_front,o=`.${i&&a.gridLines.main?on.gridLines:Se.chart}${i?" + *":""}`,s=a.main.insert("g",o).attr("clip-path",n.pathGrid).attr("class",on.grid);if(a.grid.main=s,e.grid_x_show&&s.append("g").attr("class",on.xgrids),e.grid_y_show&&s.append("g").attr("class",on.ygrids),e.axis_tooltip){const l=s.append("g").attr("class","bb-axis-tooltip");l.append("line").attr("class","bb-axis-tooltip-x"),l.append("line").attr("class","bb-axis-tooltip-y")}e.interaction_enabled&&e.grid_focus_show&&!e.axis_tooltip&&(s.append("g").attr("class",qe.xgridFocus).append("line").attr("class",qe.xgridFocus),e.grid_focus_y&&!e.tooltip_grouped&&s.append("g").attr("class",qe.ygridFocus).append("line").attr("class",qe.ygridFocus))},showAxisGridFocus(){var t,e;const n=this,{config:a,format:i,state:{event:o,width:s,height:l}}=n,c=a.axis_rotated,[f,g]=Hn(o,(t=n.$el.eventRect)==null?void 0:t.node()),v={x:f,y:g};for(const[m,S]of Object.entries(n.$el.axisTooltip)){const P=m==="x"&&!c||m!=="x"&&c?"x":"y",N=v[P];let L=(e=n.scale[m])==null?void 0:e.invert(N);L&&(L=m==="x"&&n.axis.isTimeSeries()?i.xAxisTick(L):L==null?void 0:L.toFixed(2),S==null||S.attr(P,N).text(L))}n.$el.main.selectAll("line.bb-axis-tooltip-x, line.bb-axis-tooltip-y").style("visibility",null).each(function(m,S){const P=ot(this);S===0?P.attr("x1",f).attr("x2",f).attr("y1",S?0:l).attr("y2",S?l:0):P.attr("x1",S?0:s).attr("x2",S?s:0).attr("y1",g).attr("y2",g)})},hideAxisGridFocus(){const t=this;t.$el.main.selectAll("line.bb-axis-tooltip-x, line.bb-axis-tooltip-y").style("visibility","hidden"),Object.values(t.$el.axisTooltip).forEach(e=>e==null?void 0:e.style("display","none"))},showGridFocus(t){var e;const n=this,{config:a,state:{width:i,height:o}}=n,s=a.axis_rotated,l=n.$el.main.selectAll(`line.${qe.xgridFocus}, line.${qe.ygridFocus}`),c=(t||[l.datum()]).filter(v=>v&&De(n.getBaseValue(v)));if(!a.tooltip_show||c.length===0||!a.axis_x_forceAsSingle&&n.hasType("bubble")||n.hasArcType())return;const f=a.grid_focus_edge&&!a.tooltip_grouped,g=n.xx.bind(n);l.style("visibility",null).data(c.concat(c)).each(function(v){const m=ot(this),S={x:g(v),y:n.getYScaleById(v.id)(v.value)};let P;if(m.classed(qe.xgridFocus))P=s?[null,S.x,f?S.y:i,S.x]:[S.x,f?S.y:null,S.x,o];else{const N=n.axis.getId(v.id)==="y2";P=s?[S.y,f&&!N?S.x:null,S.y,f&&N?S.x:o]:[f&&N?S.x:null,S.y,f&&!N?S.x:i,S.y]}["x1","y1","x2","y2"].forEach((N,L)=>m.attr(N,P[L]))}),Pu(l,"grid"),(e=n.showCircleFocus)==null||e.call(n,t)},hideGridFocus(){var t;const e=this,{state:{inputType:n,resizing:a},$el:{main:i}}=e;(n==="mouse"||!a)&&(i.selectAll(`line.${qe.xgridFocus}, line.${qe.ygridFocus}`).style("visibility","hidden"),(t=e.hideCircleFocus)==null||t.call(e))},updateGridFocus(){var t;const e=this,{state:{inputType:n,width:a,height:i,resizing:o},$el:{grid:s}}=e,l=s.main.select(`line.${qe.xgridFocus}`);if(n==="touch")l.empty()?o&&((t=e.showCircleFocus)==null||t.call(e)):e.showGridFocus();else{const c=e.config.axis_rotated;l.attr("x1",c?0:-10).attr("x2",c?a:-10).attr("y1",c?-10:0).attr("y2",c?-10:i)}return!0},generateGridData(t,e){const n=this,a=n.$el.main.select(`.${Tn.axisX}`).selectAll(".tick").size();let i=[];if(t==="year"){const o=n.getXDomain(),[s,l]=o.map(c=>c.getFullYear());for(let c=s;c<=l;c++)i.push(new Date(`${c}-01-01 00:00:00`))}else i=e.ticks(10),i.length>a&&(i=i.filter(o=>String(o).indexOf(".")<0));return i},getGridFilterToRemove(t){return t?e=>{let n=!1;return(je(t)?t.concat():[t]).forEach(a=>{("value"in a&&e.value===a.value||"class"in a&&e.class===a.class)&&(n=!0)}),n}:()=>!0},removeGridLines(t,e){const n=this,{config:a,$T:i}=n,o=n.getGridFilterToRemove(t),s=g=>!o(g),l=e?on.xgridLines:on.ygridLines,c=e?on.xgridLine:on.ygridLine;i(n.$el.main.select(`.${l}`).selectAll(`.${c}`).filter(o)).style("opacity","0").remove();const f=`grid_${e?"x":"y"}_lines`;a[f]=a[f].filter(s)}},Uy={initRegion(){const t=this,{$el:e}=t;e.region.main=e.main.insert("g",":first-child").attr("clip-path",t.state.clip.path).attr("class",$a.regions)},updateRegion(){const t=this,{config:e,$el:{region:n},$T:a}=t;n.main||t.initRegion(),n.main.style("visibility",t.hasArcType()?"hidden":null);const i=n.main.selectAll(`.${$a.region}`).data(e.regions);a(i.exit()).style("opacity","0").remove();const o=i.enter().append("g");o.append("rect").style("fill-opacity","0"),n.list=o.merge(i).attr("class",t.classRegion.bind(t)),n.list.each(function(s){var l;ot(this).select("text").empty()&&((l=s.label)!=null&&l.text)&&ot(this).append("text").style("opacity","0")})},redrawRegion(t){const e=this,{$el:{region:n},$T:a}=e,i=e.regionX.bind(e),o=e.regionY.bind(e),s=["width","height"];let l=n.list.select("rect"),c=n.list.selectAll("text");return l=a(l,t).attr("x",i).attr("y",o).attr("width",e.regionWidth.bind(e)).attr("height",e.regionHeight.bind(e)),c=a(c,t).text(f=>{var g;return(g=f.label)==null?void 0:g.text}).attr("transform",({label:f})=>f.rotated?" rotate(-90)":null).attr("transform",function(f){var g;const{x:v=0,y:m=0,center:S=!1,rotated:P=!1}=(g=f.label)!=null?g:{},N=this.previousElementSibling,L={x:0,y:0};return ze(S)&&["x","y"].forEach((w,X)=>{S.indexOf(w)>-1&&(L[w]=(+N.getAttribute(s[X])-Ma(this)[s[X]])/2)}),`translate(${i(f)+L.x+v}, ${o(f)+L.y+m})${P?" rotate(-90)":""}`}).attr("text-anchor",({label:f})=>f!=null&&f.rotated?"end":null).attr("dy","1em").style("fill",({label:f})=>{var g;return(g=f==null?void 0:f.color)!=null?g:null}),[l.style("fill-opacity",f=>De(f.opacity)?f.opacity:null).on("end",function(){ot(this.parentNode).selectAll("rect:not([x])").remove()}),c.style("opacity",null)]},regionX(t){return this.getRegionSize("x",t)},regionY(t){return this.getRegionSize("y",t)},regionWidth(t){return this.getRegionSize("width",t)},regionHeight(t){return this.getRegionSize("height",t)},getRegionSize(t,e){const n=this,{config:a,scale:i,state:o}=n,s=a.axis_rotated,l=/(x|y|y2)/.test(t),c=l?t==="x":t==="width",f=!l&&n[c?"regionX":"regionY"](e);let g=l?"start":"end",v=l?0:o[t],m;if(e.axis==="y"||e.axis==="y2"?(!l&&!c?g="start":l&&!c&&(g="end"),(c?s:!s)&&g in e&&(m=i[e.axis])):(c?!s:s)&&g in e&&(m=i.zoom||i.x),m){let S=0;v=e[g],n.axis.isTimeSeries(e.axis)?v=Yn.call(n,v):/(x|width)/.test(t)&&n.axis.isCategorized()&&isNaN(v)&&(v=a.axis_x_categories.indexOf(v),S=n.axis.x.tickOffset()*(g==="start"?-1:1)),v=m(v)+S}return l?v:v0&&(!i.axis_x_tick_autorotate||a.needToRotateXAxisTickTexts());return(i.axis_x_tick_multiline||L)&&N.height>S&&(P+=N.height-S),P+(a.axis.getLabelPositionById(t).isInner?0:10)+(t==="y2"&&!f?-10:0)},getEventRectWidth(){const t=this,{config:e,axis:n}=t,a=e.axis_x_inverted,i=n.x.tickInterval();return Math.max(0,a?Math.abs(i):i)},getAxisTickRotate(t){const e=this,{axis:n,config:a,state:i,$el:o}=e;let s=a[`axis_${t}_tick_rotate`];if(t==="x"){const l=n.isCategorized()||n.isTimeSeries();if(a.axis_x_tick_fit&&l){const c=a.axis_x_tick_count,f=i.current.maxTickSize.x.ticks.length;let g=0;if(c?g=c>f?f:c:f&&(g=f),g!==i.axis.x.tickCount){const{targets:v}=e.data;i.axis.x.padding=e.getXDomainPadding([e.getXDomainMinMax(v,"min"),e.getXDomainMinMax(v,"max")],g)}i.axis.x.tickCount=g}o.svg&&a.axis_x_tick_autorotate&&a.axis_x_tick_fit&&!a.axis_x_tick_multiline&&!a.axis_x_tick_culling&&l&&(s=e.needToRotateXAxisTickTexts()?a.axis_x_tick_rotate:0)}return s},needToRotateXAxisTickTexts(){const t=this,{state:{axis:e,current:n,isLegendRight:a,legendItemWidth:i}}=t,o=a&&i,s=n.width-o-t.getCurrentPaddingByDirection("left")-t.getCurrentPaddingByDirection("right"),l=e.x.tickCount+e.x.padding.left+e.x.padding.right,{width:c}=t.axis.getMaxTickSize("x"),f=l?s/l:0;return c>f}},jy={axis_x_clipPath:!0,axis_x_show:!0,axis_x_forceAsSingle:!1,axis_x_type:"indexed",axis_x_localtime:!0,axis_x_categories:[],axis_x_tick_centered:!1,axis_x_tick_format:void 0,axis_x_tick_culling:{},axis_x_tick_culling_max:10,axis_x_tick_culling_lines:!0,axis_x_tick_count:void 0,axis_x_tick_show:!0,axis_x_tick_text_show:!0,axis_x_tick_text_inner:!1,axis_x_tick_text_position:{x:0,y:0},axis_x_tick_fit:!0,axis_x_tick_values:null,axis_x_tick_autorotate:!1,axis_x_tick_rotate:0,axis_x_tick_outer:!0,axis_x_tick_multiline:!0,axis_x_tick_width:null,axis_x_tick_tooltip:!1,axis_x_max:void 0,axis_x_min:void 0,axis_x_inverted:!1,axis_x_padding:{},axis_x_height:void 0,axis_x_extent:void 0,axis_x_label:{},axis_x_axes:[]},Vy={axis_y_clipPath:!0,axis_y_show:!0,axis_y_type:"indexed",axis_y_max:void 0,axis_y_min:void 0,axis_y_inverted:!1,axis_y_center:void 0,axis_y_inner:!1,axis_y_label:{},axis_y_tick_format:void 0,axis_y_tick_culling:!1,axis_y_tick_culling_max:5,axis_y_tick_culling_lines:!0,axis_y_tick_outer:!0,axis_y_tick_values:null,axis_y_tick_rotate:0,axis_y_tick_count:void 0,axis_y_tick_show:!0,axis_y_tick_stepSize:null,axis_y_tick_text_show:!0,axis_y_tick_text_position:{x:0,y:0},axis_y_tick_time_value:void 0,axis_y_padding:{},axis_y_default:void 0,axis_y_axes:[]},Gy={axis_y2_show:!1,axis_y2_type:"indexed",axis_y2_max:void 0,axis_y2_min:void 0,axis_y2_inverted:!1,axis_y2_center:void 0,axis_y2_inner:!1,axis_y2_label:{},axis_y2_tick_format:void 0,axis_y2_tick_culling:!1,axis_y2_tick_culling_max:5,axis_y2_tick_culling_lines:!0,axis_y2_tick_outer:!0,axis_y2_tick_values:null,axis_y2_tick_rotate:0,axis_y2_tick_count:void 0,axis_y2_tick_show:!0,axis_y2_tick_stepSize:null,axis_y2_tick_text_show:!0,axis_y2_tick_text_position:{x:0,y:0},axis_y2_padding:{},axis_y2_default:void 0,axis_y2_axes:[]},Xy=Object.defineProperty,wu=Object.getOwnPropertySymbols,Hy=Object.prototype.hasOwnProperty,Yy=Object.prototype.propertyIsEnumerable,Mu=(t,e,n)=>e in t?Xy(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n,Ts=(t,e)=>{for(var n in e||(e={}))Hy.call(e,n)&&Mu(t,n,e[n]);if(wu)for(var n of wu(e))Yy.call(e,n)&&Mu(t,n,e[n]);return t},Wy=Ts(Ts(Ts({axis_evalTextSize:!0,axis_rotated:!1,axis_tooltip:!1},jy),Vy),Gy),Ky={grid_x_show:!1,grid_x_type:"tick",grid_x_lines:[],grid_y_show:!1,grid_y_lines:[],grid_y_ticks:void 0,grid_focus_edge:!1,grid_focus_show:!0,grid_focus_y:!1,grid_front:!1,grid_lines_front:!0},Zy={data_xs:{},data_xFormat:"%Y-%m-%d",data_xLocaltime:!0,data_xSort:!0,data_axes:{},data_regions:{},data_stack_normalize:!1};const Jy=[sy,ly,cy,uy,fy,dy,hy],Du={axis:Cy,clip:Ly,eventrect:wy,flow:Dy,grid:By,region:Uy,sizeAxis:zy},Lu={optDataAxis:Zy,optAxis:Wy,optGrid:Ky};var I1=Array.prototype.slice;function $s(t){return typeof t=="object"&&"length"in t?t:Array.from(t)}function Le(t){return function(){return t}}function Qy(t,e){return et?1:e>=t?0:NaN}function ky(t){return t}function qy(){var t=ky,e=Qy,n=null,a=Le(0),i=Le(zi),o=Le(0);function s(l){var c,f=(l=$s(l)).length,g,v,m=0,S=new Array(f),P=new Array(f),N=+a.apply(this,arguments),L=Math.min(zi,Math.max(-zi,i.apply(this,arguments)-N)),w,X=Math.min(Math.abs(L)/f,o.apply(this,arguments)),W=X*(L<0?-1:1),H;for(c=0;c0&&(m+=H);for(e!=null?S.sort(function(k,K){return e(P[k],P[K])}):n!=null&&S.sort(function(k,K){return n(l[k],l[K])}),c=0,v=m?(L-f*W)/m:0;c0?H*v:0)+W,P[g]={data:l[g],index:c,value:H,startAngle:N,endAngle:w,padAngle:X};return P}return s.value=function(l){return arguments.length?(t=typeof l=="function"?l:Le(+l),s):t},s.sortValues=function(l){return arguments.length?(e=l,n=null,s):e},s.sort=function(l){return arguments.length?(n=l,e=null,s):n},s.startAngle=function(l){return arguments.length?(a=typeof l=="function"?l:Le(+l),s):a},s.endAngle=function(l){return arguments.length?(i=typeof l=="function"?l:Le(+l),s):i},s.padAngle=function(l){return arguments.length?(o=typeof l=="function"?l:Le(+l),s):o},s}var _y=Math.pow;const Ss=Math.PI,As=2*Ss,Gr=1e-6,tx=As-Gr;function Nu(t){this._+=t[0];for(let e=1,n=t.length;e=0))throw new Error(`invalid digits: ${t}`);if(e>15)return Nu;const n=_y(10,e);return function(a){this._+=a[0];for(let i=1,o=a.length;iGr)if(!(Math.abs(v*c-f*g)>Gr)||!o)this._append`L${this._x1=e},${this._y1=n}`;else{let S=a-s,P=i-l,N=c*c+f*f,L=S*S+P*P,w=Math.sqrt(N),X=Math.sqrt(m),W=o*Math.tan((Ss-Math.acos((N+m-L)/(2*w*X)))/2),H=W/X,k=W/w;Math.abs(H-1)>Gr&&this._append`L${e+H*g},${n+H*v}`,this._append`A${o},${o},0,0,${+(v*S>g*P)},${this._x1=e+k*c},${this._y1=n+k*f}`}}arc(e,n,a,i,o,s){if(e=+e,n=+n,a=+a,s=!!s,a<0)throw new Error(`negative radius: ${a}`);let l=a*Math.cos(i),c=a*Math.sin(i),f=e+l,g=n+c,v=1^s,m=s?i-o:o-i;this._x1===null?this._append`M${f},${g}`:(Math.abs(this._x1-f)>Gr||Math.abs(this._y1-g)>Gr)&&this._append`L${f},${g}`,a&&(m<0&&(m=m%As+As),m>tx?this._append`A${a},${a},0,1,${v},${e-l},${n-c}A${a},${a},0,1,${v},${this._x1=f},${this._y1=g}`:m>Gr&&this._append`A${a},${a},0,${+(m>=Ss)},${v},${this._x1=e+a*Math.cos(o)},${this._y1=n+a*Math.sin(o)}`)}rect(e,n,a,i){this._append`M${this._x0=this._x1=+e},${this._y0=this._y1=+n}h${a=+a}v${+i}h${-a}Z`}toString(){return this._}}function nx(){return new Wi}nx.prototype=Wi.prototype;function O1(t=3){return new Wi(+t)}function Es(t){let e=3;return t.digits=function(n){if(!arguments.length)return e;if(n==null)e=null;else{const a=Math.floor(n);if(!(a>=0))throw new RangeError(`invalid digits: ${n}`);e=a}return t},()=>new Wi(e)}function rx(t){return t.innerRadius}function ax(t){return t.outerRadius}function ix(t){return t.startAngle}function ox(t){return t.endAngle}function sx(t){return t&&t.padAngle}function lx(t,e,n,a,i,o,s,l){var c=n-t,f=a-e,g=s-i,v=l-o,m=v*c-g*f;if(!(m*mQ*Q+St*St&&(ht=dt,$t=st),{cx:ht,cy:$t,x01:-g,y01:-v,x11:ht*(i/k-1),y11:$t*(i/k-1)}}function Fu(){var t=rx,e=ax,n=Le(0),a=null,i=ix,o=ox,s=sx,l=null,c=Es(f);function f(){var g,v,m=+t.apply(this,arguments),S=+e.apply(this,arguments),P=i.apply(this,arguments)-Ui,N=o.apply(this,arguments)-Ui,L=Hc(N-P),w=N>P;if(l||(l=g=c()),Sbn))l.moveTo(0,0);else if(L>zi-bn)l.moveTo(S*jr(P),S*rr(P)),l.arc(0,0,S,P,N,!w),m>bn&&(l.moveTo(m*jr(N),m*rr(N)),l.arc(0,0,m,N,P,w));else{var X=P,W=N,H=P,k=N,K=L,at=L,ht=s.apply(this,arguments)/2,$t=ht>bn&&(a?+a.apply(this,arguments):oa(m*m+S*S)),dt=fs(Hc(S-m)/2,+n.apply(this,arguments)),st=dt,Vt=dt,vt,Q;if($t>bn){var St=Yc($t/m*rr(ht)),ct=Yc($t/S*rr(ht));(K-=St*2)>bn?(St*=w?1:-1,H+=St,k-=St):(K=0,H=k=(P+N)/2),(at-=ct*2)>bn?(ct*=w?1:-1,X+=ct,W-=ct):(at=0,X=W=(P+N)/2)}var At=S*jr(X),Gt=S*rr(X),Bt=m*jr(k),Kt=m*rr(k);if(dt>bn){var ne=S*jr(W),le=S*rr(W),be=m*jr(H),Oe=m*rr(H),Ce;if(Lbn?Vt>bn?(vt=Ki(be,Oe,At,Gt,S,Vt,w),Q=Ki(ne,le,Bt,Kt,S,Vt,w),l.moveTo(vt.cx+vt.x01,vt.cy+vt.y01),Vtbn)||!(K>bn)?l.lineTo(Bt,Kt):st>bn?(vt=Ki(Bt,Kt,ne,le,m,-st,w),Q=Ki(At,Gt,be,Oe,m,-st,w),l.lineTo(vt.cx+vt.x01,vt.cy+vt.y01),ste in t?cx(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n,gx=(t,e)=>{for(var n in e||(e={}))dx.call(e,n)&&Uu(t,n,e[n]);if(Bu)for(var n of Bu(e))hx.call(e,n)&&Uu(t,n,e[n]);return t},vx=(t,e)=>ux(t,fx(e));function zu(t=0){const e=this,{config:n,state:a}=e,i=e.hasMultiArcGauge(),o=a.gaugeArcWidth/e.filterTargetsToShow(e.data.targets).length,s=t?Math.min(a.radiusExpanded*t-a.radius,o*.8-(1-t)*100):0;return{inner(l){const{innerRadius:c}=e.getRadius(l);return i?a.radius-o*(l.index+1):he(c)?c:0},outer(l){const{outerRadius:c}=e.getRadius(l);let f;if(i)f=a.radius-o*l.index+s;else if(e.hasType("polar")&&!t)f=e.getPolarOuterRadius(l,c);else if(f=c,t){let{radiusExpanded:g}=a;a.radius!==c&&(g-=Math.abs(a.radius-c)),f=g*t}return f},corner(l,c){const{arc_cornerRadius_ratio:f=0,arc_cornerRadius:g=0}=n,{data:{id:v},value:m}=l;let S=0;return f?S=f*c:S=he(g)?g:g.call(e.api,v,m,c),S}}}function bs(t){return function(e){const n=({startAngle:i=0,endAngle:o=0,padAngle:s=0})=>({startAngle:i,endAngle:o,padAngle:s}),a=Qr(n(this._current),n(e));return this._current=e,function(i){const o=a(i),{data:s,index:l,value:c}=e;return t(vx(gx({},o),{data:s,index:l,value:c}))}}}var px={initPie(){const t=this,{config:e}=t,n=e.data_type,a=e[`${n}_padding`],i=e[`${n}_startingAngle`]||0,o=(a?a*.01:e[`${n}_padAngle`])||0;t.pie=qy().startAngle(i).endAngle(i+2*Math.PI).padAngle(o).value(s=>{var l,c;return(c=(l=s.values)==null?void 0:l.reduce((f,g)=>f+g.value,0))!=null?c:s}).sort(t.getSortCompareFn.bind(t)(!0))},updateRadius(){const t=this,{config:e,state:n}=t,a=e.data_type,i=e[`${a}_padding`],o=e.gauge_width||e.donut_width,s=t.filterTargetsToShow(t.data.targets).length*e.gauge_arcs_minWidth;n.radiusExpanded=Math.min(n.arcWidth,n.arcHeight)/2*(t.hasMultiArcGauge()&&e.gauge_label_show?.85:1),n.radius=n.radiusExpanded*.95,n.innerRadiusRatio=o?(n.radius-o)/n.radius:.6,n.gaugeArcWidth=o||(s<=n.radius-n.innerRadius?n.radius-n.innerRadius:s<=n.radius?s:n.radius);const l=e.pie_innerRadius||(i?i*(n.innerRadiusRatio+.1):0);n.outerRadius=e.pie_outerRadius,n.innerRadius=t.hasType("donut")||t.hasType("gauge")?n.radius*n.innerRadiusRatio:l},getRadius(t){const e=this,n=t==null?void 0:t.data;let{innerRadius:a,outerRadius:i}=e.state;return!he(a)&&n&&(a=a[n.id]||0),Be(i)&&n&&n.id in i?i=i[n.id]:he(i)||(i=e.state.radius),{innerRadius:a,outerRadius:i}},updateArc(){const t=this;t.updateRadius(),t.svgArc=t.getSvgArc(),t.svgArcExpanded=t.getSvgArcExpanded()},getArcLength(){const t=this,{config:e}=t,n=e.gauge_arcLength*3.6;let a=2*(n/360);return n<-360?a=-2:n>360&&(a=2),a*Math.PI},getStartingAngle(){const t=this,{config:e}=t,n=e.data_type,a=t.hasType("gauge")?e.gauge_fullCircle:!1,i=-1*Math.PI/2,o=Math.PI/2;let s=e[`${n}_startingAngle`]||0;return!a&&s<=i?s=i:!a&&s>=o?s=o:(s>Math.PI||s<-1*Math.PI)&&(s=Math.PI),s},updateAngle(t,e=!1){var n;const a=this,{config:i,state:o}=a,s=e&&a.hasType("gauge");let{pie:l}=a,c=t,f=!1;if(!i)return null;const g=a.getStartingAngle(),v=i.gauge_fullCircle||e&&!s?a.getArcLength():g*-2;if(c.data&&a.isGaugeType(c.data)&&!a.hasMultiArcGauge()){const{gauge_min:m,gauge_max:S}=i,P=a.getTotalDataSum(o.rendered),N=v*((P-m)/(S-m));l=l.startAngle(g).endAngle(N+g)}if(e===!1&&l(a.filterTargetsToShow()).forEach((m,S)=>{var P;!f&&m.data.id===((P=c.data)==null?void 0:P.id)&&(f=!0,c=m,c.index=S)}),isNaN(c.startAngle)&&(c.startAngle=0),isNaN(c.endAngle)&&(c.endAngle=c.startAngle),e||c.data&&(i.gauge_enforceMinMax||a.hasMultiArcGauge())){const{gauge_min:m,gauge_max:S}=i,P=e&&!s?a.getTotalDataSum(o.rendered):S,N=v/(P-m),L=(n=c.value)!=null?n:0,w=L{const l=e.updateAngle(s),c=a(l);let f=0;return l&&(f=i(l,c)),l?o.cornerRadius(f)(l):"M 0 0"}},getArc(t,e,n){return n||this.isArcType(t.data)?this.svgArc(t,e):"M 0 0"},redrawArcRangeText(){const t=this,{config:e,$el:{arcs:n},state:a,$T:i}=t,o=e.arc_rangeText_format,s=t.hasType("gauge")&&e.arc_rangeText_fixed;let l=e.arc_rangeText_values;if(l!=null&&l.length){const c=e.arc_rangeText_unit==="%",f=t.getTotalDataSum(a.rendered);c&&(l=l.map(m=>f/100*m));const g=t.pie(l).map((m,S)=>(m.index=S,m));let v=n.selectAll(`.${Ve.arcRange}`).data(l);v.exit(),v=i(v.enter().append("text").attr("class",Ve.arcRange).style("text-anchor","middle").style("pointer-events","none").style("opacity","0").text(m=>{const S=c?m/f*100:m;return ve(o)?o(S):`${S}${c?"%":""}`}).merge(v)),(!a.rendered||a.rendered&&!s)&&f>0&&v.attr("transform",(m,S)=>t.transformForArcLabel(g[S],!0)),v.style("opacity",m=>!s&&(m>f||f===0)?"0":null)}},transformForArcLabel(t,e=!1){var n,a,i;const o=this,{config:s,state:{radiusExpanded:l}}=o,c=o.updateAngle(t,e);let f="";if(c){if(e||o.hasMultiArcGauge()){const g=Math.sin(c.endAngle-Math.PI/2),v=s.arc_rangeText_position;let m=Math.cos(c.endAngle-Math.PI/2)*(l+(e?5:25)),S=g*(l+15-Math.abs(g*10))+3;if(e&&v){const P=s.arc_rangeText_values,N=ve(v)?v(P[t.index]):v;m+=(n=N==null?void 0:N.x)!=null?n:0,S+=(a=N==null?void 0:N.y)!=null?a:0}f=`translate(${m},${S})`}else if(!o.hasType("gauge")||o.data.targets.length>1){let{outerRadius:g}=o.getRadius(t);o.hasType("polar")&&(g=o.getPolarOuterRadius(t,g));const v=this.svgArc.centroid(c),[m,S]=v.map(L=>isNaN(L)?0:L),P=Math.sqrt(m*m+S*S);let N=(i=["donut","gauge","pie","polar"].filter(o.hasType.bind(o)).map(L=>s[`${L}_label_ratio`]))==null?void 0:i[0];N?N=ve(N)?N.bind(o.api)(t,g,P):N:N=g&&(P?(36/g>.375?1.175-36/g:.8)*g/P:0),f=`translate(${m*N},${S*N})`}}return f},convertToArcData(t){return this.addName({id:"data"in t?t.data.id:t.id,value:t.value,ratio:this.getRatio("arc",t),index:t.index})},textForArcLabel(t){const e=this,n=e.hasType("gauge");e.shouldShowArcLabel()&&t.style("fill",e.updateTextColor.bind(e)).attr("filter",a=>e.updateTextBGColor.bind(e)(a,e.config.data_labels_backgroundColors)).each(function(a){var i;const o=ot(this),s=e.updateAngle(a),l=e.getRatio("arc",s);if(e.meetsLabelThreshold(l,(i=["donut","gauge","pie","polar"].filter(e.hasType.bind(e)))==null?void 0:i[0])){const{value:f}=s||a,g=(e.getArcLabelFormat()||e.defaultArcValueFormat)(f,l,a.data.id).toString();wa(o,g,[-1,1],n)}else o.text("")})},expandArc(t){const e=this,{state:{transiting:n},$el:a}=e;if(n){const o=setInterval(()=>{n||(clearInterval(o),a.legend.selectAll(`.${qe.legendItemFocused}`).size()>0&&e.expandArc(t))},10);return}const i=e.mapToTargetIds(t);a.svg.selectAll(e.selectorTargets(i,`.${Ve.chartArc}`)).each(function(o){if(!e.shouldExpand(o.data.id))return;const s=e.getExpandConfig(o.data.id,"duration"),l=e.getSvgArcExpanded(e.getExpandConfig(o.data.id,"rate"));ot(this).selectAll("path").transition().duration(s).attrTween("d",bs(e.svgArcExpanded.bind(e))).transition().duration(s*2).attrTween("d",bs(l.bind(e)))})},unexpandArc(t){const e=this,{state:{transiting:n},$el:{svg:a}}=e;if(n)return;const i=e.mapToTargetIds(t);a.selectAll(e.selectorTargets(i,`.${Ve.chartArc}`)).selectAll("path").transition().duration(o=>e.getExpandConfig(o.data.id,"duration")).attrTween("d",bs(e.svgArc.bind(e))),a.selectAll(`${Ve.arc}`).style("opacity",null)},getExpandConfig(t,e){const n=this,{config:a}=n,i={duration:50,rate:.98};let o;return n.isDonutType(t)?o="donut":n.isGaugeType(t)?o="gauge":n.isPieType(t)&&(o="pie"),o?a[`${o}_expand_${e}`]:i[e]},shouldExpand(t){const e=this,{config:n}=e;return e.isDonutType(t)&&n.donut_expand||e.isGaugeType(t)&&n.gauge_expand||e.isPieType(t)&&n.pie_expand},shouldShowArcLabel(){const t=this,{config:e}=t;return["donut","gauge","pie","polar"].some(n=>t.hasType(n)&&e[`${n}_label_show`])},getArcLabelFormat(){const t=this,{config:e}=t;let n=a=>a;return["donut","gauge","pie","polar"].filter(t.hasType.bind(t)).forEach(a=>{n=e[`${a}_label_format`]}),ve(n)?n.bind(t.api):n},updateTargetsForArc(t){const e=this,{$el:n}=e,a=e.hasType("gauge"),i=e.getChartClass("Arc"),o=e.getClass("arcs",!0),s=e.classFocus.bind(e),l=n.main.select(`.${Ve.chartArcs}`),c=l.selectAll(`.${Ve.chartArc}`).data(e.pie(t)).attr("class",g=>i(g)+s(g.data)),f=c.enter().append("g").attr("class",i).call(this.setCssRule(!1,`.${Ve.chartArcs} text`,["pointer-events:none","text-anchor:middle"]));f.append("g").attr("class",o).merge(c),f.append("text").attr("dy",a&&!e.hasMultiTargets()?"-.1em":".35em").style("opacity","0").style("text-anchor",e.getStylePropValue("middle")).style("pointer-events",e.getStylePropValue("none")),n.text=l.selectAll(`.${Se.target} text`)},initArc(){const t=this,{$el:e}=t;e.arcs=e.main.select(`.${Se.chart}`).append("g").attr("class",Ve.chartArcs).attr("transform",t.getTranslate("arc")),t.setArcTitle()},setArcTitle(t){const e=this,n=t||e.getArcTitle(),a=e.hasType("gauge");if(n){const i=a?Un.chartArcsGaugeTitle:Ve.chartArcsTitle;let o=e.$el.arcs.select(`.${i}`);o.empty()&&(o=e.$el.arcs.append("text").attr("class",i).style("text-anchor","middle")),a&&o.attr("dy","-0.3em"),wa(o,n,a?void 0:[-.6,1.35],!0)}},getArcTitle(){const t=this,e=t.hasType("donut")&&"donut"||t.hasType("gauge")&&"gauge";return e?t.config[`${e}_title`]:""},getArcTitleWithNeedleValue(){const t=this,{config:e,state:n}=t,a=t.getArcTitle();if(a&&t.config.arc_needle_show&&/{=[A-Z_]+}/.test(a)){let i=n.current.needle;return he(i)||(i=e.arc_needle_value),bi(a,{NEEDLE_VALUE:he(i)?i:0})}return!1},redrawArc(t,e,n){const a=this,{config:i,state:o,$el:{main:s}}=a,l=i.interaction_enabled,c=l&&i.data_selection_isselectable;let f=s.selectAll(`.${Ve.arcs}`).selectAll(`.${Ve.arc}`).data(a.arcData.bind(a));f.exit().transition().duration(e).style("opacity","0").remove(),f=f.enter().append("path").attr("class",a.getClass("arc",!0)).style("fill",g=>a.color(g.data)).style("cursor",g=>{var v;return(v=c==null?void 0:c.bind)!=null&&v.call(c,a.api)(g)?"pointer":null}).style("opacity","0").each(function(g){a.isGaugeType(g.data)&&(g.startAngle=i.gauge_startingAngle,g.endAngle=i.gauge_startingAngle),this._current=g}).merge(f),a.hasType("gauge")&&(a.updateGaugeMax(),a.hasMultiArcGauge()&&a.redrawArcGaugeLine()),f.attr("transform",g=>!a.isGaugeType(g.data)&&n?"scale(0)":"").style("opacity",function(g){return g===this._current?"0":null}).each(()=>{o.transiting=!0}).transition().duration(t).attrTween("d",function(g){const v=a.updateAngle(g);if(!v)return()=>"M 0 0";isNaN(this._current.startAngle)&&(this._current.startAngle=0),isNaN(this._current.endAngle)&&(this._current.endAngle=this._current.startAngle);const m=Qr(this._current,v);return this._current=m(0),function(S){const P=m(S);return P.data=g.data,a.getArc(P,!0)}}).attr("transform",n?"scale(1)":"").style("fill",g=>{let v;return a.levelColor?(v=a.levelColor(g.data.values[0].value),i.data_colors[g.data.id]=v):v=a.color(g.data),v}).style("opacity",null).call(Si,function(){if(a.levelColor){const g=ot(this),v=g.datum(this._current);a.updateLegendItemColor(v.data.id,g.style("fill"))}o.transiting=!1,_e(i.onrendered,a.api)}),l&&a.bindArcEvent(f),a.hasType("polar")&&a.redrawPolar(),a.hasType("gauge")&&a.redrawBackgroundArcs(),i.arc_needle_show&&a.redrawNeedle(),a.redrawArcText(t),a.redrawArcRangeText()},redrawNeedle(){const t=this,{$el:e,config:n,state:{hiddenTargetIds:a,radius:i}}=t,o=(i-1)/100*n.arc_needle_length,s=a.length!==t.data.targets.length;let l=t.$el.arcs.select(`.${Ve.needle}`);const c=n.arc_needle_path,f=n.arc_needle_bottom_width/2,g=n.arc_needle_top_width/2,v=n.arc_needle_top_rx,m=n.arc_needle_top_ry,S=n.arc_needle_bottom_len,P=n.arc_needle_bottom_rx,N=n.arc_needle_bottom_ry,L=t.getNeedleAngle(),w=()=>{const X=t.getArcTitleWithNeedleValue();X&&t.setArcTitle(X)};if(w(),l.empty()&&(l=e.arcs.append("path").classed(Ve.needle,!0),e.needle=l,e.needle.updateHelper=(X,W=!1)=>{e.needle.style("display")!=="none"&&t.$T(e.needle).style("transform",`rotate(${t.getNeedleAngle(X)}deg)`).call(Si,()=>{W&&(n.arc_needle_value=X),w()})}),s){const X=ve(c)?c.call(t,o):`M-${f} ${S} A${P} ${N} 0 0 0 ${f} ${S} L${g} -${o} A${v} ${m} 0 0 0 -${g} -${o} L-${f} ${S} Z`;t.$T(l).attr("d",X).style("fill",n.arc_needle_color).style("display",null).style("transform",`rotate(${L}deg)`)}else l.style("display","none")},getNeedleAngle(t){const e=this,{config:n,state:a}=e,i=e.getArcLength(),o=e.hasType("gauge"),s=e.getTotalDataSum(!0);let l=Qe(t)?t:n.arc_needle_value,c=n[`${n.data_type}_startingAngle`]||0,f=0;if(he(l)||(l=o&&e.data.targets.length===1?s:0),a.current.needle=l,o){c=e.getStartingAngle();const g=n.gauge_fullCircle?i:c*-2,{gauge_min:v,gauge_max:m}=n;f=g*((l-v)/(m-v))}else f=i*(l/s);return(c+f)*(180/Math.PI)},redrawBackgroundArcs(){const t=this,{config:e,state:n}=t,a=t.hasMultiArcGauge(),i=e.gauge_fullCircle,o=t.filterTargetsToShow(t.data.targets).length===0&&!!e.data_empty_label_text,s=t.getStartingAngle(),l=i?s+t.getArcLength():s*-1;let c=t.$el.arcs.select(`${a?"g":""}.${Ve.chartArcsBackground}`);if(a){let f=0;c=c.selectAll(`path.${Ve.chartArcsBackground}`).data(t.data.targets),c.enter().append("path").attr("class",(g,v)=>`${Ve.chartArcsBackground} ${Ve.chartArcsBackground}-${v}`).merge(c).style("fill",e.gauge_background||null).attr("d",({id:g})=>{if(o||n.hiddenTargetIds.indexOf(g)>=0)return"M 0 0";const v={data:[{value:e.gauge_max}],startAngle:s,endAngle:l,index:f++};return t.getArc(v,!0,!0)}),c.exit().remove()}else c.attr("d",o?"M 0 0":()=>{const f={data:[{value:e.gauge_max}],startAngle:s,endAngle:l};return t.getArc(f,!0,!0)})},bindArcEvent(t){const e=this,{config:n,state:a}=e,i=a.inputType==="touch",o=a.inputType==="mouse";function s(c,f,g){e.expandArc(g),e.api.focus(g),e.toggleFocusLegend(g,!0),e.showTooltip([f],c)}function l(c){const f=(c==null?void 0:c.id)||void 0;e.unexpandArc(f),e.api.revert(),e.revertLegend(),e.hideTooltip()}if(t.on("click",function(c,f,g){var v;const m=e.updateAngle(f);let S;m&&(S=e.convertToArcData(m),(v=e.toggleShape)==null||v.call(e,this,S,g),n.data_onclick.bind(e.api)(S,this))}),o&&t.on("mouseover",function(c,f){if(a.transiting)return;a.event=c;const g=e.updateAngle(f),v=g?e.convertToArcData(g):null,m=(v==null?void 0:v.id)||void 0;s(this,v,m),e.setOverOut(!0,v)}).on("mouseout",(c,f)=>{if(a.transiting||!n.interaction_onout)return;a.event=c;const g=e.updateAngle(f),v=g?e.convertToArcData(g):null;l(),e.setOverOut(!1,v)}).on("mousemove",function(c,f){const g=e.updateAngle(f),v=g?e.convertToArcData(g):null;a.event=c,e.showTooltip([v],this)}),i&&e.hasArcType()&&!e.radars){const c=f=>{var g,v;const{clientX:m,clientY:S}=(v=(g=f.changedTouches)==null?void 0:g[0])!=null?v:{clientX:0,clientY:0};return ot(gn.elementFromPoint(m,S))};e.$el.svg.on("touchstart touchmove",function(f){if(a.transiting)return;a.event=f;const v=c(f).datum(),m=v!=null&&v.data&&v.data.id?e.updateAngle(v):null,S=m?e.convertToArcData(m):null,P=(S==null?void 0:S.id)||void 0;e.callOverOutForTouch(S),ln(P)?l():s(this,S,P)})}},redrawArcText(t){const e=this,{config:n,state:a,$el:{main:i,arcs:o}}=e,s=e.hasType("gauge"),l=e.hasMultiArcGauge();let c;if(s&&e.data.targets.length===1&&n.gauge_title||(c=i.selectAll(`.${Ve.chartArc}`).select("text").style("opacity","0").attr("class",f=>e.isGaugeType(f.data)?Un.gaugeValue:null).call(e.textForArcLabel.bind(e)).attr("transform",f=>e.transformForArcLabel.bind(e)(f)).style("font-size",f=>e.isGaugeType(f.data)&&e.data.targets.length===1&&!l?`${Math.round(a.radius/5)}px`:null).transition().duration(t).style("opacity",f=>e.isTargetToShow(f.data.id)&&e.isArcType(f.data)?null:"0"),l&&c.attr("dy","-.1em")),i.select(`.${Ve.chartArcsTitle}`).style("opacity",e.hasType("donut")||s?null:"0"),s){const f=n.gauge_fullCircle;f&&(c==null||c.attr("dy",`${l?0:Math.round(a.radius/14)}`)),n.gauge_label_show&&(o.select(`.${Un.chartArcsGaugeUnit}`).attr("dy",`${f?1.5:.75}em`).text(n.gauge_units),o.select(`.${Un.chartArcsGaugeMin}`).attr("dx",`${-1*(a.innerRadius+(a.radius-a.innerRadius)/(f?1:2))}px`).attr("dy","1.2em").text(e.textForGaugeMinMax(n.gauge_min,!1)),!f&&o.select(`.${Un.chartArcsGaugeMax}`).attr("dx",`${a.innerRadius+(a.radius-a.innerRadius)/2}px`).attr("dy","1.2em").text(e.textForGaugeMinMax(n.gauge_max,!0)))}},getArcElementByIdOrIndex(t){const e=this,{$el:{arcs:n}}=e,a=he(t)?i=>i.index===t:i=>i.data.id===t;return n==null?void 0:n.selectAll(`.${Se.target} path`).filter(a)}};function ju(t){return t[0]}function Vu(t){return t[1]}function Gu(t,e){var n=Le(!0),a=null,i=gs,o=null,s=Es(l);t=typeof t=="function"?t:t===void 0?ju:Le(t),e=typeof e=="function"?e:e===void 0?Vu:Le(e);function l(c){var f,g=(c=$s(c)).length,v,m=!1,S;for(a==null&&(o=i(S=s())),f=0;f<=g;++f)!(f=S;--P)l.point(W[P],H[P]);l.lineEnd(),l.areaEnd()}w&&(W[m]=+t(L,m,v),H[m]=+e(L,m,v),l.point(a?+a(L,m,v):W[m],n?+n(L,m,v):H[m]))}if(X)return l=null,X+""||null}function g(){return Gu().defined(i).curve(s).context(o)}return f.x=function(v){return arguments.length?(t=typeof v=="function"?v:Le(+v),a=null,f):t},f.x0=function(v){return arguments.length?(t=typeof v=="function"?v:Le(+v),f):t},f.x1=function(v){return arguments.length?(a=v==null?null:typeof v=="function"?v:Le(+v),f):a},f.y=function(v){return arguments.length?(e=typeof v=="function"?v:Le(+v),n=null,f):e},f.y0=function(v){return arguments.length?(e=typeof v=="function"?v:Le(+v),f):e},f.y1=function(v){return arguments.length?(n=v==null?null:typeof v=="function"?v:Le(+v),f):n},f.lineX0=f.lineY0=function(){return g().x(t).y(e)},f.lineY1=function(){return g().x(t).y(n)},f.lineX1=function(){return g().x(a).y(e)},f.defined=function(v){return arguments.length?(i=typeof v=="function"?v:Le(!!v),f):i},f.curve=function(v){return arguments.length?(s=v,o!=null&&(l=s(o)),f):s},f.context=function(v){return arguments.length?(v==null?o=l=null:l=s(o=v),f):o},f}var sa={initArea(t){const e=this,{config:n}=e;t.insert("g",`.${n.area_front?$n.circles:ur.lines}`).attr("class",e.getClass("areas",!0))},updateAreaColor(t){const e=this;return e.config.area_linearGradient?e.getGradienColortUrl(t.id):e.color(t)},updateArea(t,e=!1){const n=this,{config:a,state:i,$el:o,$T:s}=n,l=e?o.subchart:o;a.area_linearGradient&&n.updateLinearGradient();const c=l.main.selectAll(`.${ti.areas}`).selectAll(`.${ti.area}`).data(n.lineData.bind(n));s(c.exit(),t).style("opacity","0").remove(),l.area=c.enter().append("path").attr("class",n.getClass("area",!0)).style("fill",n.updateAreaColor.bind(n)).style("opacity",function(){return i.orgAreaOpacity=ot(this).style("opacity"),"0"}).merge(c),c.style("opacity",i.orgAreaOpacity),n.setRatioForGroupedData(l.area.data())},redrawArea(t,e,n=!1){const a=this,{area:i}=n?this.$el.subchart:this.$el,{orgAreaOpacity:o}=a.state;return[a.$T(i,e,gr()).attr("d",t).style("fill",a.updateAreaColor.bind(a)).style("opacity",s=>String(a.isAreaRangeType(s)?o/1.75:o))]},generateDrawArea(t,e){const n=this,{config:a}=n,i=a.line_connectNull,o=a.axis_rotated,s=n.generateGetAreaPoints(t,e),l=n.getYScaleById.bind(n),c=v=>(e?n.subxx:n.xx).call(n,v),f=(v,m)=>n.isGrouped(v.id)?s(v,m)[0][1]:l(v.id,e)(n.isAreaRangeType(v)?n.getRangedData(v,"high"):n.getShapeYMin(v.id)),g=(v,m)=>n.isGrouped(v.id)?s(v,m)[1][1]:l(v.id,e)(n.isAreaRangeType(v)?n.getRangedData(v,"low"):v.value);return v=>{let m=i?n.filterRemoveNull(v.values):v.values,S=0,P=0,N;if(n.isAreaType(v)){let L=mx();L=o?L.y(c).x0(f).x1(g):L.x(c).y0(a.area_above?0:a.area_below?n.state.height:f).y1(g),i||(L=L.defined(w=>n.getBaseValue(w)!==null)),n.isStepType(v)&&(m=n.convertValuesToStep(m)),N=L.curve(n.getCurve(v))(m)}else m[0]&&(S=n.scale.x(m[0].x),P=n.getYScaleById(v.id)(m[0].value)),N=o?`M ${P} ${S}`:`M ${S} ${P}`;return N||"M 0 0"}},generateGetAreaPoints(t,e){const n=this,{config:a}=n,i=n.getShapeX(0,t,e),o=n.getShapeY(!!e),s=n.getShapeOffset(n.isAreaType,t,e),l=n.getYScaleById.bind(n);return function(c,f){const g=l.call(n,c.id,e)(n.getShapeYMin(c.id)),v=s(c,f)||g,m=i(c),S=c.value;let P=o(c);return a.axis_rotated&&(S>0&&Pg.values.some(v=>he(v.value)||e.isBarRangeType(v)))).attr("class",g=>i(g)+s(g)).enter().append("g").attr("class",i).style("opacity","0").style("pointer-events",e.getStylePropValue("none")).append("g").attr("class",o).style("cursor",g=>{var v;return(v=l==null?void 0:l.bind)!=null&&v.call(l,e.api)(g)?"pointer":null}).call(e.setCssRule(!0,` .${Kn.bar}`,["fill"],e.color))},updateBar(t,e=!1){const n=this,{config:a,$el:i,$T:o}=n,s=e?i.subchart:i,l=n.getClass("bar",!0),c=n.initialOpacity.bind(n);a.bar_linearGradient&&n.updateLinearGradient();const f=s.main.selectAll(`.${Kn.bars}`).selectAll(`.${Kn.bar}`).data(n.labelishData.bind(n));o(f.exit(),t).style("opacity","0").remove(),s.bar=f.enter().append("path").attr("class",l).style("fill",n.updateBarColor.bind(n)).merge(f).style("opacity",c),n.setRatioForGroupedData(s.bar.data())},updateBarColor(t){const e=this,n=e.getStylePropValue(e.color);return e.config.bar_linearGradient?e.getGradienColortUrl(t.id):n?n(t):null},redrawBar(t,e,n=!1){const a=this,{bar:i}=n?a.$el.subchart:a.$el;return[a.$T(i,e,gr()).attr("d",o=>(he(o.value)||a.isBarRangeType(o))&&t(o)).style("fill",a.updateBarColor.bind(a)).style("clip-path",o=>o.clipPath).style("opacity",null)]},generateDrawBar(t,e){const n=this,{config:a}=n,i=n.generateGetBarPoints(t,e),o=a.axis_rotated,s=a.bar_radius,l=a.bar_radius_ratio,c=he(s)&&s>0?()=>s:he(l)?f=>f*l:null;return(f,g)=>{const v=i(f,g),m=+o,S=+!m,P=f.value<0,N=a[`axis_${n.axis.getId(f.id)}_inverted`],L=!N&&P||N&&!P,w=["",""],X=n.isGrouped(f.id),W=c&&X?n.isStackingRadiusData(f):!1,H=[v[0][m],v[0][S]];let k=0;if(f.clipPath=null,c){const ht=o?S:m,$t=v[2][ht]-v[0][ht];k=!X||W?c($t):0;const dt=`a${k} ${k} ${L?"1 0 0":"0 0 1"} `;w[+!o]=`${dt}${k},${k}`,w[+o]=`${dt}${[-k,k][o?"sort":"reverse"]()}`,L&&w.reverse()}const K=o?v[1][m]+(L?k:-k):v[1][S]+(L?-k:k);if(k){let ht="";o?L&&H[0]K&&(ht=`0 0 0 ${H[0]-K}px`):L&&H[1]>K?ht=`${H[1]-K}px 0 0 0`:!L&&H[1]-1){const m=n.bar.filter(S=>S.id===s&&S.value===c);return!m.empty()&&/a\d+/i.test(m.attr("d"))}const f=a.data_groups.find(m=>m.indexOf(s)>-1),v=e.orderTargets(e.filterTargetsToShow(i.targets.filter(e.isBarType,e))).filter(m=>f.indexOf(m.id)>-1).map(m=>m.values.filter(S=>S.index===l&&(he(c)&&c>0?S.value>0:S.value<0))[0]).filter(Boolean).map(m=>m.id);return c!==0&&v.indexOf(s)===v.length-1},generateGetBarPoints(t,e){const n=this,{config:a}=n,i=e?n.axis.subX:n.axis.x,o=n.getIndicesMax(t)+1,s=n.getBarW("bar",i,o),l=n.getShapeX(s,t,!!e),c=n.getShapeY(!!e),f=n.getShapeOffset(n.isBarType,t,!!e),g=n.getYScaleById.bind(n);return(v,m)=>{const{id:S}=v,P=g.call(n,S,e)(n.getShapeYMin(S)),N=f(v,m)||P,L=he(s)?s:s[v.id]||s._$width,w=a[`axis_${n.axis.getId(S)}_inverted`],X=v.value,W=l(v);let H=c(v);a.axis_rotated&&!w&&(X>0&&He.isBubbleZType(s)?e.getBubbleZData(s.value,"y"):Be(s.value)?s.value.mid:s.value)),i=n*n*Math.PI,o=(e.isBubbleZType(t)?e.getBubbleZData(t.value,"z"):t.value)*(i/a);return Math.sqrt(o/Math.PI)},getBubbleZData(t,e){return Be(t)?t[e]:t[e==="y"?0:1]}},Tx=Object.defineProperty,Xu=Object.getOwnPropertySymbols,$x=Object.prototype.hasOwnProperty,Sx=Object.prototype.propertyIsEnumerable,Hu=(t,e,n)=>e in t?Tx(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n,Ax=(t,e)=>{for(var n in e||(e={}))$x.call(e,n)&&Hu(t,n,e[n]);if(Xu)for(var n of Xu(e))Sx.call(e,n)&&Hu(t,n,e[n]);return t},Ex={initCandlestick(){const{$el:t}=this;t.candlestick=t.main.select(`.${Se.chart}`).append("g").attr("class",cr.chartCandlesticks)},updateTargetsForCandlestick(t){const e=this,{$el:n}=e,a=e.getChartClass("Candlestick");n.candlestick||e.initCandlestick(),e.$el.main.select(`.${cr.chartCandlesticks}`).selectAll(`.${cr.chartCandlestick}`).data(t).enter().append("g").attr("class",a).style("pointer-events","none")},updateCandlestick(t,e=!1){const n=this,{$el:a,$T:i}=n,o=e?a.subchart:a,s=n.getClass("candlestick",!0),l=n.initialOpacity.bind(n),c=o.main.selectAll(`.${cr.chartCandlestick}`).selectAll(`.${cr.candlestick}`).data(n.labelishData.bind(n));i(c.exit(),t).style("opacity","0").remove();const f=c.enter().filter(g=>g.value).append("g").attr("class",s);f.append("line"),f.append("path"),o.candlestick=c.merge(f).style("opacity",l)},generateDrawCandlestick(t,e){const n=this,{config:a}=n,i=n.generateGetCandlestickPoints(t,e),o=a.axis_rotated,s=a.candlestick_color_down;return(l,c,f)=>{const g=i(l,c),v=n.getCandlestickData(l),m=v==null?void 0:v._isUp,S=+o,P=+!S;f.classed&&f.classed(cr[m?"valueUp":"valueDown"],!0);const N=o?`H${g[1][1]} V${g[1][0]} H${g[0][1]}`:`V${g[1][1]} H${g[1][0]} V${g[0][1]}`;f.select("path").attr("d",`M${g[0][S]},${g[0][P]}${N}z`).style("fill",X=>(m?n.color(X):Be(s)?s[X.id]:s)||n.color(X));const L=f.select("line"),w=o?{x1:g[2][1],x2:g[2][2],y1:g[2][0],y2:g[2][0]}:{x1:g[2][0],x2:g[2][0],y1:g[2][1],y2:g[2][2]};for(const X in w)L.attr(X,w[X])}},generateGetCandlestickPoints(t,e=!1){const n=this,a=e?n.axis.subX:n.axis.x,i=n.getIndicesMax(t)+1,o=n.getBarW("candlestick",a,i),s=n.getShapeX(o,t,!!e),l=n.getShapeY(!!e),c=n.getShapeOffset(n.isBarType,t,!!e),f=n.getYScaleById.bind(n);return(g,v)=>{const m=f.call(n,g.id,e)(n.getShapeYMin(g.id)),S=c(g,v)||m,P=he(o)?o:o[g.id]||o._$width,N=n.getCandlestickData(g);let L;if(N&&he(N.open)&&he(N.close)){const w={start:s(g),end:0};w.end=w.start+P;const X={start:l(N.open),end:l(N.close)},W={x:w.start+P/2,high:l(N.high),low:l(N.low)};X.start-=m-S,L=[[w.start,X.start],[w.end,X.end],[W.x,W.low,W.high]]}else L=[[0,0],[0,0],[0,0,0]];return L}},redrawCandlestick(t,e,n=!1){const a=this,{$el:i,$T:o}=a,{candlestick:s}=n?i.subchart:i,l=gr(!0);return[s.each(function(c,f){const g=o(ot(this),e,l);t(c,f,g)}).style("opacity",null)]},getCandlestickData({value:t}){let e;if(je(t)){const[n,a,i,o,s=!1]=t;e={open:n,high:a,low:i,close:o},s!==!1&&(e.volume=s)}else Be(t)&&(e=Ax({},t));return e&&(e._isUp=e.close>=e.open),e||null}},bx=Object.defineProperty,Yu=Object.getOwnPropertySymbols,Rx=Object.prototype.hasOwnProperty,Ix=Object.prototype.propertyIsEnumerable,Wu=(t,e,n)=>e in t?bx(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n,Ox=(t,e)=>{for(var n in e||(e={}))Rx.call(e,n)&&Wu(t,n,e[n]);if(Yu)for(var n of Yu(e))Ix.call(e,n)&&Wu(t,n,e[n]);return t};function Zi(t=!1){const e=this,{config:n,state:{current:{width:a,height:i}}}=e,o=e.getCurrentPadding(),s=Ox({width:a-(o.left+o.right),height:i-(n.legend_show?e.getLegendHeight()+10:0)-(o.top+o.bottom)},o);if(t){const{width:l,height:c}=Ku.call(e,{width:s.width,height:s.height});s.width{let l=o;return Be(o)&&(l=t[s?"height":"width"]*o.ratio),l}),{width:a,height:i}}function Cx(t){const e=this,{top:n,left:a,width:i}=Zi.call(e,!0),o=[];return t.forEach((s,l)=>{const{ratio:c}=s,f=l>0?o[l-1][2][1]:n;o.push(s.coords=[[a,f],[a+i,f],[a+i,l>0?c+f:c+n],[a,l>0?c+f:c+n],[a,f]])}),o}function Zu(t=!1){const e=this,{width:n,height:a,top:i,left:o}=Zi.call(e,!0),s=Ku.call(e,{width:n,height:a}),l=(n-s.width)/2,c=(n+s.width)/2,f=a-s.height,g=[[0,0],[n,0],[c,f],[c,a],[l,a],[l,f],[0,0]];return t&&g.forEach(v=>{v[0]+=o,v[1]+=i}),`M${g.join("L")}z`}function Px(t){const e=this,{config:n}=e,a=t.map(i=>({id:i.id,value:i.values.reduce((o,s)=>o+s.value,0)}));return n.data_order&&a.sort(e.getSortCompareFn.bind(e)(!0)),Ju.call(e,a)}function Ju(t){const e=this,{height:n}=Zi.call(e),a=e.getTotalDataSum(!0);return t.forEach(i=>{i.ratio=i.value/a*n}),t}var wx={initFunnel(){const t=this,{$el:e}=t;e.funnel=e.main.select(`.${Se.chart}`).append("g").classed(Ta.chartFunnels,!0),e.funnel.background=e.funnel.append("path").classed(Ta.funnelBackground,!0),t.bindFunnelEvent()},bindFunnelEvent(){const t=this,{$el:{funnel:e},config:n,state:a}=t,i=o=>{var s;const l=o.isTrusted?o.target:(s=a.eventReceiver.rect)==null?void 0:s.node();let c;return/^path$/i.test(l.tagName)&&(a.event=o,c=ot(l).datum()),c};if(n.interaction_enabled){const o=a.inputType==="touch";e.on(o?"touchstart":"mouseover mousemove",s=>{const l=i(s);l&&(t.showTooltip([l],s.target),/^(touchstart|mouseover)$/.test(s.type)&&t.setOverOut(!0,l))}).on(o?"touchend":"mouseout",s=>{const l=i(s);n.interaction_onout&&(t.hideTooltip(),t.setOverOut(!1,l))})}},updateTargetsForFunnel(t){const e=this,{$el:{funnel:n}}=e,a=e.getChartClass("Funnel"),i=e.getClass("funnel",!0);n||e.initFunnel();const o=Px.call(e,t.filter(e.isFunnelType.bind(e))),s=n.selectAll(`.${Ta.chartFunnel}`).data(o);s.exit().remove();const l=s.enter().insert("g",`.${Ta.funnelBackground}`);l.append("path"),n.path=l.merge(s).attr("class",c=>a(c)).select("path").attr("class",i).style("opacity","0").style("fill",e.color)},updateFunnel(t){const e=this,{$el:{funnel:n}}=e,a=t.map(({id:i})=>i);n.path=n.path.filter(i=>a.indexOf(i.id)>=0)},generateGetFunnelPoints(){const t=this,{$el:{funnel:e}}=t,n=t.filterTargetsToShow(e.path),{top:a,left:i,right:o}=Zi.call(t),s=(i-o)/2,l={};let c=a!=null?a:0;return n.each((f,g)=>{var v;l[f.id]=[[s,c],[s,c+=((v=n==null?void 0:n[g])!=null?v:f).ratio]]}),f=>l[f.id]},redrawFunnel(){const t=this,{$T:e,$el:{funnel:n}}=t,a=t.filterTargetsToShow(n.path),i=Cx.call(t,Ju.call(t,a.data()));n.attr("clip-path",`path('${Zu.bind(t)()}')`),n.background.attr("d",Zu.call(t,!0)),e(a).attr("d",(o,s)=>`M${i[s].join("L")}z`).style("opacity","1"),n.selectAll("g").style("opacity",null)}},Mx={initGauge(){const t=this,{config:e,$el:{arcs:n}}=t,a=(i=null,o="")=>{n.append("text").attr("class",i).style("text-anchor","middle").style("pointer-events","none").text(o)};if(t.hasType("gauge")){const i=t.hasMultiArcGauge();n.append(i?"g":"path").attr("class",Ve.chartArcsBackground).style("fill",!i&&e.gauge_background||null),e.gauge_units&&a(Un.chartArcsGaugeUnit),e.gauge_label_show&&(a(Un.chartArcsGaugeMin),!e.gauge_fullCircle&&a(Un.chartArcsGaugeMax))}},updateGaugeMax(){const t=this,{config:e,state:n}=t,i=t.hasMultiArcGauge()?t.getMinMaxData().max[0].value:t.getTotalDataSum(n.rendered);!e.gauge_enforceMinMax&&i+e.gauge_min*(e.gauge_min>0?-1:1)>e.gauge_max&&(e.gauge_max=i-e.gauge_min)},redrawArcGaugeLine(){const t=this,{config:e,state:n,$el:a}=t,{hiddenTargetIds:i}=t.state,o=a.main.selectAll(`.${Ve.arcs}`).selectAll(`.${Ve.arcLabelLine}`).data(t.arcData.bind(t));o.enter().append("rect").attr("class",l=>`${Ve.arcLabelLine} ${Se.target} ${Se.target}-${l.data.id}`).merge(o).style("fill",l=>t.levelColor?t.levelColor(l.data.values[0].value):t.color(l.data)).style("display",e.gauge_label_show?null:"none").each(function(l){let c=0;const f=2;let g=0,v=0,m="";if(i.indexOf(l.data.id)<0){const S=t.updateAngle(l),P=n.gaugeArcWidth/t.filterTargetsToShow(t.data.targets).length*(S.index+1),N=S.endAngle-Math.PI/2,L=n.radius-P,w=N-(L===0?0:1/L);c=n.radiusExpanded-n.radius+P,g=Math.cos(w)*L,v=Math.sin(w)*L,m=`rotate(${N*180/Math.PI}, ${g}, ${v})`}ot(this).attr("x",g).attr("y",v).attr("width",c).attr("height",f).attr("transform",m).style("stroke-dasharray",`0, ${c+f}, 0`)})},textForGaugeMinMax(t,e){const n=this,{config:a}=n,i=a.gauge_label_extents;return ve(i)?i.bind(n.api)(t,e):t},getGaugeLabelHeight(){const{config:t}=this;return this.config.gauge_label_show&&!t.gauge_fullCircle?20:0},getPaddingBottomForGauge(){const t=this;return t.getGaugeLabelHeight()*(t.config.gauge_label_show?2:2.5)}};function Dx(t,e,n,a=!1){const i=t?[t,0]:n;for(let o=t||n.reduce((s,l)=>s+l);o<=e;)n.forEach(s=>{o+s<=e&&i.push(s),o+=s});return i.length%2!==0&&i.push(a?n[1]:0),{dash:i.join(" "),length:i.reduce((o,s)=>o+s,0)}}function Lx(t,e,n){const a=this,i=[],o="2 2";if(Qe(e)){const s=(l,c)=>ln(l)?c:n?Yn.call(a,l):l;for(let l=0,c;c=e[l];l++){const f=s(c.start,t[0].x),g=s(c.end,t[t.length-1].x),v=c.style||{dasharray:o};i[l]={start:f,end:g,style:v}}}return i}var Nx={initLine(){const{$el:t}=this;t.line=t.main.select(`.${Se.chart}`).append("g").attr("class",ur.chartLines).call(this.setCssRule(!1,`.${ur.chartLines}`,["pointer-events:none"]))},updateTargetsForLine(t){const e=this,{$el:{area:n,line:a,main:i}}=e,o=e.getChartClass("Line"),s=e.getClass("lines",!0),l=e.classFocus.bind(e);a||e.initLine();const c=t.filter(v=>!(e.isScatterType(v)||e.isBubbleType(v))),f=i.select(`.${ur.chartLines}`).selectAll(`.${ur.chartLine}`).data(c).attr("class",v=>o(v)+l(v)),g=f.enter().append("g").attr("class",o).style("opacity","0").style("pointer-events",e.getStylePropValue("none"));if(g.append("g").attr("class",s),e.hasTypeOf("Area")){const v=(!n&&g.empty()?f:g).filter(e.isAreaType.bind(e));e.initArea(v)}e.updateTargetForCircle(c,g)},updateLine(t,e=!1){const n=this,{format:{extraLineClasses:a},$el:i,$T:o}=n,s=e?i.subchart:i,l=s.main.selectAll(`.${ur.lines}`).selectAll(`.${ur.line}`).data(n.lineData.bind(n));o(l.exit(),t).style("opacity","0").remove(),s.line=l.enter().append("path").attr("class",c=>`${n.getClass("line",!0)(c)} ${a(c)||""}`).style("stroke",n.color).merge(l).style("opacity",n.initialOpacity.bind(n)).attr("transform",null)},redrawLine(t,e,n=!1){const a=this,{$el:i,$T:o}=a,{line:s}=n?i.subchart:i;return[o(s,e,gr()).attr("d",t).style("stroke",this.color).style("opacity",null)]},getCurve(t){const e=this;return e.config.axis_rotated&&e.isStepType(t)?a=>{const i=e.getInterpolate(t)(a);return i.orgPoint=i.point,i.pointRotated=function(o,s){this._point===1&&(this._point=2);const l=this._y*(1-this._t)+s*this._t;this._context.lineTo(this._x,l),this._context.lineTo(o,l),this._x=o,this._y=s},i.point=function(o,s){this._point===0?this.orgPoint(o,s):this.pointRotated(o,s)},i}:e.getInterpolate(t)},generateDrawLine(t,e){const n=this,{config:a,scale:i}=n,o=a.line_connectNull,s=a.axis_rotated,l=n.generateGetLinePoints(t,e),c=n.getYScaleById.bind(n),f=S=>(e?n.subxx:n.xx).call(n,S),g=(S,P)=>n.isGrouped(S.id)?l(S,P)[0][1]:c(S.id,e)(n.getBaseValue(S));let v=Gu();v=s?v.x(g).y(f):v.x(f).y(g),o||(v=v.defined(S=>n.getBaseValue(S)!==null));const m=e?i.subX:i.x;return S=>{const P=c(S.id,e);let N=o?n.filterRemoveNull(S.values):S.values,L=0,w=0,X;if(n.isLineType(S)){const W=a.data_regions[S.id];W?X=n.lineWithRegions(N,i.zoom||m,P,W):(n.isStepType(S)&&(N=n.convertValuesToStep(N)),X=v.curve(n.getCurve(S))(N))}else N[0]&&(L=m(N[0].x),w=P(N[0].value)),X=s?`M ${w} ${L}`:`M ${L} ${w}`;return X||"M 0 0"}},lineWithRegions(t,e,n,a){const i=this,{config:o}=i,s=o.axis_rotated,l=i.axis.isTimeSeries(),c="2 2",f=Lx.bind(i)(t,a,l),g=i.hasNullDataValue(t);let v,m,S,P;const N=s?dt=>n(dt.value):dt=>e(dt.x),L=s?dt=>e(dt.x):dt=>n(dt.value),w=dt=>`M${dt[0][0]},${dt[0][1]}L${dt[1][0]},${dt[1][1]}`,X=l?(dt,st,Vt,vt)=>{const Q=dt.x.getTime(),St=st.x-dt.x,ct=new Date(Q+St*Vt),At=new Date(Q+St*(Vt+vt)),Gt=s?[[n(m(Vt)),e(ct)],[n(m(Vt+S)),e(At)]]:[[e(ct),n(m(Vt))],[e(At),n(m(Vt+S))]];return w(Gt)}:(dt,st,Vt,vt)=>{const Q=e(st.x,!s),St=n(st.value,s),ct=Vt+vt,At=e(v(Vt),!s),Gt=n(m(Vt),s);let Bt=e(v(ct),!s),Kt=n(m(ct),s);Bt>Q&&(Bt=Q),dt.value>st.value&&(s?KtSt)&&(Kt=St);const ne=[[At,Gt],[Bt,Kt]];return s&&ne.forEach(le=>le.reverse()),w(ne)},W={x:i.axis.getAxisType("x"),y:i.axis.getAxisType("y")};let H="";const k=i.$el.line.filter(({id:dt})=>dt===t[0].id),K=k.clone().style("display","none"),at=(dt,st)=>dt.attr("d",st).node().getTotalLength(),ht={dash:[],lastLength:0};let $t=!1;for(let dt=0,st;st=t[dt];dt++){const Vt=t[dt-1],vt=Vt&&De(Vt.value);let Q=i.isWithinRegions(st.x,f);if(De(st.value)){if(ln(f)||!Q||!vt)H+=`${dt&&vt?"L":"M"}${N(st)},${L(st)}`;else if(vt)if(Q=((Q==null?void 0:Q.dasharray)||c).split(" ").map(Number),v=zr(W.x,Vt.x,st.x),m=zr(W.y,Vt.value,st.value),g){const St=e(st.x)-e(Vt.x),ct=n(st.value)-n(Vt.value),At=Math.sqrt(Math.pow(St,2)+Math.pow(ct,2));S=Q[0]/At,P=S*Q[1];for(let Gt=S;Gt<=1;Gt+=P)H+=X(Vt,st,Gt,S),Gt+P>=1&&(H+=X(Vt,st,1,0))}else{let St=[];if($t=st.x===t[t.length-1].x,l){const Bt=+Vt.x,Kt=new Date(Bt),ne=new Date(Bt+(+st.x-Bt));St=[[e(Kt),n(m(0))],[e(ne),n(m(1))]]}else St=[[e(v(0)),n(m(0))],[e(v(1)),n(m(1))]];s&&St.forEach(Bt=>Bt.reverse());const ct=at(K,H),At=at(K,H+=`L${St[1].join(",")}`),Gt=Dx(ct-ht.lastLength,At-ht.lastLength,Q,$t);ht.lastLength+=Gt.length,ht.dash.push(Gt.dash)}}}return ht.dash.length&&(!$t&&ht.dash.push(at(K,H)),K.remove(),k.attr("stroke-dasharray",ht.dash.join(" "))),H},isWithinRegions(t,e){for(let n=0,a;a=e[n];n++)if(a.startgr();var Ji={initialOpacityForCircle(t){const{config:e,state:{withoutFadeIn:n}}=this;let a=e.point_opacity;return ln(a)&&(a=this.getBaseValue(t)!==null&&n[t.id]?this.opacityForCircle(t):"0"),a},opacityForCircle(t){var e;const{config:n}=this;let a=n.point_opacity;return ln(a)&&(a=n.point_show&&!((e=this.isPointFocusOnly)!=null&&e.call(this))?null:"0",a=De(this.getBaseValue(t))?this.isBubbleType(t)||this.isScatterType(t)?"0.5":a:"0"),a},initCircle(){const t=this,{$el:{main:e}}=t;!t.point&&(t.point=t.generatePoint()),(t.hasType("bubble")||t.hasType("scatter"))&&e.select(`.${Se.chart} > .${$n.chartCircles}`).empty()&&e.select(`.${Se.chart}`).append("g").attr("class",$n.chartCircles)},updateTargetForCircle(t,e){const n=this,{config:a,data:i,$el:o}=n,s=a.interaction_enabled&&a.data_selection_enabled,l=s&&a.data_selection_isselectable,c=n.getClass("circles",!0);if(!a.point_show)return;n.initCircle();let f=t,g=e;if(!f){f=i.targets.filter(m=>this.isScatterType(m)||this.isBubbleType(m));const v=o.main.select(`.${$n.chartCircles}`).style("pointer-events","none").selectAll(`.${$n.circles}`).data(f);v.exit().remove(),g=v.enter()}s&&g.append("g").attr("class",v=>n.generateClass(tn.selectedCircles,v.id)),g.append("g").attr("class",c).call(v=>{n.setCssRule(!0,`.${$n.circles}`,["cursor:pointer"],l)(v),n.setCssRule(!0,` .${$n.circle}`,["fill","stroke"],n.color)(v)}).style("opacity",function(){return ot(this.parentNode).attr("class").indexOf($n.chartCircles)>-1?"0":null}),s&&f.forEach(v=>{o.main.selectAll(`.${tn.selectedCircles}${n.getTargetSelectorSuffix(v.id)}`).selectAll(`${tn.selectedCircle}`).each(m=>{m.value=v.values[m.index].value})})},updateCircle(t=!1){const e=this,{config:n,state:a,$el:i}=e,o=e.isPointFocusOnly(),s=t?i.subchart:i;if(n.point_show&&!a.toggling){n.point_radialGradient&&e.updateLinearGradient();const l=s.main.selectAll(`.${$n.circles}`).selectAll(`.${$n.circle}`).data(c=>e.isLineType(c)&&e.shouldDrawPointsForLine(c)||e.isBubbleType(c)||e.isRadarType(c)||e.isScatterType(c)?o?[c.values[0]]:c.values:[]);l.exit().remove(),l.enter().filter(Boolean).append(e.point("create",this,e.pointR.bind(e),e.updateCircleColor.bind(e))),s.circle=s.main.selectAll(`.${$n.circles} .${$n.circle}`).style("stroke",e.getStylePropValue(e.color)).style("opacity",e.initialOpacityForCircle.bind(e))}},updateCircleColor(t){const e=this,n=e.getStylePropValue(e.color);return e.config.point_radialGradient?e.getGradienColortUrl(t.id):n?n(t):null},redrawCircle(t,e,n,a,i=!1){const o=this,{state:{rendered:s},$el:l,$T:c}=o,f=i?l.subchart:l,g=f.main.selectAll(`.${tn.selectedCircle}`);if(!o.config.point_show)return[];const v=o.point("update",o,t,e,o.updateCircleColor.bind(o),n,a,g),m=o.isCirclePoint()?"c":"",S=gr(),P=o.opacityForCircle.bind(o),N=[];return f.circle.each(function(L){let w=v.bind(this)(L);w=c(w,n||!s,S).style("opacity",P),N.push(w)}),[N,c(g,n).attr(`${m}x`,t).attr(`${m}y`,e)]},showCircleFocus(t){const e=this,{state:{hasRadar:n,resizing:a,toggling:i,transiting:o},$el:s}=e;let{circle:l}=s;if(o===!1&&l&&e.isPointFocusOnly()){const c=(n?e.radarCircleX:e.circleX).bind(e),f=(n?e.radarCircleY:e.circleY).bind(e),g=i||ln(t),v=e.point("update",e,c,f,e.getStylePropValue(e.color),a?!1:g);t&&(l=l.filter(function(m){var S;const P=(S=t.filter)==null?void 0:S.call(t,N=>N.id===m.id);return P.length?ot(this).datum(P[0]):!1})),l.attr("class",this.updatePointClass.bind(this)).style("opacity",null).each(function(m){const{id:S,index:P,value:N}=m;let L="hidden";De(N)&&(v.bind(this)(m),e.expandCircles(P,S),L=""),this.style.visibility=L})}},hideCircleFocus(){const t=this,{$el:{circle:e}}=t;t.isPointFocusOnly()&&e&&(t.unexpandCircles(),e.style("visibility","hidden"))},circleX(t){return this.xx(t)},updateCircleY(t=!1){const e=this,n=e.generateGetLinePoints(e.getShapeIndices(e.isLineType),t);return(a,i)=>{const o=a.id;return e.isGrouped(o)?n(a,i)[0][1]:e.getYScaleById(o,t)(e.getBaseValue(a))}},expandCircles(t,e,n){const a=this,i=a.pointExpandedR.bind(a);n&&a.unexpandCircles();const o=a.getShapeByIndex("circle",t,e).classed(Se.EXPANDED,!0),s=i(o)/a.config.point_r,l=1-s;a.isCirclePoint()?o.attr("r",i):o.each(function(){const c=ot(this);if(this.tagName==="circle")c.attr("r",i);else{const{width:f,height:g}=this.getBBox(),v=l*(+c.attr("x")+f/2),m=l*(+c.attr("y")+g/2);c.attr("transform",`translate(${v} ${m}) scale(${s})`)}})},unexpandCircles(t){const e=this,n=e.pointR.bind(e),a=e.getShapeByIndex("circle",t).filter(function(){return ot(this).classed(Se.EXPANDED)}).classed(Se.EXPANDED,!1);if(a.attr("r",n),!e.isCirclePoint()){const i=n(a)/e.config.point_r;a.attr("transform",i!==1?`scale(${i})`:null)}},pointR(t){const e=this,{config:n}=e,a=n.point_r;let i=a;return e.isBubbleType(t)?i=e.getBubbleR(t):ve(a)&&(i=a.bind(e.api)(t)),t.r=i,i},pointExpandedR(t){const e=this,{config:n}=e,a=e.isBubbleType(t)?1.15:1.75;return n.point_focus_expand_enabled?n.point_focus_expand_r||e.pointR(t)*a:e.pointR(t)},pointSelectR(t){const e=this,n=e.config.point_select_r;return ve(n)?n(t):n||e.pointR(t)*4},isPointFocusOnly(){const t=this;return t.config.point_focus_only&&!t.hasType("bubble")&&!t.hasType("scatter")&&!t.hasArcType(null,["radar"])},isWithinCircle(t,e){const{state:n}=this,a=Hn(n.event,t),i=ot(t),o=this.isCirclePoint(t)?"c":"",s=this.getPointSensitivity(i==null?void 0:i.datum());let l=+i.attr(`${o}x`),c=+i.attr(`${o}y`);if(!(l||c)&&t.nodeType===1){const{x:f,y:g}=Ma(t);l=f,c=g}return Math.sqrt(Math.pow(l-a[0],2)+Math.pow(c-a[1],2))<(e||s)},getPointSensitivity(t){const e=this;let n=e.config.point_sensitivity;if(t)ve(n)?n=n.call(e.api,t):n==="radius"&&(n=t.r);else return n;return n},updatePointClass(t){const e=this,{circle:n}=e.$el;let a=!1;return(Be(t)||n)&&(a=t===!0?n.each(function(i){let o=e.getClass("circle",!0)(i);this.getAttribute("class").indexOf(Se.EXPANDED)>-1&&(o+=` ${Se.EXPANDED}`),this.setAttribute("class",o)}):e.getClass("circle",!0)(t)),a},generateGetLinePoints(t,e){const n=this,{config:a}=n,i=n.getShapeX(0,t,e),o=n.getShapeY(e),s=n.getShapeOffset(n.isLineType,t,e),l=n.getYScaleById.bind(n);return(c,f)=>{const g=l.call(n,c.id,e)(n.getShapeYMin(c.id)),v=s(c,f)||g,m=i(c);let S=o(c);a.axis_rotated&&(c.value>0&&SDe(S.value)?e(S)-c/2:0,v=S=>De(S.value)?n(S)-f/2:0;let m=t;return i&&(o&&m.attr("x",g),m=l.$T(m,i,la()),s&&l.$T(s,i,la())),m.attr("x",g).attr("y",v).style("fill",a)}},circle:{create(t,e,n){return t.append("circle").attr("class",this.updatePointClass.bind(this)).attr("r",e).style("fill",n).node()},update(t,e,n,a,i,o,s){const l=this;let c=t;return l.hasType("bubble")&&c.attr("r",l.pointR.bind(l)),i&&(o&&c.attr("cx",e),c.attr("cx")&&(c=l.$T(c,i,la())),s&&l.$T(c,i,la())),c.attr("cx",e).attr("cy",n).style("fill",a)}},rectangle:{create(t,e,n){const a=i=>e(i)*2;return t.append("rect").attr("class",this.updatePointClass.bind(this)).attr("width",a).attr("height",a).style("fill",n).node()},update(t,e,n,a,i,o,s){const l=this,c=l.config.point_r,f=m=>e(m)-c,g=m=>n(m)-c;let v=t;return i&&(o&&v.attr("x",f),v=l.$T(v,i,la()),s&&l.$T(s,i,la())),v.attr("x",f).attr("y",g).style("fill",a)}}};function Fx(t){return nr(t)&&ve(t.create)&&ve(t.update)}function Bx(t,e){var n;const a=this,i=(c,f)=>{const g=c.attributes;for(let v=0,m;m=g[v];v++)m=m.name,f.setAttribute(m,c.getAttribute(m))},s=new DOMParser().parseFromString(t,"image/svg+xml").documentElement,l=gn.createElementNS(ae.svg,s.nodeName.toLowerCase());if(l.id=e,l.style.fill="inherit",l.style.stroke="inherit",i(s,l),(n=s.childNodes)!=null&&n.length){const c=ot(l);"innerHTML"in l?c.html(s.innerHTML):Lr(s.childNodes).forEach(f=>{i(f,c.append(f.tagName).node())})}a.$el.defs.node().appendChild(l)}var ca={hasValidPointType(t){return/^(circle|rect(angle)?|polygon|ellipse|use)$/i.test(t||this.config.point_type)},hasLegendDefsPoint(){var t;const{config:e}=this;return e.legend_show&&((t=e.point_pattern)==null?void 0:t.length)&&e.legend_usePoint},getDefsPointId(t){const{state:{datetimeId:e}}=this;return`${e}-point${t}`},generatePoint(){const t=this,{$el:e,config:n}=t,a=[],i=cn(n.point_pattern)?n.point_pattern:[n.point_type];return function(o,s,...l){return function(c){var f,g,v,m;const S=t.getTargetSelectorSuffix(c.id||((f=c.data)==null?void 0:f.id)||c),P=ot(this);a.indexOf(S)<0&&a.push(S);let N=i[a.indexOf(S)%i.length];if(t.hasValidPointType(N))N=t[N];else if(!Fx(N||n.point_type)){const L=t.getDefsPointId(S);if(e.defs.select(`#${L}`).size()<1&&Bx.bind(t)(N,L),o==="create")return(g=t.custom)==null?void 0:g.create.bind(s)(P,L,...l);if(o==="update")return(v=t.custom)==null?void 0:v.update.bind(s)(P,...l)}return(m=N[o])==null?void 0:m.bind(s)(P,...l)}}}};function Qu(t){const e=t.config.polar_level_max;let n=t.getMinMaxData().max[0].value;return e&&e>n&&(n=e),n}var Ux={initPolar(){const t=this,{$el:{arcs:e},config:n}=t,a=n.polar_level_text_show,i=n.polar_level_text_backgroundColor;e.levels=e.append("g").attr("class",Tr.levels),a&&i&&t.generateTextBGColorFilter(i)},getPolarOuterRadius(t,e){var n;const a=Qu(this);return((n=t==null?void 0:t.data.values[0].value)!=null?n:0)/a*e},updateTargetsForPolar(t){this.updateTargetsForArc(t)},redrawPolar(){const t=this,{config:e}=t;e.polar_level_show&&t.updatePolarLevel()},updatePolarLevel(){const t=this,{config:e,state:n,$el:{arcs:{levels:a}}}=t,i=e.polar_level_depth,o=Qu(t),s=Ei(0,i),l=n.radius,c=s.map(m=>l*((m+1)/i)),f=(e.polar_level_text_format||function(){}).bind(t.api),g=a.selectAll(`.${Tr.level}`).data(s);g.exit().remove();const v=g.enter().append("g").attr("class",(m,S)=>`${Tr.level} ${Tr.level}-${S}`);if(v.append("circle"),v.merge(g).selectAll("circle").style("visibility",e.polar_level_show?null:"hidden").attr("cx",0).attr("cy",0).attr("r",m=>c[m]),e.polar_level_text_show){const m=e.polar_level_text_backgroundColor,S=`#${n.datetimeId}-labels-bg${t.getTargetSelectorSuffix(m)}`;v.append("text").style("text-anchor","middle"),v.merge(g).selectAll("text").attr("dy",P=>-c[P]+5).attr("filter",m?`url(${S})`:null).text(P=>f(o/s.length*(P+1)))}}};function zx(t,e,n,a,i,o){const s=t&&a>0?n-a:a,l=2*Math.PI;return i*(1-o*(e==="x"?Math.sin:Math.cos)(s*l/n))}const ua=Ln.radarPoints,ku=Ln.radarTextWidth;var jx={initRadar(){const t=this,{config:e,state:{current:n},$el:a}=t;t.hasType("radar")&&(a.radar=a.main.select(`.${Se.chart}`).append("g").attr("class",Qs.chartRadars),a.radar.levels=a.radar.append("g").attr("class",Tr.levels),a.radar.axes=a.radar.append("g").attr("class",Tn.axis),a.radar.shapes=a.radar.append("g").attr("class",sn.shapes),n.dataMax=e.radar_axis_max||t.getMinMaxData().max[0].value,e.radar_axis_text_show&&(e.interaction_enabled&&t.bindRadarEvent(),t.updateRadarLevel(),t.updateRadarAxes()))},getRadarSize(){const t=this,{config:e,state:{arcWidth:n,arcHeight:a}}=t,i=e.axis_x_categories.length<4?-20:10,o=(Math.min(n,a)-i)/2;return[o,o]},updateTargetsForRadar(t){const e=this,{config:n}=e;qn(n.axis_x_categories)&&(n.axis_x_categories=Ei(0,_n("max",t.map(a=>a.values.length)))),e.generateRadarPoints()},getRadarPosition(t,e,n,a){const i=this,{config:o}=i,[s,l]=i.getRadarSize(),c=o.axis_x_categories.length,f=o.radar_direction_clockwise,g=Lr(t).map(v=>zx(f,v,c,e,Qe(n)?n:t==="x"?s:l,he(a)?a:o.radar_size_ratio));return g.length===1?g[0]:g},generateRadarPoints(){const t=this,e=t.data.targets,[n,a]=t.getRadarSize(),i=t.cache.get(ua)||{},o=i._size;(!o||o.width!==n&&o.height!==a)&&(e.forEach(s=>{i[s.id]=s.values.map((l,c)=>t.getRadarPosition(["x","y"],c,void 0,t.getRatio("radar",l)))}),i._size={width:n,height:a},t.cache.add(ua,i))},redrawRadar(){const t=this,{radar:e,main:n}=t.$el,a=t.getTranslate("radar");a&&(e.attr("transform",a),n.select(`.${On.chartTexts}`).attr("transform",a),t.generateRadarPoints(),t.updateRadarLevel(),t.updateRadarAxes(),t.updateRadarShape())},generateGetRadarPoints(){const t=this.cache.get(ua);return(e,n)=>{const a=t[e.id][n];return[a,a,a,a]}},updateRadarLevel(){const t=this,{config:e,state:n,$el:{radar:a}}=t,[i,o]=t.getRadarSize(),s=e.radar_level_depth,l=e.axis_x_categories.length,c=e.radar_level_text_show,f=a.levels,g=Ei(0,s),v=e.radar_size_ratio*Math.min(i,o),m=g.map(w=>v*((w+1)/s)),S=(e.radar_level_text_format||function(){}).bind(t.api),P=g.map(w=>{const X=m[w];return Ei(0,l).map(H=>t.getRadarPosition(["x","y"],H,X,1).join(",")).join(" ")}),N=f.selectAll(`.${Tr.level}`).data(g);N.exit().remove();const L=N.enter().append("g").attr("class",(w,X)=>`${Tr.level} ${Tr.level}-${X}`);L.append("polygon").style("visibility",e.radar_level_show?null:"hidden"),c&&(f.select("text").empty()&&f.append("text").attr("dx","-.5em").attr("dy","-.7em").style("text-anchor","end").text(()=>S(0)),L.append("text").attr("dx","-.5em").style("text-anchor","end").text(w=>S(n.current.dataMax/g.length*(w+1)))),L.merge(N).attr("transform",w=>`translate(${i-m[w]}, ${o-m[w]})`).selectAll("polygon").attr("points",w=>P[w]),c&&f.selectAll("text").attr("x",w=>ln(w)?i:P[w].split(",")[0]).attr("y",w=>ln(w)?o:0)},updateRadarAxes(){const t=this,{config:e,$el:{radar:n}}=t,[a,i]=t.getRadarSize(),o=e.axis_x_categories;let s=n.axes.selectAll("g").data(o);s.exit().remove();const l=s.enter().append("g").attr("class",(c,f)=>`${Tn.axis}-${f}`);if(e.radar_axis_line_show&&l.append("line"),e.radar_axis_text_show&&l.append("text"),s=l.merge(s),e.radar_axis_line_show&&s.select("line").attr("x1",a).attr("y1",i).attr("x2",(c,f)=>t.getRadarPosition("x",f)).attr("y2",(c,f)=>t.getRadarPosition("y",f)),e.radar_axis_text_show){const{x:c=0,y:f=0}=e.radar_axis_text_position,g=t.cache.get(ku)||0;if(s.select("text").style("text-anchor","middle").attr("dy",".5em").call(v=>{v.each(function(m){wa(ot(this),String(m),[-.6,1.2])})}).datum((v,m)=>({index:m})).attr("transform",function(v){ln(this.width)&&(this.width=this.getBoundingClientRect().width/2);let m=t.getRadarPosition("x",v.index,void 0,1),S=Math.round(t.getRadarPosition("y",v.index,void 0,1));return m>a?m+=this.width+c:Math.round(m)i?(S/2===i&&this.firstChild.tagName==="tspan"&&this.firstChild.setAttribute("dy","0em"),S+=f):SYl(m.node()).width);v.every(m=>m>0)&&t.cache.add(ku,v[0]-v[1])}}},bindRadarEvent(){const t=this,{config:e,state:n,$el:{radar:a,svg:i}}=t,o=t.isPointFocusOnly(),{inputType:s,transiting:l}=n,c=s==="mouse",f=g=>{if(n.event=g,!e.interaction_onout)return;const v=t.getDataIndexFromEvent(g),m=ln(v);(c||m)&&(t.hideTooltip(),o?t.hideCircleFocus():t.unexpandCircles(),c?t.setOverOut(!1,v):m&&t.callOverOutForTouch())};a.axes.on(c?"mouseover ":"touchstart",g=>{if(l)return;n.event=g;const v=t.getDataIndexFromEvent(g);t.selectRectForSingle(i.node(),v),c?t.setOverOut(!0,v):t.callOverOutForTouch(v)}).on("mouseout",c?f:null),c||i.on("touchstart",f)},updateRadarShape(){const t=this,e=t.data.targets.filter(o=>t.isRadarType(o)),n=t.cache.get(ua),a=t.$el.radar.shapes.selectAll("polygon").data(e),i=a.enter().append("g").attr("class",t.getChartClass("Radar"));t.$T(a.exit()).remove(),i.append("polygon").merge(a).style("fill",t.color).style("stroke",t.color).attr("points",o=>n[o.id].join(" ")),t.updateTargetForCircle(e,i)},radarCircleX(t){return this.cache.get(ua)[t.id][t.index][0]},radarCircleY(t){return this.cache.get(ua)[t.id][t.index][1]}};function Vx(t){var e=0,n=t.children,a=n&&n.length;if(!a)e=1;else for(;--a>=0;)e+=n[a].value;t.value=e}function Gx(){return this.eachAfter(Vx)}function Xx(t,e){let n=-1;for(const a of this)t.call(e,a,++n,this);return this}function Hx(t,e){for(var n=this,a=[n],i,o,s=-1;n=a.pop();)if(t.call(e,n,++s,this),i=n.children)for(o=i.length-1;o>=0;--o)a.push(i[o]);return this}function Yx(t,e){for(var n=this,a=[n],i=[],o,s,l,c=-1;n=a.pop();)if(i.push(n),o=n.children)for(s=0,l=o.length;s=0;)n+=a[i].value;e.value=n})}function Zx(t){return this.eachBefore(function(e){e.children&&e.children.sort(t)})}function Jx(t){for(var e=this,n=Qx(e,t),a=[e];e!==n;)e=e.parent,a.push(e);for(var i=a.length;t!==n;)a.splice(i,0,t),t=t.parent;return a}function Qx(t,e){if(t===e)return t;var n=t.ancestors(),a=e.ancestors(),i=null;for(t=n.pop(),e=a.pop();t===e;)i=t,t=n.pop(),e=a.pop();return i}function kx(){for(var t=this,e=[t];t=t.parent;)e.push(t);return e}function qx(){return Array.from(this)}function _x(){var t=[];return this.eachBefore(function(e){e.children||t.push(e)}),t}function t0(){var t=this,e=[];return t.each(function(n){n!==t&&e.push({source:n.parent,target:n})}),e}function*e0(){var t=this,e,n=[t],a,i,o;do for(e=n.reverse(),n=[];t=e.pop();)if(yield t,a=t.children)for(i=0,o=a.length;i=0;--l)i.push(o=s[l]=new Qi(s[l])),o.parent=a,o.depth=a.depth+1;return n.eachBefore(o0)}function n0(){return Rs(this).eachBefore(i0)}function r0(t){return t.children}function a0(t){return Array.isArray(t)?t[1]:null}function i0(t){t.data.value!==void 0&&(t.value=t.data.value),t.data=t.data.data}function o0(t){var e=0;do t.height=e;while((t=t.parent)&&t.height<++e)}function Qi(t){this.data=t,this.depth=this.height=0,this.parent=null}Qi.prototype=Rs.prototype={constructor:Qi,count:Gx,each:Xx,eachAfter:Yx,eachBefore:Hx,find:Wx,sum:Kx,sort:Zx,path:Jx,ancestors:kx,descendants:qx,leaves:_x,links:t0,copy:n0,[Symbol.iterator]:e0};function s0(t){t.x0=Math.round(t.x0),t.y0=Math.round(t.y0),t.x1=Math.round(t.x1),t.y1=Math.round(t.y1)}function ki(t,e,n,a,i){for(var o=t.children,s,l=-1,c=o.length,f=t.value&&(a-e)/t.value;++lX&&(X=f),K=L*L*k,W=Math.max(X/K,K/w),W>H){L-=f;break}H=W}s.push(c={value:L,dice:S1?a:1)},n}(qu);function C1(t){return t==null?null:ef(t)}function ef(t){if(typeof t!="function")throw new Error;return t}function ja(){return 0}function Va(t){return function(){return t}}function l0(){var t=tf,e=!1,n=1,a=1,i=[0],o=ja,s=ja,l=ja,c=ja,f=ja;function g(m){return m.x0=m.y0=0,m.x1=n,m.y1=a,m.eachBefore(v),i=[0],e&&m.eachBefore(s0),m}function v(m){var S=i[m.depth],P=m.x0+S,N=m.y0+S,L=m.x1-S,w=m.y1-S;L=m-1){var X=o[v];X.x0=P,X.y0=N,X.x1=L,X.y1=w;return}for(var W=f[v],H=S/2+W,k=v+1,K=m-1;k>>1;f[at]w-N){var dt=S?(P*$t+L*ht)/S:L;g(v,k,ht,P,N,dt,w),g(k,m,$t,dt,N,L,w)}else{var st=S?(N*$t+w*ht)/S:w;g(v,k,ht,P,N,L,st),g(k,m,$t,P,st,L,w)}}}function c0(t,e,n,a,i){(t.depth&1?qi:ki)(t,e,n,a,i)}var u0=function t(e){function n(a,i,o,s,l){if((c=a._squarify)&&c.ratio===e)for(var c,f,g,v,m=-1,S,P=c.length,N=a.value;++m1?a:1)},n}(qu);function f0(t,e){const n=this,{scale:{x:a,y:i},state:{width:o}}=n;t.selectAll("g").attr("transform",s=>`translate(${s===e?"0,0":`${a(s.x0)},${i(s.y0)}`})`).select("rect").attr("width",s=>s===e?o:a(s.x1)-a(s.x0)).attr("height",s=>s===e?0:i(s.y1)-i(s.y0))}function d0(t){const e=this;return t.map(n=>{const{id:a,values:i}=n,{value:o}=i[0];return{name:a,id:a,value:o,ratio:e.getRatio("treemap",i[0])}})}function h0(t){const e=this,n=Rs(t).sum(i=>i.value),a=e.getSortCompareFn(!0);return[e.treemap(a?n.sort(a):n)]}var g0={initTreemap(){const t=this,{$el:e,state:{current:{width:n,height:a},clip:i,datetimeId:o}}=t;i.id=`${o}-clip`,t.treemap=l0().tile(t.getTreemapTile()),e.defs.append("clipPath").attr("id",i.id).append("rect").attr("width",n).attr("height",a),e.treemap=e.main.select(`.${Se.chart}`).attr("clip-path",`url(#${i.id})`).append("g").classed(qs.chartTreemaps,!0),t.bindTreemapEvent()},bindTreemapEvent(){const t=this,{$el:e,config:n,state:a}=t,i=o=>{var s;const l=o.isTrusted?o.target:(s=a.eventReceiver.rect)==null?void 0:s.node();let c;return/^rect$/i.test(l.tagName)&&(a.event=o,c=ot(l).datum()),c==null?void 0:c.data};if(n.interaction_enabled){const o=a.inputType==="touch";e.treemap.on(o?"touchstart":"mouseover mousemove",s=>{const l=i(s);l&&(t.showTooltip([l],s.currentTarget),/^(touchstart|mouseover)$/.test(s.type)&&t.setOverOut(!0,l))}).on(o?"touchend":"mouseout",s=>{const l=i(s);n.interaction_onout&&(t.hideTooltip(),t.setOverOut(!1,l))})}},getTreemapTile(){var t,e;const n=this,{config:a,state:{current:{width:i,height:o}}}=n,s=(e={binary:nf,dice:ki,slice:qi,sliceDice:c0,squarify:tf,resquarify:u0}[(t=a.treemap_tile)!=null?t:"binary"])!=null?e:nf;return(l,c,f,g,v)=>{s(l,0,0,i,o);for(const m of l.children)m.x0=c+m.x0/i*(g-c),m.x1=c+m.x1/i*(g-c),m.y0=f+m.y0/o*(v-f),m.y1=f+m.y1/o*(v-f)}},getTreemapData(t){const e=this;return{name:"root",children:d0.bind(e)(e.filterTargetsToShow(t.filter(e.isTreemapType,e)))}},updateTargetsForTreemap(t){const e=this,{$el:{treemap:n}}=e,a=h0.call(e,e.getTreemapData(t!=null?t:e.data.targets));n.data(a)},updateTreemap(t){const e=this,{$el:n,$T:a}=e,i=n.treemap.datum(),o=e.getChartClass("Treemap"),s=e.getClass("treemap",!0),l=n.treemap.selectAll("g").data(i.children);a(l.exit(),t).style("opacity","0").remove(),l.enter().append("g").append("rect"),n.treemap.selectAll("g").attr("class",o).select("rect").attr("class",s).attr("fill",c=>e.color(c.data.name))},generateGetTreemapPoints(){const t=this,{$el:e,scale:{x:n,y:a}}=t,i={};return e.treemap.selectAll("g").each(o=>{i[o.data.name]=[[n(o.x0),a(o.y0)],[n(o.x1),a(o.y1)]]}),o=>i[o.id]},redrawTreemap(t){const e=this,{$el:n,state:{current:{width:a,height:i}}}=e;return n.defs.select("rect").attr("width",a).attr("height",i),[e.$T(n.treemap,t,gr()).call(f0.bind(e),n.treemap.datum())]},treemapDataLabelFormat(t){const e=this,{config:n}=e,{id:a,value:i}=t,o=n.treemap_label_format,s=e.getRatio("treemap",t),l=(s*100).toFixed(2),c=n.treemap_label_show&&e.meetsLabelThreshold(s,"treemap")?null:"0";return function(f){return f.style("opacity",c),ve(o)?o.bind(e.api)(i,s,a):`${a} +${l}%`}}},Xr={point_show:!0,point_r:2.5,point_radialGradient:!1,point_sensitivity:10,point_focus_expand_enabled:!0,point_focus_expand_r:void 0,point_focus_only:!1,point_opacity:void 0,point_pattern:[],point_select_r:void 0,point_type:"circle"},fa={area_above:!1,area_below:!1,area_front:!0,area_linearGradient:!1,area_zerobased:!0},v0={bar_front:!1,bar_indices_removeNull:!1,bar_label_threshold:0,bar_linearGradient:!1,bar_overlap:!1,bar_padding:0,bar_radius:void 0,bar_radius_ratio:void 0,bar_sensitivity:2,bar_width:void 0,bar_width_ratio:.6,bar_width_max:void 0,bar_zerobased:!0},p0={bubble_maxR:35,bubble_zerobased:!1},m0={candlestick_width:void 0,candlestick_width_ratio:.6,candlestick_width_max:void 0,candlestick_color_down:"red"},y0={line_connectNull:!1,line_step_type:"step",line_step_tooltipMatch:!1,line_zerobased:!1,line_classes:void 0,line_point:!0},x0={scatter_zerobased:!1},Is={spline_interpolation_type:"cardinal"},_i={arc_cornerRadius:0,arc_cornerRadius_ratio:0,arc_needle_show:!1,arc_needle_color:void 0,arc_needle_value:void 0,arc_needle_path:void 0,arc_needle_length:100,arc_needle_top_rx:0,arc_needle_top_ry:0,arc_needle_top_width:0,arc_needle_bottom_rx:1,arc_needle_bottom_ry:1,arc_needle_bottom_width:15,arc_needle_bottom_len:0,arc_rangeText_values:void 0,arc_rangeText_unit:"absolute",arc_rangeText_fixed:!1,arc_rangeText_format:void 0,arc_rangeText_position:void 0},T0={donut_label_show:!0,donut_label_format:void 0,donut_label_threshold:.05,donut_label_ratio:void 0,donut_width:void 0,donut_title:"",donut_expand:{},donut_expand_rate:.98,donut_expand_duration:50,donut_padAngle:0,donut_startingAngle:0},$0={funnel_neck_width:0,funnel_neck_height:0},S0={gauge_background:"",gauge_fullCircle:!1,gauge_label_show:!0,gauge_label_extents:void 0,gauge_label_format:void 0,gauge_label_ratio:void 0,gauge_label_threshold:0,gauge_enforceMinMax:!1,gauge_min:0,gauge_max:100,gauge_type:"single",gauge_startingAngle:-1*Math.PI/2,gauge_arcLength:100,gauge_title:"",gauge_units:void 0,gauge_width:void 0,gauge_arcs_minWidth:5,gauge_expand:{},gauge_expand_rate:.98,gauge_expand_duration:50},A0={pie_label_show:!0,pie_label_format:void 0,pie_label_ratio:void 0,pie_label_threshold:.05,pie_expand:{},pie_expand_rate:.98,pie_expand_duration:50,pie_innerRadius:0,pie_outerRadius:void 0,pie_padAngle:0,pie_padding:0,pie_startingAngle:0},E0={polar_label_show:!0,polar_label_format:void 0,polar_label_threshold:.05,polar_label_ratio:void 0,polar_level_depth:3,polar_level_max:void 0,polar_level_show:!0,polar_level_text_backgroundColor:"#fff",polar_level_text_format:t=>t%1===0?t:t.toFixed(2),polar_level_text_show:!0,polar_padAngle:0,polar_padding:0,polar_startingAngle:0},b0={radar_axis_max:void 0,radar_axis_line_show:!0,radar_axis_text_show:!0,radar_axis_text_position:{},radar_level_depth:3,radar_level_show:!0,radar_level_text_format:t=>t%1===0?t:t.toFixed(2),radar_level_text_show:!0,radar_size_ratio:.87,radar_direction_clockwise:!1},R0={treemap_tile:"binary",treemap_label_format:void 0,treemap_label_threshold:.05,treemap_label_show:!0};function da(t,e){yn(Vr.prototype,Object.values(Du).concat(t)),yn(Er.prototype,Jy),Nr.setOptions(Object.values(Lu).concat(e||[]))}function mr(t,e){da([ca,Ji,Nx].concat(t||[])),Nr.setOptions([Xr,y0].concat(e||[]))}function ha(t,e){yn(Vr.prototype,[px,ca].concat(t||[])),Nr.setOptions([Xr].concat(e||[]))}let rf=()=>(mr(sa,[fa]),(rf=()=>oe.AREA)()),af=()=>(mr(sa,[fa]),(af=()=>oe.AREA_LINE_RANGE)()),of=()=>(mr(sa,[fa]),(of=()=>oe.AREA_STEP_RANGE)()),sf=()=>(mr(sa,[fa,Is]),(sf=()=>oe.AREA_SPLINE)()),lf=()=>(mr(sa,[fa,Is]),(lf=()=>oe.AREA_SPLINE_RANGE)()),cf=()=>(mr(sa,[fa]),(cf=()=>oe.AREA_STEP)()),uf=()=>(mr(),(uf=()=>oe.LINE)()),ff=()=>(mr(void 0,[Is]),(ff=()=>oe.SPLINE)()),df=()=>(mr(),(df=()=>oe.STEP)()),hf=()=>(ha(void 0,[_i,T0]),(hf=()=>oe.DONUT)()),gf=()=>(ha([Mx],[_i,S0]),(gf=()=>oe.GAUGE)()),vf=()=>(ha(void 0,[_i,A0]),(vf=()=>oe.PIE)()),pf=()=>(ha([Ux],[_i,E0]),(pf=()=>oe.POLAR)()),mf=()=>(ha([Du.eventrect,Ji,jx],[Xr,b0,{axis_x_categories:Lu.optAxis.axis_x_categories}]),(mf=()=>oe.RADAR)()),yf=()=>(da([yx,ca],[v0,Xr]),(yf=()=>oe.BAR)()),xf=()=>(da([ca,Ji,xx],[p0,Xr]),(xf=()=>oe.BUBBLE)()),Tf=()=>(da([Ex,ca],[m0,Xr]),(Tf=()=>oe.CANDLESTICK)()),$f=()=>(da([ca,Ji],[Xr,x0]),($f=()=>oe.SCATTER)()),Sf=()=>(ha([wx],[$0]),(Sf=()=>oe.FUNNEL)()),Af=()=>(da([g0],[R0]),(Af=()=>oe.TREEMAP)()),Os=Object.create(null);const Ef={version:"3.15.1",generate(t){const e=ea(Object.create(null),Os,t),n=new Er(e);return n.internal.charts=this.instance,this.instance.push(n),n},defaults(t){return Be(t)&&(Os=t),Os},instance:[],plugin:{}};Object.keys(d).forEach(t=>d[t]()),Object.keys(u).forEach(t=>u[t]())}],Xa={};function zn(x){var b=Xa[x];if(b!==void 0)return b.exports;var r=Xa[x]={exports:{}};return Cs[x].call(r.exports,r,r.exports,zn),r.exports}(function(){zn.d=function(x,b){for(var r in b)zn.o(b,r)&&!zn.o(x,r)&&Object.defineProperty(x,r,{enumerable:!0,get:b[r]})}})(),function(){zn.o=function(x,b){return Object.prototype.hasOwnProperty.call(x,b)}}(),function(){zn.r=function(x){typeof Symbol!="undefined"&&Symbol.toStringTag&&Object.defineProperty(x,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(x,"__esModule",{value:!0})}}(),zn(0);var Ha=zn(584);return Ha}()}); diff --git a/app/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/js/bootstrap.bundle.min.js b/app/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/js/bootstrap.bundle.min.js new file mode 100644 index 000000000..8739c91c5 --- /dev/null +++ b/app/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/js/bootstrap.bundle.min.js @@ -0,0 +1,7 @@ +/*! + * Bootstrap v5.3.6 (https://getbootstrap.com/) + * Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap=e()}(this,(function(){"use strict";const t=new Map,e={set(e,i,n){t.has(e)||t.set(e,new Map);const s=t.get(e);s.has(i)||0===s.size?s.set(i,n):console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(s.keys())[0]}.`)},get:(e,i)=>t.has(e)&&t.get(e).get(i)||null,remove(e,i){if(!t.has(e))return;const n=t.get(e);n.delete(i),0===n.size&&t.delete(e)}},i="transitionend",n=t=>(t&&window.CSS&&window.CSS.escape&&(t=t.replace(/#([^\s"#']+)/g,((t,e)=>`#${CSS.escape(e)}`))),t),s=t=>{t.dispatchEvent(new Event(i))},o=t=>!(!t||"object"!=typeof t)&&(void 0!==t.jquery&&(t=t[0]),void 0!==t.nodeType),r=t=>o(t)?t.jquery?t[0]:t:"string"==typeof t&&t.length>0?document.querySelector(n(t)):null,a=t=>{if(!o(t)||0===t.getClientRects().length)return!1;const e="visible"===getComputedStyle(t).getPropertyValue("visibility"),i=t.closest("details:not([open])");if(!i)return e;if(i!==t){const e=t.closest("summary");if(e&&e.parentNode!==i)return!1;if(null===e)return!1}return e},l=t=>!t||t.nodeType!==Node.ELEMENT_NODE||!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")),c=t=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){const e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?c(t.parentNode):null},h=()=>{},d=t=>{t.offsetHeight},u=()=>window.jQuery&&!document.body.hasAttribute("data-bs-no-jquery")?window.jQuery:null,f=[],p=()=>"rtl"===document.documentElement.dir,m=t=>{var e;e=()=>{const e=u();if(e){const i=t.NAME,n=e.fn[i];e.fn[i]=t.jQueryInterface,e.fn[i].Constructor=t,e.fn[i].noConflict=()=>(e.fn[i]=n,t.jQueryInterface)}},"loading"===document.readyState?(f.length||document.addEventListener("DOMContentLoaded",(()=>{for(const t of f)t()})),f.push(e)):e()},g=(t,e=[],i=t)=>"function"==typeof t?t.call(...e):i,_=(t,e,n=!0)=>{if(!n)return void g(t);const o=(t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:i}=window.getComputedStyle(t);const n=Number.parseFloat(e),s=Number.parseFloat(i);return n||s?(e=e.split(",")[0],i=i.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(i))):0})(e)+5;let r=!1;const a=({target:n})=>{n===e&&(r=!0,e.removeEventListener(i,a),g(t))};e.addEventListener(i,a),setTimeout((()=>{r||s(e)}),o)},b=(t,e,i,n)=>{const s=t.length;let o=t.indexOf(e);return-1===o?!i&&n?t[s-1]:t[0]:(o+=i?1:-1,n&&(o=(o+s)%s),t[Math.max(0,Math.min(o,s-1))])},v=/[^.]*(?=\..*)\.|.*/,y=/\..*/,w=/::\d+$/,A={};let E=1;const T={mouseenter:"mouseover",mouseleave:"mouseout"},C=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function O(t,e){return e&&`${e}::${E++}`||t.uidEvent||E++}function x(t){const e=O(t);return t.uidEvent=e,A[e]=A[e]||{},A[e]}function k(t,e,i=null){return Object.values(t).find((t=>t.callable===e&&t.delegationSelector===i))}function L(t,e,i){const n="string"==typeof e,s=n?i:e||i;let o=I(t);return C.has(o)||(o=t),[n,s,o]}function S(t,e,i,n,s){if("string"!=typeof e||!t)return;let[o,r,a]=L(e,i,n);if(e in T){const t=t=>function(e){if(!e.relatedTarget||e.relatedTarget!==e.delegateTarget&&!e.delegateTarget.contains(e.relatedTarget))return t.call(this,e)};r=t(r)}const l=x(t),c=l[a]||(l[a]={}),h=k(c,r,o?i:null);if(h)return void(h.oneOff=h.oneOff&&s);const d=O(r,e.replace(v,"")),u=o?function(t,e,i){return function n(s){const o=t.querySelectorAll(e);for(let{target:r}=s;r&&r!==this;r=r.parentNode)for(const a of o)if(a===r)return P(s,{delegateTarget:r}),n.oneOff&&N.off(t,s.type,e,i),i.apply(r,[s])}}(t,i,r):function(t,e){return function i(n){return P(n,{delegateTarget:t}),i.oneOff&&N.off(t,n.type,e),e.apply(t,[n])}}(t,r);u.delegationSelector=o?i:null,u.callable=r,u.oneOff=s,u.uidEvent=d,c[d]=u,t.addEventListener(a,u,o)}function D(t,e,i,n,s){const o=k(e[i],n,s);o&&(t.removeEventListener(i,o,Boolean(s)),delete e[i][o.uidEvent])}function $(t,e,i,n){const s=e[i]||{};for(const[o,r]of Object.entries(s))o.includes(n)&&D(t,e,i,r.callable,r.delegationSelector)}function I(t){return t=t.replace(y,""),T[t]||t}const N={on(t,e,i,n){S(t,e,i,n,!1)},one(t,e,i,n){S(t,e,i,n,!0)},off(t,e,i,n){if("string"!=typeof e||!t)return;const[s,o,r]=L(e,i,n),a=r!==e,l=x(t),c=l[r]||{},h=e.startsWith(".");if(void 0===o){if(h)for(const i of Object.keys(l))$(t,l,i,e.slice(1));for(const[i,n]of Object.entries(c)){const s=i.replace(w,"");a&&!e.includes(s)||D(t,l,r,n.callable,n.delegationSelector)}}else{if(!Object.keys(c).length)return;D(t,l,r,o,s?i:null)}},trigger(t,e,i){if("string"!=typeof e||!t)return null;const n=u();let s=null,o=!0,r=!0,a=!1;e!==I(e)&&n&&(s=n.Event(e,i),n(t).trigger(s),o=!s.isPropagationStopped(),r=!s.isImmediatePropagationStopped(),a=s.isDefaultPrevented());const l=P(new Event(e,{bubbles:o,cancelable:!0}),i);return a&&l.preventDefault(),r&&t.dispatchEvent(l),l.defaultPrevented&&s&&s.preventDefault(),l}};function P(t,e={}){for(const[i,n]of Object.entries(e))try{t[i]=n}catch(e){Object.defineProperty(t,i,{configurable:!0,get:()=>n})}return t}function j(t){if("true"===t)return!0;if("false"===t)return!1;if(t===Number(t).toString())return Number(t);if(""===t||"null"===t)return null;if("string"!=typeof t)return t;try{return JSON.parse(decodeURIComponent(t))}catch(e){return t}}function M(t){return t.replace(/[A-Z]/g,(t=>`-${t.toLowerCase()}`))}const F={setDataAttribute(t,e,i){t.setAttribute(`data-bs-${M(e)}`,i)},removeDataAttribute(t,e){t.removeAttribute(`data-bs-${M(e)}`)},getDataAttributes(t){if(!t)return{};const e={},i=Object.keys(t.dataset).filter((t=>t.startsWith("bs")&&!t.startsWith("bsConfig")));for(const n of i){let i=n.replace(/^bs/,"");i=i.charAt(0).toLowerCase()+i.slice(1),e[i]=j(t.dataset[n])}return e},getDataAttribute:(t,e)=>j(t.getAttribute(`data-bs-${M(e)}`))};class H{static get Default(){return{}}static get DefaultType(){return{}}static get NAME(){throw new Error('You have to implement the static method "NAME", for each component!')}_getConfig(t){return t=this._mergeConfigObj(t),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}_configAfterMerge(t){return t}_mergeConfigObj(t,e){const i=o(e)?F.getDataAttribute(e,"config"):{};return{...this.constructor.Default,..."object"==typeof i?i:{},...o(e)?F.getDataAttributes(e):{},..."object"==typeof t?t:{}}}_typeCheckConfig(t,e=this.constructor.DefaultType){for(const[n,s]of Object.entries(e)){const e=t[n],r=o(e)?"element":null==(i=e)?`${i}`:Object.prototype.toString.call(i).match(/\s([a-z]+)/i)[1].toLowerCase();if(!new RegExp(s).test(r))throw new TypeError(`${this.constructor.NAME.toUpperCase()}: Option "${n}" provided type "${r}" but expected type "${s}".`)}var i}}class W extends H{constructor(t,i){super(),(t=r(t))&&(this._element=t,this._config=this._getConfig(i),e.set(this._element,this.constructor.DATA_KEY,this))}dispose(){e.remove(this._element,this.constructor.DATA_KEY),N.off(this._element,this.constructor.EVENT_KEY);for(const t of Object.getOwnPropertyNames(this))this[t]=null}_queueCallback(t,e,i=!0){_(t,e,i)}_getConfig(t){return t=this._mergeConfigObj(t,this._element),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}static getInstance(t){return e.get(r(t),this.DATA_KEY)}static getOrCreateInstance(t,e={}){return this.getInstance(t)||new this(t,"object"==typeof e?e:null)}static get VERSION(){return"5.3.6"}static get DATA_KEY(){return`bs.${this.NAME}`}static get EVENT_KEY(){return`.${this.DATA_KEY}`}static eventName(t){return`${t}${this.EVENT_KEY}`}}const B=t=>{let e=t.getAttribute("data-bs-target");if(!e||"#"===e){let i=t.getAttribute("href");if(!i||!i.includes("#")&&!i.startsWith("."))return null;i.includes("#")&&!i.startsWith("#")&&(i=`#${i.split("#")[1]}`),e=i&&"#"!==i?i.trim():null}return e?e.split(",").map((t=>n(t))).join(","):null},z={find:(t,e=document.documentElement)=>[].concat(...Element.prototype.querySelectorAll.call(e,t)),findOne:(t,e=document.documentElement)=>Element.prototype.querySelector.call(e,t),children:(t,e)=>[].concat(...t.children).filter((t=>t.matches(e))),parents(t,e){const i=[];let n=t.parentNode.closest(e);for(;n;)i.push(n),n=n.parentNode.closest(e);return i},prev(t,e){let i=t.previousElementSibling;for(;i;){if(i.matches(e))return[i];i=i.previousElementSibling}return[]},next(t,e){let i=t.nextElementSibling;for(;i;){if(i.matches(e))return[i];i=i.nextElementSibling}return[]},focusableChildren(t){const e=["a","button","input","textarea","select","details","[tabindex]",'[contenteditable="true"]'].map((t=>`${t}:not([tabindex^="-"])`)).join(",");return this.find(e,t).filter((t=>!l(t)&&a(t)))},getSelectorFromElement(t){const e=B(t);return e&&z.findOne(e)?e:null},getElementFromSelector(t){const e=B(t);return e?z.findOne(e):null},getMultipleElementsFromSelector(t){const e=B(t);return e?z.find(e):[]}},R=(t,e="hide")=>{const i=`click.dismiss${t.EVENT_KEY}`,n=t.NAME;N.on(document,i,`[data-bs-dismiss="${n}"]`,(function(i){if(["A","AREA"].includes(this.tagName)&&i.preventDefault(),l(this))return;const s=z.getElementFromSelector(this)||this.closest(`.${n}`);t.getOrCreateInstance(s)[e]()}))},q=".bs.alert",V=`close${q}`,K=`closed${q}`;class Q extends W{static get NAME(){return"alert"}close(){if(N.trigger(this._element,V).defaultPrevented)return;this._element.classList.remove("show");const t=this._element.classList.contains("fade");this._queueCallback((()=>this._destroyElement()),this._element,t)}_destroyElement(){this._element.remove(),N.trigger(this._element,K),this.dispose()}static jQueryInterface(t){return this.each((function(){const e=Q.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}R(Q,"close"),m(Q);const X='[data-bs-toggle="button"]';class Y extends W{static get NAME(){return"button"}toggle(){this._element.setAttribute("aria-pressed",this._element.classList.toggle("active"))}static jQueryInterface(t){return this.each((function(){const e=Y.getOrCreateInstance(this);"toggle"===t&&e[t]()}))}}N.on(document,"click.bs.button.data-api",X,(t=>{t.preventDefault();const e=t.target.closest(X);Y.getOrCreateInstance(e).toggle()})),m(Y);const U=".bs.swipe",G=`touchstart${U}`,J=`touchmove${U}`,Z=`touchend${U}`,tt=`pointerdown${U}`,et=`pointerup${U}`,it={endCallback:null,leftCallback:null,rightCallback:null},nt={endCallback:"(function|null)",leftCallback:"(function|null)",rightCallback:"(function|null)"};class st extends H{constructor(t,e){super(),this._element=t,t&&st.isSupported()&&(this._config=this._getConfig(e),this._deltaX=0,this._supportPointerEvents=Boolean(window.PointerEvent),this._initEvents())}static get Default(){return it}static get DefaultType(){return nt}static get NAME(){return"swipe"}dispose(){N.off(this._element,U)}_start(t){this._supportPointerEvents?this._eventIsPointerPenTouch(t)&&(this._deltaX=t.clientX):this._deltaX=t.touches[0].clientX}_end(t){this._eventIsPointerPenTouch(t)&&(this._deltaX=t.clientX-this._deltaX),this._handleSwipe(),g(this._config.endCallback)}_move(t){this._deltaX=t.touches&&t.touches.length>1?0:t.touches[0].clientX-this._deltaX}_handleSwipe(){const t=Math.abs(this._deltaX);if(t<=40)return;const e=t/this._deltaX;this._deltaX=0,e&&g(e>0?this._config.rightCallback:this._config.leftCallback)}_initEvents(){this._supportPointerEvents?(N.on(this._element,tt,(t=>this._start(t))),N.on(this._element,et,(t=>this._end(t))),this._element.classList.add("pointer-event")):(N.on(this._element,G,(t=>this._start(t))),N.on(this._element,J,(t=>this._move(t))),N.on(this._element,Z,(t=>this._end(t))))}_eventIsPointerPenTouch(t){return this._supportPointerEvents&&("pen"===t.pointerType||"touch"===t.pointerType)}static isSupported(){return"ontouchstart"in document.documentElement||navigator.maxTouchPoints>0}}const ot=".bs.carousel",rt=".data-api",at="ArrowLeft",lt="ArrowRight",ct="next",ht="prev",dt="left",ut="right",ft=`slide${ot}`,pt=`slid${ot}`,mt=`keydown${ot}`,gt=`mouseenter${ot}`,_t=`mouseleave${ot}`,bt=`dragstart${ot}`,vt=`load${ot}${rt}`,yt=`click${ot}${rt}`,wt="carousel",At="active",Et=".active",Tt=".carousel-item",Ct=Et+Tt,Ot={[at]:ut,[lt]:dt},xt={interval:5e3,keyboard:!0,pause:"hover",ride:!1,touch:!0,wrap:!0},kt={interval:"(number|boolean)",keyboard:"boolean",pause:"(string|boolean)",ride:"(boolean|string)",touch:"boolean",wrap:"boolean"};class Lt extends W{constructor(t,e){super(t,e),this._interval=null,this._activeElement=null,this._isSliding=!1,this.touchTimeout=null,this._swipeHelper=null,this._indicatorsElement=z.findOne(".carousel-indicators",this._element),this._addEventListeners(),this._config.ride===wt&&this.cycle()}static get Default(){return xt}static get DefaultType(){return kt}static get NAME(){return"carousel"}next(){this._slide(ct)}nextWhenVisible(){!document.hidden&&a(this._element)&&this.next()}prev(){this._slide(ht)}pause(){this._isSliding&&s(this._element),this._clearInterval()}cycle(){this._clearInterval(),this._updateInterval(),this._interval=setInterval((()=>this.nextWhenVisible()),this._config.interval)}_maybeEnableCycle(){this._config.ride&&(this._isSliding?N.one(this._element,pt,(()=>this.cycle())):this.cycle())}to(t){const e=this._getItems();if(t>e.length-1||t<0)return;if(this._isSliding)return void N.one(this._element,pt,(()=>this.to(t)));const i=this._getItemIndex(this._getActive());if(i===t)return;const n=t>i?ct:ht;this._slide(n,e[t])}dispose(){this._swipeHelper&&this._swipeHelper.dispose(),super.dispose()}_configAfterMerge(t){return t.defaultInterval=t.interval,t}_addEventListeners(){this._config.keyboard&&N.on(this._element,mt,(t=>this._keydown(t))),"hover"===this._config.pause&&(N.on(this._element,gt,(()=>this.pause())),N.on(this._element,_t,(()=>this._maybeEnableCycle()))),this._config.touch&&st.isSupported()&&this._addTouchEventListeners()}_addTouchEventListeners(){for(const t of z.find(".carousel-item img",this._element))N.on(t,bt,(t=>t.preventDefault()));const t={leftCallback:()=>this._slide(this._directionToOrder(dt)),rightCallback:()=>this._slide(this._directionToOrder(ut)),endCallback:()=>{"hover"===this._config.pause&&(this.pause(),this.touchTimeout&&clearTimeout(this.touchTimeout),this.touchTimeout=setTimeout((()=>this._maybeEnableCycle()),500+this._config.interval))}};this._swipeHelper=new st(this._element,t)}_keydown(t){if(/input|textarea/i.test(t.target.tagName))return;const e=Ot[t.key];e&&(t.preventDefault(),this._slide(this._directionToOrder(e)))}_getItemIndex(t){return this._getItems().indexOf(t)}_setActiveIndicatorElement(t){if(!this._indicatorsElement)return;const e=z.findOne(Et,this._indicatorsElement);e.classList.remove(At),e.removeAttribute("aria-current");const i=z.findOne(`[data-bs-slide-to="${t}"]`,this._indicatorsElement);i&&(i.classList.add(At),i.setAttribute("aria-current","true"))}_updateInterval(){const t=this._activeElement||this._getActive();if(!t)return;const e=Number.parseInt(t.getAttribute("data-bs-interval"),10);this._config.interval=e||this._config.defaultInterval}_slide(t,e=null){if(this._isSliding)return;const i=this._getActive(),n=t===ct,s=e||b(this._getItems(),i,n,this._config.wrap);if(s===i)return;const o=this._getItemIndex(s),r=e=>N.trigger(this._element,e,{relatedTarget:s,direction:this._orderToDirection(t),from:this._getItemIndex(i),to:o});if(r(ft).defaultPrevented)return;if(!i||!s)return;const a=Boolean(this._interval);this.pause(),this._isSliding=!0,this._setActiveIndicatorElement(o),this._activeElement=s;const l=n?"carousel-item-start":"carousel-item-end",c=n?"carousel-item-next":"carousel-item-prev";s.classList.add(c),d(s),i.classList.add(l),s.classList.add(l),this._queueCallback((()=>{s.classList.remove(l,c),s.classList.add(At),i.classList.remove(At,c,l),this._isSliding=!1,r(pt)}),i,this._isAnimated()),a&&this.cycle()}_isAnimated(){return this._element.classList.contains("slide")}_getActive(){return z.findOne(Ct,this._element)}_getItems(){return z.find(Tt,this._element)}_clearInterval(){this._interval&&(clearInterval(this._interval),this._interval=null)}_directionToOrder(t){return p()?t===dt?ht:ct:t===dt?ct:ht}_orderToDirection(t){return p()?t===ht?dt:ut:t===ht?ut:dt}static jQueryInterface(t){return this.each((function(){const e=Lt.getOrCreateInstance(this,t);if("number"!=typeof t){if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}else e.to(t)}))}}N.on(document,yt,"[data-bs-slide], [data-bs-slide-to]",(function(t){const e=z.getElementFromSelector(this);if(!e||!e.classList.contains(wt))return;t.preventDefault();const i=Lt.getOrCreateInstance(e),n=this.getAttribute("data-bs-slide-to");return n?(i.to(n),void i._maybeEnableCycle()):"next"===F.getDataAttribute(this,"slide")?(i.next(),void i._maybeEnableCycle()):(i.prev(),void i._maybeEnableCycle())})),N.on(window,vt,(()=>{const t=z.find('[data-bs-ride="carousel"]');for(const e of t)Lt.getOrCreateInstance(e)})),m(Lt);const St=".bs.collapse",Dt=`show${St}`,$t=`shown${St}`,It=`hide${St}`,Nt=`hidden${St}`,Pt=`click${St}.data-api`,jt="show",Mt="collapse",Ft="collapsing",Ht=`:scope .${Mt} .${Mt}`,Wt='[data-bs-toggle="collapse"]',Bt={parent:null,toggle:!0},zt={parent:"(null|element)",toggle:"boolean"};class Rt extends W{constructor(t,e){super(t,e),this._isTransitioning=!1,this._triggerArray=[];const i=z.find(Wt);for(const t of i){const e=z.getSelectorFromElement(t),i=z.find(e).filter((t=>t===this._element));null!==e&&i.length&&this._triggerArray.push(t)}this._initializeChildren(),this._config.parent||this._addAriaAndCollapsedClass(this._triggerArray,this._isShown()),this._config.toggle&&this.toggle()}static get Default(){return Bt}static get DefaultType(){return zt}static get NAME(){return"collapse"}toggle(){this._isShown()?this.hide():this.show()}show(){if(this._isTransitioning||this._isShown())return;let t=[];if(this._config.parent&&(t=this._getFirstLevelChildren(".collapse.show, .collapse.collapsing").filter((t=>t!==this._element)).map((t=>Rt.getOrCreateInstance(t,{toggle:!1})))),t.length&&t[0]._isTransitioning)return;if(N.trigger(this._element,Dt).defaultPrevented)return;for(const e of t)e.hide();const e=this._getDimension();this._element.classList.remove(Mt),this._element.classList.add(Ft),this._element.style[e]=0,this._addAriaAndCollapsedClass(this._triggerArray,!0),this._isTransitioning=!0;const i=`scroll${e[0].toUpperCase()+e.slice(1)}`;this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(Ft),this._element.classList.add(Mt,jt),this._element.style[e]="",N.trigger(this._element,$t)}),this._element,!0),this._element.style[e]=`${this._element[i]}px`}hide(){if(this._isTransitioning||!this._isShown())return;if(N.trigger(this._element,It).defaultPrevented)return;const t=this._getDimension();this._element.style[t]=`${this._element.getBoundingClientRect()[t]}px`,d(this._element),this._element.classList.add(Ft),this._element.classList.remove(Mt,jt);for(const t of this._triggerArray){const e=z.getElementFromSelector(t);e&&!this._isShown(e)&&this._addAriaAndCollapsedClass([t],!1)}this._isTransitioning=!0,this._element.style[t]="",this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(Ft),this._element.classList.add(Mt),N.trigger(this._element,Nt)}),this._element,!0)}_isShown(t=this._element){return t.classList.contains(jt)}_configAfterMerge(t){return t.toggle=Boolean(t.toggle),t.parent=r(t.parent),t}_getDimension(){return this._element.classList.contains("collapse-horizontal")?"width":"height"}_initializeChildren(){if(!this._config.parent)return;const t=this._getFirstLevelChildren(Wt);for(const e of t){const t=z.getElementFromSelector(e);t&&this._addAriaAndCollapsedClass([e],this._isShown(t))}}_getFirstLevelChildren(t){const e=z.find(Ht,this._config.parent);return z.find(t,this._config.parent).filter((t=>!e.includes(t)))}_addAriaAndCollapsedClass(t,e){if(t.length)for(const i of t)i.classList.toggle("collapsed",!e),i.setAttribute("aria-expanded",e)}static jQueryInterface(t){const e={};return"string"==typeof t&&/show|hide/.test(t)&&(e.toggle=!1),this.each((function(){const i=Rt.getOrCreateInstance(this,e);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t]()}}))}}N.on(document,Pt,Wt,(function(t){("A"===t.target.tagName||t.delegateTarget&&"A"===t.delegateTarget.tagName)&&t.preventDefault();for(const t of z.getMultipleElementsFromSelector(this))Rt.getOrCreateInstance(t,{toggle:!1}).toggle()})),m(Rt);var qt="top",Vt="bottom",Kt="right",Qt="left",Xt="auto",Yt=[qt,Vt,Kt,Qt],Ut="start",Gt="end",Jt="clippingParents",Zt="viewport",te="popper",ee="reference",ie=Yt.reduce((function(t,e){return t.concat([e+"-"+Ut,e+"-"+Gt])}),[]),ne=[].concat(Yt,[Xt]).reduce((function(t,e){return t.concat([e,e+"-"+Ut,e+"-"+Gt])}),[]),se="beforeRead",oe="read",re="afterRead",ae="beforeMain",le="main",ce="afterMain",he="beforeWrite",de="write",ue="afterWrite",fe=[se,oe,re,ae,le,ce,he,de,ue];function pe(t){return t?(t.nodeName||"").toLowerCase():null}function me(t){if(null==t)return window;if("[object Window]"!==t.toString()){var e=t.ownerDocument;return e&&e.defaultView||window}return t}function ge(t){return t instanceof me(t).Element||t instanceof Element}function _e(t){return t instanceof me(t).HTMLElement||t instanceof HTMLElement}function be(t){return"undefined"!=typeof ShadowRoot&&(t instanceof me(t).ShadowRoot||t instanceof ShadowRoot)}const ve={name:"applyStyles",enabled:!0,phase:"write",fn:function(t){var e=t.state;Object.keys(e.elements).forEach((function(t){var i=e.styles[t]||{},n=e.attributes[t]||{},s=e.elements[t];_e(s)&&pe(s)&&(Object.assign(s.style,i),Object.keys(n).forEach((function(t){var e=n[t];!1===e?s.removeAttribute(t):s.setAttribute(t,!0===e?"":e)})))}))},effect:function(t){var e=t.state,i={popper:{position:e.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};return Object.assign(e.elements.popper.style,i.popper),e.styles=i,e.elements.arrow&&Object.assign(e.elements.arrow.style,i.arrow),function(){Object.keys(e.elements).forEach((function(t){var n=e.elements[t],s=e.attributes[t]||{},o=Object.keys(e.styles.hasOwnProperty(t)?e.styles[t]:i[t]).reduce((function(t,e){return t[e]="",t}),{});_e(n)&&pe(n)&&(Object.assign(n.style,o),Object.keys(s).forEach((function(t){n.removeAttribute(t)})))}))}},requires:["computeStyles"]};function ye(t){return t.split("-")[0]}var we=Math.max,Ae=Math.min,Ee=Math.round;function Te(){var t=navigator.userAgentData;return null!=t&&t.brands&&Array.isArray(t.brands)?t.brands.map((function(t){return t.brand+"/"+t.version})).join(" "):navigator.userAgent}function Ce(){return!/^((?!chrome|android).)*safari/i.test(Te())}function Oe(t,e,i){void 0===e&&(e=!1),void 0===i&&(i=!1);var n=t.getBoundingClientRect(),s=1,o=1;e&&_e(t)&&(s=t.offsetWidth>0&&Ee(n.width)/t.offsetWidth||1,o=t.offsetHeight>0&&Ee(n.height)/t.offsetHeight||1);var r=(ge(t)?me(t):window).visualViewport,a=!Ce()&&i,l=(n.left+(a&&r?r.offsetLeft:0))/s,c=(n.top+(a&&r?r.offsetTop:0))/o,h=n.width/s,d=n.height/o;return{width:h,height:d,top:c,right:l+h,bottom:c+d,left:l,x:l,y:c}}function xe(t){var e=Oe(t),i=t.offsetWidth,n=t.offsetHeight;return Math.abs(e.width-i)<=1&&(i=e.width),Math.abs(e.height-n)<=1&&(n=e.height),{x:t.offsetLeft,y:t.offsetTop,width:i,height:n}}function ke(t,e){var i=e.getRootNode&&e.getRootNode();if(t.contains(e))return!0;if(i&&be(i)){var n=e;do{if(n&&t.isSameNode(n))return!0;n=n.parentNode||n.host}while(n)}return!1}function Le(t){return me(t).getComputedStyle(t)}function Se(t){return["table","td","th"].indexOf(pe(t))>=0}function De(t){return((ge(t)?t.ownerDocument:t.document)||window.document).documentElement}function $e(t){return"html"===pe(t)?t:t.assignedSlot||t.parentNode||(be(t)?t.host:null)||De(t)}function Ie(t){return _e(t)&&"fixed"!==Le(t).position?t.offsetParent:null}function Ne(t){for(var e=me(t),i=Ie(t);i&&Se(i)&&"static"===Le(i).position;)i=Ie(i);return i&&("html"===pe(i)||"body"===pe(i)&&"static"===Le(i).position)?e:i||function(t){var e=/firefox/i.test(Te());if(/Trident/i.test(Te())&&_e(t)&&"fixed"===Le(t).position)return null;var i=$e(t);for(be(i)&&(i=i.host);_e(i)&&["html","body"].indexOf(pe(i))<0;){var n=Le(i);if("none"!==n.transform||"none"!==n.perspective||"paint"===n.contain||-1!==["transform","perspective"].indexOf(n.willChange)||e&&"filter"===n.willChange||e&&n.filter&&"none"!==n.filter)return i;i=i.parentNode}return null}(t)||e}function Pe(t){return["top","bottom"].indexOf(t)>=0?"x":"y"}function je(t,e,i){return we(t,Ae(e,i))}function Me(t){return Object.assign({},{top:0,right:0,bottom:0,left:0},t)}function Fe(t,e){return e.reduce((function(e,i){return e[i]=t,e}),{})}const He={name:"arrow",enabled:!0,phase:"main",fn:function(t){var e,i=t.state,n=t.name,s=t.options,o=i.elements.arrow,r=i.modifiersData.popperOffsets,a=ye(i.placement),l=Pe(a),c=[Qt,Kt].indexOf(a)>=0?"height":"width";if(o&&r){var h=function(t,e){return Me("number"!=typeof(t="function"==typeof t?t(Object.assign({},e.rects,{placement:e.placement})):t)?t:Fe(t,Yt))}(s.padding,i),d=xe(o),u="y"===l?qt:Qt,f="y"===l?Vt:Kt,p=i.rects.reference[c]+i.rects.reference[l]-r[l]-i.rects.popper[c],m=r[l]-i.rects.reference[l],g=Ne(o),_=g?"y"===l?g.clientHeight||0:g.clientWidth||0:0,b=p/2-m/2,v=h[u],y=_-d[c]-h[f],w=_/2-d[c]/2+b,A=je(v,w,y),E=l;i.modifiersData[n]=((e={})[E]=A,e.centerOffset=A-w,e)}},effect:function(t){var e=t.state,i=t.options.element,n=void 0===i?"[data-popper-arrow]":i;null!=n&&("string"!=typeof n||(n=e.elements.popper.querySelector(n)))&&ke(e.elements.popper,n)&&(e.elements.arrow=n)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function We(t){return t.split("-")[1]}var Be={top:"auto",right:"auto",bottom:"auto",left:"auto"};function ze(t){var e,i=t.popper,n=t.popperRect,s=t.placement,o=t.variation,r=t.offsets,a=t.position,l=t.gpuAcceleration,c=t.adaptive,h=t.roundOffsets,d=t.isFixed,u=r.x,f=void 0===u?0:u,p=r.y,m=void 0===p?0:p,g="function"==typeof h?h({x:f,y:m}):{x:f,y:m};f=g.x,m=g.y;var _=r.hasOwnProperty("x"),b=r.hasOwnProperty("y"),v=Qt,y=qt,w=window;if(c){var A=Ne(i),E="clientHeight",T="clientWidth";A===me(i)&&"static"!==Le(A=De(i)).position&&"absolute"===a&&(E="scrollHeight",T="scrollWidth"),(s===qt||(s===Qt||s===Kt)&&o===Gt)&&(y=Vt,m-=(d&&A===w&&w.visualViewport?w.visualViewport.height:A[E])-n.height,m*=l?1:-1),s!==Qt&&(s!==qt&&s!==Vt||o!==Gt)||(v=Kt,f-=(d&&A===w&&w.visualViewport?w.visualViewport.width:A[T])-n.width,f*=l?1:-1)}var C,O=Object.assign({position:a},c&&Be),x=!0===h?function(t,e){var i=t.x,n=t.y,s=e.devicePixelRatio||1;return{x:Ee(i*s)/s||0,y:Ee(n*s)/s||0}}({x:f,y:m},me(i)):{x:f,y:m};return f=x.x,m=x.y,l?Object.assign({},O,((C={})[y]=b?"0":"",C[v]=_?"0":"",C.transform=(w.devicePixelRatio||1)<=1?"translate("+f+"px, "+m+"px)":"translate3d("+f+"px, "+m+"px, 0)",C)):Object.assign({},O,((e={})[y]=b?m+"px":"",e[v]=_?f+"px":"",e.transform="",e))}const Re={name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:function(t){var e=t.state,i=t.options,n=i.gpuAcceleration,s=void 0===n||n,o=i.adaptive,r=void 0===o||o,a=i.roundOffsets,l=void 0===a||a,c={placement:ye(e.placement),variation:We(e.placement),popper:e.elements.popper,popperRect:e.rects.popper,gpuAcceleration:s,isFixed:"fixed"===e.options.strategy};null!=e.modifiersData.popperOffsets&&(e.styles.popper=Object.assign({},e.styles.popper,ze(Object.assign({},c,{offsets:e.modifiersData.popperOffsets,position:e.options.strategy,adaptive:r,roundOffsets:l})))),null!=e.modifiersData.arrow&&(e.styles.arrow=Object.assign({},e.styles.arrow,ze(Object.assign({},c,{offsets:e.modifiersData.arrow,position:"absolute",adaptive:!1,roundOffsets:l})))),e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-placement":e.placement})},data:{}};var qe={passive:!0};const Ve={name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:function(t){var e=t.state,i=t.instance,n=t.options,s=n.scroll,o=void 0===s||s,r=n.resize,a=void 0===r||r,l=me(e.elements.popper),c=[].concat(e.scrollParents.reference,e.scrollParents.popper);return o&&c.forEach((function(t){t.addEventListener("scroll",i.update,qe)})),a&&l.addEventListener("resize",i.update,qe),function(){o&&c.forEach((function(t){t.removeEventListener("scroll",i.update,qe)})),a&&l.removeEventListener("resize",i.update,qe)}},data:{}};var Ke={left:"right",right:"left",bottom:"top",top:"bottom"};function Qe(t){return t.replace(/left|right|bottom|top/g,(function(t){return Ke[t]}))}var Xe={start:"end",end:"start"};function Ye(t){return t.replace(/start|end/g,(function(t){return Xe[t]}))}function Ue(t){var e=me(t);return{scrollLeft:e.pageXOffset,scrollTop:e.pageYOffset}}function Ge(t){return Oe(De(t)).left+Ue(t).scrollLeft}function Je(t){var e=Le(t),i=e.overflow,n=e.overflowX,s=e.overflowY;return/auto|scroll|overlay|hidden/.test(i+s+n)}function Ze(t){return["html","body","#document"].indexOf(pe(t))>=0?t.ownerDocument.body:_e(t)&&Je(t)?t:Ze($e(t))}function ti(t,e){var i;void 0===e&&(e=[]);var n=Ze(t),s=n===(null==(i=t.ownerDocument)?void 0:i.body),o=me(n),r=s?[o].concat(o.visualViewport||[],Je(n)?n:[]):n,a=e.concat(r);return s?a:a.concat(ti($e(r)))}function ei(t){return Object.assign({},t,{left:t.x,top:t.y,right:t.x+t.width,bottom:t.y+t.height})}function ii(t,e,i){return e===Zt?ei(function(t,e){var i=me(t),n=De(t),s=i.visualViewport,o=n.clientWidth,r=n.clientHeight,a=0,l=0;if(s){o=s.width,r=s.height;var c=Ce();(c||!c&&"fixed"===e)&&(a=s.offsetLeft,l=s.offsetTop)}return{width:o,height:r,x:a+Ge(t),y:l}}(t,i)):ge(e)?function(t,e){var i=Oe(t,!1,"fixed"===e);return i.top=i.top+t.clientTop,i.left=i.left+t.clientLeft,i.bottom=i.top+t.clientHeight,i.right=i.left+t.clientWidth,i.width=t.clientWidth,i.height=t.clientHeight,i.x=i.left,i.y=i.top,i}(e,i):ei(function(t){var e,i=De(t),n=Ue(t),s=null==(e=t.ownerDocument)?void 0:e.body,o=we(i.scrollWidth,i.clientWidth,s?s.scrollWidth:0,s?s.clientWidth:0),r=we(i.scrollHeight,i.clientHeight,s?s.scrollHeight:0,s?s.clientHeight:0),a=-n.scrollLeft+Ge(t),l=-n.scrollTop;return"rtl"===Le(s||i).direction&&(a+=we(i.clientWidth,s?s.clientWidth:0)-o),{width:o,height:r,x:a,y:l}}(De(t)))}function ni(t){var e,i=t.reference,n=t.element,s=t.placement,o=s?ye(s):null,r=s?We(s):null,a=i.x+i.width/2-n.width/2,l=i.y+i.height/2-n.height/2;switch(o){case qt:e={x:a,y:i.y-n.height};break;case Vt:e={x:a,y:i.y+i.height};break;case Kt:e={x:i.x+i.width,y:l};break;case Qt:e={x:i.x-n.width,y:l};break;default:e={x:i.x,y:i.y}}var c=o?Pe(o):null;if(null!=c){var h="y"===c?"height":"width";switch(r){case Ut:e[c]=e[c]-(i[h]/2-n[h]/2);break;case Gt:e[c]=e[c]+(i[h]/2-n[h]/2)}}return e}function si(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=void 0===n?t.placement:n,o=i.strategy,r=void 0===o?t.strategy:o,a=i.boundary,l=void 0===a?Jt:a,c=i.rootBoundary,h=void 0===c?Zt:c,d=i.elementContext,u=void 0===d?te:d,f=i.altBoundary,p=void 0!==f&&f,m=i.padding,g=void 0===m?0:m,_=Me("number"!=typeof g?g:Fe(g,Yt)),b=u===te?ee:te,v=t.rects.popper,y=t.elements[p?b:u],w=function(t,e,i,n){var s="clippingParents"===e?function(t){var e=ti($e(t)),i=["absolute","fixed"].indexOf(Le(t).position)>=0&&_e(t)?Ne(t):t;return ge(i)?e.filter((function(t){return ge(t)&&ke(t,i)&&"body"!==pe(t)})):[]}(t):[].concat(e),o=[].concat(s,[i]),r=o[0],a=o.reduce((function(e,i){var s=ii(t,i,n);return e.top=we(s.top,e.top),e.right=Ae(s.right,e.right),e.bottom=Ae(s.bottom,e.bottom),e.left=we(s.left,e.left),e}),ii(t,r,n));return a.width=a.right-a.left,a.height=a.bottom-a.top,a.x=a.left,a.y=a.top,a}(ge(y)?y:y.contextElement||De(t.elements.popper),l,h,r),A=Oe(t.elements.reference),E=ni({reference:A,element:v,placement:s}),T=ei(Object.assign({},v,E)),C=u===te?T:A,O={top:w.top-C.top+_.top,bottom:C.bottom-w.bottom+_.bottom,left:w.left-C.left+_.left,right:C.right-w.right+_.right},x=t.modifiersData.offset;if(u===te&&x){var k=x[s];Object.keys(O).forEach((function(t){var e=[Kt,Vt].indexOf(t)>=0?1:-1,i=[qt,Vt].indexOf(t)>=0?"y":"x";O[t]+=k[i]*e}))}return O}function oi(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=i.boundary,o=i.rootBoundary,r=i.padding,a=i.flipVariations,l=i.allowedAutoPlacements,c=void 0===l?ne:l,h=We(n),d=h?a?ie:ie.filter((function(t){return We(t)===h})):Yt,u=d.filter((function(t){return c.indexOf(t)>=0}));0===u.length&&(u=d);var f=u.reduce((function(e,i){return e[i]=si(t,{placement:i,boundary:s,rootBoundary:o,padding:r})[ye(i)],e}),{});return Object.keys(f).sort((function(t,e){return f[t]-f[e]}))}const ri={name:"flip",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name;if(!e.modifiersData[n]._skip){for(var s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0===r||r,l=i.fallbackPlacements,c=i.padding,h=i.boundary,d=i.rootBoundary,u=i.altBoundary,f=i.flipVariations,p=void 0===f||f,m=i.allowedAutoPlacements,g=e.options.placement,_=ye(g),b=l||(_!==g&&p?function(t){if(ye(t)===Xt)return[];var e=Qe(t);return[Ye(t),e,Ye(e)]}(g):[Qe(g)]),v=[g].concat(b).reduce((function(t,i){return t.concat(ye(i)===Xt?oi(e,{placement:i,boundary:h,rootBoundary:d,padding:c,flipVariations:p,allowedAutoPlacements:m}):i)}),[]),y=e.rects.reference,w=e.rects.popper,A=new Map,E=!0,T=v[0],C=0;C=0,S=L?"width":"height",D=si(e,{placement:O,boundary:h,rootBoundary:d,altBoundary:u,padding:c}),$=L?k?Kt:Qt:k?Vt:qt;y[S]>w[S]&&($=Qe($));var I=Qe($),N=[];if(o&&N.push(D[x]<=0),a&&N.push(D[$]<=0,D[I]<=0),N.every((function(t){return t}))){T=O,E=!1;break}A.set(O,N)}if(E)for(var P=function(t){var e=v.find((function(e){var i=A.get(e);if(i)return i.slice(0,t).every((function(t){return t}))}));if(e)return T=e,"break"},j=p?3:1;j>0&&"break"!==P(j);j--);e.placement!==T&&(e.modifiersData[n]._skip=!0,e.placement=T,e.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function ai(t,e,i){return void 0===i&&(i={x:0,y:0}),{top:t.top-e.height-i.y,right:t.right-e.width+i.x,bottom:t.bottom-e.height+i.y,left:t.left-e.width-i.x}}function li(t){return[qt,Kt,Vt,Qt].some((function(e){return t[e]>=0}))}const ci={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(t){var e=t.state,i=t.name,n=e.rects.reference,s=e.rects.popper,o=e.modifiersData.preventOverflow,r=si(e,{elementContext:"reference"}),a=si(e,{altBoundary:!0}),l=ai(r,n),c=ai(a,s,o),h=li(l),d=li(c);e.modifiersData[i]={referenceClippingOffsets:l,popperEscapeOffsets:c,isReferenceHidden:h,hasPopperEscaped:d},e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-reference-hidden":h,"data-popper-escaped":d})}},hi={name:"offset",enabled:!0,phase:"main",requires:["popperOffsets"],fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.offset,o=void 0===s?[0,0]:s,r=ne.reduce((function(t,i){return t[i]=function(t,e,i){var n=ye(t),s=[Qt,qt].indexOf(n)>=0?-1:1,o="function"==typeof i?i(Object.assign({},e,{placement:t})):i,r=o[0],a=o[1];return r=r||0,a=(a||0)*s,[Qt,Kt].indexOf(n)>=0?{x:a,y:r}:{x:r,y:a}}(i,e.rects,o),t}),{}),a=r[e.placement],l=a.x,c=a.y;null!=e.modifiersData.popperOffsets&&(e.modifiersData.popperOffsets.x+=l,e.modifiersData.popperOffsets.y+=c),e.modifiersData[n]=r}},di={name:"popperOffsets",enabled:!0,phase:"read",fn:function(t){var e=t.state,i=t.name;e.modifiersData[i]=ni({reference:e.rects.reference,element:e.rects.popper,placement:e.placement})},data:{}},ui={name:"preventOverflow",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0!==r&&r,l=i.boundary,c=i.rootBoundary,h=i.altBoundary,d=i.padding,u=i.tether,f=void 0===u||u,p=i.tetherOffset,m=void 0===p?0:p,g=si(e,{boundary:l,rootBoundary:c,padding:d,altBoundary:h}),_=ye(e.placement),b=We(e.placement),v=!b,y=Pe(_),w="x"===y?"y":"x",A=e.modifiersData.popperOffsets,E=e.rects.reference,T=e.rects.popper,C="function"==typeof m?m(Object.assign({},e.rects,{placement:e.placement})):m,O="number"==typeof C?{mainAxis:C,altAxis:C}:Object.assign({mainAxis:0,altAxis:0},C),x=e.modifiersData.offset?e.modifiersData.offset[e.placement]:null,k={x:0,y:0};if(A){if(o){var L,S="y"===y?qt:Qt,D="y"===y?Vt:Kt,$="y"===y?"height":"width",I=A[y],N=I+g[S],P=I-g[D],j=f?-T[$]/2:0,M=b===Ut?E[$]:T[$],F=b===Ut?-T[$]:-E[$],H=e.elements.arrow,W=f&&H?xe(H):{width:0,height:0},B=e.modifiersData["arrow#persistent"]?e.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},z=B[S],R=B[D],q=je(0,E[$],W[$]),V=v?E[$]/2-j-q-z-O.mainAxis:M-q-z-O.mainAxis,K=v?-E[$]/2+j+q+R+O.mainAxis:F+q+R+O.mainAxis,Q=e.elements.arrow&&Ne(e.elements.arrow),X=Q?"y"===y?Q.clientTop||0:Q.clientLeft||0:0,Y=null!=(L=null==x?void 0:x[y])?L:0,U=I+K-Y,G=je(f?Ae(N,I+V-Y-X):N,I,f?we(P,U):P);A[y]=G,k[y]=G-I}if(a){var J,Z="x"===y?qt:Qt,tt="x"===y?Vt:Kt,et=A[w],it="y"===w?"height":"width",nt=et+g[Z],st=et-g[tt],ot=-1!==[qt,Qt].indexOf(_),rt=null!=(J=null==x?void 0:x[w])?J:0,at=ot?nt:et-E[it]-T[it]-rt+O.altAxis,lt=ot?et+E[it]+T[it]-rt-O.altAxis:st,ct=f&&ot?function(t,e,i){var n=je(t,e,i);return n>i?i:n}(at,et,lt):je(f?at:nt,et,f?lt:st);A[w]=ct,k[w]=ct-et}e.modifiersData[n]=k}},requiresIfExists:["offset"]};function fi(t,e,i){void 0===i&&(i=!1);var n,s,o=_e(e),r=_e(e)&&function(t){var e=t.getBoundingClientRect(),i=Ee(e.width)/t.offsetWidth||1,n=Ee(e.height)/t.offsetHeight||1;return 1!==i||1!==n}(e),a=De(e),l=Oe(t,r,i),c={scrollLeft:0,scrollTop:0},h={x:0,y:0};return(o||!o&&!i)&&(("body"!==pe(e)||Je(a))&&(c=(n=e)!==me(n)&&_e(n)?{scrollLeft:(s=n).scrollLeft,scrollTop:s.scrollTop}:Ue(n)),_e(e)?((h=Oe(e,!0)).x+=e.clientLeft,h.y+=e.clientTop):a&&(h.x=Ge(a))),{x:l.left+c.scrollLeft-h.x,y:l.top+c.scrollTop-h.y,width:l.width,height:l.height}}function pi(t){var e=new Map,i=new Set,n=[];function s(t){i.add(t.name),[].concat(t.requires||[],t.requiresIfExists||[]).forEach((function(t){if(!i.has(t)){var n=e.get(t);n&&s(n)}})),n.push(t)}return t.forEach((function(t){e.set(t.name,t)})),t.forEach((function(t){i.has(t.name)||s(t)})),n}var mi={placement:"bottom",modifiers:[],strategy:"absolute"};function gi(){for(var t=arguments.length,e=new Array(t),i=0;iNumber.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_getPopperConfig(){const t={placement:this._getPlacement(),modifiers:[{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"offset",options:{offset:this._getOffset()}}]};return(this._inNavbar||"static"===this._config.display)&&(F.setDataAttribute(this._menu,"popper","static"),t.modifiers=[{name:"applyStyles",enabled:!1}]),{...t,...g(this._config.popperConfig,[void 0,t])}}_selectMenuItem({key:t,target:e}){const i=z.find(".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)",this._menu).filter((t=>a(t)));i.length&&b(i,e,t===Oi,!i.includes(e)).focus()}static jQueryInterface(t){return this.each((function(){const e=Ki.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}static clearMenus(t){if(2===t.button||"keyup"===t.type&&"Tab"!==t.key)return;const e=z.find(ji);for(const i of e){const e=Ki.getInstance(i);if(!e||!1===e._config.autoClose)continue;const n=t.composedPath(),s=n.includes(e._menu);if(n.includes(e._element)||"inside"===e._config.autoClose&&!s||"outside"===e._config.autoClose&&s)continue;if(e._menu.contains(t.target)&&("keyup"===t.type&&"Tab"===t.key||/input|select|option|textarea|form/i.test(t.target.tagName)))continue;const o={relatedTarget:e._element};"click"===t.type&&(o.clickEvent=t),e._completeHide(o)}}static dataApiKeydownHandler(t){const e=/input|textarea/i.test(t.target.tagName),i="Escape"===t.key,n=[Ci,Oi].includes(t.key);if(!n&&!i)return;if(e&&!i)return;t.preventDefault();const s=this.matches(Pi)?this:z.prev(this,Pi)[0]||z.next(this,Pi)[0]||z.findOne(Pi,t.delegateTarget.parentNode),o=Ki.getOrCreateInstance(s);if(n)return t.stopPropagation(),o.show(),void o._selectMenuItem(t);o._isShown()&&(t.stopPropagation(),o.hide(),s.focus())}}N.on(document,$i,Pi,Ki.dataApiKeydownHandler),N.on(document,$i,Mi,Ki.dataApiKeydownHandler),N.on(document,Di,Ki.clearMenus),N.on(document,Ii,Ki.clearMenus),N.on(document,Di,Pi,(function(t){t.preventDefault(),Ki.getOrCreateInstance(this).toggle()})),m(Ki);const Qi="backdrop",Xi="show",Yi=`mousedown.bs.${Qi}`,Ui={className:"modal-backdrop",clickCallback:null,isAnimated:!1,isVisible:!0,rootElement:"body"},Gi={className:"string",clickCallback:"(function|null)",isAnimated:"boolean",isVisible:"boolean",rootElement:"(element|string)"};class Ji extends H{constructor(t){super(),this._config=this._getConfig(t),this._isAppended=!1,this._element=null}static get Default(){return Ui}static get DefaultType(){return Gi}static get NAME(){return Qi}show(t){if(!this._config.isVisible)return void g(t);this._append();const e=this._getElement();this._config.isAnimated&&d(e),e.classList.add(Xi),this._emulateAnimation((()=>{g(t)}))}hide(t){this._config.isVisible?(this._getElement().classList.remove(Xi),this._emulateAnimation((()=>{this.dispose(),g(t)}))):g(t)}dispose(){this._isAppended&&(N.off(this._element,Yi),this._element.remove(),this._isAppended=!1)}_getElement(){if(!this._element){const t=document.createElement("div");t.className=this._config.className,this._config.isAnimated&&t.classList.add("fade"),this._element=t}return this._element}_configAfterMerge(t){return t.rootElement=r(t.rootElement),t}_append(){if(this._isAppended)return;const t=this._getElement();this._config.rootElement.append(t),N.on(t,Yi,(()=>{g(this._config.clickCallback)})),this._isAppended=!0}_emulateAnimation(t){_(t,this._getElement(),this._config.isAnimated)}}const Zi=".bs.focustrap",tn=`focusin${Zi}`,en=`keydown.tab${Zi}`,nn="backward",sn={autofocus:!0,trapElement:null},on={autofocus:"boolean",trapElement:"element"};class rn extends H{constructor(t){super(),this._config=this._getConfig(t),this._isActive=!1,this._lastTabNavDirection=null}static get Default(){return sn}static get DefaultType(){return on}static get NAME(){return"focustrap"}activate(){this._isActive||(this._config.autofocus&&this._config.trapElement.focus(),N.off(document,Zi),N.on(document,tn,(t=>this._handleFocusin(t))),N.on(document,en,(t=>this._handleKeydown(t))),this._isActive=!0)}deactivate(){this._isActive&&(this._isActive=!1,N.off(document,Zi))}_handleFocusin(t){const{trapElement:e}=this._config;if(t.target===document||t.target===e||e.contains(t.target))return;const i=z.focusableChildren(e);0===i.length?e.focus():this._lastTabNavDirection===nn?i[i.length-1].focus():i[0].focus()}_handleKeydown(t){"Tab"===t.key&&(this._lastTabNavDirection=t.shiftKey?nn:"forward")}}const an=".fixed-top, .fixed-bottom, .is-fixed, .sticky-top",ln=".sticky-top",cn="padding-right",hn="margin-right";class dn{constructor(){this._element=document.body}getWidth(){const t=document.documentElement.clientWidth;return Math.abs(window.innerWidth-t)}hide(){const t=this.getWidth();this._disableOverFlow(),this._setElementAttributes(this._element,cn,(e=>e+t)),this._setElementAttributes(an,cn,(e=>e+t)),this._setElementAttributes(ln,hn,(e=>e-t))}reset(){this._resetElementAttributes(this._element,"overflow"),this._resetElementAttributes(this._element,cn),this._resetElementAttributes(an,cn),this._resetElementAttributes(ln,hn)}isOverflowing(){return this.getWidth()>0}_disableOverFlow(){this._saveInitialAttribute(this._element,"overflow"),this._element.style.overflow="hidden"}_setElementAttributes(t,e,i){const n=this.getWidth();this._applyManipulationCallback(t,(t=>{if(t!==this._element&&window.innerWidth>t.clientWidth+n)return;this._saveInitialAttribute(t,e);const s=window.getComputedStyle(t).getPropertyValue(e);t.style.setProperty(e,`${i(Number.parseFloat(s))}px`)}))}_saveInitialAttribute(t,e){const i=t.style.getPropertyValue(e);i&&F.setDataAttribute(t,e,i)}_resetElementAttributes(t,e){this._applyManipulationCallback(t,(t=>{const i=F.getDataAttribute(t,e);null!==i?(F.removeDataAttribute(t,e),t.style.setProperty(e,i)):t.style.removeProperty(e)}))}_applyManipulationCallback(t,e){if(o(t))e(t);else for(const i of z.find(t,this._element))e(i)}}const un=".bs.modal",fn=`hide${un}`,pn=`hidePrevented${un}`,mn=`hidden${un}`,gn=`show${un}`,_n=`shown${un}`,bn=`resize${un}`,vn=`click.dismiss${un}`,yn=`mousedown.dismiss${un}`,wn=`keydown.dismiss${un}`,An=`click${un}.data-api`,En="modal-open",Tn="show",Cn="modal-static",On={backdrop:!0,focus:!0,keyboard:!0},xn={backdrop:"(boolean|string)",focus:"boolean",keyboard:"boolean"};class kn extends W{constructor(t,e){super(t,e),this._dialog=z.findOne(".modal-dialog",this._element),this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._isShown=!1,this._isTransitioning=!1,this._scrollBar=new dn,this._addEventListeners()}static get Default(){return On}static get DefaultType(){return xn}static get NAME(){return"modal"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||this._isTransitioning||N.trigger(this._element,gn,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._isTransitioning=!0,this._scrollBar.hide(),document.body.classList.add(En),this._adjustDialog(),this._backdrop.show((()=>this._showElement(t))))}hide(){this._isShown&&!this._isTransitioning&&(N.trigger(this._element,fn).defaultPrevented||(this._isShown=!1,this._isTransitioning=!0,this._focustrap.deactivate(),this._element.classList.remove(Tn),this._queueCallback((()=>this._hideModal()),this._element,this._isAnimated())))}dispose(){N.off(window,un),N.off(this._dialog,un),this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}handleUpdate(){this._adjustDialog()}_initializeBackDrop(){return new Ji({isVisible:Boolean(this._config.backdrop),isAnimated:this._isAnimated()})}_initializeFocusTrap(){return new rn({trapElement:this._element})}_showElement(t){document.body.contains(this._element)||document.body.append(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.scrollTop=0;const e=z.findOne(".modal-body",this._dialog);e&&(e.scrollTop=0),d(this._element),this._element.classList.add(Tn),this._queueCallback((()=>{this._config.focus&&this._focustrap.activate(),this._isTransitioning=!1,N.trigger(this._element,_n,{relatedTarget:t})}),this._dialog,this._isAnimated())}_addEventListeners(){N.on(this._element,wn,(t=>{"Escape"===t.key&&(this._config.keyboard?this.hide():this._triggerBackdropTransition())})),N.on(window,bn,(()=>{this._isShown&&!this._isTransitioning&&this._adjustDialog()})),N.on(this._element,yn,(t=>{N.one(this._element,vn,(e=>{this._element===t.target&&this._element===e.target&&("static"!==this._config.backdrop?this._config.backdrop&&this.hide():this._triggerBackdropTransition())}))}))}_hideModal(){this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._backdrop.hide((()=>{document.body.classList.remove(En),this._resetAdjustments(),this._scrollBar.reset(),N.trigger(this._element,mn)}))}_isAnimated(){return this._element.classList.contains("fade")}_triggerBackdropTransition(){if(N.trigger(this._element,pn).defaultPrevented)return;const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._element.style.overflowY;"hidden"===e||this._element.classList.contains(Cn)||(t||(this._element.style.overflowY="hidden"),this._element.classList.add(Cn),this._queueCallback((()=>{this._element.classList.remove(Cn),this._queueCallback((()=>{this._element.style.overflowY=e}),this._dialog)}),this._dialog),this._element.focus())}_adjustDialog(){const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._scrollBar.getWidth(),i=e>0;if(i&&!t){const t=p()?"paddingLeft":"paddingRight";this._element.style[t]=`${e}px`}if(!i&&t){const t=p()?"paddingRight":"paddingLeft";this._element.style[t]=`${e}px`}}_resetAdjustments(){this._element.style.paddingLeft="",this._element.style.paddingRight=""}static jQueryInterface(t,e){return this.each((function(){const i=kn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t](e)}}))}}N.on(document,An,'[data-bs-toggle="modal"]',(function(t){const e=z.getElementFromSelector(this);["A","AREA"].includes(this.tagName)&&t.preventDefault(),N.one(e,gn,(t=>{t.defaultPrevented||N.one(e,mn,(()=>{a(this)&&this.focus()}))}));const i=z.findOne(".modal.show");i&&kn.getInstance(i).hide(),kn.getOrCreateInstance(e).toggle(this)})),R(kn),m(kn);const Ln=".bs.offcanvas",Sn=".data-api",Dn=`load${Ln}${Sn}`,$n="show",In="showing",Nn="hiding",Pn=".offcanvas.show",jn=`show${Ln}`,Mn=`shown${Ln}`,Fn=`hide${Ln}`,Hn=`hidePrevented${Ln}`,Wn=`hidden${Ln}`,Bn=`resize${Ln}`,zn=`click${Ln}${Sn}`,Rn=`keydown.dismiss${Ln}`,qn={backdrop:!0,keyboard:!0,scroll:!1},Vn={backdrop:"(boolean|string)",keyboard:"boolean",scroll:"boolean"};class Kn extends W{constructor(t,e){super(t,e),this._isShown=!1,this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._addEventListeners()}static get Default(){return qn}static get DefaultType(){return Vn}static get NAME(){return"offcanvas"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||N.trigger(this._element,jn,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._backdrop.show(),this._config.scroll||(new dn).hide(),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.classList.add(In),this._queueCallback((()=>{this._config.scroll&&!this._config.backdrop||this._focustrap.activate(),this._element.classList.add($n),this._element.classList.remove(In),N.trigger(this._element,Mn,{relatedTarget:t})}),this._element,!0))}hide(){this._isShown&&(N.trigger(this._element,Fn).defaultPrevented||(this._focustrap.deactivate(),this._element.blur(),this._isShown=!1,this._element.classList.add(Nn),this._backdrop.hide(),this._queueCallback((()=>{this._element.classList.remove($n,Nn),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._config.scroll||(new dn).reset(),N.trigger(this._element,Wn)}),this._element,!0)))}dispose(){this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}_initializeBackDrop(){const t=Boolean(this._config.backdrop);return new Ji({className:"offcanvas-backdrop",isVisible:t,isAnimated:!0,rootElement:this._element.parentNode,clickCallback:t?()=>{"static"!==this._config.backdrop?this.hide():N.trigger(this._element,Hn)}:null})}_initializeFocusTrap(){return new rn({trapElement:this._element})}_addEventListeners(){N.on(this._element,Rn,(t=>{"Escape"===t.key&&(this._config.keyboard?this.hide():N.trigger(this._element,Hn))}))}static jQueryInterface(t){return this.each((function(){const e=Kn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}N.on(document,zn,'[data-bs-toggle="offcanvas"]',(function(t){const e=z.getElementFromSelector(this);if(["A","AREA"].includes(this.tagName)&&t.preventDefault(),l(this))return;N.one(e,Wn,(()=>{a(this)&&this.focus()}));const i=z.findOne(Pn);i&&i!==e&&Kn.getInstance(i).hide(),Kn.getOrCreateInstance(e).toggle(this)})),N.on(window,Dn,(()=>{for(const t of z.find(Pn))Kn.getOrCreateInstance(t).show()})),N.on(window,Bn,(()=>{for(const t of z.find("[aria-modal][class*=show][class*=offcanvas-]"))"fixed"!==getComputedStyle(t).position&&Kn.getOrCreateInstance(t).hide()})),R(Kn),m(Kn);const Qn={"*":["class","dir","id","lang","role",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],dd:[],div:[],dl:[],dt:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},Xn=new Set(["background","cite","href","itemtype","longdesc","poster","src","xlink:href"]),Yn=/^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i,Un=(t,e)=>{const i=t.nodeName.toLowerCase();return e.includes(i)?!Xn.has(i)||Boolean(Yn.test(t.nodeValue)):e.filter((t=>t instanceof RegExp)).some((t=>t.test(i)))},Gn={allowList:Qn,content:{},extraClass:"",html:!1,sanitize:!0,sanitizeFn:null,template:"
"},Jn={allowList:"object",content:"object",extraClass:"(string|function)",html:"boolean",sanitize:"boolean",sanitizeFn:"(null|function)",template:"string"},Zn={entry:"(string|element|function|null)",selector:"(string|element)"};class ts extends H{constructor(t){super(),this._config=this._getConfig(t)}static get Default(){return Gn}static get DefaultType(){return Jn}static get NAME(){return"TemplateFactory"}getContent(){return Object.values(this._config.content).map((t=>this._resolvePossibleFunction(t))).filter(Boolean)}hasContent(){return this.getContent().length>0}changeContent(t){return this._checkContent(t),this._config.content={...this._config.content,...t},this}toHtml(){const t=document.createElement("div");t.innerHTML=this._maybeSanitize(this._config.template);for(const[e,i]of Object.entries(this._config.content))this._setContent(t,i,e);const e=t.children[0],i=this._resolvePossibleFunction(this._config.extraClass);return i&&e.classList.add(...i.split(" ")),e}_typeCheckConfig(t){super._typeCheckConfig(t),this._checkContent(t.content)}_checkContent(t){for(const[e,i]of Object.entries(t))super._typeCheckConfig({selector:e,entry:i},Zn)}_setContent(t,e,i){const n=z.findOne(i,t);n&&((e=this._resolvePossibleFunction(e))?o(e)?this._putElementInTemplate(r(e),n):this._config.html?n.innerHTML=this._maybeSanitize(e):n.textContent=e:n.remove())}_maybeSanitize(t){return this._config.sanitize?function(t,e,i){if(!t.length)return t;if(i&&"function"==typeof i)return i(t);const n=(new window.DOMParser).parseFromString(t,"text/html"),s=[].concat(...n.body.querySelectorAll("*"));for(const t of s){const i=t.nodeName.toLowerCase();if(!Object.keys(e).includes(i)){t.remove();continue}const n=[].concat(...t.attributes),s=[].concat(e["*"]||[],e[i]||[]);for(const e of n)Un(e,s)||t.removeAttribute(e.nodeName)}return n.body.innerHTML}(t,this._config.allowList,this._config.sanitizeFn):t}_resolvePossibleFunction(t){return g(t,[void 0,this])}_putElementInTemplate(t,e){if(this._config.html)return e.innerHTML="",void e.append(t);e.textContent=t.textContent}}const es=new Set(["sanitize","allowList","sanitizeFn"]),is="fade",ns="show",ss=".tooltip-inner",os=".modal",rs="hide.bs.modal",as="hover",ls="focus",cs={AUTO:"auto",TOP:"top",RIGHT:p()?"left":"right",BOTTOM:"bottom",LEFT:p()?"right":"left"},hs={allowList:Qn,animation:!0,boundary:"clippingParents",container:!1,customClass:"",delay:0,fallbackPlacements:["top","right","bottom","left"],html:!1,offset:[0,6],placement:"top",popperConfig:null,sanitize:!0,sanitizeFn:null,selector:!1,template:'',title:"",trigger:"hover focus"},ds={allowList:"object",animation:"boolean",boundary:"(string|element)",container:"(string|element|boolean)",customClass:"(string|function)",delay:"(number|object)",fallbackPlacements:"array",html:"boolean",offset:"(array|string|function)",placement:"(string|function)",popperConfig:"(null|object|function)",sanitize:"boolean",sanitizeFn:"(null|function)",selector:"(string|boolean)",template:"string",title:"(string|element|function)",trigger:"string"};class us extends W{constructor(t,e){if(void 0===wi)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org/docs/v2/)");super(t,e),this._isEnabled=!0,this._timeout=0,this._isHovered=null,this._activeTrigger={},this._popper=null,this._templateFactory=null,this._newContent=null,this.tip=null,this._setListeners(),this._config.selector||this._fixTitle()}static get Default(){return hs}static get DefaultType(){return ds}static get NAME(){return"tooltip"}enable(){this._isEnabled=!0}disable(){this._isEnabled=!1}toggleEnabled(){this._isEnabled=!this._isEnabled}toggle(){this._isEnabled&&(this._isShown()?this._leave():this._enter())}dispose(){clearTimeout(this._timeout),N.off(this._element.closest(os),rs,this._hideModalHandler),this._element.getAttribute("data-bs-original-title")&&this._element.setAttribute("title",this._element.getAttribute("data-bs-original-title")),this._disposePopper(),super.dispose()}show(){if("none"===this._element.style.display)throw new Error("Please use show on visible elements");if(!this._isWithContent()||!this._isEnabled)return;const t=N.trigger(this._element,this.constructor.eventName("show")),e=(c(this._element)||this._element.ownerDocument.documentElement).contains(this._element);if(t.defaultPrevented||!e)return;this._disposePopper();const i=this._getTipElement();this._element.setAttribute("aria-describedby",i.getAttribute("id"));const{container:n}=this._config;if(this._element.ownerDocument.documentElement.contains(this.tip)||(n.append(i),N.trigger(this._element,this.constructor.eventName("inserted"))),this._popper=this._createPopper(i),i.classList.add(ns),"ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))N.on(t,"mouseover",h);this._queueCallback((()=>{N.trigger(this._element,this.constructor.eventName("shown")),!1===this._isHovered&&this._leave(),this._isHovered=!1}),this.tip,this._isAnimated())}hide(){if(this._isShown()&&!N.trigger(this._element,this.constructor.eventName("hide")).defaultPrevented){if(this._getTipElement().classList.remove(ns),"ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))N.off(t,"mouseover",h);this._activeTrigger.click=!1,this._activeTrigger[ls]=!1,this._activeTrigger[as]=!1,this._isHovered=null,this._queueCallback((()=>{this._isWithActiveTrigger()||(this._isHovered||this._disposePopper(),this._element.removeAttribute("aria-describedby"),N.trigger(this._element,this.constructor.eventName("hidden")))}),this.tip,this._isAnimated())}}update(){this._popper&&this._popper.update()}_isWithContent(){return Boolean(this._getTitle())}_getTipElement(){return this.tip||(this.tip=this._createTipElement(this._newContent||this._getContentForTemplate())),this.tip}_createTipElement(t){const e=this._getTemplateFactory(t).toHtml();if(!e)return null;e.classList.remove(is,ns),e.classList.add(`bs-${this.constructor.NAME}-auto`);const i=(t=>{do{t+=Math.floor(1e6*Math.random())}while(document.getElementById(t));return t})(this.constructor.NAME).toString();return e.setAttribute("id",i),this._isAnimated()&&e.classList.add(is),e}setContent(t){this._newContent=t,this._isShown()&&(this._disposePopper(),this.show())}_getTemplateFactory(t){return this._templateFactory?this._templateFactory.changeContent(t):this._templateFactory=new ts({...this._config,content:t,extraClass:this._resolvePossibleFunction(this._config.customClass)}),this._templateFactory}_getContentForTemplate(){return{[ss]:this._getTitle()}}_getTitle(){return this._resolvePossibleFunction(this._config.title)||this._element.getAttribute("data-bs-original-title")}_initializeOnDelegatedTarget(t){return this.constructor.getOrCreateInstance(t.delegateTarget,this._getDelegateConfig())}_isAnimated(){return this._config.animation||this.tip&&this.tip.classList.contains(is)}_isShown(){return this.tip&&this.tip.classList.contains(ns)}_createPopper(t){const e=g(this._config.placement,[this,t,this._element]),i=cs[e.toUpperCase()];return yi(this._element,t,this._getPopperConfig(i))}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map((t=>Number.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_resolvePossibleFunction(t){return g(t,[this._element,this._element])}_getPopperConfig(t){const e={placement:t,modifiers:[{name:"flip",options:{fallbackPlacements:this._config.fallbackPlacements}},{name:"offset",options:{offset:this._getOffset()}},{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"arrow",options:{element:`.${this.constructor.NAME}-arrow`}},{name:"preSetPlacement",enabled:!0,phase:"beforeMain",fn:t=>{this._getTipElement().setAttribute("data-popper-placement",t.state.placement)}}]};return{...e,...g(this._config.popperConfig,[void 0,e])}}_setListeners(){const t=this._config.trigger.split(" ");for(const e of t)if("click"===e)N.on(this._element,this.constructor.eventName("click"),this._config.selector,(t=>{this._initializeOnDelegatedTarget(t).toggle()}));else if("manual"!==e){const t=e===as?this.constructor.eventName("mouseenter"):this.constructor.eventName("focusin"),i=e===as?this.constructor.eventName("mouseleave"):this.constructor.eventName("focusout");N.on(this._element,t,this._config.selector,(t=>{const e=this._initializeOnDelegatedTarget(t);e._activeTrigger["focusin"===t.type?ls:as]=!0,e._enter()})),N.on(this._element,i,this._config.selector,(t=>{const e=this._initializeOnDelegatedTarget(t);e._activeTrigger["focusout"===t.type?ls:as]=e._element.contains(t.relatedTarget),e._leave()}))}this._hideModalHandler=()=>{this._element&&this.hide()},N.on(this._element.closest(os),rs,this._hideModalHandler)}_fixTitle(){const t=this._element.getAttribute("title");t&&(this._element.getAttribute("aria-label")||this._element.textContent.trim()||this._element.setAttribute("aria-label",t),this._element.setAttribute("data-bs-original-title",t),this._element.removeAttribute("title"))}_enter(){this._isShown()||this._isHovered?this._isHovered=!0:(this._isHovered=!0,this._setTimeout((()=>{this._isHovered&&this.show()}),this._config.delay.show))}_leave(){this._isWithActiveTrigger()||(this._isHovered=!1,this._setTimeout((()=>{this._isHovered||this.hide()}),this._config.delay.hide))}_setTimeout(t,e){clearTimeout(this._timeout),this._timeout=setTimeout(t,e)}_isWithActiveTrigger(){return Object.values(this._activeTrigger).includes(!0)}_getConfig(t){const e=F.getDataAttributes(this._element);for(const t of Object.keys(e))es.has(t)&&delete e[t];return t={...e,..."object"==typeof t&&t?t:{}},t=this._mergeConfigObj(t),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}_configAfterMerge(t){return t.container=!1===t.container?document.body:r(t.container),"number"==typeof t.delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),t}_getDelegateConfig(){const t={};for(const[e,i]of Object.entries(this._config))this.constructor.Default[e]!==i&&(t[e]=i);return t.selector=!1,t.trigger="manual",t}_disposePopper(){this._popper&&(this._popper.destroy(),this._popper=null),this.tip&&(this.tip.remove(),this.tip=null)}static jQueryInterface(t){return this.each((function(){const e=us.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}m(us);const fs=".popover-header",ps=".popover-body",ms={...us.Default,content:"",offset:[0,8],placement:"right",template:'',trigger:"click"},gs={...us.DefaultType,content:"(null|string|element|function)"};class _s extends us{static get Default(){return ms}static get DefaultType(){return gs}static get NAME(){return"popover"}_isWithContent(){return this._getTitle()||this._getContent()}_getContentForTemplate(){return{[fs]:this._getTitle(),[ps]:this._getContent()}}_getContent(){return this._resolvePossibleFunction(this._config.content)}static jQueryInterface(t){return this.each((function(){const e=_s.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}m(_s);const bs=".bs.scrollspy",vs=`activate${bs}`,ys=`click${bs}`,ws=`load${bs}.data-api`,As="active",Es="[href]",Ts=".nav-link",Cs=`${Ts}, .nav-item > ${Ts}, .list-group-item`,Os={offset:null,rootMargin:"0px 0px -25%",smoothScroll:!1,target:null,threshold:[.1,.5,1]},xs={offset:"(number|null)",rootMargin:"string",smoothScroll:"boolean",target:"element",threshold:"array"};class ks extends W{constructor(t,e){super(t,e),this._targetLinks=new Map,this._observableSections=new Map,this._rootElement="visible"===getComputedStyle(this._element).overflowY?null:this._element,this._activeTarget=null,this._observer=null,this._previousScrollData={visibleEntryTop:0,parentScrollTop:0},this.refresh()}static get Default(){return Os}static get DefaultType(){return xs}static get NAME(){return"scrollspy"}refresh(){this._initializeTargetsAndObservables(),this._maybeEnableSmoothScroll(),this._observer?this._observer.disconnect():this._observer=this._getNewObserver();for(const t of this._observableSections.values())this._observer.observe(t)}dispose(){this._observer.disconnect(),super.dispose()}_configAfterMerge(t){return t.target=r(t.target)||document.body,t.rootMargin=t.offset?`${t.offset}px 0px -30%`:t.rootMargin,"string"==typeof t.threshold&&(t.threshold=t.threshold.split(",").map((t=>Number.parseFloat(t)))),t}_maybeEnableSmoothScroll(){this._config.smoothScroll&&(N.off(this._config.target,ys),N.on(this._config.target,ys,Es,(t=>{const e=this._observableSections.get(t.target.hash);if(e){t.preventDefault();const i=this._rootElement||window,n=e.offsetTop-this._element.offsetTop;if(i.scrollTo)return void i.scrollTo({top:n,behavior:"smooth"});i.scrollTop=n}})))}_getNewObserver(){const t={root:this._rootElement,threshold:this._config.threshold,rootMargin:this._config.rootMargin};return new IntersectionObserver((t=>this._observerCallback(t)),t)}_observerCallback(t){const e=t=>this._targetLinks.get(`#${t.target.id}`),i=t=>{this._previousScrollData.visibleEntryTop=t.target.offsetTop,this._process(e(t))},n=(this._rootElement||document.documentElement).scrollTop,s=n>=this._previousScrollData.parentScrollTop;this._previousScrollData.parentScrollTop=n;for(const o of t){if(!o.isIntersecting){this._activeTarget=null,this._clearActiveClass(e(o));continue}const t=o.target.offsetTop>=this._previousScrollData.visibleEntryTop;if(s&&t){if(i(o),!n)return}else s||t||i(o)}}_initializeTargetsAndObservables(){this._targetLinks=new Map,this._observableSections=new Map;const t=z.find(Es,this._config.target);for(const e of t){if(!e.hash||l(e))continue;const t=z.findOne(decodeURI(e.hash),this._element);a(t)&&(this._targetLinks.set(decodeURI(e.hash),e),this._observableSections.set(e.hash,t))}}_process(t){this._activeTarget!==t&&(this._clearActiveClass(this._config.target),this._activeTarget=t,t.classList.add(As),this._activateParents(t),N.trigger(this._element,vs,{relatedTarget:t}))}_activateParents(t){if(t.classList.contains("dropdown-item"))z.findOne(".dropdown-toggle",t.closest(".dropdown")).classList.add(As);else for(const e of z.parents(t,".nav, .list-group"))for(const t of z.prev(e,Cs))t.classList.add(As)}_clearActiveClass(t){t.classList.remove(As);const e=z.find(`${Es}.${As}`,t);for(const t of e)t.classList.remove(As)}static jQueryInterface(t){return this.each((function(){const e=ks.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}))}}N.on(window,ws,(()=>{for(const t of z.find('[data-bs-spy="scroll"]'))ks.getOrCreateInstance(t)})),m(ks);const Ls=".bs.tab",Ss=`hide${Ls}`,Ds=`hidden${Ls}`,$s=`show${Ls}`,Is=`shown${Ls}`,Ns=`click${Ls}`,Ps=`keydown${Ls}`,js=`load${Ls}`,Ms="ArrowLeft",Fs="ArrowRight",Hs="ArrowUp",Ws="ArrowDown",Bs="Home",zs="End",Rs="active",qs="fade",Vs="show",Ks=".dropdown-toggle",Qs=`:not(${Ks})`,Xs='[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]',Ys=`.nav-link${Qs}, .list-group-item${Qs}, [role="tab"]${Qs}, ${Xs}`,Us=`.${Rs}[data-bs-toggle="tab"], .${Rs}[data-bs-toggle="pill"], .${Rs}[data-bs-toggle="list"]`;class Gs extends W{constructor(t){super(t),this._parent=this._element.closest('.list-group, .nav, [role="tablist"]'),this._parent&&(this._setInitialAttributes(this._parent,this._getChildren()),N.on(this._element,Ps,(t=>this._keydown(t))))}static get NAME(){return"tab"}show(){const t=this._element;if(this._elemIsActive(t))return;const e=this._getActiveElem(),i=e?N.trigger(e,Ss,{relatedTarget:t}):null;N.trigger(t,$s,{relatedTarget:e}).defaultPrevented||i&&i.defaultPrevented||(this._deactivate(e,t),this._activate(t,e))}_activate(t,e){t&&(t.classList.add(Rs),this._activate(z.getElementFromSelector(t)),this._queueCallback((()=>{"tab"===t.getAttribute("role")?(t.removeAttribute("tabindex"),t.setAttribute("aria-selected",!0),this._toggleDropDown(t,!0),N.trigger(t,Is,{relatedTarget:e})):t.classList.add(Vs)}),t,t.classList.contains(qs)))}_deactivate(t,e){t&&(t.classList.remove(Rs),t.blur(),this._deactivate(z.getElementFromSelector(t)),this._queueCallback((()=>{"tab"===t.getAttribute("role")?(t.setAttribute("aria-selected",!1),t.setAttribute("tabindex","-1"),this._toggleDropDown(t,!1),N.trigger(t,Ds,{relatedTarget:e})):t.classList.remove(Vs)}),t,t.classList.contains(qs)))}_keydown(t){if(![Ms,Fs,Hs,Ws,Bs,zs].includes(t.key))return;t.stopPropagation(),t.preventDefault();const e=this._getChildren().filter((t=>!l(t)));let i;if([Bs,zs].includes(t.key))i=e[t.key===Bs?0:e.length-1];else{const n=[Fs,Ws].includes(t.key);i=b(e,t.target,n,!0)}i&&(i.focus({preventScroll:!0}),Gs.getOrCreateInstance(i).show())}_getChildren(){return z.find(Ys,this._parent)}_getActiveElem(){return this._getChildren().find((t=>this._elemIsActive(t)))||null}_setInitialAttributes(t,e){this._setAttributeIfNotExists(t,"role","tablist");for(const t of e)this._setInitialAttributesOnChild(t)}_setInitialAttributesOnChild(t){t=this._getInnerElement(t);const e=this._elemIsActive(t),i=this._getOuterElement(t);t.setAttribute("aria-selected",e),i!==t&&this._setAttributeIfNotExists(i,"role","presentation"),e||t.setAttribute("tabindex","-1"),this._setAttributeIfNotExists(t,"role","tab"),this._setInitialAttributesOnTargetPanel(t)}_setInitialAttributesOnTargetPanel(t){const e=z.getElementFromSelector(t);e&&(this._setAttributeIfNotExists(e,"role","tabpanel"),t.id&&this._setAttributeIfNotExists(e,"aria-labelledby",`${t.id}`))}_toggleDropDown(t,e){const i=this._getOuterElement(t);if(!i.classList.contains("dropdown"))return;const n=(t,n)=>{const s=z.findOne(t,i);s&&s.classList.toggle(n,e)};n(Ks,Rs),n(".dropdown-menu",Vs),i.setAttribute("aria-expanded",e)}_setAttributeIfNotExists(t,e,i){t.hasAttribute(e)||t.setAttribute(e,i)}_elemIsActive(t){return t.classList.contains(Rs)}_getInnerElement(t){return t.matches(Ys)?t:z.findOne(Ys,t)}_getOuterElement(t){return t.closest(".nav-item, .list-group-item")||t}static jQueryInterface(t){return this.each((function(){const e=Gs.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}))}}N.on(document,Ns,Xs,(function(t){["A","AREA"].includes(this.tagName)&&t.preventDefault(),l(this)||Gs.getOrCreateInstance(this).show()})),N.on(window,js,(()=>{for(const t of z.find(Us))Gs.getOrCreateInstance(t)})),m(Gs);const Js=".bs.toast",Zs=`mouseover${Js}`,to=`mouseout${Js}`,eo=`focusin${Js}`,io=`focusout${Js}`,no=`hide${Js}`,so=`hidden${Js}`,oo=`show${Js}`,ro=`shown${Js}`,ao="hide",lo="show",co="showing",ho={animation:"boolean",autohide:"boolean",delay:"number"},uo={animation:!0,autohide:!0,delay:5e3};class fo extends W{constructor(t,e){super(t,e),this._timeout=null,this._hasMouseInteraction=!1,this._hasKeyboardInteraction=!1,this._setListeners()}static get Default(){return uo}static get DefaultType(){return ho}static get NAME(){return"toast"}show(){N.trigger(this._element,oo).defaultPrevented||(this._clearTimeout(),this._config.animation&&this._element.classList.add("fade"),this._element.classList.remove(ao),d(this._element),this._element.classList.add(lo,co),this._queueCallback((()=>{this._element.classList.remove(co),N.trigger(this._element,ro),this._maybeScheduleHide()}),this._element,this._config.animation))}hide(){this.isShown()&&(N.trigger(this._element,no).defaultPrevented||(this._element.classList.add(co),this._queueCallback((()=>{this._element.classList.add(ao),this._element.classList.remove(co,lo),N.trigger(this._element,so)}),this._element,this._config.animation)))}dispose(){this._clearTimeout(),this.isShown()&&this._element.classList.remove(lo),super.dispose()}isShown(){return this._element.classList.contains(lo)}_maybeScheduleHide(){this._config.autohide&&(this._hasMouseInteraction||this._hasKeyboardInteraction||(this._timeout=setTimeout((()=>{this.hide()}),this._config.delay)))}_onInteraction(t,e){switch(t.type){case"mouseover":case"mouseout":this._hasMouseInteraction=e;break;case"focusin":case"focusout":this._hasKeyboardInteraction=e}if(e)return void this._clearTimeout();const i=t.relatedTarget;this._element===i||this._element.contains(i)||this._maybeScheduleHide()}_setListeners(){N.on(this._element,Zs,(t=>this._onInteraction(t,!0))),N.on(this._element,to,(t=>this._onInteraction(t,!1))),N.on(this._element,eo,(t=>this._onInteraction(t,!0))),N.on(this._element,io,(t=>this._onInteraction(t,!1)))}_clearTimeout(){clearTimeout(this._timeout),this._timeout=null}static jQueryInterface(t){return this.each((function(){const e=fo.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}return R(fo),m(fo),{Alert:Q,Button:Y,Carousel:Lt,Collapse:Rt,Dropdown:Ki,Modal:kn,Offcanvas:Kn,Popover:_s,ScrollSpy:ks,Tab:Gs,Toast:fo,Tooltip:us}})); +//# sourceMappingURL=bootstrap.bundle.min.js.map \ No newline at end of file diff --git a/app/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/js/bootstrap.min.js b/app/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/js/bootstrap.min.js deleted file mode 100644 index 97206dcda..000000000 --- a/app/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/js/bootstrap.min.js +++ /dev/null @@ -1,7 +0,0 @@ -/*! - * Bootstrap v4.6.2 (https://getbootstrap.com/) - * Copyright 2011-2022 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - */ -!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("jquery"),require("popper.js")):"function"==typeof define&&define.amd?define(["exports","jquery","popper.js"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap={},t.jQuery,t.Popper)}(this,(function(t,e,n){"use strict";function i(t){return t&&"object"==typeof t&&"default"in t?t:{default:t}}var o=i(e),a=i(n);function s(t,e){for(var n=0;n=4)throw new Error("Bootstrap's JavaScript requires at least jQuery v1.9.1 but less than v4.0.0")}};d.jQueryDetection(),o.default.fn.emulateTransitionEnd=function(t){var e=this,n=!1;return o.default(this).one(d.TRANSITION_END,(function(){n=!0})),setTimeout((function(){n||d.triggerTransitionEnd(e)}),t),this},o.default.event.special[d.TRANSITION_END]={bindType:f,delegateType:f,handle:function(t){if(o.default(t.target).is(this))return t.handleObj.handler.apply(this,arguments)}};var c="bs.alert",h=o.default.fn.alert,g=function(){function t(t){this._element=t}var e=t.prototype;return e.close=function(t){var e=this._element;t&&(e=this._getRootElement(t)),this._triggerCloseEvent(e).isDefaultPrevented()||this._removeElement(e)},e.dispose=function(){o.default.removeData(this._element,c),this._element=null},e._getRootElement=function(t){var e=d.getSelectorFromElement(t),n=!1;return e&&(n=document.querySelector(e)),n||(n=o.default(t).closest(".alert")[0]),n},e._triggerCloseEvent=function(t){var e=o.default.Event("close.bs.alert");return o.default(t).trigger(e),e},e._removeElement=function(t){var e=this;if(o.default(t).removeClass("show"),o.default(t).hasClass("fade")){var n=d.getTransitionDurationFromElement(t);o.default(t).one(d.TRANSITION_END,(function(n){return e._destroyElement(t,n)})).emulateTransitionEnd(n)}else this._destroyElement(t)},e._destroyElement=function(t){o.default(t).detach().trigger("closed.bs.alert").remove()},t._jQueryInterface=function(e){return this.each((function(){var n=o.default(this),i=n.data(c);i||(i=new t(this),n.data(c,i)),"close"===e&&i[e](this)}))},t._handleDismiss=function(t){return function(e){e&&e.preventDefault(),t.close(this)}},l(t,null,[{key:"VERSION",get:function(){return"4.6.2"}}]),t}();o.default(document).on("click.bs.alert.data-api",'[data-dismiss="alert"]',g._handleDismiss(new g)),o.default.fn.alert=g._jQueryInterface,o.default.fn.alert.Constructor=g,o.default.fn.alert.noConflict=function(){return o.default.fn.alert=h,g._jQueryInterface};var m="bs.button",p=o.default.fn.button,_="active",v='[data-toggle^="button"]',y='input:not([type="hidden"])',b=".btn",E=function(){function t(t){this._element=t,this.shouldAvoidTriggerChange=!1}var e=t.prototype;return e.toggle=function(){var t=!0,e=!0,n=o.default(this._element).closest('[data-toggle="buttons"]')[0];if(n){var i=this._element.querySelector(y);if(i){if("radio"===i.type)if(i.checked&&this._element.classList.contains(_))t=!1;else{var a=n.querySelector(".active");a&&o.default(a).removeClass(_)}t&&("checkbox"!==i.type&&"radio"!==i.type||(i.checked=!this._element.classList.contains(_)),this.shouldAvoidTriggerChange||o.default(i).trigger("change")),i.focus(),e=!1}}this._element.hasAttribute("disabled")||this._element.classList.contains("disabled")||(e&&this._element.setAttribute("aria-pressed",!this._element.classList.contains(_)),t&&o.default(this._element).toggleClass(_))},e.dispose=function(){o.default.removeData(this._element,m),this._element=null},t._jQueryInterface=function(e,n){return this.each((function(){var i=o.default(this),a=i.data(m);a||(a=new t(this),i.data(m,a)),a.shouldAvoidTriggerChange=n,"toggle"===e&&a[e]()}))},l(t,null,[{key:"VERSION",get:function(){return"4.6.2"}}]),t}();o.default(document).on("click.bs.button.data-api",v,(function(t){var e=t.target,n=e;if(o.default(e).hasClass("btn")||(e=o.default(e).closest(b)[0]),!e||e.hasAttribute("disabled")||e.classList.contains("disabled"))t.preventDefault();else{var i=e.querySelector(y);if(i&&(i.hasAttribute("disabled")||i.classList.contains("disabled")))return void t.preventDefault();"INPUT"!==n.tagName&&"LABEL"===e.tagName||E._jQueryInterface.call(o.default(e),"toggle","INPUT"===n.tagName)}})).on("focus.bs.button.data-api blur.bs.button.data-api",v,(function(t){var e=o.default(t.target).closest(b)[0];o.default(e).toggleClass("focus",/^focus(in)?$/.test(t.type))})),o.default(window).on("load.bs.button.data-api",(function(){for(var t=[].slice.call(document.querySelectorAll('[data-toggle="buttons"] .btn')),e=0,n=t.length;e0,this._pointerEvent=Boolean(window.PointerEvent||window.MSPointerEvent),this._addEventListeners()}var e=t.prototype;return e.next=function(){this._isSliding||this._slide(N)},e.nextWhenVisible=function(){var t=o.default(this._element);!document.hidden&&t.is(":visible")&&"hidden"!==t.css("visibility")&&this.next()},e.prev=function(){this._isSliding||this._slide(D)},e.pause=function(t){t||(this._isPaused=!0),this._element.querySelector(".carousel-item-next, .carousel-item-prev")&&(d.triggerTransitionEnd(this._element),this.cycle(!0)),clearInterval(this._interval),this._interval=null},e.cycle=function(t){t||(this._isPaused=!1),this._interval&&(clearInterval(this._interval),this._interval=null),this._config.interval&&!this._isPaused&&(this._updateInterval(),this._interval=setInterval((document.visibilityState?this.nextWhenVisible:this.next).bind(this),this._config.interval))},e.to=function(t){var e=this;this._activeElement=this._element.querySelector(I);var n=this._getItemIndex(this._activeElement);if(!(t>this._items.length-1||t<0))if(this._isSliding)o.default(this._element).one(A,(function(){return e.to(t)}));else{if(n===t)return this.pause(),void this.cycle();var i=t>n?N:D;this._slide(i,this._items[t])}},e.dispose=function(){o.default(this._element).off(".bs.carousel"),o.default.removeData(this._element,w),this._items=null,this._config=null,this._element=null,this._interval=null,this._isPaused=null,this._isSliding=null,this._activeElement=null,this._indicatorsElement=null},e._getConfig=function(t){return t=r({},k,t),d.typeCheckConfig(T,t,O),t},e._handleSwipe=function(){var t=Math.abs(this.touchDeltaX);if(!(t<=40)){var e=t/this.touchDeltaX;this.touchDeltaX=0,e>0&&this.prev(),e<0&&this.next()}},e._addEventListeners=function(){var t=this;this._config.keyboard&&o.default(this._element).on("keydown.bs.carousel",(function(e){return t._keydown(e)})),"hover"===this._config.pause&&o.default(this._element).on("mouseenter.bs.carousel",(function(e){return t.pause(e)})).on("mouseleave.bs.carousel",(function(e){return t.cycle(e)})),this._config.touch&&this._addTouchEventListeners()},e._addTouchEventListeners=function(){var t=this;if(this._touchSupported){var e=function(e){t._pointerEvent&&j[e.originalEvent.pointerType.toUpperCase()]?t.touchStartX=e.originalEvent.clientX:t._pointerEvent||(t.touchStartX=e.originalEvent.touches[0].clientX)},n=function(e){t._pointerEvent&&j[e.originalEvent.pointerType.toUpperCase()]&&(t.touchDeltaX=e.originalEvent.clientX-t.touchStartX),t._handleSwipe(),"hover"===t._config.pause&&(t.pause(),t.touchTimeout&&clearTimeout(t.touchTimeout),t.touchTimeout=setTimeout((function(e){return t.cycle(e)}),500+t._config.interval))};o.default(this._element.querySelectorAll(".carousel-item img")).on("dragstart.bs.carousel",(function(t){return t.preventDefault()})),this._pointerEvent?(o.default(this._element).on("pointerdown.bs.carousel",(function(t){return e(t)})),o.default(this._element).on("pointerup.bs.carousel",(function(t){return n(t)})),this._element.classList.add("pointer-event")):(o.default(this._element).on("touchstart.bs.carousel",(function(t){return e(t)})),o.default(this._element).on("touchmove.bs.carousel",(function(e){return function(e){t.touchDeltaX=e.originalEvent.touches&&e.originalEvent.touches.length>1?0:e.originalEvent.touches[0].clientX-t.touchStartX}(e)})),o.default(this._element).on("touchend.bs.carousel",(function(t){return n(t)})))}},e._keydown=function(t){if(!/input|textarea/i.test(t.target.tagName))switch(t.which){case 37:t.preventDefault(),this.prev();break;case 39:t.preventDefault(),this.next()}},e._getItemIndex=function(t){return this._items=t&&t.parentNode?[].slice.call(t.parentNode.querySelectorAll(".carousel-item")):[],this._items.indexOf(t)},e._getItemByDirection=function(t,e){var n=t===N,i=t===D,o=this._getItemIndex(e),a=this._items.length-1;if((i&&0===o||n&&o===a)&&!this._config.wrap)return e;var s=(o+(t===D?-1:1))%this._items.length;return-1===s?this._items[this._items.length-1]:this._items[s]},e._triggerSlideEvent=function(t,e){var n=this._getItemIndex(t),i=this._getItemIndex(this._element.querySelector(I)),a=o.default.Event("slide.bs.carousel",{relatedTarget:t,direction:e,from:i,to:n});return o.default(this._element).trigger(a),a},e._setActiveIndicatorElement=function(t){if(this._indicatorsElement){var e=[].slice.call(this._indicatorsElement.querySelectorAll(".active"));o.default(e).removeClass(S);var n=this._indicatorsElement.children[this._getItemIndex(t)];n&&o.default(n).addClass(S)}},e._updateInterval=function(){var t=this._activeElement||this._element.querySelector(I);if(t){var e=parseInt(t.getAttribute("data-interval"),10);e?(this._config.defaultInterval=this._config.defaultInterval||this._config.interval,this._config.interval=e):this._config.interval=this._config.defaultInterval||this._config.interval}},e._slide=function(t,e){var n,i,a,s=this,l=this._element.querySelector(I),r=this._getItemIndex(l),u=e||l&&this._getItemByDirection(t,l),f=this._getItemIndex(u),c=Boolean(this._interval);if(t===N?(n="carousel-item-left",i="carousel-item-next",a="left"):(n="carousel-item-right",i="carousel-item-prev",a="right"),u&&o.default(u).hasClass(S))this._isSliding=!1;else if(!this._triggerSlideEvent(u,a).isDefaultPrevented()&&l&&u){this._isSliding=!0,c&&this.pause(),this._setActiveIndicatorElement(u),this._activeElement=u;var h=o.default.Event(A,{relatedTarget:u,direction:a,from:r,to:f});if(o.default(this._element).hasClass("slide")){o.default(u).addClass(i),d.reflow(u),o.default(l).addClass(n),o.default(u).addClass(n);var g=d.getTransitionDurationFromElement(l);o.default(l).one(d.TRANSITION_END,(function(){o.default(u).removeClass(n+" "+i).addClass(S),o.default(l).removeClass("active "+i+" "+n),s._isSliding=!1,setTimeout((function(){return o.default(s._element).trigger(h)}),0)})).emulateTransitionEnd(g)}else o.default(l).removeClass(S),o.default(u).addClass(S),this._isSliding=!1,o.default(this._element).trigger(h);c&&this.cycle()}},t._jQueryInterface=function(e){return this.each((function(){var n=o.default(this).data(w),i=r({},k,o.default(this).data());"object"==typeof e&&(i=r({},i,e));var a="string"==typeof e?e:i.slide;if(n||(n=new t(this,i),o.default(this).data(w,n)),"number"==typeof e)n.to(e);else if("string"==typeof a){if("undefined"==typeof n[a])throw new TypeError('No method named "'+a+'"');n[a]()}else i.interval&&i.ride&&(n.pause(),n.cycle())}))},t._dataApiClickHandler=function(e){var n=d.getSelectorFromElement(this);if(n){var i=o.default(n)[0];if(i&&o.default(i).hasClass("carousel")){var a=r({},o.default(i).data(),o.default(this).data()),s=this.getAttribute("data-slide-to");s&&(a.interval=!1),t._jQueryInterface.call(o.default(i),a),s&&o.default(i).data(w).to(s),e.preventDefault()}}},l(t,null,[{key:"VERSION",get:function(){return"4.6.2"}},{key:"Default",get:function(){return k}}]),t}();o.default(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",P._dataApiClickHandler),o.default(window).on("load.bs.carousel.data-api",(function(){for(var t=[].slice.call(document.querySelectorAll('[data-ride="carousel"]')),e=0,n=t.length;e0&&(this._selector=s,this._triggerArray.push(a))}this._parent=this._config.parent?this._getParent():null,this._config.parent||this._addAriaAndCollapsedClass(this._element,this._triggerArray),this._config.toggle&&this.toggle()}var e=t.prototype;return e.toggle=function(){o.default(this._element).hasClass(q)?this.hide():this.show()},e.show=function(){var e,n,i=this;if(!(this._isTransitioning||o.default(this._element).hasClass(q)||(this._parent&&0===(e=[].slice.call(this._parent.querySelectorAll(".show, .collapsing")).filter((function(t){return"string"==typeof i._config.parent?t.getAttribute("data-parent")===i._config.parent:t.classList.contains(F)}))).length&&(e=null),e&&(n=o.default(e).not(this._selector).data(R))&&n._isTransitioning))){var a=o.default.Event("show.bs.collapse");if(o.default(this._element).trigger(a),!a.isDefaultPrevented()){e&&(t._jQueryInterface.call(o.default(e).not(this._selector),"hide"),n||o.default(e).data(R,null));var s=this._getDimension();o.default(this._element).removeClass(F).addClass(Q),this._element.style[s]=0,this._triggerArray.length&&o.default(this._triggerArray).removeClass(B).attr("aria-expanded",!0),this.setTransitioning(!0);var l="scroll"+(s[0].toUpperCase()+s.slice(1)),r=d.getTransitionDurationFromElement(this._element);o.default(this._element).one(d.TRANSITION_END,(function(){o.default(i._element).removeClass(Q).addClass("collapse show"),i._element.style[s]="",i.setTransitioning(!1),o.default(i._element).trigger("shown.bs.collapse")})).emulateTransitionEnd(r),this._element.style[s]=this._element[l]+"px"}}},e.hide=function(){var t=this;if(!this._isTransitioning&&o.default(this._element).hasClass(q)){var e=o.default.Event("hide.bs.collapse");if(o.default(this._element).trigger(e),!e.isDefaultPrevented()){var n=this._getDimension();this._element.style[n]=this._element.getBoundingClientRect()[n]+"px",d.reflow(this._element),o.default(this._element).addClass(Q).removeClass("collapse show");var i=this._triggerArray.length;if(i>0)for(var a=0;a0},e._getOffset=function(){var t=this,e={};return"function"==typeof this._config.offset?e.fn=function(e){return e.offsets=r({},e.offsets,t._config.offset(e.offsets,t._element)),e}:e.offset=this._config.offset,e},e._getPopperConfig=function(){var t={placement:this._getPlacement(),modifiers:{offset:this._getOffset(),flip:{enabled:this._config.flip},preventOverflow:{boundariesElement:this._config.boundary}}};return"static"===this._config.display&&(t.modifiers.applyStyle={enabled:!1}),r({},t,this._config.popperConfig)},t._jQueryInterface=function(e){return this.each((function(){var n=o.default(this).data(K);if(n||(n=new t(this,"object"==typeof e?e:null),o.default(this).data(K,n)),"string"==typeof e){if("undefined"==typeof n[e])throw new TypeError('No method named "'+e+'"');n[e]()}}))},t._clearMenus=function(e){if(!e||3!==e.which&&("keyup"!==e.type||9===e.which))for(var n=[].slice.call(document.querySelectorAll(it)),i=0,a=n.length;i0&&s--,40===e.which&&sdocument.documentElement.clientHeight;n||(this._element.style.overflowY="hidden"),this._element.classList.add(ht);var i=d.getTransitionDurationFromElement(this._dialog);o.default(this._element).off(d.TRANSITION_END),o.default(this._element).one(d.TRANSITION_END,(function(){t._element.classList.remove(ht),n||o.default(t._element).one(d.TRANSITION_END,(function(){t._element.style.overflowY=""})).emulateTransitionEnd(t._element,i)})).emulateTransitionEnd(i),this._element.focus()}},e._showElement=function(t){var e=this,n=o.default(this._element).hasClass(dt),i=this._dialog?this._dialog.querySelector(".modal-body"):null;this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE||document.body.appendChild(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),o.default(this._dialog).hasClass("modal-dialog-scrollable")&&i?i.scrollTop=0:this._element.scrollTop=0,n&&d.reflow(this._element),o.default(this._element).addClass(ct),this._config.focus&&this._enforceFocus();var a=o.default.Event("shown.bs.modal",{relatedTarget:t}),s=function(){e._config.focus&&e._element.focus(),e._isTransitioning=!1,o.default(e._element).trigger(a)};if(n){var l=d.getTransitionDurationFromElement(this._dialog);o.default(this._dialog).one(d.TRANSITION_END,s).emulateTransitionEnd(l)}else s()},e._enforceFocus=function(){var t=this;o.default(document).off(pt).on(pt,(function(e){document!==e.target&&t._element!==e.target&&0===o.default(t._element).has(e.target).length&&t._element.focus()}))},e._setEscapeEvent=function(){var t=this;this._isShown?o.default(this._element).on(yt,(function(e){t._config.keyboard&&27===e.which?(e.preventDefault(),t.hide()):t._config.keyboard||27!==e.which||t._triggerBackdropTransition()})):this._isShown||o.default(this._element).off(yt)},e._setResizeEvent=function(){var t=this;this._isShown?o.default(window).on(_t,(function(e){return t.handleUpdate(e)})):o.default(window).off(_t)},e._hideModal=function(){var t=this;this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._showBackdrop((function(){o.default(document.body).removeClass(ft),t._resetAdjustments(),t._resetScrollbar(),o.default(t._element).trigger(gt)}))},e._removeBackdrop=function(){this._backdrop&&(o.default(this._backdrop).remove(),this._backdrop=null)},e._showBackdrop=function(t){var e=this,n=o.default(this._element).hasClass(dt)?dt:"";if(this._isShown&&this._config.backdrop){if(this._backdrop=document.createElement("div"),this._backdrop.className="modal-backdrop",n&&this._backdrop.classList.add(n),o.default(this._backdrop).appendTo(document.body),o.default(this._element).on(vt,(function(t){e._ignoreBackdropClick?e._ignoreBackdropClick=!1:t.target===t.currentTarget&&("static"===e._config.backdrop?e._triggerBackdropTransition():e.hide())})),n&&d.reflow(this._backdrop),o.default(this._backdrop).addClass(ct),!t)return;if(!n)return void t();var i=d.getTransitionDurationFromElement(this._backdrop);o.default(this._backdrop).one(d.TRANSITION_END,t).emulateTransitionEnd(i)}else if(!this._isShown&&this._backdrop){o.default(this._backdrop).removeClass(ct);var a=function(){e._removeBackdrop(),t&&t()};if(o.default(this._element).hasClass(dt)){var s=d.getTransitionDurationFromElement(this._backdrop);o.default(this._backdrop).one(d.TRANSITION_END,a).emulateTransitionEnd(s)}else a()}else t&&t()},e._adjustDialog=function(){var t=this._element.scrollHeight>document.documentElement.clientHeight;!this._isBodyOverflowing&&t&&(this._element.style.paddingLeft=this._scrollbarWidth+"px"),this._isBodyOverflowing&&!t&&(this._element.style.paddingRight=this._scrollbarWidth+"px")},e._resetAdjustments=function(){this._element.style.paddingLeft="",this._element.style.paddingRight=""},e._checkScrollbar=function(){var t=document.body.getBoundingClientRect();this._isBodyOverflowing=Math.round(t.left+t.right)
',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:0,container:!1,fallbackPlacement:"flip",boundary:"scrollParent",customClass:"",sanitize:!0,sanitizeFn:null,whiteList:{"*":["class","dir","id","lang","role",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],div:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},popperConfig:null},Ut={animation:"boolean",template:"string",title:"(string|element|function)",trigger:"string",delay:"(number|object)",html:"boolean",selector:"(string|boolean)",placement:"(string|function)",offset:"(number|string|function)",container:"(string|element|boolean)",fallbackPlacement:"(string|array)",boundary:"(string|element)",customClass:"(string|function)",sanitize:"boolean",sanitizeFn:"(null|function)",whiteList:"object",popperConfig:"(null|object)"},Mt={HIDE:"hide.bs.tooltip",HIDDEN:"hidden.bs.tooltip",SHOW:"show.bs.tooltip",SHOWN:"shown.bs.tooltip",INSERTED:"inserted.bs.tooltip",CLICK:"click.bs.tooltip",FOCUSIN:"focusin.bs.tooltip",FOCUSOUT:"focusout.bs.tooltip",MOUSEENTER:"mouseenter.bs.tooltip",MOUSELEAVE:"mouseleave.bs.tooltip"},Wt=function(){function t(t,e){if("undefined"==typeof a.default)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this.element=t,this.config=this._getConfig(e),this.tip=null,this._setListeners()}var e=t.prototype;return e.enable=function(){this._isEnabled=!0},e.disable=function(){this._isEnabled=!1},e.toggleEnabled=function(){this._isEnabled=!this._isEnabled},e.toggle=function(t){if(this._isEnabled)if(t){var e=this.constructor.DATA_KEY,n=o.default(t.currentTarget).data(e);n||(n=new this.constructor(t.currentTarget,this._getDelegateConfig()),o.default(t.currentTarget).data(e,n)),n._activeTrigger.click=!n._activeTrigger.click,n._isWithActiveTrigger()?n._enter(null,n):n._leave(null,n)}else{if(o.default(this.getTipElement()).hasClass(Rt))return void this._leave(null,this);this._enter(null,this)}},e.dispose=function(){clearTimeout(this._timeout),o.default.removeData(this.element,this.constructor.DATA_KEY),o.default(this.element).off(this.constructor.EVENT_KEY),o.default(this.element).closest(".modal").off("hide.bs.modal",this._hideModalHandler),this.tip&&o.default(this.tip).remove(),this._isEnabled=null,this._timeout=null,this._hoverState=null,this._activeTrigger=null,this._popper&&this._popper.destroy(),this._popper=null,this.element=null,this.config=null,this.tip=null},e.show=function(){var t=this;if("none"===o.default(this.element).css("display"))throw new Error("Please use show on visible elements");var e=o.default.Event(this.constructor.Event.SHOW);if(this.isWithContent()&&this._isEnabled){o.default(this.element).trigger(e);var n=d.findShadowRoot(this.element),i=o.default.contains(null!==n?n:this.element.ownerDocument.documentElement,this.element);if(e.isDefaultPrevented()||!i)return;var s=this.getTipElement(),l=d.getUID(this.constructor.NAME);s.setAttribute("id",l),this.element.setAttribute("aria-describedby",l),this.setContent(),this.config.animation&&o.default(s).addClass(Lt);var r="function"==typeof this.config.placement?this.config.placement.call(this,s,this.element):this.config.placement,u=this._getAttachment(r);this.addAttachmentClass(u);var f=this._getContainer();o.default(s).data(this.constructor.DATA_KEY,this),o.default.contains(this.element.ownerDocument.documentElement,this.tip)||o.default(s).appendTo(f),o.default(this.element).trigger(this.constructor.Event.INSERTED),this._popper=new a.default(this.element,s,this._getPopperConfig(u)),o.default(s).addClass(Rt),o.default(s).addClass(this.config.customClass),"ontouchstart"in document.documentElement&&o.default(document.body).children().on("mouseover",null,o.default.noop);var c=function(){t.config.animation&&t._fixTransition();var e=t._hoverState;t._hoverState=null,o.default(t.element).trigger(t.constructor.Event.SHOWN),e===qt&&t._leave(null,t)};if(o.default(this.tip).hasClass(Lt)){var h=d.getTransitionDurationFromElement(this.tip);o.default(this.tip).one(d.TRANSITION_END,c).emulateTransitionEnd(h)}else c()}},e.hide=function(t){var e=this,n=this.getTipElement(),i=o.default.Event(this.constructor.Event.HIDE),a=function(){e._hoverState!==xt&&n.parentNode&&n.parentNode.removeChild(n),e._cleanTipClass(),e.element.removeAttribute("aria-describedby"),o.default(e.element).trigger(e.constructor.Event.HIDDEN),null!==e._popper&&e._popper.destroy(),t&&t()};if(o.default(this.element).trigger(i),!i.isDefaultPrevented()){if(o.default(n).removeClass(Rt),"ontouchstart"in document.documentElement&&o.default(document.body).children().off("mouseover",null,o.default.noop),this._activeTrigger.click=!1,this._activeTrigger.focus=!1,this._activeTrigger.hover=!1,o.default(this.tip).hasClass(Lt)){var s=d.getTransitionDurationFromElement(n);o.default(n).one(d.TRANSITION_END,a).emulateTransitionEnd(s)}else a();this._hoverState=""}},e.update=function(){null!==this._popper&&this._popper.scheduleUpdate()},e.isWithContent=function(){return Boolean(this.getTitle())},e.addAttachmentClass=function(t){o.default(this.getTipElement()).addClass("bs-tooltip-"+t)},e.getTipElement=function(){return this.tip=this.tip||o.default(this.config.template)[0],this.tip},e.setContent=function(){var t=this.getTipElement();this.setElementContent(o.default(t.querySelectorAll(".tooltip-inner")),this.getTitle()),o.default(t).removeClass("fade show")},e.setElementContent=function(t,e){"object"!=typeof e||!e.nodeType&&!e.jquery?this.config.html?(this.config.sanitize&&(e=At(e,this.config.whiteList,this.config.sanitizeFn)),t.html(e)):t.text(e):this.config.html?o.default(e).parent().is(t)||t.empty().append(e):t.text(o.default(e).text())},e.getTitle=function(){var t=this.element.getAttribute("data-original-title");return t||(t="function"==typeof this.config.title?this.config.title.call(this.element):this.config.title),t},e._getPopperConfig=function(t){var e=this;return r({},{placement:t,modifiers:{offset:this._getOffset(),flip:{behavior:this.config.fallbackPlacement},arrow:{element:".arrow"},preventOverflow:{boundariesElement:this.config.boundary}},onCreate:function(t){t.originalPlacement!==t.placement&&e._handlePopperPlacementChange(t)},onUpdate:function(t){return e._handlePopperPlacementChange(t)}},this.config.popperConfig)},e._getOffset=function(){var t=this,e={};return"function"==typeof this.config.offset?e.fn=function(e){return e.offsets=r({},e.offsets,t.config.offset(e.offsets,t.element)),e}:e.offset=this.config.offset,e},e._getContainer=function(){return!1===this.config.container?document.body:d.isElement(this.config.container)?o.default(this.config.container):o.default(document).find(this.config.container)},e._getAttachment=function(t){return Bt[t.toUpperCase()]},e._setListeners=function(){var t=this;this.config.trigger.split(" ").forEach((function(e){if("click"===e)o.default(t.element).on(t.constructor.Event.CLICK,t.config.selector,(function(e){return t.toggle(e)}));else if("manual"!==e){var n=e===Ft?t.constructor.Event.MOUSEENTER:t.constructor.Event.FOCUSIN,i=e===Ft?t.constructor.Event.MOUSELEAVE:t.constructor.Event.FOCUSOUT;o.default(t.element).on(n,t.config.selector,(function(e){return t._enter(e)})).on(i,t.config.selector,(function(e){return t._leave(e)}))}})),this._hideModalHandler=function(){t.element&&t.hide()},o.default(this.element).closest(".modal").on("hide.bs.modal",this._hideModalHandler),this.config.selector?this.config=r({},this.config,{trigger:"manual",selector:""}):this._fixTitle()},e._fixTitle=function(){var t=typeof this.element.getAttribute("data-original-title");(this.element.getAttribute("title")||"string"!==t)&&(this.element.setAttribute("data-original-title",this.element.getAttribute("title")||""),this.element.setAttribute("title",""))},e._enter=function(t,e){var n=this.constructor.DATA_KEY;(e=e||o.default(t.currentTarget).data(n))||(e=new this.constructor(t.currentTarget,this._getDelegateConfig()),o.default(t.currentTarget).data(n,e)),t&&(e._activeTrigger["focusin"===t.type?Qt:Ft]=!0),o.default(e.getTipElement()).hasClass(Rt)||e._hoverState===xt?e._hoverState=xt:(clearTimeout(e._timeout),e._hoverState=xt,e.config.delay&&e.config.delay.show?e._timeout=setTimeout((function(){e._hoverState===xt&&e.show()}),e.config.delay.show):e.show())},e._leave=function(t,e){var n=this.constructor.DATA_KEY;(e=e||o.default(t.currentTarget).data(n))||(e=new this.constructor(t.currentTarget,this._getDelegateConfig()),o.default(t.currentTarget).data(n,e)),t&&(e._activeTrigger["focusout"===t.type?Qt:Ft]=!1),e._isWithActiveTrigger()||(clearTimeout(e._timeout),e._hoverState=qt,e.config.delay&&e.config.delay.hide?e._timeout=setTimeout((function(){e._hoverState===qt&&e.hide()}),e.config.delay.hide):e.hide())},e._isWithActiveTrigger=function(){for(var t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1},e._getConfig=function(t){var e=o.default(this.element).data();return Object.keys(e).forEach((function(t){-1!==Pt.indexOf(t)&&delete e[t]})),"number"==typeof(t=r({},this.constructor.Default,e,"object"==typeof t&&t?t:{})).delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),d.typeCheckConfig(It,t,this.constructor.DefaultType),t.sanitize&&(t.template=At(t.template,t.whiteList,t.sanitizeFn)),t},e._getDelegateConfig=function(){var t={};if(this.config)for(var e in this.config)this.constructor.Default[e]!==this.config[e]&&(t[e]=this.config[e]);return t},e._cleanTipClass=function(){var t=o.default(this.getTipElement()),e=t.attr("class").match(jt);null!==e&&e.length&&t.removeClass(e.join(""))},e._handlePopperPlacementChange=function(t){this.tip=t.instance.popper,this._cleanTipClass(),this.addAttachmentClass(this._getAttachment(t.placement))},e._fixTransition=function(){var t=this.getTipElement(),e=this.config.animation;null===t.getAttribute("x-placement")&&(o.default(t).removeClass(Lt),this.config.animation=!1,this.hide(),this.show(),this.config.animation=e)},t._jQueryInterface=function(e){return this.each((function(){var n=o.default(this),i=n.data(kt),a="object"==typeof e&&e;if((i||!/dispose|hide/.test(e))&&(i||(i=new t(this,a),n.data(kt,i)),"string"==typeof e)){if("undefined"==typeof i[e])throw new TypeError('No method named "'+e+'"');i[e]()}}))},l(t,null,[{key:"VERSION",get:function(){return"4.6.2"}},{key:"Default",get:function(){return Ht}},{key:"NAME",get:function(){return It}},{key:"DATA_KEY",get:function(){return kt}},{key:"Event",get:function(){return Mt}},{key:"EVENT_KEY",get:function(){return".bs.tooltip"}},{key:"DefaultType",get:function(){return Ut}}]),t}();o.default.fn.tooltip=Wt._jQueryInterface,o.default.fn.tooltip.Constructor=Wt,o.default.fn.tooltip.noConflict=function(){return o.default.fn.tooltip=Ot,Wt._jQueryInterface};var Vt="bs.popover",zt=o.default.fn.popover,Kt=new RegExp("(^|\\s)bs-popover\\S+","g"),Xt=r({},Wt.Default,{placement:"right",trigger:"click",content:"",template:''}),Yt=r({},Wt.DefaultType,{content:"(string|element|function)"}),$t={HIDE:"hide.bs.popover",HIDDEN:"hidden.bs.popover",SHOW:"show.bs.popover",SHOWN:"shown.bs.popover",INSERTED:"inserted.bs.popover",CLICK:"click.bs.popover",FOCUSIN:"focusin.bs.popover",FOCUSOUT:"focusout.bs.popover",MOUSEENTER:"mouseenter.bs.popover",MOUSELEAVE:"mouseleave.bs.popover"},Jt=function(t){var e,n;function i(){return t.apply(this,arguments)||this}n=t,(e=i).prototype=Object.create(n.prototype),e.prototype.constructor=e,u(e,n);var a=i.prototype;return a.isWithContent=function(){return this.getTitle()||this._getContent()},a.addAttachmentClass=function(t){o.default(this.getTipElement()).addClass("bs-popover-"+t)},a.getTipElement=function(){return this.tip=this.tip||o.default(this.config.template)[0],this.tip},a.setContent=function(){var t=o.default(this.getTipElement());this.setElementContent(t.find(".popover-header"),this.getTitle());var e=this._getContent();"function"==typeof e&&(e=e.call(this.element)),this.setElementContent(t.find(".popover-body"),e),t.removeClass("fade show")},a._getContent=function(){return this.element.getAttribute("data-content")||this.config.content},a._cleanTipClass=function(){var t=o.default(this.getTipElement()),e=t.attr("class").match(Kt);null!==e&&e.length>0&&t.removeClass(e.join(""))},i._jQueryInterface=function(t){return this.each((function(){var e=o.default(this).data(Vt),n="object"==typeof t?t:null;if((e||!/dispose|hide/.test(t))&&(e||(e=new i(this,n),o.default(this).data(Vt,e)),"string"==typeof t)){if("undefined"==typeof e[t])throw new TypeError('No method named "'+t+'"');e[t]()}}))},l(i,null,[{key:"VERSION",get:function(){return"4.6.2"}},{key:"Default",get:function(){return Xt}},{key:"NAME",get:function(){return"popover"}},{key:"DATA_KEY",get:function(){return Vt}},{key:"Event",get:function(){return $t}},{key:"EVENT_KEY",get:function(){return".bs.popover"}},{key:"DefaultType",get:function(){return Yt}}]),i}(Wt);o.default.fn.popover=Jt._jQueryInterface,o.default.fn.popover.Constructor=Jt,o.default.fn.popover.noConflict=function(){return o.default.fn.popover=zt,Jt._jQueryInterface};var Gt="scrollspy",Zt="bs.scrollspy",te=o.default.fn[Gt],ee="active",ne="position",ie=".nav, .list-group",oe={offset:10,method:"auto",target:""},ae={offset:"number",method:"string",target:"(string|element)"},se=function(){function t(t,e){var n=this;this._element=t,this._scrollElement="BODY"===t.tagName?window:t,this._config=this._getConfig(e),this._selector=this._config.target+" .nav-link,"+this._config.target+" .list-group-item,"+this._config.target+" .dropdown-item",this._offsets=[],this._targets=[],this._activeTarget=null,this._scrollHeight=0,o.default(this._scrollElement).on("scroll.bs.scrollspy",(function(t){return n._process(t)})),this.refresh(),this._process()}var e=t.prototype;return e.refresh=function(){var t=this,e=this._scrollElement===this._scrollElement.window?"offset":ne,n="auto"===this._config.method?e:this._config.method,i=n===ne?this._getScrollTop():0;this._offsets=[],this._targets=[],this._scrollHeight=this._getScrollHeight(),[].slice.call(document.querySelectorAll(this._selector)).map((function(t){var e,a=d.getSelectorFromElement(t);if(a&&(e=document.querySelector(a)),e){var s=e.getBoundingClientRect();if(s.width||s.height)return[o.default(e)[n]().top+i,a]}return null})).filter(Boolean).sort((function(t,e){return t[0]-e[0]})).forEach((function(e){t._offsets.push(e[0]),t._targets.push(e[1])}))},e.dispose=function(){o.default.removeData(this._element,Zt),o.default(this._scrollElement).off(".bs.scrollspy"),this._element=null,this._scrollElement=null,this._config=null,this._selector=null,this._offsets=null,this._targets=null,this._activeTarget=null,this._scrollHeight=null},e._getConfig=function(t){if("string"!=typeof(t=r({},oe,"object"==typeof t&&t?t:{})).target&&d.isElement(t.target)){var e=o.default(t.target).attr("id");e||(e=d.getUID(Gt),o.default(t.target).attr("id",e)),t.target="#"+e}return d.typeCheckConfig(Gt,t,ae),t},e._getScrollTop=function(){return this._scrollElement===window?this._scrollElement.pageYOffset:this._scrollElement.scrollTop},e._getScrollHeight=function(){return this._scrollElement.scrollHeight||Math.max(document.body.scrollHeight,document.documentElement.scrollHeight)},e._getOffsetHeight=function(){return this._scrollElement===window?window.innerHeight:this._scrollElement.getBoundingClientRect().height},e._process=function(){var t=this._getScrollTop()+this._config.offset,e=this._getScrollHeight(),n=this._config.offset+e-this._getOffsetHeight();if(this._scrollHeight!==e&&this.refresh(),t>=n){var i=this._targets[this._targets.length-1];this._activeTarget!==i&&this._activate(i)}else{if(this._activeTarget&&t0)return this._activeTarget=null,void this._clear();for(var o=this._offsets.length;o--;)this._activeTarget!==this._targets[o]&&t>=this._offsets[o]&&("undefined"==typeof this._offsets[o+1]||t li > .active",ge=function(){function t(t){this._element=t}var e=t.prototype;return e.show=function(){var t=this;if(!(this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE&&o.default(this._element).hasClass(ue)||o.default(this._element).hasClass("disabled")||this._element.hasAttribute("disabled"))){var e,n,i=o.default(this._element).closest(".nav, .list-group")[0],a=d.getSelectorFromElement(this._element);if(i){var s="UL"===i.nodeName||"OL"===i.nodeName?he:ce;n=(n=o.default.makeArray(o.default(i).find(s)))[n.length-1]}var l=o.default.Event("hide.bs.tab",{relatedTarget:this._element}),r=o.default.Event("show.bs.tab",{relatedTarget:n});if(n&&o.default(n).trigger(l),o.default(this._element).trigger(r),!r.isDefaultPrevented()&&!l.isDefaultPrevented()){a&&(e=document.querySelector(a)),this._activate(this._element,i);var u=function(){var e=o.default.Event("hidden.bs.tab",{relatedTarget:t._element}),i=o.default.Event("shown.bs.tab",{relatedTarget:n});o.default(n).trigger(e),o.default(t._element).trigger(i)};e?this._activate(e,e.parentNode,u):u()}}},e.dispose=function(){o.default.removeData(this._element,le),this._element=null},e._activate=function(t,e,n){var i=this,a=(!e||"UL"!==e.nodeName&&"OL"!==e.nodeName?o.default(e).children(ce):o.default(e).find(he))[0],s=n&&a&&o.default(a).hasClass(fe),l=function(){return i._transitionComplete(t,a,n)};if(a&&s){var r=d.getTransitionDurationFromElement(a);o.default(a).removeClass(de).one(d.TRANSITION_END,l).emulateTransitionEnd(r)}else l()},e._transitionComplete=function(t,e,n){if(e){o.default(e).removeClass(ue);var i=o.default(e.parentNode).find("> .dropdown-menu .active")[0];i&&o.default(i).removeClass(ue),"tab"===e.getAttribute("role")&&e.setAttribute("aria-selected",!1)}o.default(t).addClass(ue),"tab"===t.getAttribute("role")&&t.setAttribute("aria-selected",!0),d.reflow(t),t.classList.contains(fe)&&t.classList.add(de);var a=t.parentNode;if(a&&"LI"===a.nodeName&&(a=a.parentNode),a&&o.default(a).hasClass("dropdown-menu")){var s=o.default(t).closest(".dropdown")[0];if(s){var l=[].slice.call(s.querySelectorAll(".dropdown-toggle"));o.default(l).addClass(ue)}t.setAttribute("aria-expanded",!0)}n&&n()},t._jQueryInterface=function(e){return this.each((function(){var n=o.default(this),i=n.data(le);if(i||(i=new t(this),n.data(le,i)),"string"==typeof e){if("undefined"==typeof i[e])throw new TypeError('No method named "'+e+'"');i[e]()}}))},l(t,null,[{key:"VERSION",get:function(){return"4.6.2"}}]),t}();o.default(document).on("click.bs.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"], [data-toggle="list"]',(function(t){t.preventDefault(),ge._jQueryInterface.call(o.default(this),"show")})),o.default.fn.tab=ge._jQueryInterface,o.default.fn.tab.Constructor=ge,o.default.fn.tab.noConflict=function(){return o.default.fn.tab=re,ge._jQueryInterface};var me="bs.toast",pe=o.default.fn.toast,_e="hide",ve="show",ye="showing",be="click.dismiss.bs.toast",Ee={animation:!0,autohide:!0,delay:500},Te={animation:"boolean",autohide:"boolean",delay:"number"},we=function(){function t(t,e){this._element=t,this._config=this._getConfig(e),this._timeout=null,this._setListeners()}var e=t.prototype;return e.show=function(){var t=this,e=o.default.Event("show.bs.toast");if(o.default(this._element).trigger(e),!e.isDefaultPrevented()){this._clearTimeout(),this._config.animation&&this._element.classList.add("fade");var n=function(){t._element.classList.remove(ye),t._element.classList.add(ve),o.default(t._element).trigger("shown.bs.toast"),t._config.autohide&&(t._timeout=setTimeout((function(){t.hide()}),t._config.delay))};if(this._element.classList.remove(_e),d.reflow(this._element),this._element.classList.add(ye),this._config.animation){var i=d.getTransitionDurationFromElement(this._element);o.default(this._element).one(d.TRANSITION_END,n).emulateTransitionEnd(i)}else n()}},e.hide=function(){if(this._element.classList.contains(ve)){var t=o.default.Event("hide.bs.toast");o.default(this._element).trigger(t),t.isDefaultPrevented()||this._close()}},e.dispose=function(){this._clearTimeout(),this._element.classList.contains(ve)&&this._element.classList.remove(ve),o.default(this._element).off(be),o.default.removeData(this._element,me),this._element=null,this._config=null},e._getConfig=function(t){return t=r({},Ee,o.default(this._element).data(),"object"==typeof t&&t?t:{}),d.typeCheckConfig("toast",t,this.constructor.DefaultType),t},e._setListeners=function(){var t=this;o.default(this._element).on(be,'[data-dismiss="toast"]',(function(){return t.hide()}))},e._close=function(){var t=this,e=function(){t._element.classList.add(_e),o.default(t._element).trigger("hidden.bs.toast")};if(this._element.classList.remove(ve),this._config.animation){var n=d.getTransitionDurationFromElement(this._element);o.default(this._element).one(d.TRANSITION_END,e).emulateTransitionEnd(n)}else e()},e._clearTimeout=function(){clearTimeout(this._timeout),this._timeout=null},t._jQueryInterface=function(e){return this.each((function(){var n=o.default(this),i=n.data(me);if(i||(i=new t(this,"object"==typeof e&&e),n.data(me,i)),"string"==typeof e){if("undefined"==typeof i[e])throw new TypeError('No method named "'+e+'"');i[e](this)}}))},l(t,null,[{key:"VERSION",get:function(){return"4.6.2"}},{key:"DefaultType",get:function(){return Te}},{key:"Default",get:function(){return Ee}}]),t}();o.default.fn.toast=we._jQueryInterface,o.default.fn.toast.Constructor=we,o.default.fn.toast.noConflict=function(){return o.default.fn.toast=pe,we._jQueryInterface},t.Alert=g,t.Button=E,t.Carousel=P,t.Collapse=V,t.Dropdown=lt,t.Modal=Ct,t.Popover=Jt,t.Scrollspy=se,t.Tab=ge,t.Toast=we,t.Tooltip=Wt,t.Util=d,Object.defineProperty(t,"__esModule",{value:!0})})); -//# sourceMappingURL=bootstrap.min.js.map \ No newline at end of file diff --git a/app/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/js/d3.min.js b/app/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/js/d3.min.js deleted file mode 100644 index 166487309..000000000 --- a/app/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/js/d3.min.js +++ /dev/null @@ -1,5 +0,0 @@ -!function(){function n(n){return n&&(n.ownerDocument||n.document||n).documentElement}function t(n){return n&&(n.ownerDocument&&n.ownerDocument.defaultView||n.document&&n||n.defaultView)}function e(n,t){return t>n?-1:n>t?1:n>=t?0:NaN}function r(n){return null===n?NaN:+n}function i(n){return!isNaN(n)}function u(n){return{left:function(t,e,r,i){for(arguments.length<3&&(r=0),arguments.length<4&&(i=t.length);i>r;){var u=r+i>>>1;n(t[u],e)<0?r=u+1:i=u}return r},right:function(t,e,r,i){for(arguments.length<3&&(r=0),arguments.length<4&&(i=t.length);i>r;){var u=r+i>>>1;n(t[u],e)>0?i=u:r=u+1}return r}}}function o(n){return n.length}function a(n){for(var t=1;n*t%1;)t*=10;return t}function l(n,t){for(var e in t)Object.defineProperty(n.prototype,e,{value:t[e],enumerable:!1})}function c(){this._=Object.create(null)}function f(n){return(n+="")===bo||n[0]===_o?_o+n:n}function s(n){return(n+="")[0]===_o?n.slice(1):n}function h(n){return f(n)in this._}function p(n){return(n=f(n))in this._&&delete this._[n]}function g(){var n=[];for(var t in this._)n.push(s(t));return n}function v(){var n=0;for(var t in this._)++n;return n}function d(){for(var n in this._)return!1;return!0}function y(){this._=Object.create(null)}function m(n){return n}function M(n,t,e){return function(){var r=e.apply(t,arguments);return r===t?n:r}}function x(n,t){if(t in n)return t;t=t.charAt(0).toUpperCase()+t.slice(1);for(var e=0,r=wo.length;r>e;++e){var i=wo[e]+t;if(i in n)return i}}function b(){}function _(){}function w(n){function t(){for(var t,r=e,i=-1,u=r.length;++ie;e++)for(var i,u=n[e],o=0,a=u.length;a>o;o++)(i=u[o])&&t(i,o,e);return n}function Z(n){return ko(n,qo),n}function V(n){var t,e;return function(r,i,u){var o,a=n[u].update,l=a.length;for(u!=e&&(e=u,t=0),i>=t&&(t=i+1);!(o=a[t])&&++t0&&(n=n.slice(0,a));var c=To.get(n);return c&&(n=c,l=B),a?t?i:r:t?b:u}function $(n,t){return function(e){var r=ao.event;ao.event=e,t[0]=this.__data__;try{n.apply(this,t)}finally{ao.event=r}}}function B(n,t){var e=$(n,t);return function(n){var t=this,r=n.relatedTarget;r&&(r===t||8&r.compareDocumentPosition(t))||e.call(t,n)}}function W(e){var r=".dragsuppress-"+ ++Do,i="click"+r,u=ao.select(t(e)).on("touchmove"+r,S).on("dragstart"+r,S).on("selectstart"+r,S);if(null==Ro&&(Ro="onselectstart"in e?!1:x(e.style,"userSelect")),Ro){var o=n(e).style,a=o[Ro];o[Ro]="none"}return function(n){if(u.on(r,null),Ro&&(o[Ro]=a),n){var t=function(){u.on(i,null)};u.on(i,function(){S(),t()},!0),setTimeout(t,0)}}}function J(n,e){e.changedTouches&&(e=e.changedTouches[0]);var r=n.ownerSVGElement||n;if(r.createSVGPoint){var i=r.createSVGPoint();if(0>Po){var u=t(n);if(u.scrollX||u.scrollY){r=ao.select("body").append("svg").style({position:"absolute",top:0,left:0,margin:0,padding:0,border:"none"},"important");var o=r[0][0].getScreenCTM();Po=!(o.f||o.e),r.remove()}}return Po?(i.x=e.pageX,i.y=e.pageY):(i.x=e.clientX,i.y=e.clientY),i=i.matrixTransform(n.getScreenCTM().inverse()),[i.x,i.y]}var a=n.getBoundingClientRect();return[e.clientX-a.left-n.clientLeft,e.clientY-a.top-n.clientTop]}function G(){return ao.event.changedTouches[0].identifier}function K(n){return n>0?1:0>n?-1:0}function Q(n,t,e){return(t[0]-n[0])*(e[1]-n[1])-(t[1]-n[1])*(e[0]-n[0])}function nn(n){return n>1?0:-1>n?Fo:Math.acos(n)}function tn(n){return n>1?Io:-1>n?-Io:Math.asin(n)}function en(n){return((n=Math.exp(n))-1/n)/2}function rn(n){return((n=Math.exp(n))+1/n)/2}function un(n){return((n=Math.exp(2*n))-1)/(n+1)}function on(n){return(n=Math.sin(n/2))*n}function an(){}function ln(n,t,e){return this instanceof ln?(this.h=+n,this.s=+t,void(this.l=+e)):arguments.length<2?n instanceof ln?new ln(n.h,n.s,n.l):_n(""+n,wn,ln):new ln(n,t,e)}function cn(n,t,e){function r(n){return n>360?n-=360:0>n&&(n+=360),60>n?u+(o-u)*n/60:180>n?o:240>n?u+(o-u)*(240-n)/60:u}function i(n){return Math.round(255*r(n))}var u,o;return n=isNaN(n)?0:(n%=360)<0?n+360:n,t=isNaN(t)?0:0>t?0:t>1?1:t,e=0>e?0:e>1?1:e,o=.5>=e?e*(1+t):e+t-e*t,u=2*e-o,new mn(i(n+120),i(n),i(n-120))}function fn(n,t,e){return this instanceof fn?(this.h=+n,this.c=+t,void(this.l=+e)):arguments.length<2?n instanceof fn?new fn(n.h,n.c,n.l):n instanceof hn?gn(n.l,n.a,n.b):gn((n=Sn((n=ao.rgb(n)).r,n.g,n.b)).l,n.a,n.b):new fn(n,t,e)}function sn(n,t,e){return isNaN(n)&&(n=0),isNaN(t)&&(t=0),new hn(e,Math.cos(n*=Yo)*t,Math.sin(n)*t)}function hn(n,t,e){return this instanceof hn?(this.l=+n,this.a=+t,void(this.b=+e)):arguments.length<2?n instanceof hn?new hn(n.l,n.a,n.b):n instanceof fn?sn(n.h,n.c,n.l):Sn((n=mn(n)).r,n.g,n.b):new hn(n,t,e)}function pn(n,t,e){var r=(n+16)/116,i=r+t/500,u=r-e/200;return i=vn(i)*na,r=vn(r)*ta,u=vn(u)*ea,new mn(yn(3.2404542*i-1.5371385*r-.4985314*u),yn(-.969266*i+1.8760108*r+.041556*u),yn(.0556434*i-.2040259*r+1.0572252*u))}function gn(n,t,e){return n>0?new fn(Math.atan2(e,t)*Zo,Math.sqrt(t*t+e*e),n):new fn(NaN,NaN,n)}function vn(n){return n>.206893034?n*n*n:(n-4/29)/7.787037}function dn(n){return n>.008856?Math.pow(n,1/3):7.787037*n+4/29}function yn(n){return Math.round(255*(.00304>=n?12.92*n:1.055*Math.pow(n,1/2.4)-.055))}function mn(n,t,e){return this instanceof mn?(this.r=~~n,this.g=~~t,void(this.b=~~e)):arguments.length<2?n instanceof mn?new mn(n.r,n.g,n.b):_n(""+n,mn,cn):new mn(n,t,e)}function Mn(n){return new mn(n>>16,n>>8&255,255&n)}function xn(n){return Mn(n)+""}function bn(n){return 16>n?"0"+Math.max(0,n).toString(16):Math.min(255,n).toString(16)}function _n(n,t,e){var r,i,u,o=0,a=0,l=0;if(r=/([a-z]+)\((.*)\)/.exec(n=n.toLowerCase()))switch(i=r[2].split(","),r[1]){case"hsl":return e(parseFloat(i[0]),parseFloat(i[1])/100,parseFloat(i[2])/100);case"rgb":return t(Nn(i[0]),Nn(i[1]),Nn(i[2]))}return(u=ua.get(n))?t(u.r,u.g,u.b):(null==n||"#"!==n.charAt(0)||isNaN(u=parseInt(n.slice(1),16))||(4===n.length?(o=(3840&u)>>4,o=o>>4|o,a=240&u,a=a>>4|a,l=15&u,l=l<<4|l):7===n.length&&(o=(16711680&u)>>16,a=(65280&u)>>8,l=255&u)),t(o,a,l))}function wn(n,t,e){var r,i,u=Math.min(n/=255,t/=255,e/=255),o=Math.max(n,t,e),a=o-u,l=(o+u)/2;return a?(i=.5>l?a/(o+u):a/(2-o-u),r=n==o?(t-e)/a+(e>t?6:0):t==o?(e-n)/a+2:(n-t)/a+4,r*=60):(r=NaN,i=l>0&&1>l?0:r),new ln(r,i,l)}function Sn(n,t,e){n=kn(n),t=kn(t),e=kn(e);var r=dn((.4124564*n+.3575761*t+.1804375*e)/na),i=dn((.2126729*n+.7151522*t+.072175*e)/ta),u=dn((.0193339*n+.119192*t+.9503041*e)/ea);return hn(116*i-16,500*(r-i),200*(i-u))}function kn(n){return(n/=255)<=.04045?n/12.92:Math.pow((n+.055)/1.055,2.4)}function Nn(n){var t=parseFloat(n);return"%"===n.charAt(n.length-1)?Math.round(2.55*t):t}function En(n){return"function"==typeof n?n:function(){return n}}function An(n){return function(t,e,r){return 2===arguments.length&&"function"==typeof e&&(r=e,e=null),Cn(t,e,n,r)}}function Cn(n,t,e,r){function i(){var n,t=l.status;if(!t&&Ln(l)||t>=200&&300>t||304===t){try{n=e.call(u,l)}catch(r){return void o.error.call(u,r)}o.load.call(u,n)}else o.error.call(u,l)}var u={},o=ao.dispatch("beforesend","progress","load","error"),a={},l=new XMLHttpRequest,c=null;return!this.XDomainRequest||"withCredentials"in l||!/^(http(s)?:)?\/\//.test(n)||(l=new XDomainRequest),"onload"in l?l.onload=l.onerror=i:l.onreadystatechange=function(){l.readyState>3&&i()},l.onprogress=function(n){var t=ao.event;ao.event=n;try{o.progress.call(u,l)}finally{ao.event=t}},u.header=function(n,t){return n=(n+"").toLowerCase(),arguments.length<2?a[n]:(null==t?delete a[n]:a[n]=t+"",u)},u.mimeType=function(n){return arguments.length?(t=null==n?null:n+"",u):t},u.responseType=function(n){return arguments.length?(c=n,u):c},u.response=function(n){return e=n,u},["get","post"].forEach(function(n){u[n]=function(){return u.send.apply(u,[n].concat(co(arguments)))}}),u.send=function(e,r,i){if(2===arguments.length&&"function"==typeof r&&(i=r,r=null),l.open(e,n,!0),null==t||"accept"in a||(a.accept=t+",*/*"),l.setRequestHeader)for(var f in a)l.setRequestHeader(f,a[f]);return null!=t&&l.overrideMimeType&&l.overrideMimeType(t),null!=c&&(l.responseType=c),null!=i&&u.on("error",i).on("load",function(n){i(null,n)}),o.beforesend.call(u,l),l.send(null==r?null:r),u},u.abort=function(){return l.abort(),u},ao.rebind(u,o,"on"),null==r?u:u.get(zn(r))}function zn(n){return 1===n.length?function(t,e){n(null==t?e:null)}:n}function Ln(n){var t=n.responseType;return t&&"text"!==t?n.response:n.responseText}function qn(n,t,e){var r=arguments.length;2>r&&(t=0),3>r&&(e=Date.now());var i=e+t,u={c:n,t:i,n:null};return aa?aa.n=u:oa=u,aa=u,la||(ca=clearTimeout(ca),la=1,fa(Tn)),u}function Tn(){var n=Rn(),t=Dn()-n;t>24?(isFinite(t)&&(clearTimeout(ca),ca=setTimeout(Tn,t)),la=0):(la=1,fa(Tn))}function Rn(){for(var n=Date.now(),t=oa;t;)n>=t.t&&t.c(n-t.t)&&(t.c=null),t=t.n;return n}function Dn(){for(var n,t=oa,e=1/0;t;)t.c?(t.t8?function(n){return n/e}:function(n){return n*e},symbol:n}}function jn(n){var t=n.decimal,e=n.thousands,r=n.grouping,i=n.currency,u=r&&e?function(n,t){for(var i=n.length,u=[],o=0,a=r[0],l=0;i>0&&a>0&&(l+a+1>t&&(a=Math.max(1,t-l)),u.push(n.substring(i-=a,i+a)),!((l+=a+1)>t));)a=r[o=(o+1)%r.length];return u.reverse().join(e)}:m;return function(n){var e=ha.exec(n),r=e[1]||" ",o=e[2]||">",a=e[3]||"-",l=e[4]||"",c=e[5],f=+e[6],s=e[7],h=e[8],p=e[9],g=1,v="",d="",y=!1,m=!0;switch(h&&(h=+h.substring(1)),(c||"0"===r&&"="===o)&&(c=r="0",o="="),p){case"n":s=!0,p="g";break;case"%":g=100,d="%",p="f";break;case"p":g=100,d="%",p="r";break;case"b":case"o":case"x":case"X":"#"===l&&(v="0"+p.toLowerCase());case"c":m=!1;case"d":y=!0,h=0;break;case"s":g=-1,p="r"}"$"===l&&(v=i[0],d=i[1]),"r"!=p||h||(p="g"),null!=h&&("g"==p?h=Math.max(1,Math.min(21,h)):"e"!=p&&"f"!=p||(h=Math.max(0,Math.min(20,h)))),p=pa.get(p)||Fn;var M=c&&s;return function(n){var e=d;if(y&&n%1)return"";var i=0>n||0===n&&0>1/n?(n=-n,"-"):"-"===a?"":a;if(0>g){var l=ao.formatPrefix(n,h);n=l.scale(n),e=l.symbol+d}else n*=g;n=p(n,h);var x,b,_=n.lastIndexOf(".");if(0>_){var w=m?n.lastIndexOf("e"):-1;0>w?(x=n,b=""):(x=n.substring(0,w),b=n.substring(w))}else x=n.substring(0,_),b=t+n.substring(_+1);!c&&s&&(x=u(x,1/0));var S=v.length+x.length+b.length+(M?0:i.length),k=f>S?new Array(S=f-S+1).join(r):"";return M&&(x=u(k+x,k.length?f-b.length:1/0)),i+=v,n=x+b,("<"===o?i+n+k:">"===o?k+i+n:"^"===o?k.substring(0,S>>=1)+i+n+k.substring(S):i+(M?n:k+n))+e}}}function Fn(n){return n+""}function Hn(){this._=new Date(arguments.length>1?Date.UTC.apply(this,arguments):arguments[0])}function On(n,t,e){function r(t){var e=n(t),r=u(e,1);return r-t>t-e?e:r}function i(e){return t(e=n(new va(e-1)),1),e}function u(n,e){return t(n=new va(+n),e),n}function o(n,r,u){var o=i(n),a=[];if(u>1)for(;r>o;)e(o)%u||a.push(new Date(+o)),t(o,1);else for(;r>o;)a.push(new Date(+o)),t(o,1);return a}function a(n,t,e){try{va=Hn;var r=new Hn;return r._=n,o(r,t,e)}finally{va=Date}}n.floor=n,n.round=r,n.ceil=i,n.offset=u,n.range=o;var l=n.utc=In(n);return l.floor=l,l.round=In(r),l.ceil=In(i),l.offset=In(u),l.range=a,n}function In(n){return function(t,e){try{va=Hn;var r=new Hn;return r._=t,n(r,e)._}finally{va=Date}}}function Yn(n){function t(n){function t(t){for(var e,i,u,o=[],a=-1,l=0;++aa;){if(r>=c)return-1;if(i=t.charCodeAt(a++),37===i){if(o=t.charAt(a++),u=C[o in ya?t.charAt(a++):o],!u||(r=u(n,e,r))<0)return-1}else if(i!=e.charCodeAt(r++))return-1}return r}function r(n,t,e){_.lastIndex=0;var r=_.exec(t.slice(e));return r?(n.w=w.get(r[0].toLowerCase()),e+r[0].length):-1}function i(n,t,e){x.lastIndex=0;var r=x.exec(t.slice(e));return r?(n.w=b.get(r[0].toLowerCase()),e+r[0].length):-1}function u(n,t,e){N.lastIndex=0;var r=N.exec(t.slice(e));return r?(n.m=E.get(r[0].toLowerCase()),e+r[0].length):-1}function o(n,t,e){S.lastIndex=0;var r=S.exec(t.slice(e));return r?(n.m=k.get(r[0].toLowerCase()),e+r[0].length):-1}function a(n,t,r){return e(n,A.c.toString(),t,r)}function l(n,t,r){return e(n,A.x.toString(),t,r)}function c(n,t,r){return e(n,A.X.toString(),t,r)}function f(n,t,e){var r=M.get(t.slice(e,e+=2).toLowerCase());return null==r?-1:(n.p=r,e)}var s=n.dateTime,h=n.date,p=n.time,g=n.periods,v=n.days,d=n.shortDays,y=n.months,m=n.shortMonths;t.utc=function(n){function e(n){try{va=Hn;var t=new va;return t._=n,r(t)}finally{va=Date}}var r=t(n);return e.parse=function(n){try{va=Hn;var t=r.parse(n);return t&&t._}finally{va=Date}},e.toString=r.toString,e},t.multi=t.utc.multi=ct;var M=ao.map(),x=Vn(v),b=Xn(v),_=Vn(d),w=Xn(d),S=Vn(y),k=Xn(y),N=Vn(m),E=Xn(m);g.forEach(function(n,t){M.set(n.toLowerCase(),t)});var A={a:function(n){return d[n.getDay()]},A:function(n){return v[n.getDay()]},b:function(n){return m[n.getMonth()]},B:function(n){return y[n.getMonth()]},c:t(s),d:function(n,t){return Zn(n.getDate(),t,2)},e:function(n,t){return Zn(n.getDate(),t,2)},H:function(n,t){return Zn(n.getHours(),t,2)},I:function(n,t){return Zn(n.getHours()%12||12,t,2)},j:function(n,t){return Zn(1+ga.dayOfYear(n),t,3)},L:function(n,t){return Zn(n.getMilliseconds(),t,3)},m:function(n,t){return Zn(n.getMonth()+1,t,2)},M:function(n,t){return Zn(n.getMinutes(),t,2)},p:function(n){return g[+(n.getHours()>=12)]},S:function(n,t){return Zn(n.getSeconds(),t,2)},U:function(n,t){return Zn(ga.sundayOfYear(n),t,2)},w:function(n){return n.getDay()},W:function(n,t){return Zn(ga.mondayOfYear(n),t,2)},x:t(h),X:t(p),y:function(n,t){return Zn(n.getFullYear()%100,t,2)},Y:function(n,t){return Zn(n.getFullYear()%1e4,t,4)},Z:at,"%":function(){return"%"}},C={a:r,A:i,b:u,B:o,c:a,d:tt,e:tt,H:rt,I:rt,j:et,L:ot,m:nt,M:it,p:f,S:ut,U:Bn,w:$n,W:Wn,x:l,X:c,y:Gn,Y:Jn,Z:Kn,"%":lt};return t}function Zn(n,t,e){var r=0>n?"-":"",i=(r?-n:n)+"",u=i.length;return r+(e>u?new Array(e-u+1).join(t)+i:i)}function Vn(n){return new RegExp("^(?:"+n.map(ao.requote).join("|")+")","i")}function Xn(n){for(var t=new c,e=-1,r=n.length;++e68?1900:2e3)}function nt(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.m=r[0]-1,e+r[0].length):-1}function tt(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.d=+r[0],e+r[0].length):-1}function et(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+3));return r?(n.j=+r[0],e+r[0].length):-1}function rt(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.H=+r[0],e+r[0].length):-1}function it(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.M=+r[0],e+r[0].length):-1}function ut(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.S=+r[0],e+r[0].length):-1}function ot(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+3));return r?(n.L=+r[0],e+r[0].length):-1}function at(n){var t=n.getTimezoneOffset(),e=t>0?"-":"+",r=xo(t)/60|0,i=xo(t)%60;return e+Zn(r,"0",2)+Zn(i,"0",2)}function lt(n,t,e){Ma.lastIndex=0;var r=Ma.exec(t.slice(e,e+1));return r?e+r[0].length:-1}function ct(n){for(var t=n.length,e=-1;++e=0?1:-1,a=o*e,l=Math.cos(t),c=Math.sin(t),f=u*c,s=i*l+f*Math.cos(a),h=f*o*Math.sin(a);ka.add(Math.atan2(h,s)),r=n,i=l,u=c}var t,e,r,i,u;Na.point=function(o,a){Na.point=n,r=(t=o)*Yo,i=Math.cos(a=(e=a)*Yo/2+Fo/4),u=Math.sin(a)},Na.lineEnd=function(){n(t,e)}}function dt(n){var t=n[0],e=n[1],r=Math.cos(e);return[r*Math.cos(t),r*Math.sin(t),Math.sin(e)]}function yt(n,t){return n[0]*t[0]+n[1]*t[1]+n[2]*t[2]}function mt(n,t){return[n[1]*t[2]-n[2]*t[1],n[2]*t[0]-n[0]*t[2],n[0]*t[1]-n[1]*t[0]]}function Mt(n,t){n[0]+=t[0],n[1]+=t[1],n[2]+=t[2]}function xt(n,t){return[n[0]*t,n[1]*t,n[2]*t]}function bt(n){var t=Math.sqrt(n[0]*n[0]+n[1]*n[1]+n[2]*n[2]);n[0]/=t,n[1]/=t,n[2]/=t}function _t(n){return[Math.atan2(n[1],n[0]),tn(n[2])]}function wt(n,t){return xo(n[0]-t[0])a;++a)i.point((e=n[a])[0],e[1]);return void i.lineEnd()}var l=new Tt(e,n,null,!0),c=new Tt(e,null,l,!1);l.o=c,u.push(l),o.push(c),l=new Tt(r,n,null,!1),c=new Tt(r,null,l,!0),l.o=c,u.push(l),o.push(c)}}),o.sort(t),qt(u),qt(o),u.length){for(var a=0,l=e,c=o.length;c>a;++a)o[a].e=l=!l;for(var f,s,h=u[0];;){for(var p=h,g=!0;p.v;)if((p=p.n)===h)return;f=p.z,i.lineStart();do{if(p.v=p.o.v=!0,p.e){if(g)for(var a=0,c=f.length;c>a;++a)i.point((s=f[a])[0],s[1]);else r(p.x,p.n.x,1,i);p=p.n}else{if(g){f=p.p.z;for(var a=f.length-1;a>=0;--a)i.point((s=f[a])[0],s[1])}else r(p.x,p.p.x,-1,i);p=p.p}p=p.o,f=p.z,g=!g}while(!p.v);i.lineEnd()}}}function qt(n){if(t=n.length){for(var t,e,r=0,i=n[0];++r0){for(b||(u.polygonStart(),b=!0),u.lineStart();++o1&&2&t&&e.push(e.pop().concat(e.shift())),p.push(e.filter(Dt))}var p,g,v,d=t(u),y=i.invert(r[0],r[1]),m={point:o,lineStart:l,lineEnd:c,polygonStart:function(){m.point=f,m.lineStart=s,m.lineEnd=h,p=[],g=[]},polygonEnd:function(){m.point=o,m.lineStart=l,m.lineEnd=c,p=ao.merge(p);var n=Ot(y,g);p.length?(b||(u.polygonStart(),b=!0),Lt(p,Ut,n,e,u)):n&&(b||(u.polygonStart(),b=!0),u.lineStart(),e(null,null,1,u),u.lineEnd()),b&&(u.polygonEnd(),b=!1),p=g=null},sphere:function(){u.polygonStart(),u.lineStart(),e(null,null,1,u),u.lineEnd(),u.polygonEnd()}},M=Pt(),x=t(M),b=!1;return m}}function Dt(n){return n.length>1}function Pt(){var n,t=[];return{lineStart:function(){t.push(n=[])},point:function(t,e){n.push([t,e])},lineEnd:b,buffer:function(){var e=t;return t=[],n=null,e},rejoin:function(){t.length>1&&t.push(t.pop().concat(t.shift()))}}}function Ut(n,t){return((n=n.x)[0]<0?n[1]-Io-Uo:Io-n[1])-((t=t.x)[0]<0?t[1]-Io-Uo:Io-t[1])}function jt(n){var t,e=NaN,r=NaN,i=NaN;return{lineStart:function(){n.lineStart(),t=1},point:function(u,o){var a=u>0?Fo:-Fo,l=xo(u-e);xo(l-Fo)0?Io:-Io),n.point(i,r),n.lineEnd(),n.lineStart(),n.point(a,r),n.point(u,r),t=0):i!==a&&l>=Fo&&(xo(e-i)Uo?Math.atan((Math.sin(t)*(u=Math.cos(r))*Math.sin(e)-Math.sin(r)*(i=Math.cos(t))*Math.sin(n))/(i*u*o)):(t+r)/2}function Ht(n,t,e,r){var i;if(null==n)i=e*Io,r.point(-Fo,i),r.point(0,i),r.point(Fo,i),r.point(Fo,0),r.point(Fo,-i),r.point(0,-i),r.point(-Fo,-i),r.point(-Fo,0),r.point(-Fo,i);else if(xo(n[0]-t[0])>Uo){var u=n[0]a;++a){var c=t[a],f=c.length;if(f)for(var s=c[0],h=s[0],p=s[1]/2+Fo/4,g=Math.sin(p),v=Math.cos(p),d=1;;){d===f&&(d=0),n=c[d];var y=n[0],m=n[1]/2+Fo/4,M=Math.sin(m),x=Math.cos(m),b=y-h,_=b>=0?1:-1,w=_*b,S=w>Fo,k=g*M;if(ka.add(Math.atan2(k*_*Math.sin(w),v*x+k*Math.cos(w))),u+=S?b+_*Ho:b,S^h>=e^y>=e){var N=mt(dt(s),dt(n));bt(N);var E=mt(i,N);bt(E);var A=(S^b>=0?-1:1)*tn(E[2]);(r>A||r===A&&(N[0]||N[1]))&&(o+=S^b>=0?1:-1)}if(!d++)break;h=y,g=M,v=x,s=n}}return(-Uo>u||Uo>u&&-Uo>ka)^1&o}function It(n){function t(n,t){return Math.cos(n)*Math.cos(t)>u}function e(n){var e,u,l,c,f;return{lineStart:function(){c=l=!1,f=1},point:function(s,h){var p,g=[s,h],v=t(s,h),d=o?v?0:i(s,h):v?i(s+(0>s?Fo:-Fo),h):0;if(!e&&(c=l=v)&&n.lineStart(),v!==l&&(p=r(e,g),(wt(e,p)||wt(g,p))&&(g[0]+=Uo,g[1]+=Uo,v=t(g[0],g[1]))),v!==l)f=0,v?(n.lineStart(),p=r(g,e),n.point(p[0],p[1])):(p=r(e,g),n.point(p[0],p[1]),n.lineEnd()),e=p;else if(a&&e&&o^v){var y;d&u||!(y=r(g,e,!0))||(f=0,o?(n.lineStart(),n.point(y[0][0],y[0][1]),n.point(y[1][0],y[1][1]),n.lineEnd()):(n.point(y[1][0],y[1][1]),n.lineEnd(),n.lineStart(),n.point(y[0][0],y[0][1])))}!v||e&&wt(e,g)||n.point(g[0],g[1]),e=g,l=v,u=d},lineEnd:function(){l&&n.lineEnd(),e=null},clean:function(){return f|(c&&l)<<1}}}function r(n,t,e){var r=dt(n),i=dt(t),o=[1,0,0],a=mt(r,i),l=yt(a,a),c=a[0],f=l-c*c;if(!f)return!e&&n;var s=u*l/f,h=-u*c/f,p=mt(o,a),g=xt(o,s),v=xt(a,h);Mt(g,v);var d=p,y=yt(g,d),m=yt(d,d),M=y*y-m*(yt(g,g)-1);if(!(0>M)){var x=Math.sqrt(M),b=xt(d,(-y-x)/m);if(Mt(b,g),b=_t(b),!e)return b;var _,w=n[0],S=t[0],k=n[1],N=t[1];w>S&&(_=w,w=S,S=_);var E=S-w,A=xo(E-Fo)E;if(!A&&k>N&&(_=k,k=N,N=_),C?A?k+N>0^b[1]<(xo(b[0]-w)Fo^(w<=b[0]&&b[0]<=S)){var z=xt(d,(-y+x)/m);return Mt(z,g),[b,_t(z)]}}}function i(t,e){var r=o?n:Fo-n,i=0;return-r>t?i|=1:t>r&&(i|=2),-r>e?i|=4:e>r&&(i|=8),i}var u=Math.cos(n),o=u>0,a=xo(u)>Uo,l=ve(n,6*Yo);return Rt(t,e,l,o?[0,-n]:[-Fo,n-Fo])}function Yt(n,t,e,r){return function(i){var u,o=i.a,a=i.b,l=o.x,c=o.y,f=a.x,s=a.y,h=0,p=1,g=f-l,v=s-c;if(u=n-l,g||!(u>0)){if(u/=g,0>g){if(h>u)return;p>u&&(p=u)}else if(g>0){if(u>p)return;u>h&&(h=u)}if(u=e-l,g||!(0>u)){if(u/=g,0>g){if(u>p)return;u>h&&(h=u)}else if(g>0){if(h>u)return;p>u&&(p=u)}if(u=t-c,v||!(u>0)){if(u/=v,0>v){if(h>u)return;p>u&&(p=u)}else if(v>0){if(u>p)return;u>h&&(h=u)}if(u=r-c,v||!(0>u)){if(u/=v,0>v){if(u>p)return;u>h&&(h=u)}else if(v>0){if(h>u)return;p>u&&(p=u)}return h>0&&(i.a={x:l+h*g,y:c+h*v}),1>p&&(i.b={x:l+p*g,y:c+p*v}),i}}}}}}function Zt(n,t,e,r){function i(r,i){return xo(r[0]-n)0?0:3:xo(r[0]-e)0?2:1:xo(r[1]-t)0?1:0:i>0?3:2}function u(n,t){return o(n.x,t.x)}function o(n,t){var e=i(n,1),r=i(t,1);return e!==r?e-r:0===e?t[1]-n[1]:1===e?n[0]-t[0]:2===e?n[1]-t[1]:t[0]-n[0]}return function(a){function l(n){for(var t=0,e=d.length,r=n[1],i=0;e>i;++i)for(var u,o=1,a=d[i],l=a.length,c=a[0];l>o;++o)u=a[o],c[1]<=r?u[1]>r&&Q(c,u,n)>0&&++t:u[1]<=r&&Q(c,u,n)<0&&--t,c=u;return 0!==t}function c(u,a,l,c){var f=0,s=0;if(null==u||(f=i(u,l))!==(s=i(a,l))||o(u,a)<0^l>0){do c.point(0===f||3===f?n:e,f>1?r:t);while((f=(f+l+4)%4)!==s)}else c.point(a[0],a[1])}function f(i,u){return i>=n&&e>=i&&u>=t&&r>=u}function s(n,t){f(n,t)&&a.point(n,t)}function h(){C.point=g,d&&d.push(y=[]),S=!0,w=!1,b=_=NaN}function p(){v&&(g(m,M),x&&w&&E.rejoin(),v.push(E.buffer())),C.point=s,w&&a.lineEnd()}function g(n,t){n=Math.max(-Ha,Math.min(Ha,n)),t=Math.max(-Ha,Math.min(Ha,t));var e=f(n,t);if(d&&y.push([n,t]),S)m=n,M=t,x=e,S=!1,e&&(a.lineStart(),a.point(n,t));else if(e&&w)a.point(n,t);else{var r={a:{x:b,y:_},b:{x:n,y:t}};A(r)?(w||(a.lineStart(),a.point(r.a.x,r.a.y)),a.point(r.b.x,r.b.y),e||a.lineEnd(),k=!1):e&&(a.lineStart(),a.point(n,t),k=!1)}b=n,_=t,w=e}var v,d,y,m,M,x,b,_,w,S,k,N=a,E=Pt(),A=Yt(n,t,e,r),C={point:s,lineStart:h,lineEnd:p,polygonStart:function(){a=E,v=[],d=[],k=!0},polygonEnd:function(){a=N,v=ao.merge(v);var t=l([n,r]),e=k&&t,i=v.length;(e||i)&&(a.polygonStart(),e&&(a.lineStart(),c(null,null,1,a),a.lineEnd()),i&&Lt(v,u,t,c,a),a.polygonEnd()),v=d=y=null}};return C}}function Vt(n){var t=0,e=Fo/3,r=ae(n),i=r(t,e);return i.parallels=function(n){return arguments.length?r(t=n[0]*Fo/180,e=n[1]*Fo/180):[t/Fo*180,e/Fo*180]},i}function Xt(n,t){function e(n,t){var e=Math.sqrt(u-2*i*Math.sin(t))/i;return[e*Math.sin(n*=i),o-e*Math.cos(n)]}var r=Math.sin(n),i=(r+Math.sin(t))/2,u=1+r*(2*i-r),o=Math.sqrt(u)/i;return e.invert=function(n,t){var e=o-t;return[Math.atan2(n,e)/i,tn((u-(n*n+e*e)*i*i)/(2*i))]},e}function $t(){function n(n,t){Ia+=i*n-r*t,r=n,i=t}var t,e,r,i;$a.point=function(u,o){$a.point=n,t=r=u,e=i=o},$a.lineEnd=function(){n(t,e)}}function Bt(n,t){Ya>n&&(Ya=n),n>Va&&(Va=n),Za>t&&(Za=t),t>Xa&&(Xa=t)}function Wt(){function n(n,t){o.push("M",n,",",t,u)}function t(n,t){o.push("M",n,",",t),a.point=e}function e(n,t){o.push("L",n,",",t)}function r(){a.point=n}function i(){o.push("Z")}var u=Jt(4.5),o=[],a={point:n,lineStart:function(){a.point=t},lineEnd:r,polygonStart:function(){a.lineEnd=i},polygonEnd:function(){a.lineEnd=r,a.point=n},pointRadius:function(n){return u=Jt(n),a},result:function(){if(o.length){var n=o.join("");return o=[],n}}};return a}function Jt(n){return"m0,"+n+"a"+n+","+n+" 0 1,1 0,"+-2*n+"a"+n+","+n+" 0 1,1 0,"+2*n+"z"}function Gt(n,t){Ca+=n,za+=t,++La}function Kt(){function n(n,r){var i=n-t,u=r-e,o=Math.sqrt(i*i+u*u);qa+=o*(t+n)/2,Ta+=o*(e+r)/2,Ra+=o,Gt(t=n,e=r)}var t,e;Wa.point=function(r,i){Wa.point=n,Gt(t=r,e=i)}}function Qt(){Wa.point=Gt}function ne(){function n(n,t){var e=n-r,u=t-i,o=Math.sqrt(e*e+u*u);qa+=o*(r+n)/2,Ta+=o*(i+t)/2,Ra+=o,o=i*n-r*t,Da+=o*(r+n),Pa+=o*(i+t),Ua+=3*o,Gt(r=n,i=t)}var t,e,r,i;Wa.point=function(u,o){Wa.point=n,Gt(t=r=u,e=i=o)},Wa.lineEnd=function(){n(t,e)}}function te(n){function t(t,e){n.moveTo(t+o,e),n.arc(t,e,o,0,Ho)}function e(t,e){n.moveTo(t,e),a.point=r}function r(t,e){n.lineTo(t,e)}function i(){a.point=t}function u(){n.closePath()}var o=4.5,a={point:t,lineStart:function(){a.point=e},lineEnd:i,polygonStart:function(){a.lineEnd=u},polygonEnd:function(){a.lineEnd=i,a.point=t},pointRadius:function(n){return o=n,a},result:b};return a}function ee(n){function t(n){return(a?r:e)(n)}function e(t){return ue(t,function(e,r){e=n(e,r),t.point(e[0],e[1])})}function r(t){function e(e,r){e=n(e,r),t.point(e[0],e[1])}function r(){M=NaN,S.point=u,t.lineStart()}function u(e,r){var u=dt([e,r]),o=n(e,r);i(M,x,m,b,_,w,M=o[0],x=o[1],m=e,b=u[0],_=u[1],w=u[2],a,t),t.point(M,x)}function o(){S.point=e,t.lineEnd()}function l(){ -r(),S.point=c,S.lineEnd=f}function c(n,t){u(s=n,h=t),p=M,g=x,v=b,d=_,y=w,S.point=u}function f(){i(M,x,m,b,_,w,p,g,s,v,d,y,a,t),S.lineEnd=o,o()}var s,h,p,g,v,d,y,m,M,x,b,_,w,S={point:e,lineStart:r,lineEnd:o,polygonStart:function(){t.polygonStart(),S.lineStart=l},polygonEnd:function(){t.polygonEnd(),S.lineStart=r}};return S}function i(t,e,r,a,l,c,f,s,h,p,g,v,d,y){var m=f-t,M=s-e,x=m*m+M*M;if(x>4*u&&d--){var b=a+p,_=l+g,w=c+v,S=Math.sqrt(b*b+_*_+w*w),k=Math.asin(w/=S),N=xo(xo(w)-1)u||xo((m*z+M*L)/x-.5)>.3||o>a*p+l*g+c*v)&&(i(t,e,r,a,l,c,A,C,N,b/=S,_/=S,w,d,y),y.point(A,C),i(A,C,N,b,_,w,f,s,h,p,g,v,d,y))}}var u=.5,o=Math.cos(30*Yo),a=16;return t.precision=function(n){return arguments.length?(a=(u=n*n)>0&&16,t):Math.sqrt(u)},t}function re(n){var t=ee(function(t,e){return n([t*Zo,e*Zo])});return function(n){return le(t(n))}}function ie(n){this.stream=n}function ue(n,t){return{point:t,sphere:function(){n.sphere()},lineStart:function(){n.lineStart()},lineEnd:function(){n.lineEnd()},polygonStart:function(){n.polygonStart()},polygonEnd:function(){n.polygonEnd()}}}function oe(n){return ae(function(){return n})()}function ae(n){function t(n){return n=a(n[0]*Yo,n[1]*Yo),[n[0]*h+l,c-n[1]*h]}function e(n){return n=a.invert((n[0]-l)/h,(c-n[1])/h),n&&[n[0]*Zo,n[1]*Zo]}function r(){a=Ct(o=se(y,M,x),u);var n=u(v,d);return l=p-n[0]*h,c=g+n[1]*h,i()}function i(){return f&&(f.valid=!1,f=null),t}var u,o,a,l,c,f,s=ee(function(n,t){return n=u(n,t),[n[0]*h+l,c-n[1]*h]}),h=150,p=480,g=250,v=0,d=0,y=0,M=0,x=0,b=Fa,_=m,w=null,S=null;return t.stream=function(n){return f&&(f.valid=!1),f=le(b(o,s(_(n)))),f.valid=!0,f},t.clipAngle=function(n){return arguments.length?(b=null==n?(w=n,Fa):It((w=+n)*Yo),i()):w},t.clipExtent=function(n){return arguments.length?(S=n,_=n?Zt(n[0][0],n[0][1],n[1][0],n[1][1]):m,i()):S},t.scale=function(n){return arguments.length?(h=+n,r()):h},t.translate=function(n){return arguments.length?(p=+n[0],g=+n[1],r()):[p,g]},t.center=function(n){return arguments.length?(v=n[0]%360*Yo,d=n[1]%360*Yo,r()):[v*Zo,d*Zo]},t.rotate=function(n){return arguments.length?(y=n[0]%360*Yo,M=n[1]%360*Yo,x=n.length>2?n[2]%360*Yo:0,r()):[y*Zo,M*Zo,x*Zo]},ao.rebind(t,s,"precision"),function(){return u=n.apply(this,arguments),t.invert=u.invert&&e,r()}}function le(n){return ue(n,function(t,e){n.point(t*Yo,e*Yo)})}function ce(n,t){return[n,t]}function fe(n,t){return[n>Fo?n-Ho:-Fo>n?n+Ho:n,t]}function se(n,t,e){return n?t||e?Ct(pe(n),ge(t,e)):pe(n):t||e?ge(t,e):fe}function he(n){return function(t,e){return t+=n,[t>Fo?t-Ho:-Fo>t?t+Ho:t,e]}}function pe(n){var t=he(n);return t.invert=he(-n),t}function ge(n,t){function e(n,t){var e=Math.cos(t),a=Math.cos(n)*e,l=Math.sin(n)*e,c=Math.sin(t),f=c*r+a*i;return[Math.atan2(l*u-f*o,a*r-c*i),tn(f*u+l*o)]}var r=Math.cos(n),i=Math.sin(n),u=Math.cos(t),o=Math.sin(t);return e.invert=function(n,t){var e=Math.cos(t),a=Math.cos(n)*e,l=Math.sin(n)*e,c=Math.sin(t),f=c*u-l*o;return[Math.atan2(l*u+c*o,a*r+f*i),tn(f*r-a*i)]},e}function ve(n,t){var e=Math.cos(n),r=Math.sin(n);return function(i,u,o,a){var l=o*t;null!=i?(i=de(e,i),u=de(e,u),(o>0?u>i:i>u)&&(i+=o*Ho)):(i=n+o*Ho,u=n-.5*l);for(var c,f=i;o>0?f>u:u>f;f-=l)a.point((c=_t([e,-r*Math.cos(f),-r*Math.sin(f)]))[0],c[1])}}function de(n,t){var e=dt(t);e[0]-=n,bt(e);var r=nn(-e[1]);return((-e[2]<0?-r:r)+2*Math.PI-Uo)%(2*Math.PI)}function ye(n,t,e){var r=ao.range(n,t-Uo,e).concat(t);return function(n){return r.map(function(t){return[n,t]})}}function me(n,t,e){var r=ao.range(n,t-Uo,e).concat(t);return function(n){return r.map(function(t){return[t,n]})}}function Me(n){return n.source}function xe(n){return n.target}function be(n,t,e,r){var i=Math.cos(t),u=Math.sin(t),o=Math.cos(r),a=Math.sin(r),l=i*Math.cos(n),c=i*Math.sin(n),f=o*Math.cos(e),s=o*Math.sin(e),h=2*Math.asin(Math.sqrt(on(r-t)+i*o*on(e-n))),p=1/Math.sin(h),g=h?function(n){var t=Math.sin(n*=h)*p,e=Math.sin(h-n)*p,r=e*l+t*f,i=e*c+t*s,o=e*u+t*a;return[Math.atan2(i,r)*Zo,Math.atan2(o,Math.sqrt(r*r+i*i))*Zo]}:function(){return[n*Zo,t*Zo]};return g.distance=h,g}function _e(){function n(n,i){var u=Math.sin(i*=Yo),o=Math.cos(i),a=xo((n*=Yo)-t),l=Math.cos(a);Ja+=Math.atan2(Math.sqrt((a=o*Math.sin(a))*a+(a=r*u-e*o*l)*a),e*u+r*o*l),t=n,e=u,r=o}var t,e,r;Ga.point=function(i,u){t=i*Yo,e=Math.sin(u*=Yo),r=Math.cos(u),Ga.point=n},Ga.lineEnd=function(){Ga.point=Ga.lineEnd=b}}function we(n,t){function e(t,e){var r=Math.cos(t),i=Math.cos(e),u=n(r*i);return[u*i*Math.sin(t),u*Math.sin(e)]}return e.invert=function(n,e){var r=Math.sqrt(n*n+e*e),i=t(r),u=Math.sin(i),o=Math.cos(i);return[Math.atan2(n*u,r*o),Math.asin(r&&e*u/r)]},e}function Se(n,t){function e(n,t){o>0?-Io+Uo>t&&(t=-Io+Uo):t>Io-Uo&&(t=Io-Uo);var e=o/Math.pow(i(t),u);return[e*Math.sin(u*n),o-e*Math.cos(u*n)]}var r=Math.cos(n),i=function(n){return Math.tan(Fo/4+n/2)},u=n===t?Math.sin(n):Math.log(r/Math.cos(t))/Math.log(i(t)/i(n)),o=r*Math.pow(i(n),u)/u;return u?(e.invert=function(n,t){var e=o-t,r=K(u)*Math.sqrt(n*n+e*e);return[Math.atan2(n,e)/u,2*Math.atan(Math.pow(o/r,1/u))-Io]},e):Ne}function ke(n,t){function e(n,t){var e=u-t;return[e*Math.sin(i*n),u-e*Math.cos(i*n)]}var r=Math.cos(n),i=n===t?Math.sin(n):(r-Math.cos(t))/(t-n),u=r/i+n;return xo(i)i;i++){for(;r>1&&Q(n[e[r-2]],n[e[r-1]],n[i])<=0;)--r;e[r++]=i}return e.slice(0,r)}function qe(n,t){return n[0]-t[0]||n[1]-t[1]}function Te(n,t,e){return(e[0]-t[0])*(n[1]-t[1])<(e[1]-t[1])*(n[0]-t[0])}function Re(n,t,e,r){var i=n[0],u=e[0],o=t[0]-i,a=r[0]-u,l=n[1],c=e[1],f=t[1]-l,s=r[1]-c,h=(a*(l-c)-s*(i-u))/(s*o-a*f);return[i+h*o,l+h*f]}function De(n){var t=n[0],e=n[n.length-1];return!(t[0]-e[0]||t[1]-e[1])}function Pe(){rr(this),this.edge=this.site=this.circle=null}function Ue(n){var t=cl.pop()||new Pe;return t.site=n,t}function je(n){Be(n),ol.remove(n),cl.push(n),rr(n)}function Fe(n){var t=n.circle,e=t.x,r=t.cy,i={x:e,y:r},u=n.P,o=n.N,a=[n];je(n);for(var l=u;l.circle&&xo(e-l.circle.x)f;++f)c=a[f],l=a[f-1],nr(c.edge,l.site,c.site,i);l=a[0],c=a[s-1],c.edge=Ke(l.site,c.site,null,i),$e(l),$e(c)}function He(n){for(var t,e,r,i,u=n.x,o=n.y,a=ol._;a;)if(r=Oe(a,o)-u,r>Uo)a=a.L;else{if(i=u-Ie(a,o),!(i>Uo)){r>-Uo?(t=a.P,e=a):i>-Uo?(t=a,e=a.N):t=e=a;break}if(!a.R){t=a;break}a=a.R}var l=Ue(n);if(ol.insert(t,l),t||e){if(t===e)return Be(t),e=Ue(t.site),ol.insert(l,e),l.edge=e.edge=Ke(t.site,l.site),$e(t),void $e(e);if(!e)return void(l.edge=Ke(t.site,l.site));Be(t),Be(e);var c=t.site,f=c.x,s=c.y,h=n.x-f,p=n.y-s,g=e.site,v=g.x-f,d=g.y-s,y=2*(h*d-p*v),m=h*h+p*p,M=v*v+d*d,x={x:(d*m-p*M)/y+f,y:(h*M-v*m)/y+s};nr(e.edge,c,g,x),l.edge=Ke(c,n,null,x),e.edge=Ke(n,g,null,x),$e(t),$e(e)}}function Oe(n,t){var e=n.site,r=e.x,i=e.y,u=i-t;if(!u)return r;var o=n.P;if(!o)return-(1/0);e=o.site;var a=e.x,l=e.y,c=l-t;if(!c)return a;var f=a-r,s=1/u-1/c,h=f/c;return s?(-h+Math.sqrt(h*h-2*s*(f*f/(-2*c)-l+c/2+i-u/2)))/s+r:(r+a)/2}function Ie(n,t){var e=n.N;if(e)return Oe(e,t);var r=n.site;return r.y===t?r.x:1/0}function Ye(n){this.site=n,this.edges=[]}function Ze(n){for(var t,e,r,i,u,o,a,l,c,f,s=n[0][0],h=n[1][0],p=n[0][1],g=n[1][1],v=ul,d=v.length;d--;)if(u=v[d],u&&u.prepare())for(a=u.edges,l=a.length,o=0;l>o;)f=a[o].end(),r=f.x,i=f.y,c=a[++o%l].start(),t=c.x,e=c.y,(xo(r-t)>Uo||xo(i-e)>Uo)&&(a.splice(o,0,new tr(Qe(u.site,f,xo(r-s)Uo?{x:s,y:xo(t-s)Uo?{x:xo(e-g)Uo?{x:h,y:xo(t-h)Uo?{x:xo(e-p)=-jo)){var p=l*l+c*c,g=f*f+s*s,v=(s*p-c*g)/h,d=(l*g-f*p)/h,s=d+a,y=fl.pop()||new Xe;y.arc=n,y.site=i,y.x=v+o,y.y=s+Math.sqrt(v*v+d*d),y.cy=s,n.circle=y;for(var m=null,M=ll._;M;)if(y.yd||d>=a)return;if(h>g){if(u){if(u.y>=c)return}else u={x:d,y:l};e={x:d,y:c}}else{if(u){if(u.yr||r>1)if(h>g){if(u){if(u.y>=c)return}else u={x:(l-i)/r,y:l};e={x:(c-i)/r,y:c}}else{if(u){if(u.yp){if(u){if(u.x>=a)return}else u={x:o,y:r*o+i};e={x:a,y:r*a+i}}else{if(u){if(u.xu||s>o||r>h||i>p)){if(g=n.point){var g,v=t-n.x,d=e-n.y,y=v*v+d*d;if(l>y){var m=Math.sqrt(l=y);r=t-m,i=e-m,u=t+m,o=e+m,a=g}}for(var M=n.nodes,x=.5*(f+h),b=.5*(s+p),_=t>=x,w=e>=b,S=w<<1|_,k=S+4;k>S;++S)if(n=M[3&S])switch(3&S){case 0:c(n,f,s,x,b);break;case 1:c(n,x,s,h,b);break;case 2:c(n,f,b,x,p);break;case 3:c(n,x,b,h,p)}}}(n,r,i,u,o),a}function vr(n,t){n=ao.rgb(n),t=ao.rgb(t);var e=n.r,r=n.g,i=n.b,u=t.r-e,o=t.g-r,a=t.b-i;return function(n){return"#"+bn(Math.round(e+u*n))+bn(Math.round(r+o*n))+bn(Math.round(i+a*n))}}function dr(n,t){var e,r={},i={};for(e in n)e in t?r[e]=Mr(n[e],t[e]):i[e]=n[e];for(e in t)e in n||(i[e]=t[e]);return function(n){for(e in r)i[e]=r[e](n);return i}}function yr(n,t){return n=+n,t=+t,function(e){return n*(1-e)+t*e}}function mr(n,t){var e,r,i,u=hl.lastIndex=pl.lastIndex=0,o=-1,a=[],l=[];for(n+="",t+="";(e=hl.exec(n))&&(r=pl.exec(t));)(i=r.index)>u&&(i=t.slice(u,i),a[o]?a[o]+=i:a[++o]=i),(e=e[0])===(r=r[0])?a[o]?a[o]+=r:a[++o]=r:(a[++o]=null,l.push({i:o,x:yr(e,r)})),u=pl.lastIndex;return ur;++r)a[(e=l[r]).i]=e.x(n);return a.join("")})}function Mr(n,t){for(var e,r=ao.interpolators.length;--r>=0&&!(e=ao.interpolators[r](n,t)););return e}function xr(n,t){var e,r=[],i=[],u=n.length,o=t.length,a=Math.min(n.length,t.length);for(e=0;a>e;++e)r.push(Mr(n[e],t[e]));for(;u>e;++e)i[e]=n[e];for(;o>e;++e)i[e]=t[e];return function(n){for(e=0;a>e;++e)i[e]=r[e](n);return i}}function br(n){return function(t){return 0>=t?0:t>=1?1:n(t)}}function _r(n){return function(t){return 1-n(1-t)}}function wr(n){return function(t){return.5*(.5>t?n(2*t):2-n(2-2*t))}}function Sr(n){return n*n}function kr(n){return n*n*n}function Nr(n){if(0>=n)return 0;if(n>=1)return 1;var t=n*n,e=t*n;return 4*(.5>n?e:3*(n-t)+e-.75)}function Er(n){return function(t){return Math.pow(t,n)}}function Ar(n){return 1-Math.cos(n*Io)}function Cr(n){return Math.pow(2,10*(n-1))}function zr(n){return 1-Math.sqrt(1-n*n)}function Lr(n,t){var e;return arguments.length<2&&(t=.45),arguments.length?e=t/Ho*Math.asin(1/n):(n=1,e=t/4),function(r){return 1+n*Math.pow(2,-10*r)*Math.sin((r-e)*Ho/t)}}function qr(n){return n||(n=1.70158),function(t){return t*t*((n+1)*t-n)}}function Tr(n){return 1/2.75>n?7.5625*n*n:2/2.75>n?7.5625*(n-=1.5/2.75)*n+.75:2.5/2.75>n?7.5625*(n-=2.25/2.75)*n+.9375:7.5625*(n-=2.625/2.75)*n+.984375}function Rr(n,t){n=ao.hcl(n),t=ao.hcl(t);var e=n.h,r=n.c,i=n.l,u=t.h-e,o=t.c-r,a=t.l-i;return isNaN(o)&&(o=0,r=isNaN(r)?t.c:r),isNaN(u)?(u=0,e=isNaN(e)?t.h:e):u>180?u-=360:-180>u&&(u+=360),function(n){return sn(e+u*n,r+o*n,i+a*n)+""}}function Dr(n,t){n=ao.hsl(n),t=ao.hsl(t);var e=n.h,r=n.s,i=n.l,u=t.h-e,o=t.s-r,a=t.l-i;return isNaN(o)&&(o=0,r=isNaN(r)?t.s:r),isNaN(u)?(u=0,e=isNaN(e)?t.h:e):u>180?u-=360:-180>u&&(u+=360),function(n){return cn(e+u*n,r+o*n,i+a*n)+""}}function Pr(n,t){n=ao.lab(n),t=ao.lab(t);var e=n.l,r=n.a,i=n.b,u=t.l-e,o=t.a-r,a=t.b-i;return function(n){return pn(e+u*n,r+o*n,i+a*n)+""}}function Ur(n,t){return t-=n,function(e){return Math.round(n+t*e)}}function jr(n){var t=[n.a,n.b],e=[n.c,n.d],r=Hr(t),i=Fr(t,e),u=Hr(Or(e,t,-i))||0;t[0]*e[1]180?t+=360:t-n>180&&(n+=360),r.push({i:e.push(Ir(e)+"rotate(",null,")")-2,x:yr(n,t)})):t&&e.push(Ir(e)+"rotate("+t+")")}function Vr(n,t,e,r){n!==t?r.push({i:e.push(Ir(e)+"skewX(",null,")")-2,x:yr(n,t)}):t&&e.push(Ir(e)+"skewX("+t+")")}function Xr(n,t,e,r){if(n[0]!==t[0]||n[1]!==t[1]){var i=e.push(Ir(e)+"scale(",null,",",null,")");r.push({i:i-4,x:yr(n[0],t[0])},{i:i-2,x:yr(n[1],t[1])})}else 1===t[0]&&1===t[1]||e.push(Ir(e)+"scale("+t+")")}function $r(n,t){var e=[],r=[];return n=ao.transform(n),t=ao.transform(t),Yr(n.translate,t.translate,e,r),Zr(n.rotate,t.rotate,e,r),Vr(n.skew,t.skew,e,r),Xr(n.scale,t.scale,e,r),n=t=null,function(n){for(var t,i=-1,u=r.length;++i=0;)e.push(i[r])}function oi(n,t){for(var e=[n],r=[];null!=(n=e.pop());)if(r.push(n),(u=n.children)&&(i=u.length))for(var i,u,o=-1;++oe;++e)(t=n[e][1])>i&&(r=e,i=t);return r}function yi(n){return n.reduce(mi,0)}function mi(n,t){return n+t[1]}function Mi(n,t){return xi(n,Math.ceil(Math.log(t.length)/Math.LN2+1))}function xi(n,t){for(var e=-1,r=+n[0],i=(n[1]-r)/t,u=[];++e<=t;)u[e]=i*e+r;return u}function bi(n){return[ao.min(n),ao.max(n)]}function _i(n,t){return n.value-t.value}function wi(n,t){var e=n._pack_next;n._pack_next=t,t._pack_prev=n,t._pack_next=e,e._pack_prev=t}function Si(n,t){n._pack_next=t,t._pack_prev=n}function ki(n,t){var e=t.x-n.x,r=t.y-n.y,i=n.r+t.r;return.999*i*i>e*e+r*r}function Ni(n){function t(n){f=Math.min(n.x-n.r,f),s=Math.max(n.x+n.r,s),h=Math.min(n.y-n.r,h),p=Math.max(n.y+n.r,p)}if((e=n.children)&&(c=e.length)){var e,r,i,u,o,a,l,c,f=1/0,s=-(1/0),h=1/0,p=-(1/0);if(e.forEach(Ei),r=e[0],r.x=-r.r,r.y=0,t(r),c>1&&(i=e[1],i.x=i.r,i.y=0,t(i),c>2))for(u=e[2],zi(r,i,u),t(u),wi(r,u),r._pack_prev=u,wi(u,i),i=r._pack_next,o=3;c>o;o++){zi(r,i,u=e[o]);var g=0,v=1,d=1;for(a=i._pack_next;a!==i;a=a._pack_next,v++)if(ki(a,u)){g=1;break}if(1==g)for(l=r._pack_prev;l!==a._pack_prev&&!ki(l,u);l=l._pack_prev,d++);g?(d>v||v==d&&i.ro;o++)u=e[o],u.x-=y,u.y-=m,M=Math.max(M,u.r+Math.sqrt(u.x*u.x+u.y*u.y));n.r=M,e.forEach(Ai)}}function Ei(n){n._pack_next=n._pack_prev=n}function Ai(n){delete n._pack_next,delete n._pack_prev}function Ci(n,t,e,r){var i=n.children;if(n.x=t+=r*n.x,n.y=e+=r*n.y,n.r*=r,i)for(var u=-1,o=i.length;++u=0;)t=i[u],t.z+=e,t.m+=e,e+=t.s+(r+=t.c)}function Pi(n,t,e){return n.a.parent===t.parent?n.a:e}function Ui(n){return 1+ao.max(n,function(n){return n.y})}function ji(n){return n.reduce(function(n,t){return n+t.x},0)/n.length}function Fi(n){var t=n.children;return t&&t.length?Fi(t[0]):n}function Hi(n){var t,e=n.children;return e&&(t=e.length)?Hi(e[t-1]):n}function Oi(n){return{x:n.x,y:n.y,dx:n.dx,dy:n.dy}}function Ii(n,t){var e=n.x+t[3],r=n.y+t[0],i=n.dx-t[1]-t[3],u=n.dy-t[0]-t[2];return 0>i&&(e+=i/2,i=0),0>u&&(r+=u/2,u=0),{x:e,y:r,dx:i,dy:u}}function Yi(n){var t=n[0],e=n[n.length-1];return e>t?[t,e]:[e,t]}function Zi(n){return n.rangeExtent?n.rangeExtent():Yi(n.range())}function Vi(n,t,e,r){var i=e(n[0],n[1]),u=r(t[0],t[1]);return function(n){return u(i(n))}}function Xi(n,t){var e,r=0,i=n.length-1,u=n[r],o=n[i];return u>o&&(e=r,r=i,i=e,e=u,u=o,o=e),n[r]=t.floor(u),n[i]=t.ceil(o),n}function $i(n){return n?{floor:function(t){return Math.floor(t/n)*n},ceil:function(t){return Math.ceil(t/n)*n}}:Sl}function Bi(n,t,e,r){var i=[],u=[],o=0,a=Math.min(n.length,t.length)-1;for(n[a]2?Bi:Vi,l=r?Wr:Br;return o=i(n,t,l,e),a=i(t,n,l,Mr),u}function u(n){return o(n)}var o,a;return u.invert=function(n){return a(n)},u.domain=function(t){return arguments.length?(n=t.map(Number),i()):n},u.range=function(n){return arguments.length?(t=n,i()):t},u.rangeRound=function(n){return u.range(n).interpolate(Ur)},u.clamp=function(n){return arguments.length?(r=n,i()):r},u.interpolate=function(n){return arguments.length?(e=n,i()):e},u.ticks=function(t){return Qi(n,t)},u.tickFormat=function(t,e){return nu(n,t,e)},u.nice=function(t){return Gi(n,t),i()},u.copy=function(){return Wi(n,t,e,r)},i()}function Ji(n,t){return ao.rebind(n,t,"range","rangeRound","interpolate","clamp")}function Gi(n,t){return Xi(n,$i(Ki(n,t)[2])),Xi(n,$i(Ki(n,t)[2])),n}function Ki(n,t){null==t&&(t=10);var e=Yi(n),r=e[1]-e[0],i=Math.pow(10,Math.floor(Math.log(r/t)/Math.LN10)),u=t/r*i;return.15>=u?i*=10:.35>=u?i*=5:.75>=u&&(i*=2),e[0]=Math.ceil(e[0]/i)*i,e[1]=Math.floor(e[1]/i)*i+.5*i,e[2]=i,e}function Qi(n,t){return ao.range.apply(ao,Ki(n,t))}function nu(n,t,e){var r=Ki(n,t);if(e){var i=ha.exec(e);if(i.shift(),"s"===i[8]){var u=ao.formatPrefix(Math.max(xo(r[0]),xo(r[1])));return i[7]||(i[7]="."+tu(u.scale(r[2]))),i[8]="f",e=ao.format(i.join("")),function(n){return e(u.scale(n))+u.symbol}}i[7]||(i[7]="."+eu(i[8],r)),e=i.join("")}else e=",."+tu(r[2])+"f";return ao.format(e)}function tu(n){return-Math.floor(Math.log(n)/Math.LN10+.01)}function eu(n,t){var e=tu(t[2]);return n in kl?Math.abs(e-tu(Math.max(xo(t[0]),xo(t[1]))))+ +("e"!==n):e-2*("%"===n)}function ru(n,t,e,r){function i(n){return(e?Math.log(0>n?0:n):-Math.log(n>0?0:-n))/Math.log(t)}function u(n){return e?Math.pow(t,n):-Math.pow(t,-n)}function o(t){return n(i(t))}return o.invert=function(t){return u(n.invert(t))},o.domain=function(t){return arguments.length?(e=t[0]>=0,n.domain((r=t.map(Number)).map(i)),o):r},o.base=function(e){return arguments.length?(t=+e,n.domain(r.map(i)),o):t},o.nice=function(){var t=Xi(r.map(i),e?Math:El);return n.domain(t),r=t.map(u),o},o.ticks=function(){var n=Yi(r),o=[],a=n[0],l=n[1],c=Math.floor(i(a)),f=Math.ceil(i(l)),s=t%1?2:t;if(isFinite(f-c)){if(e){for(;f>c;c++)for(var h=1;s>h;h++)o.push(u(c)*h);o.push(u(c))}else for(o.push(u(c));c++0;h--)o.push(u(c)*h);for(c=0;o[c]l;f--);o=o.slice(c,f)}return o},o.tickFormat=function(n,e){if(!arguments.length)return Nl;arguments.length<2?e=Nl:"function"!=typeof e&&(e=ao.format(e));var r=Math.max(1,t*n/o.ticks().length);return function(n){var o=n/u(Math.round(i(n)));return t-.5>o*t&&(o*=t),r>=o?e(n):""}},o.copy=function(){return ru(n.copy(),t,e,r)},Ji(o,n)}function iu(n,t,e){function r(t){return n(i(t))}var i=uu(t),u=uu(1/t);return r.invert=function(t){return u(n.invert(t))},r.domain=function(t){return arguments.length?(n.domain((e=t.map(Number)).map(i)),r):e},r.ticks=function(n){return Qi(e,n)},r.tickFormat=function(n,t){return nu(e,n,t)},r.nice=function(n){return r.domain(Gi(e,n))},r.exponent=function(o){return arguments.length?(i=uu(t=o),u=uu(1/t),n.domain(e.map(i)),r):t},r.copy=function(){return iu(n.copy(),t,e)},Ji(r,n)}function uu(n){return function(t){return 0>t?-Math.pow(-t,n):Math.pow(t,n)}}function ou(n,t){function e(e){return u[((i.get(e)||("range"===t.t?i.set(e,n.push(e)):NaN))-1)%u.length]}function r(t,e){return ao.range(n.length).map(function(n){return t+e*n})}var i,u,o;return e.domain=function(r){if(!arguments.length)return n;n=[],i=new c;for(var u,o=-1,a=r.length;++oe?[NaN,NaN]:[e>0?a[e-1]:n[0],et?NaN:t/u+n,[t,t+1/u]},r.copy=function(){return lu(n,t,e)},i()}function cu(n,t){function e(e){return e>=e?t[ao.bisect(n,e)]:void 0}return e.domain=function(t){return arguments.length?(n=t,e):n},e.range=function(n){return arguments.length?(t=n,e):t},e.invertExtent=function(e){return e=t.indexOf(e),[n[e-1],n[e]]},e.copy=function(){return cu(n,t)},e}function fu(n){function t(n){return+n}return t.invert=t,t.domain=t.range=function(e){return arguments.length?(n=e.map(t),t):n},t.ticks=function(t){return Qi(n,t)},t.tickFormat=function(t,e){return nu(n,t,e)},t.copy=function(){return fu(n)},t}function su(){return 0}function hu(n){return n.innerRadius}function pu(n){return n.outerRadius}function gu(n){return n.startAngle}function vu(n){return n.endAngle}function du(n){return n&&n.padAngle}function yu(n,t,e,r){return(n-e)*t-(t-r)*n>0?0:1}function mu(n,t,e,r,i){var u=n[0]-t[0],o=n[1]-t[1],a=(i?r:-r)/Math.sqrt(u*u+o*o),l=a*o,c=-a*u,f=n[0]+l,s=n[1]+c,h=t[0]+l,p=t[1]+c,g=(f+h)/2,v=(s+p)/2,d=h-f,y=p-s,m=d*d+y*y,M=e-r,x=f*p-h*s,b=(0>y?-1:1)*Math.sqrt(Math.max(0,M*M*m-x*x)),_=(x*y-d*b)/m,w=(-x*d-y*b)/m,S=(x*y+d*b)/m,k=(-x*d+y*b)/m,N=_-g,E=w-v,A=S-g,C=k-v;return N*N+E*E>A*A+C*C&&(_=S,w=k),[[_-l,w-c],[_*e/M,w*e/M]]}function Mu(n){function t(t){function o(){c.push("M",u(n(f),a))}for(var l,c=[],f=[],s=-1,h=t.length,p=En(e),g=En(r);++s1?n.join("L"):n+"Z"}function bu(n){return n.join("L")+"Z"}function _u(n){for(var t=0,e=n.length,r=n[0],i=[r[0],",",r[1]];++t1&&i.push("H",r[0]),i.join("")}function wu(n){for(var t=0,e=n.length,r=n[0],i=[r[0],",",r[1]];++t1){a=t[1],u=n[l],l++,r+="C"+(i[0]+o[0])+","+(i[1]+o[1])+","+(u[0]-a[0])+","+(u[1]-a[1])+","+u[0]+","+u[1];for(var c=2;c9&&(i=3*t/Math.sqrt(i),o[a]=i*e,o[a+1]=i*r));for(a=-1;++a<=l;)i=(n[Math.min(l,a+1)][0]-n[Math.max(0,a-1)][0])/(6*(1+o[a]*o[a])),u.push([i||0,o[a]*i||0]);return u}function Fu(n){return n.length<3?xu(n):n[0]+Au(n,ju(n))}function Hu(n){for(var t,e,r,i=-1,u=n.length;++i=t?o(n-t):void(f.c=o)}function o(e){var i=g.active,u=g[i];u&&(u.timer.c=null,u.timer.t=NaN,--g.count,delete g[i],u.event&&u.event.interrupt.call(n,n.__data__,u.index));for(var o in g)if(r>+o){var c=g[o];c.timer.c=null,c.timer.t=NaN,--g.count,delete g[o]}f.c=a,qn(function(){return f.c&&a(e||1)&&(f.c=null,f.t=NaN),1},0,l),g.active=r,v.event&&v.event.start.call(n,n.__data__,t),p=[],v.tween.forEach(function(e,r){(r=r.call(n,n.__data__,t))&&p.push(r)}),h=v.ease,s=v.duration}function a(i){for(var u=i/s,o=h(u),a=p.length;a>0;)p[--a].call(n,o);return u>=1?(v.event&&v.event.end.call(n,n.__data__,t),--g.count?delete g[r]:delete n[e],1):void 0}var l,f,s,h,p,g=n[e]||(n[e]={active:0,count:0}),v=g[r];v||(l=i.time,f=qn(u,0,l),v=g[r]={tween:new c,time:l,timer:f,delay:i.delay,duration:i.duration,ease:i.ease,index:t},i=null,++g.count)}function no(n,t,e){n.attr("transform",function(n){var r=t(n);return"translate("+(isFinite(r)?r:e(n))+",0)"})}function to(n,t,e){n.attr("transform",function(n){var r=t(n);return"translate(0,"+(isFinite(r)?r:e(n))+")"})}function eo(n){return n.toISOString()}function ro(n,t,e){function r(t){return n(t)}function i(n,e){var r=n[1]-n[0],i=r/e,u=ao.bisect(Kl,i);return u==Kl.length?[t.year,Ki(n.map(function(n){return n/31536e6}),e)[2]]:u?t[i/Kl[u-1]1?{floor:function(t){for(;e(t=n.floor(t));)t=io(t-1);return t},ceil:function(t){for(;e(t=n.ceil(t));)t=io(+t+1);return t}}:n))},r.ticks=function(n,t){var e=Yi(r.domain()),u=null==n?i(e,10):"number"==typeof n?i(e,n):!n.range&&[{range:n},t];return u&&(n=u[0],t=u[1]),n.range(e[0],io(+e[1]+1),1>t?1:t)},r.tickFormat=function(){return e},r.copy=function(){return ro(n.copy(),t,e)},Ji(r,n)}function io(n){return new Date(n)}function uo(n){return JSON.parse(n.responseText)}function oo(n){var t=fo.createRange();return t.selectNode(fo.body),t.createContextualFragment(n.responseText)}var ao={version:"3.5.17"},lo=[].slice,co=function(n){return lo.call(n)},fo=this.document;if(fo)try{co(fo.documentElement.childNodes)[0].nodeType}catch(so){co=function(n){for(var t=n.length,e=new Array(t);t--;)e[t]=n[t];return e}}if(Date.now||(Date.now=function(){return+new Date}),fo)try{fo.createElement("DIV").style.setProperty("opacity",0,"")}catch(ho){var po=this.Element.prototype,go=po.setAttribute,vo=po.setAttributeNS,yo=this.CSSStyleDeclaration.prototype,mo=yo.setProperty;po.setAttribute=function(n,t){go.call(this,n,t+"")},po.setAttributeNS=function(n,t,e){vo.call(this,n,t,e+"")},yo.setProperty=function(n,t,e){mo.call(this,n,t+"",e)}}ao.ascending=e,ao.descending=function(n,t){return n>t?-1:t>n?1:t>=n?0:NaN},ao.min=function(n,t){var e,r,i=-1,u=n.length;if(1===arguments.length){for(;++i=r){e=r;break}for(;++ir&&(e=r)}else{for(;++i=r){e=r;break}for(;++ir&&(e=r)}return e},ao.max=function(n,t){var e,r,i=-1,u=n.length;if(1===arguments.length){for(;++i=r){e=r;break}for(;++ie&&(e=r)}else{for(;++i=r){e=r;break}for(;++ie&&(e=r)}return e},ao.extent=function(n,t){var e,r,i,u=-1,o=n.length;if(1===arguments.length){for(;++u=r){e=i=r;break}for(;++ur&&(e=r),r>i&&(i=r))}else{for(;++u=r){e=i=r;break}for(;++ur&&(e=r),r>i&&(i=r))}return[e,i]},ao.sum=function(n,t){var e,r=0,u=n.length,o=-1;if(1===arguments.length)for(;++o1?l/(f-1):void 0},ao.deviation=function(){var n=ao.variance.apply(this,arguments);return n?Math.sqrt(n):n};var Mo=u(e);ao.bisectLeft=Mo.left,ao.bisect=ao.bisectRight=Mo.right,ao.bisector=function(n){return u(1===n.length?function(t,r){return e(n(t),r)}:n)},ao.shuffle=function(n,t,e){(u=arguments.length)<3&&(e=n.length,2>u&&(t=0));for(var r,i,u=e-t;u;)i=Math.random()*u--|0,r=n[u+t],n[u+t]=n[i+t],n[i+t]=r;return n},ao.permute=function(n,t){for(var e=t.length,r=new Array(e);e--;)r[e]=n[t[e]];return r},ao.pairs=function(n){for(var t,e=0,r=n.length-1,i=n[0],u=new Array(0>r?0:r);r>e;)u[e]=[t=i,i=n[++e]];return u},ao.transpose=function(n){if(!(i=n.length))return[];for(var t=-1,e=ao.min(n,o),r=new Array(e);++t=0;)for(r=n[i],t=r.length;--t>=0;)e[--o]=r[t];return e};var xo=Math.abs;ao.range=function(n,t,e){if(arguments.length<3&&(e=1,arguments.length<2&&(t=n,n=0)),(t-n)/e===1/0)throw new Error("infinite range");var r,i=[],u=a(xo(e)),o=-1;if(n*=u,t*=u,e*=u,0>e)for(;(r=n+e*++o)>t;)i.push(r/u);else for(;(r=n+e*++o)=u.length)return r?r.call(i,o):e?o.sort(e):o;for(var l,f,s,h,p=-1,g=o.length,v=u[a++],d=new c;++p=u.length)return n;var r=[],i=o[e++];return n.forEach(function(n,i){r.push({key:n,values:t(i,e)})}),i?r.sort(function(n,t){return i(n.key,t.key)}):r}var e,r,i={},u=[],o=[];return i.map=function(t,e){return n(e,t,0)},i.entries=function(e){return t(n(ao.map,e,0),0)},i.key=function(n){return u.push(n),i},i.sortKeys=function(n){return o[u.length-1]=n,i},i.sortValues=function(n){return e=n,i},i.rollup=function(n){return r=n,i},i},ao.set=function(n){var t=new y;if(n)for(var e=0,r=n.length;r>e;++e)t.add(n[e]);return t},l(y,{has:h,add:function(n){return this._[f(n+="")]=!0,n},remove:p,values:g,size:v,empty:d,forEach:function(n){for(var t in this._)n.call(this,s(t))}}),ao.behavior={},ao.rebind=function(n,t){for(var e,r=1,i=arguments.length;++r=0&&(r=n.slice(e+1),n=n.slice(0,e)),n)return arguments.length<2?this[n].on(r):this[n].on(r,t);if(2===arguments.length){if(null==t)for(n in this)this.hasOwnProperty(n)&&this[n].on(r,null);return this}},ao.event=null,ao.requote=function(n){return n.replace(So,"\\$&")};var So=/[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g,ko={}.__proto__?function(n,t){n.__proto__=t}:function(n,t){for(var e in t)n[e]=t[e]},No=function(n,t){return t.querySelector(n)},Eo=function(n,t){return t.querySelectorAll(n)},Ao=function(n,t){var e=n.matches||n[x(n,"matchesSelector")];return(Ao=function(n,t){return e.call(n,t)})(n,t)};"function"==typeof Sizzle&&(No=function(n,t){return Sizzle(n,t)[0]||null},Eo=Sizzle,Ao=Sizzle.matchesSelector),ao.selection=function(){return ao.select(fo.documentElement)};var Co=ao.selection.prototype=[];Co.select=function(n){var t,e,r,i,u=[];n=A(n);for(var o=-1,a=this.length;++o=0&&"xmlns"!==(e=n.slice(0,t))&&(n=n.slice(t+1)),Lo.hasOwnProperty(e)?{space:Lo[e],local:n}:n}},Co.attr=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node();return n=ao.ns.qualify(n),n.local?e.getAttributeNS(n.space,n.local):e.getAttribute(n)}for(t in n)this.each(z(t,n[t]));return this}return this.each(z(n,t))},Co.classed=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node(),r=(n=T(n)).length,i=-1;if(t=e.classList){for(;++ii){if("string"!=typeof n){2>i&&(e="");for(r in n)this.each(P(r,n[r],e));return this}if(2>i){var u=this.node();return t(u).getComputedStyle(u,null).getPropertyValue(n)}r=""}return this.each(P(n,e,r))},Co.property=function(n,t){if(arguments.length<2){if("string"==typeof n)return this.node()[n];for(t in n)this.each(U(t,n[t]));return this}return this.each(U(n,t))},Co.text=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.textContent=null==t?"":t}:null==n?function(){this.textContent=""}:function(){this.textContent=n}):this.node().textContent},Co.html=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.innerHTML=null==t?"":t}:null==n?function(){this.innerHTML=""}:function(){this.innerHTML=n}):this.node().innerHTML},Co.append=function(n){return n=j(n),this.select(function(){return this.appendChild(n.apply(this,arguments))})},Co.insert=function(n,t){return n=j(n),t=A(t),this.select(function(){return this.insertBefore(n.apply(this,arguments),t.apply(this,arguments)||null)})},Co.remove=function(){return this.each(F)},Co.data=function(n,t){function e(n,e){var r,i,u,o=n.length,s=e.length,h=Math.min(o,s),p=new Array(s),g=new Array(s),v=new Array(o);if(t){var d,y=new c,m=new Array(o);for(r=-1;++rr;++r)g[r]=H(e[r]);for(;o>r;++r)v[r]=n[r]}g.update=p,g.parentNode=p.parentNode=v.parentNode=n.parentNode,a.push(g),l.push(p),f.push(v)}var r,i,u=-1,o=this.length;if(!arguments.length){for(n=new Array(o=(r=this[0]).length);++uu;u++){i.push(t=[]),t.parentNode=(e=this[u]).parentNode;for(var a=0,l=e.length;l>a;a++)(r=e[a])&&n.call(r,r.__data__,a,u)&&t.push(r)}return E(i)},Co.order=function(){for(var n=-1,t=this.length;++n=0;)(e=r[i])&&(u&&u!==e.nextSibling&&u.parentNode.insertBefore(e,u),u=e);return this},Co.sort=function(n){n=I.apply(this,arguments);for(var t=-1,e=this.length;++tn;n++)for(var e=this[n],r=0,i=e.length;i>r;r++){var u=e[r];if(u)return u}return null},Co.size=function(){var n=0;return Y(this,function(){++n}),n};var qo=[];ao.selection.enter=Z,ao.selection.enter.prototype=qo,qo.append=Co.append,qo.empty=Co.empty,qo.node=Co.node,qo.call=Co.call,qo.size=Co.size,qo.select=function(n){for(var t,e,r,i,u,o=[],a=-1,l=this.length;++ar){if("string"!=typeof n){2>r&&(t=!1);for(e in n)this.each(X(e,n[e],t));return this}if(2>r)return(r=this.node()["__on"+n])&&r._;e=!1}return this.each(X(n,t,e))};var To=ao.map({mouseenter:"mouseover",mouseleave:"mouseout"});fo&&To.forEach(function(n){"on"+n in fo&&To.remove(n)});var Ro,Do=0;ao.mouse=function(n){return J(n,k())};var Po=this.navigator&&/WebKit/.test(this.navigator.userAgent)?-1:0;ao.touch=function(n,t,e){if(arguments.length<3&&(e=t,t=k().changedTouches),t)for(var r,i=0,u=t.length;u>i;++i)if((r=t[i]).identifier===e)return J(n,r)},ao.behavior.drag=function(){function n(){this.on("mousedown.drag",u).on("touchstart.drag",o)}function e(n,t,e,u,o){return function(){function a(){var n,e,r=t(h,v);r&&(n=r[0]-M[0],e=r[1]-M[1],g|=n|e,M=r,p({type:"drag",x:r[0]+c[0],y:r[1]+c[1],dx:n,dy:e}))}function l(){t(h,v)&&(y.on(u+d,null).on(o+d,null),m(g),p({type:"dragend"}))}var c,f=this,s=ao.event.target.correspondingElement||ao.event.target,h=f.parentNode,p=r.of(f,arguments),g=0,v=n(),d=".drag"+(null==v?"":"-"+v),y=ao.select(e(s)).on(u+d,a).on(o+d,l),m=W(s),M=t(h,v);i?(c=i.apply(f,arguments),c=[c.x-M[0],c.y-M[1]]):c=[0,0],p({type:"dragstart"})}}var r=N(n,"drag","dragstart","dragend"),i=null,u=e(b,ao.mouse,t,"mousemove","mouseup"),o=e(G,ao.touch,m,"touchmove","touchend");return n.origin=function(t){return arguments.length?(i=t,n):i},ao.rebind(n,r,"on")},ao.touches=function(n,t){return arguments.length<2&&(t=k().touches),t?co(t).map(function(t){var e=J(n,t);return e.identifier=t.identifier,e}):[]};var Uo=1e-6,jo=Uo*Uo,Fo=Math.PI,Ho=2*Fo,Oo=Ho-Uo,Io=Fo/2,Yo=Fo/180,Zo=180/Fo,Vo=Math.SQRT2,Xo=2,$o=4;ao.interpolateZoom=function(n,t){var e,r,i=n[0],u=n[1],o=n[2],a=t[0],l=t[1],c=t[2],f=a-i,s=l-u,h=f*f+s*s;if(jo>h)r=Math.log(c/o)/Vo,e=function(n){return[i+n*f,u+n*s,o*Math.exp(Vo*n*r)]};else{var p=Math.sqrt(h),g=(c*c-o*o+$o*h)/(2*o*Xo*p),v=(c*c-o*o-$o*h)/(2*c*Xo*p),d=Math.log(Math.sqrt(g*g+1)-g),y=Math.log(Math.sqrt(v*v+1)-v);r=(y-d)/Vo,e=function(n){var t=n*r,e=rn(d),a=o/(Xo*p)*(e*un(Vo*t+d)-en(d));return[i+a*f,u+a*s,o*e/rn(Vo*t+d)]}}return e.duration=1e3*r,e},ao.behavior.zoom=function(){function n(n){n.on(L,s).on(Wo+".zoom",p).on("dblclick.zoom",g).on(R,h)}function e(n){return[(n[0]-k.x)/k.k,(n[1]-k.y)/k.k]}function r(n){return[n[0]*k.k+k.x,n[1]*k.k+k.y]}function i(n){k.k=Math.max(A[0],Math.min(A[1],n))}function u(n,t){t=r(t),k.x+=n[0]-t[0],k.y+=n[1]-t[1]}function o(t,e,r,o){t.__chart__={x:k.x,y:k.y,k:k.k},i(Math.pow(2,o)),u(d=e,r),t=ao.select(t),C>0&&(t=t.transition().duration(C)),t.call(n.event)}function a(){b&&b.domain(x.range().map(function(n){return(n-k.x)/k.k}).map(x.invert)),w&&w.domain(_.range().map(function(n){return(n-k.y)/k.k}).map(_.invert))}function l(n){z++||n({type:"zoomstart"})}function c(n){a(),n({type:"zoom",scale:k.k,translate:[k.x,k.y]})}function f(n){--z||(n({type:"zoomend"}),d=null)}function s(){function n(){a=1,u(ao.mouse(i),h),c(o)}function r(){s.on(q,null).on(T,null),p(a),f(o)}var i=this,o=D.of(i,arguments),a=0,s=ao.select(t(i)).on(q,n).on(T,r),h=e(ao.mouse(i)),p=W(i);Il.call(i),l(o)}function h(){function n(){var n=ao.touches(g);return p=k.k,n.forEach(function(n){n.identifier in d&&(d[n.identifier]=e(n))}),n}function t(){var t=ao.event.target;ao.select(t).on(x,r).on(b,a),_.push(t);for(var e=ao.event.changedTouches,i=0,u=e.length;u>i;++i)d[e[i].identifier]=null;var l=n(),c=Date.now();if(1===l.length){if(500>c-M){var f=l[0];o(g,f,d[f.identifier],Math.floor(Math.log(k.k)/Math.LN2)+1),S()}M=c}else if(l.length>1){var f=l[0],s=l[1],h=f[0]-s[0],p=f[1]-s[1];y=h*h+p*p}}function r(){var n,t,e,r,o=ao.touches(g);Il.call(g);for(var a=0,l=o.length;l>a;++a,r=null)if(e=o[a],r=d[e.identifier]){if(t)break;n=e,t=r}if(r){var f=(f=e[0]-n[0])*f+(f=e[1]-n[1])*f,s=y&&Math.sqrt(f/y);n=[(n[0]+e[0])/2,(n[1]+e[1])/2],t=[(t[0]+r[0])/2,(t[1]+r[1])/2],i(s*p)}M=null,u(n,t),c(v)}function a(){if(ao.event.touches.length){for(var t=ao.event.changedTouches,e=0,r=t.length;r>e;++e)delete d[t[e].identifier];for(var i in d)return void n()}ao.selectAll(_).on(m,null),w.on(L,s).on(R,h),N(),f(v)}var p,g=this,v=D.of(g,arguments),d={},y=0,m=".zoom-"+ao.event.changedTouches[0].identifier,x="touchmove"+m,b="touchend"+m,_=[],w=ao.select(g),N=W(g);t(),l(v),w.on(L,null).on(R,t)}function p(){var n=D.of(this,arguments);m?clearTimeout(m):(Il.call(this),v=e(d=y||ao.mouse(this)),l(n)),m=setTimeout(function(){m=null,f(n)},50),S(),i(Math.pow(2,.002*Bo())*k.k),u(d,v),c(n)}function g(){var n=ao.mouse(this),t=Math.log(k.k)/Math.LN2;o(this,n,e(n),ao.event.shiftKey?Math.ceil(t)-1:Math.floor(t)+1)}var v,d,y,m,M,x,b,_,w,k={x:0,y:0,k:1},E=[960,500],A=Jo,C=250,z=0,L="mousedown.zoom",q="mousemove.zoom",T="mouseup.zoom",R="touchstart.zoom",D=N(n,"zoomstart","zoom","zoomend");return Wo||(Wo="onwheel"in fo?(Bo=function(){return-ao.event.deltaY*(ao.event.deltaMode?120:1)},"wheel"):"onmousewheel"in fo?(Bo=function(){return ao.event.wheelDelta},"mousewheel"):(Bo=function(){return-ao.event.detail},"MozMousePixelScroll")),n.event=function(n){n.each(function(){var n=D.of(this,arguments),t=k;Hl?ao.select(this).transition().each("start.zoom",function(){k=this.__chart__||{x:0,y:0,k:1},l(n)}).tween("zoom:zoom",function(){var e=E[0],r=E[1],i=d?d[0]:e/2,u=d?d[1]:r/2,o=ao.interpolateZoom([(i-k.x)/k.k,(u-k.y)/k.k,e/k.k],[(i-t.x)/t.k,(u-t.y)/t.k,e/t.k]);return function(t){var r=o(t),a=e/r[2];this.__chart__=k={x:i-r[0]*a,y:u-r[1]*a,k:a},c(n)}}).each("interrupt.zoom",function(){f(n)}).each("end.zoom",function(){f(n)}):(this.__chart__=k,l(n),c(n),f(n))})},n.translate=function(t){return arguments.length?(k={x:+t[0],y:+t[1],k:k.k},a(),n):[k.x,k.y]},n.scale=function(t){return arguments.length?(k={x:k.x,y:k.y,k:null},i(+t),a(),n):k.k},n.scaleExtent=function(t){return arguments.length?(A=null==t?Jo:[+t[0],+t[1]],n):A},n.center=function(t){return arguments.length?(y=t&&[+t[0],+t[1]],n):y},n.size=function(t){return arguments.length?(E=t&&[+t[0],+t[1]],n):E},n.duration=function(t){return arguments.length?(C=+t,n):C},n.x=function(t){return arguments.length?(b=t,x=t.copy(),k={x:0,y:0,k:1},n):b},n.y=function(t){return arguments.length?(w=t,_=t.copy(),k={x:0,y:0,k:1},n):w},ao.rebind(n,D,"on")};var Bo,Wo,Jo=[0,1/0];ao.color=an,an.prototype.toString=function(){return this.rgb()+""},ao.hsl=ln;var Go=ln.prototype=new an;Go.brighter=function(n){return n=Math.pow(.7,arguments.length?n:1),new ln(this.h,this.s,this.l/n)},Go.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),new ln(this.h,this.s,n*this.l)},Go.rgb=function(){return cn(this.h,this.s,this.l)},ao.hcl=fn;var Ko=fn.prototype=new an;Ko.brighter=function(n){return new fn(this.h,this.c,Math.min(100,this.l+Qo*(arguments.length?n:1)))},Ko.darker=function(n){return new fn(this.h,this.c,Math.max(0,this.l-Qo*(arguments.length?n:1)))},Ko.rgb=function(){return sn(this.h,this.c,this.l).rgb()},ao.lab=hn;var Qo=18,na=.95047,ta=1,ea=1.08883,ra=hn.prototype=new an;ra.brighter=function(n){return new hn(Math.min(100,this.l+Qo*(arguments.length?n:1)),this.a,this.b)},ra.darker=function(n){return new hn(Math.max(0,this.l-Qo*(arguments.length?n:1)),this.a,this.b)},ra.rgb=function(){return pn(this.l,this.a,this.b)},ao.rgb=mn;var ia=mn.prototype=new an;ia.brighter=function(n){n=Math.pow(.7,arguments.length?n:1);var t=this.r,e=this.g,r=this.b,i=30;return t||e||r?(t&&i>t&&(t=i),e&&i>e&&(e=i),r&&i>r&&(r=i),new mn(Math.min(255,t/n),Math.min(255,e/n),Math.min(255,r/n))):new mn(i,i,i)},ia.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),new mn(n*this.r,n*this.g,n*this.b)},ia.hsl=function(){return wn(this.r,this.g,this.b)},ia.toString=function(){return"#"+bn(this.r)+bn(this.g)+bn(this.b)};var ua=ao.map({aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,rebeccapurple:6697881,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074});ua.forEach(function(n,t){ua.set(n,Mn(t))}),ao.functor=En,ao.xhr=An(m),ao.dsv=function(n,t){function e(n,e,u){arguments.length<3&&(u=e,e=null);var o=Cn(n,t,null==e?r:i(e),u);return o.row=function(n){return arguments.length?o.response(null==(e=n)?r:i(n)):e},o}function r(n){return e.parse(n.responseText)}function i(n){return function(t){return e.parse(t.responseText,n)}}function u(t){return t.map(o).join(n)}function o(n){return a.test(n)?'"'+n.replace(/\"/g,'""')+'"':n}var a=new RegExp('["'+n+"\n]"),l=n.charCodeAt(0);return e.parse=function(n,t){var r;return e.parseRows(n,function(n,e){if(r)return r(n,e-1);var i=new Function("d","return {"+n.map(function(n,t){return JSON.stringify(n)+": d["+t+"]"}).join(",")+"}");r=t?function(n,e){return t(i(n),e)}:i})},e.parseRows=function(n,t){function e(){if(f>=c)return o;if(i)return i=!1,u;var t=f;if(34===n.charCodeAt(t)){for(var e=t;e++f;){var r=n.charCodeAt(f++),a=1;if(10===r)i=!0;else if(13===r)i=!0,10===n.charCodeAt(f)&&(++f,++a);else if(r!==l)continue;return n.slice(t,f-a)}return n.slice(t)}for(var r,i,u={},o={},a=[],c=n.length,f=0,s=0;(r=e())!==o;){for(var h=[];r!==u&&r!==o;)h.push(r),r=e();t&&null==(h=t(h,s++))||a.push(h)}return a},e.format=function(t){if(Array.isArray(t[0]))return e.formatRows(t);var r=new y,i=[];return t.forEach(function(n){for(var t in n)r.has(t)||i.push(r.add(t))}),[i.map(o).join(n)].concat(t.map(function(t){return i.map(function(n){return o(t[n])}).join(n)})).join("\n")},e.formatRows=function(n){return n.map(u).join("\n")},e},ao.csv=ao.dsv(",","text/csv"),ao.tsv=ao.dsv(" ","text/tab-separated-values");var oa,aa,la,ca,fa=this[x(this,"requestAnimationFrame")]||function(n){setTimeout(n,17)};ao.timer=function(){qn.apply(this,arguments)},ao.timer.flush=function(){Rn(),Dn()},ao.round=function(n,t){return t?Math.round(n*(t=Math.pow(10,t)))/t:Math.round(n)};var sa=["y","z","a","f","p","n","\xb5","m","","k","M","G","T","P","E","Z","Y"].map(Un);ao.formatPrefix=function(n,t){var e=0;return(n=+n)&&(0>n&&(n*=-1),t&&(n=ao.round(n,Pn(n,t))),e=1+Math.floor(1e-12+Math.log(n)/Math.LN10),e=Math.max(-24,Math.min(24,3*Math.floor((e-1)/3)))),sa[8+e/3]};var ha=/(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i,pa=ao.map({b:function(n){return n.toString(2)},c:function(n){return String.fromCharCode(n)},o:function(n){return n.toString(8)},x:function(n){return n.toString(16)},X:function(n){return n.toString(16).toUpperCase()},g:function(n,t){return n.toPrecision(t)},e:function(n,t){return n.toExponential(t)},f:function(n,t){return n.toFixed(t)},r:function(n,t){return(n=ao.round(n,Pn(n,t))).toFixed(Math.max(0,Math.min(20,Pn(n*(1+1e-15),t))))}}),ga=ao.time={},va=Date;Hn.prototype={getDate:function(){return this._.getUTCDate()},getDay:function(){return this._.getUTCDay()},getFullYear:function(){return this._.getUTCFullYear()},getHours:function(){return this._.getUTCHours()},getMilliseconds:function(){return this._.getUTCMilliseconds()},getMinutes:function(){return this._.getUTCMinutes()},getMonth:function(){return this._.getUTCMonth()},getSeconds:function(){return this._.getUTCSeconds()},getTime:function(){return this._.getTime()},getTimezoneOffset:function(){return 0},valueOf:function(){return this._.valueOf()},setDate:function(){da.setUTCDate.apply(this._,arguments)},setDay:function(){da.setUTCDay.apply(this._,arguments)},setFullYear:function(){da.setUTCFullYear.apply(this._,arguments)},setHours:function(){da.setUTCHours.apply(this._,arguments)},setMilliseconds:function(){da.setUTCMilliseconds.apply(this._,arguments)},setMinutes:function(){da.setUTCMinutes.apply(this._,arguments)},setMonth:function(){da.setUTCMonth.apply(this._,arguments)},setSeconds:function(){da.setUTCSeconds.apply(this._,arguments)},setTime:function(){da.setTime.apply(this._,arguments)}};var da=Date.prototype;ga.year=On(function(n){return n=ga.day(n),n.setMonth(0,1),n},function(n,t){n.setFullYear(n.getFullYear()+t)},function(n){return n.getFullYear()}),ga.years=ga.year.range,ga.years.utc=ga.year.utc.range,ga.day=On(function(n){var t=new va(2e3,0);return t.setFullYear(n.getFullYear(),n.getMonth(),n.getDate()),t},function(n,t){n.setDate(n.getDate()+t)},function(n){return n.getDate()-1}),ga.days=ga.day.range,ga.days.utc=ga.day.utc.range,ga.dayOfYear=function(n){var t=ga.year(n);return Math.floor((n-t-6e4*(n.getTimezoneOffset()-t.getTimezoneOffset()))/864e5)},["sunday","monday","tuesday","wednesday","thursday","friday","saturday"].forEach(function(n,t){t=7-t;var e=ga[n]=On(function(n){return(n=ga.day(n)).setDate(n.getDate()-(n.getDay()+t)%7),n},function(n,t){n.setDate(n.getDate()+7*Math.floor(t))},function(n){var e=ga.year(n).getDay();return Math.floor((ga.dayOfYear(n)+(e+t)%7)/7)-(e!==t)});ga[n+"s"]=e.range,ga[n+"s"].utc=e.utc.range,ga[n+"OfYear"]=function(n){var e=ga.year(n).getDay();return Math.floor((ga.dayOfYear(n)+(e+t)%7)/7)}}),ga.week=ga.sunday,ga.weeks=ga.sunday.range,ga.weeks.utc=ga.sunday.utc.range,ga.weekOfYear=ga.sundayOfYear;var ya={"-":"",_:" ",0:"0"},ma=/^\s*\d+/,Ma=/^%/;ao.locale=function(n){return{numberFormat:jn(n),timeFormat:Yn(n)}};var xa=ao.locale({decimal:".",thousands:",",grouping:[3],currency:["$",""],dateTime:"%a %b %e %X %Y",date:"%m/%d/%Y",time:"%H:%M:%S",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"], -shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});ao.format=xa.numberFormat,ao.geo={},ft.prototype={s:0,t:0,add:function(n){st(n,this.t,ba),st(ba.s,this.s,this),this.s?this.t+=ba.t:this.s=ba.t},reset:function(){this.s=this.t=0},valueOf:function(){return this.s}};var ba=new ft;ao.geo.stream=function(n,t){n&&_a.hasOwnProperty(n.type)?_a[n.type](n,t):ht(n,t)};var _a={Feature:function(n,t){ht(n.geometry,t)},FeatureCollection:function(n,t){for(var e=n.features,r=-1,i=e.length;++rn?4*Fo+n:n,Na.lineStart=Na.lineEnd=Na.point=b}};ao.geo.bounds=function(){function n(n,t){M.push(x=[f=n,h=n]),s>t&&(s=t),t>p&&(p=t)}function t(t,e){var r=dt([t*Yo,e*Yo]);if(y){var i=mt(y,r),u=[i[1],-i[0],0],o=mt(u,i);bt(o),o=_t(o);var l=t-g,c=l>0?1:-1,v=o[0]*Zo*c,d=xo(l)>180;if(d^(v>c*g&&c*t>v)){var m=o[1]*Zo;m>p&&(p=m)}else if(v=(v+360)%360-180,d^(v>c*g&&c*t>v)){var m=-o[1]*Zo;s>m&&(s=m)}else s>e&&(s=e),e>p&&(p=e);d?g>t?a(f,t)>a(f,h)&&(h=t):a(t,h)>a(f,h)&&(f=t):h>=f?(f>t&&(f=t),t>h&&(h=t)):t>g?a(f,t)>a(f,h)&&(h=t):a(t,h)>a(f,h)&&(f=t)}else n(t,e);y=r,g=t}function e(){b.point=t}function r(){x[0]=f,x[1]=h,b.point=n,y=null}function i(n,e){if(y){var r=n-g;m+=xo(r)>180?r+(r>0?360:-360):r}else v=n,d=e;Na.point(n,e),t(n,e)}function u(){Na.lineStart()}function o(){i(v,d),Na.lineEnd(),xo(m)>Uo&&(f=-(h=180)),x[0]=f,x[1]=h,y=null}function a(n,t){return(t-=n)<0?t+360:t}function l(n,t){return n[0]-t[0]}function c(n,t){return t[0]<=t[1]?t[0]<=n&&n<=t[1]:nka?(f=-(h=180),s=-(p=90)):m>Uo?p=90:-Uo>m&&(s=-90),x[0]=f,x[1]=h}};return function(n){p=h=-(f=s=1/0),M=[],ao.geo.stream(n,b);var t=M.length;if(t){M.sort(l);for(var e,r=1,i=M[0],u=[i];t>r;++r)e=M[r],c(e[0],i)||c(e[1],i)?(a(i[0],e[1])>a(i[0],i[1])&&(i[1]=e[1]),a(e[0],i[1])>a(i[0],i[1])&&(i[0]=e[0])):u.push(i=e);for(var o,e,g=-(1/0),t=u.length-1,r=0,i=u[t];t>=r;i=e,++r)e=u[r],(o=a(i[1],e[0]))>g&&(g=o,f=e[0],h=i[1])}return M=x=null,f===1/0||s===1/0?[[NaN,NaN],[NaN,NaN]]:[[f,s],[h,p]]}}(),ao.geo.centroid=function(n){Ea=Aa=Ca=za=La=qa=Ta=Ra=Da=Pa=Ua=0,ao.geo.stream(n,ja);var t=Da,e=Pa,r=Ua,i=t*t+e*e+r*r;return jo>i&&(t=qa,e=Ta,r=Ra,Uo>Aa&&(t=Ca,e=za,r=La),i=t*t+e*e+r*r,jo>i)?[NaN,NaN]:[Math.atan2(e,t)*Zo,tn(r/Math.sqrt(i))*Zo]};var Ea,Aa,Ca,za,La,qa,Ta,Ra,Da,Pa,Ua,ja={sphere:b,point:St,lineStart:Nt,lineEnd:Et,polygonStart:function(){ja.lineStart=At},polygonEnd:function(){ja.lineStart=Nt}},Fa=Rt(zt,jt,Ht,[-Fo,-Fo/2]),Ha=1e9;ao.geo.clipExtent=function(){var n,t,e,r,i,u,o={stream:function(n){return i&&(i.valid=!1),i=u(n),i.valid=!0,i},extent:function(a){return arguments.length?(u=Zt(n=+a[0][0],t=+a[0][1],e=+a[1][0],r=+a[1][1]),i&&(i.valid=!1,i=null),o):[[n,t],[e,r]]}};return o.extent([[0,0],[960,500]])},(ao.geo.conicEqualArea=function(){return Vt(Xt)}).raw=Xt,ao.geo.albers=function(){return ao.geo.conicEqualArea().rotate([96,0]).center([-.6,38.7]).parallels([29.5,45.5]).scale(1070)},ao.geo.albersUsa=function(){function n(n){var u=n[0],o=n[1];return t=null,e(u,o),t||(r(u,o),t)||i(u,o),t}var t,e,r,i,u=ao.geo.albers(),o=ao.geo.conicEqualArea().rotate([154,0]).center([-2,58.5]).parallels([55,65]),a=ao.geo.conicEqualArea().rotate([157,0]).center([-3,19.9]).parallels([8,18]),l={point:function(n,e){t=[n,e]}};return n.invert=function(n){var t=u.scale(),e=u.translate(),r=(n[0]-e[0])/t,i=(n[1]-e[1])/t;return(i>=.12&&.234>i&&r>=-.425&&-.214>r?o:i>=.166&&.234>i&&r>=-.214&&-.115>r?a:u).invert(n)},n.stream=function(n){var t=u.stream(n),e=o.stream(n),r=a.stream(n);return{point:function(n,i){t.point(n,i),e.point(n,i),r.point(n,i)},sphere:function(){t.sphere(),e.sphere(),r.sphere()},lineStart:function(){t.lineStart(),e.lineStart(),r.lineStart()},lineEnd:function(){t.lineEnd(),e.lineEnd(),r.lineEnd()},polygonStart:function(){t.polygonStart(),e.polygonStart(),r.polygonStart()},polygonEnd:function(){t.polygonEnd(),e.polygonEnd(),r.polygonEnd()}}},n.precision=function(t){return arguments.length?(u.precision(t),o.precision(t),a.precision(t),n):u.precision()},n.scale=function(t){return arguments.length?(u.scale(t),o.scale(.35*t),a.scale(t),n.translate(u.translate())):u.scale()},n.translate=function(t){if(!arguments.length)return u.translate();var c=u.scale(),f=+t[0],s=+t[1];return e=u.translate(t).clipExtent([[f-.455*c,s-.238*c],[f+.455*c,s+.238*c]]).stream(l).point,r=o.translate([f-.307*c,s+.201*c]).clipExtent([[f-.425*c+Uo,s+.12*c+Uo],[f-.214*c-Uo,s+.234*c-Uo]]).stream(l).point,i=a.translate([f-.205*c,s+.212*c]).clipExtent([[f-.214*c+Uo,s+.166*c+Uo],[f-.115*c-Uo,s+.234*c-Uo]]).stream(l).point,n},n.scale(1070)};var Oa,Ia,Ya,Za,Va,Xa,$a={point:b,lineStart:b,lineEnd:b,polygonStart:function(){Ia=0,$a.lineStart=$t},polygonEnd:function(){$a.lineStart=$a.lineEnd=$a.point=b,Oa+=xo(Ia/2)}},Ba={point:Bt,lineStart:b,lineEnd:b,polygonStart:b,polygonEnd:b},Wa={point:Gt,lineStart:Kt,lineEnd:Qt,polygonStart:function(){Wa.lineStart=ne},polygonEnd:function(){Wa.point=Gt,Wa.lineStart=Kt,Wa.lineEnd=Qt}};ao.geo.path=function(){function n(n){return n&&("function"==typeof a&&u.pointRadius(+a.apply(this,arguments)),o&&o.valid||(o=i(u)),ao.geo.stream(n,o)),u.result()}function t(){return o=null,n}var e,r,i,u,o,a=4.5;return n.area=function(n){return Oa=0,ao.geo.stream(n,i($a)),Oa},n.centroid=function(n){return Ca=za=La=qa=Ta=Ra=Da=Pa=Ua=0,ao.geo.stream(n,i(Wa)),Ua?[Da/Ua,Pa/Ua]:Ra?[qa/Ra,Ta/Ra]:La?[Ca/La,za/La]:[NaN,NaN]},n.bounds=function(n){return Va=Xa=-(Ya=Za=1/0),ao.geo.stream(n,i(Ba)),[[Ya,Za],[Va,Xa]]},n.projection=function(n){return arguments.length?(i=(e=n)?n.stream||re(n):m,t()):e},n.context=function(n){return arguments.length?(u=null==(r=n)?new Wt:new te(n),"function"!=typeof a&&u.pointRadius(a),t()):r},n.pointRadius=function(t){return arguments.length?(a="function"==typeof t?t:(u.pointRadius(+t),+t),n):a},n.projection(ao.geo.albersUsa()).context(null)},ao.geo.transform=function(n){return{stream:function(t){var e=new ie(t);for(var r in n)e[r]=n[r];return e}}},ie.prototype={point:function(n,t){this.stream.point(n,t)},sphere:function(){this.stream.sphere()},lineStart:function(){this.stream.lineStart()},lineEnd:function(){this.stream.lineEnd()},polygonStart:function(){this.stream.polygonStart()},polygonEnd:function(){this.stream.polygonEnd()}},ao.geo.projection=oe,ao.geo.projectionMutator=ae,(ao.geo.equirectangular=function(){return oe(ce)}).raw=ce.invert=ce,ao.geo.rotation=function(n){function t(t){return t=n(t[0]*Yo,t[1]*Yo),t[0]*=Zo,t[1]*=Zo,t}return n=se(n[0]%360*Yo,n[1]*Yo,n.length>2?n[2]*Yo:0),t.invert=function(t){return t=n.invert(t[0]*Yo,t[1]*Yo),t[0]*=Zo,t[1]*=Zo,t},t},fe.invert=ce,ao.geo.circle=function(){function n(){var n="function"==typeof r?r.apply(this,arguments):r,t=se(-n[0]*Yo,-n[1]*Yo,0).invert,i=[];return e(null,null,1,{point:function(n,e){i.push(n=t(n,e)),n[0]*=Zo,n[1]*=Zo}}),{type:"Polygon",coordinates:[i]}}var t,e,r=[0,0],i=6;return n.origin=function(t){return arguments.length?(r=t,n):r},n.angle=function(r){return arguments.length?(e=ve((t=+r)*Yo,i*Yo),n):t},n.precision=function(r){return arguments.length?(e=ve(t*Yo,(i=+r)*Yo),n):i},n.angle(90)},ao.geo.distance=function(n,t){var e,r=(t[0]-n[0])*Yo,i=n[1]*Yo,u=t[1]*Yo,o=Math.sin(r),a=Math.cos(r),l=Math.sin(i),c=Math.cos(i),f=Math.sin(u),s=Math.cos(u);return Math.atan2(Math.sqrt((e=s*o)*e+(e=c*f-l*s*a)*e),l*f+c*s*a)},ao.geo.graticule=function(){function n(){return{type:"MultiLineString",coordinates:t()}}function t(){return ao.range(Math.ceil(u/d)*d,i,d).map(h).concat(ao.range(Math.ceil(c/y)*y,l,y).map(p)).concat(ao.range(Math.ceil(r/g)*g,e,g).filter(function(n){return xo(n%d)>Uo}).map(f)).concat(ao.range(Math.ceil(a/v)*v,o,v).filter(function(n){return xo(n%y)>Uo}).map(s))}var e,r,i,u,o,a,l,c,f,s,h,p,g=10,v=g,d=90,y=360,m=2.5;return n.lines=function(){return t().map(function(n){return{type:"LineString",coordinates:n}})},n.outline=function(){return{type:"Polygon",coordinates:[h(u).concat(p(l).slice(1),h(i).reverse().slice(1),p(c).reverse().slice(1))]}},n.extent=function(t){return arguments.length?n.majorExtent(t).minorExtent(t):n.minorExtent()},n.majorExtent=function(t){return arguments.length?(u=+t[0][0],i=+t[1][0],c=+t[0][1],l=+t[1][1],u>i&&(t=u,u=i,i=t),c>l&&(t=c,c=l,l=t),n.precision(m)):[[u,c],[i,l]]},n.minorExtent=function(t){return arguments.length?(r=+t[0][0],e=+t[1][0],a=+t[0][1],o=+t[1][1],r>e&&(t=r,r=e,e=t),a>o&&(t=a,a=o,o=t),n.precision(m)):[[r,a],[e,o]]},n.step=function(t){return arguments.length?n.majorStep(t).minorStep(t):n.minorStep()},n.majorStep=function(t){return arguments.length?(d=+t[0],y=+t[1],n):[d,y]},n.minorStep=function(t){return arguments.length?(g=+t[0],v=+t[1],n):[g,v]},n.precision=function(t){return arguments.length?(m=+t,f=ye(a,o,90),s=me(r,e,m),h=ye(c,l,90),p=me(u,i,m),n):m},n.majorExtent([[-180,-90+Uo],[180,90-Uo]]).minorExtent([[-180,-80-Uo],[180,80+Uo]])},ao.geo.greatArc=function(){function n(){return{type:"LineString",coordinates:[t||r.apply(this,arguments),e||i.apply(this,arguments)]}}var t,e,r=Me,i=xe;return n.distance=function(){return ao.geo.distance(t||r.apply(this,arguments),e||i.apply(this,arguments))},n.source=function(e){return arguments.length?(r=e,t="function"==typeof e?null:e,n):r},n.target=function(t){return arguments.length?(i=t,e="function"==typeof t?null:t,n):i},n.precision=function(){return arguments.length?n:0},n},ao.geo.interpolate=function(n,t){return be(n[0]*Yo,n[1]*Yo,t[0]*Yo,t[1]*Yo)},ao.geo.length=function(n){return Ja=0,ao.geo.stream(n,Ga),Ja};var Ja,Ga={sphere:b,point:b,lineStart:_e,lineEnd:b,polygonStart:b,polygonEnd:b},Ka=we(function(n){return Math.sqrt(2/(1+n))},function(n){return 2*Math.asin(n/2)});(ao.geo.azimuthalEqualArea=function(){return oe(Ka)}).raw=Ka;var Qa=we(function(n){var t=Math.acos(n);return t&&t/Math.sin(t)},m);(ao.geo.azimuthalEquidistant=function(){return oe(Qa)}).raw=Qa,(ao.geo.conicConformal=function(){return Vt(Se)}).raw=Se,(ao.geo.conicEquidistant=function(){return Vt(ke)}).raw=ke;var nl=we(function(n){return 1/n},Math.atan);(ao.geo.gnomonic=function(){return oe(nl)}).raw=nl,Ne.invert=function(n,t){return[n,2*Math.atan(Math.exp(t))-Io]},(ao.geo.mercator=function(){return Ee(Ne)}).raw=Ne;var tl=we(function(){return 1},Math.asin);(ao.geo.orthographic=function(){return oe(tl)}).raw=tl;var el=we(function(n){return 1/(1+n)},function(n){return 2*Math.atan(n)});(ao.geo.stereographic=function(){return oe(el)}).raw=el,Ae.invert=function(n,t){return[-t,2*Math.atan(Math.exp(n))-Io]},(ao.geo.transverseMercator=function(){var n=Ee(Ae),t=n.center,e=n.rotate;return n.center=function(n){return n?t([-n[1],n[0]]):(n=t(),[n[1],-n[0]])},n.rotate=function(n){return n?e([n[0],n[1],n.length>2?n[2]+90:90]):(n=e(),[n[0],n[1],n[2]-90])},e([0,0,90])}).raw=Ae,ao.geom={},ao.geom.hull=function(n){function t(n){if(n.length<3)return[];var t,i=En(e),u=En(r),o=n.length,a=[],l=[];for(t=0;o>t;t++)a.push([+i.call(this,n[t],t),+u.call(this,n[t],t),t]);for(a.sort(qe),t=0;o>t;t++)l.push([a[t][0],-a[t][1]]);var c=Le(a),f=Le(l),s=f[0]===c[0],h=f[f.length-1]===c[c.length-1],p=[];for(t=c.length-1;t>=0;--t)p.push(n[a[c[t]][2]]);for(t=+s;t=r&&c.x<=u&&c.y>=i&&c.y<=o?[[r,o],[u,o],[u,i],[r,i]]:[];f.point=n[a]}),t}function e(n){return n.map(function(n,t){return{x:Math.round(u(n,t)/Uo)*Uo,y:Math.round(o(n,t)/Uo)*Uo,i:t}})}var r=Ce,i=ze,u=r,o=i,a=sl;return n?t(n):(t.links=function(n){return ar(e(n)).edges.filter(function(n){return n.l&&n.r}).map(function(t){return{source:n[t.l.i],target:n[t.r.i]}})},t.triangles=function(n){var t=[];return ar(e(n)).cells.forEach(function(e,r){for(var i,u,o=e.site,a=e.edges.sort(Ve),l=-1,c=a.length,f=a[c-1].edge,s=f.l===o?f.r:f.l;++l=c,h=r>=f,p=h<<1|s;n.leaf=!1,n=n.nodes[p]||(n.nodes[p]=hr()),s?i=c:a=c,h?o=f:l=f,u(n,t,e,r,i,o,a,l)}var f,s,h,p,g,v,d,y,m,M=En(a),x=En(l);if(null!=t)v=t,d=e,y=r,m=i;else if(y=m=-(v=d=1/0),s=[],h=[],g=n.length,o)for(p=0;g>p;++p)f=n[p],f.xy&&(y=f.x),f.y>m&&(m=f.y),s.push(f.x),h.push(f.y);else for(p=0;g>p;++p){var b=+M(f=n[p],p),_=+x(f,p);v>b&&(v=b),d>_&&(d=_),b>y&&(y=b),_>m&&(m=_),s.push(b),h.push(_)}var w=y-v,S=m-d;w>S?m=d+w:y=v+S;var k=hr();if(k.add=function(n){u(k,n,+M(n,++p),+x(n,p),v,d,y,m)},k.visit=function(n){pr(n,k,v,d,y,m)},k.find=function(n){return gr(k,n[0],n[1],v,d,y,m)},p=-1,null==t){for(;++p=0?n.slice(0,t):n,r=t>=0?n.slice(t+1):"in";return e=vl.get(e)||gl,r=dl.get(r)||m,br(r(e.apply(null,lo.call(arguments,1))))},ao.interpolateHcl=Rr,ao.interpolateHsl=Dr,ao.interpolateLab=Pr,ao.interpolateRound=Ur,ao.transform=function(n){var t=fo.createElementNS(ao.ns.prefix.svg,"g");return(ao.transform=function(n){if(null!=n){t.setAttribute("transform",n);var e=t.transform.baseVal.consolidate()}return new jr(e?e.matrix:yl)})(n)},jr.prototype.toString=function(){return"translate("+this.translate+")rotate("+this.rotate+")skewX("+this.skew+")scale("+this.scale+")"};var yl={a:1,b:0,c:0,d:1,e:0,f:0};ao.interpolateTransform=$r,ao.layout={},ao.layout.bundle=function(){return function(n){for(var t=[],e=-1,r=n.length;++ea*a/y){if(v>l){var c=t.charge/l;n.px-=u*c,n.py-=o*c}return!0}if(t.point&&l&&v>l){var c=t.pointCharge/l;n.px-=u*c,n.py-=o*c}}return!t.charge}}function t(n){n.px=ao.event.x,n.py=ao.event.y,l.resume()}var e,r,i,u,o,a,l={},c=ao.dispatch("start","tick","end"),f=[1,1],s=.9,h=ml,p=Ml,g=-30,v=xl,d=.1,y=.64,M=[],x=[];return l.tick=function(){if((i*=.99)<.005)return e=null,c.end({type:"end",alpha:i=0}),!0;var t,r,l,h,p,v,y,m,b,_=M.length,w=x.length;for(r=0;w>r;++r)l=x[r],h=l.source,p=l.target,m=p.x-h.x,b=p.y-h.y,(v=m*m+b*b)&&(v=i*o[r]*((v=Math.sqrt(v))-u[r])/v,m*=v,b*=v,p.x-=m*(y=h.weight+p.weight?h.weight/(h.weight+p.weight):.5),p.y-=b*y,h.x+=m*(y=1-y),h.y+=b*y);if((y=i*d)&&(m=f[0]/2,b=f[1]/2,r=-1,y))for(;++r<_;)l=M[r],l.x+=(m-l.x)*y,l.y+=(b-l.y)*y;if(g)for(ri(t=ao.geom.quadtree(M),i,a),r=-1;++r<_;)(l=M[r]).fixed||t.visit(n(l));for(r=-1;++r<_;)l=M[r],l.fixed?(l.x=l.px,l.y=l.py):(l.x-=(l.px-(l.px=l.x))*s,l.y-=(l.py-(l.py=l.y))*s);c.tick({type:"tick",alpha:i})},l.nodes=function(n){return arguments.length?(M=n,l):M},l.links=function(n){return arguments.length?(x=n,l):x},l.size=function(n){return arguments.length?(f=n,l):f},l.linkDistance=function(n){return arguments.length?(h="function"==typeof n?n:+n,l):h},l.distance=l.linkDistance,l.linkStrength=function(n){return arguments.length?(p="function"==typeof n?n:+n,l):p},l.friction=function(n){return arguments.length?(s=+n,l):s},l.charge=function(n){return arguments.length?(g="function"==typeof n?n:+n,l):g},l.chargeDistance=function(n){return arguments.length?(v=n*n,l):Math.sqrt(v)},l.gravity=function(n){return arguments.length?(d=+n,l):d},l.theta=function(n){return arguments.length?(y=n*n,l):Math.sqrt(y)},l.alpha=function(n){return arguments.length?(n=+n,i?n>0?i=n:(e.c=null,e.t=NaN,e=null,c.end({type:"end",alpha:i=0})):n>0&&(c.start({type:"start",alpha:i=n}),e=qn(l.tick)),l):i},l.start=function(){function n(n,r){if(!e){for(e=new Array(i),l=0;i>l;++l)e[l]=[];for(l=0;c>l;++l){var u=x[l];e[u.source.index].push(u.target),e[u.target.index].push(u.source)}}for(var o,a=e[t],l=-1,f=a.length;++lt;++t)(r=M[t]).index=t,r.weight=0;for(t=0;c>t;++t)r=x[t],"number"==typeof r.source&&(r.source=M[r.source]),"number"==typeof r.target&&(r.target=M[r.target]),++r.source.weight,++r.target.weight;for(t=0;i>t;++t)r=M[t],isNaN(r.x)&&(r.x=n("x",s)),isNaN(r.y)&&(r.y=n("y",v)),isNaN(r.px)&&(r.px=r.x),isNaN(r.py)&&(r.py=r.y);if(u=[],"function"==typeof h)for(t=0;c>t;++t)u[t]=+h.call(this,x[t],t);else for(t=0;c>t;++t)u[t]=h;if(o=[],"function"==typeof p)for(t=0;c>t;++t)o[t]=+p.call(this,x[t],t);else for(t=0;c>t;++t)o[t]=p;if(a=[],"function"==typeof g)for(t=0;i>t;++t)a[t]=+g.call(this,M[t],t);else for(t=0;i>t;++t)a[t]=g;return l.resume()},l.resume=function(){return l.alpha(.1)},l.stop=function(){return l.alpha(0)},l.drag=function(){return r||(r=ao.behavior.drag().origin(m).on("dragstart.force",Qr).on("drag.force",t).on("dragend.force",ni)),arguments.length?void this.on("mouseover.force",ti).on("mouseout.force",ei).call(r):r},ao.rebind(l,c,"on")};var ml=20,Ml=1,xl=1/0;ao.layout.hierarchy=function(){function n(i){var u,o=[i],a=[];for(i.depth=0;null!=(u=o.pop());)if(a.push(u),(c=e.call(n,u,u.depth))&&(l=c.length)){for(var l,c,f;--l>=0;)o.push(f=c[l]),f.parent=u,f.depth=u.depth+1;r&&(u.value=0),u.children=c}else r&&(u.value=+r.call(n,u,u.depth)||0),delete u.children;return oi(i,function(n){var e,i;t&&(e=n.children)&&e.sort(t),r&&(i=n.parent)&&(i.value+=n.value)}),a}var t=ci,e=ai,r=li;return n.sort=function(e){return arguments.length?(t=e,n):t},n.children=function(t){return arguments.length?(e=t,n):e},n.value=function(t){return arguments.length?(r=t,n):r},n.revalue=function(t){return r&&(ui(t,function(n){n.children&&(n.value=0)}),oi(t,function(t){var e;t.children||(t.value=+r.call(n,t,t.depth)||0),(e=t.parent)&&(e.value+=t.value)})),t},n},ao.layout.partition=function(){function n(t,e,r,i){var u=t.children;if(t.x=e,t.y=t.depth*i,t.dx=r,t.dy=i,u&&(o=u.length)){var o,a,l,c=-1;for(r=t.value?r/t.value:0;++cs?-1:1),g=ao.sum(c),v=g?(s-l*p)/g:0,d=ao.range(l),y=[];return null!=e&&d.sort(e===bl?function(n,t){return c[t]-c[n]}:function(n,t){return e(o[n],o[t])}),d.forEach(function(n){y[n]={data:o[n],value:a=c[n],startAngle:f,endAngle:f+=a*v+p,padAngle:h}}),y}var t=Number,e=bl,r=0,i=Ho,u=0;return n.value=function(e){return arguments.length?(t=e,n):t},n.sort=function(t){return arguments.length?(e=t,n):e},n.startAngle=function(t){return arguments.length?(r=t,n):r},n.endAngle=function(t){return arguments.length?(i=t,n):i},n.padAngle=function(t){return arguments.length?(u=t,n):u},n};var bl={};ao.layout.stack=function(){function n(a,l){if(!(h=a.length))return a;var c=a.map(function(e,r){return t.call(n,e,r)}),f=c.map(function(t){return t.map(function(t,e){return[u.call(n,t,e),o.call(n,t,e)]})}),s=e.call(n,f,l);c=ao.permute(c,s),f=ao.permute(f,s);var h,p,g,v,d=r.call(n,f,l),y=c[0].length;for(g=0;y>g;++g)for(i.call(n,c[0][g],v=d[g],f[0][g][1]),p=1;h>p;++p)i.call(n,c[p][g],v+=f[p-1][g][1],f[p][g][1]);return a}var t=m,e=gi,r=vi,i=pi,u=si,o=hi;return n.values=function(e){return arguments.length?(t=e,n):t},n.order=function(t){return arguments.length?(e="function"==typeof t?t:_l.get(t)||gi,n):e},n.offset=function(t){return arguments.length?(r="function"==typeof t?t:wl.get(t)||vi,n):r},n.x=function(t){return arguments.length?(u=t,n):u},n.y=function(t){return arguments.length?(o=t,n):o},n.out=function(t){return arguments.length?(i=t,n):i},n};var _l=ao.map({"inside-out":function(n){var t,e,r=n.length,i=n.map(di),u=n.map(yi),o=ao.range(r).sort(function(n,t){return i[n]-i[t]}),a=0,l=0,c=[],f=[];for(t=0;r>t;++t)e=o[t],l>a?(a+=u[e],c.push(e)):(l+=u[e],f.push(e));return f.reverse().concat(c)},reverse:function(n){return ao.range(n.length).reverse()},"default":gi}),wl=ao.map({silhouette:function(n){var t,e,r,i=n.length,u=n[0].length,o=[],a=0,l=[];for(e=0;u>e;++e){for(t=0,r=0;i>t;t++)r+=n[t][e][1];r>a&&(a=r),o.push(r)}for(e=0;u>e;++e)l[e]=(a-o[e])/2;return l},wiggle:function(n){var t,e,r,i,u,o,a,l,c,f=n.length,s=n[0],h=s.length,p=[];for(p[0]=l=c=0,e=1;h>e;++e){for(t=0,i=0;f>t;++t)i+=n[t][e][1];for(t=0,u=0,a=s[e][0]-s[e-1][0];f>t;++t){for(r=0,o=(n[t][e][1]-n[t][e-1][1])/(2*a);t>r;++r)o+=(n[r][e][1]-n[r][e-1][1])/a;u+=o*n[t][e][1]}p[e]=l-=i?u/i*a:0,c>l&&(c=l)}for(e=0;h>e;++e)p[e]-=c;return p},expand:function(n){var t,e,r,i=n.length,u=n[0].length,o=1/i,a=[];for(e=0;u>e;++e){for(t=0,r=0;i>t;t++)r+=n[t][e][1];if(r)for(t=0;i>t;t++)n[t][e][1]/=r;else for(t=0;i>t;t++)n[t][e][1]=o}for(e=0;u>e;++e)a[e]=0;return a},zero:vi});ao.layout.histogram=function(){function n(n,u){for(var o,a,l=[],c=n.map(e,this),f=r.call(this,c,u),s=i.call(this,f,c,u),u=-1,h=c.length,p=s.length-1,g=t?1:1/h;++u0)for(u=-1;++u=f[0]&&a<=f[1]&&(o=l[ao.bisect(s,a,1,p)-1],o.y+=g,o.push(n[u]));return l}var t=!0,e=Number,r=bi,i=Mi;return n.value=function(t){return arguments.length?(e=t,n):e},n.range=function(t){return arguments.length?(r=En(t),n):r},n.bins=function(t){return arguments.length?(i="number"==typeof t?function(n){return xi(n,t)}:En(t),n):i},n.frequency=function(e){return arguments.length?(t=!!e,n):t},n},ao.layout.pack=function(){function n(n,u){var o=e.call(this,n,u),a=o[0],l=i[0],c=i[1],f=null==t?Math.sqrt:"function"==typeof t?t:function(){return t};if(a.x=a.y=0,oi(a,function(n){n.r=+f(n.value)}),oi(a,Ni),r){var s=r*(t?1:Math.max(2*a.r/l,2*a.r/c))/2;oi(a,function(n){n.r+=s}),oi(a,Ni),oi(a,function(n){n.r-=s})}return Ci(a,l/2,c/2,t?1:1/Math.max(2*a.r/l,2*a.r/c)),o}var t,e=ao.layout.hierarchy().sort(_i),r=0,i=[1,1];return n.size=function(t){return arguments.length?(i=t,n):i},n.radius=function(e){return arguments.length?(t=null==e||"function"==typeof e?e:+e,n):t},n.padding=function(t){return arguments.length?(r=+t,n):r},ii(n,e)},ao.layout.tree=function(){function n(n,i){var f=o.call(this,n,i),s=f[0],h=t(s);if(oi(h,e),h.parent.m=-h.z,ui(h,r),c)ui(s,u);else{var p=s,g=s,v=s;ui(s,function(n){n.xg.x&&(g=n),n.depth>v.depth&&(v=n)});var d=a(p,g)/2-p.x,y=l[0]/(g.x+a(g,p)/2+d),m=l[1]/(v.depth||1);ui(s,function(n){n.x=(n.x+d)*y,n.y=n.depth*m})}return f}function t(n){for(var t,e={A:null,children:[n]},r=[e];null!=(t=r.pop());)for(var i,u=t.children,o=0,a=u.length;a>o;++o)r.push((u[o]=i={_:u[o],parent:t,children:(i=u[o].children)&&i.slice()||[],A:null,a:null,z:0,m:0,c:0,s:0,t:null,i:o}).a=i);return e.children[0]}function e(n){var t=n.children,e=n.parent.children,r=n.i?e[n.i-1]:null;if(t.length){Di(n);var u=(t[0].z+t[t.length-1].z)/2;r?(n.z=r.z+a(n._,r._),n.m=n.z-u):n.z=u}else r&&(n.z=r.z+a(n._,r._));n.parent.A=i(n,r,n.parent.A||e[0])}function r(n){n._.x=n.z+n.parent.m,n.m+=n.parent.m}function i(n,t,e){if(t){for(var r,i=n,u=n,o=t,l=i.parent.children[0],c=i.m,f=u.m,s=o.m,h=l.m;o=Ti(o),i=qi(i),o&&i;)l=qi(l),u=Ti(u),u.a=n,r=o.z+s-i.z-c+a(o._,i._),r>0&&(Ri(Pi(o,n,e),n,r),c+=r,f+=r),s+=o.m,c+=i.m,h+=l.m,f+=u.m;o&&!Ti(u)&&(u.t=o,u.m+=s-f),i&&!qi(l)&&(l.t=i,l.m+=c-h,e=n)}return e}function u(n){n.x*=l[0],n.y=n.depth*l[1]}var o=ao.layout.hierarchy().sort(null).value(null),a=Li,l=[1,1],c=null;return n.separation=function(t){return arguments.length?(a=t,n):a},n.size=function(t){return arguments.length?(c=null==(l=t)?u:null,n):c?null:l},n.nodeSize=function(t){return arguments.length?(c=null==(l=t)?null:u,n):c?l:null},ii(n,o)},ao.layout.cluster=function(){function n(n,u){var o,a=t.call(this,n,u),l=a[0],c=0;oi(l,function(n){var t=n.children;t&&t.length?(n.x=ji(t),n.y=Ui(t)):(n.x=o?c+=e(n,o):0,n.y=0,o=n)});var f=Fi(l),s=Hi(l),h=f.x-e(f,s)/2,p=s.x+e(s,f)/2;return oi(l,i?function(n){n.x=(n.x-l.x)*r[0],n.y=(l.y-n.y)*r[1]}:function(n){n.x=(n.x-h)/(p-h)*r[0],n.y=(1-(l.y?n.y/l.y:1))*r[1]}),a}var t=ao.layout.hierarchy().sort(null).value(null),e=Li,r=[1,1],i=!1;return n.separation=function(t){return arguments.length?(e=t,n):e},n.size=function(t){return arguments.length?(i=null==(r=t),n):i?null:r},n.nodeSize=function(t){return arguments.length?(i=null!=(r=t),n):i?r:null},ii(n,t)},ao.layout.treemap=function(){function n(n,t){for(var e,r,i=-1,u=n.length;++it?0:t),e.area=isNaN(r)||0>=r?0:r}function t(e){var u=e.children;if(u&&u.length){var o,a,l,c=s(e),f=[],h=u.slice(),g=1/0,v="slice"===p?c.dx:"dice"===p?c.dy:"slice-dice"===p?1&e.depth?c.dy:c.dx:Math.min(c.dx,c.dy);for(n(h,c.dx*c.dy/e.value),f.area=0;(l=h.length)>0;)f.push(o=h[l-1]),f.area+=o.area,"squarify"!==p||(a=r(f,v))<=g?(h.pop(),g=a):(f.area-=f.pop().area,i(f,v,c,!1),v=Math.min(c.dx,c.dy),f.length=f.area=0,g=1/0);f.length&&(i(f,v,c,!0),f.length=f.area=0),u.forEach(t)}}function e(t){var r=t.children;if(r&&r.length){var u,o=s(t),a=r.slice(),l=[];for(n(a,o.dx*o.dy/t.value),l.area=0;u=a.pop();)l.push(u),l.area+=u.area,null!=u.z&&(i(l,u.z?o.dx:o.dy,o,!a.length),l.length=l.area=0);r.forEach(e)}}function r(n,t){for(var e,r=n.area,i=0,u=1/0,o=-1,a=n.length;++oe&&(u=e),e>i&&(i=e));return r*=r,t*=t,r?Math.max(t*i*g/r,r/(t*u*g)):1/0}function i(n,t,e,r){var i,u=-1,o=n.length,a=e.x,c=e.y,f=t?l(n.area/t):0; -if(t==e.dx){for((r||f>e.dy)&&(f=e.dy);++ue.dx)&&(f=e.dx);++ue&&(t=1),1>e&&(n=0),function(){var e,r,i;do e=2*Math.random()-1,r=2*Math.random()-1,i=e*e+r*r;while(!i||i>1);return n+t*e*Math.sqrt(-2*Math.log(i)/i)}},logNormal:function(){var n=ao.random.normal.apply(ao,arguments);return function(){return Math.exp(n())}},bates:function(n){var t=ao.random.irwinHall(n);return function(){return t()/n}},irwinHall:function(n){return function(){for(var t=0,e=0;n>e;e++)t+=Math.random();return t}}},ao.scale={};var Sl={floor:m,ceil:m};ao.scale.linear=function(){return Wi([0,1],[0,1],Mr,!1)};var kl={s:1,g:1,p:1,r:1,e:1};ao.scale.log=function(){return ru(ao.scale.linear().domain([0,1]),10,!0,[1,10])};var Nl=ao.format(".0e"),El={floor:function(n){return-Math.ceil(-n)},ceil:function(n){return-Math.floor(-n)}};ao.scale.pow=function(){return iu(ao.scale.linear(),1,[0,1])},ao.scale.sqrt=function(){return ao.scale.pow().exponent(.5)},ao.scale.ordinal=function(){return ou([],{t:"range",a:[[]]})},ao.scale.category10=function(){return ao.scale.ordinal().range(Al)},ao.scale.category20=function(){return ao.scale.ordinal().range(Cl)},ao.scale.category20b=function(){return ao.scale.ordinal().range(zl)},ao.scale.category20c=function(){return ao.scale.ordinal().range(Ll)};var Al=[2062260,16744206,2924588,14034728,9725885,9197131,14907330,8355711,12369186,1556175].map(xn),Cl=[2062260,11454440,16744206,16759672,2924588,10018698,14034728,16750742,9725885,12955861,9197131,12885140,14907330,16234194,8355711,13092807,12369186,14408589,1556175,10410725].map(xn),zl=[3750777,5395619,7040719,10264286,6519097,9216594,11915115,13556636,9202993,12426809,15186514,15190932,8666169,11356490,14049643,15177372,8077683,10834324,13528509,14589654].map(xn),Ll=[3244733,7057110,10406625,13032431,15095053,16616764,16625259,16634018,3253076,7652470,10607003,13101504,7695281,10394312,12369372,14342891,6513507,9868950,12434877,14277081].map(xn);ao.scale.quantile=function(){return au([],[])},ao.scale.quantize=function(){return lu(0,1,[0,1])},ao.scale.threshold=function(){return cu([.5],[0,1])},ao.scale.identity=function(){return fu([0,1])},ao.svg={},ao.svg.arc=function(){function n(){var n=Math.max(0,+e.apply(this,arguments)),c=Math.max(0,+r.apply(this,arguments)),f=o.apply(this,arguments)-Io,s=a.apply(this,arguments)-Io,h=Math.abs(s-f),p=f>s?0:1;if(n>c&&(g=c,c=n,n=g),h>=Oo)return t(c,p)+(n?t(n,1-p):"")+"Z";var g,v,d,y,m,M,x,b,_,w,S,k,N=0,E=0,A=[];if((y=(+l.apply(this,arguments)||0)/2)&&(d=u===ql?Math.sqrt(n*n+c*c):+u.apply(this,arguments),p||(E*=-1),c&&(E=tn(d/c*Math.sin(y))),n&&(N=tn(d/n*Math.sin(y)))),c){m=c*Math.cos(f+E),M=c*Math.sin(f+E),x=c*Math.cos(s-E),b=c*Math.sin(s-E);var C=Math.abs(s-f-2*E)<=Fo?0:1;if(E&&yu(m,M,x,b)===p^C){var z=(f+s)/2;m=c*Math.cos(z),M=c*Math.sin(z),x=b=null}}else m=M=0;if(n){_=n*Math.cos(s-N),w=n*Math.sin(s-N),S=n*Math.cos(f+N),k=n*Math.sin(f+N);var L=Math.abs(f-s+2*N)<=Fo?0:1;if(N&&yu(_,w,S,k)===1-p^L){var q=(f+s)/2;_=n*Math.cos(q),w=n*Math.sin(q),S=k=null}}else _=w=0;if(h>Uo&&(g=Math.min(Math.abs(c-n)/2,+i.apply(this,arguments)))>.001){v=c>n^p?0:1;var T=g,R=g;if(Fo>h){var D=null==S?[_,w]:null==x?[m,M]:Re([m,M],[S,k],[x,b],[_,w]),P=m-D[0],U=M-D[1],j=x-D[0],F=b-D[1],H=1/Math.sin(Math.acos((P*j+U*F)/(Math.sqrt(P*P+U*U)*Math.sqrt(j*j+F*F)))/2),O=Math.sqrt(D[0]*D[0]+D[1]*D[1]);R=Math.min(g,(n-O)/(H-1)),T=Math.min(g,(c-O)/(H+1))}if(null!=x){var I=mu(null==S?[_,w]:[S,k],[m,M],c,T,p),Y=mu([x,b],[_,w],c,T,p);g===T?A.push("M",I[0],"A",T,",",T," 0 0,",v," ",I[1],"A",c,",",c," 0 ",1-p^yu(I[1][0],I[1][1],Y[1][0],Y[1][1]),",",p," ",Y[1],"A",T,",",T," 0 0,",v," ",Y[0]):A.push("M",I[0],"A",T,",",T," 0 1,",v," ",Y[0])}else A.push("M",m,",",M);if(null!=S){var Z=mu([m,M],[S,k],n,-R,p),V=mu([_,w],null==x?[m,M]:[x,b],n,-R,p);g===R?A.push("L",V[0],"A",R,",",R," 0 0,",v," ",V[1],"A",n,",",n," 0 ",p^yu(V[1][0],V[1][1],Z[1][0],Z[1][1]),",",1-p," ",Z[1],"A",R,",",R," 0 0,",v," ",Z[0]):A.push("L",V[0],"A",R,",",R," 0 0,",v," ",Z[0])}else A.push("L",_,",",w)}else A.push("M",m,",",M),null!=x&&A.push("A",c,",",c," 0 ",C,",",p," ",x,",",b),A.push("L",_,",",w),null!=S&&A.push("A",n,",",n," 0 ",L,",",1-p," ",S,",",k);return A.push("Z"),A.join("")}function t(n,t){return"M0,"+n+"A"+n+","+n+" 0 1,"+t+" 0,"+-n+"A"+n+","+n+" 0 1,"+t+" 0,"+n}var e=hu,r=pu,i=su,u=ql,o=gu,a=vu,l=du;return n.innerRadius=function(t){return arguments.length?(e=En(t),n):e},n.outerRadius=function(t){return arguments.length?(r=En(t),n):r},n.cornerRadius=function(t){return arguments.length?(i=En(t),n):i},n.padRadius=function(t){return arguments.length?(u=t==ql?ql:En(t),n):u},n.startAngle=function(t){return arguments.length?(o=En(t),n):o},n.endAngle=function(t){return arguments.length?(a=En(t),n):a},n.padAngle=function(t){return arguments.length?(l=En(t),n):l},n.centroid=function(){var n=(+e.apply(this,arguments)+ +r.apply(this,arguments))/2,t=(+o.apply(this,arguments)+ +a.apply(this,arguments))/2-Io;return[Math.cos(t)*n,Math.sin(t)*n]},n};var ql="auto";ao.svg.line=function(){return Mu(m)};var Tl=ao.map({linear:xu,"linear-closed":bu,step:_u,"step-before":wu,"step-after":Su,basis:zu,"basis-open":Lu,"basis-closed":qu,bundle:Tu,cardinal:Eu,"cardinal-open":ku,"cardinal-closed":Nu,monotone:Fu});Tl.forEach(function(n,t){t.key=n,t.closed=/-closed$/.test(n)});var Rl=[0,2/3,1/3,0],Dl=[0,1/3,2/3,0],Pl=[0,1/6,2/3,1/6];ao.svg.line.radial=function(){var n=Mu(Hu);return n.radius=n.x,delete n.x,n.angle=n.y,delete n.y,n},wu.reverse=Su,Su.reverse=wu,ao.svg.area=function(){return Ou(m)},ao.svg.area.radial=function(){var n=Ou(Hu);return n.radius=n.x,delete n.x,n.innerRadius=n.x0,delete n.x0,n.outerRadius=n.x1,delete n.x1,n.angle=n.y,delete n.y,n.startAngle=n.y0,delete n.y0,n.endAngle=n.y1,delete n.y1,n},ao.svg.chord=function(){function n(n,a){var l=t(this,u,n,a),c=t(this,o,n,a);return"M"+l.p0+r(l.r,l.p1,l.a1-l.a0)+(e(l,c)?i(l.r,l.p1,l.r,l.p0):i(l.r,l.p1,c.r,c.p0)+r(c.r,c.p1,c.a1-c.a0)+i(c.r,c.p1,l.r,l.p0))+"Z"}function t(n,t,e,r){var i=t.call(n,e,r),u=a.call(n,i,r),o=l.call(n,i,r)-Io,f=c.call(n,i,r)-Io;return{r:u,a0:o,a1:f,p0:[u*Math.cos(o),u*Math.sin(o)],p1:[u*Math.cos(f),u*Math.sin(f)]}}function e(n,t){return n.a0==t.a0&&n.a1==t.a1}function r(n,t,e){return"A"+n+","+n+" 0 "+ +(e>Fo)+",1 "+t}function i(n,t,e,r){return"Q 0,0 "+r}var u=Me,o=xe,a=Iu,l=gu,c=vu;return n.radius=function(t){return arguments.length?(a=En(t),n):a},n.source=function(t){return arguments.length?(u=En(t),n):u},n.target=function(t){return arguments.length?(o=En(t),n):o},n.startAngle=function(t){return arguments.length?(l=En(t),n):l},n.endAngle=function(t){return arguments.length?(c=En(t),n):c},n},ao.svg.diagonal=function(){function n(n,i){var u=t.call(this,n,i),o=e.call(this,n,i),a=(u.y+o.y)/2,l=[u,{x:u.x,y:a},{x:o.x,y:a},o];return l=l.map(r),"M"+l[0]+"C"+l[1]+" "+l[2]+" "+l[3]}var t=Me,e=xe,r=Yu;return n.source=function(e){return arguments.length?(t=En(e),n):t},n.target=function(t){return arguments.length?(e=En(t),n):e},n.projection=function(t){return arguments.length?(r=t,n):r},n},ao.svg.diagonal.radial=function(){var n=ao.svg.diagonal(),t=Yu,e=n.projection;return n.projection=function(n){return arguments.length?e(Zu(t=n)):t},n},ao.svg.symbol=function(){function n(n,r){return(Ul.get(t.call(this,n,r))||$u)(e.call(this,n,r))}var t=Xu,e=Vu;return n.type=function(e){return arguments.length?(t=En(e),n):t},n.size=function(t){return arguments.length?(e=En(t),n):e},n};var Ul=ao.map({circle:$u,cross:function(n){var t=Math.sqrt(n/5)/2;return"M"+-3*t+","+-t+"H"+-t+"V"+-3*t+"H"+t+"V"+-t+"H"+3*t+"V"+t+"H"+t+"V"+3*t+"H"+-t+"V"+t+"H"+-3*t+"Z"},diamond:function(n){var t=Math.sqrt(n/(2*Fl)),e=t*Fl;return"M0,"+-t+"L"+e+",0 0,"+t+" "+-e+",0Z"},square:function(n){var t=Math.sqrt(n)/2;return"M"+-t+","+-t+"L"+t+","+-t+" "+t+","+t+" "+-t+","+t+"Z"},"triangle-down":function(n){var t=Math.sqrt(n/jl),e=t*jl/2;return"M0,"+e+"L"+t+","+-e+" "+-t+","+-e+"Z"},"triangle-up":function(n){var t=Math.sqrt(n/jl),e=t*jl/2;return"M0,"+-e+"L"+t+","+e+" "+-t+","+e+"Z"}});ao.svg.symbolTypes=Ul.keys();var jl=Math.sqrt(3),Fl=Math.tan(30*Yo);Co.transition=function(n){for(var t,e,r=Hl||++Zl,i=Ku(n),u=[],o=Ol||{time:Date.now(),ease:Nr,delay:0,duration:250},a=-1,l=this.length;++au;u++){i.push(t=[]);for(var e=this[u],a=0,l=e.length;l>a;a++)(r=e[a])&&n.call(r,r.__data__,a,u)&&t.push(r)}return Wu(i,this.namespace,this.id)},Yl.tween=function(n,t){var e=this.id,r=this.namespace;return arguments.length<2?this.node()[r][e].tween.get(n):Y(this,null==t?function(t){t[r][e].tween.remove(n)}:function(i){i[r][e].tween.set(n,t)})},Yl.attr=function(n,t){function e(){this.removeAttribute(a)}function r(){this.removeAttributeNS(a.space,a.local)}function i(n){return null==n?e:(n+="",function(){var t,e=this.getAttribute(a);return e!==n&&(t=o(e,n),function(n){this.setAttribute(a,t(n))})})}function u(n){return null==n?r:(n+="",function(){var t,e=this.getAttributeNS(a.space,a.local);return e!==n&&(t=o(e,n),function(n){this.setAttributeNS(a.space,a.local,t(n))})})}if(arguments.length<2){for(t in n)this.attr(t,n[t]);return this}var o="transform"==n?$r:Mr,a=ao.ns.qualify(n);return Ju(this,"attr."+n,t,a.local?u:i)},Yl.attrTween=function(n,t){function e(n,e){var r=t.call(this,n,e,this.getAttribute(i));return r&&function(n){this.setAttribute(i,r(n))}}function r(n,e){var r=t.call(this,n,e,this.getAttributeNS(i.space,i.local));return r&&function(n){this.setAttributeNS(i.space,i.local,r(n))}}var i=ao.ns.qualify(n);return this.tween("attr."+n,i.local?r:e)},Yl.style=function(n,e,r){function i(){this.style.removeProperty(n)}function u(e){return null==e?i:(e+="",function(){var i,u=t(this).getComputedStyle(this,null).getPropertyValue(n);return u!==e&&(i=Mr(u,e),function(t){this.style.setProperty(n,i(t),r)})})}var o=arguments.length;if(3>o){if("string"!=typeof n){2>o&&(e="");for(r in n)this.style(r,n[r],e);return this}r=""}return Ju(this,"style."+n,e,u)},Yl.styleTween=function(n,e,r){function i(i,u){var o=e.call(this,i,u,t(this).getComputedStyle(this,null).getPropertyValue(n));return o&&function(t){this.style.setProperty(n,o(t),r)}}return arguments.length<3&&(r=""),this.tween("style."+n,i)},Yl.text=function(n){return Ju(this,"text",n,Gu)},Yl.remove=function(){var n=this.namespace;return this.each("end.transition",function(){var t;this[n].count<2&&(t=this.parentNode)&&t.removeChild(this)})},Yl.ease=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].ease:("function"!=typeof n&&(n=ao.ease.apply(ao,arguments)),Y(this,function(r){r[e][t].ease=n}))},Yl.delay=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].delay:Y(this,"function"==typeof n?function(r,i,u){r[e][t].delay=+n.call(r,r.__data__,i,u)}:(n=+n,function(r){r[e][t].delay=n}))},Yl.duration=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].duration:Y(this,"function"==typeof n?function(r,i,u){r[e][t].duration=Math.max(1,n.call(r,r.__data__,i,u))}:(n=Math.max(1,n),function(r){r[e][t].duration=n}))},Yl.each=function(n,t){var e=this.id,r=this.namespace;if(arguments.length<2){var i=Ol,u=Hl;try{Hl=e,Y(this,function(t,i,u){Ol=t[r][e],n.call(t,t.__data__,i,u)})}finally{Ol=i,Hl=u}}else Y(this,function(i){var u=i[r][e];(u.event||(u.event=ao.dispatch("start","end","interrupt"))).on(n,t)});return this},Yl.transition=function(){for(var n,t,e,r,i=this.id,u=++Zl,o=this.namespace,a=[],l=0,c=this.length;c>l;l++){a.push(n=[]);for(var t=this[l],f=0,s=t.length;s>f;f++)(e=t[f])&&(r=e[o][i],Qu(e,f,o,u,{time:r.time,ease:r.ease,delay:r.delay+r.duration,duration:r.duration})),n.push(e)}return Wu(a,o,u)},ao.svg.axis=function(){function n(n){n.each(function(){var n,c=ao.select(this),f=this.__chart__||e,s=this.__chart__=e.copy(),h=null==l?s.ticks?s.ticks.apply(s,a):s.domain():l,p=null==t?s.tickFormat?s.tickFormat.apply(s,a):m:t,g=c.selectAll(".tick").data(h,s),v=g.enter().insert("g",".domain").attr("class","tick").style("opacity",Uo),d=ao.transition(g.exit()).style("opacity",Uo).remove(),y=ao.transition(g.order()).style("opacity",1),M=Math.max(i,0)+o,x=Zi(s),b=c.selectAll(".domain").data([0]),_=(b.enter().append("path").attr("class","domain"),ao.transition(b));v.append("line"),v.append("text");var w,S,k,N,E=v.select("line"),A=y.select("line"),C=g.select("text").text(p),z=v.select("text"),L=y.select("text"),q="top"===r||"left"===r?-1:1;if("bottom"===r||"top"===r?(n=no,w="x",k="y",S="x2",N="y2",C.attr("dy",0>q?"0em":".71em").style("text-anchor","middle"),_.attr("d","M"+x[0]+","+q*u+"V0H"+x[1]+"V"+q*u)):(n=to,w="y",k="x",S="y2",N="x2",C.attr("dy",".32em").style("text-anchor",0>q?"end":"start"),_.attr("d","M"+q*u+","+x[0]+"H0V"+x[1]+"H"+q*u)),E.attr(N,q*i),z.attr(k,q*M),A.attr(S,0).attr(N,q*i),L.attr(w,0).attr(k,q*M),s.rangeBand){var T=s,R=T.rangeBand()/2;f=s=function(n){return T(n)+R}}else f.rangeBand?f=s:d.call(n,s,f);v.call(n,f,s),y.call(n,s,s)})}var t,e=ao.scale.linear(),r=Vl,i=6,u=6,o=3,a=[10],l=null;return n.scale=function(t){return arguments.length?(e=t,n):e},n.orient=function(t){return arguments.length?(r=t in Xl?t+"":Vl,n):r},n.ticks=function(){return arguments.length?(a=co(arguments),n):a},n.tickValues=function(t){return arguments.length?(l=t,n):l},n.tickFormat=function(e){return arguments.length?(t=e,n):t},n.tickSize=function(t){var e=arguments.length;return e?(i=+t,u=+arguments[e-1],n):i},n.innerTickSize=function(t){return arguments.length?(i=+t,n):i},n.outerTickSize=function(t){return arguments.length?(u=+t,n):u},n.tickPadding=function(t){return arguments.length?(o=+t,n):o},n.tickSubdivide=function(){return arguments.length&&n},n};var Vl="bottom",Xl={top:1,right:1,bottom:1,left:1};ao.svg.brush=function(){function n(t){t.each(function(){var t=ao.select(this).style("pointer-events","all").style("-webkit-tap-highlight-color","rgba(0,0,0,0)").on("mousedown.brush",u).on("touchstart.brush",u),o=t.selectAll(".background").data([0]);o.enter().append("rect").attr("class","background").style("visibility","hidden").style("cursor","crosshair"),t.selectAll(".extent").data([0]).enter().append("rect").attr("class","extent").style("cursor","move");var a=t.selectAll(".resize").data(v,m);a.exit().remove(),a.enter().append("g").attr("class",function(n){return"resize "+n}).style("cursor",function(n){return $l[n]}).append("rect").attr("x",function(n){return/[ew]$/.test(n)?-3:null}).attr("y",function(n){return/^[ns]/.test(n)?-3:null}).attr("width",6).attr("height",6).style("visibility","hidden"),a.style("display",n.empty()?"none":null);var l,s=ao.transition(t),h=ao.transition(o);c&&(l=Zi(c),h.attr("x",l[0]).attr("width",l[1]-l[0]),r(s)),f&&(l=Zi(f),h.attr("y",l[0]).attr("height",l[1]-l[0]),i(s)),e(s)})}function e(n){n.selectAll(".resize").attr("transform",function(n){return"translate("+s[+/e$/.test(n)]+","+h[+/^s/.test(n)]+")"})}function r(n){n.select(".extent").attr("x",s[0]),n.selectAll(".extent,.n>rect,.s>rect").attr("width",s[1]-s[0])}function i(n){n.select(".extent").attr("y",h[0]),n.selectAll(".extent,.e>rect,.w>rect").attr("height",h[1]-h[0])}function u(){function u(){32==ao.event.keyCode&&(C||(M=null,L[0]-=s[1],L[1]-=h[1],C=2),S())}function v(){32==ao.event.keyCode&&2==C&&(L[0]+=s[1],L[1]+=h[1],C=0,S())}function d(){var n=ao.mouse(b),t=!1;x&&(n[0]+=x[0],n[1]+=x[1]),C||(ao.event.altKey?(M||(M=[(s[0]+s[1])/2,(h[0]+h[1])/2]),L[0]=s[+(n[0]f?(i=r,r=f):i=f),v[0]!=r||v[1]!=i?(e?a=null:o=null,v[0]=r,v[1]=i,!0):void 0}function m(){d(),k.style("pointer-events","all").selectAll(".resize").style("display",n.empty()?"none":null),ao.select("body").style("cursor",null),q.on("mousemove.brush",null).on("mouseup.brush",null).on("touchmove.brush",null).on("touchend.brush",null).on("keydown.brush",null).on("keyup.brush",null),z(),w({type:"brushend"})}var M,x,b=this,_=ao.select(ao.event.target),w=l.of(b,arguments),k=ao.select(b),N=_.datum(),E=!/^(n|s)$/.test(N)&&c,A=!/^(e|w)$/.test(N)&&f,C=_.classed("extent"),z=W(b),L=ao.mouse(b),q=ao.select(t(b)).on("keydown.brush",u).on("keyup.brush",v);if(ao.event.changedTouches?q.on("touchmove.brush",d).on("touchend.brush",m):q.on("mousemove.brush",d).on("mouseup.brush",m),k.interrupt().selectAll("*").interrupt(),C)L[0]=s[0]-L[0],L[1]=h[0]-L[1];else if(N){var T=+/w$/.test(N),R=+/^n/.test(N);x=[s[1-T]-L[0],h[1-R]-L[1]],L[0]=s[T],L[1]=h[R]}else ao.event.altKey&&(M=L.slice());k.style("pointer-events","none").selectAll(".resize").style("display",null),ao.select("body").style("cursor",_.style("cursor")),w({type:"brushstart"}),d()}var o,a,l=N(n,"brushstart","brush","brushend"),c=null,f=null,s=[0,0],h=[0,0],p=!0,g=!0,v=Bl[0];return n.event=function(n){n.each(function(){var n=l.of(this,arguments),t={x:s,y:h,i:o,j:a},e=this.__chart__||t;this.__chart__=t,Hl?ao.select(this).transition().each("start.brush",function(){o=e.i,a=e.j,s=e.x,h=e.y,n({type:"brushstart"})}).tween("brush:brush",function(){var e=xr(s,t.x),r=xr(h,t.y);return o=a=null,function(i){s=t.x=e(i),h=t.y=r(i),n({type:"brush",mode:"resize"})}}).each("end.brush",function(){o=t.i,a=t.j,n({type:"brush",mode:"resize"}),n({type:"brushend"})}):(n({type:"brushstart"}),n({type:"brush",mode:"resize"}),n({type:"brushend"}))})},n.x=function(t){return arguments.length?(c=t,v=Bl[!c<<1|!f],n):c},n.y=function(t){return arguments.length?(f=t,v=Bl[!c<<1|!f],n):f},n.clamp=function(t){return arguments.length?(c&&f?(p=!!t[0],g=!!t[1]):c?p=!!t:f&&(g=!!t),n):c&&f?[p,g]:c?p:f?g:null},n.extent=function(t){var e,r,i,u,l;return arguments.length?(c&&(e=t[0],r=t[1],f&&(e=e[0],r=r[0]),o=[e,r],c.invert&&(e=c(e),r=c(r)),e>r&&(l=e,e=r,r=l),e==s[0]&&r==s[1]||(s=[e,r])),f&&(i=t[0],u=t[1],c&&(i=i[1],u=u[1]),a=[i,u],f.invert&&(i=f(i),u=f(u)),i>u&&(l=i,i=u,u=l),i==h[0]&&u==h[1]||(h=[i,u])),n):(c&&(o?(e=o[0],r=o[1]):(e=s[0],r=s[1],c.invert&&(e=c.invert(e),r=c.invert(r)),e>r&&(l=e,e=r,r=l))),f&&(a?(i=a[0],u=a[1]):(i=h[0],u=h[1],f.invert&&(i=f.invert(i),u=f.invert(u)),i>u&&(l=i,i=u,u=l))),c&&f?[[e,i],[r,u]]:c?[e,r]:f&&[i,u])},n.clear=function(){return n.empty()||(s=[0,0],h=[0,0],o=a=null),n},n.empty=function(){return!!c&&s[0]==s[1]||!!f&&h[0]==h[1]},ao.rebind(n,l,"on")};var $l={n:"ns-resize",e:"ew-resize",s:"ns-resize",w:"ew-resize",nw:"nwse-resize",ne:"nesw-resize",se:"nwse-resize",sw:"nesw-resize"},Bl=[["n","e","s","w","nw","ne","se","sw"],["e","w"],["n","s"],[]],Wl=ga.format=xa.timeFormat,Jl=Wl.utc,Gl=Jl("%Y-%m-%dT%H:%M:%S.%LZ");Wl.iso=Date.prototype.toISOString&&+new Date("2000-01-01T00:00:00.000Z")?eo:Gl,eo.parse=function(n){var t=new Date(n);return isNaN(t)?null:t},eo.toString=Gl.toString,ga.second=On(function(n){return new va(1e3*Math.floor(n/1e3))},function(n,t){n.setTime(n.getTime()+1e3*Math.floor(t))},function(n){return n.getSeconds()}),ga.seconds=ga.second.range,ga.seconds.utc=ga.second.utc.range,ga.minute=On(function(n){return new va(6e4*Math.floor(n/6e4))},function(n,t){n.setTime(n.getTime()+6e4*Math.floor(t))},function(n){return n.getMinutes()}),ga.minutes=ga.minute.range,ga.minutes.utc=ga.minute.utc.range,ga.hour=On(function(n){var t=n.getTimezoneOffset()/60;return new va(36e5*(Math.floor(n/36e5-t)+t))},function(n,t){n.setTime(n.getTime()+36e5*Math.floor(t))},function(n){return n.getHours()}),ga.hours=ga.hour.range,ga.hours.utc=ga.hour.utc.range,ga.month=On(function(n){return n=ga.day(n),n.setDate(1),n},function(n,t){n.setMonth(n.getMonth()+t)},function(n){return n.getMonth()}),ga.months=ga.month.range,ga.months.utc=ga.month.utc.range;var Kl=[1e3,5e3,15e3,3e4,6e4,3e5,9e5,18e5,36e5,108e5,216e5,432e5,864e5,1728e5,6048e5,2592e6,7776e6,31536e6],Ql=[[ga.second,1],[ga.second,5],[ga.second,15],[ga.second,30],[ga.minute,1],[ga.minute,5],[ga.minute,15],[ga.minute,30],[ga.hour,1],[ga.hour,3],[ga.hour,6],[ga.hour,12],[ga.day,1],[ga.day,2],[ga.week,1],[ga.month,1],[ga.month,3],[ga.year,1]],nc=Wl.multi([[".%L",function(n){return n.getMilliseconds()}],[":%S",function(n){return n.getSeconds()}],["%I:%M",function(n){return n.getMinutes()}],["%I %p",function(n){return n.getHours()}],["%a %d",function(n){return n.getDay()&&1!=n.getDate()}],["%b %d",function(n){return 1!=n.getDate()}],["%B",function(n){return n.getMonth()}],["%Y",zt]]),tc={range:function(n,t,e){return ao.range(Math.ceil(n/e)*e,+t,e).map(io)},floor:m,ceil:m};Ql.year=ga.year,ga.scale=function(){return ro(ao.scale.linear(),Ql,nc)};var ec=Ql.map(function(n){return[n[0].utc,n[1]]}),rc=Jl.multi([[".%L",function(n){return n.getUTCMilliseconds()}],[":%S",function(n){return n.getUTCSeconds()}],["%I:%M",function(n){return n.getUTCMinutes()}],["%I %p",function(n){return n.getUTCHours()}],["%a %d",function(n){return n.getUTCDay()&&1!=n.getUTCDate()}],["%b %d",function(n){return 1!=n.getUTCDate()}],["%B",function(n){return n.getUTCMonth()}],["%Y",zt]]);ec.year=ga.year.utc,ga.scale.utc=function(){return ro(ao.scale.linear(),ec,rc)},ao.text=An(function(n){return n.responseText}),ao.json=function(n,t){return Cn(n,"application/json",uo,t)},ao.html=function(n,t){return Cn(n,"text/html",oo,t)},ao.xml=An(function(n){return n.responseXML}),"function"==typeof define&&define.amd?(this.d3=ao,define(ao)):"object"==typeof module&&module.exports?module.exports=ao:this.d3=ao}(); \ No newline at end of file diff --git a/app/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/js/file.js b/app/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/js/file.js index 29cacd4d1..124a8a18f 100644 --- a/app/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/js/file.js +++ b/app/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/js/file.js @@ -1,62 +1,53 @@ - $(function() { - var $window = $(window) - , $top_link = $('#toplink') - , $body = $('body, html') - , offset = $('#code').offset().top - , hidePopover = function ($target) { - $target.data('popover-hover', false); - - setTimeout(function () { - if (!$target.data('popover-hover')) { - $target.popover('hide'); - } - }, 300); - }; - - $top_link.hide().click(function(event) { +$(function () { + var $window = $(window) + , $top_link = $('#toplink') + , $body = $('body, html') + , offset = $('#code').offset().top; + + $top_link.hide().click(function (event) { event.preventDefault(); - $body.animate({scrollTop:0}, 800); - }); + $body.animate({scrollTop: 0}, 800); + }); - $window.scroll(function() { - if($window.scrollTop() > offset) { - $top_link.fadeIn(); + $window.scroll(function () { + if ($window.scrollTop() > offset) { + $top_link.fadeIn(); } else { - $top_link.fadeOut(); + $top_link.fadeOut(); } - }).scroll(); - - $('.popin') - .popover({trigger: 'manual'}) - .on({ - 'mouseenter.popover': function () { - var $target = $(this); - var $container = $target.children().first(); - - $target.data('popover-hover', true); - - // popover already displayed - if ($target.next('.popover').length) { - return; - } - - // show the popover - $container.popover('show'); - - // register mouse events on the popover - $target.next('.popover:not(.popover-initialized)') - .on({ - 'mouseenter': function () { - $target.data('popover-hover', true); - }, - 'mouseleave': function () { - hidePopover($container); + }); + + var $popovers = $('.popin > :first-child'); + $('.popin').on({ + 'click.popover': function (event) { + event.stopPropagation(); + + var $container = $(this).children().first(); + + //Close all other popovers: + $popovers.each(function () { + var $current = $(this); + if (!$current.is($container)) { + $current.popover('hide'); } - }) - .addClass('popover-initialized'); - }, - 'mouseleave.popover': function () { - hidePopover($(this).children().first()); - } - }); + }); + + // Toggle this popover: + $container.popover('toggle'); + }, + }); + + //Hide all popovers on outside click: + $(document).click(function (event) { + if ($(event.target).closest($('.popover')).length === 0) { + $popovers.popover('hide'); + } + }); + + //Hide all popovers on escape: + $(document).keyup(function (event) { + if (event.key === 'Escape') { + $popovers.popover('hide'); + } }); +}); diff --git a/app/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/js/jquery.min.js b/app/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/js/jquery.min.js index 2c69bc908..798cc8bf7 100644 --- a/app/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/js/jquery.min.js +++ b/app/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/js/jquery.min.js @@ -1,2 +1,2 @@ -/*! jQuery v3.6.1 | (c) OpenJS Foundation and other contributors | jquery.org/license */ -!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,y=n.hasOwnProperty,a=y.toString,l=a.call(Object),v={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.1",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&v(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!y||!y.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ve(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace(B,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ye(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ve(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],y=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&y.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||y.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||y.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||y.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||y.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||y.push(".#.+[+~]"),e.querySelectorAll("\\\f"),y.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&y.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&y.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&y.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),y.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),y=y.length&&new RegExp(y.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),v=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&v(p,e)?-1:t==C||t.ownerDocument==p&&v(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!y||!y.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),v.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",v.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",v.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ye(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ve(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Ut,Xt=[],Vt=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Xt.pop()||S.expando+"_"+Ct.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Vt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Vt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Vt,"$1"+r):!1!==e.jsonp&&(e.url+=(Et.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Xt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),v.createHTMLDocument=((Ut=E.implementation.createHTMLDocument("").body).innerHTML="
",2===Ut.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(v.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return B(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=_e(v.pixelPosition,function(e,t){if(t)return t=Be(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return B(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0+~]|"+ge+")"+ge+"*"),x=new RegExp(ge+"|>"),j=new RegExp(g),A=new RegExp("^"+t+"$"),D={ID:new RegExp("^#("+t+")"),CLASS:new RegExp("^\\.("+t+")"),TAG:new RegExp("^("+t+"|[*])"),ATTR:new RegExp("^"+p),PSEUDO:new RegExp("^"+g),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+ge+"*(even|odd|(([+-]|)(\\d*)n|)"+ge+"*(?:([+-]|)"+ge+"*(\\d+)|))"+ge+"*\\)|)","i"),bool:new RegExp("^(?:"+f+")$","i"),needsContext:new RegExp("^"+ge+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+ge+"*((?:-\\d)?\\d*)"+ge+"*\\)|)(?=[^-]|$)","i")},N=/^(?:input|select|textarea|button)$/i,q=/^h\d$/i,L=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,H=/[+~]/,O=new RegExp("\\\\[\\da-fA-F]{1,6}"+ge+"?|\\\\([^\\r\\n\\f])","g"),P=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},M=function(){V()},R=J(function(e){return!0===e.disabled&&fe(e,"fieldset")},{dir:"parentNode",next:"legend"});try{k.apply(oe=ae.call(ye.childNodes),ye.childNodes),oe[ye.childNodes.length].nodeType}catch(e){k={apply:function(e,t){me.apply(e,ae.call(t))},call:function(e){me.apply(e,ae.call(arguments,1))}}}function I(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(V(e),e=e||T,C)){if(11!==p&&(u=L.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return k.call(n,a),n}else if(f&&(a=f.getElementById(i))&&I.contains(e,a)&&a.id===i)return k.call(n,a),n}else{if(u[2])return k.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&e.getElementsByClassName)return k.apply(n,e.getElementsByClassName(i)),n}if(!(h[t+" "]||d&&d.test(t))){if(c=t,f=e,1===p&&(x.test(t)||m.test(t))){(f=H.test(t)&&U(e.parentNode)||e)==e&&le.scope||((s=e.getAttribute("id"))?s=ce.escapeSelector(s):e.setAttribute("id",s=S)),o=(l=Y(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+Q(l[o]);c=l.join(",")}try{return k.apply(n,f.querySelectorAll(c)),n}catch(e){h(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return re(t.replace(ve,"$1"),e,n,r)}function W(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function F(e){return e[S]=!0,e}function $(e){var t=T.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function B(t){return function(e){return fe(e,"input")&&e.type===t}}function _(t){return function(e){return(fe(e,"input")||fe(e,"button"))&&e.type===t}}function z(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&R(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function X(a){return F(function(o){return o=+o,F(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function U(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}function V(e){var t,n=e?e.ownerDocument||e:ye;return n!=T&&9===n.nodeType&&n.documentElement&&(r=(T=n).documentElement,C=!ce.isXMLDoc(T),i=r.matches||r.webkitMatchesSelector||r.msMatchesSelector,r.msMatchesSelector&&ye!=T&&(t=T.defaultView)&&t.top!==t&&t.addEventListener("unload",M),le.getById=$(function(e){return r.appendChild(e).id=ce.expando,!T.getElementsByName||!T.getElementsByName(ce.expando).length}),le.disconnectedMatch=$(function(e){return i.call(e,"*")}),le.scope=$(function(){return T.querySelectorAll(":scope")}),le.cssHas=$(function(){try{return T.querySelector(":has(*,:jqfake)"),!1}catch(e){return!0}}),le.getById?(b.filter.ID=function(e){var t=e.replace(O,P);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&C){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(O,P);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&C){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):t.querySelectorAll(e)},b.find.CLASS=function(e,t){if("undefined"!=typeof t.getElementsByClassName&&C)return t.getElementsByClassName(e)},d=[],$(function(e){var t;r.appendChild(e).innerHTML="",e.querySelectorAll("[selected]").length||d.push("\\["+ge+"*(?:value|"+f+")"),e.querySelectorAll("[id~="+S+"-]").length||d.push("~="),e.querySelectorAll("a#"+S+"+*").length||d.push(".#.+[+~]"),e.querySelectorAll(":checked").length||d.push(":checked"),(t=T.createElement("input")).setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),r.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&d.push(":enabled",":disabled"),(t=T.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||d.push("\\["+ge+"*name"+ge+"*="+ge+"*(?:''|\"\")")}),le.cssHas||d.push(":has"),d=d.length&&new RegExp(d.join("|")),l=function(e,t){if(e===t)return a=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!le.sortDetached&&t.compareDocumentPosition(e)===n?e===T||e.ownerDocument==ye&&I.contains(ye,e)?-1:t===T||t.ownerDocument==ye&&I.contains(ye,t)?1:o?se.call(o,e)-se.call(o,t):0:4&n?-1:1)}),T}for(e in I.matches=function(e,t){return I(e,null,null,t)},I.matchesSelector=function(e,t){if(V(e),C&&!h[t+" "]&&(!d||!d.test(t)))try{var n=i.call(e,t);if(n||le.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){h(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(O,P),e[3]=(e[3]||e[4]||e[5]||"").replace(O,P),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||I.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&I.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return D.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&j.test(n)&&(t=Y(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(O,P).toLowerCase();return"*"===e?function(){return!0}:function(e){return fe(e,t)}},CLASS:function(e){var t=s[e+" "];return t||(t=new RegExp("(^|"+ge+")"+e+"("+ge+"|$)"))&&s(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=I.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function T(e,n,r){return v(n)?ce.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?ce.grep(e,function(e){return e===n!==r}):"string"!=typeof n?ce.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(ce.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||k,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:S.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof ce?t[0]:t,ce.merge(this,ce.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:C,!0)),w.test(r[1])&&ce.isPlainObject(t))for(r in t)v(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=C.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):v(e)?void 0!==n.ready?n.ready(e):e(ce):ce.makeArray(e,this)}).prototype=ce.fn,k=ce(C);var E=/^(?:parents|prev(?:Until|All))/,j={children:!0,contents:!0,next:!0,prev:!0};function A(e,t){while((e=e[t])&&1!==e.nodeType);return e}ce.fn.extend({has:function(e){var t=ce(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,Ce=/^$|^module$|\/(?:java|ecma)script/i;xe=C.createDocumentFragment().appendChild(C.createElement("div")),(be=C.createElement("input")).setAttribute("type","radio"),be.setAttribute("checked","checked"),be.setAttribute("name","t"),xe.appendChild(be),le.checkClone=xe.cloneNode(!0).cloneNode(!0).lastChild.checked,xe.innerHTML="",le.noCloneChecked=!!xe.cloneNode(!0).lastChild.defaultValue,xe.innerHTML="",le.option=!!xe.lastChild;var ke={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function Se(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&fe(e,t)?ce.merge([e],n):n}function Ee(e,t){for(var n=0,r=e.length;n",""]);var je=/<|&#?\w+;/;function Ae(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function Re(e,t){return fe(e,"table")&&fe(11!==t.nodeType?t:t.firstChild,"tr")&&ce(e).children("tbody")[0]||e}function Ie(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function We(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Fe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(_.hasData(e)&&(s=_.get(e).events))for(i in _.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),C.head.appendChild(r[0])},abort:function(){i&&i()}}});var Jt,Kt=[],Zt=/(=)\?(?=&|$)|\?\?/;ce.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Kt.pop()||ce.expando+"_"+jt.guid++;return this[e]=!0,e}}),ce.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Zt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Zt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=v(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Zt,"$1"+r):!1!==e.jsonp&&(e.url+=(At.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||ce.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=ie[r],ie[r]=function(){o=arguments},n.always(function(){void 0===i?ce(ie).removeProp(r):ie[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Kt.push(r)),o&&v(i)&&i(o[0]),o=i=void 0}),"script"}),le.createHTMLDocument=((Jt=C.implementation.createHTMLDocument("").body).innerHTML="
",2===Jt.childNodes.length),ce.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(le.createHTMLDocument?((r=(t=C.implementation.createHTMLDocument("")).createElement("base")).href=C.location.href,t.head.appendChild(r)):t=C),o=!n&&[],(i=w.exec(e))?[t.createElement(i[1])]:(i=Ae([e],t,o),o&&o.length&&ce(o).remove(),ce.merge([],i.childNodes)));var r,i,o},ce.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(ce.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},ce.expr.pseudos.animated=function(t){return ce.grep(ce.timers,function(e){return t===e.elem}).length},ce.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=ce.css(e,"position"),c=ce(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=ce.css(e,"top"),u=ce.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),v(t)&&(t=t.call(e,n,ce.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},ce.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){ce.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===ce.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===ce.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=ce(e).offset()).top+=ce.css(e,"borderTopWidth",!0),i.left+=ce.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-ce.css(r,"marginTop",!0),left:t.left-i.left-ce.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===ce.css(e,"position"))e=e.offsetParent;return e||J})}}),ce.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;ce.fn[t]=function(e){return M(this,function(e,t,n){var r;if(y(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),ce.each(["top","left"],function(e,n){ce.cssHooks[n]=Ye(le.pixelPosition,function(e,t){if(t)return t=Ge(e,n),_e.test(t)?ce(e).position()[n]+"px":t})}),ce.each({Height:"height",Width:"width"},function(a,s){ce.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){ce.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return M(this,function(e,t,n){var r;return y(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?ce.css(e,t,i):ce.style(e,t,n,i)},s,n?e:void 0,n)}})}),ce.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){ce.fn[t]=function(e){return this.on(t,e)}}),ce.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.on("mouseenter",e).on("mouseleave",t||e)}}),ce.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){ce.fn[n]=function(e,t){return 0f&&(e=a.render.queue[f]);f++)d=e.generate(),typeof e.callback==typeof Function&&e.callback(d);a.render.queue.splice(0,f),a.render.queue.length?setTimeout(c):(a.dispatch.render_end(),a.render.active=!1)};setTimeout(c)},a.render.active=!1,a.render.queue=[],a.addGraph=function(b){typeof arguments[0]==typeof Function&&(b={generate:arguments[0],callback:arguments[1]}),a.render.queue.push(b),a.render.active||a.render()},"undefined"!=typeof module&&"undefined"!=typeof exports&&(module.exports=a),"undefined"!=typeof window&&(window.nv=a),a.dom.write=function(a){return void 0!==window.fastdom?fastdom.write(a):a()},a.dom.read=function(a){return void 0!==window.fastdom?fastdom.read(a):a()},a.interactiveGuideline=function(){"use strict";function b(l){l.each(function(l){function m(){var a=d3.mouse(this),d=a[0],e=a[1],i=!0,j=!1;if(k&&(d=d3.event.offsetX,e=d3.event.offsetY,"svg"!==d3.event.target.tagName&&(i=!1),d3.event.target.className.baseVal.match("nv-legend")&&(j=!0)),i&&(d-=f.left,e-=f.top),0>d||0>e||d>o||e>p||d3.event.relatedTarget&&void 0===d3.event.relatedTarget.ownerSVGElement||j){if(k&&d3.event.relatedTarget&&void 0===d3.event.relatedTarget.ownerSVGElement&&(void 0===d3.event.relatedTarget.className||d3.event.relatedTarget.className.match(c.nvPointerEventsClass)))return;return h.elementMouseout({mouseX:d,mouseY:e}),b.renderGuideLine(null),void c.hidden(!0)}c.hidden(!1);var l=g.invert(d);h.elementMousemove({mouseX:d,mouseY:e,pointXValue:l}),"dblclick"===d3.event.type&&h.elementDblclick({mouseX:d,mouseY:e,pointXValue:l}),"click"===d3.event.type&&h.elementClick({mouseX:d,mouseY:e,pointXValue:l})}var n=d3.select(this),o=d||960,p=e||400,q=n.selectAll("g.nv-wrap.nv-interactiveLineLayer").data([l]),r=q.enter().append("g").attr("class"," nv-wrap nv-interactiveLineLayer");r.append("g").attr("class","nv-interactiveGuideLine"),j&&(j.on("touchmove",m).on("mousemove",m,!0).on("mouseout",m,!0).on("dblclick",m).on("click",m),b.guideLine=null,b.renderGuideLine=function(c){i&&(b.guideLine&&b.guideLine.attr("x1")===c||a.dom.write(function(){var b=q.select(".nv-interactiveGuideLine").selectAll("line").data(null!=c?[a.utils.NaNtoZero(c)]:[],String);b.enter().append("line").attr("class","nv-guideline").attr("x1",function(a){return a}).attr("x2",function(a){return a}).attr("y1",p).attr("y2",0),b.exit().remove()}))})})}var c=a.models.tooltip();c.duration(0).hideDelay(0)._isInteractiveLayer(!0).hidden(!1);var d=null,e=null,f={left:0,top:0},g=d3.scale.linear(),h=d3.dispatch("elementMousemove","elementMouseout","elementClick","elementDblclick"),i=!0,j=null,k="ActiveXObject"in window;return b.dispatch=h,b.tooltip=c,b.margin=function(a){return arguments.length?(f.top="undefined"!=typeof a.top?a.top:f.top,f.left="undefined"!=typeof a.left?a.left:f.left,b):f},b.width=function(a){return arguments.length?(d=a,b):d},b.height=function(a){return arguments.length?(e=a,b):e},b.xScale=function(a){return arguments.length?(g=a,b):g},b.showGuideLine=function(a){return arguments.length?(i=a,b):i},b.svgContainer=function(a){return arguments.length?(j=a,b):j},b},a.interactiveBisect=function(a,b,c){"use strict";if(!(a instanceof Array))return null;var d;d="function"!=typeof c?function(a){return a.x}:c;var e=function(a,b){return d(a)-b},f=d3.bisector(e).left,g=d3.max([0,f(a,b)-1]),h=d(a[g]);if("undefined"==typeof h&&(h=g),h===b)return g;var i=d3.min([g+1,a.length-1]),j=d(a[i]);return"undefined"==typeof j&&(j=i),Math.abs(j-b)>=Math.abs(h-b)?g:i},a.nearestValueIndex=function(a,b,c){"use strict";var d=1/0,e=null;return a.forEach(function(a,f){var g=Math.abs(b-a);null!=a&&d>=g&&c>g&&(d=g,e=f)}),e},function(){"use strict";a.models.tooltip=function(){function b(){if(k){var a=d3.select(k);"svg"!==a.node().tagName&&(a=a.select("svg"));var b=a.node()?a.attr("viewBox"):null;if(b){b=b.split(" ");var c=parseInt(a.style("width"),10)/b[2];p.left=p.left*c,p.top=p.top*c}}}function c(){if(!n){var a;a=k?k:document.body,n=d3.select(a).append("div").attr("class","nvtooltip "+(j?j:"xy-tooltip")).attr("id",v),n.style("top",0).style("left",0),n.style("opacity",0),n.selectAll("div, table, td, tr").classed(w,!0),n.classed(w,!0),o=n.node()}}function d(){if(r&&B(e)){b();var f=p.left,g=null!==i?i:p.top;return a.dom.write(function(){c();var b=A(e);b&&(o.innerHTML=b),k&&u?a.dom.read(function(){var a=k.getElementsByTagName("svg")[0],b={left:0,top:0};if(a){var c=a.getBoundingClientRect(),d=k.getBoundingClientRect(),e=c.top;if(0>e){var i=k.getBoundingClientRect();e=Math.abs(e)>i.height?0:e}b.top=Math.abs(e-d.top),b.left=Math.abs(c.left-d.left)}f+=k.offsetLeft+b.left-2*k.scrollLeft,g+=k.offsetTop+b.top-2*k.scrollTop,h&&h>0&&(g=Math.floor(g/h)*h),C([f,g])}):C([f,g])}),d}}var e=null,f="w",g=25,h=0,i=null,j=null,k=null,l=!0,m=400,n=null,o=null,p={left:null,top:null},q={left:0,top:0},r=!0,s=100,t=!0,u=!1,v="nvtooltip-"+Math.floor(1e5*Math.random()),w="nv-pointer-events-none",x=function(a){return a},y=function(a){return a},z=function(a){return a},A=function(a){if(null===a)return"";var b=d3.select(document.createElement("table"));if(t){var c=b.selectAll("thead").data([a]).enter().append("thead");c.append("tr").append("td").attr("colspan",3).append("strong").classed("x-value",!0).html(y(a.value))}var d=b.selectAll("tbody").data([a]).enter().append("tbody"),e=d.selectAll("tr").data(function(a){return a.series}).enter().append("tr").classed("highlight",function(a){return a.highlight});e.append("td").classed("legend-color-guide",!0).append("div").style("background-color",function(a){return a.color}),e.append("td").classed("key",!0).html(function(a,b){return z(a.key,b)}),e.append("td").classed("value",!0).html(function(a,b){return x(a.value,b)}),e.selectAll("td").each(function(a){if(a.highlight){var b=d3.scale.linear().domain([0,1]).range(["#fff",a.color]),c=.6;d3.select(this).style("border-bottom-color",b(c)).style("border-top-color",b(c))}});var f=b.node().outerHTML;return void 0!==a.footer&&(f+=""),f},B=function(a){if(a&&a.series){if(a.series instanceof Array)return!!a.series.length;if(a.series instanceof Object)return a.series=[a.series],!0}return!1},C=function(b){o&&a.dom.read(function(){var c,d,e=parseInt(o.offsetHeight,10),h=parseInt(o.offsetWidth,10),i=a.utils.windowSize().width,j=a.utils.windowSize().height,k=window.pageYOffset,p=window.pageXOffset;j=window.innerWidth>=document.body.scrollWidth?j:j-16,i=window.innerHeight>=document.body.scrollHeight?i:i-16;var r,t,u=function(a){var b=d;do isNaN(a.offsetTop)||(b+=a.offsetTop),a=a.offsetParent;while(a);return b},v=function(a){var b=c;do isNaN(a.offsetLeft)||(b+=a.offsetLeft),a=a.offsetParent;while(a);return b};switch(f){case"e":c=b[0]-h-g,d=b[1]-e/2,r=v(o),t=u(o),p>r&&(c=b[0]+g>p?b[0]+g:p-r+c),k>t&&(d=k-t+d),t+e>k+j&&(d=k+j-t+d-e);break;case"w":c=b[0]+g,d=b[1]-e/2,r=v(o),t=u(o),r+h>i&&(c=b[0]-h-g),k>t&&(d=k+5),t+e>k+j&&(d=k+j-t+d-e);break;case"n":c=b[0]-h/2-5,d=b[1]+g,r=v(o),t=u(o),p>r&&(c=p+5),r+h>i&&(c=c-h/2+5),t+e>k+j&&(d=k+j-t+d-e);break;case"s":c=b[0]-h/2,d=b[1]-e-g,r=v(o),t=u(o),p>r&&(c=p+5),r+h>i&&(c=c-h/2+5),k>t&&(d=k);break;case"none":c=b[0],d=b[1]-g,r=v(o),t=u(o)}c-=q.left,d-=q.top;var w=o.getBoundingClientRect(),k=window.pageYOffset||document.documentElement.scrollTop,p=window.pageXOffset||document.documentElement.scrollLeft,x="translate("+(w.left+p)+"px, "+(w.top+k)+"px)",y="translate("+c+"px, "+d+"px)",z=d3.interpolateString(x,y),A=n.style("opacity")<.1;l?n.transition().delay(m).duration(0).style("opacity",0):n.interrupt().transition().duration(A?0:s).styleTween("transform",function(){return z},"important").style("-webkit-transform",y).style("opacity",1)})};return d.nvPointerEventsClass=w,d.options=a.utils.optionsFunc.bind(d),d._options=Object.create({},{duration:{get:function(){return s},set:function(a){s=a}},gravity:{get:function(){return f},set:function(a){f=a}},distance:{get:function(){return g},set:function(a){g=a}},snapDistance:{get:function(){return h},set:function(a){h=a}},classes:{get:function(){return j},set:function(a){j=a}},chartContainer:{get:function(){return k},set:function(a){k=a}},fixedTop:{get:function(){return i},set:function(a){i=a}},enabled:{get:function(){return r},set:function(a){r=a}},hideDelay:{get:function(){return m},set:function(a){m=a}},contentGenerator:{get:function(){return A},set:function(a){A=a}},valueFormatter:{get:function(){return x},set:function(a){x=a}},headerFormatter:{get:function(){return y},set:function(a){y=a}},keyFormatter:{get:function(){return z},set:function(a){z=a}},headerEnabled:{get:function(){return t},set:function(a){t=a}},_isInteractiveLayer:{get:function(){return u},set:function(a){u=!!a}},position:{get:function(){return p},set:function(a){p.left=void 0!==a.left?a.left:p.left,p.top=void 0!==a.top?a.top:p.top}},offset:{get:function(){return q},set:function(a){q.left=void 0!==a.left?a.left:q.left,q.top=void 0!==a.top?a.top:q.top}},hidden:{get:function(){return l},set:function(a){l!=a&&(l=!!a,d())}},data:{get:function(){return e},set:function(a){a.point&&(a.value=a.point.x,a.series=a.series||{},a.series.value=a.point.y,a.series.color=a.point.color||a.series.color),e=a}},tooltipElem:{get:function(){return o},set:function(){}},id:{get:function(){return v},set:function(){}}}),a.utils.initOptions(d),d}}(),a.utils.windowSize=function(){var a={width:640,height:480};return window.innerWidth&&window.innerHeight?(a.width=window.innerWidth,a.height=window.innerHeight,a):"CSS1Compat"==document.compatMode&&document.documentElement&&document.documentElement.offsetWidth?(a.width=document.documentElement.offsetWidth,a.height=document.documentElement.offsetHeight,a):document.body&&document.body.offsetWidth?(a.width=document.body.offsetWidth,a.height=document.body.offsetHeight,a):a},a.utils.windowResize=function(b){return window.addEventListener?window.addEventListener("resize",b):a.log("ERROR: Failed to bind to window.resize with: ",b),{callback:b,clear:function(){window.removeEventListener("resize",b)}}},a.utils.getColor=function(b){if(void 0===b)return a.utils.defaultColor();if(Array.isArray(b)){var c=d3.scale.ordinal().range(b);return function(a,b){var d=void 0===b?a:b;return a.color||c(d)}}return b},a.utils.defaultColor=function(){return a.utils.getColor(d3.scale.category20().range())},a.utils.customTheme=function(a,b,c){b=b||function(a){return a.key},c=c||d3.scale.category20().range();var d=c.length;return function(e){var f=b(e);return"function"==typeof a[f]?a[f]():void 0!==a[f]?a[f]:(d||(d=c.length),d-=1,c[d])}},a.utils.pjax=function(b,c){var d=function(d){d3.html(d,function(d){var e=d3.select(c).node();e.parentNode.replaceChild(d3.select(d).select(c).node(),e),a.utils.pjax(b,c)})};d3.selectAll(b).on("click",function(){history.pushState(this.href,this.textContent,this.href),d(this.href),d3.event.preventDefault()}),d3.select(window).on("popstate",function(){d3.event.state&&d(d3.event.state)})},a.utils.calcApproxTextWidth=function(a){if("function"==typeof a.style&&"function"==typeof a.text){var b=parseInt(a.style("font-size").replace("px",""),10),c=a.text().length;return c*b*.5}return 0},a.utils.NaNtoZero=function(a){return"number"!=typeof a||isNaN(a)||null===a||1/0===a||a===-1/0?0:a},d3.selection.prototype.watchTransition=function(a){var b=[this].concat([].slice.call(arguments,1));return a.transition.apply(a,b)},a.utils.renderWatch=function(b,c){if(!(this instanceof a.utils.renderWatch))return new a.utils.renderWatch(b,c);var d=void 0!==c?c:250,e=[],f=this;this.models=function(a){return a=[].slice.call(arguments,0),a.forEach(function(a){a.__rendered=!1,function(a){a.dispatch.on("renderEnd",function(){a.__rendered=!0,f.renderEnd("model")})}(a),e.indexOf(a)<0&&e.push(a)}),this},this.reset=function(a){void 0!==a&&(d=a),e=[]},this.transition=function(a,b,c){if(b=arguments.length>1?[].slice.call(arguments,1):[],c=b.length>1?b.pop():void 0!==d?d:250,a.__rendered=!1,e.indexOf(a)<0&&e.push(a),0===c)return a.__rendered=!0,a.delay=function(){return this},a.duration=function(){return this},a;a.__rendered=0===a.length?!0:a.every(function(a){return!a.length})?!0:!1;var g=0;return a.transition().duration(c).each(function(){++g}).each("end",function(){0===--g&&(a.__rendered=!0,f.renderEnd.apply(this,b))})},this.renderEnd=function(){e.every(function(a){return a.__rendered})&&(e.forEach(function(a){a.__rendered=!1}),b.renderEnd.apply(this,arguments))}},a.utils.deepExtend=function(b){var c=arguments.length>1?[].slice.call(arguments,1):[];c.forEach(function(c){for(var d in c){var e=b[d]instanceof Array,f="object"==typeof b[d],g="object"==typeof c[d];f&&!e&&g?a.utils.deepExtend(b[d],c[d]):b[d]=c[d]}})},a.utils.state=function(){if(!(this instanceof a.utils.state))return new a.utils.state;var b={},c=function(){},d=function(){return{}},e=null,f=null;this.dispatch=d3.dispatch("change","set"),this.dispatch.on("set",function(a){c(a,!0)}),this.getter=function(a){return d=a,this},this.setter=function(a,b){return b||(b=function(){}),c=function(c,d){a(c),d&&b()},this},this.init=function(b){e=e||{},a.utils.deepExtend(e,b)};var g=function(){var a=d();if(JSON.stringify(a)===JSON.stringify(b))return!1;for(var c in a)void 0===b[c]&&(b[c]={}),b[c]=a[c],f=!0;return!0};this.update=function(){e&&(c(e,!1),e=null),g.call(this)&&this.dispatch.change(b)}},a.utils.optionsFunc=function(a){return a&&d3.map(a).forEach(function(a,b){"function"==typeof this[a]&&this[a](b)}.bind(this)),this},a.utils.calcTicksX=function(b,c){var d=1,e=0;for(e;ed?f:d}return a.log("Requested number of ticks: ",b),a.log("Calculated max values to be: ",d),b=b>d?b=d-1:b,b=1>b?1:b,b=Math.floor(b),a.log("Calculating tick count as: ",b),b},a.utils.calcTicksY=function(b,c){return a.utils.calcTicksX(b,c)},a.utils.initOption=function(a,b){a._calls&&a._calls[b]?a[b]=a._calls[b]:(a[b]=function(c){return arguments.length?(a._overrides[b]=!0,a._options[b]=c,a):a._options[b]},a["_"+b]=function(c){return arguments.length?(a._overrides[b]||(a._options[b]=c),a):a._options[b]})},a.utils.initOptions=function(b){b._overrides=b._overrides||{};var c=Object.getOwnPropertyNames(b._options||{}),d=Object.getOwnPropertyNames(b._calls||{});c=c.concat(d);for(var e in c)a.utils.initOption(b,c[e])},a.utils.inheritOptionsD3=function(a,b,c){a._d3options=c.concat(a._d3options||[]),c.unshift(b),c.unshift(a),d3.rebind.apply(this,c)},a.utils.arrayUnique=function(a){return a.sort().filter(function(b,c){return!c||b!=a[c-1]})},a.utils.symbolMap=d3.map(),a.utils.symbol=function(){function b(b,e){var f=c.call(this,b,e),g=d.call(this,b,e);return-1!==d3.svg.symbolTypes.indexOf(f)?d3.svg.symbol().type(f).size(g)():a.utils.symbolMap.get(f)(g)}var c,d=64;return b.type=function(a){return arguments.length?(c=d3.functor(a),b):c},b.size=function(a){return arguments.length?(d=d3.functor(a),b):d},b},a.utils.inheritOptions=function(b,c){var d=Object.getOwnPropertyNames(c._options||{}),e=Object.getOwnPropertyNames(c._calls||{}),f=c._inherited||[],g=c._d3options||[],h=d.concat(e).concat(f).concat(g);h.unshift(c),h.unshift(b),d3.rebind.apply(this,h),b._inherited=a.utils.arrayUnique(d.concat(e).concat(f).concat(d).concat(b._inherited||[])),b._d3options=a.utils.arrayUnique(g.concat(b._d3options||[]))},a.utils.initSVG=function(a){a.classed({"nvd3-svg":!0})},a.utils.sanitizeHeight=function(a,b){return a||parseInt(b.style("height"),10)||400},a.utils.sanitizeWidth=function(a,b){return a||parseInt(b.style("width"),10)||960},a.utils.availableHeight=function(b,c,d){return a.utils.sanitizeHeight(b,c)-d.top-d.bottom},a.utils.availableWidth=function(b,c,d){return a.utils.sanitizeWidth(b,c)-d.left-d.right},a.utils.noData=function(b,c){var d=b.options(),e=d.margin(),f=d.noData(),g=null==f?["No Data Available."]:[f],h=a.utils.availableHeight(d.height(),c,e),i=a.utils.availableWidth(d.width(),c,e),j=e.left+i/2,k=e.top+h/2;c.selectAll("g").remove();var l=c.selectAll(".nv-noData").data(g);l.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),l.attr("x",j).attr("y",k).text(function(a){return a})},a.models.axis=function(){"use strict";function b(g){return s.reset(),g.each(function(b){var g=d3.select(this);a.utils.initSVG(g);var p=g.selectAll("g.nv-wrap.nv-axis").data([b]),q=p.enter().append("g").attr("class","nvd3 nv-wrap nv-axis"),t=(q.append("g"),p.select("g"));null!==n?c.ticks(n):("top"==c.orient()||"bottom"==c.orient())&&c.ticks(Math.abs(d.range()[1]-d.range()[0])/100),t.watchTransition(s,"axis").call(c),r=r||c.scale();var u=c.tickFormat();null==u&&(u=r.tickFormat());var v=t.selectAll("text.nv-axislabel").data([h||null]);v.exit().remove();var w,x,y;switch(c.orient()){case"top":v.enter().append("text").attr("class","nv-axislabel"),y=d.range().length<2?0:2===d.range().length?d.range()[1]:d.range()[d.range().length-1]+(d.range()[1]-d.range()[0]),v.attr("text-anchor","middle").attr("y",0).attr("x",y/2),i&&(x=p.selectAll("g.nv-axisMaxMin").data(d.domain()),x.enter().append("g").attr("class",function(a,b){return["nv-axisMaxMin","nv-axisMaxMin-x",0==b?"nv-axisMin-x":"nv-axisMax-x"].join(" ")}).append("text"),x.exit().remove(),x.attr("transform",function(b){return"translate("+a.utils.NaNtoZero(d(b))+",0)"}).select("text").attr("dy","-0.5em").attr("y",-c.tickPadding()).attr("text-anchor","middle").text(function(a){var b=u(a);return(""+b).match("NaN")?"":b}),x.watchTransition(s,"min-max top").attr("transform",function(b,c){return"translate("+a.utils.NaNtoZero(d.range()[c])+",0)"}));break;case"bottom":w=o+36;var z=30,A=0,B=t.selectAll("g").select("text"),C="";if(j%360){B.each(function(){var a=this.getBoundingClientRect(),b=a.width;A=a.height,b>z&&(z=b)}),C="rotate("+j+" 0,"+(A/2+c.tickPadding())+")";var D=Math.abs(Math.sin(j*Math.PI/180));w=(D?D*z:z)+30,B.attr("transform",C).style("text-anchor",j%360>0?"start":"end")}v.enter().append("text").attr("class","nv-axislabel"),y=d.range().length<2?0:2===d.range().length?d.range()[1]:d.range()[d.range().length-1]+(d.range()[1]-d.range()[0]),v.attr("text-anchor","middle").attr("y",w).attr("x",y/2),i&&(x=p.selectAll("g.nv-axisMaxMin").data([d.domain()[0],d.domain()[d.domain().length-1]]),x.enter().append("g").attr("class",function(a,b){return["nv-axisMaxMin","nv-axisMaxMin-x",0==b?"nv-axisMin-x":"nv-axisMax-x"].join(" ")}).append("text"),x.exit().remove(),x.attr("transform",function(b){return"translate("+a.utils.NaNtoZero(d(b)+(m?d.rangeBand()/2:0))+",0)"}).select("text").attr("dy",".71em").attr("y",c.tickPadding()).attr("transform",C).style("text-anchor",j?j%360>0?"start":"end":"middle").text(function(a){var b=u(a);return(""+b).match("NaN")?"":b}),x.watchTransition(s,"min-max bottom").attr("transform",function(b){return"translate("+a.utils.NaNtoZero(d(b)+(m?d.rangeBand()/2:0))+",0)"})),l&&B.attr("transform",function(a,b){return"translate(0,"+(b%2==0?"0":"12")+")"});break;case"right":v.enter().append("text").attr("class","nv-axislabel"),v.style("text-anchor",k?"middle":"begin").attr("transform",k?"rotate(90)":"").attr("y",k?-Math.max(e.right,f)+12:-10).attr("x",k?d3.max(d.range())/2:c.tickPadding()),i&&(x=p.selectAll("g.nv-axisMaxMin").data(d.domain()),x.enter().append("g").attr("class",function(a,b){return["nv-axisMaxMin","nv-axisMaxMin-y",0==b?"nv-axisMin-y":"nv-axisMax-y"].join(" ")}).append("text").style("opacity",0),x.exit().remove(),x.attr("transform",function(b){return"translate(0,"+a.utils.NaNtoZero(d(b))+")"}).select("text").attr("dy",".32em").attr("y",0).attr("x",c.tickPadding()).style("text-anchor","start").text(function(a){var b=u(a);return(""+b).match("NaN")?"":b}),x.watchTransition(s,"min-max right").attr("transform",function(b,c){return"translate(0,"+a.utils.NaNtoZero(d.range()[c])+")"}).select("text").style("opacity",1));break;case"left":v.enter().append("text").attr("class","nv-axislabel"),v.style("text-anchor",k?"middle":"end").attr("transform",k?"rotate(-90)":"").attr("y",k?-Math.max(e.left,f)+25-(o||0):-10).attr("x",k?-d3.max(d.range())/2:-c.tickPadding()),i&&(x=p.selectAll("g.nv-axisMaxMin").data(d.domain()),x.enter().append("g").attr("class",function(a,b){return["nv-axisMaxMin","nv-axisMaxMin-y",0==b?"nv-axisMin-y":"nv-axisMax-y"].join(" ")}).append("text").style("opacity",0),x.exit().remove(),x.attr("transform",function(b){return"translate(0,"+a.utils.NaNtoZero(r(b))+")"}).select("text").attr("dy",".32em").attr("y",0).attr("x",-c.tickPadding()).attr("text-anchor","end").text(function(a){var b=u(a);return(""+b).match("NaN")?"":b}),x.watchTransition(s,"min-max right").attr("transform",function(b,c){return"translate(0,"+a.utils.NaNtoZero(d.range()[c])+")"}).select("text").style("opacity",1))}if(v.text(function(a){return a}),!i||"left"!==c.orient()&&"right"!==c.orient()||(t.selectAll("g").each(function(a){d3.select(this).select("text").attr("opacity",1),(d(a)d.range()[0]-10)&&((a>1e-10||-1e-10>a)&&d3.select(this).attr("opacity",0),d3.select(this).select("text").attr("opacity",0))}),d.domain()[0]==d.domain()[1]&&0==d.domain()[0]&&p.selectAll("g.nv-axisMaxMin").style("opacity",function(a,b){return b?0:1})),i&&("top"===c.orient()||"bottom"===c.orient())){var E=[];p.selectAll("g.nv-axisMaxMin").each(function(a,b){try{E.push(b?d(a)-this.getBoundingClientRect().width-4:d(a)+this.getBoundingClientRect().width+4)}catch(c){E.push(b?d(a)-4:d(a)+4)}}),t.selectAll("g").each(function(a){(d(a)E[1])&&(a>1e-10||-1e-10>a?d3.select(this).remove():d3.select(this).select("text").remove())})}t.selectAll(".tick").filter(function(a){return!parseFloat(Math.round(1e5*a)/1e6)&&void 0!==a}).classed("zero",!0),r=d.copy()}),s.renderEnd("axis immediate"),b}var c=d3.svg.axis(),d=d3.scale.linear(),e={top:0,right:0,bottom:0,left:0},f=75,g=60,h=null,i=!0,j=0,k=!0,l=!1,m=!1,n=null,o=0,p=250,q=d3.dispatch("renderEnd");c.scale(d).orient("bottom").tickFormat(function(a){return a});var r,s=a.utils.renderWatch(q,p);return b.axis=c,b.dispatch=q,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{axisLabelDistance:{get:function(){return o},set:function(a){o=a}},staggerLabels:{get:function(){return l},set:function(a){l=a}},rotateLabels:{get:function(){return j},set:function(a){j=a}},rotateYLabel:{get:function(){return k},set:function(a){k=a}},showMaxMin:{get:function(){return i},set:function(a){i=a}},axisLabel:{get:function(){return h},set:function(a){h=a}},height:{get:function(){return g},set:function(a){g=a}},ticks:{get:function(){return n},set:function(a){n=a}},width:{get:function(){return f},set:function(a){f=a}},margin:{get:function(){return e},set:function(a){e.top=void 0!==a.top?a.top:e.top,e.right=void 0!==a.right?a.right:e.right,e.bottom=void 0!==a.bottom?a.bottom:e.bottom,e.left=void 0!==a.left?a.left:e.left}},duration:{get:function(){return p},set:function(a){p=a,s.reset(p)}},scale:{get:function(){return d},set:function(e){d=e,c.scale(d),m="function"==typeof d.rangeBands,a.utils.inheritOptionsD3(b,d,["domain","range","rangeBand","rangeBands"])}}}),a.utils.initOptions(b),a.utils.inheritOptionsD3(b,c,["orient","tickValues","tickSubdivide","tickSize","tickPadding","tickFormat"]),a.utils.inheritOptionsD3(b,d,["domain","range","rangeBand","rangeBands"]),b},a.models.boxPlot=function(){"use strict";function b(l){return v.reset(),l.each(function(b){var l=j-i.left-i.right,p=k-i.top-i.bottom;r=d3.select(this),a.utils.initSVG(r),m.domain(c||b.map(function(a,b){return o(a,b)})).rangeBands(e||[0,l],.1);var w=[];if(!d){var x=d3.min(b.map(function(a){var b=[];return b.push(a.values.Q1),a.values.hasOwnProperty("whisker_low")&&null!==a.values.whisker_low&&b.push(a.values.whisker_low),a.values.hasOwnProperty("outliers")&&null!==a.values.outliers&&(b=b.concat(a.values.outliers)),d3.min(b)})),y=d3.max(b.map(function(a){var b=[];return b.push(a.values.Q3),a.values.hasOwnProperty("whisker_high")&&null!==a.values.whisker_high&&b.push(a.values.whisker_high),a.values.hasOwnProperty("outliers")&&null!==a.values.outliers&&(b=b.concat(a.values.outliers)),d3.max(b)}));w=[x,y]}n.domain(d||w),n.range(f||[p,0]),g=g||m,h=h||n.copy().range([n(0),n(0)]);{var z=r.selectAll("g.nv-wrap").data([b]);z.enter().append("g").attr("class","nvd3 nv-wrap")}z.attr("transform","translate("+i.left+","+i.top+")");var A=z.selectAll(".nv-boxplot").data(function(a){return a}),B=A.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6);A.attr("class","nv-boxplot").attr("transform",function(a,b){return"translate("+(m(o(a,b))+.05*m.rangeBand())+", 0)"}).classed("hover",function(a){return a.hover}),A.watchTransition(v,"nv-boxplot: boxplots").style("stroke-opacity",1).style("fill-opacity",.75).delay(function(a,c){return c*t/b.length}).attr("transform",function(a,b){return"translate("+(m(o(a,b))+.05*m.rangeBand())+", 0)"}),A.exit().remove(),B.each(function(a,b){var c=d3.select(this);["low","high"].forEach(function(d){a.values.hasOwnProperty("whisker_"+d)&&null!==a.values["whisker_"+d]&&(c.append("line").style("stroke",a.color?a.color:q(a,b)).attr("class","nv-boxplot-whisker nv-boxplot-"+d),c.append("line").style("stroke",a.color?a.color:q(a,b)).attr("class","nv-boxplot-tick nv-boxplot-"+d))})});var C=A.selectAll(".nv-boxplot-outlier").data(function(a){return a.values.hasOwnProperty("outliers")&&null!==a.values.outliers?a.values.outliers:[]});C.enter().append("circle").style("fill",function(a,b,c){return q(a,c)}).style("stroke",function(a,b,c){return q(a,c)}).on("mouseover",function(a,b,c){d3.select(this).classed("hover",!0),s.elementMouseover({series:{key:a,color:q(a,c)},e:d3.event})}).on("mouseout",function(a,b,c){d3.select(this).classed("hover",!1),s.elementMouseout({series:{key:a,color:q(a,c)},e:d3.event})}).on("mousemove",function(){s.elementMousemove({e:d3.event})}),C.attr("class","nv-boxplot-outlier"),C.watchTransition(v,"nv-boxplot: nv-boxplot-outlier").attr("cx",.45*m.rangeBand()).attr("cy",function(a){return n(a)}).attr("r","3"),C.exit().remove();var D=function(){return null===u?.9*m.rangeBand():Math.min(75,.9*m.rangeBand())},E=function(){return.45*m.rangeBand()-D()/2},F=function(){return.45*m.rangeBand()+D()/2};["low","high"].forEach(function(a){var b="low"===a?"Q1":"Q3";A.select("line.nv-boxplot-whisker.nv-boxplot-"+a).watchTransition(v,"nv-boxplot: boxplots").attr("x1",.45*m.rangeBand()).attr("y1",function(b){return n(b.values["whisker_"+a])}).attr("x2",.45*m.rangeBand()).attr("y2",function(a){return n(a.values[b])}),A.select("line.nv-boxplot-tick.nv-boxplot-"+a).watchTransition(v,"nv-boxplot: boxplots").attr("x1",E).attr("y1",function(b){return n(b.values["whisker_"+a])}).attr("x2",F).attr("y2",function(b){return n(b.values["whisker_"+a])})}),["low","high"].forEach(function(a){B.selectAll(".nv-boxplot-"+a).on("mouseover",function(b,c,d){d3.select(this).classed("hover",!0),s.elementMouseover({series:{key:b.values["whisker_"+a],color:q(b,d)},e:d3.event})}).on("mouseout",function(b,c,d){d3.select(this).classed("hover",!1),s.elementMouseout({series:{key:b.values["whisker_"+a],color:q(b,d)},e:d3.event})}).on("mousemove",function(){s.elementMousemove({e:d3.event})})}),B.append("rect").attr("class","nv-boxplot-box").on("mouseover",function(a,b){d3.select(this).classed("hover",!0),s.elementMouseover({key:a.label,value:a.label,series:[{key:"Q3",value:a.values.Q3,color:a.color||q(a,b)},{key:"Q2",value:a.values.Q2,color:a.color||q(a,b)},{key:"Q1",value:a.values.Q1,color:a.color||q(a,b)}],data:a,index:b,e:d3.event})}).on("mouseout",function(a,b){d3.select(this).classed("hover",!1),s.elementMouseout({key:a.label,value:a.label,series:[{key:"Q3",value:a.values.Q3,color:a.color||q(a,b)},{key:"Q2",value:a.values.Q2,color:a.color||q(a,b)},{key:"Q1",value:a.values.Q1,color:a.color||q(a,b)}],data:a,index:b,e:d3.event})}).on("mousemove",function(){s.elementMousemove({e:d3.event})}),A.select("rect.nv-boxplot-box").watchTransition(v,"nv-boxplot: boxes").attr("y",function(a){return n(a.values.Q3)}).attr("width",D).attr("x",E).attr("height",function(a){return Math.abs(n(a.values.Q3)-n(a.values.Q1))||1}).style("fill",function(a,b){return a.color||q(a,b)}).style("stroke",function(a,b){return a.color||q(a,b)}),B.append("line").attr("class","nv-boxplot-median"),A.select("line.nv-boxplot-median").watchTransition(v,"nv-boxplot: boxplots line").attr("x1",E).attr("y1",function(a){return n(a.values.Q2)}).attr("x2",F).attr("y2",function(a){return n(a.values.Q2)}),g=m.copy(),h=n.copy()}),v.renderEnd("nv-boxplot immediate"),b}var c,d,e,f,g,h,i={top:0,right:0,bottom:0,left:0},j=960,k=500,l=Math.floor(1e4*Math.random()),m=d3.scale.ordinal(),n=d3.scale.linear(),o=function(a){return a.x},p=function(a){return a.y},q=a.utils.defaultColor(),r=null,s=d3.dispatch("elementMouseover","elementMouseout","elementMousemove","renderEnd"),t=250,u=null,v=a.utils.renderWatch(s,t);return b.dispatch=s,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return j},set:function(a){j=a}},height:{get:function(){return k},set:function(a){k=a}},maxBoxWidth:{get:function(){return u},set:function(a){u=a}},x:{get:function(){return o},set:function(a){o=a}},y:{get:function(){return p},set:function(a){p=a}},xScale:{get:function(){return m},set:function(a){m=a}},yScale:{get:function(){return n},set:function(a){n=a}},xDomain:{get:function(){return c},set:function(a){c=a}},yDomain:{get:function(){return d},set:function(a){d=a}},xRange:{get:function(){return e},set:function(a){e=a}},yRange:{get:function(){return f},set:function(a){f=a}},id:{get:function(){return l},set:function(a){l=a}},margin:{get:function(){return i},set:function(a){i.top=void 0!==a.top?a.top:i.top,i.right=void 0!==a.right?a.right:i.right,i.bottom=void 0!==a.bottom?a.bottom:i.bottom,i.left=void 0!==a.left?a.left:i.left}},color:{get:function(){return q},set:function(b){q=a.utils.getColor(b)}},duration:{get:function(){return t},set:function(a){t=a,v.reset(t)}}}),a.utils.initOptions(b),b},a.models.boxPlotChart=function(){"use strict";function b(k){return t.reset(),t.models(e),l&&t.models(f),m&&t.models(g),k.each(function(k){var p=d3.select(this);a.utils.initSVG(p);var t=(i||parseInt(p.style("width"))||960)-h.left-h.right,u=(j||parseInt(p.style("height"))||400)-h.top-h.bottom;if(b.update=function(){r.beforeUpdate(),p.transition().duration(s).call(b)},b.container=this,!(k&&k.length&&k.filter(function(a){return a.values.hasOwnProperty("Q1")&&a.values.hasOwnProperty("Q2")&&a.values.hasOwnProperty("Q3")}).length)){var v=p.selectAll(".nv-noData").data([q]);return v.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),v.attr("x",h.left+t/2).attr("y",h.top+u/2).text(function(a){return a}),b}p.selectAll(".nv-noData").remove(),c=e.xScale(),d=e.yScale().clamp(!0);var w=p.selectAll("g.nv-wrap.nv-boxPlotWithAxes").data([k]),x=w.enter().append("g").attr("class","nvd3 nv-wrap nv-boxPlotWithAxes").append("g"),y=x.append("defs"),z=w.select("g"); -x.append("g").attr("class","nv-x nv-axis"),x.append("g").attr("class","nv-y nv-axis").append("g").attr("class","nv-zeroLine").append("line"),x.append("g").attr("class","nv-barsWrap"),z.attr("transform","translate("+h.left+","+h.top+")"),n&&z.select(".nv-y.nv-axis").attr("transform","translate("+t+",0)"),e.width(t).height(u);var A=z.select(".nv-barsWrap").datum(k.filter(function(a){return!a.disabled}));if(A.transition().call(e),y.append("clipPath").attr("id","nv-x-label-clip-"+e.id()).append("rect"),z.select("#nv-x-label-clip-"+e.id()+" rect").attr("width",c.rangeBand()*(o?2:1)).attr("height",16).attr("x",-c.rangeBand()/(o?1:2)),l){f.scale(c).ticks(a.utils.calcTicksX(t/100,k)).tickSize(-u,0),z.select(".nv-x.nv-axis").attr("transform","translate(0,"+d.range()[0]+")"),z.select(".nv-x.nv-axis").call(f);var B=z.select(".nv-x.nv-axis").selectAll("g");o&&B.selectAll("text").attr("transform",function(a,b,c){return"translate(0,"+(c%2==0?"5":"17")+")"})}m&&(g.scale(d).ticks(Math.floor(u/36)).tickSize(-t,0),z.select(".nv-y.nv-axis").call(g)),z.select(".nv-zeroLine line").attr("x1",0).attr("x2",t).attr("y1",d(0)).attr("y2",d(0))}),t.renderEnd("nv-boxplot chart immediate"),b}var c,d,e=a.models.boxPlot(),f=a.models.axis(),g=a.models.axis(),h={top:15,right:10,bottom:50,left:60},i=null,j=null,k=a.utils.getColor(),l=!0,m=!0,n=!1,o=!1,p=a.models.tooltip(),q="No Data Available.",r=d3.dispatch("tooltipShow","tooltipHide","beforeUpdate","renderEnd"),s=250;f.orient("bottom").showMaxMin(!1).tickFormat(function(a){return a}),g.orient(n?"right":"left").tickFormat(d3.format(",.1f")),p.duration(0);var t=a.utils.renderWatch(r,s);return e.dispatch.on("elementMouseover.tooltip",function(a){p.data(a).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(a){p.data(a).hidden(!0)}),e.dispatch.on("elementMousemove.tooltip",function(){p.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.dispatch=r,b.boxplot=e,b.xAxis=f,b.yAxis=g,b.tooltip=p,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return i},set:function(a){i=a}},height:{get:function(){return j},set:function(a){j=a}},staggerLabels:{get:function(){return o},set:function(a){o=a}},showXAxis:{get:function(){return l},set:function(a){l=a}},showYAxis:{get:function(){return m},set:function(a){m=a}},tooltips:{get:function(){return tooltips},set:function(a){tooltips=a}},tooltipContent:{get:function(){return p},set:function(a){p=a}},noData:{get:function(){return q},set:function(a){q=a}},margin:{get:function(){return h},set:function(a){h.top=void 0!==a.top?a.top:h.top,h.right=void 0!==a.right?a.right:h.right,h.bottom=void 0!==a.bottom?a.bottom:h.bottom,h.left=void 0!==a.left?a.left:h.left}},duration:{get:function(){return s},set:function(a){s=a,t.reset(s),e.duration(s),f.duration(s),g.duration(s)}},color:{get:function(){return k},set:function(b){k=a.utils.getColor(b),e.color(k)}},rightAlignYAxis:{get:function(){return n},set:function(a){n=a,g.orient(a?"right":"left")}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.bullet=function(){"use strict";function b(d){return d.each(function(b,d){var p=m-c.left-c.right,s=n-c.top-c.bottom;o=d3.select(this),a.utils.initSVG(o);{var t=f.call(this,b,d).slice().sort(d3.descending),u=g.call(this,b,d).slice().sort(d3.descending),v=h.call(this,b,d).slice().sort(d3.descending),w=i.call(this,b,d).slice(),x=j.call(this,b,d).slice(),y=k.call(this,b,d).slice(),z=d3.scale.linear().domain(d3.extent(d3.merge([l,t]))).range(e?[p,0]:[0,p]);this.__chart__||d3.scale.linear().domain([0,1/0]).range(z.range())}this.__chart__=z;var A=d3.min(t),B=d3.max(t),C=t[1],D=o.selectAll("g.nv-wrap.nv-bullet").data([b]),E=D.enter().append("g").attr("class","nvd3 nv-wrap nv-bullet"),F=E.append("g"),G=D.select("g");F.append("rect").attr("class","nv-range nv-rangeMax"),F.append("rect").attr("class","nv-range nv-rangeAvg"),F.append("rect").attr("class","nv-range nv-rangeMin"),F.append("rect").attr("class","nv-measure"),D.attr("transform","translate("+c.left+","+c.top+")");var H=function(a){return Math.abs(z(a)-z(0))},I=function(a){return z(0>a?a:0)};G.select("rect.nv-rangeMax").attr("height",s).attr("width",H(B>0?B:A)).attr("x",I(B>0?B:A)).datum(B>0?B:A),G.select("rect.nv-rangeAvg").attr("height",s).attr("width",H(C)).attr("x",I(C)).datum(C),G.select("rect.nv-rangeMin").attr("height",s).attr("width",H(B)).attr("x",I(B)).attr("width",H(B>0?A:B)).attr("x",I(B>0?A:B)).datum(B>0?A:B),G.select("rect.nv-measure").style("fill",q).attr("height",s/3).attr("y",s/3).attr("width",0>v?z(0)-z(v[0]):z(v[0])-z(0)).attr("x",I(v)).on("mouseover",function(){r.elementMouseover({value:v[0],label:y[0]||"Current",color:d3.select(this).style("fill")})}).on("mousemove",function(){r.elementMousemove({value:v[0],label:y[0]||"Current",color:d3.select(this).style("fill")})}).on("mouseout",function(){r.elementMouseout({value:v[0],label:y[0]||"Current",color:d3.select(this).style("fill")})});var J=s/6,K=u.map(function(a,b){return{value:a,label:x[b]}});F.selectAll("path.nv-markerTriangle").data(K).enter().append("path").attr("class","nv-markerTriangle").attr("transform",function(a){return"translate("+z(a.value)+","+s/2+")"}).attr("d","M0,"+J+"L"+J+","+-J+" "+-J+","+-J+"Z").on("mouseover",function(a){r.elementMouseover({value:a.value,label:a.label||"Previous",color:d3.select(this).style("fill"),pos:[z(a.value),s/2]})}).on("mousemove",function(a){r.elementMousemove({value:a.value,label:a.label||"Previous",color:d3.select(this).style("fill")})}).on("mouseout",function(a){r.elementMouseout({value:a.value,label:a.label||"Previous",color:d3.select(this).style("fill")})}),D.selectAll(".nv-range").on("mouseover",function(a,b){var c=w[b]||(b?1==b?"Mean":"Minimum":"Maximum");r.elementMouseover({value:a,label:c,color:d3.select(this).style("fill")})}).on("mousemove",function(){r.elementMousemove({value:v[0],label:y[0]||"Previous",color:d3.select(this).style("fill")})}).on("mouseout",function(a,b){var c=w[b]||(b?1==b?"Mean":"Minimum":"Maximum");r.elementMouseout({value:a,label:c,color:d3.select(this).style("fill")})})}),b}var c={top:0,right:0,bottom:0,left:0},d="left",e=!1,f=function(a){return a.ranges},g=function(a){return a.markers?a.markers:[0]},h=function(a){return a.measures},i=function(a){return a.rangeLabels?a.rangeLabels:[]},j=function(a){return a.markerLabels?a.markerLabels:[]},k=function(a){return a.measureLabels?a.measureLabels:[]},l=[0],m=380,n=30,o=null,p=null,q=a.utils.getColor(["#1f77b4"]),r=d3.dispatch("elementMouseover","elementMouseout","elementMousemove");return b.dispatch=r,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{ranges:{get:function(){return f},set:function(a){f=a}},markers:{get:function(){return g},set:function(a){g=a}},measures:{get:function(){return h},set:function(a){h=a}},forceX:{get:function(){return l},set:function(a){l=a}},width:{get:function(){return m},set:function(a){m=a}},height:{get:function(){return n},set:function(a){n=a}},tickFormat:{get:function(){return p},set:function(a){p=a}},margin:{get:function(){return c},set:function(a){c.top=void 0!==a.top?a.top:c.top,c.right=void 0!==a.right?a.right:c.right,c.bottom=void 0!==a.bottom?a.bottom:c.bottom,c.left=void 0!==a.left?a.left:c.left}},orient:{get:function(){return d},set:function(a){d=a,e="right"==d||"bottom"==d}},color:{get:function(){return q},set:function(b){q=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.bulletChart=function(){"use strict";function b(d){return d.each(function(e,o){var p=d3.select(this);a.utils.initSVG(p);var q=a.utils.availableWidth(k,p,g),r=l-g.top-g.bottom;if(b.update=function(){b(d)},b.container=this,!e||!h.call(this,e,o))return a.utils.noData(b,p),b;p.selectAll(".nv-noData").remove();var s=h.call(this,e,o).slice().sort(d3.descending),t=i.call(this,e,o).slice().sort(d3.descending),u=j.call(this,e,o).slice().sort(d3.descending),v=p.selectAll("g.nv-wrap.nv-bulletChart").data([e]),w=v.enter().append("g").attr("class","nvd3 nv-wrap nv-bulletChart"),x=w.append("g"),y=v.select("g");x.append("g").attr("class","nv-bulletWrap"),x.append("g").attr("class","nv-titles"),v.attr("transform","translate("+g.left+","+g.top+")");var z=d3.scale.linear().domain([0,Math.max(s[0],t[0],u[0])]).range(f?[q,0]:[0,q]),A=this.__chart__||d3.scale.linear().domain([0,1/0]).range(z.range());this.__chart__=z;var B=x.select(".nv-titles").append("g").attr("text-anchor","end").attr("transform","translate(-6,"+(l-g.top-g.bottom)/2+")");B.append("text").attr("class","nv-title").text(function(a){return a.title}),B.append("text").attr("class","nv-subtitle").attr("dy","1em").text(function(a){return a.subtitle}),c.width(q).height(r);var C=y.select(".nv-bulletWrap");d3.transition(C).call(c);var D=m||z.tickFormat(q/100),E=y.selectAll("g.nv-tick").data(z.ticks(n?n:q/50),function(a){return this.textContent||D(a)}),F=E.enter().append("g").attr("class","nv-tick").attr("transform",function(a){return"translate("+A(a)+",0)"}).style("opacity",1e-6);F.append("line").attr("y1",r).attr("y2",7*r/6),F.append("text").attr("text-anchor","middle").attr("dy","1em").attr("y",7*r/6).text(D);var G=d3.transition(E).attr("transform",function(a){return"translate("+z(a)+",0)"}).style("opacity",1);G.select("line").attr("y1",r).attr("y2",7*r/6),G.select("text").attr("y",7*r/6),d3.transition(E.exit()).attr("transform",function(a){return"translate("+z(a)+",0)"}).style("opacity",1e-6).remove()}),d3.timer.flush(),b}var c=a.models.bullet(),d=a.models.tooltip(),e="left",f=!1,g={top:5,right:40,bottom:20,left:120},h=function(a){return a.ranges},i=function(a){return a.markers?a.markers:[0]},j=function(a){return a.measures},k=null,l=55,m=null,n=null,o=null,p=d3.dispatch("tooltipShow","tooltipHide");return d.duration(0).headerEnabled(!1),c.dispatch.on("elementMouseover.tooltip",function(a){a.series={key:a.label,value:a.value,color:a.color},d.data(a).hidden(!1)}),c.dispatch.on("elementMouseout.tooltip",function(){d.hidden(!0)}),c.dispatch.on("elementMousemove.tooltip",function(){d.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.bullet=c,b.dispatch=p,b.tooltip=d,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{ranges:{get:function(){return h},set:function(a){h=a}},markers:{get:function(){return i},set:function(a){i=a}},measures:{get:function(){return j},set:function(a){j=a}},width:{get:function(){return k},set:function(a){k=a}},height:{get:function(){return l},set:function(a){l=a}},tickFormat:{get:function(){return m},set:function(a){m=a}},ticks:{get:function(){return n},set:function(a){n=a}},noData:{get:function(){return o},set:function(a){o=a}},tooltips:{get:function(){return d.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),d.enabled(!!b)}},tooltipContent:{get:function(){return d.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),d.contentGenerator(b)}},margin:{get:function(){return g},set:function(a){g.top=void 0!==a.top?a.top:g.top,g.right=void 0!==a.right?a.right:g.right,g.bottom=void 0!==a.bottom?a.bottom:g.bottom,g.left=void 0!==a.left?a.left:g.left}},orient:{get:function(){return e},set:function(a){e=a,f="right"==e||"bottom"==e}}}),a.utils.inheritOptions(b,c),a.utils.initOptions(b),b},a.models.candlestickBar=function(){"use strict";function b(x){return x.each(function(b){c=d3.select(this);var x=a.utils.availableWidth(i,c,h),y=a.utils.availableHeight(j,c,h);a.utils.initSVG(c);var A=x/b[0].values.length*.45;l.domain(d||d3.extent(b[0].values.map(n).concat(t))),l.range(v?f||[.5*x/b[0].values.length,x*(b[0].values.length-.5)/b[0].values.length]:f||[5+A/2,x-A/2-5]),m.domain(e||[d3.min(b[0].values.map(s).concat(u)),d3.max(b[0].values.map(r).concat(u))]).range(g||[y,0]),l.domain()[0]===l.domain()[1]&&l.domain(l.domain()[0]?[l.domain()[0]-.01*l.domain()[0],l.domain()[1]+.01*l.domain()[1]]:[-1,1]),m.domain()[0]===m.domain()[1]&&m.domain(m.domain()[0]?[m.domain()[0]+.01*m.domain()[0],m.domain()[1]-.01*m.domain()[1]]:[-1,1]);var B=d3.select(this).selectAll("g.nv-wrap.nv-candlestickBar").data([b[0].values]),C=B.enter().append("g").attr("class","nvd3 nv-wrap nv-candlestickBar"),D=C.append("defs"),E=C.append("g"),F=B.select("g");E.append("g").attr("class","nv-ticks"),B.attr("transform","translate("+h.left+","+h.top+")"),c.on("click",function(a,b){z.chartClick({data:a,index:b,pos:d3.event,id:k})}),D.append("clipPath").attr("id","nv-chart-clip-path-"+k).append("rect"),B.select("#nv-chart-clip-path-"+k+" rect").attr("width",x).attr("height",y),F.attr("clip-path",w?"url(#nv-chart-clip-path-"+k+")":"");var G=B.select(".nv-ticks").selectAll(".nv-tick").data(function(a){return a});G.exit().remove();{var H=G.enter().append("g").attr("class",function(a,b,c){return(p(a,b)>q(a,b)?"nv-tick negative":"nv-tick positive")+" nv-tick-"+c+"-"+b});H.append("line").attr("class","nv-candlestick-lines").attr("transform",function(a,b){return"translate("+l(n(a,b))+",0)"}).attr("x1",0).attr("y1",function(a,b){return m(r(a,b))}).attr("x2",0).attr("y2",function(a,b){return m(s(a,b))}),H.append("rect").attr("class","nv-candlestick-rects nv-bars").attr("transform",function(a,b){return"translate("+(l(n(a,b))-A/2)+","+(m(o(a,b))-(p(a,b)>q(a,b)?m(q(a,b))-m(p(a,b)):0))+")"}).attr("x",0).attr("y",0).attr("width",A).attr("height",function(a,b){var c=p(a,b),d=q(a,b);return c>d?m(d)-m(c):m(c)-m(d)})}c.selectAll(".nv-candlestick-lines").transition().attr("transform",function(a,b){return"translate("+l(n(a,b))+",0)"}).attr("x1",0).attr("y1",function(a,b){return m(r(a,b))}).attr("x2",0).attr("y2",function(a,b){return m(s(a,b))}),c.selectAll(".nv-candlestick-rects").transition().attr("transform",function(a,b){return"translate("+(l(n(a,b))-A/2)+","+(m(o(a,b))-(p(a,b)>q(a,b)?m(q(a,b))-m(p(a,b)):0))+")"}).attr("x",0).attr("y",0).attr("width",A).attr("height",function(a,b){var c=p(a,b),d=q(a,b);return c>d?m(d)-m(c):m(c)-m(d)})}),b}var c,d,e,f,g,h={top:0,right:0,bottom:0,left:0},i=null,j=null,k=Math.floor(1e4*Math.random()),l=d3.scale.linear(),m=d3.scale.linear(),n=function(a){return a.x},o=function(a){return a.y},p=function(a){return a.open},q=function(a){return a.close},r=function(a){return a.high},s=function(a){return a.low},t=[],u=[],v=!1,w=!0,x=a.utils.defaultColor(),y=!1,z=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState","renderEnd","chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove");return b.highlightPoint=function(a,d){b.clearHighlights(),c.select(".nv-candlestickBar .nv-tick-0-"+a).classed("hover",d)},b.clearHighlights=function(){c.select(".nv-candlestickBar .nv-tick.hover").classed("hover",!1)},b.dispatch=z,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return i},set:function(a){i=a}},height:{get:function(){return j},set:function(a){j=a}},xScale:{get:function(){return l},set:function(a){l=a}},yScale:{get:function(){return m},set:function(a){m=a}},xDomain:{get:function(){return d},set:function(a){d=a}},yDomain:{get:function(){return e},set:function(a){e=a}},xRange:{get:function(){return f},set:function(a){f=a}},yRange:{get:function(){return g},set:function(a){g=a}},forceX:{get:function(){return t},set:function(a){t=a}},forceY:{get:function(){return u},set:function(a){u=a}},padData:{get:function(){return v},set:function(a){v=a}},clipEdge:{get:function(){return w},set:function(a){w=a}},id:{get:function(){return k},set:function(a){k=a}},interactive:{get:function(){return y},set:function(a){y=a}},x:{get:function(){return n},set:function(a){n=a}},y:{get:function(){return o},set:function(a){o=a}},open:{get:function(){return p()},set:function(a){p=a}},close:{get:function(){return q()},set:function(a){q=a}},high:{get:function(){return r},set:function(a){r=a}},low:{get:function(){return s},set:function(a){s=a}},margin:{get:function(){return h},set:function(a){h.top=void 0!=a.top?a.top:h.top,h.right=void 0!=a.right?a.right:h.right,h.bottom=void 0!=a.bottom?a.bottom:h.bottom,h.left=void 0!=a.left?a.left:h.left}},color:{get:function(){return x},set:function(b){x=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.cumulativeLineChart=function(){"use strict";function b(l){return H.reset(),H.models(f),r&&H.models(g),s&&H.models(h),l.each(function(l){function A(){d3.select(b.container).style("cursor","ew-resize")}function E(){G.x=d3.event.x,G.i=Math.round(F.invert(G.x)),K()}function H(){d3.select(b.container).style("cursor","auto"),y.index=G.i,C.stateChange(y)}function K(){bb.data([G]);var a=b.duration();b.duration(0),b.update(),b.duration(a)}var L=d3.select(this);a.utils.initSVG(L),L.classed("nv-chart-"+x,!0);var M=this,N=a.utils.availableWidth(o,L,m),O=a.utils.availableHeight(p,L,m);if(b.update=function(){0===D?L.call(b):L.transition().duration(D).call(b)},b.container=this,y.setter(J(l),b.update).getter(I(l)).update(),y.disabled=l.map(function(a){return!!a.disabled}),!z){var P;z={};for(P in y)z[P]=y[P]instanceof Array?y[P].slice(0):y[P]}var Q=d3.behavior.drag().on("dragstart",A).on("drag",E).on("dragend",H);if(!(l&&l.length&&l.filter(function(a){return a.values.length}).length))return a.utils.noData(b,L),b;if(L.selectAll(".nv-noData").remove(),d=f.xScale(),e=f.yScale(),w)f.yDomain(null);else{var R=l.filter(function(a){return!a.disabled}).map(function(a){var b=d3.extent(a.values,f.y());return b[0]<-.95&&(b[0]=-.95),[(b[0]-b[1])/(1+b[1]),(b[1]-b[0])/(1+b[0])]}),S=[d3.min(R,function(a){return a[0]}),d3.max(R,function(a){return a[1]})];f.yDomain(S)}F.domain([0,l[0].values.length-1]).range([0,N]).clamp(!0);var l=c(G.i,l),T=v?"none":"all",U=L.selectAll("g.nv-wrap.nv-cumulativeLine").data([l]),V=U.enter().append("g").attr("class","nvd3 nv-wrap nv-cumulativeLine").append("g"),W=U.select("g");if(V.append("g").attr("class","nv-interactive"),V.append("g").attr("class","nv-x nv-axis").style("pointer-events","none"),V.append("g").attr("class","nv-y nv-axis"),V.append("g").attr("class","nv-background"),V.append("g").attr("class","nv-linesWrap").style("pointer-events",T),V.append("g").attr("class","nv-avgLinesWrap").style("pointer-events","none"),V.append("g").attr("class","nv-legendWrap"),V.append("g").attr("class","nv-controlsWrap"),q&&(i.width(N),W.select(".nv-legendWrap").datum(l).call(i),m.top!=i.height()&&(m.top=i.height(),O=a.utils.availableHeight(p,L,m)),W.select(".nv-legendWrap").attr("transform","translate(0,"+-m.top+")")),u){var X=[{key:"Re-scale y-axis",disabled:!w}];j.width(140).color(["#444","#444","#444"]).rightAlign(!1).margin({top:5,right:0,bottom:5,left:20}),W.select(".nv-controlsWrap").datum(X).attr("transform","translate(0,"+-m.top+")").call(j)}U.attr("transform","translate("+m.left+","+m.top+")"),t&&W.select(".nv-y.nv-axis").attr("transform","translate("+N+",0)");var Y=l.filter(function(a){return a.tempDisabled});U.select(".tempDisabled").remove(),Y.length&&U.append("text").attr("class","tempDisabled").attr("x",N/2).attr("y","-.71em").style("text-anchor","end").text(Y.map(function(a){return a.key}).join(", ")+" values cannot be calculated for this time period."),v&&(k.width(N).height(O).margin({left:m.left,top:m.top}).svgContainer(L).xScale(d),U.select(".nv-interactive").call(k)),V.select(".nv-background").append("rect"),W.select(".nv-background rect").attr("width",N).attr("height",O),f.y(function(a){return a.display.y}).width(N).height(O).color(l.map(function(a,b){return a.color||n(a,b)}).filter(function(a,b){return!l[b].disabled&&!l[b].tempDisabled}));var Z=W.select(".nv-linesWrap").datum(l.filter(function(a){return!a.disabled&&!a.tempDisabled}));Z.call(f),l.forEach(function(a,b){a.seriesIndex=b});var $=l.filter(function(a){return!a.disabled&&!!B(a)}),_=W.select(".nv-avgLinesWrap").selectAll("line").data($,function(a){return a.key}),ab=function(a){var b=e(B(a));return 0>b?0:b>O?O:b};_.enter().append("line").style("stroke-width",2).style("stroke-dasharray","10,10").style("stroke",function(a){return f.color()(a,a.seriesIndex)}).attr("x1",0).attr("x2",N).attr("y1",ab).attr("y2",ab),_.style("stroke-opacity",function(a){var b=e(B(a));return 0>b||b>O?0:1}).attr("x1",0).attr("x2",N).attr("y1",ab).attr("y2",ab),_.exit().remove();var bb=Z.selectAll(".nv-indexLine").data([G]);bb.enter().append("rect").attr("class","nv-indexLine").attr("width",3).attr("x",-2).attr("fill","red").attr("fill-opacity",.5).style("pointer-events","all").call(Q),bb.attr("transform",function(a){return"translate("+F(a.i)+",0)"}).attr("height",O),r&&(g.scale(d)._ticks(a.utils.calcTicksX(N/70,l)).tickSize(-O,0),W.select(".nv-x.nv-axis").attr("transform","translate(0,"+e.range()[0]+")"),W.select(".nv-x.nv-axis").call(g)),s&&(h.scale(e)._ticks(a.utils.calcTicksY(O/36,l)).tickSize(-N,0),W.select(".nv-y.nv-axis").call(h)),W.select(".nv-background rect").on("click",function(){G.x=d3.mouse(this)[0],G.i=Math.round(F.invert(G.x)),y.index=G.i,C.stateChange(y),K()}),f.dispatch.on("elementClick",function(a){G.i=a.pointIndex,G.x=F(G.i),y.index=G.i,C.stateChange(y),K()}),j.dispatch.on("legendClick",function(a){a.disabled=!a.disabled,w=!a.disabled,y.rescaleY=w,C.stateChange(y),b.update()}),i.dispatch.on("stateChange",function(a){for(var c in a)y[c]=a[c];C.stateChange(y),b.update()}),k.dispatch.on("elementMousemove",function(c){f.clearHighlights();var d,e,i,j=[];if(l.filter(function(a,b){return a.seriesIndex=b,!a.disabled}).forEach(function(g,h){e=a.interactiveBisect(g.values,c.pointXValue,b.x()),f.highlightPoint(h,e,!0);var k=g.values[e];"undefined"!=typeof k&&("undefined"==typeof d&&(d=k),"undefined"==typeof i&&(i=b.xScale()(b.x()(k,e))),j.push({key:g.key,value:b.y()(k,e),color:n(g,g.seriesIndex)}))}),j.length>2){var o=b.yScale().invert(c.mouseY),p=Math.abs(b.yScale().domain()[0]-b.yScale().domain()[1]),q=.03*p,r=a.nearestValueIndex(j.map(function(a){return a.value}),o,q);null!==r&&(j[r].highlight=!0)}var s=g.tickFormat()(b.x()(d,e),e);k.tooltip.position({left:i+m.left,top:c.mouseY+m.top}).chartContainer(M.parentNode).valueFormatter(function(a){return h.tickFormat()(a)}).data({value:s,series:j})(),k.renderGuideLine(i)}),k.dispatch.on("elementMouseout",function(){f.clearHighlights()}),C.on("changeState",function(a){"undefined"!=typeof a.disabled&&(l.forEach(function(b,c){b.disabled=a.disabled[c]}),y.disabled=a.disabled),"undefined"!=typeof a.index&&(G.i=a.index,G.x=F(G.i),y.index=a.index,bb.data([G])),"undefined"!=typeof a.rescaleY&&(w=a.rescaleY),b.update()})}),H.renderEnd("cumulativeLineChart immediate"),b}function c(a,b){return K||(K=f.y()),b.map(function(b){if(!b.values)return b;var c=b.values[a];if(null==c)return b;var d=K(c,a);return-.95>d&&!E?(b.tempDisabled=!0,b):(b.tempDisabled=!1,b.values=b.values.map(function(a,b){return a.display={y:(K(a,b)-d)/(1+d)},a}),b)})}var d,e,f=a.models.line(),g=a.models.axis(),h=a.models.axis(),i=a.models.legend(),j=a.models.legend(),k=a.interactiveGuideline(),l=a.models.tooltip(),m={top:30,right:30,bottom:50,left:60},n=a.utils.defaultColor(),o=null,p=null,q=!0,r=!0,s=!0,t=!1,u=!0,v=!1,w=!0,x=f.id(),y=a.utils.state(),z=null,A=null,B=function(a){return a.average},C=d3.dispatch("stateChange","changeState","renderEnd"),D=250,E=!1;y.index=0,y.rescaleY=w,g.orient("bottom").tickPadding(7),h.orient(t?"right":"left"),l.valueFormatter(function(a,b){return h.tickFormat()(a,b)}).headerFormatter(function(a,b){return g.tickFormat()(a,b)}),j.updateState(!1);var F=d3.scale.linear(),G={i:0,x:0},H=a.utils.renderWatch(C,D),I=function(a){return function(){return{active:a.map(function(a){return!a.disabled}),index:G.i,rescaleY:w}}},J=function(a){return function(b){void 0!==b.index&&(G.i=b.index),void 0!==b.rescaleY&&(w=b.rescaleY),void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};f.dispatch.on("elementMouseover.tooltip",function(a){var c={x:b.x()(a.point),y:b.y()(a.point),color:a.point.color};a.point=c,l.data(a).position(a.pos).hidden(!1)}),f.dispatch.on("elementMouseout.tooltip",function(){l.hidden(!0)});var K=null;return b.dispatch=C,b.lines=f,b.legend=i,b.controls=j,b.xAxis=g,b.yAxis=h,b.interactiveLayer=k,b.state=y,b.tooltip=l,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return o},set:function(a){o=a}},height:{get:function(){return p},set:function(a){p=a}},rescaleY:{get:function(){return w},set:function(a){w=a}},showControls:{get:function(){return u},set:function(a){u=a}},showLegend:{get:function(){return q},set:function(a){q=a}},average:{get:function(){return B},set:function(a){B=a}},defaultState:{get:function(){return z},set:function(a){z=a}},noData:{get:function(){return A},set:function(a){A=a}},showXAxis:{get:function(){return r},set:function(a){r=a}},showYAxis:{get:function(){return s},set:function(a){s=a}},noErrorCheck:{get:function(){return E},set:function(a){E=a}},tooltips:{get:function(){return l.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),l.enabled(!!b)}},tooltipContent:{get:function(){return l.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),l.contentGenerator(b)}},margin:{get:function(){return m},set:function(a){m.top=void 0!==a.top?a.top:m.top,m.right=void 0!==a.right?a.right:m.right,m.bottom=void 0!==a.bottom?a.bottom:m.bottom,m.left=void 0!==a.left?a.left:m.left}},color:{get:function(){return n},set:function(b){n=a.utils.getColor(b),i.color(n)}},useInteractiveGuideline:{get:function(){return v},set:function(a){v=a,a===!0&&(b.interactive(!1),b.useVoronoi(!1))}},rightAlignYAxis:{get:function(){return t},set:function(a){t=a,h.orient(a?"right":"left")}},duration:{get:function(){return D},set:function(a){D=a,f.duration(D),g.duration(D),h.duration(D),H.reset(D)}}}),a.utils.inheritOptions(b,f),a.utils.initOptions(b),b},a.models.discreteBar=function(){"use strict";function b(m){return y.reset(),m.each(function(b){var m=k-j.left-j.right,x=l-j.top-j.bottom;c=d3.select(this),a.utils.initSVG(c),b.forEach(function(a,b){a.values.forEach(function(a){a.series=b})});var z=d&&e?[]:b.map(function(a){return a.values.map(function(a,b){return{x:p(a,b),y:q(a,b),y0:a.y0}})});n.domain(d||d3.merge(z).map(function(a){return a.x})).rangeBands(f||[0,m],.1),o.domain(e||d3.extent(d3.merge(z).map(function(a){return a.y}).concat(r))),o.range(t?g||[x-(o.domain()[0]<0?12:0),o.domain()[1]>0?12:0]:g||[x,0]),h=h||n,i=i||o.copy().range([o(0),o(0)]);{var A=c.selectAll("g.nv-wrap.nv-discretebar").data([b]),B=A.enter().append("g").attr("class","nvd3 nv-wrap nv-discretebar"),C=B.append("g");A.select("g")}C.append("g").attr("class","nv-groups"),A.attr("transform","translate("+j.left+","+j.top+")");var D=A.select(".nv-groups").selectAll(".nv-group").data(function(a){return a},function(a){return a.key});D.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6),D.exit().watchTransition(y,"discreteBar: exit groups").style("stroke-opacity",1e-6).style("fill-opacity",1e-6).remove(),D.attr("class",function(a,b){return"nv-group nv-series-"+b}).classed("hover",function(a){return a.hover}),D.watchTransition(y,"discreteBar: groups").style("stroke-opacity",1).style("fill-opacity",.75);var E=D.selectAll("g.nv-bar").data(function(a){return a.values});E.exit().remove();var F=E.enter().append("g").attr("transform",function(a,b){return"translate("+(n(p(a,b))+.05*n.rangeBand())+", "+o(0)+")"}).on("mouseover",function(a,b){d3.select(this).classed("hover",!0),v.elementMouseover({data:a,index:b,color:d3.select(this).style("fill")})}).on("mouseout",function(a,b){d3.select(this).classed("hover",!1),v.elementMouseout({data:a,index:b,color:d3.select(this).style("fill")})}).on("mousemove",function(a,b){v.elementMousemove({data:a,index:b,color:d3.select(this).style("fill")})}).on("click",function(a,b){v.elementClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation()}).on("dblclick",function(a,b){v.elementDblClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation()});F.append("rect").attr("height",0).attr("width",.9*n.rangeBand()/b.length),t?(F.append("text").attr("text-anchor","middle"),E.select("text").text(function(a,b){return u(q(a,b))}).watchTransition(y,"discreteBar: bars text").attr("x",.9*n.rangeBand()/2).attr("y",function(a,b){return q(a,b)<0?o(q(a,b))-o(0)+12:-4})):E.selectAll("text").remove(),E.attr("class",function(a,b){return q(a,b)<0?"nv-bar negative":"nv-bar positive"}).style("fill",function(a,b){return a.color||s(a,b)}).style("stroke",function(a,b){return a.color||s(a,b)}).select("rect").attr("class",w).watchTransition(y,"discreteBar: bars rect").attr("width",.9*n.rangeBand()/b.length),E.watchTransition(y,"discreteBar: bars").attr("transform",function(a,b){var c=n(p(a,b))+.05*n.rangeBand(),d=q(a,b)<0?o(0):o(0)-o(q(a,b))<1?o(0)-1:o(q(a,b));return"translate("+c+", "+d+")"}).select("rect").attr("height",function(a,b){return Math.max(Math.abs(o(q(a,b))-o(e&&e[0]||0))||1)}),h=n.copy(),i=o.copy()}),y.renderEnd("discreteBar immediate"),b}var c,d,e,f,g,h,i,j={top:0,right:0,bottom:0,left:0},k=960,l=500,m=Math.floor(1e4*Math.random()),n=d3.scale.ordinal(),o=d3.scale.linear(),p=function(a){return a.x},q=function(a){return a.y},r=[0],s=a.utils.defaultColor(),t=!1,u=d3.format(",.2f"),v=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove","renderEnd"),w="discreteBar",x=250,y=a.utils.renderWatch(v,x);return b.dispatch=v,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return k},set:function(a){k=a}},height:{get:function(){return l},set:function(a){l=a}},forceY:{get:function(){return r},set:function(a){r=a}},showValues:{get:function(){return t},set:function(a){t=a}},x:{get:function(){return p},set:function(a){p=a}},y:{get:function(){return q},set:function(a){q=a}},xScale:{get:function(){return n},set:function(a){n=a}},yScale:{get:function(){return o},set:function(a){o=a}},xDomain:{get:function(){return d},set:function(a){d=a}},yDomain:{get:function(){return e},set:function(a){e=a}},xRange:{get:function(){return f},set:function(a){f=a}},yRange:{get:function(){return g},set:function(a){g=a}},valueFormat:{get:function(){return u},set:function(a){u=a}},id:{get:function(){return m},set:function(a){m=a}},rectClass:{get:function(){return w},set:function(a){w=a}},margin:{get:function(){return j},set:function(a){j.top=void 0!==a.top?a.top:j.top,j.right=void 0!==a.right?a.right:j.right,j.bottom=void 0!==a.bottom?a.bottom:j.bottom,j.left=void 0!==a.left?a.left:j.left}},color:{get:function(){return s},set:function(b){s=a.utils.getColor(b)}},duration:{get:function(){return x},set:function(a){x=a,y.reset(x)}}}),a.utils.initOptions(b),b},a.models.discreteBarChart=function(){"use strict";function b(h){return t.reset(),t.models(e),m&&t.models(f),n&&t.models(g),h.each(function(h){var l=d3.select(this);a.utils.initSVG(l);var q=a.utils.availableWidth(j,l,i),t=a.utils.availableHeight(k,l,i);if(b.update=function(){r.beforeUpdate(),l.transition().duration(s).call(b)},b.container=this,!(h&&h.length&&h.filter(function(a){return a.values.length}).length))return a.utils.noData(b,l),b;l.selectAll(".nv-noData").remove(),c=e.xScale(),d=e.yScale().clamp(!0);var u=l.selectAll("g.nv-wrap.nv-discreteBarWithAxes").data([h]),v=u.enter().append("g").attr("class","nvd3 nv-wrap nv-discreteBarWithAxes").append("g"),w=v.append("defs"),x=u.select("g");v.append("g").attr("class","nv-x nv-axis"),v.append("g").attr("class","nv-y nv-axis").append("g").attr("class","nv-zeroLine").append("line"),v.append("g").attr("class","nv-barsWrap"),x.attr("transform","translate("+i.left+","+i.top+")"),o&&x.select(".nv-y.nv-axis").attr("transform","translate("+q+",0)"),e.width(q).height(t);var y=x.select(".nv-barsWrap").datum(h.filter(function(a){return!a.disabled}));if(y.transition().call(e),w.append("clipPath").attr("id","nv-x-label-clip-"+e.id()).append("rect"),x.select("#nv-x-label-clip-"+e.id()+" rect").attr("width",c.rangeBand()*(p?2:1)).attr("height",16).attr("x",-c.rangeBand()/(p?1:2)),m){f.scale(c)._ticks(a.utils.calcTicksX(q/100,h)).tickSize(-t,0),x.select(".nv-x.nv-axis").attr("transform","translate(0,"+(d.range()[0]+(e.showValues()&&d.domain()[0]<0?16:0))+")"),x.select(".nv-x.nv-axis").call(f); -var z=x.select(".nv-x.nv-axis").selectAll("g");p&&z.selectAll("text").attr("transform",function(a,b,c){return"translate(0,"+(c%2==0?"5":"17")+")"})}n&&(g.scale(d)._ticks(a.utils.calcTicksY(t/36,h)).tickSize(-q,0),x.select(".nv-y.nv-axis").call(g)),x.select(".nv-zeroLine line").attr("x1",0).attr("x2",q).attr("y1",d(0)).attr("y2",d(0))}),t.renderEnd("discreteBar chart immediate"),b}var c,d,e=a.models.discreteBar(),f=a.models.axis(),g=a.models.axis(),h=a.models.tooltip(),i={top:15,right:10,bottom:50,left:60},j=null,k=null,l=a.utils.getColor(),m=!0,n=!0,o=!1,p=!1,q=null,r=d3.dispatch("beforeUpdate","renderEnd"),s=250;f.orient("bottom").showMaxMin(!1).tickFormat(function(a){return a}),g.orient(o?"right":"left").tickFormat(d3.format(",.1f")),h.duration(0).headerEnabled(!1).valueFormatter(function(a,b){return g.tickFormat()(a,b)}).keyFormatter(function(a,b){return f.tickFormat()(a,b)});var t=a.utils.renderWatch(r,s);return e.dispatch.on("elementMouseover.tooltip",function(a){a.series={key:b.x()(a.data),value:b.y()(a.data),color:a.color},h.data(a).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(){h.hidden(!0)}),e.dispatch.on("elementMousemove.tooltip",function(){h.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.dispatch=r,b.discretebar=e,b.xAxis=f,b.yAxis=g,b.tooltip=h,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return j},set:function(a){j=a}},height:{get:function(){return k},set:function(a){k=a}},staggerLabels:{get:function(){return p},set:function(a){p=a}},showXAxis:{get:function(){return m},set:function(a){m=a}},showYAxis:{get:function(){return n},set:function(a){n=a}},noData:{get:function(){return q},set:function(a){q=a}},tooltips:{get:function(){return h.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),h.enabled(!!b)}},tooltipContent:{get:function(){return h.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),h.contentGenerator(b)}},margin:{get:function(){return i},set:function(a){i.top=void 0!==a.top?a.top:i.top,i.right=void 0!==a.right?a.right:i.right,i.bottom=void 0!==a.bottom?a.bottom:i.bottom,i.left=void 0!==a.left?a.left:i.left}},duration:{get:function(){return s},set:function(a){s=a,t.reset(s),e.duration(s),f.duration(s),g.duration(s)}},color:{get:function(){return l},set:function(b){l=a.utils.getColor(b),e.color(l)}},rightAlignYAxis:{get:function(){return o},set:function(a){o=a,g.orient(a?"right":"left")}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.distribution=function(){"use strict";function b(k){return m.reset(),k.each(function(b){var k=(e-("x"===g?d.left+d.right:d.top+d.bottom),"x"==g?"y":"x"),l=d3.select(this);a.utils.initSVG(l),c=c||j;var n=l.selectAll("g.nv-distribution").data([b]),o=n.enter().append("g").attr("class","nvd3 nv-distribution"),p=(o.append("g"),n.select("g"));n.attr("transform","translate("+d.left+","+d.top+")");var q=p.selectAll("g.nv-dist").data(function(a){return a},function(a){return a.key});q.enter().append("g"),q.attr("class",function(a,b){return"nv-dist nv-series-"+b}).style("stroke",function(a,b){return i(a,b)});var r=q.selectAll("line.nv-dist"+g).data(function(a){return a.values});r.enter().append("line").attr(g+"1",function(a,b){return c(h(a,b))}).attr(g+"2",function(a,b){return c(h(a,b))}),m.transition(q.exit().selectAll("line.nv-dist"+g),"dist exit").attr(g+"1",function(a,b){return j(h(a,b))}).attr(g+"2",function(a,b){return j(h(a,b))}).style("stroke-opacity",0).remove(),r.attr("class",function(a,b){return"nv-dist"+g+" nv-dist"+g+"-"+b}).attr(k+"1",0).attr(k+"2",f),m.transition(r,"dist").attr(g+"1",function(a,b){return j(h(a,b))}).attr(g+"2",function(a,b){return j(h(a,b))}),c=j.copy()}),m.renderEnd("distribution immediate"),b}var c,d={top:0,right:0,bottom:0,left:0},e=400,f=8,g="x",h=function(a){return a[g]},i=a.utils.defaultColor(),j=d3.scale.linear(),k=250,l=d3.dispatch("renderEnd"),m=a.utils.renderWatch(l,k);return b.options=a.utils.optionsFunc.bind(b),b.dispatch=l,b.margin=function(a){return arguments.length?(d.top="undefined"!=typeof a.top?a.top:d.top,d.right="undefined"!=typeof a.right?a.right:d.right,d.bottom="undefined"!=typeof a.bottom?a.bottom:d.bottom,d.left="undefined"!=typeof a.left?a.left:d.left,b):d},b.width=function(a){return arguments.length?(e=a,b):e},b.axis=function(a){return arguments.length?(g=a,b):g},b.size=function(a){return arguments.length?(f=a,b):f},b.getData=function(a){return arguments.length?(h=d3.functor(a),b):h},b.scale=function(a){return arguments.length?(j=a,b):j},b.color=function(c){return arguments.length?(i=a.utils.getColor(c),b):i},b.duration=function(a){return arguments.length?(k=a,m.reset(k),b):k},b},a.models.furiousLegend=function(){"use strict";function b(p){function q(a,b){return"furious"!=o?"#000":m?a.disengaged?g(a,b):"#fff":m?void 0:a.disabled?g(a,b):"#fff"}function r(a,b){return m&&"furious"==o?a.disengaged?"#fff":g(a,b):a.disabled?"#fff":g(a,b)}return p.each(function(b){var p=d-c.left-c.right,s=d3.select(this);a.utils.initSVG(s);var t=s.selectAll("g.nv-legend").data([b]),u=(t.enter().append("g").attr("class","nvd3 nv-legend").append("g"),t.select("g"));t.attr("transform","translate("+c.left+","+c.top+")");var v,w=u.selectAll(".nv-series").data(function(a){return"furious"!=o?a:a.filter(function(a){return m?!0:!a.disengaged})}),x=w.enter().append("g").attr("class","nv-series");if("classic"==o)x.append("circle").style("stroke-width",2).attr("class","nv-legend-symbol").attr("r",5),v=w.select("circle");else if("furious"==o){x.append("rect").style("stroke-width",2).attr("class","nv-legend-symbol").attr("rx",3).attr("ry",3),v=w.select("rect"),x.append("g").attr("class","nv-check-box").property("innerHTML",'').attr("transform","translate(-10,-8)scale(0.5)");var y=w.select(".nv-check-box");y.each(function(a,b){d3.select(this).selectAll("path").attr("stroke",q(a,b))})}x.append("text").attr("text-anchor","start").attr("class","nv-legend-text").attr("dy",".32em").attr("dx","8");var z=w.select("text.nv-legend-text");w.on("mouseover",function(a,b){n.legendMouseover(a,b)}).on("mouseout",function(a,b){n.legendMouseout(a,b)}).on("click",function(a,b){n.legendClick(a,b);var c=w.data();if(k){if("classic"==o)l?(c.forEach(function(a){a.disabled=!0}),a.disabled=!1):(a.disabled=!a.disabled,c.every(function(a){return a.disabled})&&c.forEach(function(a){a.disabled=!1}));else if("furious"==o)if(m)a.disengaged=!a.disengaged,a.userDisabled=void 0==a.userDisabled?!!a.disabled:a.userDisabled,a.disabled=a.disengaged||a.userDisabled;else if(!m){a.disabled=!a.disabled,a.userDisabled=a.disabled;var d=c.filter(function(a){return!a.disengaged});d.every(function(a){return a.userDisabled})&&c.forEach(function(a){a.disabled=a.userDisabled=!1})}n.stateChange({disabled:c.map(function(a){return!!a.disabled}),disengaged:c.map(function(a){return!!a.disengaged})})}}).on("dblclick",function(a,b){if(("furious"!=o||!m)&&(n.legendDblclick(a,b),k)){var c=w.data();c.forEach(function(a){a.disabled=!0,"furious"==o&&(a.userDisabled=a.disabled)}),a.disabled=!1,"furious"==o&&(a.userDisabled=a.disabled),n.stateChange({disabled:c.map(function(a){return!!a.disabled})})}}),w.classed("nv-disabled",function(a){return a.userDisabled}),w.exit().remove(),z.attr("fill",q).text(f);var A;switch(o){case"furious":A=23;break;case"classic":A=20}if(h){var B=[];w.each(function(){var b,c=d3.select(this).select("text");try{if(b=c.node().getComputedTextLength(),0>=b)throw Error()}catch(d){b=a.utils.calcApproxTextWidth(c)}B.push(b+i)});for(var C=0,D=0,E=[];p>D&&Cp&&C>1;){E=[],C--;for(var F=0;F(E[F%C]||0)&&(E[F%C]=B[F]);D=E.reduce(function(a,b){return a+b})}for(var G=[],H=0,I=0;C>H;H++)G[H]=I,I+=E[H];w.attr("transform",function(a,b){return"translate("+G[b%C]+","+(5+Math.floor(b/C)*A)+")"}),j?u.attr("transform","translate("+(d-c.right-D)+","+c.top+")"):u.attr("transform","translate(0,"+c.top+")"),e=c.top+c.bottom+Math.ceil(B.length/C)*A}else{var J,K=5,L=5,M=0;w.attr("transform",function(){var a=d3.select(this).select("text").node().getComputedTextLength()+i;return J=L,dM&&(M=L),"translate("+J+","+K+")"}),u.attr("transform","translate("+(d-c.right-M)+","+c.top+")"),e=c.top+c.bottom+K+15}"furious"==o&&v.attr("width",function(a,b){return z[0][b].getComputedTextLength()+27}).attr("height",18).attr("y",-9).attr("x",-15),v.style("fill",r).style("stroke",function(a,b){return a.color||g(a,b)})}),b}var c={top:5,right:0,bottom:5,left:0},d=400,e=20,f=function(a){return a.key},g=a.utils.getColor(),h=!0,i=28,j=!0,k=!0,l=!1,m=!1,n=d3.dispatch("legendClick","legendDblclick","legendMouseover","legendMouseout","stateChange"),o="classic";return b.dispatch=n,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return d},set:function(a){d=a}},height:{get:function(){return e},set:function(a){e=a}},key:{get:function(){return f},set:function(a){f=a}},align:{get:function(){return h},set:function(a){h=a}},rightAlign:{get:function(){return j},set:function(a){j=a}},padding:{get:function(){return i},set:function(a){i=a}},updateState:{get:function(){return k},set:function(a){k=a}},radioButtonMode:{get:function(){return l},set:function(a){l=a}},expanded:{get:function(){return m},set:function(a){m=a}},vers:{get:function(){return o},set:function(a){o=a}},margin:{get:function(){return c},set:function(a){c.top=void 0!==a.top?a.top:c.top,c.right=void 0!==a.right?a.right:c.right,c.bottom=void 0!==a.bottom?a.bottom:c.bottom,c.left=void 0!==a.left?a.left:c.left}},color:{get:function(){return g},set:function(b){g=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.historicalBar=function(){"use strict";function b(x){return x.each(function(b){w.reset(),k=d3.select(this);var x=a.utils.availableWidth(h,k,g),y=a.utils.availableHeight(i,k,g);a.utils.initSVG(k),l.domain(c||d3.extent(b[0].values.map(n).concat(p))),l.range(r?e||[.5*x/b[0].values.length,x*(b[0].values.length-.5)/b[0].values.length]:e||[0,x]),m.domain(d||d3.extent(b[0].values.map(o).concat(q))).range(f||[y,0]),l.domain()[0]===l.domain()[1]&&l.domain(l.domain()[0]?[l.domain()[0]-.01*l.domain()[0],l.domain()[1]+.01*l.domain()[1]]:[-1,1]),m.domain()[0]===m.domain()[1]&&m.domain(m.domain()[0]?[m.domain()[0]+.01*m.domain()[0],m.domain()[1]-.01*m.domain()[1]]:[-1,1]);var z=k.selectAll("g.nv-wrap.nv-historicalBar-"+j).data([b[0].values]),A=z.enter().append("g").attr("class","nvd3 nv-wrap nv-historicalBar-"+j),B=A.append("defs"),C=A.append("g"),D=z.select("g");C.append("g").attr("class","nv-bars"),z.attr("transform","translate("+g.left+","+g.top+")"),k.on("click",function(a,b){u.chartClick({data:a,index:b,pos:d3.event,id:j})}),B.append("clipPath").attr("id","nv-chart-clip-path-"+j).append("rect"),z.select("#nv-chart-clip-path-"+j+" rect").attr("width",x).attr("height",y),D.attr("clip-path",s?"url(#nv-chart-clip-path-"+j+")":"");var E=z.select(".nv-bars").selectAll(".nv-bar").data(function(a){return a},function(a,b){return n(a,b)});E.exit().remove(),E.enter().append("rect").attr("x",0).attr("y",function(b,c){return a.utils.NaNtoZero(m(Math.max(0,o(b,c))))}).attr("height",function(b,c){return a.utils.NaNtoZero(Math.abs(m(o(b,c))-m(0)))}).attr("transform",function(a,c){return"translate("+(l(n(a,c))-x/b[0].values.length*.45)+",0)"}).on("mouseover",function(a,b){v&&(d3.select(this).classed("hover",!0),u.elementMouseover({data:a,index:b,color:d3.select(this).style("fill")}))}).on("mouseout",function(a,b){v&&(d3.select(this).classed("hover",!1),u.elementMouseout({data:a,index:b,color:d3.select(this).style("fill")}))}).on("mousemove",function(a,b){v&&u.elementMousemove({data:a,index:b,color:d3.select(this).style("fill")})}).on("click",function(a,b){v&&(u.elementClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation())}).on("dblclick",function(a,b){v&&(u.elementDblClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation())}),E.attr("fill",function(a,b){return t(a,b)}).attr("class",function(a,b,c){return(o(a,b)<0?"nv-bar negative":"nv-bar positive")+" nv-bar-"+c+"-"+b}).watchTransition(w,"bars").attr("transform",function(a,c){return"translate("+(l(n(a,c))-x/b[0].values.length*.45)+",0)"}).attr("width",x/b[0].values.length*.9),E.watchTransition(w,"bars").attr("y",function(b,c){var d=o(b,c)<0?m(0):m(0)-m(o(b,c))<1?m(0)-1:m(o(b,c));return a.utils.NaNtoZero(d)}).attr("height",function(b,c){return a.utils.NaNtoZero(Math.max(Math.abs(m(o(b,c))-m(0)),1))})}),w.renderEnd("historicalBar immediate"),b}var c,d,e,f,g={top:0,right:0,bottom:0,left:0},h=null,i=null,j=Math.floor(1e4*Math.random()),k=null,l=d3.scale.linear(),m=d3.scale.linear(),n=function(a){return a.x},o=function(a){return a.y},p=[],q=[0],r=!1,s=!0,t=a.utils.defaultColor(),u=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove","renderEnd"),v=!0,w=a.utils.renderWatch(u,0);return b.highlightPoint=function(a,b){k.select(".nv-bars .nv-bar-0-"+a).classed("hover",b)},b.clearHighlights=function(){k.select(".nv-bars .nv-bar.hover").classed("hover",!1)},b.dispatch=u,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return h},set:function(a){h=a}},height:{get:function(){return i},set:function(a){i=a}},forceX:{get:function(){return p},set:function(a){p=a}},forceY:{get:function(){return q},set:function(a){q=a}},padData:{get:function(){return r},set:function(a){r=a}},x:{get:function(){return n},set:function(a){n=a}},y:{get:function(){return o},set:function(a){o=a}},xScale:{get:function(){return l},set:function(a){l=a}},yScale:{get:function(){return m},set:function(a){m=a}},xDomain:{get:function(){return c},set:function(a){c=a}},yDomain:{get:function(){return d},set:function(a){d=a}},xRange:{get:function(){return e},set:function(a){e=a}},yRange:{get:function(){return f},set:function(a){f=a}},clipEdge:{get:function(){return s},set:function(a){s=a}},id:{get:function(){return j},set:function(a){j=a}},interactive:{get:function(){return v},set:function(a){v=a}},margin:{get:function(){return g},set:function(a){g.top=void 0!==a.top?a.top:g.top,g.right=void 0!==a.right?a.right:g.right,g.bottom=void 0!==a.bottom?a.bottom:g.bottom,g.left=void 0!==a.left?a.left:g.left}},color:{get:function(){return t},set:function(b){t=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.historicalBarChart=function(b){"use strict";function c(b){return b.each(function(k){z.reset(),z.models(f),q&&z.models(g),r&&z.models(h);var w=d3.select(this),A=this;a.utils.initSVG(w);var B=a.utils.availableWidth(n,w,l),C=a.utils.availableHeight(o,w,l);if(c.update=function(){w.transition().duration(y).call(c)},c.container=this,u.disabled=k.map(function(a){return!!a.disabled}),!v){var D;v={};for(D in u)v[D]=u[D]instanceof Array?u[D].slice(0):u[D]}if(!(k&&k.length&&k.filter(function(a){return a.values.length}).length))return a.utils.noData(c,w),c;w.selectAll(".nv-noData").remove(),d=f.xScale(),e=f.yScale();var E=w.selectAll("g.nv-wrap.nv-historicalBarChart").data([k]),F=E.enter().append("g").attr("class","nvd3 nv-wrap nv-historicalBarChart").append("g"),G=E.select("g");F.append("g").attr("class","nv-x nv-axis"),F.append("g").attr("class","nv-y nv-axis"),F.append("g").attr("class","nv-barsWrap"),F.append("g").attr("class","nv-legendWrap"),F.append("g").attr("class","nv-interactive"),p&&(i.width(B),G.select(".nv-legendWrap").datum(k).call(i),l.top!=i.height()&&(l.top=i.height(),C=a.utils.availableHeight(o,w,l)),E.select(".nv-legendWrap").attr("transform","translate(0,"+-l.top+")")),E.attr("transform","translate("+l.left+","+l.top+")"),s&&G.select(".nv-y.nv-axis").attr("transform","translate("+B+",0)"),t&&(j.width(B).height(C).margin({left:l.left,top:l.top}).svgContainer(w).xScale(d),E.select(".nv-interactive").call(j)),f.width(B).height(C).color(k.map(function(a,b){return a.color||m(a,b)}).filter(function(a,b){return!k[b].disabled}));var H=G.select(".nv-barsWrap").datum(k.filter(function(a){return!a.disabled}));H.transition().call(f),q&&(g.scale(d)._ticks(a.utils.calcTicksX(B/100,k)).tickSize(-C,0),G.select(".nv-x.nv-axis").attr("transform","translate(0,"+e.range()[0]+")"),G.select(".nv-x.nv-axis").transition().call(g)),r&&(h.scale(e)._ticks(a.utils.calcTicksY(C/36,k)).tickSize(-B,0),G.select(".nv-y.nv-axis").transition().call(h)),j.dispatch.on("elementMousemove",function(b){f.clearHighlights();var d,e,i,n=[];k.filter(function(a,b){return a.seriesIndex=b,!a.disabled}).forEach(function(g){e=a.interactiveBisect(g.values,b.pointXValue,c.x()),f.highlightPoint(e,!0);var h=g.values[e];void 0!==h&&(void 0===d&&(d=h),void 0===i&&(i=c.xScale()(c.x()(h,e))),n.push({key:g.key,value:c.y()(h,e),color:m(g,g.seriesIndex),data:g.values[e]}))});var o=g.tickFormat()(c.x()(d,e));j.tooltip.position({left:i+l.left,top:b.mouseY+l.top}).chartContainer(A.parentNode).valueFormatter(function(a){return h.tickFormat()(a)}).data({value:o,index:e,series:n})(),j.renderGuideLine(i)}),j.dispatch.on("elementMouseout",function(){x.tooltipHide(),f.clearHighlights()}),i.dispatch.on("legendClick",function(a){a.disabled=!a.disabled,k.filter(function(a){return!a.disabled}).length||k.map(function(a){return a.disabled=!1,E.selectAll(".nv-series").classed("disabled",!1),a}),u.disabled=k.map(function(a){return!!a.disabled}),x.stateChange(u),b.transition().call(c)}),i.dispatch.on("legendDblclick",function(a){k.forEach(function(a){a.disabled=!0}),a.disabled=!1,u.disabled=k.map(function(a){return!!a.disabled}),x.stateChange(u),c.update()}),x.on("changeState",function(a){"undefined"!=typeof a.disabled&&(k.forEach(function(b,c){b.disabled=a.disabled[c]}),u.disabled=a.disabled),c.update()})}),z.renderEnd("historicalBarChart immediate"),c}var d,e,f=b||a.models.historicalBar(),g=a.models.axis(),h=a.models.axis(),i=a.models.legend(),j=a.interactiveGuideline(),k=a.models.tooltip(),l={top:30,right:90,bottom:50,left:90},m=a.utils.defaultColor(),n=null,o=null,p=!1,q=!0,r=!0,s=!1,t=!1,u={},v=null,w=null,x=d3.dispatch("tooltipHide","stateChange","changeState","renderEnd"),y=250;g.orient("bottom").tickPadding(7),h.orient(s?"right":"left"),k.duration(0).headerEnabled(!1).valueFormatter(function(a,b){return h.tickFormat()(a,b)}).headerFormatter(function(a,b){return g.tickFormat()(a,b)});var z=a.utils.renderWatch(x,0);return f.dispatch.on("elementMouseover.tooltip",function(a){a.series={key:c.x()(a.data),value:c.y()(a.data),color:a.color},k.data(a).hidden(!1)}),f.dispatch.on("elementMouseout.tooltip",function(){k.hidden(!0)}),f.dispatch.on("elementMousemove.tooltip",function(){k.position({top:d3.event.pageY,left:d3.event.pageX})()}),c.dispatch=x,c.bars=f,c.legend=i,c.xAxis=g,c.yAxis=h,c.interactiveLayer=j,c.tooltip=k,c.options=a.utils.optionsFunc.bind(c),c._options=Object.create({},{width:{get:function(){return n},set:function(a){n=a}},height:{get:function(){return o},set:function(a){o=a}},showLegend:{get:function(){return p},set:function(a){p=a}},showXAxis:{get:function(){return q},set:function(a){q=a}},showYAxis:{get:function(){return r},set:function(a){r=a}},defaultState:{get:function(){return v},set:function(a){v=a}},noData:{get:function(){return w},set:function(a){w=a}},tooltips:{get:function(){return k.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),k.enabled(!!b)}},tooltipContent:{get:function(){return k.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),k.contentGenerator(b)}},margin:{get:function(){return l},set:function(a){l.top=void 0!==a.top?a.top:l.top,l.right=void 0!==a.right?a.right:l.right,l.bottom=void 0!==a.bottom?a.bottom:l.bottom,l.left=void 0!==a.left?a.left:l.left}},color:{get:function(){return m},set:function(b){m=a.utils.getColor(b),i.color(m),f.color(m)}},duration:{get:function(){return y},set:function(a){y=a,z.reset(y),h.duration(y),g.duration(y)}},rightAlignYAxis:{get:function(){return s},set:function(a){s=a,h.orient(a?"right":"left")}},useInteractiveGuideline:{get:function(){return t},set:function(a){t=a,a===!0&&c.interactive(!1)}}}),a.utils.inheritOptions(c,f),a.utils.initOptions(c),c},a.models.ohlcBarChart=function(){var b=a.models.historicalBarChart(a.models.ohlcBar());return b.useInteractiveGuideline(!0),b.interactiveLayer.tooltip.contentGenerator(function(a){var c=a.series[0].data,d=c.open'+a.value+"
open:"+b.yAxis.tickFormat()(c.open)+"
close:"+b.yAxis.tickFormat()(c.close)+"
high"+b.yAxis.tickFormat()(c.high)+"
low:"+b.yAxis.tickFormat()(c.low)+"
"}),b},a.models.candlestickBarChart=function(){var b=a.models.historicalBarChart(a.models.candlestickBar());return b.useInteractiveGuideline(!0),b.interactiveLayer.tooltip.contentGenerator(function(a){var c=a.series[0].data,d=c.open'+a.value+"
open:"+b.yAxis.tickFormat()(c.open)+"
close:"+b.yAxis.tickFormat()(c.close)+"
high"+b.yAxis.tickFormat()(c.high)+"
low:"+b.yAxis.tickFormat()(c.low)+"
"}),b},a.models.legend=function(){"use strict";function b(p){function q(a,b){return"furious"!=o?"#000":m?a.disengaged?"#000":"#fff":m?void 0:(a.color||(a.color=g(a,b)),a.disabled?a.color:"#fff")}function r(a,b){return m&&"furious"==o&&a.disengaged?"#eee":a.color||g(a,b)}function s(a){return m&&"furious"==o?1:a.disabled?0:1}return p.each(function(b){var g=d-c.left-c.right,p=d3.select(this);a.utils.initSVG(p);var t=p.selectAll("g.nv-legend").data([b]),u=t.enter().append("g").attr("class","nvd3 nv-legend").append("g"),v=t.select("g");t.attr("transform","translate("+c.left+","+c.top+")");var w,x,y=v.selectAll(".nv-series").data(function(a){return"furious"!=o?a:a.filter(function(a){return m?!0:!a.disengaged})}),z=y.enter().append("g").attr("class","nv-series");switch(o){case"furious":x=23;break;case"classic":x=20}if("classic"==o)z.append("circle").style("stroke-width",2).attr("class","nv-legend-symbol").attr("r",5),w=y.select("circle");else if("furious"==o){z.append("rect").style("stroke-width",2).attr("class","nv-legend-symbol").attr("rx",3).attr("ry",3),w=y.select(".nv-legend-symbol"),z.append("g").attr("class","nv-check-box").property("innerHTML",'').attr("transform","translate(-10,-8)scale(0.5)");var A=y.select(".nv-check-box");A.each(function(a,b){d3.select(this).selectAll("path").attr("stroke",q(a,b))})}z.append("text").attr("text-anchor","start").attr("class","nv-legend-text").attr("dy",".32em").attr("dx","8");var B=y.select("text.nv-legend-text");y.on("mouseover",function(a,b){n.legendMouseover(a,b)}).on("mouseout",function(a,b){n.legendMouseout(a,b)}).on("click",function(a,b){n.legendClick(a,b);var c=y.data();if(k){if("classic"==o)l?(c.forEach(function(a){a.disabled=!0}),a.disabled=!1):(a.disabled=!a.disabled,c.every(function(a){return a.disabled})&&c.forEach(function(a){a.disabled=!1}));else if("furious"==o)if(m)a.disengaged=!a.disengaged,a.userDisabled=void 0==a.userDisabled?!!a.disabled:a.userDisabled,a.disabled=a.disengaged||a.userDisabled;else if(!m){a.disabled=!a.disabled,a.userDisabled=a.disabled;var d=c.filter(function(a){return!a.disengaged});d.every(function(a){return a.userDisabled})&&c.forEach(function(a){a.disabled=a.userDisabled=!1})}n.stateChange({disabled:c.map(function(a){return!!a.disabled}),disengaged:c.map(function(a){return!!a.disengaged})})}}).on("dblclick",function(a,b){if(("furious"!=o||!m)&&(n.legendDblclick(a,b),k)){var c=y.data();c.forEach(function(a){a.disabled=!0,"furious"==o&&(a.userDisabled=a.disabled)}),a.disabled=!1,"furious"==o&&(a.userDisabled=a.disabled),n.stateChange({disabled:c.map(function(a){return!!a.disabled})})}}),y.classed("nv-disabled",function(a){return a.userDisabled}),y.exit().remove(),B.attr("fill",q).text(f);var C=0;if(h){var D=[];y.each(function(){var b,c=d3.select(this).select("text");try{if(b=c.node().getComputedTextLength(),0>=b)throw Error()}catch(d){b=a.utils.calcApproxTextWidth(c)}D.push(b+i)});var E=0,F=[];for(C=0;g>C&&Eg&&E>1;){F=[],E--;for(var G=0;G(F[G%E]||0)&&(F[G%E]=D[G]);C=F.reduce(function(a,b){return a+b})}for(var H=[],I=0,J=0;E>I;I++)H[I]=J,J+=F[I];y.attr("transform",function(a,b){return"translate("+H[b%E]+","+(5+Math.floor(b/E)*x)+")"}),j?v.attr("transform","translate("+(d-c.right-C)+","+c.top+")"):v.attr("transform","translate(0,"+c.top+")"),e=c.top+c.bottom+Math.ceil(D.length/E)*x}else{var K,L=5,M=5,N=0;y.attr("transform",function(){var a=d3.select(this).select("text").node().getComputedTextLength()+i;return K=M,dN&&(N=M),K+N>C&&(C=K+N),"translate("+K+","+L+")"}),v.attr("transform","translate("+(d-c.right-N)+","+c.top+")"),e=c.top+c.bottom+L+15}if("furious"==o){w.attr("width",function(a,b){return B[0][b].getComputedTextLength()+27}).attr("height",18).attr("y",-9).attr("x",-15),u.insert("rect",":first-child").attr("class","nv-legend-bg").attr("fill","#eee").attr("opacity",0);var O=v.select(".nv-legend-bg");O.transition().duration(300).attr("x",-x).attr("width",C+x-12).attr("height",e+10).attr("y",-c.top-10).attr("opacity",m?1:0)}w.style("fill",r).style("fill-opacity",s).style("stroke",r)}),b}var c={top:5,right:0,bottom:5,left:0},d=400,e=20,f=function(a){return a.key},g=a.utils.getColor(),h=!0,i=32,j=!0,k=!0,l=!1,m=!1,n=d3.dispatch("legendClick","legendDblclick","legendMouseover","legendMouseout","stateChange"),o="classic";return b.dispatch=n,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return d},set:function(a){d=a}},height:{get:function(){return e},set:function(a){e=a}},key:{get:function(){return f},set:function(a){f=a}},align:{get:function(){return h},set:function(a){h=a}},rightAlign:{get:function(){return j},set:function(a){j=a}},padding:{get:function(){return i},set:function(a){i=a}},updateState:{get:function(){return k},set:function(a){k=a}},radioButtonMode:{get:function(){return l},set:function(a){l=a}},expanded:{get:function(){return m},set:function(a){m=a}},vers:{get:function(){return o},set:function(a){o=a}},margin:{get:function(){return c},set:function(a){c.top=void 0!==a.top?a.top:c.top,c.right=void 0!==a.right?a.right:c.right,c.bottom=void 0!==a.bottom?a.bottom:c.bottom,c.left=void 0!==a.left?a.left:c.left}},color:{get:function(){return g},set:function(b){g=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.line=function(){"use strict";function b(r){return v.reset(),v.models(e),r.each(function(b){i=d3.select(this);var r=a.utils.availableWidth(g,i,f),s=a.utils.availableHeight(h,i,f);a.utils.initSVG(i),c=e.xScale(),d=e.yScale(),t=t||c,u=u||d;var w=i.selectAll("g.nv-wrap.nv-line").data([b]),x=w.enter().append("g").attr("class","nvd3 nv-wrap nv-line"),y=x.append("defs"),z=x.append("g"),A=w.select("g");z.append("g").attr("class","nv-groups"),z.append("g").attr("class","nv-scatterWrap"),w.attr("transform","translate("+f.left+","+f.top+")"),e.width(r).height(s);var B=w.select(".nv-scatterWrap");B.call(e),y.append("clipPath").attr("id","nv-edge-clip-"+e.id()).append("rect"),w.select("#nv-edge-clip-"+e.id()+" rect").attr("width",r).attr("height",s>0?s:0),A.attr("clip-path",p?"url(#nv-edge-clip-"+e.id()+")":""),B.attr("clip-path",p?"url(#nv-edge-clip-"+e.id()+")":"");var C=w.select(".nv-groups").selectAll(".nv-group").data(function(a){return a},function(a){return a.key});C.enter().append("g").style("stroke-opacity",1e-6).style("stroke-width",function(a){return a.strokeWidth||j}).style("fill-opacity",1e-6),C.exit().remove(),C.attr("class",function(a,b){return(a.classed||"")+" nv-group nv-series-"+b}).classed("hover",function(a){return a.hover}).style("fill",function(a,b){return k(a,b)}).style("stroke",function(a,b){return k(a,b)}),C.watchTransition(v,"line: groups").style("stroke-opacity",1).style("fill-opacity",function(a){return a.fillOpacity||.5});var D=C.selectAll("path.nv-area").data(function(a){return o(a)?[a]:[]});D.enter().append("path").attr("class","nv-area").attr("d",function(b){return d3.svg.area().interpolate(q).defined(n).x(function(b,c){return a.utils.NaNtoZero(t(l(b,c)))}).y0(function(b,c){return a.utils.NaNtoZero(u(m(b,c)))}).y1(function(){return u(d.domain()[0]<=0?d.domain()[1]>=0?0:d.domain()[1]:d.domain()[0])}).apply(this,[b.values])}),C.exit().selectAll("path.nv-area").remove(),D.watchTransition(v,"line: areaPaths").attr("d",function(b){return d3.svg.area().interpolate(q).defined(n).x(function(b,d){return a.utils.NaNtoZero(c(l(b,d)))}).y0(function(b,c){return a.utils.NaNtoZero(d(m(b,c)))}).y1(function(){return d(d.domain()[0]<=0?d.domain()[1]>=0?0:d.domain()[1]:d.domain()[0])}).apply(this,[b.values])});var E=C.selectAll("path.nv-line").data(function(a){return[a.values]});E.enter().append("path").attr("class","nv-line").attr("d",d3.svg.line().interpolate(q).defined(n).x(function(b,c){return a.utils.NaNtoZero(t(l(b,c)))}).y(function(b,c){return a.utils.NaNtoZero(u(m(b,c)))})),E.watchTransition(v,"line: linePaths").attr("d",d3.svg.line().interpolate(q).defined(n).x(function(b,d){return a.utils.NaNtoZero(c(l(b,d)))}).y(function(b,c){return a.utils.NaNtoZero(d(m(b,c)))})),t=c.copy(),u=d.copy()}),v.renderEnd("line immediate"),b}var c,d,e=a.models.scatter(),f={top:0,right:0,bottom:0,left:0},g=960,h=500,i=null,j=1.5,k=a.utils.defaultColor(),l=function(a){return a.x},m=function(a){return a.y},n=function(a,b){return!isNaN(m(a,b))&&null!==m(a,b)},o=function(a){return a.area},p=!1,q="linear",r=250,s=d3.dispatch("elementClick","elementMouseover","elementMouseout","renderEnd");e.pointSize(16).pointDomain([16,256]);var t,u,v=a.utils.renderWatch(s,r);return b.dispatch=s,b.scatter=e,e.dispatch.on("elementClick",function(){s.elementClick.apply(this,arguments)}),e.dispatch.on("elementMouseover",function(){s.elementMouseover.apply(this,arguments)}),e.dispatch.on("elementMouseout",function(){s.elementMouseout.apply(this,arguments)}),b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return g},set:function(a){g=a}},height:{get:function(){return h},set:function(a){h=a}},defined:{get:function(){return n},set:function(a){n=a}},interpolate:{get:function(){return q},set:function(a){q=a}},clipEdge:{get:function(){return p},set:function(a){p=a}},margin:{get:function(){return f},set:function(a){f.top=void 0!==a.top?a.top:f.top,f.right=void 0!==a.right?a.right:f.right,f.bottom=void 0!==a.bottom?a.bottom:f.bottom,f.left=void 0!==a.left?a.left:f.left}},duration:{get:function(){return r},set:function(a){r=a,v.reset(r),e.duration(r)}},isArea:{get:function(){return o},set:function(a){o=d3.functor(a)}},x:{get:function(){return l},set:function(a){l=a,e.x(a)}},y:{get:function(){return m},set:function(a){m=a,e.y(a)}},color:{get:function(){return k},set:function(b){k=a.utils.getColor(b),e.color(k)}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.lineChart=function(){"use strict";function b(j){return y.reset(),y.models(e),p&&y.models(f),q&&y.models(g),j.each(function(j){var v=d3.select(this),y=this;a.utils.initSVG(v);var B=a.utils.availableWidth(m,v,k),C=a.utils.availableHeight(n,v,k);if(b.update=function(){0===x?v.call(b):v.transition().duration(x).call(b)},b.container=this,t.setter(A(j),b.update).getter(z(j)).update(),t.disabled=j.map(function(a){return!!a.disabled}),!u){var D;u={};for(D in t)u[D]=t[D]instanceof Array?t[D].slice(0):t[D] -}if(!(j&&j.length&&j.filter(function(a){return a.values.length}).length))return a.utils.noData(b,v),b;v.selectAll(".nv-noData").remove(),c=e.xScale(),d=e.yScale();var E=v.selectAll("g.nv-wrap.nv-lineChart").data([j]),F=E.enter().append("g").attr("class","nvd3 nv-wrap nv-lineChart").append("g"),G=E.select("g");F.append("rect").style("opacity",0),F.append("g").attr("class","nv-x nv-axis"),F.append("g").attr("class","nv-y nv-axis"),F.append("g").attr("class","nv-linesWrap"),F.append("g").attr("class","nv-legendWrap"),F.append("g").attr("class","nv-interactive"),G.select("rect").attr("width",B).attr("height",C>0?C:0),o&&(h.width(B),G.select(".nv-legendWrap").datum(j).call(h),k.top!=h.height()&&(k.top=h.height(),C=a.utils.availableHeight(n,v,k)),E.select(".nv-legendWrap").attr("transform","translate(0,"+-k.top+")")),E.attr("transform","translate("+k.left+","+k.top+")"),r&&G.select(".nv-y.nv-axis").attr("transform","translate("+B+",0)"),s&&(i.width(B).height(C).margin({left:k.left,top:k.top}).svgContainer(v).xScale(c),E.select(".nv-interactive").call(i)),e.width(B).height(C).color(j.map(function(a,b){return a.color||l(a,b)}).filter(function(a,b){return!j[b].disabled}));var H=G.select(".nv-linesWrap").datum(j.filter(function(a){return!a.disabled}));H.call(e),p&&(f.scale(c)._ticks(a.utils.calcTicksX(B/100,j)).tickSize(-C,0),G.select(".nv-x.nv-axis").attr("transform","translate(0,"+d.range()[0]+")"),G.select(".nv-x.nv-axis").call(f)),q&&(g.scale(d)._ticks(a.utils.calcTicksY(C/36,j)).tickSize(-B,0),G.select(".nv-y.nv-axis").call(g)),h.dispatch.on("stateChange",function(a){for(var c in a)t[c]=a[c];w.stateChange(t),b.update()}),i.dispatch.on("elementMousemove",function(c){e.clearHighlights();var d,h,m,n=[];if(j.filter(function(a,b){return a.seriesIndex=b,!a.disabled}).forEach(function(f,g){h=a.interactiveBisect(f.values,c.pointXValue,b.x());var i=f.values[h],j=b.y()(i,h);null!=j&&e.highlightPoint(g,h,!0),void 0!==i&&(void 0===d&&(d=i),void 0===m&&(m=b.xScale()(b.x()(i,h))),n.push({key:f.key,value:j,color:l(f,f.seriesIndex)}))}),n.length>2){var o=b.yScale().invert(c.mouseY),p=Math.abs(b.yScale().domain()[0]-b.yScale().domain()[1]),q=.03*p,r=a.nearestValueIndex(n.map(function(a){return a.value}),o,q);null!==r&&(n[r].highlight=!0)}var s=f.tickFormat()(b.x()(d,h));i.tooltip.position({left:c.mouseX+k.left,top:c.mouseY+k.top}).chartContainer(y.parentNode).valueFormatter(function(a){return null==a?"N/A":g.tickFormat()(a)}).data({value:s,index:h,series:n})(),i.renderGuideLine(m)}),i.dispatch.on("elementClick",function(c){var d,f=[];j.filter(function(a,b){return a.seriesIndex=b,!a.disabled}).forEach(function(e){var g=a.interactiveBisect(e.values,c.pointXValue,b.x()),h=e.values[g];if("undefined"!=typeof h){"undefined"==typeof d&&(d=b.xScale()(b.x()(h,g)));var i=b.yScale()(b.y()(h,g));f.push({point:h,pointIndex:g,pos:[d,i],seriesIndex:e.seriesIndex,series:e})}}),e.dispatch.elementClick(f)}),i.dispatch.on("elementMouseout",function(){e.clearHighlights()}),w.on("changeState",function(a){"undefined"!=typeof a.disabled&&j.length===a.disabled.length&&(j.forEach(function(b,c){b.disabled=a.disabled[c]}),t.disabled=a.disabled),b.update()})}),y.renderEnd("lineChart immediate"),b}var c,d,e=a.models.line(),f=a.models.axis(),g=a.models.axis(),h=a.models.legend(),i=a.interactiveGuideline(),j=a.models.tooltip(),k={top:30,right:20,bottom:50,left:60},l=a.utils.defaultColor(),m=null,n=null,o=!0,p=!0,q=!0,r=!1,s=!1,t=a.utils.state(),u=null,v=null,w=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState","renderEnd"),x=250;f.orient("bottom").tickPadding(7),g.orient(r?"right":"left"),j.valueFormatter(function(a,b){return g.tickFormat()(a,b)}).headerFormatter(function(a,b){return f.tickFormat()(a,b)});var y=a.utils.renderWatch(w,x),z=function(a){return function(){return{active:a.map(function(a){return!a.disabled})}}},A=function(a){return function(b){void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};return e.dispatch.on("elementMouseover.tooltip",function(a){j.data(a).position(a.pos).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(){j.hidden(!0)}),b.dispatch=w,b.lines=e,b.legend=h,b.xAxis=f,b.yAxis=g,b.interactiveLayer=i,b.tooltip=j,b.dispatch=w,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return m},set:function(a){m=a}},height:{get:function(){return n},set:function(a){n=a}},showLegend:{get:function(){return o},set:function(a){o=a}},showXAxis:{get:function(){return p},set:function(a){p=a}},showYAxis:{get:function(){return q},set:function(a){q=a}},defaultState:{get:function(){return u},set:function(a){u=a}},noData:{get:function(){return v},set:function(a){v=a}},tooltips:{get:function(){return j.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),j.enabled(!!b)}},tooltipContent:{get:function(){return j.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),j.contentGenerator(b)}},margin:{get:function(){return k},set:function(a){k.top=void 0!==a.top?a.top:k.top,k.right=void 0!==a.right?a.right:k.right,k.bottom=void 0!==a.bottom?a.bottom:k.bottom,k.left=void 0!==a.left?a.left:k.left}},duration:{get:function(){return x},set:function(a){x=a,y.reset(x),e.duration(x),f.duration(x),g.duration(x)}},color:{get:function(){return l},set:function(b){l=a.utils.getColor(b),h.color(l),e.color(l)}},rightAlignYAxis:{get:function(){return r},set:function(a){r=a,g.orient(r?"right":"left")}},useInteractiveGuideline:{get:function(){return s},set:function(a){s=a,s&&(e.interactive(!1),e.useVoronoi(!1))}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.linePlusBarChart=function(){"use strict";function b(v){return v.each(function(v){function J(a){var b=+("e"==a),c=b?1:-1,d=X/3;return"M"+.5*c+","+d+"A6,6 0 0 "+b+" "+6.5*c+","+(d+6)+"V"+(2*d-6)+"A6,6 0 0 "+b+" "+.5*c+","+2*d+"ZM"+2.5*c+","+(d+8)+"V"+(2*d-8)+"M"+4.5*c+","+(d+8)+"V"+(2*d-8)}function S(){u.empty()||u.extent(I),kb.data([u.empty()?e.domain():I]).each(function(a){var b=e(a[0])-e.range()[0],c=e.range()[1]-e(a[1]);d3.select(this).select(".left").attr("width",0>b?0:b),d3.select(this).select(".right").attr("x",e(a[1])).attr("width",0>c?0:c)})}function T(){I=u.empty()?null:u.extent(),c=u.empty()?e.domain():u.extent(),K.brush({extent:c,brush:u}),S(),l.width(V).height(W).color(v.map(function(a,b){return a.color||C(a,b)}).filter(function(a,b){return!v[b].disabled&&v[b].bar})),j.width(V).height(W).color(v.map(function(a,b){return a.color||C(a,b)}).filter(function(a,b){return!v[b].disabled&&!v[b].bar}));var b=db.select(".nv-focus .nv-barsWrap").datum(Z.length?Z.map(function(a){return{key:a.key,values:a.values.filter(function(a,b){return l.x()(a,b)>=c[0]&&l.x()(a,b)<=c[1]})}}):[{values:[]}]),h=db.select(".nv-focus .nv-linesWrap").datum($[0].disabled?[{values:[]}]:$.map(function(a){return{area:a.area,fillOpacity:a.fillOpacity,key:a.key,values:a.values.filter(function(a,b){return j.x()(a,b)>=c[0]&&j.x()(a,b)<=c[1]})}}));d=Z.length?l.xScale():j.xScale(),n.scale(d)._ticks(a.utils.calcTicksX(V/100,v)).tickSize(-W,0),n.domain([Math.ceil(c[0]),Math.floor(c[1])]),db.select(".nv-x.nv-axis").transition().duration(L).call(n),b.transition().duration(L).call(l),h.transition().duration(L).call(j),db.select(".nv-focus .nv-x.nv-axis").attr("transform","translate(0,"+f.range()[0]+")"),p.scale(f)._ticks(a.utils.calcTicksY(W/36,v)).tickSize(-V,0),q.scale(g)._ticks(a.utils.calcTicksY(W/36,v)).tickSize(Z.length?0:-V,0),db.select(".nv-focus .nv-y1.nv-axis").style("opacity",Z.length?1:0),db.select(".nv-focus .nv-y2.nv-axis").style("opacity",$.length&&!$[0].disabled?1:0).attr("transform","translate("+d.range()[1]+",0)"),db.select(".nv-focus .nv-y1.nv-axis").transition().duration(L).call(p),db.select(".nv-focus .nv-y2.nv-axis").transition().duration(L).call(q)}var U=d3.select(this);a.utils.initSVG(U);var V=a.utils.availableWidth(y,U,w),W=a.utils.availableHeight(z,U,w)-(E?H:0),X=H-x.top-x.bottom;if(b.update=function(){U.transition().duration(L).call(b)},b.container=this,M.setter(R(v),b.update).getter(Q(v)).update(),M.disabled=v.map(function(a){return!!a.disabled}),!N){var Y;N={};for(Y in M)N[Y]=M[Y]instanceof Array?M[Y].slice(0):M[Y]}if(!(v&&v.length&&v.filter(function(a){return a.values.length}).length))return a.utils.noData(b,U),b;U.selectAll(".nv-noData").remove();var Z=v.filter(function(a){return!a.disabled&&a.bar}),$=v.filter(function(a){return!a.bar});d=l.xScale(),e=o.scale(),f=l.yScale(),g=j.yScale(),h=m.yScale(),i=k.yScale();var _=v.filter(function(a){return!a.disabled&&a.bar}).map(function(a){return a.values.map(function(a,b){return{x:A(a,b),y:B(a,b)}})}),ab=v.filter(function(a){return!a.disabled&&!a.bar}).map(function(a){return a.values.map(function(a,b){return{x:A(a,b),y:B(a,b)}})});d.range([0,V]),e.domain(d3.extent(d3.merge(_.concat(ab)),function(a){return a.x})).range([0,V]);var bb=U.selectAll("g.nv-wrap.nv-linePlusBar").data([v]),cb=bb.enter().append("g").attr("class","nvd3 nv-wrap nv-linePlusBar").append("g"),db=bb.select("g");cb.append("g").attr("class","nv-legendWrap");var eb=cb.append("g").attr("class","nv-focus");eb.append("g").attr("class","nv-x nv-axis"),eb.append("g").attr("class","nv-y1 nv-axis"),eb.append("g").attr("class","nv-y2 nv-axis"),eb.append("g").attr("class","nv-barsWrap"),eb.append("g").attr("class","nv-linesWrap");var fb=cb.append("g").attr("class","nv-context");if(fb.append("g").attr("class","nv-x nv-axis"),fb.append("g").attr("class","nv-y1 nv-axis"),fb.append("g").attr("class","nv-y2 nv-axis"),fb.append("g").attr("class","nv-barsWrap"),fb.append("g").attr("class","nv-linesWrap"),fb.append("g").attr("class","nv-brushBackground"),fb.append("g").attr("class","nv-x nv-brush"),D){var gb=t.align()?V/2:V,hb=t.align()?gb:0;t.width(gb),db.select(".nv-legendWrap").datum(v.map(function(a){return a.originalKey=void 0===a.originalKey?a.key:a.originalKey,a.key=a.originalKey+(a.bar?O:P),a})).call(t),w.top!=t.height()&&(w.top=t.height(),W=a.utils.availableHeight(z,U,w)-H),db.select(".nv-legendWrap").attr("transform","translate("+hb+","+-w.top+")")}bb.attr("transform","translate("+w.left+","+w.top+")"),db.select(".nv-context").style("display",E?"initial":"none"),m.width(V).height(X).color(v.map(function(a,b){return a.color||C(a,b)}).filter(function(a,b){return!v[b].disabled&&v[b].bar})),k.width(V).height(X).color(v.map(function(a,b){return a.color||C(a,b)}).filter(function(a,b){return!v[b].disabled&&!v[b].bar}));var ib=db.select(".nv-context .nv-barsWrap").datum(Z.length?Z:[{values:[]}]),jb=db.select(".nv-context .nv-linesWrap").datum($[0].disabled?[{values:[]}]:$);db.select(".nv-context").attr("transform","translate(0,"+(W+w.bottom+x.top)+")"),ib.transition().call(m),jb.transition().call(k),G&&(o._ticks(a.utils.calcTicksX(V/100,v)).tickSize(-X,0),db.select(".nv-context .nv-x.nv-axis").attr("transform","translate(0,"+h.range()[0]+")"),db.select(".nv-context .nv-x.nv-axis").transition().call(o)),F&&(r.scale(h)._ticks(X/36).tickSize(-V,0),s.scale(i)._ticks(X/36).tickSize(Z.length?0:-V,0),db.select(".nv-context .nv-y3.nv-axis").style("opacity",Z.length?1:0).attr("transform","translate(0,"+e.range()[0]+")"),db.select(".nv-context .nv-y2.nv-axis").style("opacity",$.length?1:0).attr("transform","translate("+e.range()[1]+",0)"),db.select(".nv-context .nv-y1.nv-axis").transition().call(r),db.select(".nv-context .nv-y2.nv-axis").transition().call(s)),u.x(e).on("brush",T),I&&u.extent(I);var kb=db.select(".nv-brushBackground").selectAll("g").data([I||u.extent()]),lb=kb.enter().append("g");lb.append("rect").attr("class","left").attr("x",0).attr("y",0).attr("height",X),lb.append("rect").attr("class","right").attr("x",0).attr("y",0).attr("height",X);var mb=db.select(".nv-x.nv-brush").call(u);mb.selectAll("rect").attr("height",X),mb.selectAll(".resize").append("path").attr("d",J),t.dispatch.on("stateChange",function(a){for(var c in a)M[c]=a[c];K.stateChange(M),b.update()}),K.on("changeState",function(a){"undefined"!=typeof a.disabled&&(v.forEach(function(b,c){b.disabled=a.disabled[c]}),M.disabled=a.disabled),b.update()}),T()}),b}var c,d,e,f,g,h,i,j=a.models.line(),k=a.models.line(),l=a.models.historicalBar(),m=a.models.historicalBar(),n=a.models.axis(),o=a.models.axis(),p=a.models.axis(),q=a.models.axis(),r=a.models.axis(),s=a.models.axis(),t=a.models.legend(),u=d3.svg.brush(),v=a.models.tooltip(),w={top:30,right:30,bottom:30,left:60},x={top:0,right:30,bottom:20,left:60},y=null,z=null,A=function(a){return a.x},B=function(a){return a.y},C=a.utils.defaultColor(),D=!0,E=!0,F=!1,G=!0,H=50,I=null,J=null,K=d3.dispatch("brush","stateChange","changeState"),L=0,M=a.utils.state(),N=null,O=" (left axis)",P=" (right axis)";j.clipEdge(!0),k.interactive(!1),n.orient("bottom").tickPadding(5),p.orient("left"),q.orient("right"),o.orient("bottom").tickPadding(5),r.orient("left"),s.orient("right"),v.headerEnabled(!0).headerFormatter(function(a,b){return n.tickFormat()(a,b)});var Q=function(a){return function(){return{active:a.map(function(a){return!a.disabled})}}},R=function(a){return function(b){void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};return j.dispatch.on("elementMouseover.tooltip",function(a){v.duration(100).valueFormatter(function(a,b){return q.tickFormat()(a,b)}).data(a).position(a.pos).hidden(!1)}),j.dispatch.on("elementMouseout.tooltip",function(){v.hidden(!0)}),l.dispatch.on("elementMouseover.tooltip",function(a){a.value=b.x()(a.data),a.series={value:b.y()(a.data),color:a.color},v.duration(0).valueFormatter(function(a,b){return p.tickFormat()(a,b)}).data(a).hidden(!1)}),l.dispatch.on("elementMouseout.tooltip",function(){v.hidden(!0)}),l.dispatch.on("elementMousemove.tooltip",function(){v.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.dispatch=K,b.legend=t,b.lines=j,b.lines2=k,b.bars=l,b.bars2=m,b.xAxis=n,b.x2Axis=o,b.y1Axis=p,b.y2Axis=q,b.y3Axis=r,b.y4Axis=s,b.tooltip=v,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return y},set:function(a){y=a}},height:{get:function(){return z},set:function(a){z=a}},showLegend:{get:function(){return D},set:function(a){D=a}},brushExtent:{get:function(){return I},set:function(a){I=a}},noData:{get:function(){return J},set:function(a){J=a}},focusEnable:{get:function(){return E},set:function(a){E=a}},focusHeight:{get:function(){return H},set:function(a){H=a}},focusShowAxisX:{get:function(){return G},set:function(a){G=a}},focusShowAxisY:{get:function(){return F},set:function(a){F=a}},legendLeftAxisHint:{get:function(){return O},set:function(a){O=a}},legendRightAxisHint:{get:function(){return P},set:function(a){P=a}},tooltips:{get:function(){return v.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),v.enabled(!!b)}},tooltipContent:{get:function(){return v.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),v.contentGenerator(b)}},margin:{get:function(){return w},set:function(a){w.top=void 0!==a.top?a.top:w.top,w.right=void 0!==a.right?a.right:w.right,w.bottom=void 0!==a.bottom?a.bottom:w.bottom,w.left=void 0!==a.left?a.left:w.left}},duration:{get:function(){return L},set:function(a){L=a}},color:{get:function(){return C},set:function(b){C=a.utils.getColor(b),t.color(C)}},x:{get:function(){return A},set:function(a){A=a,j.x(a),k.x(a),l.x(a),m.x(a)}},y:{get:function(){return B},set:function(a){B=a,j.y(a),k.y(a),l.y(a),m.y(a)}}}),a.utils.inheritOptions(b,j),a.utils.initOptions(b),b},a.models.lineWithFocusChart=function(){"use strict";function b(o){return o.each(function(o){function z(a){var b=+("e"==a),c=b?1:-1,d=M/3;return"M"+.5*c+","+d+"A6,6 0 0 "+b+" "+6.5*c+","+(d+6)+"V"+(2*d-6)+"A6,6 0 0 "+b+" "+.5*c+","+2*d+"ZM"+2.5*c+","+(d+8)+"V"+(2*d-8)+"M"+4.5*c+","+(d+8)+"V"+(2*d-8)}function G(){n.empty()||n.extent(y),U.data([n.empty()?e.domain():y]).each(function(a){var b=e(a[0])-c.range()[0],d=K-e(a[1]);d3.select(this).select(".left").attr("width",0>b?0:b),d3.select(this).select(".right").attr("x",e(a[1])).attr("width",0>d?0:d)})}function H(){y=n.empty()?null:n.extent();var a=n.empty()?e.domain():n.extent();if(!(Math.abs(a[0]-a[1])<=1)){A.brush({extent:a,brush:n}),G();var b=Q.select(".nv-focus .nv-linesWrap").datum(o.filter(function(a){return!a.disabled}).map(function(b){return{key:b.key,area:b.area,values:b.values.filter(function(b,c){return g.x()(b,c)>=a[0]&&g.x()(b,c)<=a[1]})}}));b.transition().duration(B).call(g),Q.select(".nv-focus .nv-x.nv-axis").transition().duration(B).call(i),Q.select(".nv-focus .nv-y.nv-axis").transition().duration(B).call(j)}}var I=d3.select(this),J=this;a.utils.initSVG(I);var K=a.utils.availableWidth(t,I,q),L=a.utils.availableHeight(u,I,q)-v,M=v-r.top-r.bottom;if(b.update=function(){I.transition().duration(B).call(b)},b.container=this,C.setter(F(o),b.update).getter(E(o)).update(),C.disabled=o.map(function(a){return!!a.disabled}),!D){var N;D={};for(N in C)D[N]=C[N]instanceof Array?C[N].slice(0):C[N]}if(!(o&&o.length&&o.filter(function(a){return a.values.length}).length))return a.utils.noData(b,I),b;I.selectAll(".nv-noData").remove(),c=g.xScale(),d=g.yScale(),e=h.xScale(),f=h.yScale();var O=I.selectAll("g.nv-wrap.nv-lineWithFocusChart").data([o]),P=O.enter().append("g").attr("class","nvd3 nv-wrap nv-lineWithFocusChart").append("g"),Q=O.select("g");P.append("g").attr("class","nv-legendWrap");var R=P.append("g").attr("class","nv-focus");R.append("g").attr("class","nv-x nv-axis"),R.append("g").attr("class","nv-y nv-axis"),R.append("g").attr("class","nv-linesWrap"),R.append("g").attr("class","nv-interactive");var S=P.append("g").attr("class","nv-context");S.append("g").attr("class","nv-x nv-axis"),S.append("g").attr("class","nv-y nv-axis"),S.append("g").attr("class","nv-linesWrap"),S.append("g").attr("class","nv-brushBackground"),S.append("g").attr("class","nv-x nv-brush"),x&&(m.width(K),Q.select(".nv-legendWrap").datum(o).call(m),q.top!=m.height()&&(q.top=m.height(),L=a.utils.availableHeight(u,I,q)-v),Q.select(".nv-legendWrap").attr("transform","translate(0,"+-q.top+")")),O.attr("transform","translate("+q.left+","+q.top+")"),w&&(p.width(K).height(L).margin({left:q.left,top:q.top}).svgContainer(I).xScale(c),O.select(".nv-interactive").call(p)),g.width(K).height(L).color(o.map(function(a,b){return a.color||s(a,b)}).filter(function(a,b){return!o[b].disabled})),h.defined(g.defined()).width(K).height(M).color(o.map(function(a,b){return a.color||s(a,b)}).filter(function(a,b){return!o[b].disabled})),Q.select(".nv-context").attr("transform","translate(0,"+(L+q.bottom+r.top)+")");var T=Q.select(".nv-context .nv-linesWrap").datum(o.filter(function(a){return!a.disabled}));d3.transition(T).call(h),i.scale(c)._ticks(a.utils.calcTicksX(K/100,o)).tickSize(-L,0),j.scale(d)._ticks(a.utils.calcTicksY(L/36,o)).tickSize(-K,0),Q.select(".nv-focus .nv-x.nv-axis").attr("transform","translate(0,"+L+")"),n.x(e).on("brush",function(){H()}),y&&n.extent(y);var U=Q.select(".nv-brushBackground").selectAll("g").data([y||n.extent()]),V=U.enter().append("g");V.append("rect").attr("class","left").attr("x",0).attr("y",0).attr("height",M),V.append("rect").attr("class","right").attr("x",0).attr("y",0).attr("height",M);var W=Q.select(".nv-x.nv-brush").call(n);W.selectAll("rect").attr("height",M),W.selectAll(".resize").append("path").attr("d",z),H(),k.scale(e)._ticks(a.utils.calcTicksX(K/100,o)).tickSize(-M,0),Q.select(".nv-context .nv-x.nv-axis").attr("transform","translate(0,"+f.range()[0]+")"),d3.transition(Q.select(".nv-context .nv-x.nv-axis")).call(k),l.scale(f)._ticks(a.utils.calcTicksY(M/36,o)).tickSize(-K,0),d3.transition(Q.select(".nv-context .nv-y.nv-axis")).call(l),Q.select(".nv-context .nv-x.nv-axis").attr("transform","translate(0,"+f.range()[0]+")"),m.dispatch.on("stateChange",function(a){for(var c in a)C[c]=a[c];A.stateChange(C),b.update()}),p.dispatch.on("elementMousemove",function(c){g.clearHighlights();var d,f,h,k=[];if(o.filter(function(a,b){return a.seriesIndex=b,!a.disabled}).forEach(function(i,j){var l=n.empty()?e.domain():n.extent(),m=i.values.filter(function(a,b){return g.x()(a,b)>=l[0]&&g.x()(a,b)<=l[1]});f=a.interactiveBisect(m,c.pointXValue,g.x());var o=m[f],p=b.y()(o,f);null!=p&&g.highlightPoint(j,f,!0),void 0!==o&&(void 0===d&&(d=o),void 0===h&&(h=b.xScale()(b.x()(o,f))),k.push({key:i.key,value:b.y()(o,f),color:s(i,i.seriesIndex)}))}),k.length>2){var l=b.yScale().invert(c.mouseY),m=Math.abs(b.yScale().domain()[0]-b.yScale().domain()[1]),r=.03*m,t=a.nearestValueIndex(k.map(function(a){return a.value}),l,r);null!==t&&(k[t].highlight=!0)}var u=i.tickFormat()(b.x()(d,f));p.tooltip.position({left:c.mouseX+q.left,top:c.mouseY+q.top}).chartContainer(J.parentNode).valueFormatter(function(a){return null==a?"N/A":j.tickFormat()(a)}).data({value:u,index:f,series:k})(),p.renderGuideLine(h)}),p.dispatch.on("elementMouseout",function(){g.clearHighlights()}),A.on("changeState",function(a){"undefined"!=typeof a.disabled&&o.forEach(function(b,c){b.disabled=a.disabled[c]}),b.update()})}),b}var c,d,e,f,g=a.models.line(),h=a.models.line(),i=a.models.axis(),j=a.models.axis(),k=a.models.axis(),l=a.models.axis(),m=a.models.legend(),n=d3.svg.brush(),o=a.models.tooltip(),p=a.interactiveGuideline(),q={top:30,right:30,bottom:30,left:60},r={top:0,right:30,bottom:20,left:60},s=a.utils.defaultColor(),t=null,u=null,v=50,w=!1,x=!0,y=null,z=null,A=d3.dispatch("brush","stateChange","changeState"),B=250,C=a.utils.state(),D=null;g.clipEdge(!0).duration(0),h.interactive(!1),i.orient("bottom").tickPadding(5),j.orient("left"),k.orient("bottom").tickPadding(5),l.orient("left"),o.valueFormatter(function(a,b){return j.tickFormat()(a,b)}).headerFormatter(function(a,b){return i.tickFormat()(a,b)});var E=function(a){return function(){return{active:a.map(function(a){return!a.disabled})}}},F=function(a){return function(b){void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};return g.dispatch.on("elementMouseover.tooltip",function(a){o.data(a).position(a.pos).hidden(!1)}),g.dispatch.on("elementMouseout.tooltip",function(){o.hidden(!0)}),b.dispatch=A,b.legend=m,b.lines=g,b.lines2=h,b.xAxis=i,b.yAxis=j,b.x2Axis=k,b.y2Axis=l,b.interactiveLayer=p,b.tooltip=o,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return t},set:function(a){t=a}},height:{get:function(){return u},set:function(a){u=a}},focusHeight:{get:function(){return v},set:function(a){v=a}},showLegend:{get:function(){return x},set:function(a){x=a}},brushExtent:{get:function(){return y},set:function(a){y=a}},defaultState:{get:function(){return D},set:function(a){D=a}},noData:{get:function(){return z},set:function(a){z=a}},tooltips:{get:function(){return o.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),o.enabled(!!b)}},tooltipContent:{get:function(){return o.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),o.contentGenerator(b)}},margin:{get:function(){return q},set:function(a){q.top=void 0!==a.top?a.top:q.top,q.right=void 0!==a.right?a.right:q.right,q.bottom=void 0!==a.bottom?a.bottom:q.bottom,q.left=void 0!==a.left?a.left:q.left}},color:{get:function(){return s},set:function(b){s=a.utils.getColor(b),m.color(s)}},interpolate:{get:function(){return g.interpolate()},set:function(a){g.interpolate(a),h.interpolate(a)}},xTickFormat:{get:function(){return i.tickFormat()},set:function(a){i.tickFormat(a),k.tickFormat(a)}},yTickFormat:{get:function(){return j.tickFormat()},set:function(a){j.tickFormat(a),l.tickFormat(a)}},duration:{get:function(){return B},set:function(a){B=a,j.duration(B),l.duration(B),i.duration(B),k.duration(B)}},x:{get:function(){return g.x()},set:function(a){g.x(a),h.x(a)}},y:{get:function(){return g.y()},set:function(a){g.y(a),h.y(a)}},useInteractiveGuideline:{get:function(){return w},set:function(a){w=a,w&&(g.interactive(!1),g.useVoronoi(!1))}}}),a.utils.inheritOptions(b,g),a.utils.initOptions(b),b},a.models.multiBar=function(){"use strict";function b(E){return C.reset(),E.each(function(b){var E=k-j.left-j.right,F=l-j.top-j.bottom;p=d3.select(this),a.utils.initSVG(p);var G=0;if(x&&b.length&&(x=[{values:b[0].values.map(function(a){return{x:a.x,y:0,series:a.series,size:.01}})}]),u){var H=d3.layout.stack().offset(v).values(function(a){return a.values}).y(r)(!b.length&&x?x:b);H.forEach(function(a,c){a.nonStackable?(b[c].nonStackableSeries=G++,H[c]=b[c]):c>0&&H[c-1].nonStackable&&H[c].values.map(function(a,b){a.y0-=H[c-1].values[b].y,a.y1=a.y0+a.y})}),b=H}b.forEach(function(a,b){a.values.forEach(function(c){c.series=b,c.key=a.key})}),u&&b[0].values.map(function(a,c){var d=0,e=0;b.map(function(a,f){if(!b[f].nonStackable){var g=a.values[c];g.size=Math.abs(g.y),g.y<0?(g.y1=e,e-=g.size):(g.y1=g.size+d,d+=g.size)}})});var I=d&&e?[]:b.map(function(a,b){return a.values.map(function(a,c){return{x:q(a,c),y:r(a,c),y0:a.y0,y1:a.y1,idx:b}})});m.domain(d||d3.merge(I).map(function(a){return a.x})).rangeBands(f||[0,E],A),n.domain(e||d3.extent(d3.merge(I).map(function(a){var c=a.y;return u&&!b[a.idx].nonStackable&&(c=a.y>0?a.y1:a.y1+a.y),c}).concat(s))).range(g||[F,0]),m.domain()[0]===m.domain()[1]&&m.domain(m.domain()[0]?[m.domain()[0]-.01*m.domain()[0],m.domain()[1]+.01*m.domain()[1]]:[-1,1]),n.domain()[0]===n.domain()[1]&&n.domain(n.domain()[0]?[n.domain()[0]+.01*n.domain()[0],n.domain()[1]-.01*n.domain()[1]]:[-1,1]),h=h||m,i=i||n;var J=p.selectAll("g.nv-wrap.nv-multibar").data([b]),K=J.enter().append("g").attr("class","nvd3 nv-wrap nv-multibar"),L=K.append("defs"),M=K.append("g"),N=J.select("g");M.append("g").attr("class","nv-groups"),J.attr("transform","translate("+j.left+","+j.top+")"),L.append("clipPath").attr("id","nv-edge-clip-"+o).append("rect"),J.select("#nv-edge-clip-"+o+" rect").attr("width",E).attr("height",F),N.attr("clip-path",t?"url(#nv-edge-clip-"+o+")":"");var O=J.select(".nv-groups").selectAll(".nv-group").data(function(a){return a},function(a,b){return b});O.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6);var P=C.transition(O.exit().selectAll("rect.nv-bar"),"multibarExit",Math.min(100,z)).attr("y",function(a){var c=i(0)||0;return u&&b[a.series]&&!b[a.series].nonStackable&&(c=i(a.y0)),c}).attr("height",0).remove();P.delay&&P.delay(function(a,b){var c=b*(z/(D+1))-b;return c}),O.attr("class",function(a,b){return"nv-group nv-series-"+b}).classed("hover",function(a){return a.hover}).style("fill",function(a,b){return w(a,b)}).style("stroke",function(a,b){return w(a,b)}),O.style("stroke-opacity",1).style("fill-opacity",.75);var Q=O.selectAll("rect.nv-bar").data(function(a){return x&&!b.length?x.values:a.values});Q.exit().remove();Q.enter().append("rect").attr("class",function(a,b){return r(a,b)<0?"nv-bar negative":"nv-bar positive"}).attr("x",function(a,c,d){return u&&!b[d].nonStackable?0:d*m.rangeBand()/b.length}).attr("y",function(a,c,d){return i(u&&!b[d].nonStackable?a.y0:0)||0}).attr("height",0).attr("width",function(a,c,d){return m.rangeBand()/(u&&!b[d].nonStackable?1:b.length)}).attr("transform",function(a,b){return"translate("+m(q(a,b))+",0)"});Q.style("fill",function(a,b,c){return w(a,c,b)}).style("stroke",function(a,b,c){return w(a,c,b)}).on("mouseover",function(a,b){d3.select(this).classed("hover",!0),B.elementMouseover({data:a,index:b,color:d3.select(this).style("fill")})}).on("mouseout",function(a,b){d3.select(this).classed("hover",!1),B.elementMouseout({data:a,index:b,color:d3.select(this).style("fill")})}).on("mousemove",function(a,b){B.elementMousemove({data:a,index:b,color:d3.select(this).style("fill")})}).on("click",function(a,b){B.elementClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation()}).on("dblclick",function(a,b){B.elementDblClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation()}),Q.attr("class",function(a,b){return r(a,b)<0?"nv-bar negative":"nv-bar positive"}).attr("transform",function(a,b){return"translate("+m(q(a,b))+",0)"}),y&&(c||(c=b.map(function(){return!0})),Q.style("fill",function(a,b,d){return d3.rgb(y(a,b)).darker(c.map(function(a,b){return b}).filter(function(a,b){return!c[b]})[d]).toString()}).style("stroke",function(a,b,d){return d3.rgb(y(a,b)).darker(c.map(function(a,b){return b}).filter(function(a,b){return!c[b]})[d]).toString()}));var R=Q.watchTransition(C,"multibar",Math.min(250,z)).delay(function(a,c){return c*z/b[0].values.length});u?R.attr("y",function(a,c,d){var e=0;return e=b[d].nonStackable?r(a,c)<0?n(0):n(0)-n(r(a,c))<-1?n(0)-1:n(r(a,c))||0:n(a.y1)}).attr("height",function(a,c,d){return b[d].nonStackable?Math.max(Math.abs(n(r(a,c))-n(0)),1)||0:Math.max(Math.abs(n(a.y+a.y0)-n(a.y0)),1)}).attr("x",function(a,c,d){var e=0;return b[d].nonStackable&&(e=a.series*m.rangeBand()/b.length,b.length!==G&&(e=b[d].nonStackableSeries*m.rangeBand()/(2*G))),e}).attr("width",function(a,c,d){if(b[d].nonStackable){var e=m.rangeBand()/G;return b.length!==G&&(e=m.rangeBand()/(2*G)),e}return m.rangeBand()}):R.attr("x",function(a){return a.series*m.rangeBand()/b.length}).attr("width",m.rangeBand()/b.length).attr("y",function(a,b){return r(a,b)<0?n(0):n(0)-n(r(a,b))<1?n(0)-1:n(r(a,b))||0}).attr("height",function(a,b){return Math.max(Math.abs(n(r(a,b))-n(0)),1)||0}),h=m.copy(),i=n.copy(),b[0]&&b[0].values&&(D=b[0].values.length)}),C.renderEnd("multibar immediate"),b}var c,d,e,f,g,h,i,j={top:0,right:0,bottom:0,left:0},k=960,l=500,m=d3.scale.ordinal(),n=d3.scale.linear(),o=Math.floor(1e4*Math.random()),p=null,q=function(a){return a.x},r=function(a){return a.y},s=[0],t=!0,u=!1,v="zero",w=a.utils.defaultColor(),x=!1,y=null,z=500,A=.1,B=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove","renderEnd"),C=a.utils.renderWatch(B,z),D=0;return b.dispatch=B,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return k},set:function(a){k=a}},height:{get:function(){return l},set:function(a){l=a}},x:{get:function(){return q},set:function(a){q=a}},y:{get:function(){return r},set:function(a){r=a}},xScale:{get:function(){return m},set:function(a){m=a}},yScale:{get:function(){return n},set:function(a){n=a}},xDomain:{get:function(){return d},set:function(a){d=a}},yDomain:{get:function(){return e},set:function(a){e=a}},xRange:{get:function(){return f},set:function(a){f=a}},yRange:{get:function(){return g},set:function(a){g=a}},forceY:{get:function(){return s},set:function(a){s=a}},stacked:{get:function(){return u},set:function(a){u=a}},stackOffset:{get:function(){return v},set:function(a){v=a}},clipEdge:{get:function(){return t},set:function(a){t=a}},disabled:{get:function(){return c},set:function(a){c=a}},id:{get:function(){return o},set:function(a){o=a}},hideable:{get:function(){return x},set:function(a){x=a}},groupSpacing:{get:function(){return A},set:function(a){A=a}},margin:{get:function(){return j},set:function(a){j.top=void 0!==a.top?a.top:j.top,j.right=void 0!==a.right?a.right:j.right,j.bottom=void 0!==a.bottom?a.bottom:j.bottom,j.left=void 0!==a.left?a.left:j.left}},duration:{get:function(){return z},set:function(a){z=a,C.reset(z)}},color:{get:function(){return w},set:function(b){w=a.utils.getColor(b)}},barColor:{get:function(){return y},set:function(b){y=b?a.utils.getColor(b):null}}}),a.utils.initOptions(b),b},a.models.multiBarChart=function(){"use strict";function b(j){return D.reset(),D.models(e),r&&D.models(f),s&&D.models(g),j.each(function(j){var z=d3.select(this);a.utils.initSVG(z);var D=a.utils.availableWidth(l,z,k),H=a.utils.availableHeight(m,z,k);if(b.update=function(){0===C?z.call(b):z.transition().duration(C).call(b)},b.container=this,x.setter(G(j),b.update).getter(F(j)).update(),x.disabled=j.map(function(a){return!!a.disabled}),!y){var I;y={};for(I in x)y[I]=x[I]instanceof Array?x[I].slice(0):x[I]}if(!(j&&j.length&&j.filter(function(a){return a.values.length}).length))return a.utils.noData(b,z),b;z.selectAll(".nv-noData").remove(),c=e.xScale(),d=e.yScale(); -var J=z.selectAll("g.nv-wrap.nv-multiBarWithLegend").data([j]),K=J.enter().append("g").attr("class","nvd3 nv-wrap nv-multiBarWithLegend").append("g"),L=J.select("g");if(K.append("g").attr("class","nv-x nv-axis"),K.append("g").attr("class","nv-y nv-axis"),K.append("g").attr("class","nv-barsWrap"),K.append("g").attr("class","nv-legendWrap"),K.append("g").attr("class","nv-controlsWrap"),q&&(h.width(D-B()),L.select(".nv-legendWrap").datum(j).call(h),k.top!=h.height()&&(k.top=h.height(),H=a.utils.availableHeight(m,z,k)),L.select(".nv-legendWrap").attr("transform","translate("+B()+","+-k.top+")")),o){var M=[{key:p.grouped||"Grouped",disabled:e.stacked()},{key:p.stacked||"Stacked",disabled:!e.stacked()}];i.width(B()).color(["#444","#444","#444"]),L.select(".nv-controlsWrap").datum(M).attr("transform","translate(0,"+-k.top+")").call(i)}J.attr("transform","translate("+k.left+","+k.top+")"),t&&L.select(".nv-y.nv-axis").attr("transform","translate("+D+",0)"),e.disabled(j.map(function(a){return a.disabled})).width(D).height(H).color(j.map(function(a,b){return a.color||n(a,b)}).filter(function(a,b){return!j[b].disabled}));var N=L.select(".nv-barsWrap").datum(j.filter(function(a){return!a.disabled}));if(N.call(e),r){f.scale(c)._ticks(a.utils.calcTicksX(D/100,j)).tickSize(-H,0),L.select(".nv-x.nv-axis").attr("transform","translate(0,"+d.range()[0]+")"),L.select(".nv-x.nv-axis").call(f);var O=L.select(".nv-x.nv-axis > g").selectAll("g");if(O.selectAll("line, text").style("opacity",1),v){var P=function(a,b){return"translate("+a+","+b+")"},Q=5,R=17;O.selectAll("text").attr("transform",function(a,b,c){return P(0,c%2==0?Q:R)});var S=d3.selectAll(".nv-x.nv-axis .nv-wrap g g text")[0].length;L.selectAll(".nv-x.nv-axis .nv-axisMaxMin text").attr("transform",function(a,b){return P(0,0===b||S%2!==0?R:Q)})}u&&O.filter(function(a,b){return b%Math.ceil(j[0].values.length/(D/100))!==0}).selectAll("text, line").style("opacity",0),w&&O.selectAll(".tick text").attr("transform","rotate("+w+" 0,0)").style("text-anchor",w>0?"start":"end"),L.select(".nv-x.nv-axis").selectAll("g.nv-axisMaxMin text").style("opacity",1)}s&&(g.scale(d)._ticks(a.utils.calcTicksY(H/36,j)).tickSize(-D,0),L.select(".nv-y.nv-axis").call(g)),h.dispatch.on("stateChange",function(a){for(var c in a)x[c]=a[c];A.stateChange(x),b.update()}),i.dispatch.on("legendClick",function(a){if(a.disabled){switch(M=M.map(function(a){return a.disabled=!0,a}),a.disabled=!1,a.key){case"Grouped":case p.grouped:e.stacked(!1);break;case"Stacked":case p.stacked:e.stacked(!0)}x.stacked=e.stacked(),A.stateChange(x),b.update()}}),A.on("changeState",function(a){"undefined"!=typeof a.disabled&&(j.forEach(function(b,c){b.disabled=a.disabled[c]}),x.disabled=a.disabled),"undefined"!=typeof a.stacked&&(e.stacked(a.stacked),x.stacked=a.stacked,E=a.stacked),b.update()})}),D.renderEnd("multibarchart immediate"),b}var c,d,e=a.models.multiBar(),f=a.models.axis(),g=a.models.axis(),h=a.models.legend(),i=a.models.legend(),j=a.models.tooltip(),k={top:30,right:20,bottom:50,left:60},l=null,m=null,n=a.utils.defaultColor(),o=!0,p={},q=!0,r=!0,s=!0,t=!1,u=!0,v=!1,w=0,x=a.utils.state(),y=null,z=null,A=d3.dispatch("stateChange","changeState","renderEnd"),B=function(){return o?180:0},C=250;x.stacked=!1,e.stacked(!1),f.orient("bottom").tickPadding(7).showMaxMin(!1).tickFormat(function(a){return a}),g.orient(t?"right":"left").tickFormat(d3.format(",.1f")),j.duration(0).valueFormatter(function(a,b){return g.tickFormat()(a,b)}).headerFormatter(function(a,b){return f.tickFormat()(a,b)}),i.updateState(!1);var D=a.utils.renderWatch(A),E=!1,F=function(a){return function(){return{active:a.map(function(a){return!a.disabled}),stacked:E}}},G=function(a){return function(b){void 0!==b.stacked&&(E=b.stacked),void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};return e.dispatch.on("elementMouseover.tooltip",function(a){a.value=b.x()(a.data),a.series={key:a.data.key,value:b.y()(a.data),color:a.color},j.data(a).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(){j.hidden(!0)}),e.dispatch.on("elementMousemove.tooltip",function(){j.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.dispatch=A,b.multibar=e,b.legend=h,b.controls=i,b.xAxis=f,b.yAxis=g,b.state=x,b.tooltip=j,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return l},set:function(a){l=a}},height:{get:function(){return m},set:function(a){m=a}},showLegend:{get:function(){return q},set:function(a){q=a}},showControls:{get:function(){return o},set:function(a){o=a}},controlLabels:{get:function(){return p},set:function(a){p=a}},showXAxis:{get:function(){return r},set:function(a){r=a}},showYAxis:{get:function(){return s},set:function(a){s=a}},defaultState:{get:function(){return y},set:function(a){y=a}},noData:{get:function(){return z},set:function(a){z=a}},reduceXTicks:{get:function(){return u},set:function(a){u=a}},rotateLabels:{get:function(){return w},set:function(a){w=a}},staggerLabels:{get:function(){return v},set:function(a){v=a}},tooltips:{get:function(){return j.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),j.enabled(!!b)}},tooltipContent:{get:function(){return j.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),j.contentGenerator(b)}},margin:{get:function(){return k},set:function(a){k.top=void 0!==a.top?a.top:k.top,k.right=void 0!==a.right?a.right:k.right,k.bottom=void 0!==a.bottom?a.bottom:k.bottom,k.left=void 0!==a.left?a.left:k.left}},duration:{get:function(){return C},set:function(a){C=a,e.duration(C),f.duration(C),g.duration(C),D.reset(C)}},color:{get:function(){return n},set:function(b){n=a.utils.getColor(b),h.color(n)}},rightAlignYAxis:{get:function(){return t},set:function(a){t=a,g.orient(t?"right":"left")}},barColor:{get:function(){return e.barColor},set:function(a){e.barColor(a),h.color(function(a,b){return d3.rgb("#ccc").darker(1.5*b).toString()})}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.multiBarHorizontal=function(){"use strict";function b(m){return E.reset(),m.each(function(b){var m=k-j.left-j.right,C=l-j.top-j.bottom;n=d3.select(this),a.utils.initSVG(n),w&&(b=d3.layout.stack().offset("zero").values(function(a){return a.values}).y(r)(b)),b.forEach(function(a,b){a.values.forEach(function(c){c.series=b,c.key=a.key})}),w&&b[0].values.map(function(a,c){var d=0,e=0;b.map(function(a){var b=a.values[c];b.size=Math.abs(b.y),b.y<0?(b.y1=e-b.size,e-=b.size):(b.y1=d,d+=b.size)})});var F=d&&e?[]:b.map(function(a){return a.values.map(function(a,b){return{x:q(a,b),y:r(a,b),y0:a.y0,y1:a.y1}})});o.domain(d||d3.merge(F).map(function(a){return a.x})).rangeBands(f||[0,C],A),p.domain(e||d3.extent(d3.merge(F).map(function(a){return w?a.y>0?a.y1+a.y:a.y1:a.y}).concat(t))),p.range(x&&!w?g||[p.domain()[0]<0?z:0,m-(p.domain()[1]>0?z:0)]:g||[0,m]),h=h||o,i=i||d3.scale.linear().domain(p.domain()).range([p(0),p(0)]);{var G=d3.select(this).selectAll("g.nv-wrap.nv-multibarHorizontal").data([b]),H=G.enter().append("g").attr("class","nvd3 nv-wrap nv-multibarHorizontal"),I=(H.append("defs"),H.append("g"));G.select("g")}I.append("g").attr("class","nv-groups"),G.attr("transform","translate("+j.left+","+j.top+")");var J=G.select(".nv-groups").selectAll(".nv-group").data(function(a){return a},function(a,b){return b});J.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6),J.exit().watchTransition(E,"multibarhorizontal: exit groups").style("stroke-opacity",1e-6).style("fill-opacity",1e-6).remove(),J.attr("class",function(a,b){return"nv-group nv-series-"+b}).classed("hover",function(a){return a.hover}).style("fill",function(a,b){return u(a,b)}).style("stroke",function(a,b){return u(a,b)}),J.watchTransition(E,"multibarhorizontal: groups").style("stroke-opacity",1).style("fill-opacity",.75);var K=J.selectAll("g.nv-bar").data(function(a){return a.values});K.exit().remove();var L=K.enter().append("g").attr("transform",function(a,c,d){return"translate("+i(w?a.y0:0)+","+(w?0:d*o.rangeBand()/b.length+o(q(a,c)))+")"});L.append("rect").attr("width",0).attr("height",o.rangeBand()/(w?1:b.length)),K.on("mouseover",function(a,b){d3.select(this).classed("hover",!0),D.elementMouseover({data:a,index:b,color:d3.select(this).style("fill")})}).on("mouseout",function(a,b){d3.select(this).classed("hover",!1),D.elementMouseout({data:a,index:b,color:d3.select(this).style("fill")})}).on("mouseout",function(a,b){D.elementMouseout({data:a,index:b,color:d3.select(this).style("fill")})}).on("mousemove",function(a,b){D.elementMousemove({data:a,index:b,color:d3.select(this).style("fill")})}).on("click",function(a,b){D.elementClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation()}).on("dblclick",function(a,b){D.elementDblClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation()}),s(b[0],0)&&(L.append("polyline"),K.select("polyline").attr("fill","none").attr("points",function(a,c){var d=s(a,c),e=.8*o.rangeBand()/(2*(w?1:b.length));d=d.length?d:[-Math.abs(d),Math.abs(d)],d=d.map(function(a){return p(a)-p(0)});var f=[[d[0],-e],[d[0],e],[d[0],0],[d[1],0],[d[1],-e],[d[1],e]];return f.map(function(a){return a.join(",")}).join(" ")}).attr("transform",function(a,c){var d=o.rangeBand()/(2*(w?1:b.length));return"translate("+(r(a,c)<0?0:p(r(a,c))-p(0))+", "+d+")"})),L.append("text"),x&&!w?(K.select("text").attr("text-anchor",function(a,b){return r(a,b)<0?"end":"start"}).attr("y",o.rangeBand()/(2*b.length)).attr("dy",".32em").text(function(a,b){var c=B(r(a,b)),d=s(a,b);return void 0===d?c:d.length?c+"+"+B(Math.abs(d[1]))+"-"+B(Math.abs(d[0])):c+"±"+B(Math.abs(d))}),K.watchTransition(E,"multibarhorizontal: bars").select("text").attr("x",function(a,b){return r(a,b)<0?-4:p(r(a,b))-p(0)+4})):K.selectAll("text").text(""),y&&!w?(L.append("text").classed("nv-bar-label",!0),K.select("text.nv-bar-label").attr("text-anchor",function(a,b){return r(a,b)<0?"start":"end"}).attr("y",o.rangeBand()/(2*b.length)).attr("dy",".32em").text(function(a,b){return q(a,b)}),K.watchTransition(E,"multibarhorizontal: bars").select("text.nv-bar-label").attr("x",function(a,b){return r(a,b)<0?p(0)-p(r(a,b))+4:-4})):K.selectAll("text.nv-bar-label").text(""),K.attr("class",function(a,b){return r(a,b)<0?"nv-bar negative":"nv-bar positive"}),v&&(c||(c=b.map(function(){return!0})),K.style("fill",function(a,b,d){return d3.rgb(v(a,b)).darker(c.map(function(a,b){return b}).filter(function(a,b){return!c[b]})[d]).toString()}).style("stroke",function(a,b,d){return d3.rgb(v(a,b)).darker(c.map(function(a,b){return b}).filter(function(a,b){return!c[b]})[d]).toString()})),w?K.watchTransition(E,"multibarhorizontal: bars").attr("transform",function(a,b){return"translate("+p(a.y1)+","+o(q(a,b))+")"}).select("rect").attr("width",function(a,b){return Math.abs(p(r(a,b)+a.y0)-p(a.y0))}).attr("height",o.rangeBand()):K.watchTransition(E,"multibarhorizontal: bars").attr("transform",function(a,c){return"translate("+p(r(a,c)<0?r(a,c):0)+","+(a.series*o.rangeBand()/b.length+o(q(a,c)))+")"}).select("rect").attr("height",o.rangeBand()/b.length).attr("width",function(a,b){return Math.max(Math.abs(p(r(a,b))-p(0)),1)}),h=o.copy(),i=p.copy()}),E.renderEnd("multibarHorizontal immediate"),b}var c,d,e,f,g,h,i,j={top:0,right:0,bottom:0,left:0},k=960,l=500,m=Math.floor(1e4*Math.random()),n=null,o=d3.scale.ordinal(),p=d3.scale.linear(),q=function(a){return a.x},r=function(a){return a.y},s=function(a){return a.yErr},t=[0],u=a.utils.defaultColor(),v=null,w=!1,x=!1,y=!1,z=60,A=.1,B=d3.format(",.2f"),C=250,D=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove","renderEnd"),E=a.utils.renderWatch(D,C);return b.dispatch=D,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return k},set:function(a){k=a}},height:{get:function(){return l},set:function(a){l=a}},x:{get:function(){return q},set:function(a){q=a}},y:{get:function(){return r},set:function(a){r=a}},yErr:{get:function(){return s},set:function(a){s=a}},xScale:{get:function(){return o},set:function(a){o=a}},yScale:{get:function(){return p},set:function(a){p=a}},xDomain:{get:function(){return d},set:function(a){d=a}},yDomain:{get:function(){return e},set:function(a){e=a}},xRange:{get:function(){return f},set:function(a){f=a}},yRange:{get:function(){return g},set:function(a){g=a}},forceY:{get:function(){return t},set:function(a){t=a}},stacked:{get:function(){return w},set:function(a){w=a}},showValues:{get:function(){return x},set:function(a){x=a}},disabled:{get:function(){return c},set:function(a){c=a}},id:{get:function(){return m},set:function(a){m=a}},valueFormat:{get:function(){return B},set:function(a){B=a}},valuePadding:{get:function(){return z},set:function(a){z=a}},groupSpacing:{get:function(){return A},set:function(a){A=a}},margin:{get:function(){return j},set:function(a){j.top=void 0!==a.top?a.top:j.top,j.right=void 0!==a.right?a.right:j.right,j.bottom=void 0!==a.bottom?a.bottom:j.bottom,j.left=void 0!==a.left?a.left:j.left}},duration:{get:function(){return C},set:function(a){C=a,E.reset(C)}},color:{get:function(){return u},set:function(b){u=a.utils.getColor(b)}},barColor:{get:function(){return v},set:function(b){v=b?a.utils.getColor(b):null}}}),a.utils.initOptions(b),b},a.models.multiBarHorizontalChart=function(){"use strict";function b(j){return C.reset(),C.models(e),r&&C.models(f),s&&C.models(g),j.each(function(j){var w=d3.select(this);a.utils.initSVG(w);var C=a.utils.availableWidth(l,w,k),D=a.utils.availableHeight(m,w,k);if(b.update=function(){w.transition().duration(z).call(b)},b.container=this,t=e.stacked(),u.setter(B(j),b.update).getter(A(j)).update(),u.disabled=j.map(function(a){return!!a.disabled}),!v){var E;v={};for(E in u)v[E]=u[E]instanceof Array?u[E].slice(0):u[E]}if(!(j&&j.length&&j.filter(function(a){return a.values.length}).length))return a.utils.noData(b,w),b;w.selectAll(".nv-noData").remove(),c=e.xScale(),d=e.yScale();var F=w.selectAll("g.nv-wrap.nv-multiBarHorizontalChart").data([j]),G=F.enter().append("g").attr("class","nvd3 nv-wrap nv-multiBarHorizontalChart").append("g"),H=F.select("g");if(G.append("g").attr("class","nv-x nv-axis"),G.append("g").attr("class","nv-y nv-axis").append("g").attr("class","nv-zeroLine").append("line"),G.append("g").attr("class","nv-barsWrap"),G.append("g").attr("class","nv-legendWrap"),G.append("g").attr("class","nv-controlsWrap"),q&&(h.width(C-y()),H.select(".nv-legendWrap").datum(j).call(h),k.top!=h.height()&&(k.top=h.height(),D=a.utils.availableHeight(m,w,k)),H.select(".nv-legendWrap").attr("transform","translate("+y()+","+-k.top+")")),o){var I=[{key:p.grouped||"Grouped",disabled:e.stacked()},{key:p.stacked||"Stacked",disabled:!e.stacked()}];i.width(y()).color(["#444","#444","#444"]),H.select(".nv-controlsWrap").datum(I).attr("transform","translate(0,"+-k.top+")").call(i)}F.attr("transform","translate("+k.left+","+k.top+")"),e.disabled(j.map(function(a){return a.disabled})).width(C).height(D).color(j.map(function(a,b){return a.color||n(a,b)}).filter(function(a,b){return!j[b].disabled}));var J=H.select(".nv-barsWrap").datum(j.filter(function(a){return!a.disabled}));if(J.transition().call(e),r){f.scale(c)._ticks(a.utils.calcTicksY(D/24,j)).tickSize(-C,0),H.select(".nv-x.nv-axis").call(f);var K=H.select(".nv-x.nv-axis").selectAll("g");K.selectAll("line, text")}s&&(g.scale(d)._ticks(a.utils.calcTicksX(C/100,j)).tickSize(-D,0),H.select(".nv-y.nv-axis").attr("transform","translate(0,"+D+")"),H.select(".nv-y.nv-axis").call(g)),H.select(".nv-zeroLine line").attr("x1",d(0)).attr("x2",d(0)).attr("y1",0).attr("y2",-D),h.dispatch.on("stateChange",function(a){for(var c in a)u[c]=a[c];x.stateChange(u),b.update()}),i.dispatch.on("legendClick",function(a){if(a.disabled){switch(I=I.map(function(a){return a.disabled=!0,a}),a.disabled=!1,a.key){case"Grouped":e.stacked(!1);break;case"Stacked":e.stacked(!0)}u.stacked=e.stacked(),x.stateChange(u),t=e.stacked(),b.update()}}),x.on("changeState",function(a){"undefined"!=typeof a.disabled&&(j.forEach(function(b,c){b.disabled=a.disabled[c]}),u.disabled=a.disabled),"undefined"!=typeof a.stacked&&(e.stacked(a.stacked),u.stacked=a.stacked,t=a.stacked),b.update()})}),C.renderEnd("multibar horizontal chart immediate"),b}var c,d,e=a.models.multiBarHorizontal(),f=a.models.axis(),g=a.models.axis(),h=a.models.legend().height(30),i=a.models.legend().height(30),j=a.models.tooltip(),k={top:30,right:20,bottom:50,left:60},l=null,m=null,n=a.utils.defaultColor(),o=!0,p={},q=!0,r=!0,s=!0,t=!1,u=a.utils.state(),v=null,w=null,x=d3.dispatch("stateChange","changeState","renderEnd"),y=function(){return o?180:0},z=250;u.stacked=!1,e.stacked(t),f.orient("left").tickPadding(5).showMaxMin(!1).tickFormat(function(a){return a}),g.orient("bottom").tickFormat(d3.format(",.1f")),j.duration(0).valueFormatter(function(a,b){return g.tickFormat()(a,b)}).headerFormatter(function(a,b){return f.tickFormat()(a,b)}),i.updateState(!1);var A=function(a){return function(){return{active:a.map(function(a){return!a.disabled}),stacked:t}}},B=function(a){return function(b){void 0!==b.stacked&&(t=b.stacked),void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}},C=a.utils.renderWatch(x,z);return e.dispatch.on("elementMouseover.tooltip",function(a){a.value=b.x()(a.data),a.series={key:a.data.key,value:b.y()(a.data),color:a.color},j.data(a).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(){j.hidden(!0)}),e.dispatch.on("elementMousemove.tooltip",function(){j.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.dispatch=x,b.multibar=e,b.legend=h,b.controls=i,b.xAxis=f,b.yAxis=g,b.state=u,b.tooltip=j,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return l},set:function(a){l=a}},height:{get:function(){return m},set:function(a){m=a}},showLegend:{get:function(){return q},set:function(a){q=a}},showControls:{get:function(){return o},set:function(a){o=a}},controlLabels:{get:function(){return p},set:function(a){p=a}},showXAxis:{get:function(){return r},set:function(a){r=a}},showYAxis:{get:function(){return s},set:function(a){s=a}},defaultState:{get:function(){return v},set:function(a){v=a}},noData:{get:function(){return w},set:function(a){w=a}},tooltips:{get:function(){return j.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),j.enabled(!!b)}},tooltipContent:{get:function(){return j.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),j.contentGenerator(b)}},margin:{get:function(){return k},set:function(a){k.top=void 0!==a.top?a.top:k.top,k.right=void 0!==a.right?a.right:k.right,k.bottom=void 0!==a.bottom?a.bottom:k.bottom,k.left=void 0!==a.left?a.left:k.left}},duration:{get:function(){return z},set:function(a){z=a,C.reset(z),e.duration(z),f.duration(z),g.duration(z)}},color:{get:function(){return n},set:function(b){n=a.utils.getColor(b),h.color(n)}},barColor:{get:function(){return e.barColor},set:function(a){e.barColor(a),h.color(function(a,b){return d3.rgb("#ccc").darker(1.5*b).toString()})}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.multiChart=function(){"use strict";function b(j){return j.each(function(j){function k(a){var b=2===j[a.seriesIndex].yAxis?z:y;a.value=a.point.x,a.series={value:a.point.y,color:a.point.color},B.duration(100).valueFormatter(function(a,c){return b.tickFormat()(a,c)}).data(a).position(a.pos).hidden(!1)}function l(a){var b=2===j[a.seriesIndex].yAxis?z:y;a.point.x=v.x()(a.point),a.point.y=v.y()(a.point),B.duration(100).valueFormatter(function(a,c){return b.tickFormat()(a,c)}).data(a).position(a.pos).hidden(!1)}function n(a){var b=2===j[a.data.series].yAxis?z:y;a.value=t.x()(a.data),a.series={value:t.y()(a.data),color:a.color},B.duration(0).valueFormatter(function(a,c){return b.tickFormat()(a,c)}).data(a).hidden(!1)}var C=d3.select(this);a.utils.initSVG(C),b.update=function(){C.transition().call(b)},b.container=this;var D=a.utils.availableWidth(g,C,e),E=a.utils.availableHeight(h,C,e),F=j.filter(function(a){return"line"==a.type&&1==a.yAxis}),G=j.filter(function(a){return"line"==a.type&&2==a.yAxis}),H=j.filter(function(a){return"bar"==a.type&&1==a.yAxis}),I=j.filter(function(a){return"bar"==a.type&&2==a.yAxis}),J=j.filter(function(a){return"area"==a.type&&1==a.yAxis}),K=j.filter(function(a){return"area"==a.type&&2==a.yAxis});if(!(j&&j.length&&j.filter(function(a){return a.values.length}).length))return a.utils.noData(b,C),b;C.selectAll(".nv-noData").remove();var L=j.filter(function(a){return!a.disabled&&1==a.yAxis}).map(function(a){return a.values.map(function(a){return{x:a.x,y:a.y}})}),M=j.filter(function(a){return!a.disabled&&2==a.yAxis}).map(function(a){return a.values.map(function(a){return{x:a.x,y:a.y}})});o.domain(d3.extent(d3.merge(L.concat(M)),function(a){return a.x})).range([0,D]);var N=C.selectAll("g.wrap.multiChart").data([j]),O=N.enter().append("g").attr("class","wrap nvd3 multiChart").append("g");O.append("g").attr("class","nv-x nv-axis"),O.append("g").attr("class","nv-y1 nv-axis"),O.append("g").attr("class","nv-y2 nv-axis"),O.append("g").attr("class","lines1Wrap"),O.append("g").attr("class","lines2Wrap"),O.append("g").attr("class","bars1Wrap"),O.append("g").attr("class","bars2Wrap"),O.append("g").attr("class","stack1Wrap"),O.append("g").attr("class","stack2Wrap"),O.append("g").attr("class","legendWrap");var P=N.select("g"),Q=j.map(function(a,b){return j[b].color||f(a,b)});if(i){var R=A.align()?D/2:D,S=A.align()?R:0;A.width(R),A.color(Q),P.select(".legendWrap").datum(j.map(function(a){return a.originalKey=void 0===a.originalKey?a.key:a.originalKey,a.key=a.originalKey+(1==a.yAxis?"":" (right axis)"),a})).call(A),e.top!=A.height()&&(e.top=A.height(),E=a.utils.availableHeight(h,C,e)),P.select(".legendWrap").attr("transform","translate("+S+","+-e.top+")")}r.width(D).height(E).interpolate(m).color(Q.filter(function(a,b){return!j[b].disabled&&1==j[b].yAxis&&"line"==j[b].type})),s.width(D).height(E).interpolate(m).color(Q.filter(function(a,b){return!j[b].disabled&&2==j[b].yAxis&&"line"==j[b].type})),t.width(D).height(E).color(Q.filter(function(a,b){return!j[b].disabled&&1==j[b].yAxis&&"bar"==j[b].type})),u.width(D).height(E).color(Q.filter(function(a,b){return!j[b].disabled&&2==j[b].yAxis&&"bar"==j[b].type})),v.width(D).height(E).color(Q.filter(function(a,b){return!j[b].disabled&&1==j[b].yAxis&&"area"==j[b].type})),w.width(D).height(E).color(Q.filter(function(a,b){return!j[b].disabled&&2==j[b].yAxis&&"area"==j[b].type})),P.attr("transform","translate("+e.left+","+e.top+")");var T=P.select(".lines1Wrap").datum(F.filter(function(a){return!a.disabled})),U=P.select(".bars1Wrap").datum(H.filter(function(a){return!a.disabled})),V=P.select(".stack1Wrap").datum(J.filter(function(a){return!a.disabled})),W=P.select(".lines2Wrap").datum(G.filter(function(a){return!a.disabled})),X=P.select(".bars2Wrap").datum(I.filter(function(a){return!a.disabled})),Y=P.select(".stack2Wrap").datum(K.filter(function(a){return!a.disabled})),Z=J.length?J.map(function(a){return a.values}).reduce(function(a,b){return a.map(function(a,c){return{x:a.x,y:a.y+b[c].y}})}).concat([{x:0,y:0}]):[],$=K.length?K.map(function(a){return a.values}).reduce(function(a,b){return a.map(function(a,c){return{x:a.x,y:a.y+b[c].y}})}).concat([{x:0,y:0}]):[];p.domain(c||d3.extent(d3.merge(L).concat(Z),function(a){return a.y})).range([0,E]),q.domain(d||d3.extent(d3.merge(M).concat($),function(a){return a.y})).range([0,E]),r.yDomain(p.domain()),t.yDomain(p.domain()),v.yDomain(p.domain()),s.yDomain(q.domain()),u.yDomain(q.domain()),w.yDomain(q.domain()),J.length&&d3.transition(V).call(v),K.length&&d3.transition(Y).call(w),H.length&&d3.transition(U).call(t),I.length&&d3.transition(X).call(u),F.length&&d3.transition(T).call(r),G.length&&d3.transition(W).call(s),x._ticks(a.utils.calcTicksX(D/100,j)).tickSize(-E,0),P.select(".nv-x.nv-axis").attr("transform","translate(0,"+E+")"),d3.transition(P.select(".nv-x.nv-axis")).call(x),y._ticks(a.utils.calcTicksY(E/36,j)).tickSize(-D,0),d3.transition(P.select(".nv-y1.nv-axis")).call(y),z._ticks(a.utils.calcTicksY(E/36,j)).tickSize(-D,0),d3.transition(P.select(".nv-y2.nv-axis")).call(z),P.select(".nv-y1.nv-axis").classed("nv-disabled",L.length?!1:!0).attr("transform","translate("+o.range()[0]+",0)"),P.select(".nv-y2.nv-axis").classed("nv-disabled",M.length?!1:!0).attr("transform","translate("+o.range()[1]+",0)"),A.dispatch.on("stateChange",function(){b.update()}),r.dispatch.on("elementMouseover.tooltip",k),s.dispatch.on("elementMouseover.tooltip",k),r.dispatch.on("elementMouseout.tooltip",function(){B.hidden(!0)}),s.dispatch.on("elementMouseout.tooltip",function(){B.hidden(!0)}),v.dispatch.on("elementMouseover.tooltip",l),w.dispatch.on("elementMouseover.tooltip",l),v.dispatch.on("elementMouseout.tooltip",function(){B.hidden(!0)}),w.dispatch.on("elementMouseout.tooltip",function(){B.hidden(!0)}),t.dispatch.on("elementMouseover.tooltip",n),u.dispatch.on("elementMouseover.tooltip",n),t.dispatch.on("elementMouseout.tooltip",function(){B.hidden(!0)}),u.dispatch.on("elementMouseout.tooltip",function(){B.hidden(!0)}),t.dispatch.on("elementMousemove.tooltip",function(){B.position({top:d3.event.pageY,left:d3.event.pageX})()}),u.dispatch.on("elementMousemove.tooltip",function(){B.position({top:d3.event.pageY,left:d3.event.pageX})()})}),b}var c,d,e={top:30,right:20,bottom:50,left:60},f=a.utils.defaultColor(),g=null,h=null,i=!0,j=null,k=function(a){return a.x},l=function(a){return a.y},m="monotone",n=!0,o=d3.scale.linear(),p=d3.scale.linear(),q=d3.scale.linear(),r=a.models.line().yScale(p),s=a.models.line().yScale(q),t=a.models.multiBar().stacked(!1).yScale(p),u=a.models.multiBar().stacked(!1).yScale(q),v=a.models.stackedArea().yScale(p),w=a.models.stackedArea().yScale(q),x=a.models.axis().scale(o).orient("bottom").tickPadding(5),y=a.models.axis().scale(p).orient("left"),z=a.models.axis().scale(q).orient("right"),A=a.models.legend().height(30),B=a.models.tooltip(),C=d3.dispatch();return b.dispatch=C,b.lines1=r,b.lines2=s,b.bars1=t,b.bars2=u,b.stack1=v,b.stack2=w,b.xAxis=x,b.yAxis1=y,b.yAxis2=z,b.tooltip=B,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return g},set:function(a){g=a}},height:{get:function(){return h},set:function(a){h=a}},showLegend:{get:function(){return i},set:function(a){i=a}},yDomain1:{get:function(){return c},set:function(a){c=a}},yDomain2:{get:function(){return d},set:function(a){d=a}},noData:{get:function(){return j},set:function(a){j=a}},interpolate:{get:function(){return m},set:function(a){m=a}},tooltips:{get:function(){return B.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),B.enabled(!!b)}},tooltipContent:{get:function(){return B.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),B.contentGenerator(b)}},margin:{get:function(){return e},set:function(a){e.top=void 0!==a.top?a.top:e.top,e.right=void 0!==a.right?a.right:e.right,e.bottom=void 0!==a.bottom?a.bottom:e.bottom,e.left=void 0!==a.left?a.left:e.left}},color:{get:function(){return f},set:function(b){f=a.utils.getColor(b)}},x:{get:function(){return k},set:function(a){k=a,r.x(a),s.x(a),t.x(a),u.x(a),v.x(a),w.x(a)}},y:{get:function(){return l},set:function(a){l=a,r.y(a),s.y(a),v.y(a),w.y(a),t.y(a),u.y(a)}},useVoronoi:{get:function(){return n},set:function(a){n=a,r.useVoronoi(a),s.useVoronoi(a),v.useVoronoi(a),w.useVoronoi(a)}}}),a.utils.initOptions(b),b},a.models.ohlcBar=function(){"use strict";function b(y){return y.each(function(b){k=d3.select(this);var y=a.utils.availableWidth(h,k,g),A=a.utils.availableHeight(i,k,g);a.utils.initSVG(k);var B=y/b[0].values.length*.9;l.domain(c||d3.extent(b[0].values.map(n).concat(t))),l.range(v?e||[.5*y/b[0].values.length,y*(b[0].values.length-.5)/b[0].values.length]:e||[5+B/2,y-B/2-5]),m.domain(d||[d3.min(b[0].values.map(s).concat(u)),d3.max(b[0].values.map(r).concat(u))]).range(f||[A,0]),l.domain()[0]===l.domain()[1]&&l.domain(l.domain()[0]?[l.domain()[0]-.01*l.domain()[0],l.domain()[1]+.01*l.domain()[1]]:[-1,1]),m.domain()[0]===m.domain()[1]&&m.domain(m.domain()[0]?[m.domain()[0]+.01*m.domain()[0],m.domain()[1]-.01*m.domain()[1]]:[-1,1]);var C=d3.select(this).selectAll("g.nv-wrap.nv-ohlcBar").data([b[0].values]),D=C.enter().append("g").attr("class","nvd3 nv-wrap nv-ohlcBar"),E=D.append("defs"),F=D.append("g"),G=C.select("g");F.append("g").attr("class","nv-ticks"),C.attr("transform","translate("+g.left+","+g.top+")"),k.on("click",function(a,b){z.chartClick({data:a,index:b,pos:d3.event,id:j})}),E.append("clipPath").attr("id","nv-chart-clip-path-"+j).append("rect"),C.select("#nv-chart-clip-path-"+j+" rect").attr("width",y).attr("height",A),G.attr("clip-path",w?"url(#nv-chart-clip-path-"+j+")":"");var H=C.select(".nv-ticks").selectAll(".nv-tick").data(function(a){return a});H.exit().remove(),H.enter().append("path").attr("class",function(a,b,c){return(p(a,b)>q(a,b)?"nv-tick negative":"nv-tick positive")+" nv-tick-"+c+"-"+b}).attr("d",function(a,b){return"m0,0l0,"+(m(p(a,b))-m(r(a,b)))+"l"+-B/2+",0l"+B/2+",0l0,"+(m(s(a,b))-m(p(a,b)))+"l0,"+(m(q(a,b))-m(s(a,b)))+"l"+B/2+",0l"+-B/2+",0z"}).attr("transform",function(a,b){return"translate("+l(n(a,b))+","+m(r(a,b))+")"}).attr("fill",function(){return x[0]}).attr("stroke",function(){return x[0]}).attr("x",0).attr("y",function(a,b){return m(Math.max(0,o(a,b)))}).attr("height",function(a,b){return Math.abs(m(o(a,b))-m(0))}),H.attr("class",function(a,b,c){return(p(a,b)>q(a,b)?"nv-tick negative":"nv-tick positive")+" nv-tick-"+c+"-"+b}),d3.transition(H).attr("transform",function(a,b){return"translate("+l(n(a,b))+","+m(r(a,b))+")"}).attr("d",function(a,c){var d=y/b[0].values.length*.9;return"m0,0l0,"+(m(p(a,c))-m(r(a,c)))+"l"+-d/2+",0l"+d/2+",0l0,"+(m(s(a,c))-m(p(a,c)))+"l0,"+(m(q(a,c))-m(s(a,c)))+"l"+d/2+",0l"+-d/2+",0z"})}),b}var c,d,e,f,g={top:0,right:0,bottom:0,left:0},h=null,i=null,j=Math.floor(1e4*Math.random()),k=null,l=d3.scale.linear(),m=d3.scale.linear(),n=function(a){return a.x},o=function(a){return a.y},p=function(a){return a.open},q=function(a){return a.close},r=function(a){return a.high},s=function(a){return a.low},t=[],u=[],v=!1,w=!0,x=a.utils.defaultColor(),y=!1,z=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState","renderEnd","chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove");return b.highlightPoint=function(a,c){b.clearHighlights(),k.select(".nv-ohlcBar .nv-tick-0-"+a).classed("hover",c)},b.clearHighlights=function(){k.select(".nv-ohlcBar .nv-tick.hover").classed("hover",!1)},b.dispatch=z,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return h},set:function(a){h=a}},height:{get:function(){return i},set:function(a){i=a}},xScale:{get:function(){return l},set:function(a){l=a}},yScale:{get:function(){return m},set:function(a){m=a}},xDomain:{get:function(){return c},set:function(a){c=a}},yDomain:{get:function(){return d},set:function(a){d=a}},xRange:{get:function(){return e},set:function(a){e=a}},yRange:{get:function(){return f},set:function(a){f=a}},forceX:{get:function(){return t},set:function(a){t=a}},forceY:{get:function(){return u},set:function(a){u=a}},padData:{get:function(){return v},set:function(a){v=a}},clipEdge:{get:function(){return w},set:function(a){w=a}},id:{get:function(){return j},set:function(a){j=a}},interactive:{get:function(){return y},set:function(a){y=a}},x:{get:function(){return n},set:function(a){n=a}},y:{get:function(){return o},set:function(a){o=a}},open:{get:function(){return p()},set:function(a){p=a}},close:{get:function(){return q()},set:function(a){q=a}},high:{get:function(){return r},set:function(a){r=a}},low:{get:function(){return s},set:function(a){s=a}},margin:{get:function(){return g},set:function(a){g.top=void 0!=a.top?a.top:g.top,g.right=void 0!=a.right?a.right:g.right,g.bottom=void 0!=a.bottom?a.bottom:g.bottom,g.left=void 0!=a.left?a.left:g.left -}},color:{get:function(){return x},set:function(b){x=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.parallelCoordinates=function(){"use strict";function b(p){return p.each(function(b){function p(a){return F(h.map(function(b){if(isNaN(a[b])||isNaN(parseFloat(a[b]))){var c=g[b].domain(),d=g[b].range(),e=c[0]-(c[1]-c[0])/9;if(J.indexOf(b)<0){var h=d3.scale.linear().domain([e,c[1]]).range([x-12,d[1]]);g[b].brush.y(h),J.push(b)}return[f(b),g[b](e)]}return J.length>0?(D.style("display","inline"),E.style("display","inline")):(D.style("display","none"),E.style("display","none")),[f(b),g[b](a[b])]}))}function q(){var a=h.filter(function(a){return!g[a].brush.empty()}),b=a.map(function(a){return g[a].brush.extent()});k=[],a.forEach(function(a,c){k[c]={dimension:a,extent:b[c]}}),l=[],M.style("display",function(c){var d=a.every(function(a,d){return isNaN(c[a])&&b[d][0]==g[a].brush.y().domain()[0]?!0:b[d][0]<=c[a]&&c[a]<=b[d][1]});return d&&l.push(c),d?null:"none"}),o.brush({filters:k,active:l})}function r(a){m[a]=this.parentNode.__origin__=f(a),L.attr("visibility","hidden")}function s(a){m[a]=Math.min(w,Math.max(0,this.parentNode.__origin__+=d3.event.x)),M.attr("d",p),h.sort(function(a,b){return u(a)-u(b)}),f.domain(h),N.attr("transform",function(a){return"translate("+u(a)+")"})}function t(a){delete this.parentNode.__origin__,delete m[a],d3.select(this.parentNode).attr("transform","translate("+f(a)+")"),M.attr("d",p),L.attr("d",p).attr("visibility",null)}function u(a){var b=m[a];return null==b?f(a):b}var v=d3.select(this),w=a.utils.availableWidth(d,v,c),x=a.utils.availableHeight(e,v,c);a.utils.initSVG(v),l=b,f.rangePoints([0,w],1).domain(h);var y={};h.forEach(function(a){var c=d3.extent(b,function(b){return+b[a]});return y[a]=!1,void 0===c[0]&&(y[a]=!0,c[0]=0,c[1]=0),c[0]===c[1]&&(c[0]=c[0]-1,c[1]=c[1]+1),g[a]=d3.scale.linear().domain(c).range([.9*(x-12),0]),g[a].brush=d3.svg.brush().y(g[a]).on("brush",q),"name"!=a});var z=v.selectAll("g.nv-wrap.nv-parallelCoordinates").data([b]),A=z.enter().append("g").attr("class","nvd3 nv-wrap nv-parallelCoordinates"),B=A.append("g"),C=z.select("g");B.append("g").attr("class","nv-parallelCoordinates background"),B.append("g").attr("class","nv-parallelCoordinates foreground"),B.append("g").attr("class","nv-parallelCoordinates missingValuesline"),z.attr("transform","translate("+c.left+","+c.top+")");var D,E,F=d3.svg.line().interpolate("cardinal").tension(n),G=d3.svg.axis().orient("left"),H=d3.behavior.drag().on("dragstart",r).on("drag",s).on("dragend",t),I=f.range()[1]-f.range()[0],J=[],K=[0+I/2,x-12,w-I/2,x-12];D=z.select(".missingValuesline").selectAll("line").data([K]),D.enter().append("line"),D.exit().remove(),D.attr("x1",function(a){return a[0]}).attr("y1",function(a){return a[1]}).attr("x2",function(a){return a[2]}).attr("y2",function(a){return a[3]}),E=z.select(".missingValuesline").selectAll("text").data(["undefined values"]),E.append("text").data(["undefined values"]),E.enter().append("text"),E.exit().remove(),E.attr("y",x).attr("x",w-92-I/2).text(function(a){return a});var L=z.select(".background").selectAll("path").data(b);L.enter().append("path"),L.exit().remove(),L.attr("d",p);var M=z.select(".foreground").selectAll("path").data(b);M.enter().append("path"),M.exit().remove(),M.attr("d",p).attr("stroke",j),M.on("mouseover",function(a,b){d3.select(this).classed("hover",!0),o.elementMouseover({label:a.name,data:a.data,index:b,pos:[d3.mouse(this.parentNode)[0],d3.mouse(this.parentNode)[1]]})}),M.on("mouseout",function(a,b){d3.select(this).classed("hover",!1),o.elementMouseout({label:a.name,data:a.data,index:b})});var N=C.selectAll(".dimension").data(h),O=N.enter().append("g").attr("class","nv-parallelCoordinates dimension");O.append("g").attr("class","nv-parallelCoordinates nv-axis"),O.append("g").attr("class","nv-parallelCoordinates-brush"),O.append("text").attr("class","nv-parallelCoordinates nv-label"),N.attr("transform",function(a){return"translate("+f(a)+",0)"}),N.exit().remove(),N.select(".nv-label").style("cursor","move").attr("dy","-1em").attr("text-anchor","middle").text(String).on("mouseover",function(a){o.elementMouseover({dim:a,pos:[d3.mouse(this.parentNode.parentNode)[0],d3.mouse(this.parentNode.parentNode)[1]]})}).on("mouseout",function(a){o.elementMouseout({dim:a})}).call(H),N.select(".nv-axis").each(function(a,b){d3.select(this).call(G.scale(g[a]).tickFormat(d3.format(i[b])))}),N.select(".nv-parallelCoordinates-brush").each(function(a){d3.select(this).call(g[a].brush)}).selectAll("rect").attr("x",-8).attr("width",16)}),b}var c={top:30,right:0,bottom:10,left:0},d=null,e=null,f=d3.scale.ordinal(),g={},h=[],i=[],j=a.utils.defaultColor(),k=[],l=[],m=[],n=1,o=d3.dispatch("brush","elementMouseover","elementMouseout");return b.dispatch=o,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return d},set:function(a){d=a}},height:{get:function(){return e},set:function(a){e=a}},dimensionNames:{get:function(){return h},set:function(a){h=a}},dimensionFormats:{get:function(){return i},set:function(a){i=a}},lineTension:{get:function(){return n},set:function(a){n=a}},dimensions:{get:function(){return h},set:function(b){a.deprecated("dimensions","use dimensionNames instead"),h=b}},margin:{get:function(){return c},set:function(a){c.top=void 0!==a.top?a.top:c.top,c.right=void 0!==a.right?a.right:c.right,c.bottom=void 0!==a.bottom?a.bottom:c.bottom,c.left=void 0!==a.left?a.left:c.left}},color:{get:function(){return j},set:function(b){j=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.pie=function(){"use strict";function b(E){return D.reset(),E.each(function(b){function E(a,b){a.endAngle=isNaN(a.endAngle)?0:a.endAngle,a.startAngle=isNaN(a.startAngle)?0:a.startAngle,p||(a.innerRadius=0);var c=d3.interpolate(this._current,a);return this._current=c(0),function(a){return B[b](c(a))}}var F=d-c.left-c.right,G=e-c.top-c.bottom,H=Math.min(F,G)/2,I=[],J=[];if(i=d3.select(this),0===z.length)for(var K=H-H/5,L=y*H,M=0;Mc)return"";if("function"==typeof n)d=n(a,b,{key:f(a.data),value:g(a.data),percent:k(c)});else switch(n){case"key":d=f(a.data);break;case"value":d=k(g(a.data));break;case"percent":d=d3.format("%")(c)}return d})}}),D.renderEnd("pie immediate"),b}var c={top:0,right:0,bottom:0,left:0},d=500,e=500,f=function(a){return a.x},g=function(a){return a.y},h=Math.floor(1e4*Math.random()),i=null,j=a.utils.defaultColor(),k=d3.format(",.2f"),l=!0,m=!1,n="key",o=.02,p=!1,q=!1,r=!0,s=0,t=!1,u=!1,v=!1,w=!1,x=0,y=.5,z=[],A=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove","renderEnd"),B=[],C=[],D=a.utils.renderWatch(A);return b.dispatch=A,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{arcsRadius:{get:function(){return z},set:function(a){z=a}},width:{get:function(){return d},set:function(a){d=a}},height:{get:function(){return e},set:function(a){e=a}},showLabels:{get:function(){return l},set:function(a){l=a}},title:{get:function(){return q},set:function(a){q=a}},titleOffset:{get:function(){return s},set:function(a){s=a}},labelThreshold:{get:function(){return o},set:function(a){o=a}},valueFormat:{get:function(){return k},set:function(a){k=a}},x:{get:function(){return f},set:function(a){f=a}},id:{get:function(){return h},set:function(a){h=a}},endAngle:{get:function(){return w},set:function(a){w=a}},startAngle:{get:function(){return u},set:function(a){u=a}},padAngle:{get:function(){return v},set:function(a){v=a}},cornerRadius:{get:function(){return x},set:function(a){x=a}},donutRatio:{get:function(){return y},set:function(a){y=a}},labelsOutside:{get:function(){return m},set:function(a){m=a}},labelSunbeamLayout:{get:function(){return t},set:function(a){t=a}},donut:{get:function(){return p},set:function(a){p=a}},growOnHover:{get:function(){return r},set:function(a){r=a}},pieLabelsOutside:{get:function(){return m},set:function(b){m=b,a.deprecated("pieLabelsOutside","use labelsOutside instead")}},donutLabelsOutside:{get:function(){return m},set:function(b){m=b,a.deprecated("donutLabelsOutside","use labelsOutside instead")}},labelFormat:{get:function(){return k},set:function(b){k=b,a.deprecated("labelFormat","use valueFormat instead")}},margin:{get:function(){return c},set:function(a){c.top="undefined"!=typeof a.top?a.top:c.top,c.right="undefined"!=typeof a.right?a.right:c.right,c.bottom="undefined"!=typeof a.bottom?a.bottom:c.bottom,c.left="undefined"!=typeof a.left?a.left:c.left}},y:{get:function(){return g},set:function(a){g=d3.functor(a)}},color:{get:function(){return j},set:function(b){j=a.utils.getColor(b)}},labelType:{get:function(){return n},set:function(a){n=a||"key"}}}),a.utils.initOptions(b),b},a.models.pieChart=function(){"use strict";function b(e){return q.reset(),q.models(c),e.each(function(e){var k=d3.select(this);a.utils.initSVG(k);var n=a.utils.availableWidth(g,k,f),o=a.utils.availableHeight(h,k,f);if(b.update=function(){k.transition().call(b)},b.container=this,l.setter(s(e),b.update).getter(r(e)).update(),l.disabled=e.map(function(a){return!!a.disabled}),!m){var q;m={};for(q in l)m[q]=l[q]instanceof Array?l[q].slice(0):l[q]}if(!e||!e.length)return a.utils.noData(b,k),b;k.selectAll(".nv-noData").remove();var t=k.selectAll("g.nv-wrap.nv-pieChart").data([e]),u=t.enter().append("g").attr("class","nvd3 nv-wrap nv-pieChart").append("g"),v=t.select("g");if(u.append("g").attr("class","nv-pieWrap"),u.append("g").attr("class","nv-legendWrap"),i)if("top"===j)d.width(n).key(c.x()),t.select(".nv-legendWrap").datum(e).call(d),f.top!=d.height()&&(f.top=d.height(),o=a.utils.availableHeight(h,k,f)),t.select(".nv-legendWrap").attr("transform","translate(0,"+-f.top+")");else if("right"===j){var w=a.models.legend().width();w>n/2&&(w=n/2),d.height(o).key(c.x()),d.width(w),n-=d.width(),t.select(".nv-legendWrap").datum(e).call(d).attr("transform","translate("+n+",0)")}t.attr("transform","translate("+f.left+","+f.top+")"),c.width(n).height(o);var x=v.select(".nv-pieWrap").datum([e]);d3.transition(x).call(c),d.dispatch.on("stateChange",function(a){for(var c in a)l[c]=a[c];p.stateChange(l),b.update()}),p.on("changeState",function(a){"undefined"!=typeof a.disabled&&(e.forEach(function(b,c){b.disabled=a.disabled[c]}),l.disabled=a.disabled),b.update()})}),q.renderEnd("pieChart immediate"),b}var c=a.models.pie(),d=a.models.legend(),e=a.models.tooltip(),f={top:30,right:20,bottom:20,left:20},g=null,h=null,i=!0,j="top",k=a.utils.defaultColor(),l=a.utils.state(),m=null,n=null,o=250,p=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState","renderEnd");e.headerEnabled(!1).duration(0).valueFormatter(function(a,b){return c.valueFormat()(a,b)});var q=a.utils.renderWatch(p),r=function(a){return function(){return{active:a.map(function(a){return!a.disabled})}}},s=function(a){return function(b){void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};return c.dispatch.on("elementMouseover.tooltip",function(a){a.series={key:b.x()(a.data),value:b.y()(a.data),color:a.color},e.data(a).hidden(!1)}),c.dispatch.on("elementMouseout.tooltip",function(){e.hidden(!0)}),c.dispatch.on("elementMousemove.tooltip",function(){e.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.legend=d,b.dispatch=p,b.pie=c,b.tooltip=e,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{noData:{get:function(){return n},set:function(a){n=a}},showLegend:{get:function(){return i},set:function(a){i=a}},legendPosition:{get:function(){return j},set:function(a){j=a}},defaultState:{get:function(){return m},set:function(a){m=a}},tooltips:{get:function(){return e.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),e.enabled(!!b)}},tooltipContent:{get:function(){return e.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),e.contentGenerator(b)}},color:{get:function(){return k},set:function(a){k=a,d.color(k),c.color(k)}},duration:{get:function(){return o},set:function(a){o=a,q.reset(o)}},margin:{get:function(){return f},set:function(a){f.top=void 0!==a.top?a.top:f.top,f.right=void 0!==a.right?a.right:f.right,f.bottom=void 0!==a.bottom?a.bottom:f.bottom,f.left=void 0!==a.left?a.left:f.left}}}),a.utils.inheritOptions(b,c),a.utils.initOptions(b),b},a.models.scatter=function(){"use strict";function b(N){return P.reset(),N.each(function(b){function N(){if(O=!1,!w)return!1;if(M===!0){var a=d3.merge(b.map(function(a,b){return a.values.map(function(a,c){var d=p(a,c),e=q(a,c);return[m(d)+1e-4*Math.random(),n(e)+1e-4*Math.random(),b,c,a]}).filter(function(a,b){return x(a[4],b)})}));if(0==a.length)return!1;a.length<3&&(a.push([m.range()[0]-20,n.range()[0]-20,null,null]),a.push([m.range()[1]+20,n.range()[1]+20,null,null]),a.push([m.range()[0]-20,n.range()[0]+20,null,null]),a.push([m.range()[1]+20,n.range()[1]-20,null,null]));var c=d3.geom.polygon([[-10,-10],[-10,i+10],[h+10,i+10],[h+10,-10]]),d=d3.geom.voronoi(a).map(function(b,d){return{data:c.clip(b),series:a[d][2],point:a[d][3]}});U.select(".nv-point-paths").selectAll("path").remove();var e=U.select(".nv-point-paths").selectAll("path").data(d),f=e.enter().append("svg:path").attr("d",function(a){return a&&a.data&&0!==a.data.length?"M"+a.data.join(",")+"Z":"M 0 0"}).attr("id",function(a,b){return"nv-path-"+b}).attr("clip-path",function(a,b){return"url(#nv-clip-"+b+")"});C&&f.style("fill",d3.rgb(230,230,230)).style("fill-opacity",.4).style("stroke-opacity",1).style("stroke",d3.rgb(200,200,200)),B&&(U.select(".nv-point-clips").selectAll("clipPath").remove(),U.select(".nv-point-clips").selectAll("clipPath").data(a).enter().append("svg:clipPath").attr("id",function(a,b){return"nv-clip-"+b}).append("svg:circle").attr("cx",function(a){return a[0]}).attr("cy",function(a){return a[1]}).attr("r",D));var k=function(a,c){if(O)return 0;var d=b[a.series];if(void 0!==d){var e=d.values[a.point];e.color=j(d,a.series),e.x=p(e),e.y=q(e);var f=l.node().getBoundingClientRect(),h=window.pageYOffset||document.documentElement.scrollTop,i=window.pageXOffset||document.documentElement.scrollLeft,k={left:m(p(e,a.point))+f.left+i+g.left+10,top:n(q(e,a.point))+f.top+h+g.top+10};c({point:e,series:d,pos:k,seriesIndex:a.series,pointIndex:a.point})}};e.on("click",function(a){k(a,L.elementClick)}).on("dblclick",function(a){k(a,L.elementDblClick)}).on("mouseover",function(a){k(a,L.elementMouseover)}).on("mouseout",function(a){k(a,L.elementMouseout)})}else U.select(".nv-groups").selectAll(".nv-group").selectAll(".nv-point").on("click",function(a,c){if(O||!b[a.series])return 0;var d=b[a.series],e=d.values[c];L.elementClick({point:e,series:d,pos:[m(p(e,c))+g.left,n(q(e,c))+g.top],seriesIndex:a.series,pointIndex:c})}).on("dblclick",function(a,c){if(O||!b[a.series])return 0;var d=b[a.series],e=d.values[c];L.elementDblClick({point:e,series:d,pos:[m(p(e,c))+g.left,n(q(e,c))+g.top],seriesIndex:a.series,pointIndex:c})}).on("mouseover",function(a,c){if(O||!b[a.series])return 0;var d=b[a.series],e=d.values[c];L.elementMouseover({point:e,series:d,pos:[m(p(e,c))+g.left,n(q(e,c))+g.top],seriesIndex:a.series,pointIndex:c,color:j(a,c)})}).on("mouseout",function(a,c){if(O||!b[a.series])return 0;var d=b[a.series],e=d.values[c];L.elementMouseout({point:e,series:d,seriesIndex:a.series,pointIndex:c,color:j(a,c)})})}l=d3.select(this);var R=a.utils.availableWidth(h,l,g),S=a.utils.availableHeight(i,l,g);a.utils.initSVG(l),b.forEach(function(a,b){a.values.forEach(function(a){a.series=b})});var T=E&&F&&I?[]:d3.merge(b.map(function(a){return a.values.map(function(a,b){return{x:p(a,b),y:q(a,b),size:r(a,b)}})}));m.domain(E||d3.extent(T.map(function(a){return a.x}).concat(t))),m.range(y&&b[0]?G||[(R*z+R)/(2*b[0].values.length),R-R*(1+z)/(2*b[0].values.length)]:G||[0,R]),n.domain(F||d3.extent(T.map(function(a){return a.y}).concat(u))).range(H||[S,0]),o.domain(I||d3.extent(T.map(function(a){return a.size}).concat(v))).range(J||Q),K=m.domain()[0]===m.domain()[1]||n.domain()[0]===n.domain()[1],m.domain()[0]===m.domain()[1]&&m.domain(m.domain()[0]?[m.domain()[0]-.01*m.domain()[0],m.domain()[1]+.01*m.domain()[1]]:[-1,1]),n.domain()[0]===n.domain()[1]&&n.domain(n.domain()[0]?[n.domain()[0]-.01*n.domain()[0],n.domain()[1]+.01*n.domain()[1]]:[-1,1]),isNaN(m.domain()[0])&&m.domain([-1,1]),isNaN(n.domain()[0])&&n.domain([-1,1]),c=c||m,d=d||n,e=e||o;var U=l.selectAll("g.nv-wrap.nv-scatter").data([b]),V=U.enter().append("g").attr("class","nvd3 nv-wrap nv-scatter nv-chart-"+k),W=V.append("defs"),X=V.append("g"),Y=U.select("g");U.classed("nv-single-point",K),X.append("g").attr("class","nv-groups"),X.append("g").attr("class","nv-point-paths"),V.append("g").attr("class","nv-point-clips"),U.attr("transform","translate("+g.left+","+g.top+")"),W.append("clipPath").attr("id","nv-edge-clip-"+k).append("rect"),U.select("#nv-edge-clip-"+k+" rect").attr("width",R).attr("height",S>0?S:0),Y.attr("clip-path",A?"url(#nv-edge-clip-"+k+")":""),O=!0;var Z=U.select(".nv-groups").selectAll(".nv-group").data(function(a){return a},function(a){return a.key});Z.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6),Z.exit().remove(),Z.attr("class",function(a,b){return"nv-group nv-series-"+b}).classed("hover",function(a){return a.hover}),Z.watchTransition(P,"scatter: groups").style("fill",function(a,b){return j(a,b)}).style("stroke",function(a,b){return j(a,b)}).style("stroke-opacity",1).style("fill-opacity",.5);var $=Z.selectAll("path.nv-point").data(function(a){return a.values.map(function(a,b){return[a,b]}).filter(function(a,b){return x(a[0],b)})});$.enter().append("path").style("fill",function(a){return a.color}).style("stroke",function(a){return a.color}).attr("transform",function(a){return"translate("+c(p(a[0],a[1]))+","+d(q(a[0],a[1]))+")"}).attr("d",a.utils.symbol().type(function(a){return s(a[0])}).size(function(a){return o(r(a[0],a[1]))})),$.exit().remove(),Z.exit().selectAll("path.nv-point").watchTransition(P,"scatter exit").attr("transform",function(a){return"translate("+m(p(a[0],a[1]))+","+n(q(a[0],a[1]))+")"}).remove(),$.each(function(a){d3.select(this).classed("nv-point",!0).classed("nv-point-"+a[1],!0).classed("nv-noninteractive",!w).classed("hover",!1)}),$.watchTransition(P,"scatter points").attr("transform",function(a){return"translate("+m(p(a[0],a[1]))+","+n(q(a[0],a[1]))+")"}).attr("d",a.utils.symbol().type(function(a){return s(a[0])}).size(function(a){return o(r(a[0],a[1]))})),clearTimeout(f),f=setTimeout(N,300),c=m.copy(),d=n.copy(),e=o.copy()}),P.renderEnd("scatter immediate"),b}var c,d,e,f,g={top:0,right:0,bottom:0,left:0},h=null,i=null,j=a.utils.defaultColor(),k=Math.floor(1e5*Math.random()),l=null,m=d3.scale.linear(),n=d3.scale.linear(),o=d3.scale.linear(),p=function(a){return a.x},q=function(a){return a.y},r=function(a){return a.size||1},s=function(a){return a.shape||"circle"},t=[],u=[],v=[],w=!0,x=function(a){return!a.notActive},y=!1,z=.1,A=!1,B=!0,C=!1,D=function(){return 25},E=null,F=null,G=null,H=null,I=null,J=null,K=!1,L=d3.dispatch("elementClick","elementDblClick","elementMouseover","elementMouseout","renderEnd"),M=!0,N=250,O=!1,P=a.utils.renderWatch(L,N),Q=[16,256];return b.dispatch=L,b.options=a.utils.optionsFunc.bind(b),b._calls=new function(){this.clearHighlights=function(){return a.dom.write(function(){l.selectAll(".nv-point.hover").classed("hover",!1)}),null},this.highlightPoint=function(b,c,d){a.dom.write(function(){l.select(" .nv-series-"+b+" .nv-point-"+c).classed("hover",d)})}},L.on("elementMouseover.point",function(a){w&&b._calls.highlightPoint(a.seriesIndex,a.pointIndex,!0)}),L.on("elementMouseout.point",function(a){w&&b._calls.highlightPoint(a.seriesIndex,a.pointIndex,!1)}),b._options=Object.create({},{width:{get:function(){return h},set:function(a){h=a}},height:{get:function(){return i},set:function(a){i=a}},xScale:{get:function(){return m},set:function(a){m=a}},yScale:{get:function(){return n},set:function(a){n=a}},pointScale:{get:function(){return o},set:function(a){o=a}},xDomain:{get:function(){return E},set:function(a){E=a}},yDomain:{get:function(){return F},set:function(a){F=a}},pointDomain:{get:function(){return I},set:function(a){I=a}},xRange:{get:function(){return G},set:function(a){G=a}},yRange:{get:function(){return H},set:function(a){H=a}},pointRange:{get:function(){return J},set:function(a){J=a}},forceX:{get:function(){return t},set:function(a){t=a}},forceY:{get:function(){return u},set:function(a){u=a}},forcePoint:{get:function(){return v},set:function(a){v=a}},interactive:{get:function(){return w},set:function(a){w=a}},pointActive:{get:function(){return x},set:function(a){x=a}},padDataOuter:{get:function(){return z},set:function(a){z=a}},padData:{get:function(){return y},set:function(a){y=a}},clipEdge:{get:function(){return A},set:function(a){A=a}},clipVoronoi:{get:function(){return B},set:function(a){B=a}},clipRadius:{get:function(){return D},set:function(a){D=a}},showVoronoi:{get:function(){return C},set:function(a){C=a}},id:{get:function(){return k},set:function(a){k=a}},x:{get:function(){return p},set:function(a){p=d3.functor(a)}},y:{get:function(){return q},set:function(a){q=d3.functor(a)}},pointSize:{get:function(){return r},set:function(a){r=d3.functor(a)}},pointShape:{get:function(){return s},set:function(a){s=d3.functor(a)}},margin:{get:function(){return g},set:function(a){g.top=void 0!==a.top?a.top:g.top,g.right=void 0!==a.right?a.right:g.right,g.bottom=void 0!==a.bottom?a.bottom:g.bottom,g.left=void 0!==a.left?a.left:g.left}},duration:{get:function(){return N},set:function(a){N=a,P.reset(N)}},color:{get:function(){return j},set:function(b){j=a.utils.getColor(b)}},useVoronoi:{get:function(){return M},set:function(a){M=a,M===!1&&(B=!1)}}}),a.utils.initOptions(b),b},a.models.scatterChart=function(){"use strict";function b(z){return D.reset(),D.models(c),t&&D.models(d),u&&D.models(e),q&&D.models(g),r&&D.models(h),z.each(function(z){m=d3.select(this),a.utils.initSVG(m);var G=a.utils.availableWidth(k,m,j),H=a.utils.availableHeight(l,m,j);if(b.update=function(){0===A?m.call(b):m.transition().duration(A).call(b)},b.container=this,w.setter(F(z),b.update).getter(E(z)).update(),w.disabled=z.map(function(a){return!!a.disabled}),!x){var I;x={};for(I in w)x[I]=w[I]instanceof Array?w[I].slice(0):w[I]}if(!(z&&z.length&&z.filter(function(a){return a.values.length}).length))return a.utils.noData(b,m),D.renderEnd("scatter immediate"),b;m.selectAll(".nv-noData").remove(),o=c.xScale(),p=c.yScale();var J=m.selectAll("g.nv-wrap.nv-scatterChart").data([z]),K=J.enter().append("g").attr("class","nvd3 nv-wrap nv-scatterChart nv-chart-"+c.id()),L=K.append("g"),M=J.select("g");if(L.append("rect").attr("class","nvd3 nv-background").style("pointer-events","none"),L.append("g").attr("class","nv-x nv-axis"),L.append("g").attr("class","nv-y nv-axis"),L.append("g").attr("class","nv-scatterWrap"),L.append("g").attr("class","nv-regressionLinesWrap"),L.append("g").attr("class","nv-distWrap"),L.append("g").attr("class","nv-legendWrap"),v&&M.select(".nv-y.nv-axis").attr("transform","translate("+G+",0)"),s){var N=G;f.width(N),J.select(".nv-legendWrap").datum(z).call(f),j.top!=f.height()&&(j.top=f.height(),H=a.utils.availableHeight(l,m,j)),J.select(".nv-legendWrap").attr("transform","translate(0,"+-j.top+")")}J.attr("transform","translate("+j.left+","+j.top+")"),c.width(G).height(H).color(z.map(function(a,b){return a.color=a.color||n(a,b),a.color}).filter(function(a,b){return!z[b].disabled})),J.select(".nv-scatterWrap").datum(z.filter(function(a){return!a.disabled})).call(c),J.select(".nv-regressionLinesWrap").attr("clip-path","url(#nv-edge-clip-"+c.id()+")");var O=J.select(".nv-regressionLinesWrap").selectAll(".nv-regLines").data(function(a){return a});O.enter().append("g").attr("class","nv-regLines");var P=O.selectAll(".nv-regLine").data(function(a){return[a]});P.enter().append("line").attr("class","nv-regLine").style("stroke-opacity",0),P.filter(function(a){return a.intercept&&a.slope}).watchTransition(D,"scatterPlusLineChart: regline").attr("x1",o.range()[0]).attr("x2",o.range()[1]).attr("y1",function(a){return p(o.domain()[0]*a.slope+a.intercept)}).attr("y2",function(a){return p(o.domain()[1]*a.slope+a.intercept)}).style("stroke",function(a,b,c){return n(a,c)}).style("stroke-opacity",function(a){return a.disabled||"undefined"==typeof a.slope||"undefined"==typeof a.intercept?0:1}),t&&(d.scale(o)._ticks(a.utils.calcTicksX(G/100,z)).tickSize(-H,0),M.select(".nv-x.nv-axis").attr("transform","translate(0,"+p.range()[0]+")").call(d)),u&&(e.scale(p)._ticks(a.utils.calcTicksY(H/36,z)).tickSize(-G,0),M.select(".nv-y.nv-axis").call(e)),q&&(g.getData(c.x()).scale(o).width(G).color(z.map(function(a,b){return a.color||n(a,b)}).filter(function(a,b){return!z[b].disabled})),L.select(".nv-distWrap").append("g").attr("class","nv-distributionX"),M.select(".nv-distributionX").attr("transform","translate(0,"+p.range()[0]+")").datum(z.filter(function(a){return!a.disabled})).call(g)),r&&(h.getData(c.y()).scale(p).width(H).color(z.map(function(a,b){return a.color||n(a,b)}).filter(function(a,b){return!z[b].disabled})),L.select(".nv-distWrap").append("g").attr("class","nv-distributionY"),M.select(".nv-distributionY").attr("transform","translate("+(v?G:-h.size())+",0)").datum(z.filter(function(a){return!a.disabled})).call(h)),f.dispatch.on("stateChange",function(a){for(var c in a)w[c]=a[c];y.stateChange(w),b.update()}),y.on("changeState",function(a){"undefined"!=typeof a.disabled&&(z.forEach(function(b,c){b.disabled=a.disabled[c]}),w.disabled=a.disabled),b.update()}),c.dispatch.on("elementMouseout.tooltip",function(a){i.hidden(!0),m.select(".nv-chart-"+c.id()+" .nv-series-"+a.seriesIndex+" .nv-distx-"+a.pointIndex).attr("y1",0),m.select(".nv-chart-"+c.id()+" .nv-series-"+a.seriesIndex+" .nv-disty-"+a.pointIndex).attr("x2",h.size())}),c.dispatch.on("elementMouseover.tooltip",function(a){m.select(".nv-series-"+a.seriesIndex+" .nv-distx-"+a.pointIndex).attr("y1",a.pos.top-H-j.top),m.select(".nv-series-"+a.seriesIndex+" .nv-disty-"+a.pointIndex).attr("x2",a.pos.left+g.size()-j.left),i.position(a.pos).data(a).hidden(!1)}),B=o.copy(),C=p.copy()}),D.renderEnd("scatter with line immediate"),b}var c=a.models.scatter(),d=a.models.axis(),e=a.models.axis(),f=a.models.legend(),g=a.models.distribution(),h=a.models.distribution(),i=a.models.tooltip(),j={top:30,right:20,bottom:50,left:75},k=null,l=null,m=null,n=a.utils.defaultColor(),o=c.xScale(),p=c.yScale(),q=!1,r=!1,s=!0,t=!0,u=!0,v=!1,w=a.utils.state(),x=null,y=d3.dispatch("stateChange","changeState","renderEnd"),z=null,A=250;c.xScale(o).yScale(p),d.orient("bottom").tickPadding(10),e.orient(v?"right":"left").tickPadding(10),g.axis("x"),h.axis("y"),i.headerFormatter(function(a,b){return d.tickFormat()(a,b)}).valueFormatter(function(a,b){return e.tickFormat()(a,b)});var B,C,D=a.utils.renderWatch(y,A),E=function(a){return function(){return{active:a.map(function(a){return!a.disabled})}}},F=function(a){return function(b){void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};return b.dispatch=y,b.scatter=c,b.legend=f,b.xAxis=d,b.yAxis=e,b.distX=g,b.distY=h,b.tooltip=i,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return k},set:function(a){k=a}},height:{get:function(){return l},set:function(a){l=a}},container:{get:function(){return m},set:function(a){m=a}},showDistX:{get:function(){return q},set:function(a){q=a}},showDistY:{get:function(){return r},set:function(a){r=a}},showLegend:{get:function(){return s},set:function(a){s=a}},showXAxis:{get:function(){return t},set:function(a){t=a}},showYAxis:{get:function(){return u},set:function(a){u=a}},defaultState:{get:function(){return x},set:function(a){x=a}},noData:{get:function(){return z},set:function(a){z=a}},duration:{get:function(){return A},set:function(a){A=a}},tooltips:{get:function(){return i.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),i.enabled(!!b) -}},tooltipContent:{get:function(){return i.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),i.contentGenerator(b)}},tooltipXContent:{get:function(){return i.contentGenerator()},set:function(){a.deprecated("tooltipContent","This option is removed, put values into main tooltip.")}},tooltipYContent:{get:function(){return i.contentGenerator()},set:function(){a.deprecated("tooltipContent","This option is removed, put values into main tooltip.")}},margin:{get:function(){return j},set:function(a){j.top=void 0!==a.top?a.top:j.top,j.right=void 0!==a.right?a.right:j.right,j.bottom=void 0!==a.bottom?a.bottom:j.bottom,j.left=void 0!==a.left?a.left:j.left}},rightAlignYAxis:{get:function(){return v},set:function(a){v=a,e.orient(a?"right":"left")}},color:{get:function(){return n},set:function(b){n=a.utils.getColor(b),f.color(n),g.color(n),h.color(n)}}}),a.utils.inheritOptions(b,c),a.utils.initOptions(b),b},a.models.sparkline=function(){"use strict";function b(k){return k.each(function(b){var k=h-g.left-g.right,q=i-g.top-g.bottom;j=d3.select(this),a.utils.initSVG(j),l.domain(c||d3.extent(b,n)).range(e||[0,k]),m.domain(d||d3.extent(b,o)).range(f||[q,0]);{var r=j.selectAll("g.nv-wrap.nv-sparkline").data([b]),s=r.enter().append("g").attr("class","nvd3 nv-wrap nv-sparkline");s.append("g"),r.select("g")}r.attr("transform","translate("+g.left+","+g.top+")");var t=r.selectAll("path").data(function(a){return[a]});t.enter().append("path"),t.exit().remove(),t.style("stroke",function(a,b){return a.color||p(a,b)}).attr("d",d3.svg.line().x(function(a,b){return l(n(a,b))}).y(function(a,b){return m(o(a,b))}));var u=r.selectAll("circle.nv-point").data(function(a){function b(b){if(-1!=b){var c=a[b];return c.pointIndex=b,c}return null}var c=a.map(function(a,b){return o(a,b)}),d=b(c.lastIndexOf(m.domain()[1])),e=b(c.indexOf(m.domain()[0])),f=b(c.length-1);return[e,d,f].filter(function(a){return null!=a})});u.enter().append("circle"),u.exit().remove(),u.attr("cx",function(a){return l(n(a,a.pointIndex))}).attr("cy",function(a){return m(o(a,a.pointIndex))}).attr("r",2).attr("class",function(a){return n(a,a.pointIndex)==l.domain()[1]?"nv-point nv-currentValue":o(a,a.pointIndex)==m.domain()[0]?"nv-point nv-minValue":"nv-point nv-maxValue"})}),b}var c,d,e,f,g={top:2,right:0,bottom:2,left:0},h=400,i=32,j=null,k=!0,l=d3.scale.linear(),m=d3.scale.linear(),n=function(a){return a.x},o=function(a){return a.y},p=a.utils.getColor(["#000"]);return b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return h},set:function(a){h=a}},height:{get:function(){return i},set:function(a){i=a}},xDomain:{get:function(){return c},set:function(a){c=a}},yDomain:{get:function(){return d},set:function(a){d=a}},xRange:{get:function(){return e},set:function(a){e=a}},yRange:{get:function(){return f},set:function(a){f=a}},xScale:{get:function(){return l},set:function(a){l=a}},yScale:{get:function(){return m},set:function(a){m=a}},animate:{get:function(){return k},set:function(a){k=a}},x:{get:function(){return n},set:function(a){n=d3.functor(a)}},y:{get:function(){return o},set:function(a){o=d3.functor(a)}},margin:{get:function(){return g},set:function(a){g.top=void 0!==a.top?a.top:g.top,g.right=void 0!==a.right?a.right:g.right,g.bottom=void 0!==a.bottom?a.bottom:g.bottom,g.left=void 0!==a.left?a.left:g.left}},color:{get:function(){return p},set:function(b){p=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.sparklinePlus=function(){"use strict";function b(p){return p.each(function(p){function q(){if(!j){var a=z.selectAll(".nv-hoverValue").data(i),b=a.enter().append("g").attr("class","nv-hoverValue").style("stroke-opacity",0).style("fill-opacity",0);a.exit().transition().duration(250).style("stroke-opacity",0).style("fill-opacity",0).remove(),a.attr("transform",function(a){return"translate("+c(e.x()(p[a],a))+",0)"}).transition().duration(250).style("stroke-opacity",1).style("fill-opacity",1),i.length&&(b.append("line").attr("x1",0).attr("y1",-f.top).attr("x2",0).attr("y2",u),b.append("text").attr("class","nv-xValue").attr("x",-6).attr("y",-f.top).attr("text-anchor","end").attr("dy",".9em"),z.select(".nv-hoverValue .nv-xValue").text(k(e.x()(p[i[0]],i[0]))),b.append("text").attr("class","nv-yValue").attr("x",6).attr("y",-f.top).attr("text-anchor","start").attr("dy",".9em"),z.select(".nv-hoverValue .nv-yValue").text(l(e.y()(p[i[0]],i[0]))))}}function r(){function a(a,b){for(var c=Math.abs(e.x()(a[0],0)-b),d=0,f=0;fc;++c){for(b=0,d=0;bb;b++)a[b][c][1]/=d;else for(b=0;e>b;b++)a[b][c][1]=0}for(c=0;f>c;++c)g[c]=0;return g}}),u.renderEnd("stackedArea immediate"),b}var c,d,e={top:0,right:0,bottom:0,left:0},f=960,g=500,h=a.utils.defaultColor(),i=Math.floor(1e5*Math.random()),j=null,k=function(a){return a.x},l=function(a){return a.y},m="stack",n="zero",o="default",p="linear",q=!1,r=a.models.scatter(),s=250,t=d3.dispatch("areaClick","areaMouseover","areaMouseout","renderEnd","elementClick","elementMouseover","elementMouseout");r.pointSize(2.2).pointDomain([2.2,2.2]);var u=a.utils.renderWatch(t,s);return b.dispatch=t,b.scatter=r,r.dispatch.on("elementClick",function(){t.elementClick.apply(this,arguments)}),r.dispatch.on("elementMouseover",function(){t.elementMouseover.apply(this,arguments)}),r.dispatch.on("elementMouseout",function(){t.elementMouseout.apply(this,arguments)}),b.interpolate=function(a){return arguments.length?(p=a,b):p},b.duration=function(a){return arguments.length?(s=a,u.reset(s),r.duration(s),b):s},b.dispatch=t,b.scatter=r,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return f},set:function(a){f=a}},height:{get:function(){return g},set:function(a){g=a}},clipEdge:{get:function(){return q},set:function(a){q=a}},offset:{get:function(){return n},set:function(a){n=a}},order:{get:function(){return o},set:function(a){o=a}},interpolate:{get:function(){return p},set:function(a){p=a}},x:{get:function(){return k},set:function(a){k=d3.functor(a)}},y:{get:function(){return l},set:function(a){l=d3.functor(a)}},margin:{get:function(){return e},set:function(a){e.top=void 0!==a.top?a.top:e.top,e.right=void 0!==a.right?a.right:e.right,e.bottom=void 0!==a.bottom?a.bottom:e.bottom,e.left=void 0!==a.left?a.left:e.left}},color:{get:function(){return h},set:function(b){h=a.utils.getColor(b)}},style:{get:function(){return m},set:function(a){switch(m=a){case"stack":b.offset("zero"),b.order("default");break;case"stream":b.offset("wiggle"),b.order("inside-out");break;case"stream-center":b.offset("silhouette"),b.order("inside-out");break;case"expand":b.offset("expand"),b.order("default");break;case"stack_percent":b.offset(b.d3_stackedOffset_stackPercent),b.order("default")}}},duration:{get:function(){return s},set:function(a){s=a,u.reset(s),r.duration(s)}}}),a.utils.inheritOptions(b,r),a.utils.initOptions(b),b},a.models.stackedAreaChart=function(){"use strict";function b(k){return F.reset(),F.models(e),r&&F.models(f),s&&F.models(g),k.each(function(k){var x=d3.select(this),F=this;a.utils.initSVG(x);var K=a.utils.availableWidth(m,x,l),L=a.utils.availableHeight(n,x,l);if(b.update=function(){x.transition().duration(C).call(b)},b.container=this,v.setter(I(k),b.update).getter(H(k)).update(),v.disabled=k.map(function(a){return!!a.disabled}),!w){var M;w={};for(M in v)w[M]=v[M]instanceof Array?v[M].slice(0):v[M]}if(!(k&&k.length&&k.filter(function(a){return a.values.length}).length))return a.utils.noData(b,x),b;x.selectAll(".nv-noData").remove(),c=e.xScale(),d=e.yScale();var N=x.selectAll("g.nv-wrap.nv-stackedAreaChart").data([k]),O=N.enter().append("g").attr("class","nvd3 nv-wrap nv-stackedAreaChart").append("g"),P=N.select("g");if(O.append("rect").style("opacity",0),O.append("g").attr("class","nv-x nv-axis"),O.append("g").attr("class","nv-y nv-axis"),O.append("g").attr("class","nv-stackedWrap"),O.append("g").attr("class","nv-legendWrap"),O.append("g").attr("class","nv-controlsWrap"),O.append("g").attr("class","nv-interactive"),P.select("rect").attr("width",K).attr("height",L),q){var Q=p?K-z:K;h.width(Q),P.select(".nv-legendWrap").datum(k).call(h),l.top!=h.height()&&(l.top=h.height(),L=a.utils.availableHeight(n,x,l)),P.select(".nv-legendWrap").attr("transform","translate("+(K-Q)+","+-l.top+")")}if(p){var R=[{key:B.stacked||"Stacked",metaKey:"Stacked",disabled:"stack"!=e.style(),style:"stack"},{key:B.stream||"Stream",metaKey:"Stream",disabled:"stream"!=e.style(),style:"stream"},{key:B.expanded||"Expanded",metaKey:"Expanded",disabled:"expand"!=e.style(),style:"expand"},{key:B.stack_percent||"Stack %",metaKey:"Stack_Percent",disabled:"stack_percent"!=e.style(),style:"stack_percent"}];z=A.length/3*260,R=R.filter(function(a){return-1!==A.indexOf(a.metaKey)}),i.width(z).color(["#444","#444","#444"]),P.select(".nv-controlsWrap").datum(R).call(i),l.top!=Math.max(i.height(),h.height())&&(l.top=Math.max(i.height(),h.height()),L=a.utils.availableHeight(n,x,l)),P.select(".nv-controlsWrap").attr("transform","translate(0,"+-l.top+")")}N.attr("transform","translate("+l.left+","+l.top+")"),t&&P.select(".nv-y.nv-axis").attr("transform","translate("+K+",0)"),u&&(j.width(K).height(L).margin({left:l.left,top:l.top}).svgContainer(x).xScale(c),N.select(".nv-interactive").call(j)),e.width(K).height(L);var S=P.select(".nv-stackedWrap").datum(k);if(S.transition().call(e),r&&(f.scale(c)._ticks(a.utils.calcTicksX(K/100,k)).tickSize(-L,0),P.select(".nv-x.nv-axis").attr("transform","translate(0,"+L+")"),P.select(".nv-x.nv-axis").transition().duration(0).call(f)),s){var T;if(T="wiggle"===e.offset()?0:a.utils.calcTicksY(L/36,k),g.scale(d)._ticks(T).tickSize(-K,0),"expand"===e.style()||"stack_percent"===e.style()){var U=g.tickFormat();D&&U===J||(D=U),g.tickFormat(J)}else D&&(g.tickFormat(D),D=null);P.select(".nv-y.nv-axis").transition().duration(0).call(g)}e.dispatch.on("areaClick.toggle",function(a){k.forEach(1===k.filter(function(a){return!a.disabled}).length?function(a){a.disabled=!1}:function(b,c){b.disabled=c!=a.seriesIndex}),v.disabled=k.map(function(a){return!!a.disabled}),y.stateChange(v),b.update()}),h.dispatch.on("stateChange",function(a){for(var c in a)v[c]=a[c];y.stateChange(v),b.update()}),i.dispatch.on("legendClick",function(a){a.disabled&&(R=R.map(function(a){return a.disabled=!0,a}),a.disabled=!1,e.style(a.style),v.style=e.style(),y.stateChange(v),b.update())}),j.dispatch.on("elementMousemove",function(c){e.clearHighlights();var d,g,h,i=[];if(k.filter(function(a,b){return a.seriesIndex=b,!a.disabled}).forEach(function(f,j){g=a.interactiveBisect(f.values,c.pointXValue,b.x());var k=f.values[g],l=b.y()(k,g);if(null!=l&&e.highlightPoint(j,g,!0),"undefined"!=typeof k){"undefined"==typeof d&&(d=k),"undefined"==typeof h&&(h=b.xScale()(b.x()(k,g)));var m="expand"==e.style()?k.display.y:b.y()(k,g);i.push({key:f.key,value:m,color:o(f,f.seriesIndex),stackedValue:k.display})}}),i.reverse(),i.length>2){var m=b.yScale().invert(c.mouseY),n=null;i.forEach(function(a,b){m=Math.abs(m);var c=Math.abs(a.stackedValue.y0),d=Math.abs(a.stackedValue.y);return m>=c&&d+c>=m?void(n=b):void 0}),null!=n&&(i[n].highlight=!0)}var p=f.tickFormat()(b.x()(d,g)),q=j.tooltip.valueFormatter();"expand"===e.style()||"stack_percent"===e.style()?(E||(E=q),q=d3.format(".1%")):E&&(q=E,E=null),j.tooltip.position({left:h+l.left,top:c.mouseY+l.top}).chartContainer(F.parentNode).valueFormatter(q).data({value:p,series:i})(),j.renderGuideLine(h)}),j.dispatch.on("elementMouseout",function(){e.clearHighlights()}),y.on("changeState",function(a){"undefined"!=typeof a.disabled&&k.length===a.disabled.length&&(k.forEach(function(b,c){b.disabled=a.disabled[c]}),v.disabled=a.disabled),"undefined"!=typeof a.style&&(e.style(a.style),G=a.style),b.update()})}),F.renderEnd("stacked Area chart immediate"),b}var c,d,e=a.models.stackedArea(),f=a.models.axis(),g=a.models.axis(),h=a.models.legend(),i=a.models.legend(),j=a.interactiveGuideline(),k=a.models.tooltip(),l={top:30,right:25,bottom:50,left:60},m=null,n=null,o=a.utils.defaultColor(),p=!0,q=!0,r=!0,s=!0,t=!1,u=!1,v=a.utils.state(),w=null,x=null,y=d3.dispatch("stateChange","changeState","renderEnd"),z=250,A=["Stacked","Stream","Expanded"],B={},C=250;v.style=e.style(),f.orient("bottom").tickPadding(7),g.orient(t?"right":"left"),k.headerFormatter(function(a,b){return f.tickFormat()(a,b)}).valueFormatter(function(a,b){return g.tickFormat()(a,b)}),j.tooltip.headerFormatter(function(a,b){return f.tickFormat()(a,b)}).valueFormatter(function(a,b){return g.tickFormat()(a,b)});var D=null,E=null;i.updateState(!1);var F=a.utils.renderWatch(y),G=e.style(),H=function(a){return function(){return{active:a.map(function(a){return!a.disabled}),style:e.style()}}},I=function(a){return function(b){void 0!==b.style&&(G=b.style),void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}},J=d3.format("%");return e.dispatch.on("elementMouseover.tooltip",function(a){a.point.x=e.x()(a.point),a.point.y=e.y()(a.point),k.data(a).position(a.pos).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(){k.hidden(!0)}),b.dispatch=y,b.stacked=e,b.legend=h,b.controls=i,b.xAxis=f,b.yAxis=g,b.interactiveLayer=j,b.tooltip=k,b.dispatch=y,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return m},set:function(a){m=a}},height:{get:function(){return n},set:function(a){n=a}},showLegend:{get:function(){return q},set:function(a){q=a}},showXAxis:{get:function(){return r},set:function(a){r=a}},showYAxis:{get:function(){return s},set:function(a){s=a}},defaultState:{get:function(){return w},set:function(a){w=a}},noData:{get:function(){return x},set:function(a){x=a}},showControls:{get:function(){return p},set:function(a){p=a}},controlLabels:{get:function(){return B},set:function(a){B=a}},controlOptions:{get:function(){return A},set:function(a){A=a}},tooltips:{get:function(){return k.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),k.enabled(!!b)}},tooltipContent:{get:function(){return k.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),k.contentGenerator(b)}},margin:{get:function(){return l},set:function(a){l.top=void 0!==a.top?a.top:l.top,l.right=void 0!==a.right?a.right:l.right,l.bottom=void 0!==a.bottom?a.bottom:l.bottom,l.left=void 0!==a.left?a.left:l.left}},duration:{get:function(){return C},set:function(a){C=a,F.reset(C),e.duration(C),f.duration(C),g.duration(C)}},color:{get:function(){return o},set:function(b){o=a.utils.getColor(b),h.color(o),e.color(o)}},rightAlignYAxis:{get:function(){return t},set:function(a){t=a,g.orient(t?"right":"left")}},useInteractiveGuideline:{get:function(){return u},set:function(a){u=!!a,b.interactive(!a),b.useVoronoi(!a),e.scatter.interactive(!a)}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.sunburst=function(){"use strict";function b(u){return t.reset(),u.each(function(b){function t(a){a.x0=a.x,a.dx0=a.dx}function u(a){var b=d3.interpolate(p.domain(),[a.x,a.x+a.dx]),c=d3.interpolate(q.domain(),[a.y,1]),d=d3.interpolate(q.range(),[a.y?20:0,y]);return function(a,e){return e?function(){return s(a)}:function(e){return p.domain(b(e)),q.domain(c(e)).range(d(e)),s(a)}}}l=d3.select(this);var v,w=a.utils.availableWidth(g,l,f),x=a.utils.availableHeight(h,l,f),y=Math.min(w,x)/2;a.utils.initSVG(l);var z=l.selectAll(".nv-wrap.nv-sunburst").data(b),A=z.enter().append("g").attr("class","nvd3 nv-wrap nv-sunburst nv-chart-"+k),B=A.selectAll("nv-sunburst");z.attr("transform","translate("+w/2+","+x/2+")"),l.on("click",function(a,b){o.chartClick({data:a,index:b,pos:d3.event,id:k})}),q.range([0,y]),c=c||b,e=b[0],r.value(j[i]||j.count),v=B.data(r.nodes).enter().append("path").attr("d",s).style("fill",function(a){return m((a.children?a:a.parent).name)}).style("stroke","#FFF").on("click",function(a){d!==c&&c!==a&&(d=c),c=a,v.transition().duration(n).attrTween("d",u(a))}).each(t).on("dblclick",function(a){d.parent==a&&v.transition().duration(n).attrTween("d",u(e))}).each(t).on("mouseover",function(a){d3.select(this).classed("hover",!0).style("opacity",.8),o.elementMouseover({data:a,color:d3.select(this).style("fill")})}).on("mouseout",function(a){d3.select(this).classed("hover",!1).style("opacity",1),o.elementMouseout({data:a})}).on("mousemove",function(a){o.elementMousemove({data:a})})}),t.renderEnd("sunburst immediate"),b}var c,d,e,f={top:0,right:0,bottom:0,left:0},g=null,h=null,i="count",j={count:function(){return 1},size:function(a){return a.size}},k=Math.floor(1e4*Math.random()),l=null,m=a.utils.defaultColor(),n=500,o=d3.dispatch("chartClick","elementClick","elementDblClick","elementMousemove","elementMouseover","elementMouseout","renderEnd"),p=d3.scale.linear().range([0,2*Math.PI]),q=d3.scale.sqrt(),r=d3.layout.partition().sort(null).value(function(){return 1}),s=d3.svg.arc().startAngle(function(a){return Math.max(0,Math.min(2*Math.PI,p(a.x)))}).endAngle(function(a){return Math.max(0,Math.min(2*Math.PI,p(a.x+a.dx)))}).innerRadius(function(a){return Math.max(0,q(a.y))}).outerRadius(function(a){return Math.max(0,q(a.y+a.dy))}),t=a.utils.renderWatch(o);return b.dispatch=o,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return g},set:function(a){g=a}},height:{get:function(){return h},set:function(a){h=a}},mode:{get:function(){return i},set:function(a){i=a}},id:{get:function(){return k},set:function(a){k=a}},duration:{get:function(){return n},set:function(a){n=a}},margin:{get:function(){return f},set:function(a){f.top=void 0!=a.top?a.top:f.top,f.right=void 0!=a.right?a.right:f.right,f.bottom=void 0!=a.bottom?a.bottom:f.bottom,f.left=void 0!=a.left?a.left:f.left}},color:{get:function(){return m},set:function(b){m=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.sunburstChart=function(){"use strict";function b(d){return m.reset(),m.models(c),d.each(function(d){var h=d3.select(this);a.utils.initSVG(h);var i=a.utils.availableWidth(f,h,e),j=a.utils.availableHeight(g,h,e);if(b.update=function(){0===k?h.call(b):h.transition().duration(k).call(b)},b.container=this,!d||!d.length)return a.utils.noData(b,h),b;h.selectAll(".nv-noData").remove();var l=h.selectAll("g.nv-wrap.nv-sunburstChart").data(d),m=l.enter().append("g").attr("class","nvd3 nv-wrap nv-sunburstChart").append("g"),n=l.select("g");m.append("g").attr("class","nv-sunburstWrap"),l.attr("transform","translate("+e.left+","+e.top+")"),c.width(i).height(j);var o=n.select(".nv-sunburstWrap").datum(d);d3.transition(o).call(c)}),m.renderEnd("sunburstChart immediate"),b}var c=a.models.sunburst(),d=a.models.tooltip(),e={top:30,right:20,bottom:20,left:20},f=null,g=null,h=a.utils.defaultColor(),i=(Math.round(1e5*Math.random()),null),j=null,k=250,l=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState","renderEnd"),m=a.utils.renderWatch(l);return d.headerEnabled(!1).duration(0).valueFormatter(function(a){return a}),c.dispatch.on("elementMouseover.tooltip",function(a){a.series={key:a.data.name,value:a.data.size,color:a.color},d.data(a).hidden(!1)}),c.dispatch.on("elementMouseout.tooltip",function(){d.hidden(!0)}),c.dispatch.on("elementMousemove.tooltip",function(){d.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.dispatch=l,b.sunburst=c,b.tooltip=d,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{noData:{get:function(){return j},set:function(a){j=a}},defaultState:{get:function(){return i},set:function(a){i=a}},color:{get:function(){return h},set:function(a){h=a,c.color(h)}},duration:{get:function(){return k},set:function(a){k=a,m.reset(k),c.duration(k)}},margin:{get:function(){return e},set:function(a){e.top=void 0!==a.top?a.top:e.top,e.right=void 0!==a.right?a.right:e.right,e.bottom=void 0!==a.bottom?a.bottom:e.bottom,e.left=void 0!==a.left?a.left:e.left}}}),a.utils.inheritOptions(b,c),a.utils.initOptions(b),b},a.version="1.8.1"}(); \ No newline at end of file diff --git a/app/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/js/popper.min.js b/app/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/js/popper.min.js deleted file mode 100644 index bb1aaae3e..000000000 --- a/app/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/js/popper.min.js +++ /dev/null @@ -1,5 +0,0 @@ -/* - Copyright (C) Federico Zivolo 2020 - Distributed under the MIT License (license terms are at http://opensource.org/licenses/MIT). - */(function(e,t){'object'==typeof exports&&'undefined'!=typeof module?module.exports=t():'function'==typeof define&&define.amd?define(t):e.Popper=t()})(this,function(){'use strict';function e(e){return e&&'[object Function]'==={}.toString.call(e)}function t(e,t){if(1!==e.nodeType)return[];var o=e.ownerDocument.defaultView,n=o.getComputedStyle(e,null);return t?n[t]:n}function o(e){return'HTML'===e.nodeName?e:e.parentNode||e.host}function n(e){if(!e)return document.body;switch(e.nodeName){case'HTML':case'BODY':return e.ownerDocument.body;case'#document':return e.body;}var i=t(e),r=i.overflow,p=i.overflowX,s=i.overflowY;return /(auto|scroll|overlay)/.test(r+s+p)?e:n(o(e))}function i(e){return e&&e.referenceNode?e.referenceNode:e}function r(e){return 11===e?re:10===e?pe:re||pe}function p(e){if(!e)return document.documentElement;for(var o=r(10)?document.body:null,n=e.offsetParent||null;n===o&&e.nextElementSibling;)n=(e=e.nextElementSibling).offsetParent;var i=n&&n.nodeName;return i&&'BODY'!==i&&'HTML'!==i?-1!==['TH','TD','TABLE'].indexOf(n.nodeName)&&'static'===t(n,'position')?p(n):n:e?e.ownerDocument.documentElement:document.documentElement}function s(e){var t=e.nodeName;return'BODY'!==t&&('HTML'===t||p(e.firstElementChild)===e)}function d(e){return null===e.parentNode?e:d(e.parentNode)}function a(e,t){if(!e||!e.nodeType||!t||!t.nodeType)return document.documentElement;var o=e.compareDocumentPosition(t)&Node.DOCUMENT_POSITION_FOLLOWING,n=o?e:t,i=o?t:e,r=document.createRange();r.setStart(n,0),r.setEnd(i,0);var l=r.commonAncestorContainer;if(e!==l&&t!==l||n.contains(i))return s(l)?l:p(l);var f=d(e);return f.host?a(f.host,t):a(e,d(t).host)}function l(e){var t=1=o.clientWidth&&n>=o.clientHeight}),l=0a[e]&&!t.escapeWithReference&&(n=Q(f[o],a[e]-('right'===e?f.width:f.height))),ae({},o,n)}};return l.forEach(function(e){var t=-1===['left','top'].indexOf(e)?'secondary':'primary';f=le({},f,m[t](e))}),e.offsets.popper=f,e},priority:['left','right','top','bottom'],padding:5,boundariesElement:'scrollParent'},keepTogether:{order:400,enabled:!0,fn:function(e){var t=e.offsets,o=t.popper,n=t.reference,i=e.placement.split('-')[0],r=Z,p=-1!==['top','bottom'].indexOf(i),s=p?'right':'bottom',d=p?'left':'top',a=p?'width':'height';return o[s]r(n[s])&&(e.offsets.popper[d]=r(n[s])),e}},arrow:{order:500,enabled:!0,fn:function(e,o){var n;if(!K(e.instance.modifiers,'arrow','keepTogether'))return e;var i=o.element;if('string'==typeof i){if(i=e.instance.popper.querySelector(i),!i)return e;}else if(!e.instance.popper.contains(i))return console.warn('WARNING: `arrow.element` must be child of its popper element!'),e;var r=e.placement.split('-')[0],p=e.offsets,s=p.popper,d=p.reference,a=-1!==['left','right'].indexOf(r),l=a?'height':'width',f=a?'Top':'Left',m=f.toLowerCase(),h=a?'left':'top',c=a?'bottom':'right',u=S(i)[l];d[c]-us[c]&&(e.offsets.popper[m]+=d[m]+u-s[c]),e.offsets.popper=g(e.offsets.popper);var b=d[m]+d[l]/2-u/2,w=t(e.instance.popper),y=parseFloat(w['margin'+f]),E=parseFloat(w['border'+f+'Width']),v=b-e.offsets.popper[m]-y-E;return v=ee(Q(s[l]-u,v),0),e.arrowElement=i,e.offsets.arrow=(n={},ae(n,m,$(v)),ae(n,h,''),n),e},element:'[x-arrow]'},flip:{order:600,enabled:!0,fn:function(e,t){if(W(e.instance.modifiers,'inner'))return e;if(e.flipped&&e.placement===e.originalPlacement)return e;var o=v(e.instance.popper,e.instance.reference,t.padding,t.boundariesElement,e.positionFixed),n=e.placement.split('-')[0],i=T(n),r=e.placement.split('-')[1]||'',p=[];switch(t.behavior){case ce.FLIP:p=[n,i];break;case ce.CLOCKWISE:p=G(n);break;case ce.COUNTERCLOCKWISE:p=G(n,!0);break;default:p=t.behavior;}return p.forEach(function(s,d){if(n!==s||p.length===d+1)return e;n=e.placement.split('-')[0],i=T(n);var a=e.offsets.popper,l=e.offsets.reference,f=Z,m='left'===n&&f(a.right)>f(l.left)||'right'===n&&f(a.left)f(l.top)||'bottom'===n&&f(a.top)f(o.right),g=f(a.top)f(o.bottom),b='left'===n&&h||'right'===n&&c||'top'===n&&g||'bottom'===n&&u,w=-1!==['top','bottom'].indexOf(n),y=!!t.flipVariations&&(w&&'start'===r&&h||w&&'end'===r&&c||!w&&'start'===r&&g||!w&&'end'===r&&u),E=!!t.flipVariationsByContent&&(w&&'start'===r&&c||w&&'end'===r&&h||!w&&'start'===r&&u||!w&&'end'===r&&g),v=y||E;(m||b||v)&&(e.flipped=!0,(m||b)&&(n=p[d+1]),v&&(r=z(r)),e.placement=n+(r?'-'+r:''),e.offsets.popper=le({},e.offsets.popper,C(e.instance.popper,e.offsets.reference,e.placement)),e=P(e.instance.modifiers,e,'flip'))}),e},behavior:'flip',padding:5,boundariesElement:'viewport',flipVariations:!1,flipVariationsByContent:!1},inner:{order:700,enabled:!1,fn:function(e){var t=e.placement,o=t.split('-')[0],n=e.offsets,i=n.popper,r=n.reference,p=-1!==['left','right'].indexOf(o),s=-1===['top','left'].indexOf(o);return i[p?'left':'top']=r[o]-(s?i[p?'width':'height']:0),e.placement=T(t),e.offsets.popper=g(i),e}},hide:{order:800,enabled:!0,fn:function(e){if(!K(e.instance.modifiers,'hide','preventOverflow'))return e;var t=e.offsets.reference,o=D(e.instance.modifiers,function(e){return'preventOverflow'===e.name}).boundaries;if(t.bottomo.right||t.top>o.bottom||t.rightwindow.devicePixelRatio||!fe),c='bottom'===o?'top':'bottom',g='right'===n?'left':'right',b=B('transform');if(d='bottom'==c?'HTML'===l.nodeName?-l.clientHeight+h.bottom:-f.height+h.bottom:h.top,s='right'==g?'HTML'===l.nodeName?-l.clientWidth+h.right:-f.width+h.right:h.left,a&&b)m[b]='translate3d('+s+'px, '+d+'px, 0)',m[c]=0,m[g]=0,m.willChange='transform';else{var w='bottom'==c?-1:1,y='right'==g?-1:1;m[c]=d*w,m[g]=s*y,m.willChange=c+', '+g}var E={"x-placement":e.placement};return e.attributes=le({},E,e.attributes),e.styles=le({},m,e.styles),e.arrowStyles=le({},e.offsets.arrow,e.arrowStyles),e},gpuAcceleration:!0,x:'bottom',y:'right'},applyStyle:{order:900,enabled:!0,fn:function(e){return V(e.instance.popper,e.styles),j(e.instance.popper,e.attributes),e.arrowElement&&Object.keys(e.arrowStyles).length&&V(e.arrowElement,e.arrowStyles),e},onLoad:function(e,t,o,n,i){var r=L(i,t,e,o.positionFixed),p=O(o.placement,r,t,e,o.modifiers.flip.boundariesElement,o.modifiers.flip.padding);return t.setAttribute('x-placement',p),V(t,{position:o.positionFixed?'fixed':'absolute'}),o},gpuAcceleration:void 0}}},ge}); -//# sourceMappingURL=popper.min.js.map diff --git a/app/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/line.html.dist b/app/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/line.html.dist index 89810d157..d7e055f63 100644 --- a/app/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/line.html.dist +++ b/app/vendor/phpunit/php-code-coverage/src/Report/Html/Renderer/Template/line.html.dist @@ -1 +1 @@ - {{lineNumber}}{{lineContent}} + {{lineNumber}}{{lineContent}} diff --git a/app/vendor/phpunit/php-code-coverage/src/Report/OpenClover.php b/app/vendor/phpunit/php-code-coverage/src/Report/OpenClover.php new file mode 100644 index 000000000..042132b33 --- /dev/null +++ b/app/vendor/phpunit/php-code-coverage/src/Report/OpenClover.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 SebastianBergmann\CodeCoverage\Report; + +use function assert; +use function basename; +use function count; +use function dirname; +use function file_put_contents; +use function is_string; +use function ksort; +use function max; +use function range; +use function str_contains; +use function str_replace; +use function time; +use DOMDocument; +use DOMElement; +use SebastianBergmann\CodeCoverage\CodeCoverage; +use SebastianBergmann\CodeCoverage\Node\File; +use SebastianBergmann\CodeCoverage\Util\Filesystem; +use SebastianBergmann\CodeCoverage\Version; +use SebastianBergmann\CodeCoverage\WriteOperationFailedException; + +final class OpenClover +{ + /** + * @throws WriteOperationFailedException + */ + public function process(CodeCoverage $coverage, ?string $target = null, ?string $name = null): string + { + $time = (string) time(); + + $xmlDocument = new DOMDocument('1.0', 'UTF-8'); + $xmlDocument->formatOutput = true; + + $xmlCoverage = $xmlDocument->createElement('coverage'); + $xmlCoverage->setAttribute('clover', Version::id()); + $xmlCoverage->setAttribute('generated', $time); + $xmlDocument->appendChild($xmlCoverage); + + $xmlProject = $xmlDocument->createElement('project'); + $xmlProject->setAttribute('timestamp', $time); + + if (is_string($name)) { + $xmlProject->setAttribute('name', $name); + } + + $xmlCoverage->appendChild($xmlProject); + + /** @var array $packages */ + $packages = []; + $report = $coverage->getReport(); + + foreach ($report as $item) { + if (!$item instanceof File) { + continue; + } + + $xmlFile = $xmlDocument->createElement('file'); + $xmlFile->setAttribute('name', basename($item->pathAsString())); + $xmlFile->setAttribute('path', $item->pathAsString()); + + $classes = $item->classesAndTraits(); + $coverageData = $item->lineCoverageData(); + $lines = []; + $namespace = 'global'; + + foreach ($classes as $className => $class) { + $classStatements = 0; + $coveredClassStatements = 0; + $coveredMethods = 0; + $classMethods = 0; + + // Assumption: one namespace per file + if ($class['namespace'] !== '') { + $namespace = $class['namespace']; + } + + foreach ($class['methods'] as $methodName => $method) { + /** @phpstan-ignore equal.notAllowed */ + if ($method['executableLines'] == 0) { + continue; + } + + $classMethods++; + $classStatements += $method['executableLines']; + $coveredClassStatements += $method['executedLines']; + + /** @phpstan-ignore equal.notAllowed */ + if ($method['coverage'] == 100) { + $coveredMethods++; + } + + $methodCount = 0; + + foreach (range($method['startLine'], $method['endLine']) as $line) { + if (isset($coverageData[$line])) { + $methodCount = max($methodCount, count($coverageData[$line])); + } + } + + $lines[$method['startLine']] = [ + 'ccn' => $method['ccn'], + 'count' => $methodCount, + 'type' => 'method', + 'signature' => $method['signature'], + 'visibility' => $method['visibility'], + ]; + } + + $xmlClass = $xmlDocument->createElement('class'); + $xmlClass->setAttribute('name', str_replace($class['namespace'] . '\\', '', $className)); + + $xmlFile->appendChild($xmlClass); + + $xmlMetrics = $xmlDocument->createElement('metrics'); + $xmlMetrics->setAttribute('complexity', (string) $class['ccn']); + $xmlMetrics->setAttribute('elements', (string) ($classMethods + $classStatements + $class['executableBranches'])); + $xmlMetrics->setAttribute('coveredelements', (string) ($coveredMethods + $coveredClassStatements + $class['executedBranches'])); + $xmlMetrics->setAttribute('conditionals', (string) $class['executableBranches']); + $xmlMetrics->setAttribute('coveredconditionals', (string) $class['executedBranches']); + $xmlMetrics->setAttribute('statements', (string) $classStatements); + $xmlMetrics->setAttribute('coveredstatements', (string) $coveredClassStatements); + $xmlMetrics->setAttribute('methods', (string) $classMethods); + $xmlMetrics->setAttribute('coveredmethods', (string) $coveredMethods); + $xmlClass->insertBefore($xmlMetrics, $xmlClass->firstChild); + } + + foreach ($coverageData as $line => $data) { + if ($data === null || isset($lines[$line])) { + continue; + } + + $lines[$line] = [ + 'count' => count($data), + 'type' => 'stmt', + ]; + } + + ksort($lines); + + foreach ($lines as $line => $data) { + $xmlLine = $xmlDocument->createElement('line'); + $xmlLine->setAttribute('num', (string) $line); + $xmlLine->setAttribute('type', $data['type']); + + if (isset($data['ccn'])) { + $xmlLine->setAttribute('complexity', (string) $data['ccn']); + } + + $xmlLine->setAttribute('count', (string) $data['count']); + + if (isset($data['signature'])) { + $xmlLine->setAttribute('signature', $data['signature']); + } + + if (isset($data['visibility'])) { + $xmlLine->setAttribute('visibility', $data['visibility']); + } + + $xmlFile->appendChild($xmlLine); + } + + $linesOfCode = $item->linesOfCode(); + + $xmlMetrics = $xmlDocument->createElement('metrics'); + $xmlMetrics->setAttribute('loc', (string) $linesOfCode->linesOfCode()); + $xmlMetrics->setAttribute('ncloc', (string) $linesOfCode->nonCommentLinesOfCode()); + $xmlMetrics->setAttribute('classes', (string) $item->numberOfClassesAndTraits()); + $xmlMetrics->setAttribute('complexity', (string) $item->cyclomaticComplexity()); + $xmlMetrics->setAttribute('elements', (string) ($item->numberOfMethods() + $item->numberOfExecutableLines() + $item->numberOfExecutableBranches())); + $xmlMetrics->setAttribute('coveredelements', (string) ($item->numberOfTestedMethods() + $item->numberOfExecutedLines() + $item->numberOfExecutedBranches())); + $xmlMetrics->setAttribute('conditionals', (string) $item->numberOfExecutableBranches()); + $xmlMetrics->setAttribute('coveredconditionals', (string) $item->numberOfExecutedBranches()); + $xmlMetrics->setAttribute('statements', (string) $item->numberOfExecutableLines()); + $xmlMetrics->setAttribute('coveredstatements', (string) $item->numberOfExecutedLines()); + $xmlMetrics->setAttribute('methods', (string) $item->numberOfMethods()); + $xmlMetrics->setAttribute('coveredmethods', (string) $item->numberOfTestedMethods()); + $xmlFile->insertBefore($xmlMetrics, $xmlFile->firstChild); + + if (!isset($packages[$namespace])) { + $packages[$namespace] = $xmlDocument->createElement('package'); + $packages[$namespace]->setAttribute('name', $namespace); + + $xmlPackageMetrics = $xmlDocument->createElement('metrics'); + $xmlPackageMetrics->setAttribute('complexity', '0'); + $xmlPackageMetrics->setAttribute('elements', '0'); + $xmlPackageMetrics->setAttribute('coveredelements', '0'); + $xmlPackageMetrics->setAttribute('conditionals', '0'); + $xmlPackageMetrics->setAttribute('coveredconditionals', '0'); + $xmlPackageMetrics->setAttribute('statements', '0'); + $xmlPackageMetrics->setAttribute('coveredstatements', '0'); + $xmlPackageMetrics->setAttribute('methods', '0'); + $xmlPackageMetrics->setAttribute('coveredmethods', '0'); + $packages[$namespace]->appendChild($xmlPackageMetrics); + + $xmlProject->appendChild($packages[$namespace]); + } + + $xmlPackageMetrics = $packages[$namespace]->firstChild; + + assert($xmlPackageMetrics instanceof DOMElement); + + $xmlPackageMetrics->setAttribute('complexity', (string) ((int) $xmlPackageMetrics->getAttribute('complexity') + $item->cyclomaticComplexity())); + $xmlPackageMetrics->setAttribute('elements', (string) ((int) $xmlPackageMetrics->getAttribute('elements') + $item->numberOfMethods() + $item->numberOfExecutableLines() + $item->numberOfExecutableBranches())); + $xmlPackageMetrics->setAttribute('coveredelements', (string) ((int) $xmlPackageMetrics->getAttribute('coveredelements') + $item->numberOfTestedMethods() + $item->numberOfExecutedLines() + $item->numberOfExecutedBranches())); + $xmlPackageMetrics->setAttribute('conditionals', (string) ((int) $xmlPackageMetrics->getAttribute('conditionals') + $item->numberOfExecutableBranches())); + $xmlPackageMetrics->setAttribute('coveredconditionals', (string) ((int) $xmlPackageMetrics->getAttribute('coveredconditionals') + $item->numberOfExecutedBranches())); + $xmlPackageMetrics->setAttribute('statements', (string) ((int) $xmlPackageMetrics->getAttribute('statements') + $item->numberOfExecutableLines())); + $xmlPackageMetrics->setAttribute('coveredstatements', (string) ((int) $xmlPackageMetrics->getAttribute('coveredstatements') + $item->numberOfExecutedLines())); + $xmlPackageMetrics->setAttribute('methods', (string) ((int) $xmlPackageMetrics->getAttribute('methods') + $item->numberOfMethods())); + $xmlPackageMetrics->setAttribute('coveredmethods', (string) ((int) $xmlPackageMetrics->getAttribute('coveredmethods') + $item->numberOfTestedMethods())); + + $packages[$namespace]->appendChild($xmlFile); + } + + $linesOfCode = $report->linesOfCode(); + + $xmlMetrics = $xmlDocument->createElement('metrics'); + $xmlMetrics->setAttribute('files', (string) count($report)); + $xmlMetrics->setAttribute('loc', (string) $linesOfCode->linesOfCode()); + $xmlMetrics->setAttribute('ncloc', (string) $linesOfCode->nonCommentLinesOfCode()); + $xmlMetrics->setAttribute('classes', (string) $report->numberOfClassesAndTraits()); + $xmlMetrics->setAttribute('complexity', (string) $report->cyclomaticComplexity()); + $xmlMetrics->setAttribute('elements', (string) ($report->numberOfMethods() + $report->numberOfExecutableLines() + $report->numberOfExecutableBranches())); + $xmlMetrics->setAttribute('coveredelements', (string) ($report->numberOfTestedMethods() + $report->numberOfExecutedLines() + $report->numberOfExecutedBranches())); + $xmlMetrics->setAttribute('conditionals', (string) $report->numberOfExecutableBranches()); + $xmlMetrics->setAttribute('coveredconditionals', (string) $report->numberOfExecutedBranches()); + $xmlMetrics->setAttribute('statements', (string) $report->numberOfExecutableLines()); + $xmlMetrics->setAttribute('coveredstatements', (string) $report->numberOfExecutedLines()); + $xmlMetrics->setAttribute('methods', (string) $report->numberOfMethods()); + $xmlMetrics->setAttribute('coveredmethods', (string) $report->numberOfTestedMethods()); + $xmlProject->insertBefore($xmlMetrics, $xmlProject->firstChild); + + $buffer = $xmlDocument->saveXML(); + + if ($target !== null) { + if (!str_contains($target, '://')) { + Filesystem::createDirectory(dirname($target)); + } + + if (@file_put_contents($target, $buffer) === false) { + throw new WriteOperationFailedException($target); + } + } + + return $buffer; + } +} diff --git a/app/vendor/phpunit/php-code-coverage/src/Report/PHP.php b/app/vendor/phpunit/php-code-coverage/src/Report/PHP.php index 1f8186d04..051f9154e 100644 --- a/app/vendor/phpunit/php-code-coverage/src/Report/PHP.php +++ b/app/vendor/phpunit/php-code-coverage/src/Report/PHP.php @@ -9,13 +9,14 @@ */ namespace SebastianBergmann\CodeCoverage\Report; +use const PHP_EOL; use function dirname; use function file_put_contents; use function serialize; -use function strpos; +use function str_contains; use SebastianBergmann\CodeCoverage\CodeCoverage; -use SebastianBergmann\CodeCoverage\Driver\WriteOperationFailedException; use SebastianBergmann\CodeCoverage\Util\Filesystem; +use SebastianBergmann\CodeCoverage\WriteOperationFailedException; final class PHP { @@ -27,7 +28,7 @@ public function process(CodeCoverage $coverage, ?string $target = null): string return \unserialize(<<<'END_OF_COVERAGE_SERIALIZATION'" . PHP_EOL . serialize($coverage) . PHP_EOL . 'END_OF_COVERAGE_SERIALIZATION' . PHP_EOL . ');'; if ($target !== null) { - if (!strpos($target, '://') !== false) { + if (!str_contains($target, '://')) { Filesystem::createDirectory(dirname($target)); } diff --git a/app/vendor/phpunit/php-code-coverage/src/Report/Text.php b/app/vendor/phpunit/php-code-coverage/src/Report/Text.php index 755c2dd48..f18820b70 100644 --- a/app/vendor/phpunit/php-code-coverage/src/Report/Text.php +++ b/app/vendor/phpunit/php-code-coverage/src/Report/Text.php @@ -23,67 +23,25 @@ final class Text { - /** - * @var string - */ - private const COLOR_GREEN = "\x1b[30;42m"; - - /** - * @var string - */ - private const COLOR_YELLOW = "\x1b[30;43m"; - - /** - * @var string - */ - private const COLOR_RED = "\x1b[37;41m"; - - /** - * @var string - */ - private const COLOR_HEADER = "\x1b[1;37;40m"; - - /** - * @var string - */ - private const COLOR_RESET = "\x1b[0m"; - - /** - * @var string - */ - private const COLOR_EOL = "\x1b[2K"; - - /** - * @var int - */ - private $lowUpperBound; - - /** - * @var int - */ - private $highLowerBound; - - /** - * @var bool - */ - private $showUncoveredFiles; - - /** - * @var bool - */ - private $showOnlySummary; - - public function __construct(int $lowUpperBound = 50, int $highLowerBound = 90, bool $showUncoveredFiles = false, bool $showOnlySummary = false) + private const string COLOR_GREEN = "\x1b[30;42m"; + private const string COLOR_YELLOW = "\x1b[30;43m"; + private const string COLOR_RED = "\x1b[37;41m"; + private const string COLOR_HEADER = "\x1b[1;37;40m"; + private const string COLOR_RESET = "\x1b[0m"; + private readonly Thresholds $thresholds; + private readonly bool $showUncoveredFiles; + private readonly bool $showOnlySummary; + + public function __construct(Thresholds $thresholds, bool $showUncoveredFiles = false, bool $showOnlySummary = false) { - $this->lowUpperBound = $lowUpperBound; - $this->highLowerBound = $highLowerBound; + $this->thresholds = $thresholds; $this->showUncoveredFiles = $showUncoveredFiles; $this->showOnlySummary = $showOnlySummary; } public function process(CodeCoverage $coverage, bool $showColors = false): string { - $hasBranchCoverage = !empty($coverage->getData(true)->functionCoverage()); + $hasBranchCoverage = $coverage->getData(true)->functionCoverage() !== []; $output = PHP_EOL . PHP_EOL; $report = $coverage->getReport(); @@ -96,48 +54,46 @@ public function process(CodeCoverage $coverage, bool $showColors = false): strin 'branches' => '', 'paths' => '', 'reset' => '', - 'eol' => '', ]; if ($showColors) { $colors['classes'] = $this->coverageColor( $report->numberOfTestedClassesAndTraits(), - $report->numberOfClassesAndTraits() + $report->numberOfClassesAndTraits(), ); $colors['methods'] = $this->coverageColor( $report->numberOfTestedMethods(), - $report->numberOfMethods() + $report->numberOfMethods(), ); $colors['lines'] = $this->coverageColor( $report->numberOfExecutedLines(), - $report->numberOfExecutableLines() + $report->numberOfExecutableLines(), ); $colors['branches'] = $this->coverageColor( $report->numberOfExecutedBranches(), - $report->numberOfExecutableBranches() + $report->numberOfExecutableBranches(), ); $colors['paths'] = $this->coverageColor( $report->numberOfExecutedPaths(), - $report->numberOfExecutablePaths() + $report->numberOfExecutablePaths(), ); $colors['reset'] = self::COLOR_RESET; $colors['header'] = self::COLOR_HEADER; - $colors['eol'] = self::COLOR_EOL; } $classes = sprintf( ' Classes: %6s (%d/%d)', Percentage::fromFractionAndTotal( $report->numberOfTestedClassesAndTraits(), - $report->numberOfClassesAndTraits() + $report->numberOfClassesAndTraits(), )->asString(), $report->numberOfTestedClassesAndTraits(), - $report->numberOfClassesAndTraits() + $report->numberOfClassesAndTraits(), ); $methods = sprintf( @@ -147,7 +103,7 @@ public function process(CodeCoverage $coverage, bool $showColors = false): strin $report->numberOfMethods(), )->asString(), $report->numberOfTestedMethods(), - $report->numberOfMethods() + $report->numberOfMethods(), ); $paths = ''; @@ -161,7 +117,7 @@ public function process(CodeCoverage $coverage, bool $showColors = false): strin $report->numberOfExecutablePaths(), )->asString(), $report->numberOfExecutedPaths(), - $report->numberOfExecutablePaths() + $report->numberOfExecutablePaths(), ); $branches = sprintf( @@ -171,7 +127,7 @@ public function process(CodeCoverage $coverage, bool $showColors = false): strin $report->numberOfExecutableBranches(), )->asString(), $report->numberOfExecutedBranches(), - $report->numberOfExecutableBranches() + $report->numberOfExecutableBranches(), ); } @@ -182,7 +138,7 @@ public function process(CodeCoverage $coverage, bool $showColors = false): strin $report->numberOfExecutableLines(), )->asString(), $report->numberOfExecutedLines(), - $report->numberOfExecutableLines() + $report->numberOfExecutableLines(), ); $padding = max(array_map('strlen', [$classes, $methods, $lines])); @@ -235,18 +191,20 @@ public function process(CodeCoverage $coverage, bool $showColors = false): strin $classMethods = 0; foreach ($class['methods'] as $method) { + /** @phpstan-ignore equal.notAllowed */ if ($method['executableLines'] == 0) { continue; } $classMethods++; - $classExecutableLines += $method['executableLines']; - $classExecutedLines += $method['executedLines']; + $classExecutableLines += $method['executableLines']; + $classExecutedLines += $method['executedLines']; $classExecutableBranches += $method['executableBranches']; - $classExecutedBranches += $method['executedBranches']; - $classExecutablePaths += $method['executablePaths']; - $classExecutedPaths += $method['executedPaths']; + $classExecutedBranches += $method['executedBranches']; + $classExecutablePaths += $method['executablePaths']; + $classExecutedPaths += $method['executedPaths']; + /** @phpstan-ignore equal.notAllowed */ if ($method['coverage'] == 100) { $coveredMethods++; } @@ -276,6 +234,7 @@ public function process(CodeCoverage $coverage, bool $showColors = false): strin $resetColor = ''; foreach ($classCoverage as $fullQualifiedPath => $classInfo) { + /** @phpstan-ignore notEqual.notAllowed */ if ($this->showUncoveredFiles || $classInfo['statementsCovered'] != 0) { if ($showColors) { $methodColor = $this->coverageColor($classInfo['methodsCovered'], $classInfo['methodCount']); @@ -303,14 +262,14 @@ private function coverageColor(int $numberOfCoveredElements, int $totalNumberOfE { $coverage = Percentage::fromFractionAndTotal( $numberOfCoveredElements, - $totalNumberOfElements + $totalNumberOfElements, ); - if ($coverage->asFloat() >= $this->highLowerBound) { + if ($coverage->asFloat() >= $this->thresholds->highLowerBound()) { return self::COLOR_GREEN; } - if ($coverage->asFloat() > $this->lowUpperBound) { + if ($coverage->asFloat() > $this->thresholds->lowUpperBound()) { return self::COLOR_YELLOW; } @@ -323,19 +282,18 @@ private function printCoverageCounts(int $numberOfCoveredElements, int $totalNum return Percentage::fromFractionAndTotal( $numberOfCoveredElements, - $totalNumberOfElements + $totalNumberOfElements, )->asFixedWidthString() . ' (' . sprintf($format, $numberOfCoveredElements) . '/' . sprintf($format, $totalNumberOfElements) . ')'; } - /** - * @param false|string $string - */ - private function format(string $color, int $padding, $string): string + private function format(string $color, int $padding, false|string $string): string { - $reset = $color ? self::COLOR_RESET : ''; + if ($color === '') { + return (string) $string . PHP_EOL; + } - return $color . str_pad((string) $string, $padding) . $reset . PHP_EOL; + return $color . str_pad((string) $string, $padding) . self::COLOR_RESET . PHP_EOL; } } diff --git a/app/vendor/phpunit/php-code-coverage/src/Report/Thresholds.php b/app/vendor/phpunit/php-code-coverage/src/Report/Thresholds.php new file mode 100644 index 000000000..d1a81a260 --- /dev/null +++ b/app/vendor/phpunit/php-code-coverage/src/Report/Thresholds.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 SebastianBergmann\CodeCoverage\Report; + +use SebastianBergmann\CodeCoverage\InvalidArgumentException; + +/** + * @immutable + */ +final readonly class Thresholds +{ + private int $lowUpperBound; + private int $highLowerBound; + + public static function default(): self + { + return new self(50, 90); + } + + /** + * @throws InvalidArgumentException + */ + public static function from(int $lowUpperBound, int $highLowerBound): self + { + if ($lowUpperBound > $highLowerBound) { + throw new InvalidArgumentException( + '$lowUpperBound must not be larger than $highLowerBound', + ); + } + + return new self($lowUpperBound, $highLowerBound); + } + + private function __construct(int $lowUpperBound, int $highLowerBound) + { + $this->lowUpperBound = $lowUpperBound; + $this->highLowerBound = $highLowerBound; + } + + public function lowUpperBound(): int + { + return $this->lowUpperBound; + } + + public function highLowerBound(): int + { + return $this->highLowerBound; + } +} diff --git a/app/vendor/phpunit/php-code-coverage/src/Report/Xml/BuildInformation.php b/app/vendor/phpunit/php-code-coverage/src/Report/Xml/BuildInformation.php index ebdbae612..dba230123 100644 --- a/app/vendor/phpunit/php-code-coverage/src/Report/Xml/BuildInformation.php +++ b/app/vendor/phpunit/php-code-coverage/src/Report/Xml/BuildInformation.php @@ -9,7 +9,7 @@ */ namespace SebastianBergmann\CodeCoverage\Report\Xml; -use function constant; +use function assert; use function phpversion; use DateTimeImmutable; use DOMElement; @@ -18,12 +18,9 @@ /** * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ -final class BuildInformation +final readonly class BuildInformation { - /** - * @var DOMElement - */ - private $contextNode; + private DOMElement $contextNode; public function __construct(DOMElement $contextNode) { @@ -40,11 +37,6 @@ public function setRuntimeInformation(Runtime $runtime): void $driverNode = $this->nodeByName('driver'); - if ($runtime->hasPHPDBGCodeCoverage()) { - $driverNode->setAttribute('name', 'phpdbg'); - $driverNode->setAttribute('version', constant('PHPDBG_VERSION')); - } - if ($runtime->hasXdebug()) { $driverNode->setAttribute('name', 'xdebug'); $driverNode->setAttribute('version', phpversion('xdebug')); @@ -71,18 +63,20 @@ private function nodeByName(string $name): DOMElement { $node = $this->contextNode->getElementsByTagNameNS( 'https://schema.phpunit.de/coverage/1.0', - $name + $name, )->item(0); - if (!$node) { + if ($node === null) { $node = $this->contextNode->appendChild( $this->contextNode->ownerDocument->createElementNS( 'https://schema.phpunit.de/coverage/1.0', - $name - ) + $name, + ), ); } + assert($node instanceof DOMElement); + return $node; } } diff --git a/app/vendor/phpunit/php-code-coverage/src/Report/Xml/Coverage.php b/app/vendor/phpunit/php-code-coverage/src/Report/Xml/Coverage.php index b556d8205..afb70a069 100644 --- a/app/vendor/phpunit/php-code-coverage/src/Report/Xml/Coverage.php +++ b/app/vendor/phpunit/php-code-coverage/src/Report/Xml/Coverage.php @@ -18,20 +18,9 @@ */ final class Coverage { - /** - * @var XMLWriter - */ - private $writer; - - /** - * @var DOMElement - */ - private $contextNode; - - /** - * @var bool - */ - private $finalized = false; + private readonly XMLWriter $writer; + private readonly DOMElement $contextNode; + private bool $finalized = false; public function __construct(DOMElement $context, string $line) { @@ -39,7 +28,7 @@ public function __construct(DOMElement $context, string $line) $this->writer = new XMLWriter; $this->writer->openMemory(); - $this->writer->startElementNS(null, $context->nodeName, 'https://schema.phpunit.de/coverage/1.0'); + $this->writer->startElementNs(null, $context->nodeName, 'https://schema.phpunit.de/coverage/1.0'); $this->writer->writeAttribute('nr', $line); } @@ -49,7 +38,9 @@ public function __construct(DOMElement $context, string $line) public function addTest(string $test): void { if ($this->finalized) { + // @codeCoverageIgnoreStart throw new ReportAlreadyFinalizedException; + // @codeCoverageIgnoreEnd } $this->writer->startElement('covered'); @@ -66,7 +57,7 @@ public function finalize(): void $this->contextNode->parentNode->replaceChild( $fragment, - $this->contextNode + $this->contextNode, ); $this->finalized = true; diff --git a/app/vendor/phpunit/php-code-coverage/src/Report/Xml/Facade.php b/app/vendor/phpunit/php-code-coverage/src/Report/Xml/Facade.php index 3ecc7506f..ee2e8aa01 100644 --- a/app/vendor/phpunit/php-code-coverage/src/Report/Xml/Facade.php +++ b/app/vendor/phpunit/php-code-coverage/src/Report/Xml/Facade.php @@ -28,32 +28,28 @@ use DateTimeImmutable; use DOMDocument; use SebastianBergmann\CodeCoverage\CodeCoverage; -use SebastianBergmann\CodeCoverage\Driver\PathExistsButIsNotDirectoryException; -use SebastianBergmann\CodeCoverage\Driver\WriteOperationFailedException; use SebastianBergmann\CodeCoverage\Node\AbstractNode; use SebastianBergmann\CodeCoverage\Node\Directory as DirectoryNode; +use SebastianBergmann\CodeCoverage\Node\File; use SebastianBergmann\CodeCoverage\Node\File as FileNode; +use SebastianBergmann\CodeCoverage\PathExistsButIsNotDirectoryException; use SebastianBergmann\CodeCoverage\Util\Filesystem as DirectoryUtil; use SebastianBergmann\CodeCoverage\Version; +use SebastianBergmann\CodeCoverage\WriteOperationFailedException; use SebastianBergmann\CodeCoverage\XmlException; use SebastianBergmann\Environment\Runtime; +/** + * @phpstan-import-type ProcessedClassType from File + * @phpstan-import-type ProcessedTraitType from File + * @phpstan-import-type ProcessedFunctionType from File + * @phpstan-import-type TestType from CodeCoverage + */ final class Facade { - /** - * @var string - */ - private $target; - - /** - * @var Project - */ - private $project; - - /** - * @var string - */ - private $phpUnitVersion; + private string $target; + private Project $project; + private readonly string $phpUnitVersion; public function __construct(string $version) { @@ -75,7 +71,7 @@ public function process(CodeCoverage $coverage, string $target): void $report = $coverage->getReport(); $this->project = new Project( - $coverage->getReport()->name() + $coverage->getReport()->name(), ); $this->setBuildInformation(); @@ -100,6 +96,7 @@ private function setBuildInformation(): void private function initTargetDirectory(string $directory): void { if (is_file($directory)) { + // @codeCoverageIgnoreStart if (!is_dir($directory)) { throw new PathExistsButIsNotDirectoryException($directory); } @@ -107,6 +104,7 @@ private function initTargetDirectory(string $directory): void if (!is_writable($directory)) { throw new WriteOperationFailedException($directory); } + // @codeCoverageIgnoreEnd } DirectoryUtil::createDirectory($directory); @@ -143,14 +141,14 @@ private function processFile(FileNode $file, Directory $context): void { $fileObject = $context->addFile( $file->name(), - $file->id() . '.xml' + $file->id() . '.xml', ); $this->setTotals($file, $fileObject->totals()); $path = substr( $file->pathAsString(), - strlen($this->project->projectSourceDirectory()) + strlen($this->project->projectSourceDirectory()), ); $fileReport = new Report($path); @@ -180,12 +178,15 @@ private function processFile(FileNode $file, Directory $context): void } $fileReport->source()->setSourceCode( - file_get_contents($file->pathAsString()) + file_get_contents($file->pathAsString()), ); $this->saveDocument($fileReport->asDom(), $file->id()); } + /** + * @param ProcessedClassType|ProcessedTraitType $unit + */ private function processUnit(array $unit, Report $report): void { if (isset($unit['className'])) { @@ -197,7 +198,7 @@ private function processUnit(array $unit, Report $report): void $unitObject->setLines( $unit['startLine'], $unit['executableLines'], - $unit['executedLines'] + $unit['executedLines'], ); $unitObject->setCrap((float) $unit['crap']); @@ -211,11 +212,14 @@ private function processUnit(array $unit, Report $report): void $methodObject->setTotals( (string) $method['executableLines'], (string) $method['executedLines'], - (string) $method['coverage'] + (string) $method['coverage'], ); } } + /** + * @param ProcessedFunctionType $function + */ private function processFunction(array $function, Report $report): void { $functionObject = $report->functionObject($function['functionName']); @@ -226,6 +230,9 @@ private function processFunction(array $function, Report $report): void $functionObject->setTotals((string) $function['executableLines'], (string) $function['executedLines'], (string) $function['coverage']); } + /** + * @param array $tests + */ private function processTests(array $tests): void { $testsObject = $this->project->tests(); @@ -240,31 +247,31 @@ private function setTotals(AbstractNode $node, Totals $totals): void $loc = $node->linesOfCode(); $totals->setNumLines( - $loc['linesOfCode'], - $loc['commentLinesOfCode'], - $loc['nonCommentLinesOfCode'], + $loc->linesOfCode(), + $loc->commentLinesOfCode(), + $loc->nonCommentLinesOfCode(), $node->numberOfExecutableLines(), - $node->numberOfExecutedLines() + $node->numberOfExecutedLines(), ); $totals->setNumClasses( $node->numberOfClasses(), - $node->numberOfTestedClasses() + $node->numberOfTestedClasses(), ); $totals->setNumTraits( $node->numberOfTraits(), - $node->numberOfTestedTraits() + $node->numberOfTestedTraits(), ); $totals->setNumMethods( $node->numberOfMethods(), - $node->numberOfTestedMethods() + $node->numberOfTestedMethods(), ); $totals->setNumFunctions( $node->numberOfFunctions(), - $node->numberOfTestedFunctions() + $node->numberOfTestedFunctions(), ); } @@ -298,6 +305,7 @@ private function documentAsString(DOMDocument $document): string $xml = $document->saveXML(); if ($xml === false) { + // @codeCoverageIgnoreStart $message = 'Unable to generate the XML'; foreach (libxml_get_errors() as $error) { @@ -305,6 +313,7 @@ private function documentAsString(DOMDocument $document): string } throw new XmlException($message); + // @codeCoverageIgnoreEnd } libxml_clear_errors(); diff --git a/app/vendor/phpunit/php-code-coverage/src/Report/Xml/File.php b/app/vendor/phpunit/php-code-coverage/src/Report/Xml/File.php index 245c5cee6..4a3fea008 100644 --- a/app/vendor/phpunit/php-code-coverage/src/Report/Xml/File.php +++ b/app/vendor/phpunit/php-code-coverage/src/Report/Xml/File.php @@ -9,6 +9,7 @@ */ namespace SebastianBergmann\CodeCoverage\Report\Xml; +use function assert; use DOMDocument; use DOMElement; @@ -17,15 +18,8 @@ */ class File { - /** - * @var DOMDocument - */ - private $dom; - - /** - * @var DOMElement - */ - private $contextNode; + private readonly DOMDocument $dom; + private readonly DOMElement $contextNode; public function __construct(DOMElement $context) { @@ -37,15 +31,17 @@ public function totals(): Totals { $totalsContainer = $this->contextNode->firstChild; - if (!$totalsContainer) { + if ($totalsContainer === null) { $totalsContainer = $this->contextNode->appendChild( $this->dom->createElementNS( 'https://schema.phpunit.de/coverage/1.0', - 'totals' - ) + 'totals', + ), ); } + assert($totalsContainer instanceof DOMElement); + return new Totals($totalsContainer); } @@ -53,25 +49,27 @@ public function lineCoverage(string $line): Coverage { $coverage = $this->contextNode->getElementsByTagNameNS( 'https://schema.phpunit.de/coverage/1.0', - 'coverage' + 'coverage', )->item(0); - if (!$coverage) { + if ($coverage === null) { $coverage = $this->contextNode->appendChild( $this->dom->createElementNS( 'https://schema.phpunit.de/coverage/1.0', - 'coverage' - ) + 'coverage', + ), ); } $lineNode = $coverage->appendChild( $this->dom->createElementNS( 'https://schema.phpunit.de/coverage/1.0', - 'line' - ) + 'line', + ), ); + assert($lineNode instanceof DOMElement); + return new Coverage($lineNode, $line); } diff --git a/app/vendor/phpunit/php-code-coverage/src/Report/Xml/Method.php b/app/vendor/phpunit/php-code-coverage/src/Report/Xml/Method.php index 7e3009997..1994d0f79 100644 --- a/app/vendor/phpunit/php-code-coverage/src/Report/Xml/Method.php +++ b/app/vendor/phpunit/php-code-coverage/src/Report/Xml/Method.php @@ -14,12 +14,9 @@ /** * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ -final class Method +final readonly class Method { - /** - * @var DOMElement - */ - private $contextNode; + private DOMElement $contextNode; public function __construct(DOMElement $context, string $name) { diff --git a/app/vendor/phpunit/php-code-coverage/src/Report/Xml/Node.php b/app/vendor/phpunit/php-code-coverage/src/Report/Xml/Node.php index 159923093..e41197a08 100644 --- a/app/vendor/phpunit/php-code-coverage/src/Report/Xml/Node.php +++ b/app/vendor/phpunit/php-code-coverage/src/Report/Xml/Node.php @@ -9,6 +9,7 @@ */ namespace SebastianBergmann\CodeCoverage\Report\Xml; +use function assert; use DOMDocument; use DOMElement; @@ -17,15 +18,8 @@ */ abstract class Node { - /** - * @var DOMDocument - */ - private $dom; - - /** - * @var DOMElement - */ - private $contextNode; + private DOMDocument $dom; + private DOMElement $contextNode; public function __construct(DOMElement $context) { @@ -41,15 +35,17 @@ public function totals(): Totals { $totalsContainer = $this->contextNode()->firstChild; - if (!$totalsContainer) { + if ($totalsContainer === null) { $totalsContainer = $this->contextNode()->appendChild( $this->dom->createElementNS( 'https://schema.phpunit.de/coverage/1.0', - 'totals' - ) + 'totals', + ), ); } + assert($totalsContainer instanceof DOMElement); + return new Totals($totalsContainer); } @@ -57,7 +53,7 @@ public function addDirectory(string $name): Directory { $dirNode = $this->dom()->createElementNS( 'https://schema.phpunit.de/coverage/1.0', - 'directory' + 'directory', ); $dirNode->setAttribute('name', $name); @@ -70,7 +66,7 @@ public function addFile(string $name, string $href): File { $fileNode = $this->dom()->createElementNS( 'https://schema.phpunit.de/coverage/1.0', - 'file' + 'file', ); $fileNode->setAttribute('name', $name); diff --git a/app/vendor/phpunit/php-code-coverage/src/Report/Xml/Project.php b/app/vendor/phpunit/php-code-coverage/src/Report/Xml/Project.php index b566bce06..21b5a2ce1 100644 --- a/app/vendor/phpunit/php-code-coverage/src/Report/Xml/Project.php +++ b/app/vendor/phpunit/php-code-coverage/src/Report/Xml/Project.php @@ -9,13 +9,18 @@ */ namespace SebastianBergmann\CodeCoverage\Report\Xml; +use function assert; use DOMDocument; +use DOMElement; /** * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ final class Project extends Node { + /** + * @phpstan-ignore constructor.missingParentCall + */ public function __construct(string $directory) { $this->init(); @@ -31,18 +36,20 @@ public function buildInformation(): BuildInformation { $buildNode = $this->dom()->getElementsByTagNameNS( 'https://schema.phpunit.de/coverage/1.0', - 'build' + 'build', )->item(0); - if (!$buildNode) { + if ($buildNode === null) { $buildNode = $this->dom()->documentElement->appendChild( $this->dom()->createElementNS( 'https://schema.phpunit.de/coverage/1.0', - 'build' - ) + 'build', + ), ); } + assert($buildNode instanceof DOMElement); + return new BuildInformation($buildNode); } @@ -50,18 +57,20 @@ public function tests(): Tests { $testsNode = $this->contextNode()->getElementsByTagNameNS( 'https://schema.phpunit.de/coverage/1.0', - 'tests' + 'tests', )->item(0); - if (!$testsNode) { + if ($testsNode === null) { $testsNode = $this->contextNode()->appendChild( $this->dom()->createElementNS( 'https://schema.phpunit.de/coverage/1.0', - 'tests' - ) + 'tests', + ), ); } + assert($testsNode instanceof DOMElement); + return new Tests($testsNode); } @@ -78,8 +87,8 @@ private function init(): void $this->setContextNode( $dom->getElementsByTagNameNS( 'https://schema.phpunit.de/coverage/1.0', - 'project' - )->item(0) + 'project', + )->item(0), ); } diff --git a/app/vendor/phpunit/php-code-coverage/src/Report/Xml/Report.php b/app/vendor/phpunit/php-code-coverage/src/Report/Xml/Report.php index 7f2badaeb..f39ab860c 100644 --- a/app/vendor/phpunit/php-code-coverage/src/Report/Xml/Report.php +++ b/app/vendor/phpunit/php-code-coverage/src/Report/Xml/Report.php @@ -9,9 +9,11 @@ */ namespace SebastianBergmann\CodeCoverage\Report\Xml; +use function assert; use function basename; use function dirname; use DOMDocument; +use DOMElement; /** * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage @@ -25,7 +27,7 @@ public function __construct(string $name) $contextNode = $dom->getElementsByTagNameNS( 'https://schema.phpunit.de/coverage/1.0', - 'file' + 'file', )->item(0); parent::__construct($contextNode); @@ -38,24 +40,26 @@ public function asDom(): DOMDocument return $this->dom(); } - public function functionObject($name): Method + public function functionObject(string $name): Method { $node = $this->contextNode()->appendChild( $this->dom()->createElementNS( 'https://schema.phpunit.de/coverage/1.0', - 'function' - ) + 'function', + ), ); + assert($node instanceof DOMElement); + return new Method($node, $name); } - public function classObject($name): Unit + public function classObject(string $name): Unit { return $this->unitObject('class', $name); } - public function traitObject($name): Unit + public function traitObject(string $name): Unit { return $this->unitObject('trait', $name); } @@ -64,18 +68,20 @@ public function source(): Source { $source = $this->contextNode()->getElementsByTagNameNS( 'https://schema.phpunit.de/coverage/1.0', - 'source' + 'source', )->item(0); - if (!$source) { + if ($source === null) { $source = $this->contextNode()->appendChild( $this->dom()->createElementNS( 'https://schema.phpunit.de/coverage/1.0', - 'source' - ) + 'source', + ), ); } + assert($source instanceof DOMElement); + return new Source($source); } @@ -85,15 +91,17 @@ private function setName(string $name): void $this->contextNode()->setAttribute('path', dirname($name)); } - private function unitObject(string $tagName, $name): Unit + private function unitObject(string $tagName, string $name): Unit { $node = $this->contextNode()->appendChild( $this->dom()->createElementNS( 'https://schema.phpunit.de/coverage/1.0', - $tagName - ) + $tagName, + ), ); + assert($node instanceof DOMElement); + return new Unit($node, $name); } } diff --git a/app/vendor/phpunit/php-code-coverage/src/Report/Xml/Source.php b/app/vendor/phpunit/php-code-coverage/src/Report/Xml/Source.php index 2b67ce1da..448fe72d6 100644 --- a/app/vendor/phpunit/php-code-coverage/src/Report/Xml/Source.php +++ b/app/vendor/phpunit/php-code-coverage/src/Report/Xml/Source.php @@ -17,10 +17,9 @@ /** * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ -final class Source +final readonly class Source { - /** @var DOMElement */ - private $context; + private DOMElement $context; public function __construct(DOMElement $context) { @@ -36,7 +35,7 @@ public function setSourceCode(string $source): void $context->parentNode->replaceChild( $context->ownerDocument->importNode($srcDom->documentElement, true), - $context + $context, ); } } diff --git a/app/vendor/phpunit/php-code-coverage/src/Report/Xml/Tests.php b/app/vendor/phpunit/php-code-coverage/src/Report/Xml/Tests.php index c6da4145b..c9e9c48ef 100644 --- a/app/vendor/phpunit/php-code-coverage/src/Report/Xml/Tests.php +++ b/app/vendor/phpunit/php-code-coverage/src/Report/Xml/Tests.php @@ -9,42 +9,40 @@ */ namespace SebastianBergmann\CodeCoverage\Report\Xml; +use function assert; use DOMElement; +use SebastianBergmann\CodeCoverage\CodeCoverage; /** * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + * + * @phpstan-import-type TestType from CodeCoverage */ -final class Tests +final readonly class Tests { - private $contextNode; - private $codeMap = [ - -1 => 'UNKNOWN', // PHPUnit_Runner_BaseTestRunner::STATUS_UNKNOWN - 0 => 'PASSED', // PHPUnit_Runner_BaseTestRunner::STATUS_PASSED - 1 => 'SKIPPED', // PHPUnit_Runner_BaseTestRunner::STATUS_SKIPPED - 2 => 'INCOMPLETE', // PHPUnit_Runner_BaseTestRunner::STATUS_INCOMPLETE - 3 => 'FAILURE', // PHPUnit_Runner_BaseTestRunner::STATUS_FAILURE - 4 => 'ERROR', // PHPUnit_Runner_BaseTestRunner::STATUS_ERROR - 5 => 'RISKY', // PHPUnit_Runner_BaseTestRunner::STATUS_RISKY - 6 => 'WARNING', // PHPUnit_Runner_BaseTestRunner::STATUS_WARNING - ]; + private DOMElement $contextNode; public function __construct(DOMElement $context) { $this->contextNode = $context; } + /** + * @param TestType $result + */ public function addTest(string $test, array $result): void { $node = $this->contextNode->appendChild( $this->contextNode->ownerDocument->createElementNS( 'https://schema.phpunit.de/coverage/1.0', - 'test' - ) + 'test', + ), ); + assert($node instanceof DOMElement); + $node->setAttribute('name', $test); $node->setAttribute('size', $result['size']); - $node->setAttribute('result', (string) $result['status']); - $node->setAttribute('status', $this->codeMap[(int) $result['status']]); + $node->setAttribute('status', $result['status']); } } diff --git a/app/vendor/phpunit/php-code-coverage/src/Report/Xml/Totals.php b/app/vendor/phpunit/php-code-coverage/src/Report/Xml/Totals.php index 370813188..8e285a78e 100644 --- a/app/vendor/phpunit/php-code-coverage/src/Report/Xml/Totals.php +++ b/app/vendor/phpunit/php-code-coverage/src/Report/Xml/Totals.php @@ -11,72 +11,46 @@ use function sprintf; use DOMElement; -use DOMNode; use SebastianBergmann\CodeCoverage\Util\Percentage; /** * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ -final class Totals +final readonly class Totals { - /** - * @var DOMNode - */ - private $container; - - /** - * @var DOMElement - */ - private $linesNode; - - /** - * @var DOMElement - */ - private $methodsNode; - - /** - * @var DOMElement - */ - private $functionsNode; - - /** - * @var DOMElement - */ - private $classesNode; - - /** - * @var DOMElement - */ - private $traitsNode; + private DOMElement $linesNode; + private DOMElement $methodsNode; + private DOMElement $functionsNode; + private DOMElement $classesNode; + private DOMElement $traitsNode; public function __construct(DOMElement $container) { - $this->container = $container; - $dom = $container->ownerDocument; + $dom = $container->ownerDocument; $this->linesNode = $dom->createElementNS( 'https://schema.phpunit.de/coverage/1.0', - 'lines' + 'lines', ); $this->methodsNode = $dom->createElementNS( 'https://schema.phpunit.de/coverage/1.0', - 'methods' + 'methods', ); $this->functionsNode = $dom->createElementNS( 'https://schema.phpunit.de/coverage/1.0', - 'functions' + 'functions', ); $this->classesNode = $dom->createElementNS( 'https://schema.phpunit.de/coverage/1.0', - 'classes' + 'classes', ); $this->traitsNode = $dom->createElementNS( 'https://schema.phpunit.de/coverage/1.0', - 'traits' + 'traits', ); $container->appendChild($this->linesNode); @@ -86,11 +60,6 @@ public function __construct(DOMElement $container) $container->appendChild($this->traitsNode); } - public function container(): DOMNode - { - return $this->container; - } - public function setNumLines(int $loc, int $cloc, int $ncloc, int $executable, int $executed): void { $this->linesNode->setAttribute('total', (string) $loc); @@ -100,7 +69,7 @@ public function setNumLines(int $loc, int $cloc, int $ncloc, int $executable, in $this->linesNode->setAttribute('executed', (string) $executed); $this->linesNode->setAttribute( 'percent', - $executable === 0 ? '0' : sprintf('%01.2F', Percentage::fromFractionAndTotal($executed, $executable)->asFloat()) + $executable === 0 ? '0' : sprintf('%01.2F', Percentage::fromFractionAndTotal($executed, $executable)->asFloat()), ); } @@ -110,7 +79,7 @@ public function setNumClasses(int $count, int $tested): void $this->classesNode->setAttribute('tested', (string) $tested); $this->classesNode->setAttribute( 'percent', - $count === 0 ? '0' : sprintf('%01.2F', Percentage::fromFractionAndTotal($tested, $count)->asFloat()) + $count === 0 ? '0' : sprintf('%01.2F', Percentage::fromFractionAndTotal($tested, $count)->asFloat()), ); } @@ -120,7 +89,7 @@ public function setNumTraits(int $count, int $tested): void $this->traitsNode->setAttribute('tested', (string) $tested); $this->traitsNode->setAttribute( 'percent', - $count === 0 ? '0' : sprintf('%01.2F', Percentage::fromFractionAndTotal($tested, $count)->asFloat()) + $count === 0 ? '0' : sprintf('%01.2F', Percentage::fromFractionAndTotal($tested, $count)->asFloat()), ); } @@ -130,7 +99,7 @@ public function setNumMethods(int $count, int $tested): void $this->methodsNode->setAttribute('tested', (string) $tested); $this->methodsNode->setAttribute( 'percent', - $count === 0 ? '0' : sprintf('%01.2F', Percentage::fromFractionAndTotal($tested, $count)->asFloat()) + $count === 0 ? '0' : sprintf('%01.2F', Percentage::fromFractionAndTotal($tested, $count)->asFloat()), ); } @@ -140,7 +109,7 @@ public function setNumFunctions(int $count, int $tested): void $this->functionsNode->setAttribute('tested', (string) $tested); $this->functionsNode->setAttribute( 'percent', - $count === 0 ? '0' : sprintf('%01.2F', Percentage::fromFractionAndTotal($tested, $count)->asFloat()) + $count === 0 ? '0' : sprintf('%01.2F', Percentage::fromFractionAndTotal($tested, $count)->asFloat()), ); } } diff --git a/app/vendor/phpunit/php-code-coverage/src/Report/Xml/Unit.php b/app/vendor/phpunit/php-code-coverage/src/Report/Xml/Unit.php index d84dc481f..a00f85d39 100644 --- a/app/vendor/phpunit/php-code-coverage/src/Report/Xml/Unit.php +++ b/app/vendor/phpunit/php-code-coverage/src/Report/Xml/Unit.php @@ -9,17 +9,15 @@ */ namespace SebastianBergmann\CodeCoverage\Report\Xml; +use function assert; use DOMElement; /** * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ -final class Unit +final readonly class Unit { - /** - * @var DOMElement - */ - private $contextNode; + private DOMElement $contextNode; public function __construct(DOMElement $context, string $name) { @@ -44,18 +42,20 @@ public function setNamespace(string $namespace): void { $node = $this->contextNode->getElementsByTagNameNS( 'https://schema.phpunit.de/coverage/1.0', - 'namespace' + 'namespace', )->item(0); - if (!$node) { + if ($node === null) { $node = $this->contextNode->appendChild( $this->contextNode->ownerDocument->createElementNS( 'https://schema.phpunit.de/coverage/1.0', - 'namespace' - ) + 'namespace', + ), ); } + assert($node instanceof DOMElement); + $node->setAttribute('name', $namespace); } @@ -64,10 +64,12 @@ public function addMethod(string $name): Method $node = $this->contextNode->appendChild( $this->contextNode->ownerDocument->createElementNS( 'https://schema.phpunit.de/coverage/1.0', - 'method' - ) + 'method', + ), ); + assert($node instanceof DOMElement); + return new Method($node, $name); } diff --git a/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/CacheWarmer.php b/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/CacheWarmer.php index 6f8a04494..dcd0c61ec 100644 --- a/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/CacheWarmer.php +++ b/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/CacheWarmer.php @@ -9,24 +9,36 @@ */ namespace SebastianBergmann\CodeCoverage\StaticAnalysis; +use function file_get_contents; use SebastianBergmann\CodeCoverage\Filter; -final class CacheWarmer +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final readonly class CacheWarmer { - public function warmCache(string $cacheDirectory, bool $useAnnotationsForIgnoringCode, bool $ignoreDeprecatedCode, Filter $filter): void + /** + * @return array{cacheHits: non-negative-int, cacheMisses: non-negative-int} + */ + public function warmCache(string $cacheDirectory, bool $useAnnotationsForIgnoringCode, bool $ignoreDeprecatedCode, Filter $filter): array { - $analyser = new CachingFileAnalyser( + $analyser = new CachingSourceAnalyser( $cacheDirectory, - new ParsingFileAnalyser( - $useAnnotationsForIgnoringCode, - $ignoreDeprecatedCode - ), - $useAnnotationsForIgnoringCode, - $ignoreDeprecatedCode, + new ParsingSourceAnalyser, ); foreach ($filter->files() as $file) { - $analyser->process($file); + $analyser->analyse( + $file, + file_get_contents($file), + $useAnnotationsForIgnoringCode, + $ignoreDeprecatedCode, + ); } + + return [ + 'cacheHits' => $analyser->cacheHits(), + 'cacheMisses' => $analyser->cacheMisses(), + ]; } } diff --git a/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/CachingFileAnalyser.php b/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/CachingFileAnalyser.php deleted file mode 100644 index 63e6e22ba..000000000 --- a/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/CachingFileAnalyser.php +++ /dev/null @@ -1,209 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace SebastianBergmann\CodeCoverage\StaticAnalysis; - -use function file_get_contents; -use function file_put_contents; -use function implode; -use function is_file; -use function md5; -use function serialize; -use function unserialize; -use SebastianBergmann\CodeCoverage\Util\Filesystem; -use SebastianBergmann\FileIterator\Facade as FileIteratorFacade; - -/** - * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage - */ -final class CachingFileAnalyser implements FileAnalyser -{ - /** - * @var ?string - */ - private static $cacheVersion; - - /** - * @var string - */ - private $directory; - - /** - * @var FileAnalyser - */ - private $analyser; - - /** - * @var bool - */ - private $useAnnotationsForIgnoringCode; - - /** - * @var bool - */ - private $ignoreDeprecatedCode; - - /** - * @var array - */ - private $cache = []; - - public function __construct(string $directory, FileAnalyser $analyser, bool $useAnnotationsForIgnoringCode, bool $ignoreDeprecatedCode) - { - Filesystem::createDirectory($directory); - - $this->analyser = $analyser; - $this->directory = $directory; - $this->useAnnotationsForIgnoringCode = $useAnnotationsForIgnoringCode; - $this->ignoreDeprecatedCode = $ignoreDeprecatedCode; - } - - public function classesIn(string $filename): array - { - if (!isset($this->cache[$filename])) { - $this->process($filename); - } - - return $this->cache[$filename]['classesIn']; - } - - public function traitsIn(string $filename): array - { - if (!isset($this->cache[$filename])) { - $this->process($filename); - } - - return $this->cache[$filename]['traitsIn']; - } - - public function functionsIn(string $filename): array - { - if (!isset($this->cache[$filename])) { - $this->process($filename); - } - - return $this->cache[$filename]['functionsIn']; - } - - /** - * @psalm-return array{linesOfCode: int, commentLinesOfCode: int, nonCommentLinesOfCode: int} - */ - public function linesOfCodeFor(string $filename): array - { - if (!isset($this->cache[$filename])) { - $this->process($filename); - } - - return $this->cache[$filename]['linesOfCodeFor']; - } - - public function executableLinesIn(string $filename): array - { - if (!isset($this->cache[$filename])) { - $this->process($filename); - } - - return $this->cache[$filename]['executableLinesIn']; - } - - public function ignoredLinesFor(string $filename): array - { - if (!isset($this->cache[$filename])) { - $this->process($filename); - } - - return $this->cache[$filename]['ignoredLinesFor']; - } - - public function process(string $filename): void - { - $cache = $this->read($filename); - - if ($cache !== false) { - $this->cache[$filename] = $cache; - - return; - } - - $this->cache[$filename] = [ - 'classesIn' => $this->analyser->classesIn($filename), - 'traitsIn' => $this->analyser->traitsIn($filename), - 'functionsIn' => $this->analyser->functionsIn($filename), - 'linesOfCodeFor' => $this->analyser->linesOfCodeFor($filename), - 'ignoredLinesFor' => $this->analyser->ignoredLinesFor($filename), - 'executableLinesIn' => $this->analyser->executableLinesIn($filename), - ]; - - $this->write($filename, $this->cache[$filename]); - } - - /** - * @return mixed - */ - private function read(string $filename) - { - $cacheFile = $this->cacheFile($filename); - - if (!is_file($cacheFile)) { - return false; - } - - return unserialize( - file_get_contents($cacheFile), - ['allowed_classes' => false] - ); - } - - /** - * @param mixed $data - */ - private function write(string $filename, $data): void - { - file_put_contents( - $this->cacheFile($filename), - serialize($data) - ); - } - - private function cacheFile(string $filename): string - { - $cacheKey = md5( - implode( - "\0", - [ - $filename, - file_get_contents($filename), - self::cacheVersion(), - $this->useAnnotationsForIgnoringCode, - $this->ignoreDeprecatedCode, - ] - ) - ); - - return $this->directory . DIRECTORY_SEPARATOR . $cacheKey; - } - - private static function cacheVersion(): string - { - if (self::$cacheVersion !== null) { - return self::$cacheVersion; - } - - $buffer = []; - - foreach ((new FileIteratorFacade)->getFilesAsArray(__DIR__, '.php') as $file) { - $buffer[] = $file; - $buffer[] = file_get_contents($file); - } - - self::$cacheVersion = md5(implode("\0", $buffer)); - - return self::$cacheVersion; - } -} diff --git a/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/CachingSourceAnalyser.php b/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/CachingSourceAnalyser.php new file mode 100644 index 000000000..6a7b8001b --- /dev/null +++ b/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/CachingSourceAnalyser.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 SebastianBergmann\CodeCoverage\StaticAnalysis; + +use const DIRECTORY_SEPARATOR; +use function file_get_contents; +use function file_put_contents; +use function hash; +use function implode; +use function is_file; +use function serialize; +use function unserialize; +use SebastianBergmann\CodeCoverage\Util\Filesystem; +use SebastianBergmann\CodeCoverage\Version; + +/** + * @internal This interface is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class CachingSourceAnalyser implements SourceAnalyser +{ + /** + * @var non-empty-string + */ + private readonly string $directory; + private readonly SourceAnalyser $sourceAnalyser; + + /** + * @var non-negative-int + */ + private int $cacheHits = 0; + + /** + * @var non-negative-int + */ + private int $cacheMisses = 0; + + public function __construct(string $directory, SourceAnalyser $sourceAnalyser) + { + Filesystem::createDirectory($directory); + + $this->directory = $directory; + $this->sourceAnalyser = $sourceAnalyser; + } + + /** + * @param non-empty-string $sourceCodeFile + */ + public function analyse(string $sourceCodeFile, string $sourceCode, bool $useAnnotationsForIgnoringCode, bool $ignoreDeprecatedCode): AnalysisResult + { + $cacheFile = $this->cacheFile( + $sourceCode, + $useAnnotationsForIgnoringCode, + $ignoreDeprecatedCode, + ); + + $cachedAnalysisResult = $this->read($cacheFile); + + if ($cachedAnalysisResult !== false) { + $this->cacheHits++; + + return $cachedAnalysisResult; + } + + $this->cacheMisses++; + + $analysisResult = $this->sourceAnalyser->analyse( + $sourceCodeFile, + $sourceCode, + $useAnnotationsForIgnoringCode, + $ignoreDeprecatedCode, + ); + + $this->write($cacheFile, $analysisResult); + + return $analysisResult; + } + + /** + * @return non-negative-int + */ + public function cacheHits(): int + { + return $this->cacheHits; + } + + /** + * @return non-negative-int + */ + public function cacheMisses(): int + { + return $this->cacheMisses; + } + + /** + * @param non-empty-string $cacheFile + */ + private function read(string $cacheFile): AnalysisResult|false + { + if (!is_file($cacheFile)) { + return false; + } + + return unserialize( + file_get_contents($cacheFile), + [ + 'allowed_classes' => [ + AnalysisResult::class, + Class_::class, + Function_::class, + Interface_::class, + LinesOfCode::class, + Method::class, + Trait_::class, + ], + ], + ); + } + + /** + * @param non-empty-string $cacheFile + */ + private function write(string $cacheFile, AnalysisResult $result): void + { + file_put_contents( + $cacheFile, + serialize($result), + ); + } + + private function cacheFile(string $source, bool $useAnnotationsForIgnoringCode, bool $ignoreDeprecatedCode): string + { + $cacheKey = hash( + 'sha256', + implode( + "\0", + [ + $source, + Version::id(), + $useAnnotationsForIgnoringCode, + $ignoreDeprecatedCode, + ], + ), + ); + + return $this->directory . DIRECTORY_SEPARATOR . $cacheKey; + } +} diff --git a/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/CodeUnitFindingVisitor.php b/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/CodeUnitFindingVisitor.php deleted file mode 100644 index cb85cd61e..000000000 --- a/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/CodeUnitFindingVisitor.php +++ /dev/null @@ -1,345 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace SebastianBergmann\CodeCoverage\StaticAnalysis; - -use function assert; -use function implode; -use function rtrim; -use function trim; -use PhpParser\Node; -use PhpParser\Node\ComplexType; -use PhpParser\Node\Identifier; -use PhpParser\Node\IntersectionType; -use PhpParser\Node\Name; -use PhpParser\Node\NullableType; -use PhpParser\Node\Stmt\Class_; -use PhpParser\Node\Stmt\ClassMethod; -use PhpParser\Node\Stmt\Enum_; -use PhpParser\Node\Stmt\Function_; -use PhpParser\Node\Stmt\Interface_; -use PhpParser\Node\Stmt\Trait_; -use PhpParser\Node\UnionType; -use PhpParser\NodeAbstract; -use PhpParser\NodeTraverser; -use PhpParser\NodeVisitorAbstract; -use SebastianBergmann\Complexity\CyclomaticComplexityCalculatingVisitor; - -/** - * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage - */ -final class CodeUnitFindingVisitor extends NodeVisitorAbstract -{ - /** - * @psalm-var array}> - */ - private $classes = []; - - /** - * @psalm-var array}> - */ - private $traits = []; - - /** - * @psalm-var array - */ - private $functions = []; - - public function enterNode(Node $node): void - { - if ($node instanceof Class_) { - if ($node->isAnonymous()) { - return; - } - - $this->processClass($node); - } - - if ($node instanceof Trait_) { - $this->processTrait($node); - } - - if (!$node instanceof ClassMethod && !$node instanceof Function_) { - return; - } - - if ($node instanceof ClassMethod) { - $parentNode = $node->getAttribute('parent'); - - if ($parentNode instanceof Class_ && $parentNode->isAnonymous()) { - return; - } - - $this->processMethod($node); - - return; - } - - $this->processFunction($node); - } - - /** - * @psalm-return array}> - */ - public function classes(): array - { - return $this->classes; - } - - /** - * @psalm-return array}> - */ - public function traits(): array - { - return $this->traits; - } - - /** - * @psalm-return array - */ - public function functions(): array - { - return $this->functions; - } - - /** - * @psalm-param ClassMethod|Function_ $node - */ - private function cyclomaticComplexity(Node $node): int - { - assert($node instanceof ClassMethod || $node instanceof Function_); - - $nodes = $node->getStmts(); - - if ($nodes === null) { - return 0; - } - - $traverser = new NodeTraverser; - - $cyclomaticComplexityCalculatingVisitor = new CyclomaticComplexityCalculatingVisitor; - - $traverser->addVisitor($cyclomaticComplexityCalculatingVisitor); - - /* @noinspection UnusedFunctionResultInspection */ - $traverser->traverse($nodes); - - return $cyclomaticComplexityCalculatingVisitor->cyclomaticComplexity(); - } - - /** - * @psalm-param ClassMethod|Function_ $node - */ - private function signature(Node $node): string - { - assert($node instanceof ClassMethod || $node instanceof Function_); - - $signature = ($node->returnsByRef() ? '&' : '') . $node->name->toString() . '('; - $parameters = []; - - foreach ($node->getParams() as $parameter) { - assert(isset($parameter->var->name)); - - $parameterAsString = ''; - - if ($parameter->type !== null) { - $parameterAsString = $this->type($parameter->type) . ' '; - } - - $parameterAsString .= '$' . $parameter->var->name; - - /* @todo Handle default values */ - - $parameters[] = $parameterAsString; - } - - $signature .= implode(', ', $parameters) . ')'; - - $returnType = $node->getReturnType(); - - if ($returnType !== null) { - $signature .= ': ' . $this->type($returnType); - } - - return $signature; - } - - /** - * @psalm-param Identifier|Name|ComplexType $type - */ - private function type(Node $type): string - { - assert($type instanceof Identifier || $type instanceof Name || $type instanceof ComplexType); - - if ($type instanceof NullableType) { - return '?' . $type->type; - } - - if ($type instanceof UnionType) { - return $this->unionTypeAsString($type); - } - - if ($type instanceof IntersectionType) { - return $this->intersectionTypeAsString($type); - } - - return $type->toString(); - } - - private function visibility(ClassMethod $node): string - { - if ($node->isPrivate()) { - return 'private'; - } - - if ($node->isProtected()) { - return 'protected'; - } - - return 'public'; - } - - private function processClass(Class_ $node): void - { - $name = $node->name->toString(); - $namespacedName = $node->namespacedName->toString(); - - $this->classes[$namespacedName] = [ - 'name' => $name, - 'namespacedName' => $namespacedName, - 'namespace' => $this->namespace($namespacedName, $name), - 'startLine' => $node->getStartLine(), - 'endLine' => $node->getEndLine(), - 'methods' => [], - ]; - } - - private function processTrait(Trait_ $node): void - { - $name = $node->name->toString(); - $namespacedName = $node->namespacedName->toString(); - - $this->traits[$namespacedName] = [ - 'name' => $name, - 'namespacedName' => $namespacedName, - 'namespace' => $this->namespace($namespacedName, $name), - 'startLine' => $node->getStartLine(), - 'endLine' => $node->getEndLine(), - 'methods' => [], - ]; - } - - private function processMethod(ClassMethod $node): void - { - $parentNode = $node->getAttribute('parent'); - - if ($parentNode instanceof Interface_) { - return; - } - - assert($parentNode instanceof Class_ || $parentNode instanceof Trait_ || $parentNode instanceof Enum_); - assert(isset($parentNode->name)); - assert(isset($parentNode->namespacedName)); - assert($parentNode->namespacedName instanceof Name); - - $parentName = $parentNode->name->toString(); - $parentNamespacedName = $parentNode->namespacedName->toString(); - - if ($parentNode instanceof Class_) { - $storage = &$this->classes; - } else { - $storage = &$this->traits; - } - - if (!isset($storage[$parentNamespacedName])) { - $storage[$parentNamespacedName] = [ - 'name' => $parentName, - 'namespacedName' => $parentNamespacedName, - 'namespace' => $this->namespace($parentNamespacedName, $parentName), - 'startLine' => $parentNode->getStartLine(), - 'endLine' => $parentNode->getEndLine(), - 'methods' => [], - ]; - } - - $storage[$parentNamespacedName]['methods'][$node->name->toString()] = [ - 'methodName' => $node->name->toString(), - 'signature' => $this->signature($node), - 'visibility' => $this->visibility($node), - 'startLine' => $node->getStartLine(), - 'endLine' => $node->getEndLine(), - 'ccn' => $this->cyclomaticComplexity($node), - ]; - } - - private function processFunction(Function_ $node): void - { - assert(isset($node->name)); - assert(isset($node->namespacedName)); - assert($node->namespacedName instanceof Name); - - $name = $node->name->toString(); - $namespacedName = $node->namespacedName->toString(); - - $this->functions[$namespacedName] = [ - 'name' => $name, - 'namespacedName' => $namespacedName, - 'namespace' => $this->namespace($namespacedName, $name), - 'signature' => $this->signature($node), - 'startLine' => $node->getStartLine(), - 'endLine' => $node->getEndLine(), - 'ccn' => $this->cyclomaticComplexity($node), - ]; - } - - private function namespace(string $namespacedName, string $name): string - { - return trim(rtrim($namespacedName, $name), '\\'); - } - - private function unionTypeAsString(UnionType $node): string - { - $types = []; - - foreach ($node->types as $type) { - if ($type instanceof IntersectionType) { - $types[] = '(' . $this->intersectionTypeAsString($type) . ')'; - - continue; - } - - $types[] = $this->typeAsString($type); - } - - return implode('|', $types); - } - - private function intersectionTypeAsString(IntersectionType $node): string - { - $types = []; - - foreach ($node->types as $type) { - $types[] = $this->typeAsString($type); - } - - return implode('&', $types); - } - - /** - * @psalm-param Identifier|Name $node $node - */ - private function typeAsString(NodeAbstract $node): string - { - if ($node instanceof Name) { - return $node->toCodeString(); - } - - return $node->toString(); - } -} diff --git a/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/FileAnalyser.php b/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/FileAnalyser.php index 3dbcf68f6..3b1c5a42b 100644 --- a/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/FileAnalyser.php +++ b/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/FileAnalyser.php @@ -9,23 +9,45 @@ */ namespace SebastianBergmann\CodeCoverage\StaticAnalysis; +use function file_get_contents; + /** * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ -interface FileAnalyser +final class FileAnalyser { - public function classesIn(string $filename): array; + private readonly SourceAnalyser $sourceAnalyser; + private readonly bool $useAnnotationsForIgnoringCode; + private readonly bool $ignoreDeprecatedCode; - public function traitsIn(string $filename): array; + /** + * @var array + */ + private array $cache = []; - public function functionsIn(string $filename): array; + public function __construct(SourceAnalyser $sourceAnalyser, bool $useAnnotationsForIgnoringCode, bool $ignoreDeprecatedCode) + { + $this->sourceAnalyser = $sourceAnalyser; + $this->useAnnotationsForIgnoringCode = $useAnnotationsForIgnoringCode; + $this->ignoreDeprecatedCode = $ignoreDeprecatedCode; + } /** - * @psalm-return array{linesOfCode: int, commentLinesOfCode: int, nonCommentLinesOfCode: int} + * @param non-empty-string $sourceCodeFile */ - public function linesOfCodeFor(string $filename): array; + public function analyse(string $sourceCodeFile): AnalysisResult + { + if (isset($this->cache[$sourceCodeFile])) { + return $this->cache[$sourceCodeFile]; + } - public function executableLinesIn(string $filename): array; + $this->cache[$sourceCodeFile] = $this->sourceAnalyser->analyse( + $sourceCodeFile, + file_get_contents($sourceCodeFile), + $this->useAnnotationsForIgnoringCode, + $this->ignoreDeprecatedCode, + ); - public function ignoredLinesFor(string $filename): array; + return $this->cache[$sourceCodeFile]; + } } diff --git a/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/IgnoredLinesFindingVisitor.php b/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/IgnoredLinesFindingVisitor.php deleted file mode 100644 index 3c0b2373c..000000000 --- a/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/IgnoredLinesFindingVisitor.php +++ /dev/null @@ -1,119 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace SebastianBergmann\CodeCoverage\StaticAnalysis; - -use function array_merge; -use function assert; -use function range; -use function strpos; -use PhpParser\Node; -use PhpParser\Node\Attribute; -use PhpParser\Node\Stmt\Class_; -use PhpParser\Node\Stmt\ClassMethod; -use PhpParser\Node\Stmt\Function_; -use PhpParser\Node\Stmt\Interface_; -use PhpParser\Node\Stmt\Trait_; -use PhpParser\NodeVisitorAbstract; - -/** - * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage - */ -final class IgnoredLinesFindingVisitor extends NodeVisitorAbstract -{ - /** - * @psalm-var list - */ - private $ignoredLines = []; - - /** - * @var bool - */ - private $useAnnotationsForIgnoringCode; - - /** - * @var bool - */ - private $ignoreDeprecated; - - public function __construct(bool $useAnnotationsForIgnoringCode, bool $ignoreDeprecated) - { - $this->useAnnotationsForIgnoringCode = $useAnnotationsForIgnoringCode; - $this->ignoreDeprecated = $ignoreDeprecated; - } - - public function enterNode(Node $node): void - { - if (!$node instanceof Class_ && - !$node instanceof Trait_ && - !$node instanceof Interface_ && - !$node instanceof ClassMethod && - !$node instanceof Function_ && - !$node instanceof Attribute) { - return; - } - - if ($node instanceof Class_ && $node->isAnonymous()) { - return; - } - - if ($node instanceof Class_ || - $node instanceof Trait_ || - $node instanceof Interface_ || - $node instanceof Attribute) { - $this->ignoredLines[] = $node->getStartLine(); - - assert($node->name !== null); - - // Workaround for https://github.com/nikic/PHP-Parser/issues/886 - $this->ignoredLines[] = $node->name->getStartLine(); - } - - if (!$this->useAnnotationsForIgnoringCode) { - return; - } - - if ($node instanceof Interface_) { - return; - } - - $this->processDocComment($node); - } - - /** - * @psalm-return list - */ - public function ignoredLines(): array - { - return $this->ignoredLines; - } - - private function processDocComment(Node $node): void - { - $docComment = $node->getDocComment(); - - if ($docComment === null) { - return; - } - - if (strpos($docComment->getText(), '@codeCoverageIgnore') !== false) { - $this->ignoredLines = array_merge( - $this->ignoredLines, - range($node->getStartLine(), $node->getEndLine()) - ); - } - - if ($this->ignoreDeprecated && strpos($docComment->getText(), '@deprecated') !== false) { - $this->ignoredLines = array_merge( - $this->ignoredLines, - range($node->getStartLine(), $node->getEndLine()) - ); - } - } -} diff --git a/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/ParsingFileAnalyser.php b/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/ParsingFileAnalyser.php deleted file mode 100644 index 923b1f50f..000000000 --- a/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/ParsingFileAnalyser.php +++ /dev/null @@ -1,249 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace SebastianBergmann\CodeCoverage\StaticAnalysis; - -use function array_merge; -use function array_unique; -use function assert; -use function file_get_contents; -use function is_array; -use function max; -use function range; -use function sort; -use function sprintf; -use function substr_count; -use function token_get_all; -use function trim; -use PhpParser\Error; -use PhpParser\NodeTraverser; -use PhpParser\NodeVisitor\NameResolver; -use PhpParser\NodeVisitor\ParentConnectingVisitor; -use PhpParser\ParserFactory; -use SebastianBergmann\CodeCoverage\ParserException; -use SebastianBergmann\LinesOfCode\LineCountingVisitor; - -/** - * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage - */ -final class ParsingFileAnalyser implements FileAnalyser -{ - /** - * @var array - */ - private $classes = []; - - /** - * @var array - */ - private $traits = []; - - /** - * @var array - */ - private $functions = []; - - /** - * @var array - */ - private $linesOfCode = []; - - /** - * @var array - */ - private $ignoredLines = []; - - /** - * @var array - */ - private $executableLines = []; - - /** - * @var bool - */ - private $useAnnotationsForIgnoringCode; - - /** - * @var bool - */ - private $ignoreDeprecatedCode; - - public function __construct(bool $useAnnotationsForIgnoringCode, bool $ignoreDeprecatedCode) - { - $this->useAnnotationsForIgnoringCode = $useAnnotationsForIgnoringCode; - $this->ignoreDeprecatedCode = $ignoreDeprecatedCode; - } - - public function classesIn(string $filename): array - { - $this->analyse($filename); - - return $this->classes[$filename]; - } - - public function traitsIn(string $filename): array - { - $this->analyse($filename); - - return $this->traits[$filename]; - } - - public function functionsIn(string $filename): array - { - $this->analyse($filename); - - return $this->functions[$filename]; - } - - /** - * @psalm-return array{linesOfCode: int, commentLinesOfCode: int, nonCommentLinesOfCode: int} - */ - public function linesOfCodeFor(string $filename): array - { - $this->analyse($filename); - - return $this->linesOfCode[$filename]; - } - - public function executableLinesIn(string $filename): array - { - $this->analyse($filename); - - return $this->executableLines[$filename]; - } - - public function ignoredLinesFor(string $filename): array - { - $this->analyse($filename); - - return $this->ignoredLines[$filename]; - } - - /** - * @throws ParserException - */ - private function analyse(string $filename): void - { - if (isset($this->classes[$filename])) { - return; - } - - $source = file_get_contents($filename); - $linesOfCode = max(substr_count($source, "\n") + 1, substr_count($source, "\r") + 1); - - if ($linesOfCode === 0 && !empty($source)) { - $linesOfCode = 1; - } - - $parser = (new ParserFactory)->createForHostVersion(); - - try { - $nodes = $parser->parse($source); - - assert($nodes !== null); - - $traverser = new NodeTraverser; - $codeUnitFindingVisitor = new CodeUnitFindingVisitor; - $lineCountingVisitor = new LineCountingVisitor($linesOfCode); - $ignoredLinesFindingVisitor = new IgnoredLinesFindingVisitor($this->useAnnotationsForIgnoringCode, $this->ignoreDeprecatedCode); - $executableLinesFindingVisitor = new ExecutableLinesFindingVisitor($source); - - $traverser->addVisitor(new NameResolver); - $traverser->addVisitor(new ParentConnectingVisitor); - $traverser->addVisitor($codeUnitFindingVisitor); - $traverser->addVisitor($lineCountingVisitor); - $traverser->addVisitor($ignoredLinesFindingVisitor); - $traverser->addVisitor($executableLinesFindingVisitor); - - /* @noinspection UnusedFunctionResultInspection */ - $traverser->traverse($nodes); - // @codeCoverageIgnoreStart - } catch (Error $error) { - throw new ParserException( - sprintf( - 'Cannot parse %s: %s', - $filename, - $error->getMessage() - ), - $error->getCode(), - $error - ); - } - // @codeCoverageIgnoreEnd - - $this->classes[$filename] = $codeUnitFindingVisitor->classes(); - $this->traits[$filename] = $codeUnitFindingVisitor->traits(); - $this->functions[$filename] = $codeUnitFindingVisitor->functions(); - $this->executableLines[$filename] = $executableLinesFindingVisitor->executableLinesGroupedByBranch(); - $this->ignoredLines[$filename] = []; - - $this->findLinesIgnoredByLineBasedAnnotations($filename, $source, $this->useAnnotationsForIgnoringCode); - - $this->ignoredLines[$filename] = array_unique( - array_merge( - $this->ignoredLines[$filename], - $ignoredLinesFindingVisitor->ignoredLines() - ) - ); - - sort($this->ignoredLines[$filename]); - - $result = $lineCountingVisitor->result(); - - $this->linesOfCode[$filename] = [ - 'linesOfCode' => $result->linesOfCode(), - 'commentLinesOfCode' => $result->commentLinesOfCode(), - 'nonCommentLinesOfCode' => $result->nonCommentLinesOfCode(), - ]; - } - - private function findLinesIgnoredByLineBasedAnnotations(string $filename, string $source, bool $useAnnotationsForIgnoringCode): void - { - if (!$useAnnotationsForIgnoringCode) { - return; - } - - $start = false; - - foreach (token_get_all($source) as $token) { - if (!is_array($token) || - !(T_COMMENT === $token[0] || T_DOC_COMMENT === $token[0])) { - continue; - } - - $comment = trim($token[1]); - - if ($comment === '// @codeCoverageIgnore' || - $comment === '//@codeCoverageIgnore') { - $this->ignoredLines[$filename][] = $token[2]; - - continue; - } - - if ($comment === '// @codeCoverageIgnoreStart' || - $comment === '//@codeCoverageIgnoreStart') { - $start = $token[2]; - - continue; - } - - if ($comment === '// @codeCoverageIgnoreEnd' || - $comment === '//@codeCoverageIgnoreEnd') { - if (false === $start) { - $start = $token[2]; - } - - $this->ignoredLines[$filename] = array_merge( - $this->ignoredLines[$filename], - range($start, $token[2]) - ); - } - } - } -} diff --git a/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/ParsingSourceAnalyser.php b/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/ParsingSourceAnalyser.php new file mode 100644 index 000000000..7f46eeca6 --- /dev/null +++ b/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/ParsingSourceAnalyser.php @@ -0,0 +1,163 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\StaticAnalysis; + +use const T_COMMENT; +use const T_DOC_COMMENT; +use function array_merge; +use function array_unique; +use function assert; +use function is_array; +use function max; +use function range; +use function sort; +use function sprintf; +use function substr_count; +use function token_get_all; +use function trim; +use PhpParser\Error; +use PhpParser\NodeTraverser; +use PhpParser\NodeVisitor\NameResolver; +use PhpParser\ParserFactory; +use SebastianBergmann\CodeCoverage\ParserException; +use SebastianBergmann\LinesOfCode\LineCountingVisitor; + +/** + * @internal This interface is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final readonly class ParsingSourceAnalyser implements SourceAnalyser +{ + /** + * @param non-empty-string $sourceCodeFile + */ + public function analyse(string $sourceCodeFile, string $sourceCode, bool $useAnnotationsForIgnoringCode, bool $ignoreDeprecatedCode): AnalysisResult + { + $linesOfCode = max(substr_count($sourceCode, "\n") + 1, substr_count($sourceCode, "\r") + 1); + + if ($linesOfCode === 0 && $sourceCode !== '') { + $linesOfCode = 1; + } + + assert($linesOfCode > 0); + + $parser = (new ParserFactory)->createForHostVersion(); + + try { + $nodes = $parser->parse($sourceCode); + + assert($nodes !== null); + + $traverser = new NodeTraverser; + $codeUnitFindingVisitor = new CodeUnitFindingVisitor($sourceCodeFile); + $lineCountingVisitor = new LineCountingVisitor($linesOfCode); + $ignoredLinesFindingVisitor = new IgnoredLinesFindingVisitor($useAnnotationsForIgnoringCode, $ignoreDeprecatedCode); + $executableLinesFindingVisitor = new ExecutableLinesFindingVisitor($sourceCode); + + $traverser->addVisitor(new NameResolver); + $traverser->addVisitor(new AttributeParentConnectingVisitor); + $traverser->addVisitor($codeUnitFindingVisitor); + $traverser->addVisitor($lineCountingVisitor); + $traverser->addVisitor($ignoredLinesFindingVisitor); + $traverser->addVisitor($executableLinesFindingVisitor); + + /* @noinspection UnusedFunctionResultInspection */ + $traverser->traverse($nodes); + // @codeCoverageIgnoreStart + } catch (Error $error) { + throw new ParserException( + sprintf( + 'Cannot parse %s: %s', + $sourceCodeFile, + $error->getMessage(), + ), + $error->getCode(), + $error, + ); + } + // @codeCoverageIgnoreEnd + + $ignoredLines = array_unique( + array_merge( + $this->findLinesIgnoredByLineBasedAnnotations( + $sourceCodeFile, + $sourceCode, + $useAnnotationsForIgnoringCode, + ), + $ignoredLinesFindingVisitor->ignoredLines(), + ), + ); + + sort($ignoredLines); + + return new AnalysisResult( + $codeUnitFindingVisitor->interfaces(), + $codeUnitFindingVisitor->classes(), + $codeUnitFindingVisitor->traits(), + $codeUnitFindingVisitor->functions(), + new LinesOfCode( + $lineCountingVisitor->result()->linesOfCode(), + $lineCountingVisitor->result()->commentLinesOfCode(), + $lineCountingVisitor->result()->nonCommentLinesOfCode(), + ), + $executableLinesFindingVisitor->executableLinesGroupedByBranch(), + $ignoredLines, + ); + } + + /** + * @return array + */ + private function findLinesIgnoredByLineBasedAnnotations(string $filename, string $source, bool $useAnnotationsForIgnoringCode): array + { + if (!$useAnnotationsForIgnoringCode) { + return []; + } + + $result = []; + $start = false; + + foreach (token_get_all($source) as $token) { + if (!is_array($token) || + !(T_COMMENT === $token[0] || T_DOC_COMMENT === $token[0])) { + continue; + } + + $comment = trim($token[1]); + + if ($comment === '// @codeCoverageIgnore' || + $comment === '//@codeCoverageIgnore') { + $result[] = $token[2]; + + continue; + } + + if ($comment === '// @codeCoverageIgnoreStart' || + $comment === '//@codeCoverageIgnoreStart') { + $start = $token[2]; + + continue; + } + + if ($comment === '// @codeCoverageIgnoreEnd' || + $comment === '//@codeCoverageIgnoreEnd') { + if (false === $start) { + $start = $token[2]; + } + + $result = array_merge( + $result, + range($start, $token[2]), + ); + } + } + + return $result; + } +} diff --git a/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/SourceAnalyser.php b/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/SourceAnalyser.php new file mode 100644 index 000000000..bc18996ff --- /dev/null +++ b/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/SourceAnalyser.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 SebastianBergmann\CodeCoverage\StaticAnalysis; + +/** + * @internal This interface is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +interface SourceAnalyser +{ + /** + * @param non-empty-string $sourceCodeFile + */ + public function analyse(string $sourceCodeFile, string $sourceCode, bool $useAnnotationsForIgnoringCode, bool $ignoreDeprecatedCode): AnalysisResult; +} diff --git a/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/Value/AnalysisResult.php b/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/Value/AnalysisResult.php new file mode 100644 index 000000000..88fff2c46 --- /dev/null +++ b/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/Value/AnalysisResult.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 SebastianBergmann\CodeCoverage\StaticAnalysis; + +/** + * @phpstan-type LinesType array + * + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final readonly class AnalysisResult +{ + /** + * @var array + */ + private array $interfaces; + + /** + * @var array + */ + private array $classes; + + /** + * @var array + */ + private array $traits; + + /** + * @var array + */ + private array $functions; + private LinesOfCode $linesOfCode; + + /** + * @var LinesType + */ + private array $executableLines; + + /** + * @var LinesType + */ + private array $ignoredLines; + + /** + * @param array $interfaces + * @param array $classes + * @param array $traits + * @param array $functions + * @param LinesType $executableLines + * @param LinesType $ignoredLines + */ + public function __construct(array $interfaces, array $classes, array $traits, array $functions, LinesOfCode $linesOfCode, array $executableLines, array $ignoredLines) + { + $this->interfaces = $interfaces; + $this->classes = $classes; + $this->traits = $traits; + $this->functions = $functions; + $this->linesOfCode = $linesOfCode; + $this->executableLines = $executableLines; + $this->ignoredLines = $ignoredLines; + } + + /** + * @return array + */ + public function interfaces(): array + { + return $this->interfaces; + } + + /** + * @return array + */ + public function classes(): array + { + return $this->classes; + } + + /** + * @return array + */ + public function traits(): array + { + return $this->traits; + } + + /** + * @return array + */ + public function functions(): array + { + return $this->functions; + } + + public function linesOfCode(): LinesOfCode + { + return $this->linesOfCode; + } + + /** + * @return LinesType + */ + public function executableLines(): array + { + return $this->executableLines; + } + + /** + * @return LinesType + */ + public function ignoredLines(): array + { + return $this->ignoredLines; + } +} diff --git a/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/Value/Class_.php b/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/Value/Class_.php new file mode 100644 index 000000000..ee87b246e --- /dev/null +++ b/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/Value/Class_.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 SebastianBergmann\CodeCoverage\StaticAnalysis; + +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final readonly class Class_ +{ + /** + * @var non-empty-string + */ + private string $name; + + /** + * @var non-empty-string + */ + private string $namespacedName; + private string $namespace; + + /** + * @var non-empty-string + */ + private string $file; + + /** + * @var non-negative-int + */ + private int $startLine; + + /** + * @var non-negative-int + */ + private int $endLine; + + /** + * @var ?non-empty-string + */ + private ?string $parentClass; + + /** + * @var list + */ + private array $interfaces; + + /** + * @var list + */ + private array $traits; + + /** + * @var array + */ + private array $methods; + + /** + * @param non-empty-string $name + * @param non-empty-string $namespacedName + * @param non-empty-string $file + * @param non-negative-int $startLine + * @param non-negative-int $endLine + * @param ?non-empty-string $parentClass + * @param list $interfaces + * @param list $traits + * @param array $methods + */ + public function __construct(string $name, string $namespacedName, string $namespace, string $file, int $startLine, int $endLine, ?string $parentClass, array $interfaces, array $traits, array $methods) + { + $this->name = $name; + $this->namespacedName = $namespacedName; + $this->namespace = $namespace; + $this->file = $file; + $this->startLine = $startLine; + $this->endLine = $endLine; + $this->parentClass = $parentClass; + $this->interfaces = $interfaces; + $this->traits = $traits; + $this->methods = $methods; + } + + /** + * @return non-empty-string + */ + public function name(): string + { + return $this->name; + } + + /** + * @return non-empty-string + */ + public function namespacedName(): string + { + return $this->namespacedName; + } + + public function isNamespaced(): bool + { + return $this->namespace !== ''; + } + + public function namespace(): string + { + return $this->namespace; + } + + /** + * @return non-empty-string + */ + public function file(): string + { + return $this->file; + } + + /** + * @return non-negative-int + */ + public function startLine(): int + { + return $this->startLine; + } + + /** + * @return non-negative-int + */ + public function endLine(): int + { + return $this->endLine; + } + + public function hasParent(): bool + { + return $this->parentClass !== null; + } + + /** + * @return ?non-empty-string + */ + public function parentClass(): ?string + { + return $this->parentClass; + } + + /** + * @return list + */ + public function interfaces(): array + { + return $this->interfaces; + } + + /** + * @return list + */ + public function traits(): array + { + return $this->traits; + } + + /** + * @return array + */ + public function methods(): array + { + return $this->methods; + } +} diff --git a/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/Value/Function_.php b/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/Value/Function_.php new file mode 100644 index 000000000..7069dec99 --- /dev/null +++ b/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/Value/Function_.php @@ -0,0 +1,124 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\StaticAnalysis; + +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final readonly class Function_ +{ + /** + * @var non-empty-string + */ + private string $name; + + /** + * @var non-empty-string + */ + private string $namespacedName; + private string $namespace; + + /** + * @var non-negative-int + */ + private int $startLine; + + /** + * @var non-negative-int + */ + private int $endLine; + + /** + * @var non-empty-string + */ + private string $signature; + + /** + * @var positive-int + */ + private int $cyclomaticComplexity; + + /** + * @param non-empty-string $name + * @param non-empty-string $namespacedName + * @param non-negative-int $startLine + * @param non-negative-int $endLine + * @param non-empty-string $signature + * @param positive-int $cyclomaticComplexity + */ + public function __construct(string $name, string $namespacedName, string $namespace, int $startLine, int $endLine, string $signature, int $cyclomaticComplexity) + { + $this->name = $name; + $this->namespacedName = $namespacedName; + $this->namespace = $namespace; + $this->startLine = $startLine; + $this->endLine = $endLine; + $this->signature = $signature; + $this->cyclomaticComplexity = $cyclomaticComplexity; + } + + /** + * @return non-empty-string + */ + public function name(): string + { + return $this->name; + } + + /** + * @return non-empty-string + */ + public function namespacedName(): string + { + return $this->namespacedName; + } + + public function isNamespaced(): bool + { + return $this->namespace !== ''; + } + + public function namespace(): string + { + return $this->namespace; + } + + /** + * @return non-negative-int + */ + public function startLine(): int + { + return $this->startLine; + } + + /** + * @return non-negative-int + */ + public function endLine(): int + { + return $this->endLine; + } + + /** + * @return non-empty-string + */ + public function signature(): string + { + return $this->signature; + } + + /** + * @return positive-int + */ + public function cyclomaticComplexity(): int + { + return $this->cyclomaticComplexity; + } +} diff --git a/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/Value/Interface_.php b/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/Value/Interface_.php new file mode 100644 index 000000000..0b2579280 --- /dev/null +++ b/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/Value/Interface_.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 SebastianBergmann\CodeCoverage\StaticAnalysis; + +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final readonly class Interface_ +{ + /** + * @var non-empty-string + */ + private string $name; + + /** + * @var non-empty-string + */ + private string $namespacedName; + private string $namespace; + + /** + * @var non-negative-int + */ + private int $startLine; + + /** + * @var non-negative-int + */ + private int $endLine; + + /** + * @var list + */ + private array $parentInterfaces; + + /** + * @param non-empty-string $name + * @param non-empty-string $namespacedName + * @param non-negative-int $startLine + * @param non-negative-int $endLine + * @param list $parentInterfaces + */ + public function __construct(string $name, string $namespacedName, string $namespace, int $startLine, int $endLine, array $parentInterfaces) + { + $this->name = $name; + $this->namespacedName = $namespacedName; + $this->namespace = $namespace; + $this->startLine = $startLine; + $this->endLine = $endLine; + $this->parentInterfaces = $parentInterfaces; + } + + /** + * @return non-empty-string + */ + public function name(): string + { + return $this->name; + } + + /** + * @return non-empty-string + */ + public function namespacedName(): string + { + return $this->namespacedName; + } + + public function isNamespaced(): bool + { + return $this->namespace !== ''; + } + + public function namespace(): string + { + return $this->namespace; + } + + /** + * @return non-negative-int + */ + public function startLine(): int + { + return $this->startLine; + } + + /** + * @return non-negative-int + */ + public function endLine(): int + { + return $this->endLine; + } + + /** + * @return list + */ + public function parentInterfaces(): array + { + return $this->parentInterfaces; + } +} diff --git a/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/Value/LinesOfCode.php b/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/Value/LinesOfCode.php new file mode 100644 index 000000000..f8720e1a7 --- /dev/null +++ b/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/Value/LinesOfCode.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 SebastianBergmann\CodeCoverage\StaticAnalysis; + +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final readonly class LinesOfCode +{ + /** + * @var non-negative-int + */ + private int $linesOfCode; + + /** + * @var non-negative-int + */ + private int $commentLinesOfCode; + + /** + * @var non-negative-int + */ + private int $nonCommentLinesOfCode; + + /** + * @param non-negative-int $linesOfCode + * @param non-negative-int $commentLinesOfCode + * @param non-negative-int $nonCommentLinesOfCode + */ + public function __construct(int $linesOfCode, int $commentLinesOfCode, int $nonCommentLinesOfCode) + { + $this->linesOfCode = $linesOfCode; + $this->commentLinesOfCode = $commentLinesOfCode; + $this->nonCommentLinesOfCode = $nonCommentLinesOfCode; + } + + /** + * @return non-negative-int + */ + public function linesOfCode(): int + { + return $this->linesOfCode; + } + + /** + * @return non-negative-int + */ + public function commentLinesOfCode(): int + { + return $this->commentLinesOfCode; + } + + /** + * @return non-negative-int + */ + public function nonCommentLinesOfCode(): int + { + return $this->nonCommentLinesOfCode; + } +} diff --git a/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/Value/Method.php b/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/Value/Method.php new file mode 100644 index 000000000..12e3438eb --- /dev/null +++ b/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/Value/Method.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 SebastianBergmann\CodeCoverage\StaticAnalysis; + +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final readonly class Method +{ + /** + * @var non-empty-string + */ + private string $name; + + /** + * @var non-negative-int + */ + private int $startLine; + + /** + * @var non-negative-int + */ + private int $endLine; + private Visibility $visibility; + + /** + * @var non-empty-string + */ + private string $signature; + + /** + * @var positive-int + */ + private int $cyclomaticComplexity; + + /** + * @param non-empty-string $name + * @param non-negative-int $startLine + * @param non-negative-int $endLine + * @param non-empty-string $signature + * @param positive-int $cyclomaticComplexity + */ + public function __construct(string $name, int $startLine, int $endLine, string $signature, Visibility $visibility, int $cyclomaticComplexity) + { + $this->name = $name; + $this->startLine = $startLine; + $this->endLine = $endLine; + $this->signature = $signature; + $this->visibility = $visibility; + $this->cyclomaticComplexity = $cyclomaticComplexity; + } + + /** + * @return non-empty-string + */ + public function name(): string + { + return $this->name; + } + + /** + * @return non-negative-int + */ + public function startLine(): int + { + return $this->startLine; + } + + /** + * @return non-negative-int + */ + public function endLine(): int + { + return $this->endLine; + } + + /** + * @return non-empty-string + */ + public function signature(): string + { + return $this->signature; + } + + public function visibility(): Visibility + { + return $this->visibility; + } + + /** + * @return positive-int + */ + public function cyclomaticComplexity(): int + { + return $this->cyclomaticComplexity; + } +} diff --git a/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/Value/Trait_.php b/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/Value/Trait_.php new file mode 100644 index 000000000..7bf1084cd --- /dev/null +++ b/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/Value/Trait_.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 SebastianBergmann\CodeCoverage\StaticAnalysis; + +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final readonly class Trait_ +{ + /** + * @var non-empty-string + */ + private string $name; + + /** + * @var non-empty-string + */ + private string $namespacedName; + private string $namespace; + + /** + * @var non-empty-string + */ + private string $file; + + /** + * @var non-negative-int + */ + private int $startLine; + + /** + * @var non-negative-int + */ + private int $endLine; + + /** + * @var list + */ + private array $traits; + + /** + * @var array + */ + private array $methods; + + /** + * @param non-empty-string $name + * @param non-empty-string $namespacedName + * @param non-empty-string $file + * @param non-negative-int $startLine + * @param non-negative-int $endLine + * @param list $traits + * @param array $methods + */ + public function __construct(string $name, string $namespacedName, string $namespace, string $file, int $startLine, int $endLine, array $traits, array $methods) + { + $this->name = $name; + $this->namespacedName = $namespacedName; + $this->namespace = $namespace; + $this->file = $file; + $this->startLine = $startLine; + $this->endLine = $endLine; + $this->traits = $traits; + $this->methods = $methods; + } + + /** + * @return non-empty-string + */ + public function name(): string + { + return $this->name; + } + + /** + * @return non-empty-string + */ + public function namespacedName(): string + { + return $this->namespacedName; + } + + public function isNamespaced(): bool + { + return $this->namespace !== ''; + } + + public function namespace(): string + { + return $this->namespace; + } + + /** + * @return non-empty-string + */ + public function file(): string + { + return $this->file; + } + + /** + * @return non-negative-int + */ + public function startLine(): int + { + return $this->startLine; + } + + /** + * @return non-negative-int + */ + public function endLine(): int + { + return $this->endLine; + } + + /** + * @return list + */ + public function traits(): array + { + return $this->traits; + } + + /** + * @return array + */ + public function methods(): array + { + return $this->methods; + } +} diff --git a/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/Value/Visibility.php b/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/Value/Visibility.php new file mode 100644 index 000000000..c92797751 --- /dev/null +++ b/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/Value/Visibility.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 SebastianBergmann\CodeCoverage\StaticAnalysis; + +/** + * @internal This enumeration is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +enum Visibility: string +{ + case Public = 'public'; + case Protected = 'protected'; + case Private = 'private'; +} diff --git a/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/Visitor/AttributeParentConnectingVisitor.php b/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/Visitor/AttributeParentConnectingVisitor.php new file mode 100644 index 000000000..2e2141405 --- /dev/null +++ b/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/Visitor/AttributeParentConnectingVisitor.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 SebastianBergmann\CodeCoverage\StaticAnalysis; + +use function array_pop; +use function count; +use PhpParser\Node; +use PhpParser\NodeVisitor; + +/** + * Visitor that connects a child node to its parent node optimized for Attribute nodes. + * + * On the child node, the parent node can be accessed through + * $node->getAttribute('parent'). + * + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class AttributeParentConnectingVisitor implements NodeVisitor +{ + /** + * @var Node[] + */ + private array $stack = []; + + public function beforeTraverse(array $nodes): null + { + $this->stack = []; + + return null; + } + + public function enterNode(Node $node): null + { + if ($this->stack !== [] && + ($node instanceof Node\Attribute || $node instanceof Node\AttributeGroup)) { + $node->setAttribute('parent', $this->stack[count($this->stack) - 1]); + } + + $this->stack[] = $node; + + return null; + } + + public function leaveNode(Node $node): null + { + array_pop($this->stack); + + return null; + } + + public function afterTraverse(array $nodes): null + { + return null; + } +} diff --git a/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/Visitor/CodeUnitFindingVisitor.php b/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/Visitor/CodeUnitFindingVisitor.php new file mode 100644 index 000000000..558d3e053 --- /dev/null +++ b/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/Visitor/CodeUnitFindingVisitor.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 SebastianBergmann\CodeCoverage\StaticAnalysis; + +use function assert; +use function implode; +use function rtrim; +use function trim; +use PhpParser\Node; +use PhpParser\Node\ComplexType; +use PhpParser\Node\Identifier; +use PhpParser\Node\IntersectionType; +use PhpParser\Node\Name; +use PhpParser\Node\NullableType; +use PhpParser\Node\Stmt\Class_; +use PhpParser\Node\Stmt\ClassMethod; +use PhpParser\Node\Stmt\Enum_; +use PhpParser\Node\Stmt\Function_; +use PhpParser\Node\Stmt\Interface_; +use PhpParser\Node\Stmt\Trait_; +use PhpParser\Node\UnionType; +use PhpParser\NodeTraverser; +use PhpParser\NodeVisitorAbstract; +use SebastianBergmann\Complexity\CyclomaticComplexityCalculatingVisitor; + +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class CodeUnitFindingVisitor extends NodeVisitorAbstract +{ + /** + * @var non-empty-string + */ + private string $file; + + /** + * @var array + */ + private array $interfaces = []; + + /** + * @var array + */ + private array $classes = []; + + /** + * @var array + */ + private array $traits = []; + + /** + * @var array + */ + private array $functions = []; + + /** + * @param non-empty-string $file + */ + public function __construct(string $file) + { + $this->file = $file; + } + + public function enterNode(Node $node): null + { + if ($node instanceof Interface_) { + $this->processInterface($node); + } + + if ($node instanceof Class_) { + if ($node->isAnonymous()) { + return null; + } + + $this->processClass($node); + } + + if ($node instanceof Enum_) { + $this->processClass($node); + } + + if ($node instanceof Trait_) { + $this->processTrait($node); + } + + if (!$node instanceof Function_) { + return null; + } + + $this->processFunction($node); + + return null; + } + + public function leaveNode(Node $node): null + { + if ($node instanceof Class_ && $node->isAnonymous()) { + return null; + } + + if (!$node instanceof Class_ && !$node instanceof Trait_) { + return null; + } + + $traits = []; + + foreach ($node->getTraitUses() as $traitUse) { + foreach ($traitUse->traits as $trait) { + $traits[] = $trait->toString(); + } + } + + if ($traits === []) { + return null; + } + + $this->postProcessClassOrTrait($node, $traits); + + return null; + } + + /** + * @return array + */ + public function interfaces(): array + { + return $this->interfaces; + } + + /** + * @return array + */ + public function classes(): array + { + return $this->classes; + } + + /** + * @return array + */ + public function traits(): array + { + return $this->traits; + } + + /** + * @return array + */ + public function functions(): array + { + return $this->functions; + } + + private function cyclomaticComplexity(ClassMethod|Function_ $node): int + { + $nodes = $node->getStmts(); + + if ($nodes === null) { + return 0; + } + + $traverser = new NodeTraverser; + + $cyclomaticComplexityCalculatingVisitor = new CyclomaticComplexityCalculatingVisitor; + + $traverser->addVisitor($cyclomaticComplexityCalculatingVisitor); + + /* @noinspection UnusedFunctionResultInspection */ + $traverser->traverse($nodes); + + return $cyclomaticComplexityCalculatingVisitor->cyclomaticComplexity(); + } + + private function signature(ClassMethod|Function_ $node): string + { + $signature = ($node->returnsByRef() ? '&' : '') . $node->name->toString() . '('; + $parameters = []; + + foreach ($node->getParams() as $parameter) { + assert(isset($parameter->var->name)); + + $parameterAsString = ''; + + if ($parameter->type !== null) { + $parameterAsString = $this->type($parameter->type) . ' '; + } + + $parameterAsString .= '$' . $parameter->var->name; + + /* @todo Handle default values */ + + $parameters[] = $parameterAsString; + } + + $signature .= implode(', ', $parameters) . ')'; + + $returnType = $node->getReturnType(); + + if ($returnType !== null) { + $signature .= ': ' . $this->type($returnType); + } + + return $signature; + } + + private function type(ComplexType|Identifier|Name $type): string + { + if ($type instanceof NullableType) { + return '?' . $type->type; + } + + if ($type instanceof UnionType) { + return $this->unionTypeAsString($type); + } + + if ($type instanceof IntersectionType) { + return $this->intersectionTypeAsString($type); + } + + return $type->toString(); + } + + private function visibility(ClassMethod $node): Visibility + { + if ($node->isPrivate()) { + return Visibility::Private; + } + + if ($node->isProtected()) { + return Visibility::Protected; + } + + return Visibility::Public; + } + + private function processInterface(Interface_ $node): void + { + $name = $node->name->toString(); + $namespacedName = $node->namespacedName->toString(); + $parentInterfaces = []; + + foreach ($node->extends as $parentInterface) { + $parentInterfaces[] = $parentInterface->toString(); + } + + $this->interfaces[$namespacedName] = new \SebastianBergmann\CodeCoverage\StaticAnalysis\Interface_( + $name, + $namespacedName, + $this->namespace($namespacedName, $name), + $node->getStartLine(), + $node->getEndLine(), + $parentInterfaces, + ); + } + + private function processClass(Class_|Enum_ $node): void + { + $name = $node->name->toString(); + $namespacedName = $node->namespacedName->toString(); + $parentClass = null; + $interfaces = []; + + if (!$node instanceof Enum_) { + if ($node->extends instanceof Name) { + $parentClass = $node->extends->toString(); + } + + foreach ($node->implements as $interface) { + $interfaces[] = $interface->toString(); + } + } + + $this->classes[$namespacedName] = new \SebastianBergmann\CodeCoverage\StaticAnalysis\Class_( + $name, + $namespacedName, + $this->namespace($namespacedName, $name), + $this->file, + $node->getStartLine(), + $node->getEndLine(), + $parentClass, + $interfaces, + [], + $this->processMethods($node->getMethods()), + ); + } + + private function processTrait(Trait_ $node): void + { + $name = $node->name->toString(); + $namespacedName = $node->namespacedName->toString(); + + $this->traits[$namespacedName] = new \SebastianBergmann\CodeCoverage\StaticAnalysis\Trait_( + $name, + $namespacedName, + $this->namespace($namespacedName, $name), + $this->file, + $node->getStartLine(), + $node->getEndLine(), + [], + $this->processMethods($node->getMethods()), + ); + } + + /** + * @param list $nodes + * + * @return array + */ + private function processMethods(array $nodes): array + { + $methods = []; + + foreach ($nodes as $node) { + $methods[$node->name->toString()] = new Method( + $node->name->toString(), + $node->getStartLine(), + $node->getEndLine(), + $this->signature($node), + $this->visibility($node), + $this->cyclomaticComplexity($node), + ); + } + + return $methods; + } + + private function processFunction(Function_ $node): void + { + assert(isset($node->name)); + assert(isset($node->namespacedName)); + assert($node->namespacedName instanceof Name); + + $name = $node->name->toString(); + $namespacedName = $node->namespacedName->toString(); + + $this->functions[$namespacedName] = new \SebastianBergmann\CodeCoverage\StaticAnalysis\Function_( + $name, + $namespacedName, + $this->namespace($namespacedName, $name), + $node->getStartLine(), + $node->getEndLine(), + $this->signature($node), + $this->cyclomaticComplexity($node), + ); + } + + private function namespace(string $namespacedName, string $name): string + { + return trim(rtrim($namespacedName, $name), '\\'); + } + + private function unionTypeAsString(UnionType $node): string + { + $types = []; + + foreach ($node->types as $type) { + if ($type instanceof IntersectionType) { + $types[] = '(' . $this->intersectionTypeAsString($type) . ')'; + + continue; + } + + $types[] = $this->typeAsString($type); + } + + return implode('|', $types); + } + + private function intersectionTypeAsString(IntersectionType $node): string + { + $types = []; + + foreach ($node->types as $type) { + $types[] = $this->typeAsString($type); + } + + return implode('&', $types); + } + + private function typeAsString(Identifier|Name $node): string + { + if ($node instanceof Name) { + return $node->toCodeString(); + } + + return $node->toString(); + } + + /** + * @param list $traits + */ + private function postProcessClassOrTrait(Class_|Trait_ $node, array $traits): void + { + $name = $node->namespacedName->toString(); + + if ($node instanceof Class_) { + assert(isset($this->classes[$name])); + + $this->classes[$name] = new \SebastianBergmann\CodeCoverage\StaticAnalysis\Class_( + $this->classes[$name]->name(), + $this->classes[$name]->namespacedName(), + $this->classes[$name]->namespace(), + $this->classes[$name]->file(), + $this->classes[$name]->startLine(), + $this->classes[$name]->endLine(), + $this->classes[$name]->parentClass(), + $this->classes[$name]->interfaces(), + $traits, + $this->classes[$name]->methods(), + ); + + return; + } + + assert(isset($this->traits[$name])); + + $this->traits[$name] = new \SebastianBergmann\CodeCoverage\StaticAnalysis\Trait_( + $this->traits[$name]->name(), + $this->traits[$name]->namespacedName(), + $this->traits[$name]->namespace(), + $this->traits[$name]->file(), + $this->traits[$name]->startLine(), + $this->traits[$name]->endLine(), + $traits, + $this->traits[$name]->methods(), + ); + } +} diff --git a/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/ExecutableLinesFindingVisitor.php b/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/Visitor/ExecutableLinesFindingVisitor.php similarity index 82% rename from app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/ExecutableLinesFindingVisitor.php rename to app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/Visitor/ExecutableLinesFindingVisitor.php index 38e64c0b0..6a114e1b9 100644 --- a/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/ExecutableLinesFindingVisitor.php +++ b/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/Visitor/ExecutableLinesFindingVisitor.php @@ -26,40 +26,35 @@ /** * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + * + * @phpstan-import-type LinesType from AnalysisResult */ final class ExecutableLinesFindingVisitor extends NodeVisitorAbstract { - /** - * @var int - */ - private $nextBranch = 0; - - /** - * @var string - */ - private $source; + private int $nextBranch = 0; + private readonly string $source; /** - * @var array + * @var LinesType */ - private $executableLinesGroupedByBranch = []; + private array $executableLinesGroupedByBranch = []; /** * @var array */ - private $unsets = []; + private array $unsets = []; /** * @var array */ - private $commentsToCheckForUnset = []; + private array $commentsToCheckForUnset = []; public function __construct(string $source) { $this->source = $source; } - public function enterNode(Node $node): void + public function enterNode(Node $node): null { foreach ($node->getComments() as $comment) { $commentLine = $comment->getStartLine(); @@ -85,7 +80,7 @@ public function enterNode(Node $node): void } } - return; + return null; } if ($node instanceof Node\Stmt\Interface_) { @@ -93,7 +88,7 @@ public function enterNode(Node $node): void $this->unsets[$line] = true; } - return; + return null; } if ($node instanceof Node\Stmt\Declare_ || @@ -110,7 +105,6 @@ public function enterNode(Node $node): void $node instanceof Node\Stmt\Use_ || $node instanceof Node\Stmt\UseUse || $node instanceof Node\Expr\ConstFetch || - $node instanceof Node\Expr\Match_ || $node instanceof Node\Expr\Variable || $node instanceof Node\Expr\Throw_ || $node instanceof Node\ComplexType || @@ -119,28 +113,25 @@ public function enterNode(Node $node): void $node instanceof Node\Name || $node instanceof Node\Param || $node instanceof Node\Scalar) { - return; + return null; } - /* - * nikic/php-parser ^4.18 represents throw statements - * as Stmt\Throw_ objects - */ - if ($node instanceof Node\Stmt\Throw_) { - $this->setLineBranch($node->expr->getEndLine(), $node->expr->getEndLine(), ++$this->nextBranch); + if ($node instanceof Node\Expr\Match_) { + foreach ($node->arms as $arm) { + $this->setLineBranch( + $arm->body->getStartLine(), + $arm->body->getEndLine(), + ++$this->nextBranch, + ); + } - return; + return null; } - /* - * nikic/php-parser ^5 represents throw statements - * as Stmt\Expression objects that contain an - * Expr\Throw_ object - */ if ($node instanceof Node\Stmt\Expression && $node->expr instanceof Node\Expr\Throw_) { $this->setLineBranch($node->expr->expr->getEndLine(), $node->expr->expr->getEndLine(), ++$this->nextBranch); - return; + return null; } if ($node instanceof Node\Stmt\Enum_ || @@ -149,6 +140,20 @@ public function enterNode(Node $node): void $node instanceof Node\Stmt\ClassMethod || $node instanceof Node\Expr\Closure || $node instanceof Node\Stmt\Trait_) { + if ($node instanceof Node\Stmt\Function_ || $node instanceof Node\Stmt\ClassMethod) { + $unsets = []; + + foreach ($node->getParams() as $param) { + foreach (range($param->getStartLine(), $param->getEndLine()) as $line) { + $unsets[$line] = true; + } + } + + unset($unsets[$node->getEndLine()]); + + $this->unsets += $unsets; + } + $isConcreteClassLike = $node instanceof Node\Stmt\Enum_ || $node instanceof Node\Stmt\Class_ || $node instanceof Node\Stmt\Trait_; if (null !== $node->stmts) { @@ -171,7 +176,7 @@ public function enterNode(Node $node): void } if ($isConcreteClassLike) { - return; + return null; } $hasEmptyBody = [] === $node->stmts || @@ -182,33 +187,33 @@ public function enterNode(Node $node): void ); if ($hasEmptyBody) { - if ($node->getEndLine() === $node->getStartLine()) { - return; + if ($node->getEndLine() === $node->getStartLine() && isset($this->executableLinesGroupedByBranch[$node->getStartLine()])) { + return null; } $this->setLineBranch($node->getEndLine(), $node->getEndLine(), ++$this->nextBranch); - return; + return null; } - return; + return null; } if ($node instanceof Node\Expr\ArrowFunction) { $startLine = max( $node->getStartLine() + 1, - $node->expr->getStartLine() + $node->expr->getStartLine(), ); $endLine = $node->expr->getEndLine(); if ($endLine < $startLine) { - return; + return null; } $this->setLineBranch($startLine, $endLine, ++$this->nextBranch); - return; + return null; } if ($node instanceof Node\Expr\Ternary) { @@ -221,7 +226,7 @@ public function enterNode(Node $node): void $this->setLineBranch($node->else->getStartLine(), $node->else->getEndLine(), ++$this->nextBranch); } - return; + return null; } if ($node instanceof Node\Expr\BinaryOp\Coalesce) { @@ -229,23 +234,23 @@ public function enterNode(Node $node): void $this->setLineBranch($node->getEndLine(), $node->getEndLine(), ++$this->nextBranch); } - return; + return null; } if ($node instanceof Node\Stmt\If_ || $node instanceof Node\Stmt\ElseIf_ || $node instanceof Node\Stmt\Case_) { if (null === $node->cond) { - return; + return null; } $this->setLineBranch( $node->cond->getStartLine(), $node->cond->getStartLine(), - ++$this->nextBranch + ++$this->nextBranch, ); - return; + return null; } if ($node instanceof Node\Stmt\For_) { @@ -287,26 +292,26 @@ public function enterNode(Node $node): void } if (null === $startLine || null === $endLine) { - return; + return null; } $this->setLineBranch( $startLine, $endLine, - ++$this->nextBranch + ++$this->nextBranch, ); - return; + return null; } if ($node instanceof Node\Stmt\Foreach_) { $this->setLineBranch( $node->expr->getStartLine(), $node->valueVar->getEndLine(), - ++$this->nextBranch + ++$this->nextBranch, ); - return; + return null; } if ($node instanceof Node\Stmt\While_ || @@ -314,10 +319,10 @@ public function enterNode(Node $node): void $this->setLineBranch( $node->cond->getStartLine(), $node->cond->getEndLine(), - ++$this->nextBranch + ++$this->nextBranch, ); - return; + return null; } if ($node instanceof Node\Stmt\Catch_) { @@ -329,10 +334,10 @@ public function enterNode(Node $node): void $this->setLineBranch( $startLine, $endLine, - ++$this->nextBranch + ++$this->nextBranch, ); - return; + return null; } if ($node instanceof Node\Expr\CallLike) { @@ -344,17 +349,19 @@ public function enterNode(Node $node): void $this->setLineBranch($node->getStartLine(), $node->getEndLine(), $branch); - return; + return null; } if (isset($this->executableLinesGroupedByBranch[$node->getStartLine()])) { - return; + return null; } $this->setLineBranch($node->getStartLine(), $node->getEndLine(), ++$this->nextBranch); + + return null; } - public function afterTraverse(array $nodes): void + public function afterTraverse(array $nodes): null { $lines = explode("\n", $this->source); @@ -372,10 +379,15 @@ public function afterTraverse(array $nodes): void $this->executableLinesGroupedByBranch = array_diff_key( $this->executableLinesGroupedByBranch, - $this->unsets + $this->unsets, ); + + return null; } + /** + * @return LinesType + */ public function executableLinesGroupedByBranch(): array { return $this->executableLinesGroupedByBranch; diff --git a/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/Visitor/IgnoredLinesFindingVisitor.php b/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/Visitor/IgnoredLinesFindingVisitor.php new file mode 100644 index 000000000..fefe646c8 --- /dev/null +++ b/app/vendor/phpunit/php-code-coverage/src/StaticAnalysis/Visitor/IgnoredLinesFindingVisitor.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\StaticAnalysis; + +use function assert; +use function str_contains; +use PhpParser\Node; +use PhpParser\Node\Attribute; +use PhpParser\Node\Stmt\Class_; +use PhpParser\Node\Stmt\ClassMethod; +use PhpParser\Node\Stmt\Enum_; +use PhpParser\Node\Stmt\Function_; +use PhpParser\Node\Stmt\Interface_; +use PhpParser\Node\Stmt\Trait_; +use PhpParser\NodeVisitorAbstract; + +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class IgnoredLinesFindingVisitor extends NodeVisitorAbstract +{ + /** + * @var array + */ + private array $ignoredLines = []; + private readonly bool $useAnnotationsForIgnoringCode; + private readonly bool $ignoreDeprecated; + + public function __construct(bool $useAnnotationsForIgnoringCode, bool $ignoreDeprecated) + { + $this->useAnnotationsForIgnoringCode = $useAnnotationsForIgnoringCode; + $this->ignoreDeprecated = $ignoreDeprecated; + } + + public function enterNode(Node $node): null + { + if (!$node instanceof Class_ && + !$node instanceof Trait_ && + !$node instanceof Interface_ && + !$node instanceof Enum_ && + !$node instanceof ClassMethod && + !$node instanceof Function_ && + !$node instanceof Attribute) { + return null; + } + + if ($node instanceof Class_ && $node->isAnonymous()) { + return null; + } + + if ($node instanceof Class_ || + $node instanceof Trait_ || + $node instanceof Interface_ || + $node instanceof Attribute) { + $this->ignoredLines[] = $node->getStartLine(); + + assert($node->name !== null); + + // Workaround for https://github.com/nikic/PHP-Parser/issues/886 + $this->ignoredLines[] = $node->name->getStartLine(); + } + + if (!$this->useAnnotationsForIgnoringCode) { + return null; + } + + if ($node instanceof Interface_) { + return null; + } + + if ($node instanceof Attribute && + $node->name->toString() === 'PHPUnit\Framework\Attributes\CodeCoverageIgnore') { + $attributeGroup = $node->getAttribute('parent'); + $attributedNode = $attributeGroup->getAttribute('parent'); + + for ($line = $attributedNode->getStartLine(); $line <= $attributedNode->getEndLine(); $line++) { + $this->ignoredLines[] = $line; + } + + return null; + } + + $this->processDocComment($node); + + return null; + } + + /** + * @return array + */ + public function ignoredLines(): array + { + return $this->ignoredLines; + } + + private function processDocComment(Node $node): void + { + $docComment = $node->getDocComment(); + + if ($docComment === null) { + return; + } + + if (str_contains($docComment->getText(), '@codeCoverageIgnore')) { + for ($line = $node->getStartLine(); $line <= $node->getEndLine(); $line++) { + $this->ignoredLines[] = $line; + } + } + + if ($this->ignoreDeprecated && str_contains($docComment->getText(), '@deprecated')) { + for ($line = $node->getStartLine(); $line <= $node->getEndLine(); $line++) { + $this->ignoredLines[] = $line; + } + } + } +} diff --git a/app/vendor/phpunit/php-code-coverage/src/Target/Class_.php b/app/vendor/phpunit/php-code-coverage/src/Target/Class_.php new file mode 100644 index 000000000..9e6697802 --- /dev/null +++ b/app/vendor/phpunit/php-code-coverage/src/Target/Class_.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 SebastianBergmann\CodeCoverage\Test\Target; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class Class_ extends Target +{ + /** + * @var class-string + */ + private string $className; + + /** + * @param class-string $className + */ + protected function __construct(string $className) + { + $this->className = $className; + } + + public function isClass(): true + { + return true; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + /** + * @return non-empty-string + */ + public function key(): string + { + return 'classes'; + } + + /** + * @return non-empty-string + */ + public function target(): string + { + return $this->className; + } + + /** + * @return non-empty-string + */ + public function description(): string + { + return 'Class ' . $this->target(); + } +} diff --git a/app/vendor/phpunit/php-code-coverage/src/Target/ClassesThatExtendClass.php b/app/vendor/phpunit/php-code-coverage/src/Target/ClassesThatExtendClass.php new file mode 100644 index 000000000..94fdb56d8 --- /dev/null +++ b/app/vendor/phpunit/php-code-coverage/src/Target/ClassesThatExtendClass.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 SebastianBergmann\CodeCoverage\Test\Target; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class ClassesThatExtendClass extends Target +{ + /** + * @var class-string + */ + private string $className; + + /** + * @param class-string $className + */ + protected function __construct(string $className) + { + $this->className = $className; + } + + public function isClassesThatExtendClass(): true + { + return true; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + /** + * @return non-empty-string + */ + public function key(): string + { + return 'classesThatExtendClass'; + } + + /** + * @return non-empty-string + */ + public function target(): string + { + return $this->className; + } + + /** + * @return non-empty-string + */ + public function description(): string + { + return 'Classes that extend class ' . $this->target(); + } +} diff --git a/app/vendor/phpunit/php-code-coverage/src/Target/ClassesThatImplementInterface.php b/app/vendor/phpunit/php-code-coverage/src/Target/ClassesThatImplementInterface.php new file mode 100644 index 000000000..6bbe18efe --- /dev/null +++ b/app/vendor/phpunit/php-code-coverage/src/Target/ClassesThatImplementInterface.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 SebastianBergmann\CodeCoverage\Test\Target; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class ClassesThatImplementInterface extends Target +{ + /** + * @var class-string + */ + private string $interfaceName; + + /** + * @param class-string $interfaceName + */ + protected function __construct(string $interfaceName) + { + $this->interfaceName = $interfaceName; + } + + public function isClassesThatImplementInterface(): true + { + return true; + } + + /** + * @return class-string + */ + public function interfaceName(): string + { + return $this->interfaceName; + } + + /** + * @return non-empty-string + */ + public function key(): string + { + return 'classesThatImplementInterface'; + } + + /** + * @return non-empty-string + */ + public function target(): string + { + return $this->interfaceName; + } + + /** + * @return non-empty-string + */ + public function description(): string + { + return 'Classes that implement interface ' . $this->target(); + } +} diff --git a/app/vendor/phpunit/php-code-coverage/src/Target/Function_.php b/app/vendor/phpunit/php-code-coverage/src/Target/Function_.php new file mode 100644 index 000000000..1d7cdddc0 --- /dev/null +++ b/app/vendor/phpunit/php-code-coverage/src/Target/Function_.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 SebastianBergmann\CodeCoverage\Test\Target; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class Function_ extends Target +{ + /** + * @var non-empty-string + */ + private string $functionName; + + /** + * @param non-empty-string $functionName + */ + protected function __construct(string $functionName) + { + $this->functionName = $functionName; + } + + public function isFunction(): true + { + return true; + } + + /** + * @return non-empty-string + */ + public function functionName(): string + { + return $this->functionName; + } + + /** + * @return non-empty-string + */ + public function key(): string + { + return 'functions'; + } + + /** + * @return non-empty-string + */ + public function target(): string + { + return $this->functionName; + } + + /** + * @return non-empty-string + */ + public function description(): string + { + return 'Function ' . $this->target(); + } +} diff --git a/app/vendor/phpunit/php-code-coverage/src/Target/MapBuilder.php b/app/vendor/phpunit/php-code-coverage/src/Target/MapBuilder.php new file mode 100644 index 000000000..8e8d93fbb --- /dev/null +++ b/app/vendor/phpunit/php-code-coverage/src/Target/MapBuilder.php @@ -0,0 +1,274 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Test\Target; + +use function array_keys; +use function array_merge; +use function array_slice; +use function array_unique; +use function count; +use function explode; +use function implode; +use function range; +use SebastianBergmann\CodeCoverage\Filter; +use SebastianBergmann\CodeCoverage\StaticAnalysis\Class_; +use SebastianBergmann\CodeCoverage\StaticAnalysis\FileAnalyser; +use SebastianBergmann\CodeCoverage\StaticAnalysis\Trait_; + +/** + * @phpstan-import-type TargetMap from Mapper + * @phpstan-import-type TargetMapPart from Mapper + * + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for phpunit/php-code-coverage + * + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final readonly class MapBuilder +{ + /** + * @return TargetMap + */ + public function build(Filter $filter, FileAnalyser $analyser): array + { + /** + * @var array $classDetails + */ + $classDetails = []; + + $namespaces = []; + $classes = []; + $classesThatExtendClass = []; + $classesThatImplementInterface = []; + $traits = []; + $methods = []; + $functions = []; + $reverseLookup = []; + + foreach ($filter->files() as $file) { + foreach ($analyser->analyse($file)->traits() as $trait) { + if ($trait->isNamespaced()) { + $this->processNamespace($namespaces, $trait->namespace(), $file, $trait->startLine(), $trait->endLine()); + } + + $this->process($traits, $trait->namespacedName(), $file, $trait->startLine(), $trait->endLine()); + $this->processMethods($trait, $file, $methods, $reverseLookup); + } + } + + foreach ($filter->files() as $file) { + foreach ($analyser->analyse($file)->traits() as $trait) { + foreach ($trait->traits() as $traitName) { + if (!isset($traits[$traitName])) { + continue; + } + + $this->mergeLines($trait->namespacedName(), $traits[$traitName], $traits); + } + } + } + + foreach ($filter->files() as $file) { + $analysisResult = $analyser->analyse($file); + + foreach ($analysisResult->interfaces() as $interface) { + $classesThatImplementInterface[$interface->namespacedName()] = []; + } + + foreach ($analysisResult->classes() as $class) { + if ($class->isNamespaced()) { + $this->processNamespace($namespaces, $class->namespace(), $file, $class->startLine(), $class->endLine()); + } + + $this->process($classes, $class->namespacedName(), $file, $class->startLine(), $class->endLine()); + + foreach ($class->traits() as $traitName) { + if (!isset($traits[$traitName])) { + continue; + } + + $this->mergeLines($class->namespacedName(), $traits[$traitName], $classes); + } + + $this->processMethods($class, $file, $methods, $reverseLookup); + + $classesThatExtendClass[$class->namespacedName()] = []; + $classDetails[$class->namespacedName()] = $class; + } + + foreach ($analysisResult->functions() as $function) { + if ($function->isNamespaced()) { + $this->processNamespace($namespaces, $function->namespace(), $file, $function->startLine(), $function->endLine()); + } + + $this->process($functions, $function->namespacedName(), $file, $function->startLine(), $function->endLine()); + + foreach (range($function->startLine(), $function->endLine()) as $line) { + $reverseLookup[$file . ':' . $line] = $function->namespacedName(); + } + } + } + + foreach ($namespaces as $namespace => $files) { + foreach (array_keys($files) as $file) { + $namespaces[$namespace][$file] = array_unique($namespaces[$namespace][$file]); + } + } + + foreach ($classDetails as $class) { + foreach ($class->interfaces() as $interfaceName) { + if (!isset($classesThatImplementInterface[$interfaceName])) { + continue; + } + + $this->process($classesThatImplementInterface, $interfaceName, $class->file(), $class->startLine(), $class->endLine()); + } + + foreach ($this->parentClasses($classDetails, $class) as $parentClass) { + $this->mergeLines($class->namespacedName(), $classes[$parentClass->namespacedName()], $classes); + + if (isset($classesThatExtendClass[$parentClass->namespacedName()])) { + $this->process($classesThatExtendClass, $parentClass->namespacedName(), $class->file(), $class->startLine(), $class->endLine()); + } + } + } + + foreach (array_keys($classesThatImplementInterface) as $className) { + if ($classesThatImplementInterface[$className] !== []) { + continue; + } + + unset($classesThatImplementInterface[$className]); + } + + foreach (array_keys($classesThatExtendClass) as $className) { + if ($classesThatExtendClass[$className] !== []) { + continue; + } + + unset($classesThatExtendClass[$className]); + } + + return [ + 'namespaces' => $namespaces, + 'traits' => $traits, + 'classes' => $classes, + 'classesThatExtendClass' => $classesThatExtendClass, + 'classesThatImplementInterface' => $classesThatImplementInterface, + 'methods' => $methods, + 'functions' => $functions, + 'reverseLookup' => $reverseLookup, + ]; + } + + private function mergeLines(string $targetClass, array $sourceData, array &$data): void + { + /** + * In large inheritance trees we might handle a lot of data. + * This loop needs to prevent unnecessary work whenever possible. + */ + foreach ($sourceData as $file => $lines) { + if (!isset($data[$targetClass][$file])) { + $data[$targetClass][$file] = $lines; + + continue; + } + + if ($data[$targetClass][$file] === $lines) { + continue; + } + + $data[$targetClass][$file] = array_unique( + array_merge( + $data[$targetClass][$file], + $lines, + ), + ); + } + } + + private function processMethods(Class_|Trait_ $classOrTrait, string $file, array &$methods, array &$reverseLookup): void + { + foreach ($classOrTrait->methods() as $method) { + $methodName = $classOrTrait->namespacedName() . '::' . $method->name(); + + $this->process($methods, $methodName, $file, $method->startLine(), $method->endLine()); + + foreach (range($method->startLine(), $method->endLine()) as $line) { + $reverseLookup[$file . ':' . $line] = $methodName; + } + } + } + + /** + * @param TargetMapPart $data + * @param non-empty-string $namespace + * @param non-empty-string $file + * @param positive-int $startLine + * @param positive-int $endLine + * + * @param-out TargetMapPart $data + */ + private function processNamespace(array &$data, string $namespace, string $file, int $startLine, int $endLine): void + { + $parts = explode('\\', $namespace); + + foreach (range(1, count($parts)) as $i) { + $this->process($data, implode('\\', array_slice($parts, 0, $i)), $file, $startLine, $endLine); + } + } + + /** + * @param TargetMapPart $data + * @param non-empty-string $unit + * @param non-empty-string $file + * @param positive-int $startLine + * @param positive-int $endLine + * + * @param-out TargetMapPart $data + */ + private function process(array &$data, string $unit, string $file, int $startLine, int $endLine): void + { + if (!isset($data[$unit])) { + $data[$unit] = []; + } + + if (!isset($data[$unit][$file])) { + $data[$unit][$file] = []; + } + + $data[$unit][$file] = array_merge( + $data[$unit][$file], + range($startLine, $endLine), + ); + } + + /** + * @param array $classDetails + * + * @return array + */ + private function parentClasses(array $classDetails, Class_ $class): array + { + if (!$class->hasParent()) { + return []; + } + + if (!isset($classDetails[$class->parentClass()])) { + return []; + } + + return array_merge( + [$classDetails[$class->parentClass()]], + $this->parentClasses($classDetails, $classDetails[$class->parentClass()]), + ); + } +} diff --git a/app/vendor/phpunit/php-code-coverage/src/Target/Mapper.php b/app/vendor/phpunit/php-code-coverage/src/Target/Mapper.php new file mode 100644 index 000000000..50c243bec --- /dev/null +++ b/app/vendor/phpunit/php-code-coverage/src/Target/Mapper.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Test\Target; + +use function array_keys; +use function array_merge; +use function array_unique; +use function strcasecmp; + +/** + * @phpstan-type TargetMap array{namespaces: TargetMapPart, traits: TargetMapPart, classes: TargetMapPart, classesThatExtendClass: TargetMapPart, classesThatImplementInterface: TargetMapPart, methods: TargetMapPart, functions: TargetMapPart, reverseLookup: ReverseLookup} + * @phpstan-type TargetMapPart array>> + * @phpstan-type ReverseLookup array + * + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for phpunit/php-code-coverage + * + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final readonly class Mapper +{ + /** + * @var TargetMap + */ + private array $map; + + /** + * @param TargetMap $map + */ + public function __construct(array $map) + { + $this->map = $map; + } + + /** + * @return array> + */ + public function mapTargets(TargetCollection $targets): array + { + $result = []; + + foreach ($targets as $target) { + foreach ($this->mapTarget($target) as $file => $lines) { + if (!isset($result[$file])) { + $result[$file] = $lines; + + continue; + } + + $result[$file] = array_unique(array_merge($result[$file], $lines)); + } + } + + return $result; + } + + /** + * @throws InvalidCodeCoverageTargetException + * + * @return array> + */ + public function mapTarget(Target $target): array + { + if (isset($this->map[$target->key()][$target->target()])) { + return $this->map[$target->key()][$target->target()]; + } + + foreach (array_keys($this->map[$target->key()]) as $key) { + if (strcasecmp($key, $target->target()) === 0) { + return $this->map[$target->key()][$key]; + } + } + + throw new InvalidCodeCoverageTargetException($target); + } + + /** + * @param non-empty-string $file + * @param positive-int $line + * + * @return non-empty-string + */ + public function lookup(string $file, int $line): string + { + $key = $file . ':' . $line; + + if (isset($this->map['reverseLookup'][$key])) { + return $this->map['reverseLookup'][$key]; + } + + return $key; + } +} diff --git a/app/vendor/phpunit/php-code-coverage/src/Target/Method.php b/app/vendor/phpunit/php-code-coverage/src/Target/Method.php new file mode 100644 index 000000000..0b46ba90f --- /dev/null +++ b/app/vendor/phpunit/php-code-coverage/src/Target/Method.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 SebastianBergmann\CodeCoverage\Test\Target; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class Method extends Target +{ + /** + * @var class-string + */ + private string $className; + + /** + * @var non-empty-string + */ + private string $methodName; + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + protected function __construct(string $className, string $methodName) + { + $this->className = $className; + $this->methodName = $methodName; + } + + public function isMethod(): true + { + return true; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + /** + * @return non-empty-string + */ + public function methodName(): string + { + return $this->methodName; + } + + /** + * @return non-empty-string + */ + public function key(): string + { + return 'methods'; + } + + /** + * @return non-empty-string + */ + public function target(): string + { + return $this->className . '::' . $this->methodName; + } + + /** + * @return non-empty-string + */ + public function description(): string + { + return 'Method ' . $this->target(); + } +} diff --git a/app/vendor/phpunit/php-code-coverage/src/Target/Namespace_.php b/app/vendor/phpunit/php-code-coverage/src/Target/Namespace_.php new file mode 100644 index 000000000..651927d9f --- /dev/null +++ b/app/vendor/phpunit/php-code-coverage/src/Target/Namespace_.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 SebastianBergmann\CodeCoverage\Test\Target; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class Namespace_ extends Target +{ + /** + * @var non-empty-string + */ + private string $namespace; + + /** + * @param non-empty-string $namespace + */ + protected function __construct(string $namespace) + { + $this->namespace = $namespace; + } + + public function isNamespace(): true + { + return true; + } + + /** + * @return non-empty-string + */ + public function namespace(): string + { + return $this->namespace; + } + + /** + * @return non-empty-string + */ + public function key(): string + { + return 'namespaces'; + } + + /** + * @return non-empty-string + */ + public function target(): string + { + return $this->namespace; + } + + /** + * @return non-empty-string + */ + public function description(): string + { + return 'Namespace ' . $this->target(); + } +} diff --git a/app/vendor/phpunit/php-code-coverage/src/Target/Target.php b/app/vendor/phpunit/php-code-coverage/src/Target/Target.php new file mode 100644 index 000000000..7432c81a9 --- /dev/null +++ b/app/vendor/phpunit/php-code-coverage/src/Target/Target.php @@ -0,0 +1,125 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Test\Target; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +abstract class Target +{ + /** + * @param non-empty-string $namespace + */ + public static function forNamespace(string $namespace): Namespace_ + { + return new Namespace_($namespace); + } + + /** + * @param class-string $className + */ + public static function forClass(string $className): Class_ + { + return new Class_($className); + } + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public static function forMethod(string $className, string $methodName): Method + { + return new Method($className, $methodName); + } + + /** + * @param class-string $interfaceName + */ + public static function forClassesThatImplementInterface(string $interfaceName): ClassesThatImplementInterface + { + return new ClassesThatImplementInterface($interfaceName); + } + + /** + * @param class-string $className + */ + public static function forClassesThatExtendClass(string $className): ClassesThatExtendClass + { + return new ClassesThatExtendClass($className); + } + + /** + * @param non-empty-string $functionName + */ + public static function forFunction(string $functionName): Function_ + { + return new Function_($functionName); + } + + /** + * @param trait-string $traitName + */ + public static function forTrait(string $traitName): Trait_ + { + return new Trait_($traitName); + } + + public function isNamespace(): bool + { + return false; + } + + public function isClass(): bool + { + return false; + } + + public function isMethod(): bool + { + return false; + } + + public function isClassesThatImplementInterface(): bool + { + return false; + } + + public function isClassesThatExtendClass(): bool + { + return false; + } + + public function isFunction(): bool + { + return false; + } + + public function isTrait(): bool + { + return false; + } + + /** + * @return non-empty-string + */ + abstract public function key(): string; + + /** + * @return non-empty-string + */ + abstract public function target(): string; + + /** + * @return non-empty-string + */ + abstract public function description(): string; +} diff --git a/app/vendor/phpunit/php-code-coverage/src/Target/TargetCollection.php b/app/vendor/phpunit/php-code-coverage/src/Target/TargetCollection.php new file mode 100644 index 000000000..ef6e32ac8 --- /dev/null +++ b/app/vendor/phpunit/php-code-coverage/src/Target/TargetCollection.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 SebastianBergmann\CodeCoverage\Test\Target; + +use function count; +use Countable; +use IteratorAggregate; + +/** + * @template-implements IteratorAggregate + * + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final readonly class TargetCollection implements Countable, IteratorAggregate +{ + /** + * @var list + */ + private array $targets; + + /** + * @param list $targets + */ + public static function fromArray(array $targets): self + { + return new self(...$targets); + } + + private function __construct(Target ...$targets) + { + $this->targets = $targets; + } + + /** + * @return list + */ + public function asArray(): array + { + return $this->targets; + } + + public function count(): int + { + return count($this->targets); + } + + public function isEmpty(): bool + { + return $this->count() === 0; + } + + public function isNotEmpty(): bool + { + return $this->count() > 0; + } + + public function getIterator(): TargetCollectionIterator + { + return new TargetCollectionIterator($this); + } +} diff --git a/app/vendor/phpunit/php-code-coverage/src/Target/TargetCollectionIterator.php b/app/vendor/phpunit/php-code-coverage/src/Target/TargetCollectionIterator.php new file mode 100644 index 000000000..9a5ca06d9 --- /dev/null +++ b/app/vendor/phpunit/php-code-coverage/src/Target/TargetCollectionIterator.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 SebastianBergmann\CodeCoverage\Test\Target; + +use function count; +use Iterator; + +/** + * @template-implements Iterator + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class TargetCollectionIterator implements Iterator +{ + /** + * @var list + */ + private readonly array $targets; + private int $position = 0; + + public function __construct(TargetCollection $metadata) + { + $this->targets = $metadata->asArray(); + } + + public function rewind(): void + { + $this->position = 0; + } + + public function valid(): bool + { + return $this->position < count($this->targets); + } + + public function key(): int + { + return $this->position; + } + + public function current(): Target + { + return $this->targets[$this->position]; + } + + public function next(): void + { + $this->position++; + } +} diff --git a/app/vendor/phpunit/php-code-coverage/src/Target/TargetCollectionValidator.php b/app/vendor/phpunit/php-code-coverage/src/Target/TargetCollectionValidator.php new file mode 100644 index 000000000..370341438 --- /dev/null +++ b/app/vendor/phpunit/php-code-coverage/src/Target/TargetCollectionValidator.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 SebastianBergmann\CodeCoverage\Test\Target; + +use function implode; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for phpunit/php-code-coverage + * + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final readonly class TargetCollectionValidator +{ + public function validate(Mapper $mapper, TargetCollection $targets): ValidationResult + { + $errors = []; + + foreach ($targets as $target) { + try { + $mapper->mapTarget($target); + } catch (InvalidCodeCoverageTargetException $e) { + $errors[] = $e->getMessage(); + } + } + + if ($errors === []) { + return ValidationResult::success(); + } + + return ValidationResult::failure(implode("\n", $errors)); + } +} diff --git a/app/vendor/phpunit/php-code-coverage/src/Target/Trait_.php b/app/vendor/phpunit/php-code-coverage/src/Target/Trait_.php new file mode 100644 index 000000000..a698a18ae --- /dev/null +++ b/app/vendor/phpunit/php-code-coverage/src/Target/Trait_.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 SebastianBergmann\CodeCoverage\Test\Target; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final class Trait_ extends Target +{ + /** + * @var trait-string + */ + private string $traitName; + + /** + * @param trait-string $traitName + */ + protected function __construct(string $traitName) + { + $this->traitName = $traitName; + } + + public function isTrait(): true + { + return true; + } + + /** + * @return trait-string + */ + public function traitName(): string + { + return $this->traitName; + } + + /** + * @return non-empty-string + */ + public function key(): string + { + return 'traits'; + } + + /** + * @return non-empty-string + */ + public function target(): string + { + return $this->traitName; + } + + /** + * @return non-empty-string + */ + public function description(): string + { + return 'Trait ' . $this->target(); + } +} diff --git a/app/vendor/phpunit/php-code-coverage/src/Target/ValidationFailure.php b/app/vendor/phpunit/php-code-coverage/src/Target/ValidationFailure.php new file mode 100644 index 000000000..e43791f5a --- /dev/null +++ b/app/vendor/phpunit/php-code-coverage/src/Target/ValidationFailure.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 SebastianBergmann\CodeCoverage\Test\Target; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final readonly class ValidationFailure extends ValidationResult +{ + /** + * @var non-empty-string + */ + private string $message; + + /** + * @param non-empty-string $message + * + * @noinspection PhpMissingParentConstructorInspection + */ + protected function __construct(string $message) + { + $this->message = $message; + } + + public function isFailure(): true + { + return true; + } + + /** + * @return non-empty-string + */ + public function message(): string + { + return $this->message; + } +} diff --git a/app/vendor/phpunit/php-code-coverage/src/Target/ValidationResult.php b/app/vendor/phpunit/php-code-coverage/src/Target/ValidationResult.php new file mode 100644 index 000000000..8abf5112c --- /dev/null +++ b/app/vendor/phpunit/php-code-coverage/src/Target/ValidationResult.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 SebastianBergmann\CodeCoverage\Test\Target; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +abstract readonly class ValidationResult +{ + public static function success(): ValidationSuccess + { + return new ValidationSuccess; + } + + /** + * @param non-empty-string $message + */ + public static function failure(string $message): ValidationFailure + { + return new ValidationFailure($message); + } + + /** + * @phpstan-assert-if-true ValidationSuccess $this + */ + public function isSuccess(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true ValidationFailure $this + */ + public function isFailure(): bool + { + return false; + } +} diff --git a/app/vendor/phpunit/php-code-coverage/src/Target/ValidationSuccess.php b/app/vendor/phpunit/php-code-coverage/src/Target/ValidationSuccess.php new file mode 100644 index 000000000..1dffd0d2d --- /dev/null +++ b/app/vendor/phpunit/php-code-coverage/src/Target/ValidationSuccess.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 SebastianBergmann\CodeCoverage\Test\Target; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for phpunit/php-code-coverage + */ +final readonly class ValidationSuccess extends ValidationResult +{ + public function isSuccess(): true + { + return true; + } +} diff --git a/app/vendor/phpunit/php-code-coverage/src/TestSize/Known.php b/app/vendor/phpunit/php-code-coverage/src/TestSize/Known.php new file mode 100644 index 000000000..2bd72abe2 --- /dev/null +++ b/app/vendor/phpunit/php-code-coverage/src/TestSize/Known.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 SebastianBergmann\CodeCoverage\Test\TestSize; + +/** + * @immutable + */ +abstract class Known extends TestSize +{ + public function isKnown(): true + { + return true; + } + + abstract public function isGreaterThan(self $other): bool; +} diff --git a/app/vendor/phpunit/php-code-coverage/src/TestSize/Large.php b/app/vendor/phpunit/php-code-coverage/src/TestSize/Large.php new file mode 100644 index 000000000..f6a1b9532 --- /dev/null +++ b/app/vendor/phpunit/php-code-coverage/src/TestSize/Large.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 SebastianBergmann\CodeCoverage\Test\TestSize; + +/** + * @immutable + */ +final class Large extends Known +{ + public function isLarge(): true + { + return true; + } + + public function isGreaterThan(TestSize $other): bool + { + return !$other->isLarge(); + } + + public function asString(): string + { + return 'large'; + } +} diff --git a/app/vendor/phpunit/php-code-coverage/src/TestSize/Medium.php b/app/vendor/phpunit/php-code-coverage/src/TestSize/Medium.php new file mode 100644 index 000000000..f7fd7c86f --- /dev/null +++ b/app/vendor/phpunit/php-code-coverage/src/TestSize/Medium.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 SebastianBergmann\CodeCoverage\Test\TestSize; + +/** + * @immutable + */ +final class Medium extends Known +{ + public function isMedium(): true + { + return true; + } + + public function isGreaterThan(TestSize $other): bool + { + return $other->isSmall(); + } + + public function asString(): string + { + return 'medium'; + } +} diff --git a/app/vendor/phpunit/php-code-coverage/src/TestSize/Small.php b/app/vendor/phpunit/php-code-coverage/src/TestSize/Small.php new file mode 100644 index 000000000..7c695ef46 --- /dev/null +++ b/app/vendor/phpunit/php-code-coverage/src/TestSize/Small.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 SebastianBergmann\CodeCoverage\Test\TestSize; + +/** + * @immutable + */ +final class Small extends Known +{ + public function isSmall(): true + { + return true; + } + + public function isGreaterThan(TestSize $other): bool + { + return false; + } + + public function asString(): string + { + return 'small'; + } +} diff --git a/app/vendor/phpunit/php-code-coverage/src/TestSize/TestSize.php b/app/vendor/phpunit/php-code-coverage/src/TestSize/TestSize.php new file mode 100644 index 000000000..a2c92dcc3 --- /dev/null +++ b/app/vendor/phpunit/php-code-coverage/src/TestSize/TestSize.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Test\TestSize; + +/** + * @immutable + */ +abstract class TestSize +{ + public static function unknown(): Unknown + { + return new Unknown; + } + + public static function small(): Small + { + return new Small; + } + + public static function medium(): Medium + { + return new Medium; + } + + public static function large(): Large + { + return new Large; + } + + /** + * @phpstan-assert-if-true Known $this + */ + public function isKnown(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true Unknown $this + */ + public function isUnknown(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true Small $this + */ + public function isSmall(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true Medium $this + */ + public function isMedium(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true Large $this + */ + public function isLarge(): bool + { + return false; + } + + abstract public function asString(): string; +} diff --git a/app/vendor/phpunit/php-code-coverage/src/TestSize/Unknown.php b/app/vendor/phpunit/php-code-coverage/src/TestSize/Unknown.php new file mode 100644 index 000000000..92808f688 --- /dev/null +++ b/app/vendor/phpunit/php-code-coverage/src/TestSize/Unknown.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Test\TestSize; + +/** + * @immutable + */ +final class Unknown extends TestSize +{ + public function isUnknown(): true + { + return true; + } + + public function asString(): string + { + return 'unknown'; + } +} diff --git a/app/vendor/phpunit/php-code-coverage/src/TestStatus/Failure.php b/app/vendor/phpunit/php-code-coverage/src/TestStatus/Failure.php new file mode 100644 index 000000000..a08ac3f6e --- /dev/null +++ b/app/vendor/phpunit/php-code-coverage/src/TestStatus/Failure.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Test\TestStatus; + +/** + * @immutable + */ +final class Failure extends Known +{ + public function isFailure(): true + { + return true; + } + + public function asString(): string + { + return 'failure'; + } +} diff --git a/app/vendor/phpunit/php-code-coverage/src/TestStatus/Known.php b/app/vendor/phpunit/php-code-coverage/src/TestStatus/Known.php new file mode 100644 index 000000000..358abe165 --- /dev/null +++ b/app/vendor/phpunit/php-code-coverage/src/TestStatus/Known.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 SebastianBergmann\CodeCoverage\Test\TestStatus; + +/** + * @immutable + */ +abstract class Known extends TestStatus +{ + public function isKnown(): true + { + return true; + } +} diff --git a/app/vendor/phpunit/php-code-coverage/src/TestStatus/Success.php b/app/vendor/phpunit/php-code-coverage/src/TestStatus/Success.php new file mode 100644 index 000000000..55f84a6b2 --- /dev/null +++ b/app/vendor/phpunit/php-code-coverage/src/TestStatus/Success.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Test\TestStatus; + +/** + * @immutable + */ +final class Success extends Known +{ + public function isSuccess(): true + { + return true; + } + + public function asString(): string + { + return 'success'; + } +} diff --git a/app/vendor/phpunit/php-code-coverage/src/TestStatus/TestStatus.php b/app/vendor/phpunit/php-code-coverage/src/TestStatus/TestStatus.php new file mode 100644 index 000000000..c8015992f --- /dev/null +++ b/app/vendor/phpunit/php-code-coverage/src/TestStatus/TestStatus.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 SebastianBergmann\CodeCoverage\Test\TestStatus; + +/** + * @immutable + */ +abstract class TestStatus +{ + public static function unknown(): self + { + return new Unknown; + } + + public static function success(): self + { + return new Success; + } + + public static function failure(): self + { + return new Failure; + } + + /** + * @phpstan-assert-if-true Known $this + */ + public function isKnown(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true Unknown $this + */ + public function isUnknown(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true Success $this + */ + public function isSuccess(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true Failure $this + */ + public function isFailure(): bool + { + return false; + } + + abstract public function asString(): string; +} diff --git a/app/vendor/phpunit/php-code-coverage/src/TestStatus/Unknown.php b/app/vendor/phpunit/php-code-coverage/src/TestStatus/Unknown.php new file mode 100644 index 000000000..ecde844dd --- /dev/null +++ b/app/vendor/phpunit/php-code-coverage/src/TestStatus/Unknown.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Test\TestStatus; + +/** + * @immutable + */ +final class Unknown extends TestStatus +{ + public function isUnknown(): true + { + return true; + } + + public function asString(): string + { + return 'unknown'; + } +} diff --git a/app/vendor/phpunit/php-code-coverage/src/Util/Filesystem.php b/app/vendor/phpunit/php-code-coverage/src/Util/Filesystem.php index ff0e16ae9..0e99b1593 100644 --- a/app/vendor/phpunit/php-code-coverage/src/Util/Filesystem.php +++ b/app/vendor/phpunit/php-code-coverage/src/Util/Filesystem.php @@ -23,14 +23,14 @@ final class Filesystem */ public static function createDirectory(string $directory): void { - $success = !(!is_dir($directory) && !@mkdir($directory, 0777, true) && !is_dir($directory)); + $success = !(!is_dir($directory) && !@mkdir($directory, 0o777, true) && !is_dir($directory)); if (!$success) { throw new DirectoryCouldNotBeCreatedException( sprintf( 'Directory "%s" could not be created', - $directory - ) + $directory, + ), ); } } diff --git a/app/vendor/phpunit/php-code-coverage/src/Util/Percentage.php b/app/vendor/phpunit/php-code-coverage/src/Util/Percentage.php index 0f7a3fec1..1de640f80 100644 --- a/app/vendor/phpunit/php-code-coverage/src/Util/Percentage.php +++ b/app/vendor/phpunit/php-code-coverage/src/Util/Percentage.php @@ -14,17 +14,10 @@ /** * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage */ -final class Percentage +final readonly class Percentage { - /** - * @var float - */ - private $fraction; - - /** - * @var float - */ - private $total; + private float $fraction; + private float $total; public static function fromFractionAndTotal(float $fraction, float $total): self { diff --git a/app/vendor/phpunit/php-code-coverage/src/Version.php b/app/vendor/phpunit/php-code-coverage/src/Version.php index 93cf32d94..3666e06c9 100644 --- a/app/vendor/phpunit/php-code-coverage/src/Version.php +++ b/app/vendor/phpunit/php-code-coverage/src/Version.php @@ -14,15 +14,12 @@ final class Version { - /** - * @var string - */ - private static $version; + private static string $version = ''; public static function id(): string { - if (self::$version === null) { - self::$version = (new VersionId('9.2.32', dirname(__DIR__)))->getVersion(); + if (self::$version === '') { + self::$version = (new VersionId('12.3.4', dirname(__DIR__)))->asString(); } return self::$version; diff --git a/app/vendor/phpunit/php-file-iterator/ChangeLog.md b/app/vendor/phpunit/php-file-iterator/ChangeLog.md index 44833762a..a0fa134a5 100644 --- a/app/vendor/phpunit/php-file-iterator/ChangeLog.md +++ b/app/vendor/phpunit/php-file-iterator/ChangeLog.md @@ -2,6 +2,56 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## [6.0.0] - 2025-02-07 + +### Removed + +* This component is no longer supported on PHP 8.2 + +## [5.1.0] - 2024-08-27 + +### Added + +* [#83](https://github.com/sebastianbergmann/php-file-iterator/pull/83): Support for "Globstar" pattern + +## [5.0.1] - 2024-07-03 + +### Changed + +* This project now uses PHPStan instead of Psalm for static analysis + +## [5.0.0] - 2024-02-02 + +### Removed + +* This component is no longer supported on PHP 8.1 + +## [4.1.0] - 2023-08-31 + +### Added + +* [#81](https://github.com/sebastianbergmann/php-file-iterator/issues/81): Accept `array|string $paths` in `Facade::getFilesAsArray()` + +## [4.0.2] - 2023-05-07 + +### Fixed + +* [#80](https://github.com/sebastianbergmann/php-file-iterator/pull/80): Ignore unresolvable symbolic link + +## [4.0.1] - 2023-02-10 + +### Fixed + +* [#67](https://github.com/sebastianbergmann/php-file-iterator/issues/61): Excluded directories are traversed unnecessarily + +## [4.0.0] - 2023-02-03 + +### Removed + +* The optional `$commonPath` parameter of `SebastianBergmann\FileIterator\Facade` as well as the functionality it controlled has been removed +* The `SebastianBergmann\FileIterator\Factory` and `SebastianBergmann\FileIterator\Iterator` classes are now marked `@internal` +* This component is no longer supported on PHP 7.3, PHP 7.4 and PHP 8.0 + ## [3.0.6] - 2021-12-02 ### Changed @@ -124,6 +174,14 @@ No changes * [#23](https://github.com/sebastianbergmann/php-file-iterator/pull/23): Added support for wildcards (glob) in exclude +[6.0.0]: https://github.com/sebastianbergmann/php-file-iterator/compare/5.1...6.0.0 +[5.1.0]: https://github.com/sebastianbergmann/php-file-iterator/compare/5.0.1...5.1.0 +[5.0.1]: https://github.com/sebastianbergmann/php-file-iterator/compare/5.0.0...5.0.1 +[5.0.0]: https://github.com/sebastianbergmann/php-file-iterator/compare/4.1...5.0.0 +[4.1.0]: https://github.com/sebastianbergmann/php-file-iterator/compare/4.0.2...4.1.0 +[4.0.2]: https://github.com/sebastianbergmann/php-file-iterator/compare/4.0.1...4.0.2 +[4.0.1]: https://github.com/sebastianbergmann/php-file-iterator/compare/4.0.0...4.0.1 +[4.0.0]: https://github.com/sebastianbergmann/php-file-iterator/compare/3.0.6...4.0.0 [3.0.6]: https://github.com/sebastianbergmann/php-file-iterator/compare/3.0.5...3.0.6 [3.0.5]: https://github.com/sebastianbergmann/php-file-iterator/compare/3.0.4...3.0.5 [3.0.4]: https://github.com/sebastianbergmann/php-file-iterator/compare/3.0.3...3.0.4 diff --git a/app/vendor/phpunit/php-file-iterator/LICENSE b/app/vendor/phpunit/php-file-iterator/LICENSE index 51db9163e..017eb48b1 100644 --- a/app/vendor/phpunit/php-file-iterator/LICENSE +++ b/app/vendor/phpunit/php-file-iterator/LICENSE @@ -1,33 +1,29 @@ -php-file-iterator +BSD 3-Clause License -Copyright (c) 2009-2021, Sebastian Bergmann . +Copyright (c) 2009-2025, Sebastian Bergmann All rights reserved. Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: +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. +1. 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. +2. 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 Sebastian Bergmann nor the names of his - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. +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/app/vendor/phpunit/php-file-iterator/README.md b/app/vendor/phpunit/php-file-iterator/README.md index 3cbfdaae7..5bc7da65d 100644 --- a/app/vendor/phpunit/php-file-iterator/README.md +++ b/app/vendor/phpunit/php-file-iterator/README.md @@ -1,4 +1,7 @@ -[![Build Status](https://travis-ci.org/sebastianbergmann/php-file-iterator.svg?branch=master)](https://travis-ci.org/sebastianbergmann/php-file-iterator) +[![Latest Stable Version](https://poser.pugx.org/phpunit/php-file-iterator/v)](https://packagist.org/packages/phpunit/php-file-iterator) +[![CI Status](https://github.com/sebastianbergmann/php-file-iterator/workflows/CI/badge.svg)](https://github.com/sebastianbergmann/php-file-iterator/actions) +[![Type Coverage](https://shepherd.dev/github/sebastianbergmann/php-file-iterator/coverage.svg)](https://shepherd.dev/github/sebastianbergmann/php-file-iterator) +[![codecov](https://codecov.io/gh/sebastianbergmann/php-file-iterator/branch/main/graph/badge.svg)](https://codecov.io/gh/sebastianbergmann/php-file-iterator) # php-file-iterator diff --git a/app/vendor/phpunit/php-file-iterator/SECURITY.md b/app/vendor/phpunit/php-file-iterator/SECURITY.md new file mode 100644 index 000000000..d88ff0019 --- /dev/null +++ b/app/vendor/phpunit/php-file-iterator/SECURITY.md @@ -0,0 +1,30 @@ +# Security Policy + +If you believe you have found a security vulnerability in the library that is developed in this repository, please report it to us through coordinated disclosure. + +**Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.** + +Instead, please email `sebastian@phpunit.de`. + +Please include as much of the information listed below as you can to help us better understand and resolve the issue: + +* The type of issue +* Full paths of source file(s) related to the manifestation of the issue +* The location of the affected source code (tag/branch/commit or direct URL) +* Any special configuration required to reproduce the issue +* Step-by-step instructions to reproduce the issue +* Proof-of-concept or exploit code (if possible) +* Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +## Web Context + +The library that is developed in this repository was either extracted from [PHPUnit](https://github.com/sebastianbergmann/phpunit) or developed specifically as a dependency for PHPUnit. + +The library is developed with a focus on development environments and the command-line. No specific testing or hardening with regard to using the library in an HTTP or web context or with untrusted input data is performed. The library might also contain functionality that intentionally exposes internal application data for debugging purposes. + +If the library is used in a web application, the application developer is responsible for filtering inputs or escaping outputs as necessary and for verifying that the used functionality is safe for use within the intended context. + +Vulnerabilities specific to the use outside a development context will be fixed as applicable, provided that the fix does not have an averse effect on the primary use case for development purposes. + diff --git a/app/vendor/phpunit/php-file-iterator/composer.json b/app/vendor/phpunit/php-file-iterator/composer.json index f1b95b3f8..528f298ba 100644 --- a/app/vendor/phpunit/php-file-iterator/composer.json +++ b/app/vendor/phpunit/php-file-iterator/composer.json @@ -16,21 +16,22 @@ } ], "support": { - "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues" + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy" }, "config": { "platform": { - "php": "7.3.0" + "php": "8.3.0" }, "optimize-autoloader": true, "sort-packages": true }, "prefer-stable": true, "require": { - "php": ">=7.3" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^12.0" }, "autoload": { "classmap": [ @@ -39,7 +40,7 @@ }, "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "6.0-dev" } } } diff --git a/app/vendor/phpunit/php-file-iterator/src/ExcludeIterator.php b/app/vendor/phpunit/php-file-iterator/src/ExcludeIterator.php new file mode 100644 index 000000000..209ec00f5 --- /dev/null +++ b/app/vendor/phpunit/php-file-iterator/src/ExcludeIterator.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 SebastianBergmann\FileIterator; + +use function assert; +use function str_starts_with; +use RecursiveDirectoryIterator; +use RecursiveFilterIterator; +use SplFileInfo; + +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-file-iterator + */ +final class ExcludeIterator extends RecursiveFilterIterator +{ + /** + * @var list + */ + private array $exclude; + + /** + * @param list $exclude + */ + public function __construct(RecursiveDirectoryIterator $iterator, array $exclude) + { + parent::__construct($iterator); + + $this->exclude = $exclude; + } + + public function accept(): bool + { + $current = $this->current(); + + assert($current instanceof SplFileInfo); + + $path = $current->getRealPath(); + + if ($path === false) { + return false; + } + + foreach ($this->exclude as $exclude) { + if (str_starts_with($path, $exclude)) { + return false; + } + } + + return true; + } + + public function hasChildren(): bool + { + return $this->getInnerIterator()->hasChildren(); + } + + public function getChildren(): self + { + return new self( + $this->getInnerIterator()->getChildren(), + $this->exclude, + ); + } + + public function getInnerIterator(): RecursiveDirectoryIterator + { + $innerIterator = parent::getInnerIterator(); + + assert($innerIterator instanceof RecursiveDirectoryIterator); + + return $innerIterator; + } +} diff --git a/app/vendor/phpunit/php-file-iterator/src/Facade.php b/app/vendor/phpunit/php-file-iterator/src/Facade.php index 87b6588dd..a0410e842 100644 --- a/app/vendor/phpunit/php-file-iterator/src/Facade.php +++ b/app/vendor/phpunit/php-file-iterator/src/Facade.php @@ -9,34 +9,33 @@ */ namespace SebastianBergmann\FileIterator; -use const DIRECTORY_SEPARATOR; use function array_unique; -use function count; -use function dirname; -use function explode; -use function is_file; -use function is_string; -use function realpath; +use function assert; use function sort; +use SplFileInfo; -class Facade +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class Facade { /** - * @param array|string $paths - * @param array|string $suffixes - * @param array|string $prefixes + * @param list|non-empty-string $paths + * @param list|string $suffixes + * @param list|string $prefixes + * @param list $exclude + * + * @return list */ - public function getFilesAsArray($paths, $suffixes = '', $prefixes = '', array $exclude = [], bool $commonPath = false): array + public function getFilesAsArray(array|string $paths, array|string $suffixes = '', array|string $prefixes = '', array $exclude = []): array { - if (is_string($paths)) { - $paths = [$paths]; - } - $iterator = (new Factory)->getFileIterator($paths, $suffixes, $prefixes, $exclude); $files = []; foreach ($iterator as $file) { + assert($file instanceof SplFileInfo); + $file = $file->getRealPath(); if ($file) { @@ -44,72 +43,10 @@ public function getFilesAsArray($paths, $suffixes = '', $prefixes = '', array $e } } - foreach ($paths as $path) { - if (is_file($path)) { - $files[] = realpath($path); - } - } - $files = array_unique($files); - sort($files); - if ($commonPath) { - return [ - 'commonPath' => $this->getCommonPath($files), - 'files' => $files, - ]; - } + sort($files); return $files; } - - protected function getCommonPath(array $files): string - { - $count = count($files); - - if ($count === 0) { - return ''; - } - - if ($count === 1) { - return dirname($files[0]) . DIRECTORY_SEPARATOR; - } - - $_files = []; - - foreach ($files as $file) { - $_files[] = $_fileParts = explode(DIRECTORY_SEPARATOR, $file); - - if (empty($_fileParts[0])) { - $_fileParts[0] = DIRECTORY_SEPARATOR; - } - } - - $common = ''; - $done = false; - $j = 0; - $count--; - - while (!$done) { - for ($i = 0; $i < $count; $i++) { - if ($_files[$i][$j] != $_files[$i + 1][$j]) { - $done = true; - - break; - } - } - - if (!$done) { - $common .= $_files[0][$j]; - - if ($j > 0) { - $common .= DIRECTORY_SEPARATOR; - } - } - - $j++; - } - - return DIRECTORY_SEPARATOR . $common; - } } diff --git a/app/vendor/phpunit/php-file-iterator/src/Factory.php b/app/vendor/phpunit/php-file-iterator/src/Factory.php index 08f8de999..da53df923 100644 --- a/app/vendor/phpunit/php-file-iterator/src/Factory.php +++ b/app/vendor/phpunit/php-file-iterator/src/Factory.php @@ -13,29 +13,41 @@ use function array_filter; use function array_map; use function array_merge; +use function array_unique; +use function array_values; use function glob; use function is_dir; use function is_string; use function realpath; +use function sort; +use function stripos; +use function substr; use AppendIterator; +use FilesystemIterator; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; -class Factory +/** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-file-iterator + */ +final class Factory { /** - * @param array|string $paths - * @param array|string $suffixes - * @param array|string $prefixes + * @param list|non-empty-string $paths + * @param list|string $suffixes + * @param list|string $prefixes + * @param list $exclude + * + * @phpstan-ignore missingType.generics */ - public function getFileIterator($paths, $suffixes = '', $prefixes = '', array $exclude = []): AppendIterator + public function getFileIterator(array|string $paths, array|string $suffixes = '', array|string $prefixes = '', array $exclude = []): AppendIterator { if (is_string($paths)) { $paths = [$paths]; } - $paths = $this->getPathsAfterResolvingWildcards($paths); - $exclude = $this->getPathsAfterResolvingWildcards($exclude); + $paths = $this->resolveWildcards($paths); + $exclude = $this->resolveWildcards($exclude); if (is_string($prefixes)) { if ($prefixes !== '') { @@ -61,12 +73,14 @@ public function getFileIterator($paths, $suffixes = '', $prefixes = '', array $e new Iterator( $path, new RecursiveIteratorIterator( - new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::FOLLOW_SYMLINKS | RecursiveDirectoryIterator::SKIP_DOTS) + new ExcludeIterator( + new RecursiveDirectoryIterator($path, FilesystemIterator::FOLLOW_SYMLINKS | FilesystemIterator::SKIP_DOTS), + $exclude, + ), ), $suffixes, $prefixes, - $exclude - ) + ), ); } } @@ -74,18 +88,70 @@ public function getFileIterator($paths, $suffixes = '', $prefixes = '', array $e return $iterator; } - protected function getPathsAfterResolvingWildcards(array $paths): array + /** + * @param list $paths + * + * @return list + */ + private function resolveWildcards(array $paths): array { $_paths = [[]]; foreach ($paths as $path) { - if ($locals = glob($path, GLOB_ONLYDIR)) { + if ($locals = $this->globstar($path)) { $_paths[] = array_map('\realpath', $locals); } else { + // @codeCoverageIgnoreStart $_paths[] = [realpath($path)]; + // @codeCoverageIgnoreEnd } } - return array_filter(array_merge(...$_paths)); + return array_values(array_filter(array_merge(...$_paths))); + } + + /** + * @see https://gist.github.com/funkjedi/3feee27d873ae2297b8e2370a7082aad + * + * @return list + */ + private function globstar(string $pattern): array + { + if (stripos($pattern, '**') === false) { + $files = glob($pattern, GLOB_ONLYDIR); + } else { + $position = stripos($pattern, '**'); + $rootPattern = substr($pattern, 0, $position - 1); + $restPattern = substr($pattern, $position + 2); + + $patterns = [$rootPattern . $restPattern]; + $rootPattern .= '/*'; + + while ($directories = glob($rootPattern, GLOB_ONLYDIR)) { + $rootPattern .= '/*'; + + foreach ($directories as $directory) { + $patterns[] = $directory . $restPattern; + } + } + + $files = []; + + foreach ($patterns as $_pattern) { + $files = array_merge($files, $this->globstar($_pattern)); + } + } + + if ($files !== false) { + $files = array_unique($files); + + sort($files); + + return $files; + } + + // @codeCoverageIgnoreStart + return []; + // @codeCoverageIgnoreEnd } } diff --git a/app/vendor/phpunit/php-file-iterator/src/Iterator.php b/app/vendor/phpunit/php-file-iterator/src/Iterator.php index 7eb82ad68..b29f235a3 100644 --- a/app/vendor/phpunit/php-file-iterator/src/Iterator.php +++ b/app/vendor/phpunit/php-file-iterator/src/Iterator.php @@ -9,60 +9,59 @@ */ namespace SebastianBergmann\FileIterator; -use function array_filter; -use function array_map; use function preg_match; use function realpath; +use function str_ends_with; use function str_replace; -use function strlen; -use function strpos; -use function substr; +use function str_starts_with; use FilterIterator; +use SplFileInfo; -class Iterator extends FilterIterator +/** + * @template-extends FilterIterator + * + * @internal This class is not covered by the backward compatibility promise for phpunit/php-file-iterator + */ +final class Iterator extends FilterIterator { - public const PREFIX = 0; - - public const SUFFIX = 1; - - /** - * @var string - */ - private $basePath; + public const int PREFIX = 0; + public const int SUFFIX = 1; + private false|string $basePath; /** - * @var array + * @var list */ - private $suffixes = []; + private array $suffixes; /** - * @var array + * @var list */ - private $prefixes = []; + private array $prefixes; /** - * @var array + * @param list $suffixes + * @param list $prefixes */ - private $exclude = []; - - public function __construct(string $basePath, \Iterator $iterator, array $suffixes = [], array $prefixes = [], array $exclude = []) + public function __construct(string $basePath, \Iterator $iterator, array $suffixes = [], array $prefixes = []) { $this->basePath = realpath($basePath); $this->prefixes = $prefixes; $this->suffixes = $suffixes; - $this->exclude = array_filter(array_map('realpath', $exclude)); parent::__construct($iterator); } public function accept(): bool { - $current = $this->getInnerIterator()->current(); + $current = $this->getInnerIterator()->current(); + $filename = $current->getFilename(); $realPath = $current->getRealPath(); if ($realPath === false) { + // @codeCoverageIgnoreStart return false; + // @codeCoverageIgnoreEnd } return $this->acceptPath($realPath) && @@ -73,16 +72,10 @@ public function accept(): bool private function acceptPath(string $path): bool { // Filter files in hidden directories by checking path that is relative to the base path. - if (preg_match('=/\.[^/]*/=', str_replace($this->basePath, '', $path))) { + if (preg_match('=/\.[^/]*/=', str_replace((string) $this->basePath, '', $path))) { return false; } - foreach ($this->exclude as $exclude) { - if (strpos($path, $exclude) === 0) { - return false; - } - } - return true; } @@ -96,24 +89,22 @@ private function acceptSuffix(string $filename): bool return $this->acceptSubString($filename, $this->suffixes, self::SUFFIX); } + /** + * @param list $subStrings + */ private function acceptSubString(string $filename, array $subStrings, int $type): bool { if (empty($subStrings)) { return true; } - $matched = false; - foreach ($subStrings as $string) { - if (($type === self::PREFIX && strpos($filename, $string) === 0) || - ($type === self::SUFFIX && - substr($filename, -1 * strlen($string)) === $string)) { - $matched = true; - - break; + if (($type === self::PREFIX && str_starts_with($filename, $string)) || + ($type === self::SUFFIX && str_ends_with($filename, $string))) { + return true; } } - return $matched; + return false; } } diff --git a/app/vendor/phpunit/php-invoker/ChangeLog.md b/app/vendor/phpunit/php-invoker/ChangeLog.md index 15cff1f11..9af1807a6 100644 --- a/app/vendor/phpunit/php-invoker/ChangeLog.md +++ b/app/vendor/phpunit/php-invoker/ChangeLog.md @@ -2,6 +2,30 @@ All notable changes are documented in this file using the [Keep a CHANGELOG](https://keepachangelog.com/) principles. +## [6.0.0] - 2025-02-07 + +### Removed + +* This component is no longer supported on PHP 8.2 + +## [5.0.1] - 2024-07-03 + +### Changed + +* This project now uses PHPStan instead of Psalm for static analysis + +## [5.0.0] - 2024-02-02 + +### Removed + +* This component is no longer supported on PHP 8.1 + +## [4.0.0] - 2023-02-03 + +### Removed + +* This component is no longer supported on PHP 7.3, PHP 7.4 and PHP 8.0 + ## [3.1.1] - 2020-09-28 ### Changed @@ -41,6 +65,10 @@ All notable changes are documented in this file using the [Keep a CHANGELOG](htt * This component is no longer supported on PHP 7.1 and PHP 7.2 +[6.0.0]: https://github.com/sebastianbergmann/php-invoker/compare/5.0...6.0.0 +[5.0.1]: https://github.com/sebastianbergmann/php-invoker/compare/5.0.1...5.0.1 +[5.0.0]: https://github.com/sebastianbergmann/php-invoker/compare/4.0...5.0.0 +[4.0.0]: https://github.com/sebastianbergmann/php-invoker/compare/3.1.1...4.0.0 [3.1.1]: https://github.com/sebastianbergmann/php-invoker/compare/3.1.0...3.1.1 [3.1.0]: https://github.com/sebastianbergmann/php-invoker/compare/3.0.2...3.1.0 [3.0.2]: https://github.com/sebastianbergmann/php-invoker/compare/3.0.1...3.0.2 diff --git a/app/vendor/phpunit/php-invoker/LICENSE b/app/vendor/phpunit/php-invoker/LICENSE index 4620776e1..9d94d32e4 100644 --- a/app/vendor/phpunit/php-invoker/LICENSE +++ b/app/vendor/phpunit/php-invoker/LICENSE @@ -1,33 +1,29 @@ -php-invoker +BSD 3-Clause License -Copyright (c) 2011-2020, Sebastian Bergmann . +Copyright (c) 2011-2025, Sebastian Bergmann All rights reserved. Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: +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. +1. 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. +2. 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 Sebastian Bergmann nor the names of his - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. +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/app/vendor/phpunit/php-invoker/README.md b/app/vendor/phpunit/php-invoker/README.md index ace07e5fd..aedc33974 100644 --- a/app/vendor/phpunit/php-invoker/README.md +++ b/app/vendor/phpunit/php-invoker/README.md @@ -1,7 +1,8 @@ # phpunit/php-invoker +[![Latest Stable Version](https://poser.pugx.org/phpunit/php-invoker/v)](https://packagist.org/packages/phpunit/php-invoker) [![CI Status](https://github.com/sebastianbergmann/php-invoker/workflows/CI/badge.svg)](https://github.com/sebastianbergmann/php-invoker/actions) -[![Type Coverage](https://shepherd.dev/github/sebastianbergmann/php-invoker/coverage.svg)](https://shepherd.dev/github/sebastianbergmann/php-invoker) +[![codecov](https://codecov.io/gh/sebastianbergmann/php-invoker/branch/main/graph/badge.svg)](https://codecov.io/gh/sebastianbergmann/php-invoker) ## Installation diff --git a/app/vendor/phpunit/php-invoker/SECURITY.md b/app/vendor/phpunit/php-invoker/SECURITY.md new file mode 100644 index 000000000..d88ff0019 --- /dev/null +++ b/app/vendor/phpunit/php-invoker/SECURITY.md @@ -0,0 +1,30 @@ +# Security Policy + +If you believe you have found a security vulnerability in the library that is developed in this repository, please report it to us through coordinated disclosure. + +**Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.** + +Instead, please email `sebastian@phpunit.de`. + +Please include as much of the information listed below as you can to help us better understand and resolve the issue: + +* The type of issue +* Full paths of source file(s) related to the manifestation of the issue +* The location of the affected source code (tag/branch/commit or direct URL) +* Any special configuration required to reproduce the issue +* Step-by-step instructions to reproduce the issue +* Proof-of-concept or exploit code (if possible) +* Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +## Web Context + +The library that is developed in this repository was either extracted from [PHPUnit](https://github.com/sebastianbergmann/phpunit) or developed specifically as a dependency for PHPUnit. + +The library is developed with a focus on development environments and the command-line. No specific testing or hardening with regard to using the library in an HTTP or web context or with untrusted input data is performed. The library might also contain functionality that intentionally exposes internal application data for debugging purposes. + +If the library is used in a web application, the application developer is responsible for filtering inputs or escaping outputs as necessary and for verifying that the used functionality is safe for use within the intended context. + +Vulnerabilities specific to the use outside a development context will be fixed as applicable, provided that the fix does not have an averse effect on the primary use case for development purposes. + diff --git a/app/vendor/phpunit/php-invoker/composer.json b/app/vendor/phpunit/php-invoker/composer.json index 6c007cd81..b5bd9b85f 100644 --- a/app/vendor/phpunit/php-invoker/composer.json +++ b/app/vendor/phpunit/php-invoker/composer.json @@ -15,22 +15,23 @@ } ], "support": { - "issues": "https://github.com/sebastianbergmann/php-invoker/issues" + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "security": "https://github.com/sebastianbergmann/php-invoker/security/policy" }, "prefer-stable": true, "config": { "platform": { - "php": "7.3.0" + "php": "8.3.0" }, "optimize-autoloader": true, "sort-packages": true }, "require": { - "php": ">=7.3" + "php": ">=8.3" }, "require-dev": { "ext-pcntl": "*", - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^12.0" }, "autoload": { "classmap": [ @@ -47,7 +48,7 @@ }, "extra": { "branch-alias": { - "dev-master": "3.1-dev" + "dev-main": "6.0-dev" } } } diff --git a/app/vendor/phpunit/php-invoker/src/Invoker.php b/app/vendor/phpunit/php-invoker/src/Invoker.php index 656f4180a..18ae4ff80 100644 --- a/app/vendor/phpunit/php-invoker/src/Invoker.php +++ b/app/vendor/phpunit/php-invoker/src/Invoker.php @@ -11,6 +11,7 @@ use const SIGALRM; use function call_user_func_array; +use function extension_loaded; use function function_exists; use function pcntl_alarm; use function pcntl_async_signals; @@ -21,37 +22,32 @@ final class Invoker { /** - * @var int - */ - private $timeout; - - /** + * @param array $arguments + * * @throws Throwable */ - public function invoke(callable $callable, array $arguments, int $timeout) + public function invoke(callable $callable, array $arguments, int $timeout): mixed { if (!$this->canInvokeWithTimeout()) { - throw new ProcessControlExtensionNotLoadedException( - 'The pcntl (process control) extension for PHP is required' - ); + // @codeCoverageIgnoreStart + throw new ProcessControlExtensionNotLoadedException; + // @codeCoverageIgnoreEnd } pcntl_signal( SIGALRM, - function (): void { + static function () use ($timeout): void + { throw new TimeoutException( sprintf( 'Execution aborted after %d second%s', - $this->timeout, - $this->timeout === 1 ? '' : 's' - ) + $timeout, + $timeout === 1 ? '' : 's', + ), ); }, - true ); - $this->timeout = $timeout; - pcntl_async_signals(true); pcntl_alarm($timeout); @@ -64,6 +60,6 @@ function (): void { public function canInvokeWithTimeout(): bool { - return function_exists('pcntl_signal') && function_exists('pcntl_async_signals') && function_exists('pcntl_alarm'); + return extension_loaded('pcntl') && function_exists('pcntl_signal') && function_exists('pcntl_async_signals') && function_exists('pcntl_alarm'); } } diff --git a/app/vendor/phpunit/php-invoker/src/exceptions/ProcessControlExtensionNotLoadedException.php b/app/vendor/phpunit/php-invoker/src/exceptions/ProcessControlExtensionNotLoadedException.php index ef42fd195..7f410d3c5 100644 --- a/app/vendor/phpunit/php-invoker/src/exceptions/ProcessControlExtensionNotLoadedException.php +++ b/app/vendor/phpunit/php-invoker/src/exceptions/ProcessControlExtensionNotLoadedException.php @@ -9,8 +9,34 @@ */ namespace SebastianBergmann\Invoker; +use const PHP_EOL; +use function extension_loaded; +use function function_exists; +use function implode; use RuntimeException; final class ProcessControlExtensionNotLoadedException extends RuntimeException implements Exception { + public function __construct() + { + $message = []; + + if (!extension_loaded('pcntl')) { + $message[] = 'The pcntl (process control) extension for PHP must be loaded.'; + } + + if (!function_exists('pcntl_signal')) { + $message[] = 'The pcntl_signal() function must not be disabled.'; + } + + if (!function_exists('pcntl_async_signals')) { + $message[] = 'The pcntl_async_signals() function must not be disabled.'; + } + + if (!function_exists('pcntl_alarm')) { + $message[] = 'The pcntl_alarm() function must not be disabled.'; + } + + parent::__construct(implode(PHP_EOL, $message)); + } } diff --git a/app/vendor/phpunit/php-text-template/.psalm/baseline.xml b/app/vendor/phpunit/php-text-template/.psalm/baseline.xml index 77e688e07..1f9a5e312 100644 --- a/app/vendor/phpunit/php-text-template/.psalm/baseline.xml +++ b/app/vendor/phpunit/php-text-template/.psalm/baseline.xml @@ -1,2 +1,2 @@ - + diff --git a/app/vendor/phpunit/php-text-template/.psalm/config.xml b/app/vendor/phpunit/php-text-template/.psalm/config.xml index 2a4b16f22..343cc8e3e 100644 --- a/app/vendor/phpunit/php-text-template/.psalm/config.xml +++ b/app/vendor/phpunit/php-text-template/.psalm/config.xml @@ -4,8 +4,9 @@ xmlns="https://getpsalm.org/schema/config" xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd" resolveFromConfigFile="false" - totallyTyped="false" errorBaseline=".psalm/baseline.xml" + findUnusedBaselineEntry="true" + findUnusedCode="false" > diff --git a/app/vendor/phpunit/php-text-template/ChangeLog.md b/app/vendor/phpunit/php-text-template/ChangeLog.md index 32a48a7a1..ff91aa434 100644 --- a/app/vendor/phpunit/php-text-template/ChangeLog.md +++ b/app/vendor/phpunit/php-text-template/ChangeLog.md @@ -2,6 +2,37 @@ All notable changes are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles. +## [5.0.0] - 2025-02-07 + +### Removed + +* This component is no longer supported on PHP 8.2 + +## [4.0.1] - 2024-07-03 + +### Changed + +* This project now uses PHPStan instead of Psalm for static analysis + +## [4.0.0] - 2024-02-02 + +### Removed + +* The `SebastianBergmann\Template\Template::setFile()` method has been removed +* This component is no longer supported on PHP 8.1 + +## [3.0.1] - 2023-08-31 + +### Changed + +* Warnings from `file_put_contents()` are now suppressed + +## [3.0.0] - 2023-02-03 + +### Removed + +* This component is no longer supported on PHP 7.3, PHP 7.4 and PHP 8.0 + ## [2.0.4] - 2020-10-26 ### Fixed @@ -36,6 +67,11 @@ All notable changes are documented in this file using the [Keep a CHANGELOG](htt * Removed support for PHP 5.3, PHP 5.4, PHP 5.5, PHP 5.6, PHP 7.0, PHP 7.1, and PHP 7.2 +[5.0.0]: https://github.com/sebastianbergmann/php-text-template/compare/4.0...5.0.0 +[4.0.1]: https://github.com/sebastianbergmann/php-text-template/compare/4.0.0...4.0.1 +[4.0.0]: https://github.com/sebastianbergmann/php-text-template/compare/3.0...4.0.0 +[3.0.1]: https://github.com/sebastianbergmann/php-text-template/compare/3.0.0...3.0.1 +[3.0.0]: https://github.com/sebastianbergmann/php-text-template/compare/2.0.4...3.0.0 [2.0.4]: https://github.com/sebastianbergmann/php-text-template/compare/2.0.3...2.0.4 [2.0.3]: https://github.com/sebastianbergmann/php-text-template/compare/2.0.2...2.0.3 [2.0.2]: https://github.com/sebastianbergmann/php-text-template/compare/2.0.1...2.0.2 diff --git a/app/vendor/phpunit/php-text-template/LICENSE b/app/vendor/phpunit/php-text-template/LICENSE index 6db5566c8..017eb48b1 100644 --- a/app/vendor/phpunit/php-text-template/LICENSE +++ b/app/vendor/phpunit/php-text-template/LICENSE @@ -1,33 +1,29 @@ -phpunit/php-text-template +BSD 3-Clause License -Copyright (c) 2009-2020, Sebastian Bergmann . +Copyright (c) 2009-2025, Sebastian Bergmann All rights reserved. Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: +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. +1. 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. +2. 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 Sebastian Bergmann nor the names of his - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. +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/app/vendor/phpunit/php-text-template/README.md b/app/vendor/phpunit/php-text-template/README.md index b28659354..009d54663 100644 --- a/app/vendor/phpunit/php-text-template/README.md +++ b/app/vendor/phpunit/php-text-template/README.md @@ -1,4 +1,8 @@ -# Text_Template +[![Latest Stable Version](https://poser.pugx.org/phpunit/php-text-template/v)](https://packagist.org/packages/phpunit/php-text-template) +[![CI Status](https://github.com/sebastianbergmann/php-text-template/workflows/CI/badge.svg)](https://github.com/sebastianbergmann/php-text-template/actions) +[![codecov](https://codecov.io/gh/sebastianbergmann/php-text-template/branch/main/graph/badge.svg)](https://codecov.io/gh/sebastianbergmann/php-text-template) + +# php-text-template ## Installation diff --git a/app/vendor/phpunit/php-text-template/SECURITY.md b/app/vendor/phpunit/php-text-template/SECURITY.md new file mode 100644 index 000000000..d88ff0019 --- /dev/null +++ b/app/vendor/phpunit/php-text-template/SECURITY.md @@ -0,0 +1,30 @@ +# Security Policy + +If you believe you have found a security vulnerability in the library that is developed in this repository, please report it to us through coordinated disclosure. + +**Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.** + +Instead, please email `sebastian@phpunit.de`. + +Please include as much of the information listed below as you can to help us better understand and resolve the issue: + +* The type of issue +* Full paths of source file(s) related to the manifestation of the issue +* The location of the affected source code (tag/branch/commit or direct URL) +* Any special configuration required to reproduce the issue +* Step-by-step instructions to reproduce the issue +* Proof-of-concept or exploit code (if possible) +* Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +## Web Context + +The library that is developed in this repository was either extracted from [PHPUnit](https://github.com/sebastianbergmann/phpunit) or developed specifically as a dependency for PHPUnit. + +The library is developed with a focus on development environments and the command-line. No specific testing or hardening with regard to using the library in an HTTP or web context or with untrusted input data is performed. The library might also contain functionality that intentionally exposes internal application data for debugging purposes. + +If the library is used in a web application, the application developer is responsible for filtering inputs or escaping outputs as necessary and for verifying that the used functionality is safe for use within the intended context. + +Vulnerabilities specific to the use outside a development context will be fixed as applicable, provided that the fix does not have an averse effect on the primary use case for development purposes. + diff --git a/app/vendor/phpunit/php-text-template/composer.json b/app/vendor/phpunit/php-text-template/composer.json index a51b34b95..f7a5ae77a 100644 --- a/app/vendor/phpunit/php-text-template/composer.json +++ b/app/vendor/phpunit/php-text-template/composer.json @@ -15,20 +15,22 @@ } ], "support": { - "issues": "https://github.com/sebastianbergmann/php-text-template/issues" + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy" }, "config": { "platform": { - "php": "7.3.0" + "php": "8.3.0" }, "optimize-autoloader": true, "sort-packages": true }, + "prefer-stable": true, "require": { - "php": ">=7.3" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^12.0" }, "autoload": { "classmap": [ @@ -37,7 +39,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "5.0-dev" } } } diff --git a/app/vendor/phpunit/php-text-template/src/Template.php b/app/vendor/phpunit/php-text-template/src/Template.php index 25e29ea97..c062a0cdf 100644 --- a/app/vendor/phpunit/php-text-template/src/Template.php +++ b/app/vendor/phpunit/php-text-template/src/Template.php @@ -9,81 +9,70 @@ */ namespace SebastianBergmann\Template; +use function array_keys; use function array_merge; -use function file_exists; use function file_get_contents; use function file_put_contents; +use function is_file; +use function is_string; use function sprintf; use function str_replace; final class Template { /** - * @var string + * @var non-empty-string */ - private $template = ''; + private readonly string $template; /** - * @var string + * @var non-empty-string */ - private $openDelimiter; + private readonly string $openDelimiter; /** - * @var string + * @var non-empty-string */ - private $closeDelimiter; + private readonly string $closeDelimiter; /** - * @var array + * @var array */ - private $values = []; + private array $values = []; /** + * @param non-empty-string $templateFile + * @param non-empty-string $openDelimiter + * @param non-empty-string $closeDelimiter + * * @throws InvalidArgumentException */ - public function __construct(string $file = '', string $openDelimiter = '{', string $closeDelimiter = '}') + public function __construct(string $templateFile, string $openDelimiter = '{', string $closeDelimiter = '}') { - $this->setFile($file); - + $this->template = $this->loadTemplateFile($templateFile); $this->openDelimiter = $openDelimiter; $this->closeDelimiter = $closeDelimiter; } /** - * @throws InvalidArgumentException + * @param array $values */ - public function setFile(string $file): void - { - $distFile = $file . '.dist'; - - if (file_exists($file)) { - $this->template = file_get_contents($file); - } elseif (file_exists($distFile)) { - $this->template = file_get_contents($distFile); - } else { - throw new InvalidArgumentException( - sprintf( - 'Failed to load template "%s"', - $file - ) - ); - } - } - public function setVar(array $values, bool $merge = true): void { if (!$merge || empty($this->values)) { $this->values = $values; - } else { - $this->values = array_merge($this->values, $values); + + return; } + + $this->values = array_merge($this->values, $values); } public function render(): string { $keys = []; - foreach ($this->values as $key => $value) { + foreach (array_keys($this->values) as $key) { $keys[] = $this->openDelimiter . $key . $this->closeDelimiter; } @@ -95,13 +84,48 @@ public function render(): string */ public function renderTo(string $target): void { - if (!file_put_contents($target, $this->render())) { + if (!@file_put_contents($target, $this->render())) { throw new RuntimeException( sprintf( 'Writing rendered result to "%s" failed', - $target - ) + $target, + ), ); } } + + /** + * @param non-empty-string $file + * + * @throws InvalidArgumentException + * + * @return non-empty-string + */ + private function loadTemplateFile(string $file): string + { + if (is_file($file)) { + $template = file_get_contents($file); + + if (is_string($template) && !empty($template)) { + return $template; + } + } + + $distFile = $file . '.dist'; + + if (is_file($distFile)) { + $template = file_get_contents($distFile); + + if (is_string($template) && !empty($template)) { + return $template; + } + } + + throw new InvalidArgumentException( + sprintf( + 'Failed to load template "%s"', + $file, + ), + ); + } } diff --git a/app/vendor/phpunit/php-timer/.psalm/baseline.xml b/app/vendor/phpunit/php-timer/.psalm/baseline.xml deleted file mode 100644 index 77e688e07..000000000 --- a/app/vendor/phpunit/php-timer/.psalm/baseline.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/app/vendor/phpunit/php-timer/.psalm/config.xml b/app/vendor/phpunit/php-timer/.psalm/config.xml deleted file mode 100644 index 15abef058..000000000 --- a/app/vendor/phpunit/php-timer/.psalm/config.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - diff --git a/app/vendor/phpunit/php-timer/ChangeLog.md b/app/vendor/phpunit/php-timer/ChangeLog.md index 34ef7d1d7..e103064ef 100644 --- a/app/vendor/phpunit/php-timer/ChangeLog.md +++ b/app/vendor/phpunit/php-timer/ChangeLog.md @@ -2,6 +2,30 @@ All notable changes are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles. +## [8.0.0] - 2025-02-07 + +### Removed + +* This component is no longer supported on PHP 8.1 + +## [7.0.1] - 2024-07-03 + +### Changed + +* This project now uses PHPStan instead of Psalm for static analysis + +## [7.0.0] - 2024-02-02 + +### Removed + +* This component is no longer supported on PHP 8.1 + +## [6.0.0] - 2023-02-03 + +### Removed + +* This component is no longer supported on PHP 7.3, PHP 7.4 and PHP 8.0 + ## [5.0.3] - 2020-10-26 ### Fixed @@ -121,6 +145,10 @@ All notable changes are documented in this file using the [Keep a CHANGELOG](htt * This component is no longer supported on PHP 5.3, PHP 5.4, PHP 5.5, PHP 5.6, and PHP 7.0 +[8.0.0]: https://github.com/sebastianbergmann/php-timer/compare/7.0...8.0.0 +[7.0.1]: https://github.com/sebastianbergmann/php-timer/compare/7.0.0...7.0.1 +[7.0.0]: https://github.com/sebastianbergmann/php-timer/compare/6.0...7.0.0 +[6.0.0]: https://github.com/sebastianbergmann/php-timer/compare/5.0.3...6.0.0 [5.0.3]: https://github.com/sebastianbergmann/php-timer/compare/5.0.2...5.0.3 [5.0.2]: https://github.com/sebastianbergmann/php-timer/compare/5.0.1...5.0.2 [5.0.1]: https://github.com/sebastianbergmann/php-timer/compare/5.0.0...5.0.1 diff --git a/app/vendor/phpunit/php-timer/LICENSE b/app/vendor/phpunit/php-timer/LICENSE index 4193d8ae6..fd52ce618 100644 --- a/app/vendor/phpunit/php-timer/LICENSE +++ b/app/vendor/phpunit/php-timer/LICENSE @@ -1,33 +1,29 @@ -phpunit/php-timer +BSD 3-Clause License -Copyright (c) 2010-2020, Sebastian Bergmann . +Copyright (c) 2010-2025, Sebastian Bergmann All rights reserved. Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: +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. +1. 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. +2. 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 Sebastian Bergmann nor the names of his - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. +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/app/vendor/phpunit/php-timer/README.md b/app/vendor/phpunit/php-timer/README.md index a7d1e70de..bc9157c32 100644 --- a/app/vendor/phpunit/php-timer/README.md +++ b/app/vendor/phpunit/php-timer/README.md @@ -1,7 +1,8 @@ # phpunit/php-timer +[![Latest Stable Version](https://poser.pugx.org/phpunit/php-timer/v)](https://packagist.org/packages/phpunit/php-timer) [![CI Status](https://github.com/sebastianbergmann/php-timer/workflows/CI/badge.svg)](https://github.com/sebastianbergmann/php-timer/actions) -[![Type Coverage](https://shepherd.dev/github/sebastianbergmann/php-timer/coverage.svg)](https://shepherd.dev/github/sebastianbergmann/php-timer) +[![codecov](https://codecov.io/gh/sebastianbergmann/php-timer/branch/main/graph/badge.svg)](https://codecov.io/gh/sebastianbergmann/php-timer) Utility class for timing things, factored out of PHPUnit into a stand-alone component. diff --git a/app/vendor/phpunit/php-timer/SECURITY.md b/app/vendor/phpunit/php-timer/SECURITY.md new file mode 100644 index 000000000..d88ff0019 --- /dev/null +++ b/app/vendor/phpunit/php-timer/SECURITY.md @@ -0,0 +1,30 @@ +# Security Policy + +If you believe you have found a security vulnerability in the library that is developed in this repository, please report it to us through coordinated disclosure. + +**Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.** + +Instead, please email `sebastian@phpunit.de`. + +Please include as much of the information listed below as you can to help us better understand and resolve the issue: + +* The type of issue +* Full paths of source file(s) related to the manifestation of the issue +* The location of the affected source code (tag/branch/commit or direct URL) +* Any special configuration required to reproduce the issue +* Step-by-step instructions to reproduce the issue +* Proof-of-concept or exploit code (if possible) +* Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +## Web Context + +The library that is developed in this repository was either extracted from [PHPUnit](https://github.com/sebastianbergmann/phpunit) or developed specifically as a dependency for PHPUnit. + +The library is developed with a focus on development environments and the command-line. No specific testing or hardening with regard to using the library in an HTTP or web context or with untrusted input data is performed. The library might also contain functionality that intentionally exposes internal application data for debugging purposes. + +If the library is used in a web application, the application developer is responsible for filtering inputs or escaping outputs as necessary and for verifying that the used functionality is safe for use within the intended context. + +Vulnerabilities specific to the use outside a development context will be fixed as applicable, provided that the fix does not have an averse effect on the primary use case for development purposes. + diff --git a/app/vendor/phpunit/php-timer/composer.json b/app/vendor/phpunit/php-timer/composer.json index 001701c24..89c398425 100644 --- a/app/vendor/phpunit/php-timer/composer.json +++ b/app/vendor/phpunit/php-timer/composer.json @@ -15,18 +15,19 @@ } ], "support": { - "issues": "https://github.com/sebastianbergmann/php-timer/issues" + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "security": "https://github.com/sebastianbergmann/php-timer/security/policy" }, "prefer-stable": true, "require": { - "php": ">=7.3" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^12.0" }, "config": { "platform": { - "php": "7.3.0" + "php": "8.3.0" }, "optimize-autoloader": true, "sort-packages": true @@ -38,7 +39,7 @@ }, "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-main": "8.0-dev" } } } diff --git a/app/vendor/phpunit/php-timer/src/Duration.php b/app/vendor/phpunit/php-timer/src/Duration.php index e52bf018f..1ab58b4e3 100644 --- a/app/vendor/phpunit/php-timer/src/Duration.php +++ b/app/vendor/phpunit/php-timer/src/Duration.php @@ -13,34 +13,15 @@ use function sprintf; /** - * @psalm-immutable + * @immutable */ -final class Duration +final readonly class Duration { - /** - * @var float - */ - private $nanoseconds; - - /** - * @var int - */ - private $hours; - - /** - * @var int - */ - private $minutes; - - /** - * @var int - */ - private $seconds; - - /** - * @var int - */ - private $milliseconds; + private float $nanoseconds; + private int $hours; + private int $minutes; + private int $seconds; + private int $milliseconds; public static function fromMicroseconds(float $microseconds): self { diff --git a/app/vendor/phpunit/php-timer/src/ResourceUsageFormatter.php b/app/vendor/phpunit/php-timer/src/ResourceUsageFormatter.php index ad7926277..f797bc033 100644 --- a/app/vendor/phpunit/php-timer/src/ResourceUsageFormatter.php +++ b/app/vendor/phpunit/php-timer/src/ResourceUsageFormatter.php @@ -17,9 +17,9 @@ final class ResourceUsageFormatter { /** - * @psalm-var array + * @var array */ - private const SIZES = [ + private const array SIZES = [ 'GB' => 1073741824, 'MB' => 1048576, 'KB' => 1024, @@ -30,7 +30,7 @@ public function resourceUsage(Duration $duration): string return sprintf( 'Time: %s, Memory: %s', $duration->asString(), - $this->bytesToString(memory_get_peak_usage(true)) + $this->bytesToString(memory_get_peak_usage(true)), ); } @@ -41,20 +41,20 @@ public function resourceUsageSinceStartOfRequest(): string { if (!isset($_SERVER['REQUEST_TIME_FLOAT'])) { throw new TimeSinceStartOfRequestNotAvailableException( - 'Cannot determine time at which the request started because $_SERVER[\'REQUEST_TIME_FLOAT\'] is not available' + 'Cannot determine time at which the request started because $_SERVER[\'REQUEST_TIME_FLOAT\'] is not available', ); } if (!is_float($_SERVER['REQUEST_TIME_FLOAT'])) { throw new TimeSinceStartOfRequestNotAvailableException( - 'Cannot determine time at which the request started because $_SERVER[\'REQUEST_TIME_FLOAT\'] is not of type float' + 'Cannot determine time at which the request started because $_SERVER[\'REQUEST_TIME_FLOAT\'] is not of type float', ); } return $this->resourceUsage( Duration::fromMicroseconds( - (1000000 * (microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'])) - ) + (1000000 * (microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'])), + ), ); } @@ -62,7 +62,7 @@ private function bytesToString(int $bytes): string { foreach (self::SIZES as $unit => $value) { if ($bytes >= $value) { - return sprintf('%.2f %s', $bytes >= 1024 ? $bytes / $value : $bytes, $unit); + return sprintf('%.2f %s', $bytes / $value, $unit); } } diff --git a/app/vendor/phpunit/php-timer/src/Timer.php b/app/vendor/phpunit/php-timer/src/Timer.php index 0917109be..d59eef639 100644 --- a/app/vendor/phpunit/php-timer/src/Timer.php +++ b/app/vendor/phpunit/php-timer/src/Timer.php @@ -15,9 +15,9 @@ final class Timer { /** - * @psalm-var list + * @var list */ - private $startTimes = []; + private array $startTimes = []; public function start(): void { @@ -31,7 +31,7 @@ public function stop(): Duration { if (empty($this->startTimes)) { throw new NoActiveTimerException( - 'Timer::start() has to be called before Timer::stop()' + 'Timer::start() has to be called before Timer::stop()', ); } diff --git a/app/vendor/phpunit/phpunit/ChangeLog-12.3.md b/app/vendor/phpunit/phpunit/ChangeLog-12.3.md new file mode 100644 index 000000000..fb0991261 --- /dev/null +++ b/app/vendor/phpunit/phpunit/ChangeLog-12.3.md @@ -0,0 +1,101 @@ +# Changes in PHPUnit 12.3 + +All notable changes of the PHPUnit 12.3 release series are documented in this file using the [Keep a CHANGELOG](https://keepachangelog.com/) principles. + +## [12.3.7] - 2025-08-28 + +### Changed + +* `#[IgnorePhpunitDeprecations]` is now considered for test runner deprecations (where applicable) + +## [12.3.6] - 2025-08-20 + +### Changed + +* Do not configure `report_memleaks` setting (which will be deprecated in PHP 8.5) for PHPT processes + +## [12.3.5] - 2025-08-16 + +### Changed + +* [#6319](https://github.com/sebastianbergmann/phpunit/issues/6319): Detect premature end of PHPUnit's main PHP process +* [#6321](https://github.com/sebastianbergmann/phpunit/issues/6321): Allow `error_reporting=E_ALL` for `--check-php-configuration` + +### Fixed + +* [#5863](https://github.com/sebastianbergmann/phpunit/issues/5863): TestDox printer does not show previous exception +* [#6102](https://github.com/sebastianbergmann/phpunit/issues/6102): `expectUserDeprecationMessage*()` fails when test is run in separate process + +## [12.3.4] - 2025-08-12 + +### Changed + +* [#6308](https://github.com/sebastianbergmann/phpunit/pull/6308): Improve output of `--check-php-configuration` +* The version number for the test result cache file has been incremented to reflect that its structure for PHPUnit 12.3 is not compatible with its structure for PHPUnit 8.5 and PHPUnit 9.6 + +### Fixed + +* [#6197](https://github.com/sebastianbergmann/phpunit/issues/6197): `ini_set('error_log')` sets filepath outside `open_basedir` +* [#6279](https://github.com/sebastianbergmann/phpunit/issues/6279): Deprecation triggered in data provider method affects all test methods using that data provider method +* [#6281](https://github.com/sebastianbergmann/phpunit/issues/6281): Exceptions raised in after-test method are not reported for skipped tests + +## [12.3.3] - 2025-08-11 + +### Fixed + +* [#6304](https://github.com/sebastianbergmann/phpunit/issues/6304): PHPUnit 11.5.29 hangs when a test runner deprecation is triggered and process isolation is used (this reverts "`#[IgnorePhpunitDeprecations]` is now considered for test runner deprecations" from PHPUnit 12.3.1) + +## [12.3.2] - 2025-08-10 + +### Changed + +* [#6300](https://github.com/sebastianbergmann/phpunit/issues/6300): Emit warning when the name of a data provider method begins with `test` +* Do not use `SplObjectStorage` methods that will be deprecated in PHP 8.5 + +## [12.3.1] - 2025-08-09 + +### Added + +* [#6297](https://github.com/sebastianbergmann/phpunit/issues/6297): `--check-php-configuration` CLI option for checking whether PHP is configured for testing + +### Changed + +* `#[IgnorePhpunitDeprecations]` is now considered for test runner deprecations (where applicable) + +### Fixed + +* [#6160](https://github.com/sebastianbergmann/phpunit/issues/6160): Baseline file in a subdirectory contains absolute paths +* [#6294](https://github.com/sebastianbergmann/phpunit/issues/6294): Silent failure of PHP fatal errors +* Errors due to invalid data provided using `#[TestWith]` or `#[TestWithJson]` attributes are now properly reported +* The `DataProviderMethodFinished` event is now also emitted when the provided data set has an invalid key + +## [12.3.0] - 2025-08-01 + +### Added + +* [#3795](https://github.com/sebastianbergmann/phpunit/issues/3795): Bootstrap scripts specific to test suites +* [#6268](https://github.com/sebastianbergmann/phpunit/pull/6268): `#[IgnorePHPUnitWarnings]` attribute for ignoring PHPUnit warnings +* `#[TestDoxFormatter]` and `#[TestDoxFormatterExternal]` attributes for configuring a custom TestDox formatter for tests that use data from data providers +* `TestRunner\ChildProcessErrored` event +* `Configuration::includeTestSuites()` and `Configuration::excludeTestSuites()` + +### Changed + +* [#6237](https://github.com/sebastianbergmann/phpunit/issues/6237): Do not run tests when code coverage analysis is requested but code coverage data cannot be collected +* [#6272](https://github.com/sebastianbergmann/phpunit/issues/6272): Use `@` format (compatible with `--filter` CLI option) in defect messages +* [#6273](https://github.com/sebastianbergmann/phpunit/pull/6273): Warn when `#[DataProvider*]` attributes are mixed with `#[TestWith*]` attributes + +### Deprecated + +* [#6229](https://github.com/sebastianbergmann/phpunit/issues/6229): `Configuration::includeTestSuite()`, use `Configuration::includeTestSuites()` instead +* [#6229](https://github.com/sebastianbergmann/phpunit/issues/6229): `Configuration::excludeTestSuite()`, use `Configuration::excludeTestSuites()` instead +* [#6246](https://github.com/sebastianbergmann/phpunit/issues/6246): Using `#[CoversNothing]` on a test method + +[12.3.7]: https://github.com/sebastianbergmann/phpunit/compare/12.3.6...12.3.7 +[12.3.6]: https://github.com/sebastianbergmann/phpunit/compare/12.3.5...12.3.6 +[12.3.5]: https://github.com/sebastianbergmann/phpunit/compare/12.3.4...12.3.5 +[12.3.4]: https://github.com/sebastianbergmann/phpunit/compare/12.3.3...12.3.4 +[12.3.3]: https://github.com/sebastianbergmann/phpunit/compare/12.3.2...12.3.3 +[12.3.2]: https://github.com/sebastianbergmann/phpunit/compare/12.3.1...12.3.2 +[12.3.1]: https://github.com/sebastianbergmann/phpunit/compare/12.3.0...12.3.1 +[12.3.0]: https://github.com/sebastianbergmann/phpunit/compare/12.2.9...12.3.0 diff --git a/app/vendor/phpunit/phpunit/ChangeLog-9.6.md b/app/vendor/phpunit/phpunit/ChangeLog-9.6.md deleted file mode 100644 index c42084404..000000000 --- a/app/vendor/phpunit/phpunit/ChangeLog-9.6.md +++ /dev/null @@ -1,195 +0,0 @@ -# Changes in PHPUnit 9.6 - -All notable changes of the PHPUnit 9.6 release series are documented in this file using the [Keep a CHANGELOG](https://keepachangelog.com/) principles. - -## [9.6.23] - 2025-05-02 - -### Changed - -* [#5956](https://github.com/sebastianbergmann/phpunit/issues/5956): Improved handling of deprecated `E_STRICT` constant -* Improved message when test is considered risky for printing unexpected output - -## [9.6.22] - 2024-12-05 - -### Fixed - -* [#6071](https://github.com/sebastianbergmann/phpunit/issues/6071): PHP Archives (PHARs) of PHPUnit 8.5 and PHPUnit 9.6 bundle outdated versions of Prophecy - -## [9.6.21] - 2024-09-19 - -### Changed - -* [#5956](https://github.com/sebastianbergmann/phpunit/issues/5956): Deprecation of the `E_STRICT` constant in PHP 8.4 -* Removed `.phpstorm.meta.php` file as methods such as `TestCase::createStub()` use generics / template types for their return types and PhpStorm, for example, uses that information - -## [9.6.20] - 2024-07-10 - -### Changed - -* Updated dependencies (so that users that install using Composer's `--prefer-lowest` CLI option also get recent versions) - -## [9.6.19] - 2024-04-05 - -### Changed - -* The namespaces of dependencies are now prefixed with `PHPUnitPHAR` instead of just `PHPUnit` for the PHAR distribution of PHPUnit - -## [9.6.18] - 2024-03-21 - -### Changed - -* [#5763](https://github.com/sebastianbergmann/phpunit/issues/5763): Release nullable type changes for PHPUnit 9.6 - -## [9.6.17] - 2024-02-23 - -### Changed - -* Improve output of `--check-version` CLI option -* Improve description of `--check-version` CLI option -* Show help for `--manifest`, `--sbom`, and `--composer-lock` when the PHAR is used - -### Fixed - -* [#5712](https://github.com/sebastianbergmann/phpunit/issues/5712): Update dependencies for PHAR distribution of PHPUnit 9.6 - -## [9.6.16] - 2024-01-19 - -### Changed - -* Make PHAR build reproducible (the only remaining differences were in the timestamps for the files in the PHAR) - -### Fixed - -* [#5516](https://github.com/sebastianbergmann/phpunit/issues/5516): Assertions that use the `LogicalNot` constraint (`assertNotEquals()`, `assertStringNotContainsString()`, ...) can generate confusing failure messages -* [#5666](https://github.com/sebastianbergmann/phpunit/issues/5666): `--no-extensions` CLI option does not work -* [#5673](https://github.com/sebastianbergmann/phpunit/issues/5673): Confusing error message when migration of a configuration is requested that does not need to be migrated - -## [9.6.15] - 2023-12-01 - -### Fixed - -* [#5596](https://github.com/sebastianbergmann/phpunit/issues/5596): `PHPUnit\Framework\TestCase` has `@internal` annotation in PHAR - -## [9.6.14] - 2023-12-01 - -### Added - -* [#5577](https://github.com/sebastianbergmann/phpunit/issues/5577): `--composer-lock` CLI option for PHAR binary that displays the `composer.lock` used to build the PHAR - -## [9.6.13] - 2023-09-19 - -### Changed - -* The child processes used for process isolation now use temporary files to communicate their result to the parent process - -## [9.6.12] - 2023-09-12 - -### Changed - -* [#5508](https://github.com/sebastianbergmann/phpunit/pull/5508): Generate code coverage report in PHP format as first in list to avoid serializing cache data - -## [9.6.11] - 2023-08-19 - -### Added - -* [#5478](https://github.com/sebastianbergmann/phpunit/pull/5478): `assertObjectHasProperty()` and `assertObjectNotHasProperty()` - -## [9.6.10] - 2023-07-10 - -### Changed - -* [#5419](https://github.com/sebastianbergmann/phpunit/pull/5419): Allow empty `` element in XML configuration - -## [9.6.9] - 2023-06-11 - -### Fixed - -* [#5405](https://github.com/sebastianbergmann/phpunit/issues/5405): XML configuration migration does not migrate `whitelist/file` elements -* Always use `X.Y.Z` version number (and not just `X.Y`) of PHPUnit's version when checking whether a PHAR-distributed extension is compatible - -## [9.6.8] - 2023-05-11 - -### Fixed - -* [#5345](https://github.com/sebastianbergmann/phpunit/issues/5345): No stack trace shown for previous exceptions during bootstrap - -## [9.6.7] - 2023-04-14 - -### Fixed - -* Tests that have `@doesNotPerformAssertions` do not contribute to code coverage - -## [9.6.6] - 2023-03-27 - -### Fixed - -* [#5270](https://github.com/sebastianbergmann/phpunit/issues/5270): `GlobalState::getIniSettingsAsString()` generates code that triggers warnings - -## [9.6.5] - 2023-03-09 - -### Changed - -* Backported the HTML and CSS improvements made to the `--testdox-html` from PHPUnit 10 - -### Fixed - -* [#5205](https://github.com/sebastianbergmann/phpunit/issues/5205): Wrong default value for optional parameter of `PHPUnit\Util\Test::parseTestMethodAnnotations()` causes `ReflectionException` - -## [9.6.4] - 2023-02-27 - -### Fixed - -* [#5186](https://github.com/sebastianbergmann/phpunit/issues/5186): SBOM does not validate - -## [9.6.3] - 2023-02-04 - -### Fixed - -* [#5164](https://github.com/sebastianbergmann/phpunit/issues/5164): `markTestSkipped()` not handled correctly when called in "before first test" method - -## [9.6.2] - 2023-02-04 - -### Fixed - -* [#4618](https://github.com/sebastianbergmann/phpunit/issues/4618): Support for generators in `assertCount()` etc. is not marked as deprecated in PHPUnit 9.6 - -## [9.6.1] - 2023-02-03 - -### Fixed - -* [#5073](https://github.com/sebastianbergmann/phpunit/issues/5073): `--no-extensions` CLI option only prevents extension PHARs from being loaded -* [#5160](https://github.com/sebastianbergmann/phpunit/issues/5160): Deprecate `assertClassHasAttribute()`, `assertClassNotHasAttribute()`, `assertClassHasStaticAttribute()`, `assertClassNotHasStaticAttribute()`, `assertObjectHasAttribute()`, `assertObjectNotHasAttribute()`, `classHasAttribute()`, `classHasStaticAttribute()`, and `objectHasAttribute()` - -## [9.6.0] - 2023-02-03 - -### Changed - -* [#5062](https://github.com/sebastianbergmann/phpunit/issues/5062): Deprecate `expectDeprecation()`, `expectDeprecationMessage()`, `expectDeprecationMessageMatches()`, `expectError()`, `expectErrorMessage()`, `expectErrorMessageMatches()`, `expectNotice()`, `expectNoticeMessage()`, `expectNoticeMessageMatches()`, `expectWarning()`, `expectWarningMessage()`, and `expectWarningMessageMatches()` -* [#5063](https://github.com/sebastianbergmann/phpunit/issues/5063): Deprecate `withConsecutive()` -* [#5064](https://github.com/sebastianbergmann/phpunit/issues/5064): Deprecate `PHPUnit\Framework\TestCase::getMockClass()` -* [#5132](https://github.com/sebastianbergmann/phpunit/issues/5132): Deprecate `Test` suffix for abstract test case classes - -[9.6.23]: https://github.com/sebastianbergmann/phpunit/compare/9.6.22...9.6.23 -[9.6.22]: https://github.com/sebastianbergmann/phpunit/compare/9.6.21...9.6.22 -[9.6.21]: https://github.com/sebastianbergmann/phpunit/compare/9.6.20...9.6.21 -[9.6.20]: https://github.com/sebastianbergmann/phpunit/compare/9.6.19...9.6.20 -[9.6.19]: https://github.com/sebastianbergmann/phpunit/compare/9.6.18...9.6.19 -[9.6.18]: https://github.com/sebastianbergmann/phpunit/compare/9.6.17...9.6.18 -[9.6.17]: https://github.com/sebastianbergmann/phpunit/compare/9.6.16...9.6.17 -[9.6.16]: https://github.com/sebastianbergmann/phpunit/compare/9.6.15...9.6.16 -[9.6.15]: https://github.com/sebastianbergmann/phpunit/compare/9.6.14...9.6.15 -[9.6.14]: https://github.com/sebastianbergmann/phpunit/compare/9.6.13...9.6.14 -[9.6.13]: https://github.com/sebastianbergmann/phpunit/compare/9.6.12...9.6.13 -[9.6.12]: https://github.com/sebastianbergmann/phpunit/compare/9.6.11...9.6.12 -[9.6.11]: https://github.com/sebastianbergmann/phpunit/compare/9.6.10...9.6.11 -[9.6.10]: https://github.com/sebastianbergmann/phpunit/compare/9.6.9...9.6.10 -[9.6.9]: https://github.com/sebastianbergmann/phpunit/compare/9.6.8...9.6.9 -[9.6.8]: https://github.com/sebastianbergmann/phpunit/compare/9.6.7...9.6.8 -[9.6.7]: https://github.com/sebastianbergmann/phpunit/compare/9.6.6...9.6.7 -[9.6.6]: https://github.com/sebastianbergmann/phpunit/compare/9.6.5...9.6.6 -[9.6.5]: https://github.com/sebastianbergmann/phpunit/compare/9.6.4...9.6.5 -[9.6.4]: https://github.com/sebastianbergmann/phpunit/compare/9.6.3...9.6.4 -[9.6.3]: https://github.com/sebastianbergmann/phpunit/compare/9.6.2...9.6.3 -[9.6.2]: https://github.com/sebastianbergmann/phpunit/compare/9.6.1...9.6.2 -[9.6.1]: https://github.com/sebastianbergmann/phpunit/compare/9.6.0...9.6.1 -[9.6.0]: https://github.com/sebastianbergmann/phpunit/compare/9.5.28...9.6.0 diff --git a/app/vendor/phpunit/phpunit/DEPRECATIONS.md b/app/vendor/phpunit/phpunit/DEPRECATIONS.md index b8349592b..b3f3fb98c 100644 --- a/app/vendor/phpunit/phpunit/DEPRECATIONS.md +++ b/app/vendor/phpunit/phpunit/DEPRECATIONS.md @@ -4,14 +4,24 @@ This functionality is currently [soft-deprecated](https://phpunit.de/backward-compatibility.html#soft-deprecation): -### Writing Tests - -#### Test Double API +### Extending PHPUnit -| Issue | Description | Since | Replacement | -|-------------------------------------------------------------------|-----------------------------------|-------|-------------| -| [#3687](https://github.com/sebastianbergmann/phpunit/issues/3687) | `MockBuilder::setMethods()` | 8.3.0 | | -| [#3687](https://github.com/sebastianbergmann/phpunit/issues/3687) | `MockBuilder::setMethodsExcept()` | 9.6.0 | | +| Issue | Description | Since | Replacement | +|-------------------------------------------------------------------|---------------------------------------------|--------|-------------------------------------------------| +| [#6140](https://github.com/sebastianbergmann/phpunit/issues/6140) | `AfterTestMethodCalled::testCaseClass()` | 12.1.0 | `AfterTestMethodCalled::test()->className()` | +| [#6140](https://github.com/sebastianbergmann/phpunit/issues/6140) | `AfterTestMethodErrored::testCaseClass()` | 12.1.0 | `AfterTestMethodErrored::test()->className()` | +| [#6140](https://github.com/sebastianbergmann/phpunit/issues/6140) | `AfterTestMethodFinished::testCaseClass()` | 12.1.0 | `AfterTestMethodFinished::test()->className()` | +| [#6140](https://github.com/sebastianbergmann/phpunit/issues/6140) | `BeforeTestMethodCalled::testCaseClass()` | 12.1.0 | `BeforeTestMethodCalled::test()->className()` | +| [#6140](https://github.com/sebastianbergmann/phpunit/issues/6140) | `BeforeTestMethodErrored::testCaseClass()` | 12.1.0 | `BeforeTestMethodErrored::test()->className()` | +| [#6140](https://github.com/sebastianbergmann/phpunit/issues/6140) | `BeforeTestMethodFinished::testCaseClass()` | 12.1.0 | `BeforeTestMethodFinished::test()->className()` | +| [#6140](https://github.com/sebastianbergmann/phpunit/issues/6140) | `PreConditionCalled::testCaseClass()` | 12.1.0 | `PreConditionCalled::test()->className()` | +| [#6140](https://github.com/sebastianbergmann/phpunit/issues/6140) | `PreConditionErrored::testCaseClass()` | 12.1.0 | `PreConditionErrored::test()->className()` | +| [#6140](https://github.com/sebastianbergmann/phpunit/issues/6140) | `PreConditionFinished::testCaseClass()` | 12.1.0 | `PreConditionFinished::test()->className()` | +| [#6140](https://github.com/sebastianbergmann/phpunit/issues/6140) | `PostConditionCalled::testCaseClass()` | 12.1.0 | `PostConditionCalled::test()->className()` | +| [#6140](https://github.com/sebastianbergmann/phpunit/issues/6140) | `PostConditionErrored::testCaseClass()` | 12.1.0 | `PostConditionErrored::test()->className()` | +| [#6140](https://github.com/sebastianbergmann/phpunit/issues/6140) | `PostConditionFinished::testCaseClass()` | 12.1.0 | `PostConditionFinished::test()->className()` | +| [#6229](https://github.com/sebastianbergmann/phpunit/issues/6229) | `Configuration::includeTestSuite()` | 12.3.0 | `Configuration::includeTestSuites()` | +| [#6229](https://github.com/sebastianbergmann/phpunit/issues/6229) | `Configuration::excludeTestSuite()` | 12.3.0 | `Configuration::excludeTestSuites()` | ## Hard Deprecations @@ -21,69 +31,16 @@ This functionality is currently [hard-deprecated](https://phpunit.de/backward-co #### Assertions, Constraints, and Expectations -| Issue | Description | Since | Replacement | -|-------------------------------------------------------------------|------------------------------------------------|-------|---------------------------------------------------| -| [#4062](https://github.com/sebastianbergmann/phpunit/issues/4062) | `TestCase::assertNotIsReadable()` | 9.1.0 | `TestCase::assertIsNotReadable()` | -| [#4065](https://github.com/sebastianbergmann/phpunit/issues/4065) | `TestCase::assertNotIsWritable()` | 9.1.0 | `TestCase::assertIsNotWritable()` | -| [#4068](https://github.com/sebastianbergmann/phpunit/issues/4068) | `TestCase::assertDirectoryNotExists()` | 9.1.0 | `TestCase::assertDirectoryDoesNotExist()` | -| [#4071](https://github.com/sebastianbergmann/phpunit/issues/4071) | `TestCase::assertDirectoryNotIsReadable()` | 9.1.0 | `TestCase::assertDirectoryIsNotReadable()` | -| [#4074](https://github.com/sebastianbergmann/phpunit/issues/4074) | `TestCase::assertDirectoryNotIsWritable()` | 9.1.0 | `TestCase::assertDirectoryIsNotWritable()` | -| [#4077](https://github.com/sebastianbergmann/phpunit/issues/4077) | `TestCase::assertFileNotExists()` | 9.1.0 | `TestCase::assertFileDoesNotExist()` | -| [#4080](https://github.com/sebastianbergmann/phpunit/issues/4080) | `TestCase::assertFileNotIsReadable()` | 9.1.0 | `TestCase::assertFileIsNotReadable()` | -| [#4083](https://github.com/sebastianbergmann/phpunit/issues/4083) | `TestCase::assertFileNotIsWritable()` | 9.1.0 | `TestCase::assertFileIsNotWritable()` | -| [#4086](https://github.com/sebastianbergmann/phpunit/issues/4086) | `TestCase::assertRegExp()` | 9.1.0 | `TestCase::assertMatchesRegularExpression()` | -| [#4089](https://github.com/sebastianbergmann/phpunit/issues/4089) | `TestCase::assertNotRegExp()` | 9.1.0 | `TestCase::assertDoesNotMatchRegularExpression()` | -| [#4091](https://github.com/sebastianbergmann/phpunit/issues/4091) | `TestCase::assertEqualXMLStructure()` | 9.1.0 | | -| [#5062](https://github.com/sebastianbergmann/phpunit/issues/5062) | `TestCase::expectDeprecation()` | 9.6.0 | | -| [#5062](https://github.com/sebastianbergmann/phpunit/issues/5062) | `TestCase::expectDeprecationMessage()` | 9.6.0 | | -| [#5062](https://github.com/sebastianbergmann/phpunit/issues/5062) | `TestCase::expectDeprecationMessageMatches()` | 9.6.0 | | -| [#5062](https://github.com/sebastianbergmann/phpunit/issues/5062) | `TestCase::expectError()` | 9.6.0 | | -| [#5062](https://github.com/sebastianbergmann/phpunit/issues/5062) | `TestCase::expectErrorMessage()` | 9.6.0 | | -| [#5062](https://github.com/sebastianbergmann/phpunit/issues/5062) | `TestCase::expectErrorMessageMatches()` | 9.6.0 | | -| [#5062](https://github.com/sebastianbergmann/phpunit/issues/5062) | `TestCase::expectNotice()` | 9.6.0 | | -| [#5062](https://github.com/sebastianbergmann/phpunit/issues/5062) | `TestCase::expectNoticeMessage()` | 9.6.0 | | -| [#5062](https://github.com/sebastianbergmann/phpunit/issues/5062) | `TestCase::expectNoticeMessageMatches()` | 9.6.0 | | -| [#5062](https://github.com/sebastianbergmann/phpunit/issues/5062) | `TestCase::expectWarning()` | 9.6.0 | | -| [#5062](https://github.com/sebastianbergmann/phpunit/issues/5062) | `TestCase::expectWarningMessage()` | 9.6.0 | | -| [#5062](https://github.com/sebastianbergmann/phpunit/issues/5062) | `TestCase::expectWarningMessageMatches()` | 9.6.0 | | -| [#4601](https://github.com/sebastianbergmann/phpunit/issues/4601) | `TestCase::assertClassHasAttribute()` | 9.6.1 | | -| [#4601](https://github.com/sebastianbergmann/phpunit/issues/4601) | `TestCase::assertClassNotHasAttribute()` | 9.6.1 | | -| [#4601](https://github.com/sebastianbergmann/phpunit/issues/4601) | `TestCase::assertClassHasStaticAttribute()` | 9.6.1 | | -| [#4601](https://github.com/sebastianbergmann/phpunit/issues/4601) | `TestCase::assertClassNotHasStaticAttribute()` | 9.6.1 | | -| [#4601](https://github.com/sebastianbergmann/phpunit/issues/4601) | `TestCase::assertObjectHasAttribute()` | 9.6.1 | `TestCase::assertObjectHasProperty()` | -| [#4601](https://github.com/sebastianbergmann/phpunit/issues/4601) | `TestCase::assertObjectNotHasAttribute()` | 9.6.1 | `TestCase::assertObjectNotHasProperty()` | -| [#4601](https://github.com/sebastianbergmann/phpunit/issues/4601) | `TestCase::classHasAttribute()` | 9.6.1 | | -| [#4601](https://github.com/sebastianbergmann/phpunit/issues/4601) | `TestCase::classHasStaticAttribute()` | 9.6.1 | | -| [#4601](https://github.com/sebastianbergmann/phpunit/issues/4601) | `TestCase::objectHasAttribute()` | 9.6.1 | | -| [#4601](https://github.com/sebastianbergmann/phpunit/issues/4601) | `ClassHasAttribute` | 9.6.1 | | -| [#4601](https://github.com/sebastianbergmann/phpunit/issues/4601) | `ClassHasStaticAttribute` | 9.6.1 | | -| [#4601](https://github.com/sebastianbergmann/phpunit/issues/4601) | `ObjectHasAttribute` | 9.6.1 | `ObjectHasProperty` | - -#### Test Double API +| Issue | Description | Since | Replacement | +|-------------------------------------------------------------------|-------------------------------------------|--------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [#6052](https://github.com/sebastianbergmann/phpunit/issues/6052) | `Assert::isType()` | 11.5.0 | Use `isArray()`, `isBool()`, `isCallable()`, `isFloat()`, `isInt()`, `isIterable()`, `isNull()`, `isNumeric()`, `isObject()`, `isResource()`, `isClosedResource()`, `isScalar()`, or `isString()` instead | +| [#6055](https://github.com/sebastianbergmann/phpunit/issues/6055) | `Assert::assertContainsOnly()` | 11.5.0 | Use `assertContainsOnlyArray()`, `assertContainsOnlyBool()`, `assertContainsOnlyCallable()`, `assertContainsOnlyFloat()`, `assertContainsOnlyInt()`, `assertContainsOnlyIterable()`, `assertContainsOnlyNumeric()`, `assertContainsOnlyObject()`, `assertContainsOnlyResource()`, `assertContainsOnlyClosedResource()`, `assertContainsOnlyScalar()`, or `assertContainsOnlyString()` instead | +| [#6055](https://github.com/sebastianbergmann/phpunit/issues/6055) | `Assert::assertNotContainsOnly()` | 11.5.0 | Use `assertContainsNotOnlyArray()`, `assertContainsNotOnlyBool()`, `assertContainsNotOnlyCallable()`, `assertContainsNotOnlyFloat()`, `assertContainsNotOnlyInt()`, `assertContainsNotOnlyIterable()`, `assertContainsNotOnlyNumeric()`, `assertContainsNotOnlyObject()`, `assertContainsNotOnlyResource()`, `assertContainsNotOnlyClosedResource()`, `assertContainsNotOnlyScalar()`, or `assertContainsNotOnlyString()` instead | +| [#6059](https://github.com/sebastianbergmann/phpunit/issues/6059) | `Assert::containsOnly()` | 11.5.0 | Use `containsOnlyArray()`, `containsOnlyBool()`, `containsOnlyCallable()`, `containsOnlyFloat()`, `containsOnlyInt()`, `containsOnlyIterable()`, `containsOnlyNumeric()`, `containsOnlyObject()`, `containsOnlyResource()`, `containsOnlyClosedResource()`, `containsOnlyScalar()`, or `containsOnlyString()` instead | +| [#6246](https://github.com/sebastianbergmann/phpunit/issues/6246) | Using `#[CoversNothing]` on a test method | 12.3.0 | | -| Issue | Description | Since | Replacement | -|-------------------------------------------------------------------|---------------------------------------|-------|-------------------------------------------------------------------------| -| [#4141](https://github.com/sebastianbergmann/phpunit/issues/4141) | `TestCase::prophesize()` | 9.1.0 | [phpspec/prophecy-phpunit](https://github.com/phpspec/prophecy-phpunit) | -| [#4297](https://github.com/sebastianbergmann/phpunit/issues/4297) | `TestCase::at()` | 9.3.0 | | -| [#4297](https://github.com/sebastianbergmann/phpunit/issues/4297) | `InvokedAtIndex` | 9.3.0 | | -| [#5063](https://github.com/sebastianbergmann/phpunit/issues/5063) | `InvocationMocker::withConsecutive()` | 9.6.0 | | -| [#5063](https://github.com/sebastianbergmann/phpunit/issues/5063) | `ConsecutiveParameters` | 9.6.0 | | -| [#5064](https://github.com/sebastianbergmann/phpunit/issues/5064) | `TestCase::getMockClass()` | 9.6.0 | | - -#### Miscellaneous - -| Issue | Description | Since | Replacement | -|-------------------------------------------------------------------|----------------------------------------------|-------|------------------------------------------------| -| [#5132](https://github.com/sebastianbergmann/phpunit/issues/5132) | `Test` suffix for abstract test case classes | | | -| | `TestCase::$backupGlobalsBlacklist` | 9.3.0 | `TestCase::$backupGlobalsExcludeList` | -| | `TestCase::$backupStaticAttributesBlacklist` | 9.3.0 | `TestCase::$backupStaticAttributesExcludeList` | - -### Extending PHPUnit +### Running Tests -| Issue | Description | Since | Replacement | -|-------------------------------------------------------------------|--------------------------------------|-------|-------------------------------------------------------------| -| [#4676](https://github.com/sebastianbergmann/phpunit/issues/4676) | `TestListener` | 8.0.0 | [Event System](https://docs.phpunit.de/en/10.3/events.html) | -| [#4039](https://github.com/sebastianbergmann/phpunit/issues/4039) | `Command::handleLoader()` | 9.1.0 | | -| [#4039](https://github.com/sebastianbergmann/phpunit/issues/4039) | `TestSuiteLoader` | 9.1.0 | | -| [#4039](https://github.com/sebastianbergmann/phpunit/issues/4039) | `StandardTestSuiteLoader` | 9.1.0 | | -| [#4676](https://github.com/sebastianbergmann/phpunit/issues/4676) | `TestListenerDefaultImplementation` | 8.2.4 | [Event System](https://docs.phpunit.de/en/10.3/events.html) | +| Issue | Description | Since | Replacement | +|-------------------------------------------------------------------|------------------------------------------|--------|--------------------------------------------------------| +| [#6240](https://github.com/sebastianbergmann/phpunit/issues/6240) | `--dont-report-useless-tests` CLI option | 12.2.3 | Use `--do-not-report-useless-tests` CLI option instead | diff --git a/app/vendor/phpunit/phpunit/README.md b/app/vendor/phpunit/phpunit/README.md index 8aff03453..afec18c22 100644 --- a/app/vendor/phpunit/phpunit/README.md +++ b/app/vendor/phpunit/phpunit/README.md @@ -1,14 +1,20 @@ -# PHPUnit +[![PHPUnit](.github/img/phpunit.svg)](https://phpunit.de/?ref=github) -[![Latest Stable Version](https://poser.pugx.org/phpunit/phpunit/v)](https://packagist.org/packages/phpunit/phpunit) [![CI Status](https://github.com/sebastianbergmann/phpunit/workflows/CI/badge.svg)](https://github.com/sebastianbergmann/phpunit/actions) -[![codecov](https://codecov.io/gh/sebastianbergmann/phpunit/branch/9.6/graph/badge.svg)](https://codecov.io/gh/sebastianbergmann/phpunit) +[![codecov](https://codecov.io/gh/sebastianbergmann/phpunit/branch/main/graph/badge.svg?token=0yzBUK8Wri)](https://codecov.io/gh/sebastianbergmann/phpunit) +[![Latest Stable Version](https://poser.pugx.org/phpunit/phpunit/v)](https://packagist.org/packages/phpunit/phpunit) +[![Total Downloads](https://poser.pugx.org/phpunit/phpunit/downloads)](https://packagist.org/packages/phpunit/phpunit/stats) +[![Monthly Downloads](https://poser.pugx.org/phpunit/phpunit/d/monthly)](https://packagist.org/packages/phpunit/phpunit/stats) +[![Daily Downloads](https://poser.pugx.org/phpunit/phpunit/d/daily)](https://packagist.org/packages/phpunit/phpunit/stats) -PHPUnit is a programmer-oriented testing framework for PHP. It is an instance of the xUnit architecture for unit testing frameworks. +# PHPUnit + +PHPUnit is a programmer-oriented testing framework for PHP. +It is an instance of the xUnit architecture for unit testing frameworks. ## Installation -We distribute a [PHP Archive (PHAR)](https://php.net/phar) that has all required (as well as some optional) dependencies of PHPUnit bundled in a single file: +We distribute a [PHP Archive (PHAR)](https://php.net/phar) that has all required dependencies of PHPUnit bundled in a single file: ```bash $ wget https://phar.phpunit.de/phpunit-X.Y.phar @@ -18,18 +24,85 @@ $ php phpunit-X.Y.phar --version Please replace `X.Y` with the version of PHPUnit you are interested in. -Alternatively, you may use [Composer](https://getcomposer.org/) to download and install PHPUnit as well as its dependencies. Please refer to the [documentation](https://phpunit.de/documentation.html) for details on how to install PHPUnit. +Alternatively, you may use [Composer](https://getcomposer.org/) to download and install PHPUnit as well as its dependencies. +Please refer to the [documentation](https://phpunit.de/documentation.html?ref=github) for details on how to install PHPUnit. ## Contribute Please refer to [CONTRIBUTING.md](https://github.com/sebastianbergmann/phpunit/blob/main/.github/CONTRIBUTING.md) for information on how to contribute to PHPUnit and its related projects. -## List of Contributors +A big "Thank you!" to everyone who has contributed to PHPUnit! +You can find a detailed list of contributors on every PHPUnit related package on GitHub. + +Here is a list of all components that are primarily developed and maintained by [Sebastian Bergmann](https://sebastian-bergmann.de/open-source.html?ref=github): + +* [phpunit/phpunit](https://github.com/sebastianbergmann/phpunit) +* [phpunit/php-code-coverage](https://github.com/sebastianbergmann/php-code-coverage) +* [phpunit/php-file-iterator](https://github.com/sebastianbergmann/php-file-iterator) +* [phpunit/php-invoker](https://github.com/sebastianbergmann/php-invoker) +* [phpunit/php-text-template](https://github.com/sebastianbergmann/php-text-template) +* [phpunit/php-timer](https://github.com/sebastianbergmann/php-timer) +* [sebastian/cli-parser](https://github.com/sebastianbergmann/cli-parser) +* [sebastian/comparator](https://github.com/sebastianbergmann/comparator) +* [sebastian/complexity](https://github.com/sebastianbergmann/complexity) +* [sebastian/diff](https://github.com/sebastianbergmann/diff) +* [sebastian/environment](https://github.com/sebastianbergmann/environment) +* [sebastian/exporter](https://github.com/sebastianbergmann/exporter) +* [sebastian/global-state](https://github.com/sebastianbergmann/global-state) +* [sebastian/lines-of-code](https://github.com/sebastianbergmann/lines-of-code) +* [sebastian/object-enumerator](https://github.com/sebastianbergmann/object-enumerator) +* [sebastian/object-reflector](https://github.com/sebastianbergmann/object-reflector) +* [sebastian/recursion-context](https://github.com/sebastianbergmann/recursion-context) +* [sebastian/type](https://github.com/sebastianbergmann/type) +* [sebastian/version](https://github.com/sebastianbergmann/version) + +A very special thanks to everyone who has contributed to the [PHPUnit Manual](https://github.com/sebastianbergmann/phpunit-documentation-english). + +In addition to the components listed above, PHPUnit depends on the components listed below: + +* [myclabs/deep-copy](https://github.com/myclabs/DeepCopy) +* [nikic/php-parser](https://github.com/nikic/php-parser) +* [phar-io/manifest](https://github.com/phar-io/manifest) +* [phar-io/version](https://github.com/phar-io/version) +* [staabm/side-effects-detector](https://github.com/staabm/side-effects-detector) +* [theseer/tokenizer](https://github.com/theseer/tokenizer) + +These tools are used to develop PHPUnit: + +* [Composer](https://getcomposer.org/) +* [Phive](https://phar.io/) +* [PHP Autoload Builder](https://github.com/theseer/Autoload/) +* [PHP-CS-Fixer](https://cs.symfony.com/) +* [PHP-Scoper](https://github.com/humbug/php-scoper) +* [PHPStan](https://phpstan.org/) + +## Sponsors + +It has taken [Sebastian Bergmann](https://sebastian-bergmann.de/open-source.html?ref=github) thousands of hours to develop, test, and support PHPUnit. +[**You can sponsor his Open Source work through GitHub Sponsors**](https://github.com/sponsors/sebastianbergmann), for example. -Thanks to everyone who has contributed to PHPUnit! You can find a detailed list of contributors on every PHPUnit related package on GitHub. This list shows only the major components: +These businesses support Sebastian Bergmann's work on PHPUnit: -* [PHPUnit](https://github.com/sebastianbergmann/phpunit/graphs/contributors) -* [php-code-coverage](https://github.com/sebastianbergmann/php-code-coverage/graphs/contributors) + + + + + + + + + + + + + + + + +
Bubble Shooterin2it vofRoave
Testmo GmbHTideways GmbHTYPO3 GmbH
VEMA Versicherungsmakler Genossenschaft eG
-A very special thanks to everyone who has contributed to the [documentation](https://github.com/sebastianbergmann/phpunit-documentation-english/graphs/contributors). +Would you like to see your logo here as well as on the [PHPUnit website](https://phpunit.de/sponsors.html?ref=github)? +Contact Sebastian Bergmann at [sponsoring@phpunit.de](mailto:sponsoring@phpunit.de) to learn more about how you can support his work on PHPUnit. +Whether you are a CEO, CFO, CTO, or a developer: your company surely depends on Open Source software. +[It is time to pay your share](https://opensourcepledge.com/) and support maintainers like [Sebastian Bergmann](https://sebastian-bergmann.de/open-source.html?ref=github). diff --git a/app/vendor/phpunit/phpunit/composer.json b/app/vendor/phpunit/phpunit/composer.json index ef995c906..0793bb52a 100644 --- a/app/vendor/phpunit/phpunit/composer.json +++ b/app/vendor/phpunit/phpunit/composer.json @@ -22,46 +22,40 @@ }, "prefer-stable": true, "require": { - "php": ">=7.3", + "php": ">=8.3", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "doctrine/instantiator": "^1.5.0 || ^2", - "myclabs/deep-copy": "^1.13.1", + "myclabs/deep-copy": "^1.13.4", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", - "phpunit/php-code-coverage": "^9.2.32", - "phpunit/php-file-iterator": "^3.0.6", - "phpunit/php-invoker": "^3.1.1", - "phpunit/php-text-template": "^2.0.4", - "phpunit/php-timer": "^5.0.3", - "sebastian/cli-parser": "^1.0.2", - "sebastian/code-unit": "^1.0.8", - "sebastian/comparator": "^4.0.8", - "sebastian/diff": "^4.0.6", - "sebastian/environment": "^5.1.5", - "sebastian/exporter": "^4.0.6", - "sebastian/global-state": "^5.0.7", - "sebastian/object-enumerator": "^4.0.4", - "sebastian/resource-operations": "^3.0.4", - "sebastian/type": "^3.2.1", - "sebastian/version": "^3.0.2" + "phpunit/php-code-coverage": "^12.3.3", + "phpunit/php-file-iterator": "^6.0.0", + "phpunit/php-invoker": "^6.0.0", + "phpunit/php-text-template": "^5.0.0", + "phpunit/php-timer": "^8.0.0", + "sebastian/cli-parser": "^4.0.0", + "sebastian/comparator": "^7.1.3", + "sebastian/diff": "^7.0.0", + "sebastian/environment": "^8.0.3", + "sebastian/exporter": "^7.0.0", + "sebastian/global-state": "^8.0.0", + "sebastian/object-enumerator": "^7.0.0", + "sebastian/type": "^6.0.3", + "sebastian/version": "^6.0.0", + "staabm/side-effects-detector": "^1.0.5" }, "config": { "platform": { - "php": "7.3.0" + "php": "8.3.0" }, "classmap-authoritative": true, "optimize-autoloader": true, "sort-packages": true }, - "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" - }, "bin": [ "phpunit" ], @@ -75,17 +69,50 @@ }, "autoload-dev": { "classmap": [ - "tests/" + "tests/_files" ], "files": [ - "tests/_files/CoverageNamespacedFunctionTest.php", + "tests/_files/deprecation-trigger/trigger_deprecation.php", + "tests/unit/Event/AbstractEventTestCase.php", + "tests/unit/TextUI/AbstractSouceFilterTestCase.php", + "tests/unit/Framework/MockObject/TestDoubleTestCase.php", + "tests/unit/Metadata/Parser/AttributeParserTestCase.php", + "tests/unit/Framework/Assert/assertContainsOnlyArrayTest.php", + "tests/unit/Framework/Assert/assertContainsOnlyBoolTest.php", + "tests/unit/Framework/Assert/assertContainsOnlyCallableTest.php", + "tests/unit/Framework/Assert/assertContainsOnlyFloatTest.php", + "tests/unit/Framework/Assert/assertContainsOnlyInstancesOfTest.php", + "tests/unit/Framework/Assert/assertContainsOnlyIntTest.php", + "tests/unit/Framework/Assert/assertContainsOnlyIterableTest.php", + "tests/unit/Framework/Assert/assertContainsOnlyNullTest.php", + "tests/unit/Framework/Assert/assertContainsOnlyNumericTest.php", + "tests/unit/Framework/Assert/assertContainsOnlyObjectTest.php", + "tests/unit/Framework/Assert/assertContainsOnlyResourceTest.php", + "tests/unit/Framework/Assert/assertContainsOnlyClosedResourceTest.php", + "tests/unit/Framework/Assert/assertContainsOnlyScalarTest.php", + "tests/unit/Framework/Assert/assertContainsOnlyStringTest.php", + "tests/unit/Framework/Assert/assertDirectoryExistsTest.php", + "tests/unit/Framework/Assert/assertFileExistsTest.php", + "tests/unit/Framework/Assert/assertIsNumericTest.php", + "tests/unit/Framework/Assert/assertIsObjectTest.php", + "tests/unit/Framework/Assert/assertIsReadableTest.php", + "tests/unit/Framework/Assert/assertIsResourceTest.php", + "tests/unit/Framework/Assert/assertIsScalarTest.php", + "tests/unit/Framework/Assert/assertIsStringTest.php", + "tests/unit/Framework/Assert/assertIsWritableTest.php", + "tests/unit/Framework/Assert/assertMatchesRegularExpressionTest.php", + "tests/unit/Framework/Assert/assertNullTest.php", + "tests/unit/Framework/Assert/assertSameSizeTest.php", + "tests/unit/Framework/Assert/assertSameTest.php", "tests/_files/CoveredFunction.php", - "tests/_files/NamespaceCoveredFunction.php" + "tests/_files/Generator.php", + "tests/_files/NamespaceCoveredFunction.php", + "tests/end-to-end/_files/listing-tests-and-groups/ExampleAbstractTestCase.php" ] }, "extra": { "branch-alias": { - "dev-master": "9.6-dev" + "dev-main": "12.3-dev" } } } diff --git a/app/vendor/phpunit/phpunit/composer.lock b/app/vendor/phpunit/phpunit/composer.lock index be0c8c824..fa0588b73 100644 --- a/app/vendor/phpunit/phpunit/composer.lock +++ b/app/vendor/phpunit/phpunit/composer.lock @@ -4,90 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "4f546599de962e2d0c22325fa943c784", + "content-hash": "a38e5e620a055e6455309127f34a0716", "packages": [ - { - "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.13.1", + "version": "1.13.4", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c" + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/1720ddd719e16cf0db4eb1c6eca108031636d46c", - "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", "shasum": "" }, "require": { @@ -126,7 +56,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.13.1" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" }, "funding": [ { @@ -134,29 +64,31 @@ "type": "tidelift" } ], - "time": "2025-04-29T12:36:36+00:00" + "time": "2025-08-01T08:46:24+00:00" }, { "name": "nikic/php-parser", - "version": "v4.19.4", + "version": "v5.6.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "715f4d25e225bc47b293a8b997fe6ce99bf987d2" + "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/715f4d25e225bc47b293a8b997fe6ce99bf987d2", - "reference": "715f4d25e225bc47b293a8b997fe6ce99bf987d2", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2", + "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2", "shasum": "" }, "require": { + "ext-ctype": "*", + "ext-json": "*", "ext-tokenizer": "*", - "php": ">=7.1" + "php": ">=7.4" }, "require-dev": { "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + "phpunit/phpunit": "^9.0" }, "bin": [ "bin/php-parse" @@ -164,7 +96,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.9-dev" + "dev-master": "5.x-dev" } }, "autoload": { @@ -188,9 +120,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.19.4" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.1" }, - "time": "2024-09-29T15:01:53+00:00" + "time": "2025-08-13T20:13:15+00:00" }, { "name": "phar-io/manifest", @@ -312,35 +244,34 @@ }, { "name": "phpunit/php-code-coverage", - "version": "9.2.32", + "version": "12.3.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5" + "reference": "733025d94635a001f67db71a2ed1bab4e7e4a9dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/85402a822d1ecf1db1096959413d35e1c37cf1a5", - "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/733025d94635a001f67db71a2ed1bab4e7e4a9dc", + "reference": "733025d94635a001f67db71a2ed1bab4e7e4a9dc", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.19.1 || ^5.1.0", - "php": ">=7.3", - "phpunit/php-file-iterator": "^3.0.6", - "phpunit/php-text-template": "^2.0.4", - "sebastian/code-unit-reverse-lookup": "^2.0.3", - "sebastian/complexity": "^2.0.3", - "sebastian/environment": "^5.1.5", - "sebastian/lines-of-code": "^1.0.4", - "sebastian/version": "^3.0.2", + "nikic/php-parser": "^5.4.0", + "php": ">=8.3", + "phpunit/php-file-iterator": "^6.0", + "phpunit/php-text-template": "^5.0", + "sebastian/complexity": "^5.0", + "sebastian/environment": "^8.0", + "sebastian/lines-of-code": "^4.0", + "sebastian/version": "^6.0", "theseer/tokenizer": "^1.2.3" }, "require-dev": { - "phpunit/phpunit": "^9.6" + "phpunit/phpunit": "^12.1" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -349,7 +280,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "9.2.x-dev" + "dev-main": "12.3.x-dev" } }, "autoload": { @@ -378,40 +309,52 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.32" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.3.3" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-code-coverage", + "type": "tidelift" } ], - "time": "2024-08-22T04:23:01+00:00" + "time": "2025-08-27T14:43:48+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "3.0.6", + "version": "6.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" + "reference": "961bc913d42fe24a257bfff826a5068079ac7782" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", - "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/961bc913d42fe24a257bfff826a5068079ac7782", + "reference": "961bc913d42fe24a257bfff826a5068079ac7782", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^12.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -438,7 +381,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" + "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/6.0.0" }, "funding": [ { @@ -446,28 +390,28 @@ "type": "github" } ], - "time": "2021-12-02T12:48:52+00:00" + "time": "2025-02-07T04:58:37+00:00" }, { "name": "phpunit/php-invoker", - "version": "3.1.1", + "version": "6.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-invoker.git", - "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" + "reference": "12b54e689b07a25a9b41e57736dfab6ec9ae5406" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", - "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/12b54e689b07a25a9b41e57736dfab6ec9ae5406", + "reference": "12b54e689b07a25a9b41e57736dfab6ec9ae5406", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.3" }, "require-dev": { "ext-pcntl": "*", - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^12.0" }, "suggest": { "ext-pcntl": "*" @@ -475,7 +419,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -501,7 +445,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-invoker/issues", - "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/6.0.0" }, "funding": [ { @@ -509,32 +454,32 @@ "type": "github" } ], - "time": "2020-09-28T05:58:55+00:00" + "time": "2025-02-07T04:58:58+00:00" }, { "name": "phpunit/php-text-template", - "version": "2.0.4", + "version": "5.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" + "reference": "e1367a453f0eda562eedb4f659e13aa900d66c53" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", - "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/e1367a453f0eda562eedb4f659e13aa900d66c53", + "reference": "e1367a453f0eda562eedb4f659e13aa900d66c53", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^12.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -560,7 +505,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-text-template/issues", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/5.0.0" }, "funding": [ { @@ -568,32 +514,32 @@ "type": "github" } ], - "time": "2020-10-26T05:33:50+00:00" + "time": "2025-02-07T04:59:16+00:00" }, { "name": "phpunit/php-timer", - "version": "5.0.3", + "version": "8.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" + "reference": "f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", - "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc", + "reference": "f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^12.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-main": "8.0-dev" } }, "autoload": { @@ -619,7 +565,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + "security": "https://github.com/sebastianbergmann/php-timer/security/policy", + "source": "https://github.com/sebastianbergmann/php-timer/tree/8.0.0" }, "funding": [ { @@ -627,32 +574,32 @@ "type": "github" } ], - "time": "2020-10-26T13:16:10+00:00" + "time": "2025-02-07T04:59:38+00:00" }, { "name": "sebastian/cli-parser", - "version": "1.0.2", + "version": "4.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b" + "reference": "6d584c727d9114bcdc14c86711cd1cad51778e7c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2b56bea83a09de3ac06bb18b92f068e60cc6f50b", - "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/6d584c727d9114bcdc14c86711cd1cad51778e7c", + "reference": "6d584c727d9114bcdc14c86711cd1cad51778e7c", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^12.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -675,63 +622,8 @@ "homepage": "https://github.com/sebastianbergmann/cli-parser", "support": { "issues": "https://github.com/sebastianbergmann/cli-parser/issues", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.2" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-03-02T06:27:43+00:00" - }, - { - "name": "sebastian/code-unit", - "version": "1.0.8", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit.git", - "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", - "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.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": "Collection of value objects that represent the PHP code units", - "homepage": "https://github.com/sebastianbergmann/code-unit", - "support": { - "issues": "https://github.com/sebastianbergmann/code-unit/issues", - "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/4.0.0" }, "funding": [ { @@ -739,89 +631,39 @@ "type": "github" } ], - "time": "2020-10-26T13:08:54+00:00" - }, - { - "name": "sebastian/code-unit-reverse-lookup", - "version": "2.0.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", - "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "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": "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/2.0.3" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-09-28T05:30:19+00:00" + "time": "2025-02-07T04:53:50+00:00" }, { "name": "sebastian/comparator", - "version": "4.0.8", + "version": "7.1.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "fa0f136dd2334583309d32b62544682ee972b51a" + "reference": "dc904b4bb3ab070865fa4068cd84f3da8b945148" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", - "reference": "fa0f136dd2334583309d32b62544682ee972b51a", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/dc904b4bb3ab070865fa4068cd84f3da8b945148", + "reference": "dc904b4bb3ab070865fa4068cd84f3da8b945148", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/diff": "^4.0", - "sebastian/exporter": "^4.0" + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.3", + "sebastian/diff": "^7.0", + "sebastian/exporter": "^7.0" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^12.2" + }, + "suggest": { + "ext-bcmath": "For comparing BcMath\\Number objects" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "7.1-dev" } }, "autoload": { @@ -860,41 +702,54 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", - "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" + "security": "https://github.com/sebastianbergmann/comparator/security/policy", + "source": "https://github.com/sebastianbergmann/comparator/tree/7.1.3" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" } ], - "time": "2022-09-14T12:41:17+00:00" + "time": "2025-08-20T11:27:00+00:00" }, { "name": "sebastian/complexity", - "version": "2.0.3", + "version": "5.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a" + "reference": "bad4316aba5303d0221f43f8cee37eb58d384bbb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a", - "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/bad4316aba5303d0221f43f8cee37eb58d384bbb", + "reference": "bad4316aba5303d0221f43f8cee37eb58d384bbb", "shasum": "" }, "require": { - "nikic/php-parser": "^4.18 || ^5.0", - "php": ">=7.3" + "nikic/php-parser": "^5.0", + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^12.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -917,7 +772,8 @@ "homepage": "https://github.com/sebastianbergmann/complexity", "support": { "issues": "https://github.com/sebastianbergmann/complexity/issues", - "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.3" + "security": "https://github.com/sebastianbergmann/complexity/security/policy", + "source": "https://github.com/sebastianbergmann/complexity/tree/5.0.0" }, "funding": [ { @@ -925,33 +781,33 @@ "type": "github" } ], - "time": "2023-12-22T06:19:30+00:00" + "time": "2025-02-07T04:55:25+00:00" }, { "name": "sebastian/diff", - "version": "4.0.6", + "version": "7.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc" + "reference": "7ab1ea946c012266ca32390913653d844ecd085f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc", - "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7ab1ea946c012266ca32390913653d844ecd085f", + "reference": "7ab1ea946c012266ca32390913653d844ecd085f", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^9.3", - "symfony/process": "^4.2 || ^5" + "phpunit/phpunit": "^12.0", + "symfony/process": "^7.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -983,7 +839,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/4.0.6" + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/7.0.0" }, "funding": [ { @@ -991,27 +848,27 @@ "type": "github" } ], - "time": "2024-03-02T06:30:58+00:00" + "time": "2025-02-07T04:55:46+00:00" }, { "name": "sebastian/environment", - "version": "5.1.5", + "version": "8.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" + "reference": "24a711b5c916efc6d6e62aa65aa2ec98fef77f68" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", - "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/24a711b5c916efc6d6e62aa65aa2ec98fef77f68", + "reference": "24a711b5c916efc6d6e62aa65aa2ec98fef77f68", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^12.0" }, "suggest": { "ext-posix": "*" @@ -1019,7 +876,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.1-dev" + "dev-main": "8.0-dev" } }, "autoload": { @@ -1038,7 +895,7 @@ } ], "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "http://www.github.com/sebastianbergmann/environment", + "homepage": "https://github.com/sebastianbergmann/environment", "keywords": [ "Xdebug", "environment", @@ -1046,42 +903,55 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", - "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" + "security": "https://github.com/sebastianbergmann/environment/security/policy", + "source": "https://github.com/sebastianbergmann/environment/tree/8.0.3" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/environment", + "type": "tidelift" } ], - "time": "2023-02-03T06:03:51+00:00" + "time": "2025-08-12T14:11:56+00:00" }, { "name": "sebastian/exporter", - "version": "4.0.6", + "version": "7.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72" + "reference": "76432aafc58d50691a00d86d0632f1217a47b688" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/78c00df8f170e02473b682df15bfcdacc3d32d72", - "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/76432aafc58d50691a00d86d0632f1217a47b688", + "reference": "76432aafc58d50691a00d86d0632f1217a47b688", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/recursion-context": "^4.0" + "ext-mbstring": "*", + "php": ">=8.3", + "sebastian/recursion-context": "^7.0" }, "require-dev": { - "ext-mbstring": "*", - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^12.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -1123,7 +993,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.6" + "security": "https://github.com/sebastianbergmann/exporter/security/policy", + "source": "https://github.com/sebastianbergmann/exporter/tree/7.0.0" }, "funding": [ { @@ -1131,38 +1002,35 @@ "type": "github" } ], - "time": "2024-03-02T06:33:00+00:00" + "time": "2025-02-07T04:56:42+00:00" }, { "name": "sebastian/global-state", - "version": "5.0.7", + "version": "8.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9" + "reference": "570a2aeb26d40f057af686d63c4e99b075fb6cbc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", - "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/570a2aeb26d40f057af686d63c4e99b075fb6cbc", + "reference": "570a2aeb26d40f057af686d63c4e99b075fb6cbc", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/object-reflector": "^2.0", - "sebastian/recursion-context": "^4.0" + "php": ">=8.3", + "sebastian/object-reflector": "^5.0", + "sebastian/recursion-context": "^7.0" }, "require-dev": { "ext-dom": "*", - "phpunit/phpunit": "^9.3" - }, - "suggest": { - "ext-uopz": "*" + "phpunit/phpunit": "^12.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-main": "8.0-dev" } }, "autoload": { @@ -1181,13 +1049,14 @@ } ], "description": "Snapshotting of global state", - "homepage": "http://www.github.com/sebastianbergmann/global-state", + "homepage": "https://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/5.0.7" + "security": "https://github.com/sebastianbergmann/global-state/security/policy", + "source": "https://github.com/sebastianbergmann/global-state/tree/8.0.0" }, "funding": [ { @@ -1195,33 +1064,33 @@ "type": "github" } ], - "time": "2024-03-02T06:35:11+00:00" + "time": "2025-02-07T04:56:59+00:00" }, { "name": "sebastian/lines-of-code", - "version": "1.0.4", + "version": "4.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5" + "reference": "97ffee3bcfb5805568d6af7f0f893678fc076d2f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5", - "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/97ffee3bcfb5805568d6af7f0f893678fc076d2f", + "reference": "97ffee3bcfb5805568d6af7f0f893678fc076d2f", "shasum": "" }, "require": { - "nikic/php-parser": "^4.18 || ^5.0", - "php": ">=7.3" + "nikic/php-parser": "^5.0", + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^12.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -1244,7 +1113,8 @@ "homepage": "https://github.com/sebastianbergmann/lines-of-code", "support": { "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4" + "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/4.0.0" }, "funding": [ { @@ -1252,34 +1122,34 @@ "type": "github" } ], - "time": "2023-12-22T06:20:34+00:00" + "time": "2025-02-07T04:57:28+00:00" }, { "name": "sebastian/object-enumerator", - "version": "4.0.4", + "version": "7.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" + "reference": "1effe8e9b8e068e9ae228e542d5d11b5d16db894" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", - "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/1effe8e9b8e068e9ae228e542d5d11b5d16db894", + "reference": "1effe8e9b8e068e9ae228e542d5d11b5d16db894", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/object-reflector": "^2.0", - "sebastian/recursion-context": "^4.0" + "php": ">=8.3", + "sebastian/object-reflector": "^5.0", + "sebastian/recursion-context": "^7.0" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^12.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -1301,7 +1171,8 @@ "homepage": "https://github.com/sebastianbergmann/object-enumerator/", "support": { "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/7.0.0" }, "funding": [ { @@ -1309,32 +1180,32 @@ "type": "github" } ], - "time": "2020-10-26T13:12:34+00:00" + "time": "2025-02-07T04:57:48+00:00" }, { "name": "sebastian/object-reflector", - "version": "2.0.4", + "version": "5.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" + "reference": "4bfa827c969c98be1e527abd576533293c634f6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", - "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/4bfa827c969c98be1e527abd576533293c634f6a", + "reference": "4bfa827c969c98be1e527abd576533293c634f6a", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^12.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -1356,7 +1227,8 @@ "homepage": "https://github.com/sebastianbergmann/object-reflector/", "support": { "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/5.0.0" }, "funding": [ { @@ -1364,32 +1236,32 @@ "type": "github" } ], - "time": "2020-10-26T13:14:26+00:00" + "time": "2025-02-07T04:58:17+00:00" }, { "name": "sebastian/recursion-context", - "version": "4.0.5", + "version": "7.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" + "reference": "0b01998a7d5b1f122911a66bebcb8d46f0c82d8c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", - "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/0b01998a7d5b1f122911a66bebcb8d46f0c82d8c", + "reference": "0b01998a7d5b1f122911a66bebcb8d46f0c82d8c", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^12.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -1419,40 +1291,53 @@ "homepage": "https://github.com/sebastianbergmann/recursion-context", "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/7.0.1" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "type": "tidelift" } ], - "time": "2023-02-03T06:07:39+00:00" + "time": "2025-08-13T04:44:59+00:00" }, { - "name": "sebastian/resource-operations", - "version": "3.0.4", + "name": "sebastian/type", + "version": "6.0.3", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e" + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "e549163b9760b8f71f191651d22acf32d56d6d4d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/05d5692a7993ecccd56a03e40cd7e5b09b1d404e", - "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/e549163b9760b8f71f191651d22acf32d56d6d4d", + "reference": "e549163b9760b8f71f191651d22acf32d56d6d4d", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^9.0" + "phpunit/phpunit": "^12.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -1467,46 +1352,58 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Provides a list of PHP built-in functions that operate on resources", - "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", "support": { - "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.4" + "issues": "https://github.com/sebastianbergmann/type/issues", + "security": "https://github.com/sebastianbergmann/type/security/policy", + "source": "https://github.com/sebastianbergmann/type/tree/6.0.3" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/type", + "type": "tidelift" } ], - "time": "2024-03-14T16:00:52+00:00" + "time": "2025-08-09T06:57:12+00:00" }, { - "name": "sebastian/type", - "version": "3.2.1", + "name": "sebastian/version", + "version": "6.0.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/type.git", - "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "3e6ccf7657d4f0a59200564b08cead899313b53c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", - "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/3e6ccf7657d4f0a59200564b08cead899313b53c", + "reference": "3e6ccf7657d4f0a59200564b08cead899313b53c", "shasum": "" }, "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.5" + "php": ">=8.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -1525,11 +1422,12 @@ "role": "lead" } ], - "description": "Collection of value objects that represent the types of the PHP type system", - "homepage": "https://github.com/sebastianbergmann/type", + "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/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" + "issues": "https://github.com/sebastianbergmann/version/issues", + "security": "https://github.com/sebastianbergmann/version/security/policy", + "source": "https://github.com/sebastianbergmann/version/tree/6.0.0" }, "funding": [ { @@ -1537,60 +1435,59 @@ "type": "github" } ], - "time": "2023-02-03T06:13:03+00:00" + "time": "2025-02-07T05:00:38+00:00" }, { - "name": "sebastian/version", - "version": "3.0.2", + "name": "staabm/side-effects-detector", + "version": "1.0.5", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/version.git", - "reference": "c6c1022351a901512170118436c764e473f6de8c" + "url": "https://github.com/staabm/side-effects-detector.git", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", - "reference": "c6c1022351a901512170118436c764e473f6de8c", + "url": "https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163", "shasum": "" }, "require": { - "php": ">=7.3" + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } + "require-dev": { + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.6", + "phpunit/phpunit": "^9.6.21", + "symfony/var-dumper": "^5.4.43", + "tomasvotruba/type-coverage": "1.0.0", + "tomasvotruba/unused-public": "1.0.0" }, + "type": "library", "autoload": { "classmap": [ - "src/" + "lib/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } + "description": "A static analysis tool to detect side effects in PHP code", + "keywords": [ + "static analysis" ], - "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/3.0.2" + "issues": "https://github.com/staabm/side-effects-detector/issues", + "source": "https://github.com/staabm/side-effects-detector/tree/1.0.5" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://github.com/staabm", "type": "github" } ], - "time": "2020-09-28T06:39:44+00:00" + "time": "2024-10-20T05:08:20+00:00" }, { "name": "theseer/tokenizer", @@ -1650,7 +1547,7 @@ "prefer-stable": true, "prefer-lowest": false, "platform": { - "php": ">=7.3", + "php": ">=8.3", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", @@ -1660,7 +1557,7 @@ }, "platform-dev": {}, "platform-overrides": { - "php": "7.3.0" + "php": "8.3.0" }, "plugin-api-version": "2.6.0" } diff --git a/app/vendor/phpunit/phpunit/phpunit b/app/vendor/phpunit/phpunit/phpunit index b9f5cf29d..84afaf14c 100755 --- a/app/vendor/phpunit/phpunit/phpunit +++ b/app/vendor/phpunit/phpunit/phpunit @@ -24,11 +24,11 @@ if (!version_compare(PHP_VERSION, PHP_VERSION, '=')) { die(1); } -if (version_compare('7.3.0', PHP_VERSION, '>')) { +if (version_compare('8.3.0', PHP_VERSION, '>')) { fwrite( STDERR, sprintf( - 'This version of PHPUnit requires PHP >= 7.3.' . PHP_EOL . + 'This version of PHPUnit requires PHP >= 8.3.' . PHP_EOL . 'You are using PHP %s (%s).' . PHP_EOL, PHP_VERSION, PHP_BINARY @@ -38,31 +38,6 @@ if (version_compare('7.3.0', PHP_VERSION, '>')) { die(1); } -$requiredExtensions = ['dom', 'json', 'libxml', 'mbstring', 'tokenizer', 'xml', 'xmlwriter']; - -$unavailableExtensions = array_filter( - $requiredExtensions, - static function ($extension) { - return !extension_loaded($extension); - } -); - -if ([] !== $unavailableExtensions) { - fwrite( - STDERR, - sprintf( - 'PHPUnit requires the "%s" extensions, but the "%s" %s not available.' . PHP_EOL, - implode('", "', $requiredExtensions), - implode('", "', $unavailableExtensions), - count($unavailableExtensions) === 1 ? 'extension is' : 'extensions are' - ) - ); - - die(1); -} - -unset($requiredExtensions, $unavailableExtensions); - if (!ini_get('date.timezone')) { ini_set('date.timezone', 'UTC'); } @@ -94,14 +69,36 @@ if (!defined('PHPUNIT_COMPOSER_INSTALL')) { die(1); } -$options = getopt('', array('prepend:')); +require PHPUNIT_COMPOSER_INSTALL; + +$requiredExtensions = ['dom', 'json', 'libxml', 'mbstring', 'tokenizer', 'xml', 'xmlwriter']; -if (isset($options['prepend'])) { - require $options['prepend']; +$unavailableExtensions = array_filter( + $requiredExtensions, + static function ($extension) { + return !extension_loaded($extension); + } +); + +// Workaround for https://github.com/sebastianbergmann/phpunit/issues/5662 +if (!function_exists('ctype_alnum')) { + $unavailableExtensions[] = 'ctype'; } -unset($options); +if ([] !== $unavailableExtensions) { + fwrite( + STDERR, + sprintf( + 'PHPUnit requires the "%s" extensions, but the "%s" %s not available.' . PHP_EOL, + implode('", "', $requiredExtensions), + implode('", "', $unavailableExtensions), + count($unavailableExtensions) === 1 ? 'extension is' : 'extensions are' + ) + ); -require PHPUNIT_COMPOSER_INSTALL; + die(1); +} + +unset($requiredExtensions, $unavailableExtensions); -PHPUnit\TextUI\Command::main(); +exit((new PHPUnit\TextUI\Application)->run($_SERVER['argv'])); diff --git a/app/vendor/phpunit/phpunit/phpunit.xsd b/app/vendor/phpunit/phpunit/phpunit.xsd index 619434ef8..73e7c2144 100644 --- a/app/vendor/phpunit/phpunit/phpunit.xsd +++ b/app/vendor/phpunit/phpunit/phpunit.xsd @@ -2,7 +2,7 @@ - This Schema file defines the rules by which the XML configuration file of PHPUnit 9.6 may be structured. + This Schema file defines the rules by which the XML configuration file of PHPUnit 12.3 may be structured. @@ -11,28 +11,60 @@ Root Element - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - @@ -57,62 +89,19 @@ - + - + - - - - - - - - - - + - - - - - - - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -125,27 +114,11 @@ - - - - - - - - - - - - - - - - - + @@ -164,17 +137,6 @@ - - - - - - - - - - - @@ -207,65 +169,72 @@ - + + - - - - - - - - + + + + + + + + + + + + + + + - + - - - - - + - - - + - - - + - - + + + + + + + + + + - + - @@ -280,18 +249,50 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -300,11 +301,10 @@ + - - @@ -313,6 +313,10 @@ + + + + @@ -321,10 +325,24 @@ + + + + + + + + + + + + + + diff --git a/app/vendor/phpunit/phpunit/schema/10.0.xsd b/app/vendor/phpunit/phpunit/schema/10.0.xsd new file mode 100644 index 000000000..480d54dec --- /dev/null +++ b/app/vendor/phpunit/phpunit/schema/10.0.xsd @@ -0,0 +1,284 @@ + + + + + This Schema file defines the rules by which the XML configuration file of PHPUnit 10.0 may be structured. + + + + + + Root Element + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The main type specifying the document structure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/vendor/phpunit/phpunit/schema/10.1.xsd b/app/vendor/phpunit/phpunit/schema/10.1.xsd new file mode 100644 index 000000000..1b190c21b --- /dev/null +++ b/app/vendor/phpunit/phpunit/schema/10.1.xsd @@ -0,0 +1,312 @@ + + + + + This Schema file defines the rules by which the XML configuration file of PHPUnit 10.1 may be structured. + + + + + + Root Element + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The main type specifying the document structure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/vendor/phpunit/phpunit/schema/10.2.xsd b/app/vendor/phpunit/phpunit/schema/10.2.xsd new file mode 100644 index 000000000..269b7a3ae --- /dev/null +++ b/app/vendor/phpunit/phpunit/schema/10.2.xsd @@ -0,0 +1,319 @@ + + + + + This Schema file defines the rules by which the XML configuration file of PHPUnit 10.2 may be structured. + + + + + + Root Element + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The main type specifying the document structure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/vendor/phpunit/phpunit/schema/10.3.xsd b/app/vendor/phpunit/phpunit/schema/10.3.xsd new file mode 100644 index 000000000..03a54ee0b --- /dev/null +++ b/app/vendor/phpunit/phpunit/schema/10.3.xsd @@ -0,0 +1,321 @@ + + + + + This Schema file defines the rules by which the XML configuration file of PHPUnit 10.3 may be structured. + + + + + + Root Element + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The main type specifying the document structure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/vendor/phpunit/phpunit/schema/10.4.xsd b/app/vendor/phpunit/phpunit/schema/10.4.xsd new file mode 100644 index 000000000..bd22b2ca2 --- /dev/null +++ b/app/vendor/phpunit/phpunit/schema/10.4.xsd @@ -0,0 +1,322 @@ + + + + + This Schema file defines the rules by which the XML configuration file of PHPUnit 10.4 may be structured. + + + + + + Root Element + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The main type specifying the document structure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/vendor/phpunit/phpunit/schema/10.5.xsd b/app/vendor/phpunit/phpunit/schema/10.5.xsd new file mode 100644 index 000000000..284820b0b --- /dev/null +++ b/app/vendor/phpunit/phpunit/schema/10.5.xsd @@ -0,0 +1,327 @@ + + + + + This Schema file defines the rules by which the XML configuration file of PHPUnit 10.5 may be structured. + + + + + + Root Element + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The main type specifying the document structure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/vendor/phpunit/phpunit/schema/11.0.xsd b/app/vendor/phpunit/phpunit/schema/11.0.xsd new file mode 100644 index 000000000..a6e7cb8cd --- /dev/null +++ b/app/vendor/phpunit/phpunit/schema/11.0.xsd @@ -0,0 +1,323 @@ + + + + + This Schema file defines the rules by which the XML configuration file of PHPUnit 11.0 may be structured. + + + + + + Root Element + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The main type specifying the document structure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/vendor/phpunit/phpunit/schema/11.1.xsd b/app/vendor/phpunit/phpunit/schema/11.1.xsd new file mode 100644 index 000000000..6172e8349 --- /dev/null +++ b/app/vendor/phpunit/phpunit/schema/11.1.xsd @@ -0,0 +1,333 @@ + + + + + This Schema file defines the rules by which the XML configuration file of PHPUnit 11.1 may be structured. + + + + + + Root Element + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The main type specifying the document structure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/vendor/phpunit/phpunit/schema/11.2.xsd b/app/vendor/phpunit/phpunit/schema/11.2.xsd new file mode 100644 index 000000000..d7c7dcac0 --- /dev/null +++ b/app/vendor/phpunit/phpunit/schema/11.2.xsd @@ -0,0 +1,331 @@ + + + + + This Schema file defines the rules by which the XML configuration file of PHPUnit 11.2 may be structured. + + + + + + Root Element + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The main type specifying the document structure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/vendor/phpunit/phpunit/schema/11.3.xsd b/app/vendor/phpunit/phpunit/schema/11.3.xsd new file mode 100644 index 000000000..3b30de4d6 --- /dev/null +++ b/app/vendor/phpunit/phpunit/schema/11.3.xsd @@ -0,0 +1,335 @@ + + + + + This Schema file defines the rules by which the XML configuration file of PHPUnit 11.3 may be structured. + + + + + + Root Element + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The main type specifying the document structure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/vendor/phpunit/phpunit/schema/11.4.xsd b/app/vendor/phpunit/phpunit/schema/11.4.xsd new file mode 100644 index 000000000..52db36302 --- /dev/null +++ b/app/vendor/phpunit/phpunit/schema/11.4.xsd @@ -0,0 +1,334 @@ + + + + + This Schema file defines the rules by which the XML configuration file of PHPUnit 11.4 may be structured. + + + + + + Root Element + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The main type specifying the document structure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/vendor/phpunit/phpunit/schema/11.5.xsd b/app/vendor/phpunit/phpunit/schema/11.5.xsd new file mode 100644 index 000000000..ec0227111 --- /dev/null +++ b/app/vendor/phpunit/phpunit/schema/11.5.xsd @@ -0,0 +1,339 @@ + + + + + This Schema file defines the rules by which the XML configuration file of PHPUnit 11.5 may be structured. + + + + + + Root Element + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The main type specifying the document structure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/vendor/phpunit/phpunit/schema/12.0.xsd b/app/vendor/phpunit/phpunit/schema/12.0.xsd new file mode 100644 index 000000000..c993640f9 --- /dev/null +++ b/app/vendor/phpunit/phpunit/schema/12.0.xsd @@ -0,0 +1,335 @@ + + + + + This Schema file defines the rules by which the XML configuration file of PHPUnit 12.0 may be structured. + + + + + + Root Element + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The main type specifying the document structure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/vendor/phpunit/phpunit/schema/12.1.xsd b/app/vendor/phpunit/phpunit/schema/12.1.xsd new file mode 100644 index 000000000..51f225d45 --- /dev/null +++ b/app/vendor/phpunit/phpunit/schema/12.1.xsd @@ -0,0 +1,339 @@ + + + + + This Schema file defines the rules by which the XML configuration file of PHPUnit 12.1 may be structured. + + + + + + Root Element + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The main type specifying the document structure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/vendor/phpunit/phpunit/schema/12.2.xsd b/app/vendor/phpunit/phpunit/schema/12.2.xsd new file mode 100644 index 000000000..9303b6433 --- /dev/null +++ b/app/vendor/phpunit/phpunit/schema/12.2.xsd @@ -0,0 +1,347 @@ + + + + + This Schema file defines the rules by which the XML configuration file of PHPUnit 12.2 may be structured. + + + + + + Root Element + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The main type specifying the document structure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/vendor/phpunit/phpunit/src/Event/Dispatcher/CollectingDispatcher.php b/app/vendor/phpunit/phpunit/src/Event/Dispatcher/CollectingDispatcher.php new file mode 100644 index 000000000..e3e9462ee --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Dispatcher/CollectingDispatcher.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +use PHPUnit\Runner\DeprecationCollector\Facade as DeprecationCollector; +use PHPUnit\Runner\DeprecationCollector\TestTriggeredDeprecationSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class CollectingDispatcher implements Dispatcher +{ + private EventCollection $events; + private DirectDispatcher $isolatedDirectDispatcher; + + public function __construct(DirectDispatcher $directDispatcher) + { + $this->isolatedDirectDispatcher = $directDispatcher; + $this->events = new EventCollection; + + $this->isolatedDirectDispatcher->registerSubscriber(new TestTriggeredDeprecationSubscriber(DeprecationCollector::collector())); + } + + public function dispatch(Event $event): void + { + $this->events->add($event); + + try { + $this->isolatedDirectDispatcher->dispatch($event); + } catch (UnknownEventTypeException) { + // Do nothing. + } + } + + public function flush(): EventCollection + { + $events = $this->events; + + $this->events = new EventCollection; + + return $events; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Dispatcher/DeferringDispatcher.php b/app/vendor/phpunit/phpunit/src/Event/Dispatcher/DeferringDispatcher.php new file mode 100644 index 000000000..6895facb3 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Dispatcher/DeferringDispatcher.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 PHPUnit\Event; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class DeferringDispatcher implements SubscribableDispatcher +{ + private readonly SubscribableDispatcher $dispatcher; + private EventCollection $events; + private bool $recording = true; + + public function __construct(SubscribableDispatcher $dispatcher) + { + $this->dispatcher = $dispatcher; + $this->events = new EventCollection; + } + + public function registerTracer(Tracer\Tracer $tracer): void + { + $this->dispatcher->registerTracer($tracer); + } + + public function registerSubscriber(Subscriber $subscriber): void + { + $this->dispatcher->registerSubscriber($subscriber); + } + + public function dispatch(Event $event): void + { + if ($this->recording) { + $this->events->add($event); + + return; + } + + $this->dispatcher->dispatch($event); + } + + public function flush(): void + { + $this->recording = false; + + foreach ($this->events as $event) { + $this->dispatcher->dispatch($event); + } + + $this->events = new EventCollection; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Dispatcher/DirectDispatcher.php b/app/vendor/phpunit/phpunit/src/Event/Dispatcher/DirectDispatcher.php new file mode 100644 index 000000000..b5cbc8e31 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Dispatcher/DirectDispatcher.php @@ -0,0 +1,140 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +use const PHP_EOL; +use function array_key_exists; +use function dirname; +use function sprintf; +use function str_starts_with; +use Throwable; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class DirectDispatcher implements SubscribableDispatcher +{ + private readonly TypeMap $typeMap; + + /** + * @var array> + */ + private array $subscribers = []; + + /** + * @var list + */ + private array $tracers = []; + + public function __construct(TypeMap $map) + { + $this->typeMap = $map; + } + + public function registerTracer(Tracer\Tracer $tracer): void + { + $this->tracers[] = $tracer; + } + + /** + * @throws MapError + * @throws UnknownSubscriberTypeException + */ + public function registerSubscriber(Subscriber $subscriber): void + { + if (!$this->typeMap->isKnownSubscriberType($subscriber)) { + throw new UnknownSubscriberTypeException( + sprintf( + 'Subscriber "%s" does not implement any known interface - did you forget to register it?', + $subscriber::class, + ), + ); + } + + $eventClassName = $this->typeMap->map($subscriber); + + if (!array_key_exists($eventClassName, $this->subscribers)) { + $this->subscribers[$eventClassName] = []; + } + + $this->subscribers[$eventClassName][] = $subscriber; + } + + /** + * @throws Throwable + * @throws UnknownEventTypeException + */ + public function dispatch(Event $event): void + { + $eventClassName = $event::class; + + if (!$this->typeMap->isKnownEventType($event)) { + throw new UnknownEventTypeException( + sprintf( + 'Unknown event type "%s"', + $eventClassName, + ), + ); + } + + foreach ($this->tracers as $tracer) { + try { + $tracer->trace($event); + // @codeCoverageIgnoreStart + } catch (Throwable $t) { + $this->handleThrowable($t); + } + // @codeCoverageIgnoreEnd + } + + if (!array_key_exists($eventClassName, $this->subscribers)) { + return; + } + + foreach ($this->subscribers[$eventClassName] as $subscriber) { + try { + /** @phpstan-ignore method.notFound */ + $subscriber->notify($event); + } catch (Throwable $t) { + $this->handleThrowable($t); + } + } + } + + /** + * @throws Throwable + */ + public function handleThrowable(Throwable $t): void + { + if ($this->isThrowableFromThirdPartySubscriber($t)) { + Facade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + 'Exception in third-party event subscriber: %s%s%s', + $t->getMessage(), + PHP_EOL, + $t->getTraceAsString(), + ), + ); + + return; + } + + // @codeCoverageIgnoreStart + throw $t; + // @codeCoverageIgnoreEnd + } + + private function isThrowableFromThirdPartySubscriber(Throwable $t): bool + { + return !str_starts_with($t->getFile(), dirname(__DIR__, 2)); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Dispatcher/Dispatcher.php b/app/vendor/phpunit/phpunit/src/Event/Dispatcher/Dispatcher.php new file mode 100644 index 000000000..e70865395 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Dispatcher/Dispatcher.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 PHPUnit\Event; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This interface is not covered by the backward compatibility promise for PHPUnit + */ +interface Dispatcher +{ + /** + * @throws UnknownEventTypeException + */ + public function dispatch(Event $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Dispatcher/SubscribableDispatcher.php b/app/vendor/phpunit/phpunit/src/Event/Dispatcher/SubscribableDispatcher.php new file mode 100644 index 000000000..c4393da12 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Dispatcher/SubscribableDispatcher.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This interface is not covered by the backward compatibility promise for PHPUnit + */ +interface SubscribableDispatcher extends Dispatcher +{ + /** + * @throws UnknownSubscriberTypeException + */ + public function registerSubscriber(Subscriber $subscriber): void; + + public function registerTracer(Tracer\Tracer $tracer): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Emitter/DispatchingEmitter.php b/app/vendor/phpunit/phpunit/src/Event/Emitter/DispatchingEmitter.php new file mode 100644 index 000000000..606eac4dd --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Emitter/DispatchingEmitter.php @@ -0,0 +1,1460 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +use function assert; +use function memory_reset_peak_usage; +use PHPUnit\Event\Code\ClassMethod; +use PHPUnit\Event\Code\ComparisonFailure; +use PHPUnit\Event\Code\IssueTrigger\IssueTrigger; +use PHPUnit\Event\Code\NoTestCaseObjectOnCallStackException; +use PHPUnit\Event\Code\TestMethod; +use PHPUnit\Event\Code\TestMethodBuilder; +use PHPUnit\Event\Code\Throwable; +use PHPUnit\Event\Test\DataProviderMethodCalled; +use PHPUnit\Event\Test\DataProviderMethodFinished; +use PHPUnit\Event\TestSuite\Filtered as TestSuiteFiltered; +use PHPUnit\Event\TestSuite\Finished as TestSuiteFinished; +use PHPUnit\Event\TestSuite\Loaded as TestSuiteLoaded; +use PHPUnit\Event\TestSuite\Skipped as TestSuiteSkipped; +use PHPUnit\Event\TestSuite\Sorted as TestSuiteSorted; +use PHPUnit\Event\TestSuite\Started as TestSuiteStarted; +use PHPUnit\Event\TestSuite\TestSuite; +use PHPUnit\Framework\TestCase; +use PHPUnit\Metadata\IgnorePhpunitWarnings; +use PHPUnit\Metadata\Parser\Registry; +use PHPUnit\TextUI\Configuration\Configuration; +use SebastianBergmann\Comparator\Comparator; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class DispatchingEmitter implements Emitter +{ + private readonly Dispatcher $dispatcher; + private readonly Telemetry\System $system; + private readonly Telemetry\Snapshot $startSnapshot; + private Telemetry\Snapshot $previousSnapshot; + + public function __construct(Dispatcher $dispatcher, Telemetry\System $system) + { + $this->dispatcher = $dispatcher; + $this->system = $system; + + $this->startSnapshot = $system->snapshot(); + $this->previousSnapshot = $this->startSnapshot; + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function applicationStarted(): void + { + $this->dispatcher->dispatch( + new Application\Started( + $this->telemetryInfo(), + new Runtime\Runtime, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testRunnerStarted(): void + { + $this->dispatcher->dispatch( + new TestRunner\Started( + $this->telemetryInfo(), + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testRunnerConfigured(Configuration $configuration): void + { + $this->dispatcher->dispatch( + new TestRunner\Configured( + $this->telemetryInfo(), + $configuration, + ), + ); + } + + /** + * @param non-empty-string $filename + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testRunnerBootstrapFinished(string $filename): void + { + $this->dispatcher->dispatch( + new TestRunner\BootstrapFinished( + $this->telemetryInfo(), + $filename, + ), + ); + } + + /** + * @param non-empty-string $filename + * @param non-empty-string $name + * @param non-empty-string $version + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testRunnerLoadedExtensionFromPhar(string $filename, string $name, string $version): void + { + $this->dispatcher->dispatch( + new TestRunner\ExtensionLoadedFromPhar( + $this->telemetryInfo(), + $filename, + $name, + $version, + ), + ); + } + + /** + * @param class-string $className + * @param array $parameters + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testRunnerBootstrappedExtension(string $className, array $parameters): void + { + $this->dispatcher->dispatch( + new TestRunner\ExtensionBootstrapped( + $this->telemetryInfo(), + $className, + $parameters, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function dataProviderMethodCalled(ClassMethod $testMethod, ClassMethod $dataProviderMethod): void + { + $this->dispatcher->dispatch( + new DataProviderMethodCalled( + $this->telemetryInfo(), + $testMethod, + $dataProviderMethod, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function dataProviderMethodFinished(ClassMethod $testMethod, ClassMethod ...$calledMethods): void + { + $this->dispatcher->dispatch( + new DataProviderMethodFinished( + $this->telemetryInfo(), + $testMethod, + ...$calledMethods, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testSuiteLoaded(TestSuite $testSuite): void + { + $this->dispatcher->dispatch( + new TestSuiteLoaded( + $this->telemetryInfo(), + $testSuite, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testSuiteFiltered(TestSuite $testSuite): void + { + $this->dispatcher->dispatch( + new TestSuiteFiltered( + $this->telemetryInfo(), + $testSuite, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testSuiteSorted(int $executionOrder, int $executionOrderDefects, bool $resolveDependencies): void + { + $this->dispatcher->dispatch( + new TestSuiteSorted( + $this->telemetryInfo(), + $executionOrder, + $executionOrderDefects, + $resolveDependencies, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testRunnerEventFacadeSealed(): void + { + $this->dispatcher->dispatch( + new TestRunner\EventFacadeSealed( + $this->telemetryInfo(), + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testRunnerExecutionStarted(TestSuite $testSuite): void + { + $this->dispatcher->dispatch( + new TestRunner\ExecutionStarted( + $this->telemetryInfo(), + $testSuite, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testRunnerDisabledGarbageCollection(): void + { + $this->dispatcher->dispatch( + new TestRunner\GarbageCollectionDisabled($this->telemetryInfo()), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testRunnerTriggeredGarbageCollection(): void + { + $this->dispatcher->dispatch( + new TestRunner\GarbageCollectionTriggered($this->telemetryInfo()), + ); + } + + public function childProcessStarted(): void + { + $this->dispatcher->dispatch( + new TestRunner\ChildProcessStarted($this->telemetryInfo()), + ); + } + + public function childProcessErrored(): void + { + $this->dispatcher->dispatch( + new TestRunner\ChildProcessErrored($this->telemetryInfo()), + ); + } + + public function childProcessFinished(string $stdout, string $stderr): void + { + $this->dispatcher->dispatch( + new TestRunner\ChildProcessFinished( + $this->telemetryInfo(), + $stdout, + $stderr, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testSuiteSkipped(TestSuite $testSuite, string $message): void + { + $this->dispatcher->dispatch( + new TestSuiteSkipped( + $this->telemetryInfo(), + $testSuite, + $message, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testSuiteStarted(TestSuite $testSuite): void + { + $this->dispatcher->dispatch( + new TestSuiteStarted( + $this->telemetryInfo(), + $testSuite, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testPreparationStarted(Code\Test $test): void + { + $this->dispatcher->dispatch( + new Test\PreparationStarted( + $this->telemetryInfo(), + $test, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testPreparationErrored(Code\Test $test, Throwable $throwable): void + { + $this->dispatcher->dispatch( + new Test\PreparationErrored( + $this->telemetryInfo(), + $test, + $throwable, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testPreparationFailed(Code\Test $test, Throwable $throwable): void + { + $this->dispatcher->dispatch( + new Test\PreparationFailed( + $this->telemetryInfo(), + $test, + $throwable, + ), + ); + } + + /** + * @param class-string $testClassName + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function beforeFirstTestMethodCalled(string $testClassName, ClassMethod $calledMethod): void + { + $this->dispatcher->dispatch( + new Test\BeforeFirstTestMethodCalled( + $this->telemetryInfo(), + $testClassName, + $calledMethod, + ), + ); + } + + /** + * @param class-string $testClassName + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function beforeFirstTestMethodErrored(string $testClassName, ClassMethod $calledMethod, Throwable $throwable): void + { + $this->dispatcher->dispatch( + new Test\BeforeFirstTestMethodErrored( + $this->telemetryInfo(), + $testClassName, + $calledMethod, + $throwable, + ), + ); + } + + /** + * @param class-string $testClassName + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function beforeFirstTestMethodFailed(string $testClassName, ClassMethod $calledMethod, Throwable $throwable): void + { + $this->dispatcher->dispatch( + new Test\BeforeFirstTestMethodFailed( + $this->telemetryInfo(), + $testClassName, + $calledMethod, + $throwable, + ), + ); + } + + /** + * @param class-string $testClassName + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function beforeFirstTestMethodFinished(string $testClassName, ClassMethod ...$calledMethods): void + { + $this->dispatcher->dispatch( + new Test\BeforeFirstTestMethodFinished( + $this->telemetryInfo(), + $testClassName, + ...$calledMethods, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function beforeTestMethodCalled(TestMethod $test, ClassMethod $calledMethod): void + { + $this->dispatcher->dispatch( + new Test\BeforeTestMethodCalled( + $this->telemetryInfo(), + $test, + $calledMethod, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function beforeTestMethodErrored(TestMethod $test, ClassMethod $calledMethod, Throwable $throwable): void + { + $this->dispatcher->dispatch( + new Test\BeforeTestMethodErrored( + $this->telemetryInfo(), + $test, + $calledMethod, + $throwable, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function beforeTestMethodFailed(TestMethod $test, ClassMethod $calledMethod, Throwable $throwable): void + { + $this->dispatcher->dispatch( + new Test\BeforeTestMethodFailed( + $this->telemetryInfo(), + $test, + $calledMethod, + $throwable, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function beforeTestMethodFinished(TestMethod $test, ClassMethod ...$calledMethods): void + { + $this->dispatcher->dispatch( + new Test\BeforeTestMethodFinished( + $this->telemetryInfo(), + $test, + ...$calledMethods, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function preConditionCalled(TestMethod $test, ClassMethod $calledMethod): void + { + $this->dispatcher->dispatch( + new Test\PreConditionCalled( + $this->telemetryInfo(), + $test, + $calledMethod, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function preConditionErrored(TestMethod $test, ClassMethod $calledMethod, Throwable $throwable): void + { + $this->dispatcher->dispatch( + new Test\PreConditionErrored( + $this->telemetryInfo(), + $test, + $calledMethod, + $throwable, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function preConditionFailed(TestMethod $test, ClassMethod $calledMethod, Throwable $throwable): void + { + $this->dispatcher->dispatch( + new Test\PreConditionFailed( + $this->telemetryInfo(), + $test, + $calledMethod, + $throwable, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function preConditionFinished(TestMethod $test, ClassMethod ...$calledMethods): void + { + $this->dispatcher->dispatch( + new Test\PreConditionFinished( + $this->telemetryInfo(), + $test, + ...$calledMethods, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testPrepared(Code\Test $test): void + { + memory_reset_peak_usage(); + + $this->dispatcher->dispatch( + new Test\Prepared( + $this->telemetryInfo(), + $test, + ), + ); + } + + /** + * @param class-string $className + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testRegisteredComparator(string $className): void + { + $this->dispatcher->dispatch( + new Test\ComparatorRegistered( + $this->telemetryInfo(), + $className, + ), + ); + } + + /** + * @param class-string $className + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testCreatedMockObject(string $className): void + { + $this->dispatcher->dispatch( + new Test\MockObjectCreated( + $this->telemetryInfo(), + $className, + ), + ); + } + + /** + * @param list $interfaces + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testCreatedMockObjectForIntersectionOfInterfaces(array $interfaces): void + { + $this->dispatcher->dispatch( + new Test\MockObjectForIntersectionOfInterfacesCreated( + $this->telemetryInfo(), + $interfaces, + ), + ); + } + + /** + * @param class-string $className + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testCreatedPartialMockObject(string $className, string ...$methodNames): void + { + $this->dispatcher->dispatch( + new Test\PartialMockObjectCreated( + $this->telemetryInfo(), + $className, + ...$methodNames, + ), + ); + } + + /** + * @param class-string $className + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testCreatedStub(string $className): void + { + $this->dispatcher->dispatch( + new Test\TestStubCreated( + $this->telemetryInfo(), + $className, + ), + ); + } + + /** + * @param list $interfaces + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testCreatedStubForIntersectionOfInterfaces(array $interfaces): void + { + $this->dispatcher->dispatch( + new Test\TestStubForIntersectionOfInterfacesCreated( + $this->telemetryInfo(), + $interfaces, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testErrored(Code\Test $test, Throwable $throwable): void + { + $this->dispatcher->dispatch( + new Test\Errored( + $this->telemetryInfo(), + $test, + $throwable, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testFailed(Code\Test $test, Throwable $throwable, ?ComparisonFailure $comparisonFailure): void + { + $this->dispatcher->dispatch( + new Test\Failed( + $this->telemetryInfo(), + $test, + $throwable, + $comparisonFailure, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testPassed(Code\Test $test): void + { + $this->dispatcher->dispatch( + new Test\Passed( + $this->telemetryInfo(), + $test, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testConsideredRisky(Code\Test $test, string $message): void + { + $this->dispatcher->dispatch( + new Test\ConsideredRisky( + $this->telemetryInfo(), + $test, + $message, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testMarkedAsIncomplete(Code\Test $test, Throwable $throwable): void + { + $this->dispatcher->dispatch( + new Test\MarkedIncomplete( + $this->telemetryInfo(), + $test, + $throwable, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testSkipped(Code\Test $test, string $message): void + { + $this->dispatcher->dispatch( + new Test\Skipped( + $this->telemetryInfo(), + $test, + $message, + ), + ); + } + + /** + * @param non-empty-string $message + * + * @throws InvalidArgumentException + * @throws NoTestCaseObjectOnCallStackException + * @throws UnknownEventTypeException + */ + public function testTriggeredPhpunitDeprecation(?Code\Test $test, string $message): void + { + if ($test === null) { + $test = TestMethodBuilder::fromCallStack(); + } + + if ($test->isTestMethod()) { + assert($test instanceof TestMethod); + + if ($test->metadata()->isIgnorePhpunitDeprecations()->isNotEmpty()) { + return; + } + } + + $this->dispatcher->dispatch( + new Test\PhpunitDeprecationTriggered( + $this->telemetryInfo(), + $test, + $message, + ), + ); + } + + /** + * @param non-empty-string $message + * + * @throws InvalidArgumentException + * @throws NoTestCaseObjectOnCallStackException + * @throws UnknownEventTypeException + */ + public function testTriggeredPhpunitNotice(?Code\Test $test, string $message): void + { + if ($test === null) { + $test = TestMethodBuilder::fromCallStack(); + } + + $this->dispatcher->dispatch( + new Test\PhpunitNoticeTriggered( + $this->telemetryInfo(), + $test, + $message, + ), + ); + } + + /** + * @param non-empty-string $message + * @param non-empty-string $file + * @param positive-int $line + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testTriggeredPhpDeprecation(Code\Test $test, string $message, string $file, int $line, bool $suppressed, bool $ignoredByBaseline, bool $ignoredByTest, IssueTrigger $trigger): void + { + $this->dispatcher->dispatch( + new Test\PhpDeprecationTriggered( + $this->telemetryInfo(), + $test, + $message, + $file, + $line, + $suppressed, + $ignoredByBaseline, + $ignoredByTest, + $trigger, + ), + ); + } + + /** + * @param non-empty-string $message + * @param non-empty-string $file + * @param positive-int $line + * @param non-empty-string $stackTrace + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testTriggeredDeprecation(Code\Test $test, string $message, string $file, int $line, bool $suppressed, bool $ignoredByBaseline, bool $ignoredByTest, IssueTrigger $trigger, string $stackTrace): void + { + $this->dispatcher->dispatch( + new Test\DeprecationTriggered( + $this->telemetryInfo(), + $test, + $message, + $file, + $line, + $suppressed, + $ignoredByBaseline, + $ignoredByTest, + $trigger, + $stackTrace, + ), + ); + } + + /** + * @param non-empty-string $message + * @param non-empty-string $file + * @param positive-int $line + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testTriggeredError(Code\Test $test, string $message, string $file, int $line, bool $suppressed): void + { + $this->dispatcher->dispatch( + new Test\ErrorTriggered( + $this->telemetryInfo(), + $test, + $message, + $file, + $line, + $suppressed, + ), + ); + } + + /** + * @param non-empty-string $message + * @param non-empty-string $file + * @param positive-int $line + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testTriggeredNotice(Code\Test $test, string $message, string $file, int $line, bool $suppressed, bool $ignoredByBaseline): void + { + $this->dispatcher->dispatch( + new Test\NoticeTriggered( + $this->telemetryInfo(), + $test, + $message, + $file, + $line, + $suppressed, + $ignoredByBaseline, + ), + ); + } + + /** + * @param non-empty-string $message + * @param non-empty-string $file + * @param positive-int $line + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testTriggeredPhpNotice(Code\Test $test, string $message, string $file, int $line, bool $suppressed, bool $ignoredByBaseline): void + { + $this->dispatcher->dispatch( + new Test\PhpNoticeTriggered( + $this->telemetryInfo(), + $test, + $message, + $file, + $line, + $suppressed, + $ignoredByBaseline, + ), + ); + } + + /** + * @param non-empty-string $message + * @param non-empty-string $file + * @param positive-int $line + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testTriggeredWarning(Code\Test $test, string $message, string $file, int $line, bool $suppressed, bool $ignoredByBaseline): void + { + $this->dispatcher->dispatch( + new Test\WarningTriggered( + $this->telemetryInfo(), + $test, + $message, + $file, + $line, + $suppressed, + $ignoredByBaseline, + ), + ); + } + + /** + * @param non-empty-string $message + * @param non-empty-string $file + * @param positive-int $line + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testTriggeredPhpWarning(Code\Test $test, string $message, string $file, int $line, bool $suppressed, bool $ignoredByBaseline): void + { + $this->dispatcher->dispatch( + new Test\PhpWarningTriggered( + $this->telemetryInfo(), + $test, + $message, + $file, + $line, + $suppressed, + $ignoredByBaseline, + ), + ); + } + + /** + * @param non-empty-string $message + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testTriggeredPhpunitError(Code\Test $test, string $message): void + { + $this->dispatcher->dispatch( + new Test\PhpunitErrorTriggered( + $this->telemetryInfo(), + $test, + $message, + ), + ); + } + + /** + * @param non-empty-string $message + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testTriggeredPhpunitWarning(Code\Test $test, string $message): void + { + $ignoredByTest = false; + + if ($test->isTestMethod()) { + assert($test instanceof TestMethod); + + $metadata = Registry::parser()->forMethod($test->className(), $test->methodName())->isIgnorePhpunitWarnings()->asArray(); + + if (isset($metadata[0])) { + assert($metadata[0] instanceof IgnorePhpunitWarnings); + + if ($metadata[0]->shouldIgnore($message)) { + $ignoredByTest = true; + } + } + } + + $this->dispatcher->dispatch( + new Test\PhpunitWarningTriggered( + $this->telemetryInfo(), + $test, + $message, + $ignoredByTest, + ), + ); + } + + /** + * @param non-empty-string $output + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testPrintedUnexpectedOutput(string $output): void + { + $this->dispatcher->dispatch( + new Test\PrintedUnexpectedOutput( + $this->telemetryInfo(), + $output, + ), + ); + } + + /** + * @param non-empty-string $additionalInformation + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testProvidedAdditionalInformation(TestMethod $test, string $additionalInformation): void + { + $this->dispatcher->dispatch( + new Test\AdditionalInformationProvided( + $this->telemetryInfo(), + $test, + $additionalInformation, + ), + ); + } + + /** + * @param non-negative-int $numberOfAssertionsPerformed + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testFinished(Code\Test $test, int $numberOfAssertionsPerformed): void + { + $this->dispatcher->dispatch( + new Test\Finished( + $this->telemetryInfo(), + $test, + $numberOfAssertionsPerformed, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function postConditionCalled(TestMethod $test, ClassMethod $calledMethod): void + { + $this->dispatcher->dispatch( + new Test\PostConditionCalled( + $this->telemetryInfo(), + $test, + $calledMethod, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function postConditionErrored(TestMethod $test, ClassMethod $calledMethod, Throwable $throwable): void + { + $this->dispatcher->dispatch( + new Test\PostConditionErrored( + $this->telemetryInfo(), + $test, + $calledMethod, + $throwable, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function postConditionFailed(TestMethod $test, ClassMethod $calledMethod, Throwable $throwable): void + { + $this->dispatcher->dispatch( + new Test\PostConditionFailed( + $this->telemetryInfo(), + $test, + $calledMethod, + $throwable, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function postConditionFinished(TestMethod $test, ClassMethod ...$calledMethods): void + { + $this->dispatcher->dispatch( + new Test\PostConditionFinished( + $this->telemetryInfo(), + $test, + ...$calledMethods, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function afterTestMethodCalled(TestMethod $test, ClassMethod $calledMethod): void + { + $this->dispatcher->dispatch( + new Test\AfterTestMethodCalled( + $this->telemetryInfo(), + $test, + $calledMethod, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function afterTestMethodErrored(TestMethod $test, ClassMethod $calledMethod, Throwable $throwable): void + { + $this->dispatcher->dispatch( + new Test\AfterTestMethodErrored( + $this->telemetryInfo(), + $test, + $calledMethod, + $throwable, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function afterTestMethodFailed(TestMethod $test, ClassMethod $calledMethod, Throwable $throwable): void + { + $this->dispatcher->dispatch( + new Test\AfterTestMethodFailed( + $this->telemetryInfo(), + $test, + $calledMethod, + $throwable, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function afterTestMethodFinished(TestMethod $test, ClassMethod ...$calledMethods): void + { + $this->dispatcher->dispatch( + new Test\AfterTestMethodFinished( + $this->telemetryInfo(), + $test, + ...$calledMethods, + ), + ); + } + + /** + * @param class-string $testClassName + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function afterLastTestMethodCalled(string $testClassName, ClassMethod $calledMethod): void + { + $this->dispatcher->dispatch( + new Test\AfterLastTestMethodCalled( + $this->telemetryInfo(), + $testClassName, + $calledMethod, + ), + ); + } + + /** + * @param class-string $testClassName + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function afterLastTestMethodErrored(string $testClassName, ClassMethod $calledMethod, Throwable $throwable): void + { + $this->dispatcher->dispatch( + new Test\AfterLastTestMethodErrored( + $this->telemetryInfo(), + $testClassName, + $calledMethod, + $throwable, + ), + ); + } + + /** + * @param class-string $testClassName + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function afterLastTestMethodFailed(string $testClassName, ClassMethod $calledMethod, Throwable $throwable): void + { + $this->dispatcher->dispatch( + new Test\AfterLastTestMethodFailed( + $this->telemetryInfo(), + $testClassName, + $calledMethod, + $throwable, + ), + ); + } + + /** + * @param class-string $testClassName + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function afterLastTestMethodFinished(string $testClassName, ClassMethod ...$calledMethods): void + { + $this->dispatcher->dispatch( + new Test\AfterLastTestMethodFinished( + $this->telemetryInfo(), + $testClassName, + ...$calledMethods, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testSuiteFinished(TestSuite $testSuite): void + { + $this->dispatcher->dispatch( + new TestSuiteFinished( + $this->telemetryInfo(), + $testSuite, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testRunnerStartedStaticAnalysisForCodeCoverage(): void + { + $this->dispatcher->dispatch( + new TestRunner\StaticAnalysisForCodeCoverageStarted( + $this->telemetryInfo(), + ), + ); + } + + /** + * @param non-negative-int $cacheHits + * @param non-negative-int $cacheMisses + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testRunnerFinishedStaticAnalysisForCodeCoverage(int $cacheHits, int $cacheMisses): void + { + $this->dispatcher->dispatch( + new TestRunner\StaticAnalysisForCodeCoverageFinished( + $this->telemetryInfo(), + $cacheHits, + $cacheMisses, + ), + ); + } + + /** + * @param non-empty-string $message + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testRunnerTriggeredPhpunitDeprecation(string $message): void + { + try { + if (TestMethodBuilder::fromCallStack()->metadata()->isIgnorePhpunitDeprecations()->isNotEmpty()) { + return; + } + } catch (NoTestCaseObjectOnCallStackException) { + } + + $this->dispatcher->dispatch( + new TestRunner\DeprecationTriggered( + $this->telemetryInfo(), + $message, + ), + ); + } + + /** + * @param non-empty-string $message + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testRunnerTriggeredPhpunitNotice(string $message): void + { + $this->dispatcher->dispatch( + new TestRunner\NoticeTriggered( + $this->telemetryInfo(), + $message, + ), + ); + } + + /** + * @param non-empty-string $message + * + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testRunnerTriggeredPhpunitWarning(string $message): void + { + $this->dispatcher->dispatch( + new TestRunner\WarningTriggered( + $this->telemetryInfo(), + $message, + ), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testRunnerEnabledGarbageCollection(): void + { + $this->dispatcher->dispatch( + new TestRunner\GarbageCollectionEnabled($this->telemetryInfo()), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testRunnerExecutionAborted(): void + { + $this->dispatcher->dispatch( + new TestRunner\ExecutionAborted($this->telemetryInfo()), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testRunnerExecutionFinished(): void + { + $this->dispatcher->dispatch( + new TestRunner\ExecutionFinished($this->telemetryInfo()), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function testRunnerFinished(): void + { + $this->dispatcher->dispatch( + new TestRunner\Finished($this->telemetryInfo()), + ); + } + + /** + * @throws InvalidArgumentException + * @throws UnknownEventTypeException + */ + public function applicationFinished(int $shellExitCode): void + { + $this->dispatcher->dispatch( + new Application\Finished( + $this->telemetryInfo(), + $shellExitCode, + ), + ); + } + + /** + * @throws InvalidArgumentException + */ + private function telemetryInfo(): Telemetry\Info + { + $current = $this->system->snapshot(); + + $info = new Telemetry\Info( + $current, + $current->time()->duration($this->startSnapshot->time()), + $current->memoryUsage()->diff($this->startSnapshot->memoryUsage()), + $current->time()->duration($this->previousSnapshot->time()), + $current->memoryUsage()->diff($this->previousSnapshot->memoryUsage()), + ); + + $this->previousSnapshot = $current; + + return $info; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Emitter/Emitter.php b/app/vendor/phpunit/phpunit/src/Event/Emitter/Emitter.php new file mode 100644 index 000000000..eb9d75cca --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Emitter/Emitter.php @@ -0,0 +1,331 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +use PHPUnit\Event\Code\ClassMethod; +use PHPUnit\Event\Code\ComparisonFailure; +use PHPUnit\Event\Code\IssueTrigger\IssueTrigger; +use PHPUnit\Event\Code\TestMethod; +use PHPUnit\Event\Code\Throwable; +use PHPUnit\Event\TestSuite\TestSuite; +use PHPUnit\Framework\TestCase; +use PHPUnit\TextUI\Configuration\Configuration; +use SebastianBergmann\Comparator\Comparator; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +interface Emitter +{ + public function applicationStarted(): void; + + public function testRunnerStarted(): void; + + public function testRunnerConfigured(Configuration $configuration): void; + + /** + * @param non-empty-string $filename + */ + public function testRunnerBootstrapFinished(string $filename): void; + + /** + * @param non-empty-string $filename + * @param non-empty-string $name + * @param non-empty-string $version + */ + public function testRunnerLoadedExtensionFromPhar(string $filename, string $name, string $version): void; + + /** + * @param class-string $className + * @param array $parameters + */ + public function testRunnerBootstrappedExtension(string $className, array $parameters): void; + + public function dataProviderMethodCalled(ClassMethod $testMethod, ClassMethod $dataProviderMethod): void; + + public function dataProviderMethodFinished(ClassMethod $testMethod, ClassMethod ...$calledMethods): void; + + public function testSuiteLoaded(TestSuite $testSuite): void; + + public function testSuiteFiltered(TestSuite $testSuite): void; + + public function testSuiteSorted(int $executionOrder, int $executionOrderDefects, bool $resolveDependencies): void; + + public function testRunnerEventFacadeSealed(): void; + + public function testRunnerExecutionStarted(TestSuite $testSuite): void; + + public function testRunnerDisabledGarbageCollection(): void; + + public function testRunnerTriggeredGarbageCollection(): void; + + /** + * @param non-empty-string $message + */ + public function testSuiteSkipped(TestSuite $testSuite, string $message): void; + + public function testSuiteStarted(TestSuite $testSuite): void; + + public function testPreparationStarted(Code\Test $test): void; + + public function testPreparationErrored(Code\Test $test, Throwable $throwable): void; + + public function testPreparationFailed(Code\Test $test, Throwable $throwable): void; + + /** + * @param class-string $testClassName + */ + public function beforeFirstTestMethodCalled(string $testClassName, ClassMethod $calledMethod): void; + + /** + * @param class-string $testClassName + */ + public function beforeFirstTestMethodErrored(string $testClassName, ClassMethod $calledMethod, Throwable $throwable): void; + + /** + * @param class-string $testClassName + */ + public function beforeFirstTestMethodFailed(string $testClassName, ClassMethod $calledMethod, Throwable $throwable): void; + + /** + * @param class-string $testClassName + */ + public function beforeFirstTestMethodFinished(string $testClassName, ClassMethod ...$calledMethods): void; + + public function beforeTestMethodCalled(TestMethod $test, ClassMethod $calledMethod): void; + + public function beforeTestMethodErrored(TestMethod $test, ClassMethod $calledMethod, Throwable $throwable): void; + + public function beforeTestMethodFailed(TestMethod $test, ClassMethod $calledMethod, Throwable $throwable): void; + + public function beforeTestMethodFinished(TestMethod $test, ClassMethod ...$calledMethods): void; + + public function preConditionCalled(TestMethod $test, ClassMethod $calledMethod): void; + + public function preConditionErrored(TestMethod $test, ClassMethod $calledMethod, Throwable $throwable): void; + + public function preConditionFailed(TestMethod $test, ClassMethod $calledMethod, Throwable $throwable): void; + + public function preConditionFinished(TestMethod $test, ClassMethod ...$calledMethods): void; + + public function testPrepared(Code\Test $test): void; + + /** + * @param class-string $className + */ + public function testRegisteredComparator(string $className): void; + + /** + * @param class-string $className + */ + public function testCreatedMockObject(string $className): void; + + /** + * @param list $interfaces + */ + public function testCreatedMockObjectForIntersectionOfInterfaces(array $interfaces): void; + + /** + * @param class-string $className + */ + public function testCreatedPartialMockObject(string $className, string ...$methodNames): void; + + /** + * @param class-string $className + */ + public function testCreatedStub(string $className): void; + + /** + * @param list $interfaces + */ + public function testCreatedStubForIntersectionOfInterfaces(array $interfaces): void; + + public function testErrored(Code\Test $test, Throwable $throwable): void; + + public function testFailed(Code\Test $test, Throwable $throwable, ?ComparisonFailure $comparisonFailure): void; + + public function testPassed(Code\Test $test): void; + + /** + * @param non-empty-string $message + */ + public function testConsideredRisky(Code\Test $test, string $message): void; + + public function testMarkedAsIncomplete(Code\Test $test, Throwable $throwable): void; + + /** + * @param non-empty-string $message + */ + public function testSkipped(Code\Test $test, string $message): void; + + /** + * @param non-empty-string $message + */ + public function testTriggeredPhpunitDeprecation(?Code\Test $test, string $message): void; + + /** + * @param non-empty-string $message + */ + public function testTriggeredPhpunitNotice(?Code\Test $test, string $message): void; + + /** + * @param non-empty-string $message + * @param non-empty-string $file + * @param positive-int $line + */ + public function testTriggeredPhpDeprecation(Code\Test $test, string $message, string $file, int $line, bool $suppressed, bool $ignoredByBaseline, bool $ignoredByTest, IssueTrigger $trigger): void; + + /** + * @param non-empty-string $message + * @param non-empty-string $file + * @param positive-int $line + * @param non-empty-string $stackTrace + */ + public function testTriggeredDeprecation(Code\Test $test, string $message, string $file, int $line, bool $suppressed, bool $ignoredByBaseline, bool $ignoredByTest, IssueTrigger $trigger, string $stackTrace): void; + + /** + * @param non-empty-string $message + * @param non-empty-string $file + * @param positive-int $line + */ + public function testTriggeredError(Code\Test $test, string $message, string $file, int $line, bool $suppressed): void; + + /** + * @param non-empty-string $message + * @param non-empty-string $file + * @param positive-int $line + */ + public function testTriggeredNotice(Code\Test $test, string $message, string $file, int $line, bool $suppressed, bool $ignoredByBaseline): void; + + /** + * @param non-empty-string $message + * @param non-empty-string $file + * @param positive-int $line + */ + public function testTriggeredPhpNotice(Code\Test $test, string $message, string $file, int $line, bool $suppressed, bool $ignoredByBaseline): void; + + /** + * @param non-empty-string $message + * @param non-empty-string $file + * @param positive-int $line + */ + public function testTriggeredWarning(Code\Test $test, string $message, string $file, int $line, bool $suppressed, bool $ignoredByBaseline): void; + + /** + * @param non-empty-string $message + * @param non-empty-string $file + * @param positive-int $line + */ + public function testTriggeredPhpWarning(Code\Test $test, string $message, string $file, int $line, bool $suppressed, bool $ignoredByBaseline): void; + + /** + * @param non-empty-string $message + */ + public function testTriggeredPhpunitError(Code\Test $test, string $message): void; + + /** + * @param non-empty-string $message + */ + public function testTriggeredPhpunitWarning(Code\Test $test, string $message): void; + + /** + * @param non-empty-string $output + */ + public function testPrintedUnexpectedOutput(string $output): void; + + /** + * @param non-empty-string $additionalInformation + */ + public function testProvidedAdditionalInformation(TestMethod $test, string $additionalInformation): void; + + /** + * @param non-negative-int $numberOfAssertionsPerformed + */ + public function testFinished(Code\Test $test, int $numberOfAssertionsPerformed): void; + + public function postConditionCalled(TestMethod $test, ClassMethod $calledMethod): void; + + public function postConditionErrored(TestMethod $test, ClassMethod $calledMethod, Throwable $throwable): void; + + public function postConditionFailed(TestMethod $test, ClassMethod $calledMethod, Throwable $throwable): void; + + public function postConditionFinished(TestMethod $test, ClassMethod ...$calledMethods): void; + + public function afterTestMethodCalled(TestMethod $test, ClassMethod $calledMethod): void; + + public function afterTestMethodErrored(TestMethod $test, ClassMethod $calledMethod, Throwable $throwable): void; + + public function afterTestMethodFailed(TestMethod $test, ClassMethod $calledMethod, Throwable $throwable): void; + + public function afterTestMethodFinished(TestMethod $test, ClassMethod ...$calledMethods): void; + + /** + * @param class-string $testClassName + */ + public function afterLastTestMethodCalled(string $testClassName, ClassMethod $calledMethod): void; + + /** + * @param class-string $testClassName + */ + public function afterLastTestMethodErrored(string $testClassName, ClassMethod $calledMethod, Throwable $throwable): void; + + /** + * @param class-string $testClassName + */ + public function afterLastTestMethodFailed(string $testClassName, ClassMethod $calledMethod, Throwable $throwable): void; + + /** + * @param class-string $testClassName + */ + public function afterLastTestMethodFinished(string $testClassName, ClassMethod ...$calledMethods): void; + + public function testSuiteFinished(TestSuite $testSuite): void; + + public function childProcessStarted(): void; + + public function childProcessErrored(): void; + + public function childProcessFinished(string $stdout, string $stderr): void; + + public function testRunnerStartedStaticAnalysisForCodeCoverage(): void; + + /** + * @param non-negative-int $cacheHits + * @param non-negative-int $cacheMisses + */ + public function testRunnerFinishedStaticAnalysisForCodeCoverage(int $cacheHits, int $cacheMisses): void; + + /** + * @param non-empty-string $message + */ + public function testRunnerTriggeredPhpunitDeprecation(string $message): void; + + /** + * @param non-empty-string $message + */ + public function testRunnerTriggeredPhpunitNotice(string $message): void; + + /** + * @param non-empty-string $message + */ + public function testRunnerTriggeredPhpunitWarning(string $message): void; + + public function testRunnerEnabledGarbageCollection(): void; + + public function testRunnerExecutionAborted(): void; + + public function testRunnerExecutionFinished(): void; + + public function testRunnerFinished(): void; + + public function applicationFinished(int $shellExitCode): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Application/Finished.php b/app/vendor/phpunit/phpunit/src/Event/Events/Application/Finished.php new file mode 100644 index 000000000..6e94da2a9 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Application/Finished.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Application; + +use function sprintf; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Finished implements Event +{ + private Telemetry\Info $telemetryInfo; + private int $shellExitCode; + + public function __construct(Telemetry\Info $telemetryInfo, int $shellExitCode) + { + $this->telemetryInfo = $telemetryInfo; + $this->shellExitCode = $shellExitCode; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function shellExitCode(): int + { + return $this->shellExitCode; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'PHPUnit Finished (Shell Exit Code: %d)', + $this->shellExitCode, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Application/FinishedSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Application/FinishedSubscriber.php new file mode 100644 index 000000000..1e7597761 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Application/FinishedSubscriber.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 PHPUnit\Event\Application; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface FinishedSubscriber extends Subscriber +{ + public function notify(Finished $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Application/Started.php b/app/vendor/phpunit/phpunit/src/Event/Events/Application/Started.php new file mode 100644 index 000000000..a9aa959a7 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Application/Started.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 PHPUnit\Event\Application; + +use function sprintf; +use PHPUnit\Event\Event; +use PHPUnit\Event\Runtime\Runtime; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Started implements Event +{ + private Telemetry\Info $telemetryInfo; + private Runtime $runtime; + + public function __construct(Telemetry\Info $telemetryInfo, Runtime $runtime) + { + $this->telemetryInfo = $telemetryInfo; + $this->runtime = $runtime; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function runtime(): Runtime + { + return $this->runtime; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'PHPUnit Started (%s)', + $this->runtime->asString(), + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Application/StartedSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Application/StartedSubscriber.php new file mode 100644 index 000000000..f2ebee2fd --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Application/StartedSubscriber.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 PHPUnit\Event\Application; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface StartedSubscriber extends Subscriber +{ + public function notify(Started $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Event.php b/app/vendor/phpunit/phpunit/src/Event/Events/Event.php new file mode 100644 index 000000000..443ca7294 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Event.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 PHPUnit\Event; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface Event +{ + public function telemetryInfo(): Telemetry\Info; + + /** + * @return non-empty-string + */ + public function asString(): string; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/EventCollection.php b/app/vendor/phpunit/phpunit/src/Event/Events/EventCollection.php new file mode 100644 index 000000000..7c6912692 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/EventCollection.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +use function count; +use Countable; +use IteratorAggregate; + +/** + * @template-implements IteratorAggregate + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class EventCollection implements Countable, IteratorAggregate +{ + /** + * @var list + */ + private array $events = []; + + public function add(Event ...$events): void + { + foreach ($events as $event) { + $this->events[] = $event; + } + } + + /** + * @return list + */ + public function asArray(): array + { + return $this->events; + } + + public function count(): int + { + return count($this->events); + } + + public function isEmpty(): bool + { + return $this->count() === 0; + } + + public function isNotEmpty(): bool + { + return $this->count() > 0; + } + + public function getIterator(): EventCollectionIterator + { + return new EventCollectionIterator($this); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/EventCollectionIterator.php b/app/vendor/phpunit/phpunit/src/Event/Events/EventCollectionIterator.php new file mode 100644 index 000000000..d121dea6e --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/EventCollectionIterator.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 PHPUnit\Event; + +use function count; +use Iterator; + +/** + * @template-implements Iterator + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class EventCollectionIterator implements Iterator +{ + /** + * @var list + */ + private readonly array $events; + private int $position = 0; + + public function __construct(EventCollection $events) + { + $this->events = $events->asArray(); + } + + public function rewind(): void + { + $this->position = 0; + } + + public function valid(): bool + { + return $this->position < count($this->events); + } + + public function key(): int + { + return $this->position; + } + + public function current(): Event + { + return $this->events[$this->position]; + } + + public function next(): void + { + $this->position++; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/AdditionalInformationProvided.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/AdditionalInformationProvided.php new file mode 100644 index 000000000..d8f819ee4 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/AdditionalInformationProvided.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use PHPUnit\Event\Code\TestMethod; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class AdditionalInformationProvided implements Event +{ + private Telemetry\Info $telemetryInfo; + private TestMethod $test; + + /** + * @var non-empty-string + */ + private string $additionalInformation; + + /** + * @param non-empty-string $additionalInformation + */ + public function __construct(Telemetry\Info $telemetryInfo, TestMethod $test, string $additionalInformation) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->additionalInformation = $additionalInformation; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): TestMethod + { + return $this->test; + } + + /** + * @return non-empty-string + */ + public function additionalInformation(): string + { + return $this->additionalInformation; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Test Provided Additional Information%s%s', + PHP_EOL, + $this->additionalInformation, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/AdditionalInformationProvidedSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/AdditionalInformationProvidedSubscriber.php new file mode 100644 index 000000000..7d175c594 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/AdditionalInformationProvidedSubscriber.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface AdditionalInformationProvidedSubscriber extends Subscriber +{ + public function notify(AdditionalInformationProvided $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/ComparatorRegistered.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/ComparatorRegistered.php new file mode 100644 index 000000000..9db33d5ea --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/ComparatorRegistered.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 PHPUnit\Event\Test; + +use function sprintf; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; +use SebastianBergmann\Comparator\Comparator; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ComparatorRegistered implements Event +{ + private Telemetry\Info $telemetryInfo; + + /** + * @var class-string + */ + private string $className; + + /** + * @param class-string $className + */ + public function __construct(Telemetry\Info $telemetryInfo, string $className) + { + $this->telemetryInfo = $telemetryInfo; + $this->className = $className; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Comparator Registered (%s)', + $this->className, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/ComparatorRegisteredSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/ComparatorRegisteredSubscriber.php new file mode 100644 index 000000000..10ba78e4f --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/ComparatorRegisteredSubscriber.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface ComparatorRegisteredSubscriber extends Subscriber +{ + public function notify(ComparatorRegistered $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/AfterLastTestMethodCalled.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/AfterLastTestMethodCalled.php new file mode 100644 index 000000000..2c875a8cf --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/AfterLastTestMethodCalled.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; +use PHPUnit\Framework\TestCase; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class AfterLastTestMethodCalled implements Event +{ + private Telemetry\Info $telemetryInfo; + + /** + * @var class-string + */ + private string $testClassName; + private Code\ClassMethod $calledMethod; + + /** + * @param class-string $testClassName + */ + public function __construct(Telemetry\Info $telemetryInfo, string $testClassName, Code\ClassMethod $calledMethod) + { + $this->telemetryInfo = $telemetryInfo; + $this->testClassName = $testClassName; + $this->calledMethod = $calledMethod; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return class-string + */ + public function testClassName(): string + { + return $this->testClassName; + } + + public function calledMethod(): Code\ClassMethod + { + return $this->calledMethod; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'After Last Test Method Called (%s::%s)', + $this->calledMethod->className(), + $this->calledMethod->methodName(), + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/AfterLastTestMethodCalledSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/AfterLastTestMethodCalledSubscriber.php new file mode 100644 index 000000000..08530ab7b --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/AfterLastTestMethodCalledSubscriber.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface AfterLastTestMethodCalledSubscriber extends Subscriber +{ + public function notify(AfterLastTestMethodCalled $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/AfterLastTestMethodErrored.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/AfterLastTestMethodErrored.php new file mode 100644 index 000000000..ecdb7a15d --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/AfterLastTestMethodErrored.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Code\Throwable; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; +use PHPUnit\Framework\TestCase; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class AfterLastTestMethodErrored implements Event +{ + private Telemetry\Info $telemetryInfo; + + /** + * @var class-string + */ + private string $testClassName; + private Code\ClassMethod $calledMethod; + private Throwable $throwable; + + /** + * @param class-string $testClassName + */ + public function __construct(Telemetry\Info $telemetryInfo, string $testClassName, Code\ClassMethod $calledMethod, Throwable $throwable) + { + $this->telemetryInfo = $telemetryInfo; + $this->testClassName = $testClassName; + $this->calledMethod = $calledMethod; + $this->throwable = $throwable; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return class-string + */ + public function testClassName(): string + { + return $this->testClassName; + } + + public function calledMethod(): Code\ClassMethod + { + return $this->calledMethod; + } + + public function throwable(): Throwable + { + return $this->throwable; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = $this->throwable->message(); + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + return sprintf( + 'After Last Test Method Errored (%s::%s)%s', + $this->calledMethod->className(), + $this->calledMethod->methodName(), + $message, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/AfterLastTestMethodErroredSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/AfterLastTestMethodErroredSubscriber.php new file mode 100644 index 000000000..b994fdeb2 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/AfterLastTestMethodErroredSubscriber.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface AfterLastTestMethodErroredSubscriber extends Subscriber +{ + public function notify(AfterLastTestMethodErrored $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/AfterLastTestMethodFailed.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/AfterLastTestMethodFailed.php new file mode 100644 index 000000000..b22146ea4 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/AfterLastTestMethodFailed.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Code\Throwable; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; +use PHPUnit\Framework\TestCase; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class AfterLastTestMethodFailed implements Event +{ + private Telemetry\Info $telemetryInfo; + + /** + * @var class-string + */ + private string $testClassName; + private Code\ClassMethod $calledMethod; + private Throwable $throwable; + + /** + * @param class-string $testClassName + */ + public function __construct(Telemetry\Info $telemetryInfo, string $testClassName, Code\ClassMethod $calledMethod, Throwable $throwable) + { + $this->telemetryInfo = $telemetryInfo; + $this->testClassName = $testClassName; + $this->calledMethod = $calledMethod; + $this->throwable = $throwable; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return class-string + */ + public function testClassName(): string + { + return $this->testClassName; + } + + public function calledMethod(): Code\ClassMethod + { + return $this->calledMethod; + } + + public function throwable(): Throwable + { + return $this->throwable; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = $this->throwable->message(); + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + return sprintf( + 'After Last Test Method Failed (%s::%s)%s', + $this->calledMethod->className(), + $this->calledMethod->methodName(), + $message, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/AfterLastTestMethodFailedSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/AfterLastTestMethodFailedSubscriber.php new file mode 100644 index 000000000..3b011bd99 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/AfterLastTestMethodFailedSubscriber.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface AfterLastTestMethodFailedSubscriber extends Subscriber +{ + public function notify(AfterLastTestMethodFailed $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/AfterLastTestMethodFinished.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/AfterLastTestMethodFinished.php new file mode 100644 index 000000000..d8a5a11f2 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/AfterLastTestMethodFinished.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 PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; +use PHPUnit\Framework\TestCase; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class AfterLastTestMethodFinished implements Event +{ + private Telemetry\Info $telemetryInfo; + + /** + * @var class-string + */ + private string $testClassName; + + /** + * @var list + */ + private array $calledMethods; + + /** + * @param class-string $testClassName + */ + public function __construct(Telemetry\Info $telemetryInfo, string $testClassName, Code\ClassMethod ...$calledMethods) + { + $this->telemetryInfo = $telemetryInfo; + $this->testClassName = $testClassName; + $this->calledMethods = $calledMethods; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return class-string + */ + public function testClassName(): string + { + return $this->testClassName; + } + + /** + * @return list + */ + public function calledMethods(): array + { + return $this->calledMethods; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $buffer = 'After Last Test Method Finished:'; + + foreach ($this->calledMethods as $calledMethod) { + $buffer .= sprintf( + PHP_EOL . '- %s::%s', + $calledMethod->className(), + $calledMethod->methodName(), + ); + } + + return $buffer; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/AfterLastTestMethodFinishedSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/AfterLastTestMethodFinishedSubscriber.php new file mode 100644 index 000000000..0a366b0d8 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/AfterLastTestMethodFinishedSubscriber.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface AfterLastTestMethodFinishedSubscriber extends Subscriber +{ + public function notify(AfterLastTestMethodFinished $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/AfterTestMethodCalled.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/AfterTestMethodCalled.php new file mode 100644 index 000000000..93c4e12cb --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/AfterTestMethodCalled.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 PHPUnit\Event\Test; + +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class AfterTestMethodCalled implements Event +{ + private Telemetry\Info $telemetryInfo; + private Code\TestMethod $test; + private Code\ClassMethod $calledMethod; + + public function __construct(Telemetry\Info $telemetryInfo, Code\TestMethod $test, Code\ClassMethod $calledMethod) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->calledMethod = $calledMethod; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Code\TestMethod + { + return $this->test; + } + + /** + * @return class-string + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/6140 + */ + public function testClassName(): string + { + return $this->test->className(); + } + + public function calledMethod(): Code\ClassMethod + { + return $this->calledMethod; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'After Test Method Called (%s::%s)', + $this->calledMethod->className(), + $this->calledMethod->methodName(), + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/AfterTestMethodCalledSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/AfterTestMethodCalledSubscriber.php new file mode 100644 index 000000000..3e72fc91c --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/AfterTestMethodCalledSubscriber.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface AfterTestMethodCalledSubscriber extends Subscriber +{ + public function notify(AfterTestMethodCalled $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/AfterTestMethodErrored.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/AfterTestMethodErrored.php new file mode 100644 index 000000000..fd277e2d0 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/AfterTestMethodErrored.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 PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Code\Throwable; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class AfterTestMethodErrored implements Event +{ + private Telemetry\Info $telemetryInfo; + private Code\TestMethod $test; + private Code\ClassMethod $calledMethod; + private Throwable $throwable; + + public function __construct(Telemetry\Info $telemetryInfo, Code\TestMethod $test, Code\ClassMethod $calledMethod, Throwable $throwable) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->calledMethod = $calledMethod; + $this->throwable = $throwable; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Code\TestMethod + { + return $this->test; + } + + /** + * @return class-string + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/6140 + */ + public function testClassName(): string + { + return $this->test->className(); + } + + public function calledMethod(): Code\ClassMethod + { + return $this->calledMethod; + } + + public function throwable(): Throwable + { + return $this->throwable; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = $this->throwable->message(); + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + return sprintf( + 'After Test Method Errored (%s::%s)%s', + $this->calledMethod->className(), + $this->calledMethod->methodName(), + $message, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/AfterTestMethodErroredSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/AfterTestMethodErroredSubscriber.php new file mode 100644 index 000000000..622f91625 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/AfterTestMethodErroredSubscriber.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface AfterTestMethodErroredSubscriber extends Subscriber +{ + public function notify(AfterTestMethodErrored $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/AfterTestMethodFailed.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/AfterTestMethodFailed.php new file mode 100644 index 000000000..e405066f7 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/AfterTestMethodFailed.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 PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Code\Throwable; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class AfterTestMethodFailed implements Event +{ + private Telemetry\Info $telemetryInfo; + private Code\TestMethod $test; + private Code\ClassMethod $calledMethod; + private Throwable $throwable; + + public function __construct(Telemetry\Info $telemetryInfo, Code\TestMethod $test, Code\ClassMethod $calledMethod, Throwable $throwable) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->calledMethod = $calledMethod; + $this->throwable = $throwable; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Code\TestMethod + { + return $this->test; + } + + public function calledMethod(): Code\ClassMethod + { + return $this->calledMethod; + } + + public function throwable(): Throwable + { + return $this->throwable; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = $this->throwable->message(); + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + return sprintf( + 'After Test Method Failed (%s::%s)%s', + $this->calledMethod->className(), + $this->calledMethod->methodName(), + $message, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/AfterTestMethodFailedSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/AfterTestMethodFailedSubscriber.php new file mode 100644 index 000000000..16134322a --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/AfterTestMethodFailedSubscriber.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface AfterTestMethodFailedSubscriber extends Subscriber +{ + public function notify(AfterTestMethodFailed $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/AfterTestMethodFinished.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/AfterTestMethodFinished.php new file mode 100644 index 000000000..f897198e0 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/AfterTestMethodFinished.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 PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class AfterTestMethodFinished implements Event +{ + private Telemetry\Info $telemetryInfo; + private Code\TestMethod $test; + + /** + * @var list + */ + private array $calledMethods; + + public function __construct(Telemetry\Info $telemetryInfo, Code\TestMethod $test, Code\ClassMethod ...$calledMethods) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->calledMethods = $calledMethods; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Code\TestMethod + { + return $this->test; + } + + /** + * @return class-string + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/6140 + */ + public function testClassName(): string + { + return $this->test->className(); + } + + /** + * @return list + */ + public function calledMethods(): array + { + return $this->calledMethods; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $buffer = 'After Test Method Finished:'; + + foreach ($this->calledMethods as $calledMethod) { + $buffer .= sprintf( + PHP_EOL . '- %s::%s', + $calledMethod->className(), + $calledMethod->methodName(), + ); + } + + return $buffer; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/AfterTestMethodFinishedSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/AfterTestMethodFinishedSubscriber.php new file mode 100644 index 000000000..5e5668898 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/AfterTestMethodFinishedSubscriber.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface AfterTestMethodFinishedSubscriber extends Subscriber +{ + public function notify(AfterTestMethodFinished $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/BeforeFirstTestMethodCalled.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/BeforeFirstTestMethodCalled.php new file mode 100644 index 000000000..b08eb8b95 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/BeforeFirstTestMethodCalled.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; +use PHPUnit\Framework\TestCase; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class BeforeFirstTestMethodCalled implements Event +{ + private Telemetry\Info $telemetryInfo; + + /** + * @var class-string + */ + private string $testClassName; + private Code\ClassMethod $calledMethod; + + /** + * @param class-string $testClassName + */ + public function __construct(Telemetry\Info $telemetryInfo, string $testClassName, Code\ClassMethod $calledMethod) + { + $this->telemetryInfo = $telemetryInfo; + $this->testClassName = $testClassName; + $this->calledMethod = $calledMethod; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return class-string + */ + public function testClassName(): string + { + return $this->testClassName; + } + + public function calledMethod(): Code\ClassMethod + { + return $this->calledMethod; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Before First Test Method Called (%s::%s)', + $this->calledMethod->className(), + $this->calledMethod->methodName(), + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/BeforeFirstTestMethodCalledSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/BeforeFirstTestMethodCalledSubscriber.php new file mode 100644 index 000000000..a0d4281ff --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/BeforeFirstTestMethodCalledSubscriber.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface BeforeFirstTestMethodCalledSubscriber extends Subscriber +{ + public function notify(BeforeFirstTestMethodCalled $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/BeforeFirstTestMethodErrored.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/BeforeFirstTestMethodErrored.php new file mode 100644 index 000000000..a2e37b337 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/BeforeFirstTestMethodErrored.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Code\Throwable; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; +use PHPUnit\Framework\TestCase; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class BeforeFirstTestMethodErrored implements Event +{ + private Telemetry\Info $telemetryInfo; + + /** + * @var class-string + */ + private string $testClassName; + private Code\ClassMethod $calledMethod; + private Throwable $throwable; + + /** + * @param class-string $testClassName + */ + public function __construct(Telemetry\Info $telemetryInfo, string $testClassName, Code\ClassMethod $calledMethod, Throwable $throwable) + { + $this->telemetryInfo = $telemetryInfo; + $this->testClassName = $testClassName; + $this->calledMethod = $calledMethod; + $this->throwable = $throwable; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return class-string + */ + public function testClassName(): string + { + return $this->testClassName; + } + + public function calledMethod(): Code\ClassMethod + { + return $this->calledMethod; + } + + public function throwable(): Throwable + { + return $this->throwable; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = $this->throwable->message(); + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + return sprintf( + 'Before First Test Method Errored (%s::%s)%s', + $this->calledMethod->className(), + $this->calledMethod->methodName(), + $message, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/BeforeFirstTestMethodErroredSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/BeforeFirstTestMethodErroredSubscriber.php new file mode 100644 index 000000000..9a1b87542 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/BeforeFirstTestMethodErroredSubscriber.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface BeforeFirstTestMethodErroredSubscriber extends Subscriber +{ + public function notify(BeforeFirstTestMethodErrored $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/BeforeFirstTestMethodFailed.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/BeforeFirstTestMethodFailed.php new file mode 100644 index 000000000..33e669a4e --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/BeforeFirstTestMethodFailed.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Code\Throwable; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; +use PHPUnit\Framework\TestCase; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class BeforeFirstTestMethodFailed implements Event +{ + private Telemetry\Info $telemetryInfo; + + /** + * @var class-string + */ + private string $testClassName; + private Code\ClassMethod $calledMethod; + private Throwable $throwable; + + /** + * @param class-string $testClassName + */ + public function __construct(Telemetry\Info $telemetryInfo, string $testClassName, Code\ClassMethod $calledMethod, Throwable $throwable) + { + $this->telemetryInfo = $telemetryInfo; + $this->testClassName = $testClassName; + $this->calledMethod = $calledMethod; + $this->throwable = $throwable; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return class-string + */ + public function testClassName(): string + { + return $this->testClassName; + } + + public function calledMethod(): Code\ClassMethod + { + return $this->calledMethod; + } + + public function throwable(): Throwable + { + return $this->throwable; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = $this->throwable->message(); + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + return sprintf( + 'Before First Test Method Failed (%s::%s)%s', + $this->calledMethod->className(), + $this->calledMethod->methodName(), + $message, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/BeforeFirstTestMethodFailedSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/BeforeFirstTestMethodFailedSubscriber.php new file mode 100644 index 000000000..4e0b7eff7 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/BeforeFirstTestMethodFailedSubscriber.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface BeforeFirstTestMethodFailedSubscriber extends Subscriber +{ + public function notify(BeforeFirstTestMethodFailed $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/BeforeFirstTestMethodFinished.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/BeforeFirstTestMethodFinished.php new file mode 100644 index 000000000..87230a676 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/BeforeFirstTestMethodFinished.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 PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; +use PHPUnit\Framework\TestCase; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class BeforeFirstTestMethodFinished implements Event +{ + private Telemetry\Info$telemetryInfo; + + /** + * @var class-string + */ + private string $testClassName; + + /** + * @var list + */ + private array $calledMethods; + + /** + * @param class-string $testClassName + */ + public function __construct(Telemetry\Info $telemetryInfo, string $testClassName, Code\ClassMethod ...$calledMethods) + { + $this->telemetryInfo = $telemetryInfo; + $this->testClassName = $testClassName; + $this->calledMethods = $calledMethods; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return class-string + */ + public function testClassName(): string + { + return $this->testClassName; + } + + /** + * @return list + */ + public function calledMethods(): array + { + return $this->calledMethods; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $buffer = 'Before First Test Method Finished:'; + + foreach ($this->calledMethods as $calledMethod) { + $buffer .= sprintf( + PHP_EOL . '- %s::%s', + $calledMethod->className(), + $calledMethod->methodName(), + ); + } + + return $buffer; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/BeforeFirstTestMethodFinishedSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/BeforeFirstTestMethodFinishedSubscriber.php new file mode 100644 index 000000000..c9f180641 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/BeforeFirstTestMethodFinishedSubscriber.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface BeforeFirstTestMethodFinishedSubscriber extends Subscriber +{ + public function notify(BeforeFirstTestMethodFinished $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/BeforeTestMethodCalled.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/BeforeTestMethodCalled.php new file mode 100644 index 000000000..2927a6851 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/BeforeTestMethodCalled.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 PHPUnit\Event\Test; + +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class BeforeTestMethodCalled implements Event +{ + private Telemetry\Info $telemetryInfo; + private Code\TestMethod $test; + private Code\ClassMethod $calledMethod; + + public function __construct(Telemetry\Info $telemetryInfo, Code\TestMethod $test, Code\ClassMethod $calledMethod) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->calledMethod = $calledMethod; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Code\TestMethod + { + return $this->test; + } + + /** + * @return class-string + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/6140 + */ + public function testClassName(): string + { + return $this->test->className(); + } + + public function calledMethod(): Code\ClassMethod + { + return $this->calledMethod; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Before Test Method Called (%s::%s)', + $this->calledMethod->className(), + $this->calledMethod->methodName(), + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/BeforeTestMethodCalledSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/BeforeTestMethodCalledSubscriber.php new file mode 100644 index 000000000..5f4e180e6 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/BeforeTestMethodCalledSubscriber.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface BeforeTestMethodCalledSubscriber extends Subscriber +{ + public function notify(BeforeTestMethodCalled $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/BeforeTestMethodErrored.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/BeforeTestMethodErrored.php new file mode 100644 index 000000000..7e18a0c65 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/BeforeTestMethodErrored.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 PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Code\Throwable; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class BeforeTestMethodErrored implements Event +{ + private Telemetry\Info $telemetryInfo; + private Code\TestMethod $test; + private Code\ClassMethod $calledMethod; + private Throwable $throwable; + + public function __construct(Telemetry\Info $telemetryInfo, Code\TestMethod $test, Code\ClassMethod $calledMethod, Throwable $throwable) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->calledMethod = $calledMethod; + $this->throwable = $throwable; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Code\TestMethod + { + return $this->test; + } + + /** + * @return class-string + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/6140 + */ + public function testClassName(): string + { + return $this->test->className(); + } + + public function calledMethod(): Code\ClassMethod + { + return $this->calledMethod; + } + + public function throwable(): Throwable + { + return $this->throwable; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = $this->throwable->message(); + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + return sprintf( + 'Before Test Method Errored (%s::%s)%s', + $this->calledMethod->className(), + $this->calledMethod->methodName(), + $message, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/BeforeTestMethodErroredSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/BeforeTestMethodErroredSubscriber.php new file mode 100644 index 000000000..e53771c49 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/BeforeTestMethodErroredSubscriber.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface BeforeTestMethodErroredSubscriber extends Subscriber +{ + public function notify(BeforeTestMethodErrored $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/BeforeTestMethodFailed.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/BeforeTestMethodFailed.php new file mode 100644 index 000000000..95b46e410 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/BeforeTestMethodFailed.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 PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Code\Throwable; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class BeforeTestMethodFailed implements Event +{ + private Telemetry\Info $telemetryInfo; + private Code\TestMethod $test; + private Code\ClassMethod $calledMethod; + private Throwable $throwable; + + public function __construct(Telemetry\Info $telemetryInfo, Code\TestMethod $test, Code\ClassMethod $calledMethod, Throwable $throwable) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->calledMethod = $calledMethod; + $this->throwable = $throwable; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Code\TestMethod + { + return $this->test; + } + + public function calledMethod(): Code\ClassMethod + { + return $this->calledMethod; + } + + public function throwable(): Throwable + { + return $this->throwable; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = $this->throwable->message(); + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + return sprintf( + 'Before Test Method Failed (%s::%s)%s', + $this->calledMethod->className(), + $this->calledMethod->methodName(), + $message, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/BeforeTestMethodFailedSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/BeforeTestMethodFailedSubscriber.php new file mode 100644 index 000000000..0f9f071ce --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/BeforeTestMethodFailedSubscriber.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface BeforeTestMethodFailedSubscriber extends Subscriber +{ + public function notify(BeforeTestMethodFailed $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/BeforeTestMethodFinished.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/BeforeTestMethodFinished.php new file mode 100644 index 000000000..971ad06bb --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/BeforeTestMethodFinished.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 PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class BeforeTestMethodFinished implements Event +{ + private Telemetry\Info $telemetryInfo; + private Code\TestMethod $test; + + /** + * @var list + */ + private array $calledMethods; + + public function __construct(Telemetry\Info $telemetryInfo, Code\TestMethod $test, Code\ClassMethod ...$calledMethods) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->calledMethods = $calledMethods; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Code\TestMethod + { + return $this->test; + } + + /** + * @return class-string + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/6140 + */ + public function testClassName(): string + { + return $this->test->className(); + } + + /** + * @return list + */ + public function calledMethods(): array + { + return $this->calledMethods; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $buffer = 'Before Test Method Finished:'; + + foreach ($this->calledMethods as $calledMethod) { + $buffer .= sprintf( + PHP_EOL . '- %s::%s', + $calledMethod->className(), + $calledMethod->methodName(), + ); + } + + return $buffer; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/BeforeTestMethodFinishedSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/BeforeTestMethodFinishedSubscriber.php new file mode 100644 index 000000000..2a6c758ca --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/BeforeTestMethodFinishedSubscriber.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface BeforeTestMethodFinishedSubscriber extends Subscriber +{ + public function notify(BeforeTestMethodFinished $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/PostConditionCalled.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/PostConditionCalled.php new file mode 100644 index 000000000..2035f6432 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/PostConditionCalled.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 PHPUnit\Event\Test; + +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PostConditionCalled implements Event +{ + private Telemetry\Info $telemetryInfo; + private Code\TestMethod $test; + private Code\ClassMethod $calledMethod; + + public function __construct(Telemetry\Info $telemetryInfo, Code\TestMethod $test, Code\ClassMethod $calledMethod) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->calledMethod = $calledMethod; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Code\TestMethod + { + return $this->test; + } + + /** + * @return class-string + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/6140 + */ + public function testClassName(): string + { + return $this->test->className(); + } + + public function calledMethod(): Code\ClassMethod + { + return $this->calledMethod; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Post Condition Method Called (%s::%s)', + $this->calledMethod->className(), + $this->calledMethod->methodName(), + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/PostConditionCalledSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/PostConditionCalledSubscriber.php new file mode 100644 index 000000000..2c135f504 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/PostConditionCalledSubscriber.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface PostConditionCalledSubscriber extends Subscriber +{ + public function notify(PostConditionCalled $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/PostConditionErrored.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/PostConditionErrored.php new file mode 100644 index 000000000..29cfe0d64 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/PostConditionErrored.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 PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Code\Throwable; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PostConditionErrored implements Event +{ + private Telemetry\Info $telemetryInfo; + private Code\TestMethod $test; + private Code\ClassMethod $calledMethod; + private Throwable $throwable; + + public function __construct(Telemetry\Info $telemetryInfo, Code\TestMethod $test, Code\ClassMethod $calledMethod, Throwable $throwable) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->calledMethod = $calledMethod; + $this->throwable = $throwable; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Code\TestMethod + { + return $this->test; + } + + /** + * @return class-string + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/6140 + */ + public function testClassName(): string + { + return $this->test->className(); + } + + public function calledMethod(): Code\ClassMethod + { + return $this->calledMethod; + } + + public function throwable(): Throwable + { + return $this->throwable; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = $this->throwable->message(); + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + return sprintf( + 'Post Condition Method Errored (%s::%s)%s', + $this->calledMethod->className(), + $this->calledMethod->methodName(), + $message, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/PostConditionErroredSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/PostConditionErroredSubscriber.php new file mode 100644 index 000000000..7bd2c54ce --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/PostConditionErroredSubscriber.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface PostConditionErroredSubscriber extends Subscriber +{ + public function notify(PostConditionErrored $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/PostConditionFailed.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/PostConditionFailed.php new file mode 100644 index 000000000..cb4868973 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/PostConditionFailed.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 PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Code\Throwable; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PostConditionFailed implements Event +{ + private Telemetry\Info $telemetryInfo; + private Code\TestMethod $test; + private Code\ClassMethod $calledMethod; + private Throwable $throwable; + + public function __construct(Telemetry\Info $telemetryInfo, Code\TestMethod $test, Code\ClassMethod $calledMethod, Throwable $throwable) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->calledMethod = $calledMethod; + $this->throwable = $throwable; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Code\TestMethod + { + return $this->test; + } + + public function calledMethod(): Code\ClassMethod + { + return $this->calledMethod; + } + + public function throwable(): Throwable + { + return $this->throwable; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = $this->throwable->message(); + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + return sprintf( + 'Post Condition Method Failed (%s::%s)%s', + $this->calledMethod->className(), + $this->calledMethod->methodName(), + $message, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/PostConditionFailedSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/PostConditionFailedSubscriber.php new file mode 100644 index 000000000..e6ff7557a --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/PostConditionFailedSubscriber.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface PostConditionFailedSubscriber extends Subscriber +{ + public function notify(PostConditionFailed $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/PostConditionFinished.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/PostConditionFinished.php new file mode 100644 index 000000000..0910a2a8b --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/PostConditionFinished.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 PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PostConditionFinished implements Event +{ + private Telemetry\Info $telemetryInfo; + private Code\TestMethod $test; + + /** + * @var list + */ + private array $calledMethods; + + public function __construct(Telemetry\Info $telemetryInfo, Code\TestMethod $test, Code\ClassMethod ...$calledMethods) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->calledMethods = $calledMethods; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Code\TestMethod + { + return $this->test; + } + + /** + * @return class-string + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/6140 + */ + public function testClassName(): string + { + return $this->test->className(); + } + + /** + * @return list + */ + public function calledMethods(): array + { + return $this->calledMethods; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $buffer = 'Post Condition Method Finished:'; + + foreach ($this->calledMethods as $calledMethod) { + $buffer .= sprintf( + PHP_EOL . '- %s::%s', + $calledMethod->className(), + $calledMethod->methodName(), + ); + } + + return $buffer; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/PostConditionFinishedSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/PostConditionFinishedSubscriber.php new file mode 100644 index 000000000..f24d94800 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/PostConditionFinishedSubscriber.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface PostConditionFinishedSubscriber extends Subscriber +{ + public function notify(PostConditionFinished $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/PreConditionCalled.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/PreConditionCalled.php new file mode 100644 index 000000000..80a10ee15 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/PreConditionCalled.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 PHPUnit\Event\Test; + +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PreConditionCalled implements Event +{ + private Telemetry\Info$telemetryInfo; + private Code\TestMethod $test; + private Code\ClassMethod $calledMethod; + + public function __construct(Telemetry\Info $telemetryInfo, Code\TestMethod $test, Code\ClassMethod $calledMethod) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->calledMethod = $calledMethod; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Code\TestMethod + { + return $this->test; + } + + /** + * @return class-string + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/6140 + */ + public function testClassName(): string + { + return $this->test->className(); + } + + public function calledMethod(): Code\ClassMethod + { + return $this->calledMethod; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Pre Condition Method Called (%s::%s)', + $this->calledMethod->className(), + $this->calledMethod->methodName(), + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/PreConditionCalledSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/PreConditionCalledSubscriber.php new file mode 100644 index 000000000..431dfcc40 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/PreConditionCalledSubscriber.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface PreConditionCalledSubscriber extends Subscriber +{ + public function notify(PreConditionCalled $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/PreConditionErrored.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/PreConditionErrored.php new file mode 100644 index 000000000..f2773d981 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/PreConditionErrored.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 PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Code\Throwable; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PreConditionErrored implements Event +{ + private Telemetry\Info $telemetryInfo; + private Code\TestMethod $test; + private Code\ClassMethod $calledMethod; + private Throwable $throwable; + + public function __construct(Telemetry\Info $telemetryInfo, Code\TestMethod $test, Code\ClassMethod $calledMethod, Throwable $throwable) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->calledMethod = $calledMethod; + $this->throwable = $throwable; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Code\TestMethod + { + return $this->test; + } + + /** + * @return class-string + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/6140 + */ + public function testClassName(): string + { + return $this->test->className(); + } + + public function calledMethod(): Code\ClassMethod + { + return $this->calledMethod; + } + + public function throwable(): Throwable + { + return $this->throwable; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = $this->throwable->message(); + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + return sprintf( + 'Pre Condition Method Errored (%s::%s)%s', + $this->calledMethod->className(), + $this->calledMethod->methodName(), + $message, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/PreConditionErroredSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/PreConditionErroredSubscriber.php new file mode 100644 index 000000000..3465040bb --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/PreConditionErroredSubscriber.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface PreConditionErroredSubscriber extends Subscriber +{ + public function notify(PreConditionErrored $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/PreConditionFailed.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/PreConditionFailed.php new file mode 100644 index 000000000..bf94de211 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/PreConditionFailed.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 PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Code\Throwable; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PreConditionFailed implements Event +{ + private Telemetry\Info $telemetryInfo; + private Code\TestMethod $test; + private Code\ClassMethod $calledMethod; + private Throwable $throwable; + + public function __construct(Telemetry\Info $telemetryInfo, Code\TestMethod $test, Code\ClassMethod $calledMethod, Throwable $throwable) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->calledMethod = $calledMethod; + $this->throwable = $throwable; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Code\TestMethod + { + return $this->test; + } + + public function calledMethod(): Code\ClassMethod + { + return $this->calledMethod; + } + + public function throwable(): Throwable + { + return $this->throwable; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = $this->throwable->message(); + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + return sprintf( + 'Pre Condition Method Failed (%s::%s)%s', + $this->calledMethod->className(), + $this->calledMethod->methodName(), + $message, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/PreConditionFailedSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/PreConditionFailedSubscriber.php new file mode 100644 index 000000000..26ce7cdce --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/PreConditionFailedSubscriber.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface PreConditionFailedSubscriber extends Subscriber +{ + public function notify(PreConditionFailed $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/PreConditionFinished.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/PreConditionFinished.php new file mode 100644 index 000000000..8a708ee05 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/PreConditionFinished.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 PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PreConditionFinished implements Event +{ + private Telemetry\Info $telemetryInfo; + private Code\TestMethod $test; + + /** + * @var list + */ + private array $calledMethods; + + public function __construct(Telemetry\Info $telemetryInfo, Code\TestMethod $test, Code\ClassMethod ...$calledMethods) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->calledMethods = $calledMethods; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Code\TestMethod + { + return $this->test; + } + + /** + * @return class-string + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/6140 + */ + public function testClassName(): string + { + return $this->test->className(); + } + + /** + * @return list + */ + public function calledMethods(): array + { + return $this->calledMethods; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $buffer = 'Pre Condition Method Finished:'; + + foreach ($this->calledMethods as $calledMethod) { + $buffer .= sprintf( + PHP_EOL . '- %s::%s', + $calledMethod->className(), + $calledMethod->methodName(), + ); + } + + return $buffer; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/PreConditionFinishedSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/PreConditionFinishedSubscriber.php new file mode 100644 index 000000000..9c499407e --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/HookMethod/PreConditionFinishedSubscriber.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface PreConditionFinishedSubscriber extends Subscriber +{ + public function notify(PreConditionFinished $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/ConsideredRisky.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/ConsideredRisky.php new file mode 100644 index 000000000..306c04e7d --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/ConsideredRisky.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ConsideredRisky implements Event +{ + private Telemetry\Info $telemetryInfo; + private Code\Test $test; + + /** + * @var non-empty-string + */ + private string $message; + + /** + * @param non-empty-string $message + */ + public function __construct(Telemetry\Info $telemetryInfo, Code\Test $test, string $message) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->message = $message; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Code\Test + { + return $this->test; + } + + /** + * @return non-empty-string + */ + public function message(): string + { + return $this->message; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Test Considered Risky (%s)%s%s', + $this->test->id(), + PHP_EOL, + $this->message, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/ConsideredRiskySubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/ConsideredRiskySubscriber.php new file mode 100644 index 000000000..a0c714a91 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/ConsideredRiskySubscriber.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface ConsideredRiskySubscriber extends Subscriber +{ + public function notify(ConsideredRisky $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/DeprecationTriggered.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/DeprecationTriggered.php new file mode 100644 index 000000000..09ec56fc8 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/DeprecationTriggered.php @@ -0,0 +1,169 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function implode; +use function sprintf; +use PHPUnit\Event\Code\IssueTrigger\IssueTrigger; +use PHPUnit\Event\Code\Test; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class DeprecationTriggered implements Event +{ + private Telemetry\Info $telemetryInfo; + private Test $test; + + /** + * @var non-empty-string + */ + private string $message; + + /** + * @var non-empty-string + */ + private string $file; + + /** + * @var positive-int + */ + private int $line; + private bool $suppressed; + private bool $ignoredByBaseline; + private bool $ignoredByTest; + private IssueTrigger $trigger; + + /** + * @var non-empty-string + */ + private string $stackTrace; + + /** + * @param non-empty-string $message + * @param non-empty-string $file + * @param positive-int $line + * @param non-empty-string $stackTrace + */ + public function __construct(Telemetry\Info $telemetryInfo, Test $test, string $message, string $file, int $line, bool $suppressed, bool $ignoredByBaseline, bool $ignoredByTest, IssueTrigger $trigger, string $stackTrace) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->message = $message; + $this->file = $file; + $this->line = $line; + $this->suppressed = $suppressed; + $this->ignoredByBaseline = $ignoredByBaseline; + $this->ignoredByTest = $ignoredByTest; + $this->trigger = $trigger; + $this->stackTrace = $stackTrace; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Test + { + return $this->test; + } + + /** + * @return non-empty-string + */ + public function message(): string + { + return $this->message; + } + + /** + * @return non-empty-string + */ + public function file(): string + { + return $this->file; + } + + /** + * @return positive-int + */ + public function line(): int + { + return $this->line; + } + + public function wasSuppressed(): bool + { + return $this->suppressed; + } + + public function ignoredByBaseline(): bool + { + return $this->ignoredByBaseline; + } + + public function ignoredByTest(): bool + { + return $this->ignoredByTest; + } + + public function trigger(): IssueTrigger + { + return $this->trigger; + } + + /** + * @return non-empty-string + */ + public function stackTrace(): string + { + return $this->stackTrace; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = $this->message; + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + $details = [$this->test->id(), $this->trigger->asString()]; + + if ($this->suppressed) { + $details[] = 'suppressed using operator'; + } + + if ($this->ignoredByTest) { + $details[] = 'ignored by test'; + } + + if ($this->ignoredByBaseline) { + $details[] = 'ignored by baseline'; + } + + return sprintf( + 'Test Triggered Deprecation (%s) in %s:%d%s', + implode(', ', $details), + $this->file, + $this->line, + $message, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/DeprecationTriggeredSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/DeprecationTriggeredSubscriber.php new file mode 100644 index 000000000..e166dbed9 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/DeprecationTriggeredSubscriber.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface DeprecationTriggeredSubscriber extends Subscriber +{ + public function notify(DeprecationTriggered $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/ErrorTriggered.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/ErrorTriggered.php new file mode 100644 index 000000000..7faefc634 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/ErrorTriggered.php @@ -0,0 +1,124 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function implode; +use function sprintf; +use PHPUnit\Event\Code\Test; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ErrorTriggered implements Event +{ + private Telemetry\Info $telemetryInfo; + private Test $test; + + /** + * @var non-empty-string + */ + private string $message; + + /** + * @var non-empty-string + */ + private string $file; + + /** + * @var positive-int + */ + private int $line; + private bool $suppressed; + + /** + * @param non-empty-string $message + * @param non-empty-string $file + * @param positive-int $line + */ + public function __construct(Telemetry\Info $telemetryInfo, Test $test, string $message, string $file, int $line, bool $suppressed) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->message = $message; + $this->file = $file; + $this->line = $line; + $this->suppressed = $suppressed; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Test + { + return $this->test; + } + + /** + * @return non-empty-string + */ + public function message(): string + { + return $this->message; + } + + /** + * @return non-empty-string + */ + public function file(): string + { + return $this->file; + } + + /** + * @return positive-int + */ + public function line(): int + { + return $this->line; + } + + public function wasSuppressed(): bool + { + return $this->suppressed; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = $this->message; + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + $details = [$this->test->id()]; + + if ($this->suppressed) { + $details[] = 'suppressed using operator'; + } + + return sprintf( + 'Test Triggered Error (%s) in %s:%d%s', + implode(', ', $details), + $this->file, + $this->line, + $message, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/ErrorTriggeredSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/ErrorTriggeredSubscriber.php new file mode 100644 index 000000000..901d88556 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/ErrorTriggeredSubscriber.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface ErrorTriggeredSubscriber extends Subscriber +{ + public function notify(ErrorTriggered $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/NoticeTriggered.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/NoticeTriggered.php new file mode 100644 index 000000000..237e1f18a --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/NoticeTriggered.php @@ -0,0 +1,135 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function implode; +use function sprintf; +use PHPUnit\Event\Code\Test; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class NoticeTriggered implements Event +{ + private Telemetry\Info $telemetryInfo; + private Test $test; + + /** + * @var non-empty-string + */ + private string $message; + + /** + * @var non-empty-string + */ + private string $file; + + /** + * @var positive-int + */ + private int $line; + private bool $suppressed; + private bool $ignoredByBaseline; + + /** + * @param non-empty-string $message + * @param non-empty-string $file + * @param positive-int $line + */ + public function __construct(Telemetry\Info $telemetryInfo, Test $test, string $message, string $file, int $line, bool $suppressed, bool $ignoredByBaseline) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->message = $message; + $this->file = $file; + $this->line = $line; + $this->suppressed = $suppressed; + $this->ignoredByBaseline = $ignoredByBaseline; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Test + { + return $this->test; + } + + /** + * @return non-empty-string + */ + public function message(): string + { + return $this->message; + } + + /** + * @return non-empty-string + */ + public function file(): string + { + return $this->file; + } + + /** + * @return positive-int + */ + public function line(): int + { + return $this->line; + } + + public function wasSuppressed(): bool + { + return $this->suppressed; + } + + public function ignoredByBaseline(): bool + { + return $this->ignoredByBaseline; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = $this->message; + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + $details = [$this->test->id()]; + + if ($this->suppressed) { + $details[] = 'suppressed using operator'; + } + + if ($this->ignoredByBaseline) { + $details[] = 'ignored by baseline'; + } + + return sprintf( + 'Test Triggered Notice (%s) in %s:%d%s', + implode(', ', $details), + $this->file, + $this->line, + $message, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/NoticeTriggeredSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/NoticeTriggeredSubscriber.php new file mode 100644 index 000000000..95230d0ff --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/NoticeTriggeredSubscriber.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface NoticeTriggeredSubscriber extends Subscriber +{ + public function notify(NoticeTriggered $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/PhpDeprecationTriggered.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/PhpDeprecationTriggered.php new file mode 100644 index 000000000..5b9878ad4 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/PhpDeprecationTriggered.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 PHPUnit\Event\Test; + +use const PHP_EOL; +use function implode; +use function sprintf; +use PHPUnit\Event\Code\IssueTrigger\IssueTrigger; +use PHPUnit\Event\Code\Test; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PhpDeprecationTriggered implements Event +{ + private Telemetry\Info $telemetryInfo; + private Test $test; + + /** + * @var non-empty-string + */ + private string $message; + + /** + * @var non-empty-string + */ + private string $file; + + /** + * @var positive-int + */ + private int $line; + private bool $suppressed; + private bool $ignoredByBaseline; + private bool $ignoredByTest; + private IssueTrigger $trigger; + + /** + * @param non-empty-string $message + * @param non-empty-string $file + * @param positive-int $line + */ + public function __construct(Telemetry\Info $telemetryInfo, Test $test, string $message, string $file, int $line, bool $suppressed, bool $ignoredByBaseline, bool $ignoredByTest, IssueTrigger $trigger) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->message = $message; + $this->file = $file; + $this->line = $line; + $this->suppressed = $suppressed; + $this->ignoredByBaseline = $ignoredByBaseline; + $this->ignoredByTest = $ignoredByTest; + $this->trigger = $trigger; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Test + { + return $this->test; + } + + /** + * @return non-empty-string + */ + public function message(): string + { + return $this->message; + } + + /** + * @return non-empty-string + */ + public function file(): string + { + return $this->file; + } + + /** + * @return positive-int + */ + public function line(): int + { + return $this->line; + } + + public function wasSuppressed(): bool + { + return $this->suppressed; + } + + public function ignoredByBaseline(): bool + { + return $this->ignoredByBaseline; + } + + public function ignoredByTest(): bool + { + return $this->ignoredByTest; + } + + public function trigger(): IssueTrigger + { + return $this->trigger; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = $this->message; + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + $details = [$this->test->id(), $this->trigger->asString()]; + + if ($this->suppressed) { + $details[] = 'suppressed using operator'; + } + + if ($this->ignoredByTest) { + $details[] = 'ignored by test'; + } + + if ($this->ignoredByBaseline) { + $details[] = 'ignored by baseline'; + } + + return sprintf( + 'Test Triggered PHP Deprecation (%s) in %s:%d%s', + implode(', ', $details), + $this->file, + $this->line, + $message, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/PhpDeprecationTriggeredSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/PhpDeprecationTriggeredSubscriber.php new file mode 100644 index 000000000..06159a7ce --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/PhpDeprecationTriggeredSubscriber.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface PhpDeprecationTriggeredSubscriber extends Subscriber +{ + public function notify(PhpDeprecationTriggered $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/PhpNoticeTriggered.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/PhpNoticeTriggered.php new file mode 100644 index 000000000..ad976cfcf --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/PhpNoticeTriggered.php @@ -0,0 +1,135 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function implode; +use function sprintf; +use PHPUnit\Event\Code\Test; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PhpNoticeTriggered implements Event +{ + private Telemetry\Info $telemetryInfo; + private Test $test; + + /** + * @var non-empty-string + */ + private string $message; + + /** + * @var non-empty-string + */ + private string $file; + + /** + * @var positive-int + */ + private int $line; + private bool $suppressed; + private bool $ignoredByBaseline; + + /** + * @param non-empty-string $message + * @param non-empty-string $file + * @param positive-int $line + */ + public function __construct(Telemetry\Info $telemetryInfo, Test $test, string $message, string $file, int $line, bool $suppressed, bool $ignoredByBaseline) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->message = $message; + $this->file = $file; + $this->line = $line; + $this->suppressed = $suppressed; + $this->ignoredByBaseline = $ignoredByBaseline; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Test + { + return $this->test; + } + + /** + * @return non-empty-string + */ + public function message(): string + { + return $this->message; + } + + /** + * @return non-empty-string + */ + public function file(): string + { + return $this->file; + } + + /** + * @return positive-int + */ + public function line(): int + { + return $this->line; + } + + public function wasSuppressed(): bool + { + return $this->suppressed; + } + + public function ignoredByBaseline(): bool + { + return $this->ignoredByBaseline; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = $this->message; + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + $details = [$this->test->id()]; + + if ($this->suppressed) { + $details[] = 'suppressed using operator'; + } + + if ($this->ignoredByBaseline) { + $details[] = 'ignored by baseline'; + } + + return sprintf( + 'Test Triggered PHP Notice (%s) in %s:%d%s', + implode(', ', $details), + $this->file, + $this->line, + $message, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/PhpNoticeTriggeredSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/PhpNoticeTriggeredSubscriber.php new file mode 100644 index 000000000..98649bda3 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/PhpNoticeTriggeredSubscriber.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface PhpNoticeTriggeredSubscriber extends Subscriber +{ + public function notify(PhpNoticeTriggered $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/PhpWarningTriggered.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/PhpWarningTriggered.php new file mode 100644 index 000000000..3d65125f7 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/PhpWarningTriggered.php @@ -0,0 +1,135 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function implode; +use function sprintf; +use PHPUnit\Event\Code\Test; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PhpWarningTriggered implements Event +{ + private Telemetry\Info $telemetryInfo; + private Test $test; + + /** + * @var non-empty-string + */ + private string $message; + + /** + * @var non-empty-string + */ + private string $file; + + /** + * @var positive-int + */ + private int $line; + private bool $suppressed; + private bool $ignoredByBaseline; + + /** + * @param non-empty-string $message + * @param non-empty-string $file + * @param positive-int $line + */ + public function __construct(Telemetry\Info $telemetryInfo, Test $test, string $message, string $file, int $line, bool $suppressed, bool $ignoredByBaseline) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->message = $message; + $this->file = $file; + $this->line = $line; + $this->suppressed = $suppressed; + $this->ignoredByBaseline = $ignoredByBaseline; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Test + { + return $this->test; + } + + /** + * @return non-empty-string + */ + public function message(): string + { + return $this->message; + } + + /** + * @return non-empty-string + */ + public function file(): string + { + return $this->file; + } + + /** + * @return positive-int + */ + public function line(): int + { + return $this->line; + } + + public function wasSuppressed(): bool + { + return $this->suppressed; + } + + public function ignoredByBaseline(): bool + { + return $this->ignoredByBaseline; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = $this->message; + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + $details = [$this->test->id()]; + + if ($this->suppressed) { + $details[] = 'suppressed using operator'; + } + + if ($this->ignoredByBaseline) { + $details[] = 'ignored by baseline'; + } + + return sprintf( + 'Test Triggered PHP Warning (%s) in %s:%d%s', + implode(', ', $details), + $this->file, + $this->line, + $message, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/PhpWarningTriggeredSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/PhpWarningTriggeredSubscriber.php new file mode 100644 index 000000000..3638ba1aa --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/PhpWarningTriggeredSubscriber.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface PhpWarningTriggeredSubscriber extends Subscriber +{ + public function notify(PhpWarningTriggered $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/PhpunitDeprecationTriggered.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/PhpunitDeprecationTriggered.php new file mode 100644 index 000000000..4e1603f35 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/PhpunitDeprecationTriggered.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use PHPUnit\Event\Code\Test; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PhpunitDeprecationTriggered implements Event +{ + private Telemetry\Info $telemetryInfo; + private Test $test; + + /** + * @var non-empty-string + */ + private string $message; + + /** + * @param non-empty-string $message + */ + public function __construct(Telemetry\Info $telemetryInfo, Test $test, string $message) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->message = $message; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Test + { + return $this->test; + } + + /** + * @return non-empty-string + */ + public function message(): string + { + return $this->message; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = $this->message; + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + return sprintf( + 'Test Triggered PHPUnit Deprecation (%s)%s', + $this->test->id(), + $message, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/PhpunitDeprecationTriggeredSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/PhpunitDeprecationTriggeredSubscriber.php new file mode 100644 index 000000000..f6b3a239a --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/PhpunitDeprecationTriggeredSubscriber.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface PhpunitDeprecationTriggeredSubscriber extends Subscriber +{ + public function notify(PhpunitDeprecationTriggered $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/PhpunitErrorTriggered.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/PhpunitErrorTriggered.php new file mode 100644 index 000000000..abd5e8a1f --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/PhpunitErrorTriggered.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use function trim; +use PHPUnit\Event\Code\Test; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PhpunitErrorTriggered implements Event +{ + private Telemetry\Info $telemetryInfo; + private Test $test; + + /** + * @var non-empty-string + */ + private string $message; + + /** + * @param non-empty-string $message + */ + public function __construct(Telemetry\Info $telemetryInfo, Test $test, string $message) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->message = $message; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Test + { + return $this->test; + } + + /** + * @return non-empty-string + */ + public function message(): string + { + return $this->message; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = trim($this->message); + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + return sprintf( + 'Test Triggered PHPUnit Error (%s)%s', + $this->test->id(), + $message, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/PhpunitErrorTriggeredSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/PhpunitErrorTriggeredSubscriber.php new file mode 100644 index 000000000..e94d1dde2 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/PhpunitErrorTriggeredSubscriber.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface PhpunitErrorTriggeredSubscriber extends Subscriber +{ + public function notify(PhpunitErrorTriggered $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/PhpunitNoticeTriggered.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/PhpunitNoticeTriggered.php new file mode 100644 index 000000000..33984ba42 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/PhpunitNoticeTriggered.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use function trim; +use PHPUnit\Event\Code\Test; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PhpunitNoticeTriggered implements Event +{ + private Telemetry\Info $telemetryInfo; + private Test $test; + + /** + * @var non-empty-string + */ + private string $message; + + /** + * @param non-empty-string $message + */ + public function __construct(Telemetry\Info $telemetryInfo, Test $test, string $message) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->message = $message; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Test + { + return $this->test; + } + + /** + * @return non-empty-string + */ + public function message(): string + { + return $this->message; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = trim($this->message); + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + return sprintf( + 'Test Triggered PHPUnit Notice (%s)%s', + $this->test->id(), + $message, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/PhpunitNoticeTriggeredSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/PhpunitNoticeTriggeredSubscriber.php new file mode 100644 index 000000000..0935c6dd3 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/PhpunitNoticeTriggeredSubscriber.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface PhpunitNoticeTriggeredSubscriber extends Subscriber +{ + public function notify(PhpunitNoticeTriggered $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/PhpunitWarningTriggered.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/PhpunitWarningTriggered.php new file mode 100644 index 000000000..75ef4894d --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/PhpunitWarningTriggered.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 PHPUnit\Event\Test; + +use const PHP_EOL; +use function implode; +use function sprintf; +use PHPUnit\Event\Code\Test; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PhpunitWarningTriggered implements Event +{ + private Telemetry\Info $telemetryInfo; + private Test $test; + + /** + * @var non-empty-string + */ + private string $message; + private bool $ignoredByTest; + + /** + * @param non-empty-string $message + */ + public function __construct(Telemetry\Info $telemetryInfo, Test $test, string $message, bool $ignoredByTest) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->message = $message; + $this->ignoredByTest = $ignoredByTest; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Test + { + return $this->test; + } + + /** + * @return non-empty-string + */ + public function message(): string + { + return $this->message; + } + + public function ignoredByTest(): bool + { + return $this->ignoredByTest; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = $this->message; + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + $details = [$this->test->id()]; + + if ($this->ignoredByTest) { + $details[] = 'ignored by test'; + } + + return sprintf( + 'Test Triggered PHPUnit Warning (%s)%s', + implode(', ', $details), + $message, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/PhpunitWarningTriggeredSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/PhpunitWarningTriggeredSubscriber.php new file mode 100644 index 000000000..72149b2c8 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/PhpunitWarningTriggeredSubscriber.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface PhpunitWarningTriggeredSubscriber extends Subscriber +{ + public function notify(PhpunitWarningTriggered $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/WarningTriggered.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/WarningTriggered.php new file mode 100644 index 000000000..7b3e313bf --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/WarningTriggered.php @@ -0,0 +1,135 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function implode; +use function sprintf; +use PHPUnit\Event\Code\Test; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class WarningTriggered implements Event +{ + private Telemetry\Info $telemetryInfo; + private Test $test; + + /** + * @var non-empty-string + */ + private string $message; + + /** + * @var non-empty-string + */ + private string $file; + + /** + * @var positive-int + */ + private int $line; + private bool $suppressed; + private bool $ignoredByBaseline; + + /** + * @param non-empty-string $message + * @param non-empty-string $file + * @param positive-int $line + */ + public function __construct(Telemetry\Info $telemetryInfo, Test $test, string $message, string $file, int $line, bool $suppressed, bool $ignoredByBaseline) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->message = $message; + $this->file = $file; + $this->line = $line; + $this->suppressed = $suppressed; + $this->ignoredByBaseline = $ignoredByBaseline; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Test + { + return $this->test; + } + + /** + * @return non-empty-string + */ + public function message(): string + { + return $this->message; + } + + /** + * @return non-empty-string + */ + public function file(): string + { + return $this->file; + } + + /** + * @return positive-int + */ + public function line(): int + { + return $this->line; + } + + public function wasSuppressed(): bool + { + return $this->suppressed; + } + + public function ignoredByBaseline(): bool + { + return $this->ignoredByBaseline; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = $this->message; + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + $details = [$this->test->id()]; + + if ($this->suppressed) { + $details[] = 'suppressed using operator'; + } + + if ($this->ignoredByBaseline) { + $details[] = 'ignored by baseline'; + } + + return sprintf( + 'Test Triggered Warning (%s) in %s:%d%s', + implode(', ', $details), + $this->file, + $this->line, + $message, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/WarningTriggeredSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/WarningTriggeredSubscriber.php new file mode 100644 index 000000000..8eb66648e --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Issue/WarningTriggeredSubscriber.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface WarningTriggeredSubscriber extends Subscriber +{ + public function notify(WarningTriggered $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/Lifecycle/DataProviderMethodCalled.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Lifecycle/DataProviderMethodCalled.php new file mode 100644 index 000000000..5631e1cf2 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Lifecycle/DataProviderMethodCalled.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 PHPUnit\Event\Test; + +use function sprintf; +use PHPUnit\Event\Code\ClassMethod; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry\Info; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class DataProviderMethodCalled implements Event +{ + private Info $telemetryInfo; + private ClassMethod $testMethod; + private ClassMethod $dataProviderMethod; + + public function __construct(Info $telemetryInfo, ClassMethod $testMethod, ClassMethod $dataProviderMethod) + { + $this->telemetryInfo = $telemetryInfo; + $this->testMethod = $testMethod; + $this->dataProviderMethod = $dataProviderMethod; + } + + public function telemetryInfo(): Info + { + return $this->telemetryInfo; + } + + public function testMethod(): ClassMethod + { + return $this->testMethod; + } + + public function dataProviderMethod(): ClassMethod + { + return $this->dataProviderMethod; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Data Provider Method Called (%s::%s for test method %s::%s)', + $this->dataProviderMethod->className(), + $this->dataProviderMethod->methodName(), + $this->testMethod->className(), + $this->testMethod->methodName(), + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/Lifecycle/DataProviderMethodCalledSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Lifecycle/DataProviderMethodCalledSubscriber.php new file mode 100644 index 000000000..5f7d4013b --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Lifecycle/DataProviderMethodCalledSubscriber.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface DataProviderMethodCalledSubscriber extends Subscriber +{ + public function notify(DataProviderMethodCalled $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/Lifecycle/DataProviderMethodFinished.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Lifecycle/DataProviderMethodFinished.php new file mode 100644 index 000000000..ec2677994 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Lifecycle/DataProviderMethodFinished.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use PHPUnit\Event\Code\ClassMethod; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class DataProviderMethodFinished implements Event +{ + private Telemetry\Info $telemetryInfo; + private ClassMethod $testMethod; + + /** + * @var list + */ + private array $calledMethods; + + public function __construct(Telemetry\Info $telemetryInfo, ClassMethod $testMethod, ClassMethod ...$calledMethods) + { + $this->telemetryInfo = $telemetryInfo; + $this->testMethod = $testMethod; + $this->calledMethods = $calledMethods; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function testMethod(): ClassMethod + { + return $this->testMethod; + } + + /** + * @return list + */ + public function calledMethods(): array + { + return $this->calledMethods; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $buffer = sprintf( + 'Data Provider Method Finished for %s::%s:', + $this->testMethod->className(), + $this->testMethod->methodName(), + ); + + foreach ($this->calledMethods as $calledMethod) { + $buffer .= sprintf( + PHP_EOL . '- %s::%s', + $calledMethod->className(), + $calledMethod->methodName(), + ); + } + + return $buffer; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/Lifecycle/DataProviderMethodFinishedSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Lifecycle/DataProviderMethodFinishedSubscriber.php new file mode 100644 index 000000000..624f8921d --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Lifecycle/DataProviderMethodFinishedSubscriber.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface DataProviderMethodFinishedSubscriber extends Subscriber +{ + public function notify(DataProviderMethodFinished $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/Lifecycle/Finished.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Lifecycle/Finished.php new file mode 100644 index 000000000..3cc9a52ff --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Lifecycle/Finished.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 PHPUnit\Event\Test; + +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Finished implements Event +{ + private Telemetry\Info $telemetryInfo; + private Code\Test $test; + + /** + * @var non-negative-int + */ + private int $numberOfAssertionsPerformed; + + /** + * @param non-negative-int $numberOfAssertionsPerformed + */ + public function __construct(Telemetry\Info $telemetryInfo, Code\Test $test, int $numberOfAssertionsPerformed) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->numberOfAssertionsPerformed = $numberOfAssertionsPerformed; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Code\Test + { + return $this->test; + } + + /** + * @return non-negative-int + */ + public function numberOfAssertionsPerformed(): int + { + return $this->numberOfAssertionsPerformed; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Test Finished (%s)', + $this->test->id(), + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/Lifecycle/FinishedSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Lifecycle/FinishedSubscriber.php new file mode 100644 index 000000000..5751e3df7 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Lifecycle/FinishedSubscriber.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface FinishedSubscriber extends Subscriber +{ + public function notify(Finished $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/Lifecycle/PreparationErrored.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Lifecycle/PreparationErrored.php new file mode 100644 index 000000000..866bf5f1c --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Lifecycle/PreparationErrored.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 PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Code\Throwable; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PreparationErrored implements Event +{ + private Telemetry\Info $telemetryInfo; + private Code\Test $test; + private Throwable $throwable; + + public function __construct(Telemetry\Info $telemetryInfo, Code\Test $test, Throwable $throwable) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->throwable = $throwable; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Code\Test + { + return $this->test; + } + + public function throwable(): Throwable + { + return $this->throwable; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = $this->throwable->message(); + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + return sprintf( + 'Test Preparation Errored (%s)%s', + $this->test->id(), + $message, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/Lifecycle/PreparationErroredSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Lifecycle/PreparationErroredSubscriber.php new file mode 100644 index 000000000..2cb43d2e8 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Lifecycle/PreparationErroredSubscriber.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface PreparationErroredSubscriber extends Subscriber +{ + public function notify(PreparationErrored $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/Lifecycle/PreparationFailed.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Lifecycle/PreparationFailed.php new file mode 100644 index 000000000..7a8b1d67f --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Lifecycle/PreparationFailed.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 PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Code\Throwable; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PreparationFailed implements Event +{ + private Telemetry\Info $telemetryInfo; + private Code\Test $test; + private Throwable $throwable; + + public function __construct(Telemetry\Info $telemetryInfo, Code\Test $test, Throwable $throwable) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->throwable = $throwable; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Code\Test + { + return $this->test; + } + + public function throwable(): Throwable + { + return $this->throwable; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = $this->throwable->message(); + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + return sprintf( + 'Test Preparation Failed (%s)%s', + $this->test->id(), + $message, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/Lifecycle/PreparationFailedSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Lifecycle/PreparationFailedSubscriber.php new file mode 100644 index 000000000..da20f11ef --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Lifecycle/PreparationFailedSubscriber.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface PreparationFailedSubscriber extends Subscriber +{ + public function notify(PreparationFailed $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/Lifecycle/PreparationStarted.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Lifecycle/PreparationStarted.php new file mode 100644 index 000000000..7c548b08b --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Lifecycle/PreparationStarted.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 PHPUnit\Event\Test; + +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PreparationStarted implements Event +{ + private Telemetry\Info $telemetryInfo; + private Code\Test $test; + + public function __construct(Telemetry\Info $telemetryInfo, Code\Test $test) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Code\Test + { + return $this->test; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Test Preparation Started (%s)', + $this->test->id(), + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/Lifecycle/PreparationStartedSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Lifecycle/PreparationStartedSubscriber.php new file mode 100644 index 000000000..f13296b4f --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Lifecycle/PreparationStartedSubscriber.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface PreparationStartedSubscriber extends Subscriber +{ + public function notify(PreparationStarted $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/Lifecycle/Prepared.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Lifecycle/Prepared.php new file mode 100644 index 000000000..d83f1d595 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Lifecycle/Prepared.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 PHPUnit\Event\Test; + +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Prepared implements Event +{ + private Telemetry\Info $telemetryInfo; + private Code\Test $test; + + public function __construct(Telemetry\Info $telemetryInfo, Code\Test $test) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Code\Test + { + return $this->test; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Test Prepared (%s)', + $this->test->id(), + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/Lifecycle/PreparedSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Lifecycle/PreparedSubscriber.php new file mode 100644 index 000000000..f53e227f4 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Lifecycle/PreparedSubscriber.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface PreparedSubscriber extends Subscriber +{ + public function notify(Prepared $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/Outcome/Errored.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Outcome/Errored.php new file mode 100644 index 000000000..ef0684989 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Outcome/Errored.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 PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use function trim; +use PHPUnit\Event\Code; +use PHPUnit\Event\Code\Throwable; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Errored implements Event +{ + private Telemetry\Info $telemetryInfo; + private Code\Test $test; + private Throwable $throwable; + + public function __construct(Telemetry\Info $telemetryInfo, Code\Test $test, Throwable $throwable) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->throwable = $throwable; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Code\Test + { + return $this->test; + } + + public function throwable(): Throwable + { + return $this->throwable; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = trim($this->throwable->message()); + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + return sprintf( + 'Test Errored (%s)%s', + $this->test->id(), + $message, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/Outcome/ErroredSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Outcome/ErroredSubscriber.php new file mode 100644 index 000000000..42dd5b24d --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Outcome/ErroredSubscriber.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface ErroredSubscriber extends Subscriber +{ + public function notify(Errored $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/Outcome/Failed.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Outcome/Failed.php new file mode 100644 index 000000000..bcc5867f0 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Outcome/Failed.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use function trim; +use PHPUnit\Event\Code; +use PHPUnit\Event\Code\ComparisonFailure; +use PHPUnit\Event\Code\Throwable; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Failed implements Event +{ + private Telemetry\Info $telemetryInfo; + private Code\Test $test; + private Throwable $throwable; + private ?ComparisonFailure $comparisonFailure; + + public function __construct(Telemetry\Info $telemetryInfo, Code\Test $test, Throwable $throwable, ?ComparisonFailure $comparisonFailure) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->throwable = $throwable; + $this->comparisonFailure = $comparisonFailure; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Code\Test + { + return $this->test; + } + + public function throwable(): Throwable + { + return $this->throwable; + } + + /** + * @phpstan-assert-if-true !null $this->comparisonFailure + */ + public function hasComparisonFailure(): bool + { + return $this->comparisonFailure !== null; + } + + /** + * @throws NoComparisonFailureException + */ + public function comparisonFailure(): ComparisonFailure + { + if ($this->comparisonFailure === null) { + throw new NoComparisonFailureException; + } + + return $this->comparisonFailure; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = trim($this->throwable->message()); + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + return sprintf( + 'Test Failed (%s)%s', + $this->test->id(), + $message, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/Outcome/FailedSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Outcome/FailedSubscriber.php new file mode 100644 index 000000000..8da6a85f1 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Outcome/FailedSubscriber.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface FailedSubscriber extends Subscriber +{ + public function notify(Failed $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/Outcome/MarkedIncomplete.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Outcome/MarkedIncomplete.php new file mode 100644 index 000000000..a69b48a42 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Outcome/MarkedIncomplete.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 PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use function trim; +use PHPUnit\Event\Code; +use PHPUnit\Event\Code\Throwable; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class MarkedIncomplete implements Event +{ + private Telemetry\Info $telemetryInfo; + private Code\Test $test; + private Throwable $throwable; + + public function __construct(Telemetry\Info $telemetryInfo, Code\Test $test, Throwable $throwable) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->throwable = $throwable; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Code\Test + { + return $this->test; + } + + public function throwable(): Throwable + { + return $this->throwable; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = trim($this->throwable->message()); + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + return sprintf( + 'Test Marked Incomplete (%s)%s', + $this->test->id(), + $message, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/Outcome/MarkedIncompleteSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Outcome/MarkedIncompleteSubscriber.php new file mode 100644 index 000000000..ff0acd863 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Outcome/MarkedIncompleteSubscriber.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface MarkedIncompleteSubscriber extends Subscriber +{ + public function notify(MarkedIncomplete $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/Outcome/Passed.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Outcome/Passed.php new file mode 100644 index 000000000..38f2d9816 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Outcome/Passed.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 PHPUnit\Event\Test; + +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Passed implements Event +{ + private Telemetry\Info $telemetryInfo; + private Code\Test $test; + + public function __construct(Telemetry\Info $telemetryInfo, Code\Test $test) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Code\Test + { + return $this->test; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Test Passed (%s)', + $this->test->id(), + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/Outcome/PassedSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Outcome/PassedSubscriber.php new file mode 100644 index 000000000..4a5673816 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Outcome/PassedSubscriber.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface PassedSubscriber extends Subscriber +{ + public function notify(Passed $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/Outcome/Skipped.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Outcome/Skipped.php new file mode 100644 index 000000000..fe605fff4 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Outcome/Skipped.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 PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use PHPUnit\Event\Code; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Skipped implements Event +{ + private Telemetry\Info $telemetryInfo; + private Code\Test $test; + private string $message; + + public function __construct(Telemetry\Info $telemetryInfo, Code\Test $test, string $message) + { + $this->telemetryInfo = $telemetryInfo; + $this->test = $test; + $this->message = $message; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function test(): Code\Test + { + return $this->test; + } + + public function message(): string + { + return $this->message; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + $message = $this->message; + + if ($message !== '') { + $message = PHP_EOL . $message; + } + + return sprintf( + 'Test Skipped (%s)%s', + $this->test->id(), + $message, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/Outcome/SkippedSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Outcome/SkippedSubscriber.php new file mode 100644 index 000000000..5fd48ac6a --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/Outcome/SkippedSubscriber.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface SkippedSubscriber extends Subscriber +{ + public function notify(Skipped $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/PrintedUnexpectedOutput.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/PrintedUnexpectedOutput.php new file mode 100644 index 000000000..4a0ceab39 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/PrintedUnexpectedOutput.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use const PHP_EOL; +use function sprintf; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PrintedUnexpectedOutput implements Event +{ + private Telemetry\Info $telemetryInfo; + + /** + * @var non-empty-string + */ + private string $output; + + /** + * @param non-empty-string $output + */ + public function __construct(Telemetry\Info $telemetryInfo, string $output) + { + $this->telemetryInfo = $telemetryInfo; + $this->output = $output; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return non-empty-string + */ + public function output(): string + { + return $this->output; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Test Printed Unexpected Output%s%s', + PHP_EOL, + $this->output, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/PrintedUnexpectedOutputSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/PrintedUnexpectedOutputSubscriber.php new file mode 100644 index 000000000..ee2015723 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/PrintedUnexpectedOutputSubscriber.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface PrintedUnexpectedOutputSubscriber extends Subscriber +{ + public function notify(PrintedUnexpectedOutput $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/TestDouble/MockObjectCreated.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/TestDouble/MockObjectCreated.php new file mode 100644 index 000000000..8e91237c4 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/TestDouble/MockObjectCreated.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use function sprintf; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class MockObjectCreated implements Event +{ + private Telemetry\Info $telemetryInfo; + + /** + * @var class-string + */ + private string $className; + + /** + * @param class-string $className + */ + public function __construct(Telemetry\Info $telemetryInfo, string $className) + { + $this->telemetryInfo = $telemetryInfo; + $this->className = $className; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Mock Object Created (%s)', + $this->className, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/TestDouble/MockObjectCreatedSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/TestDouble/MockObjectCreatedSubscriber.php new file mode 100644 index 000000000..8ad2f176b --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/TestDouble/MockObjectCreatedSubscriber.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface MockObjectCreatedSubscriber extends Subscriber +{ + public function notify(MockObjectCreated $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/TestDouble/MockObjectForIntersectionOfInterfacesCreated.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/TestDouble/MockObjectForIntersectionOfInterfacesCreated.php new file mode 100644 index 000000000..3548189e8 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/TestDouble/MockObjectForIntersectionOfInterfacesCreated.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 PHPUnit\Event\Test; + +use function implode; +use function sprintf; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class MockObjectForIntersectionOfInterfacesCreated implements Event +{ + private Telemetry\Info $telemetryInfo; + + /** + * @var list + */ + private array $interfaces; + + /** + * @param list $interfaces + */ + public function __construct(Telemetry\Info $telemetryInfo, array $interfaces) + { + $this->telemetryInfo = $telemetryInfo; + $this->interfaces = $interfaces; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return list + */ + public function interfaces(): array + { + return $this->interfaces; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Mock Object Created (%s)', + implode('&', $this->interfaces), + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/TestDouble/MockObjectForIntersectionOfInterfacesCreatedSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/TestDouble/MockObjectForIntersectionOfInterfacesCreatedSubscriber.php new file mode 100644 index 000000000..5b345b563 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/TestDouble/MockObjectForIntersectionOfInterfacesCreatedSubscriber.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface MockObjectForIntersectionOfInterfacesCreatedSubscriber extends Subscriber +{ + public function notify(MockObjectForIntersectionOfInterfacesCreated $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/TestDouble/PartialMockObjectCreated.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/TestDouble/PartialMockObjectCreated.php new file mode 100644 index 000000000..625747816 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/TestDouble/PartialMockObjectCreated.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 PHPUnit\Event\Test; + +use function sprintf; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PartialMockObjectCreated implements Event +{ + private Telemetry\Info $telemetryInfo; + + /** + * @var class-string + */ + private string $className; + + /** + * @var list + */ + private array $methodNames; + + /** + * @param class-string $className + */ + public function __construct(Telemetry\Info $telemetryInfo, string $className, string ...$methodNames) + { + $this->telemetryInfo = $telemetryInfo; + $this->className = $className; + $this->methodNames = $methodNames; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + /** + * @return list + */ + public function methodNames(): array + { + return $this->methodNames; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Partial Mock Object Created (%s)', + $this->className, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/TestDouble/PartialMockObjectCreatedSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/TestDouble/PartialMockObjectCreatedSubscriber.php new file mode 100644 index 000000000..e76407418 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/TestDouble/PartialMockObjectCreatedSubscriber.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface PartialMockObjectCreatedSubscriber extends Subscriber +{ + public function notify(PartialMockObjectCreated $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/TestDouble/TestStubCreated.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/TestDouble/TestStubCreated.php new file mode 100644 index 000000000..667fbad4b --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/TestDouble/TestStubCreated.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Test; + +use function sprintf; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestStubCreated implements Event +{ + private Telemetry\Info $telemetryInfo; + + /** + * @var class-string + */ + private string $className; + + /** + * @param class-string $className + */ + public function __construct(Telemetry\Info $telemetryInfo, string $className) + { + $this->telemetryInfo = $telemetryInfo; + $this->className = $className; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Test Stub Created (%s)', + $this->className, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/TestDouble/TestStubCreatedSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/TestDouble/TestStubCreatedSubscriber.php new file mode 100644 index 000000000..6b5deaf37 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/TestDouble/TestStubCreatedSubscriber.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface TestStubCreatedSubscriber extends Subscriber +{ + public function notify(TestStubCreated $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/TestDouble/TestStubForIntersectionOfInterfacesCreated.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/TestDouble/TestStubForIntersectionOfInterfacesCreated.php new file mode 100644 index 000000000..bba93d9e0 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/TestDouble/TestStubForIntersectionOfInterfacesCreated.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 PHPUnit\Event\Test; + +use function implode; +use function sprintf; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestStubForIntersectionOfInterfacesCreated implements Event +{ + private Telemetry\Info $telemetryInfo; + + /** + * @var list + */ + private array $interfaces; + + /** + * @param list $interfaces + */ + public function __construct(Telemetry\Info $telemetryInfo, array $interfaces) + { + $this->telemetryInfo = $telemetryInfo; + $this->interfaces = $interfaces; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return list + */ + public function interfaces(): array + { + return $this->interfaces; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Test Stub Created (%s)', + implode('&', $this->interfaces), + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/Test/TestDouble/TestStubForIntersectionOfInterfacesCreatedSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/Test/TestDouble/TestStubForIntersectionOfInterfacesCreatedSubscriber.php new file mode 100644 index 000000000..aec6f66ce --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/Test/TestDouble/TestStubForIntersectionOfInterfacesCreatedSubscriber.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface TestStubForIntersectionOfInterfacesCreatedSubscriber extends Subscriber +{ + public function notify(TestStubForIntersectionOfInterfacesCreated $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/BootstrapFinished.php b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/BootstrapFinished.php new file mode 100644 index 000000000..8e46a00a8 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/BootstrapFinished.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use function sprintf; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class BootstrapFinished implements Event +{ + private Telemetry\Info $telemetryInfo; + + /** + * @var non-empty-string + */ + private string $filename; + + /** + * @param non-empty-string $filename + */ + public function __construct(Telemetry\Info $telemetryInfo, string $filename) + { + $this->telemetryInfo = $telemetryInfo; + $this->filename = $filename; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return non-empty-string + */ + public function filename(): string + { + return $this->filename; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Bootstrap Finished (%s)', + $this->filename, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/BootstrapFinishedSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/BootstrapFinishedSubscriber.php new file mode 100644 index 000000000..749648ec4 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/BootstrapFinishedSubscriber.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 PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface BootstrapFinishedSubscriber extends Subscriber +{ + public function notify(BootstrapFinished $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/ChildProcessErrored.php b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/ChildProcessErrored.php new file mode 100644 index 000000000..2cb96422b --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/ChildProcessErrored.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 PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ChildProcessErrored implements Event +{ + private Telemetry\Info $telemetryInfo; + + public function __construct(Telemetry\Info $telemetryInfo) + { + $this->telemetryInfo = $telemetryInfo; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return 'Child Process Errored'; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/ChildProcessErroredSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/ChildProcessErroredSubscriber.php new file mode 100644 index 000000000..6ced57982 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/ChildProcessErroredSubscriber.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 PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface ChildProcessErroredSubscriber extends Subscriber +{ + public function notify(ChildProcessErrored $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/ChildProcessFinished.php b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/ChildProcessFinished.php new file mode 100644 index 000000000..705a0c634 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/ChildProcessFinished.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 PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ChildProcessFinished implements Event +{ + private Telemetry\Info $telemetryInfo; + private string $stdout; + private string $stderr; + + public function __construct(Telemetry\Info $telemetryInfo, string $stdout, string $stderr) + { + $this->telemetryInfo = $telemetryInfo; + $this->stdout = $stdout; + $this->stderr = $stderr; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function stdout(): string + { + return $this->stdout; + } + + public function stderr(): string + { + return $this->stderr; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return 'Child Process Finished'; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/ChildProcessFinishedSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/ChildProcessFinishedSubscriber.php new file mode 100644 index 000000000..45fefa182 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/ChildProcessFinishedSubscriber.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 PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface ChildProcessFinishedSubscriber extends Subscriber +{ + public function notify(ChildProcessFinished $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/ChildProcessStarted.php b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/ChildProcessStarted.php new file mode 100644 index 000000000..2c20471e2 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/ChildProcessStarted.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 PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ChildProcessStarted implements Event +{ + private Telemetry\Info $telemetryInfo; + + public function __construct(Telemetry\Info $telemetryInfo) + { + $this->telemetryInfo = $telemetryInfo; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return 'Child Process Started'; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/ChildProcessStartedSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/ChildProcessStartedSubscriber.php new file mode 100644 index 000000000..4ba549ce8 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/ChildProcessStartedSubscriber.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 PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface ChildProcessStartedSubscriber extends Subscriber +{ + public function notify(ChildProcessStarted $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/Configured.php b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/Configured.php new file mode 100644 index 000000000..e0d14360a --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/Configured.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 PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; +use PHPUnit\TextUI\Configuration\Configuration; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Configured implements Event +{ + private Telemetry\Info $telemetryInfo; + private Configuration $configuration; + + public function __construct(Telemetry\Info $telemetryInfo, Configuration $configuration) + { + $this->telemetryInfo = $telemetryInfo; + $this->configuration = $configuration; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function configuration(): Configuration + { + return $this->configuration; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return 'Test Runner Configured'; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/ConfiguredSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/ConfiguredSubscriber.php new file mode 100644 index 000000000..0b58f70bf --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/ConfiguredSubscriber.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 PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface ConfiguredSubscriber extends Subscriber +{ + public function notify(Configured $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/DeprecationTriggered.php b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/DeprecationTriggered.php new file mode 100644 index 000000000..5cfef8f78 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/DeprecationTriggered.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use function sprintf; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class DeprecationTriggered implements Event +{ + private Telemetry\Info $telemetryInfo; + + /** + * @var non-empty-string + */ + private string $message; + + /** + * @param non-empty-string $message + */ + public function __construct(Telemetry\Info $telemetryInfo, string $message) + { + $this->telemetryInfo = $telemetryInfo; + $this->message = $message; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return non-empty-string + */ + public function message(): string + { + return $this->message; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Test Runner Triggered Deprecation (%s)', + $this->message, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/DeprecationTriggeredSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/DeprecationTriggeredSubscriber.php new file mode 100644 index 000000000..627ffbd0f --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/DeprecationTriggeredSubscriber.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 PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface DeprecationTriggeredSubscriber extends Subscriber +{ + public function notify(DeprecationTriggered $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/EventFacadeSealed.php b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/EventFacadeSealed.php new file mode 100644 index 000000000..bd4f5f603 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/EventFacadeSealed.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 PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class EventFacadeSealed implements Event +{ + private Telemetry\Info $telemetryInfo; + + public function __construct(Telemetry\Info $telemetryInfo) + { + $this->telemetryInfo = $telemetryInfo; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return 'Event Facade Sealed'; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/EventFacadeSealedSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/EventFacadeSealedSubscriber.php new file mode 100644 index 000000000..4d0d3d010 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/EventFacadeSealedSubscriber.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 PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface EventFacadeSealedSubscriber extends Subscriber +{ + public function notify(EventFacadeSealed $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/ExecutionAborted.php b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/ExecutionAborted.php new file mode 100644 index 000000000..6107e099b --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/ExecutionAborted.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 PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ExecutionAborted implements Event +{ + private Telemetry\Info $telemetryInfo; + + public function __construct(Telemetry\Info $telemetryInfo) + { + $this->telemetryInfo = $telemetryInfo; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return 'Test Runner Execution Aborted'; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/ExecutionAbortedSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/ExecutionAbortedSubscriber.php new file mode 100644 index 000000000..00397cca2 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/ExecutionAbortedSubscriber.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 PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface ExecutionAbortedSubscriber extends Subscriber +{ + public function notify(ExecutionAborted $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/ExecutionFinished.php b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/ExecutionFinished.php new file mode 100644 index 000000000..25789fe7c --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/ExecutionFinished.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 PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ExecutionFinished implements Event +{ + private Telemetry\Info $telemetryInfo; + + public function __construct(Telemetry\Info $telemetryInfo) + { + $this->telemetryInfo = $telemetryInfo; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return 'Test Runner Execution Finished'; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/ExecutionFinishedSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/ExecutionFinishedSubscriber.php new file mode 100644 index 000000000..9945fc77e --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/ExecutionFinishedSubscriber.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 PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface ExecutionFinishedSubscriber extends Subscriber +{ + public function notify(ExecutionFinished $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/ExecutionStarted.php b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/ExecutionStarted.php new file mode 100644 index 000000000..e38a2a4d9 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/ExecutionStarted.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use function sprintf; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; +use PHPUnit\Event\TestSuite\TestSuite; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ExecutionStarted implements Event +{ + private Telemetry\Info $telemetryInfo; + private TestSuite $testSuite; + + public function __construct(Telemetry\Info $telemetryInfo, TestSuite $testSuite) + { + $this->telemetryInfo = $telemetryInfo; + $this->testSuite = $testSuite; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function testSuite(): TestSuite + { + return $this->testSuite; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Test Runner Execution Started (%d test%s)', + $this->testSuite->count(), + $this->testSuite->count() !== 1 ? 's' : '', + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/ExecutionStartedSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/ExecutionStartedSubscriber.php new file mode 100644 index 000000000..532f4409a --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/ExecutionStartedSubscriber.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 PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface ExecutionStartedSubscriber extends Subscriber +{ + public function notify(ExecutionStarted $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/ExtensionBootstrapped.php b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/ExtensionBootstrapped.php new file mode 100644 index 000000000..4ae1a6d5b --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/ExtensionBootstrapped.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 PHPUnit\Event\TestRunner; + +use function sprintf; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ExtensionBootstrapped implements Event +{ + private Telemetry\Info $telemetryInfo; + + /** + * @var class-string + */ + private string $className; + + /** + * @var array + */ + private array $parameters; + + /** + * @param class-string $className + * @param array $parameters + */ + public function __construct(Telemetry\Info $telemetryInfo, string $className, array $parameters) + { + $this->telemetryInfo = $telemetryInfo; + $this->className = $className; + $this->parameters = $parameters; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + /** + * @return array + */ + public function parameters(): array + { + return $this->parameters; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Extension Bootstrapped (%s)', + $this->className, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/ExtensionBootstrappedSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/ExtensionBootstrappedSubscriber.php new file mode 100644 index 000000000..c4c7d55c4 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/ExtensionBootstrappedSubscriber.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 PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface ExtensionBootstrappedSubscriber extends Subscriber +{ + public function notify(ExtensionBootstrapped $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/ExtensionLoadedFromPhar.php b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/ExtensionLoadedFromPhar.php new file mode 100644 index 000000000..2ce358d5c --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/ExtensionLoadedFromPhar.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use function sprintf; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ExtensionLoadedFromPhar implements Event +{ + private Telemetry\Info $telemetryInfo; + + /** + * @var non-empty-string + */ + private string $filename; + + /** + * @var non-empty-string + */ + private string $name; + + /** + * @var non-empty-string + */ + private string $version; + + /** + * @param non-empty-string $filename + * @param non-empty-string $name + * @param non-empty-string $version + */ + public function __construct(Telemetry\Info $telemetryInfo, string $filename, string $name, string $version) + { + $this->telemetryInfo = $telemetryInfo; + $this->filename = $filename; + $this->name = $name; + $this->version = $version; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return non-empty-string + */ + public function filename(): string + { + return $this->filename; + } + + /** + * @return non-empty-string + */ + public function name(): string + { + return $this->name; + } + + /** + * @return non-empty-string + */ + public function version(): string + { + return $this->version; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Extension Loaded from PHAR (%s %s)', + $this->name, + $this->version, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/ExtensionLoadedFromPharSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/ExtensionLoadedFromPharSubscriber.php new file mode 100644 index 000000000..fc7c2b0ab --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/ExtensionLoadedFromPharSubscriber.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 PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface ExtensionLoadedFromPharSubscriber extends Subscriber +{ + public function notify(ExtensionLoadedFromPhar $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/Finished.php b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/Finished.php new file mode 100644 index 000000000..2abc685bb --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/Finished.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 PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Finished implements Event +{ + private Telemetry\Info $telemetryInfo; + + public function __construct(Telemetry\Info $telemetryInfo) + { + $this->telemetryInfo = $telemetryInfo; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return 'Test Runner Finished'; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/FinishedSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/FinishedSubscriber.php new file mode 100644 index 000000000..6efc622d0 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/FinishedSubscriber.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 PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface FinishedSubscriber extends Subscriber +{ + public function notify(Finished $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/GarbageCollectionDisabled.php b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/GarbageCollectionDisabled.php new file mode 100644 index 000000000..4324a5c15 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/GarbageCollectionDisabled.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 PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class GarbageCollectionDisabled implements Event +{ + private Telemetry\Info $telemetryInfo; + + public function __construct(Telemetry\Info $telemetryInfo) + { + $this->telemetryInfo = $telemetryInfo; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return 'Test Runner Disabled Garbage Collection'; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/GarbageCollectionDisabledSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/GarbageCollectionDisabledSubscriber.php new file mode 100644 index 000000000..bb7e224fc --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/GarbageCollectionDisabledSubscriber.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 PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface GarbageCollectionDisabledSubscriber extends Subscriber +{ + public function notify(GarbageCollectionDisabled $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/GarbageCollectionEnabled.php b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/GarbageCollectionEnabled.php new file mode 100644 index 000000000..1c4e08887 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/GarbageCollectionEnabled.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 PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class GarbageCollectionEnabled implements Event +{ + private Telemetry\Info $telemetryInfo; + + public function __construct(Telemetry\Info $telemetryInfo) + { + $this->telemetryInfo = $telemetryInfo; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return 'Test Runner Enabled Garbage Collection'; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/GarbageCollectionEnabledSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/GarbageCollectionEnabledSubscriber.php new file mode 100644 index 000000000..437eddc23 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/GarbageCollectionEnabledSubscriber.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 PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface GarbageCollectionEnabledSubscriber extends Subscriber +{ + public function notify(GarbageCollectionEnabled $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/GarbageCollectionTriggered.php b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/GarbageCollectionTriggered.php new file mode 100644 index 000000000..d6a1ce643 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/GarbageCollectionTriggered.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 PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class GarbageCollectionTriggered implements Event +{ + private Telemetry\Info $telemetryInfo; + + public function __construct(Telemetry\Info $telemetryInfo) + { + $this->telemetryInfo = $telemetryInfo; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return 'Test Runner Triggered Garbage Collection'; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/GarbageCollectionTriggeredSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/GarbageCollectionTriggeredSubscriber.php new file mode 100644 index 000000000..8b941c535 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/GarbageCollectionTriggeredSubscriber.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 PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface GarbageCollectionTriggeredSubscriber extends Subscriber +{ + public function notify(GarbageCollectionTriggered $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/NoticeTriggered.php b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/NoticeTriggered.php new file mode 100644 index 000000000..a5bfa04f3 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/NoticeTriggered.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use function sprintf; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class NoticeTriggered implements Event +{ + private Telemetry\Info $telemetryInfo; + private string $message; + + public function __construct(Telemetry\Info $telemetryInfo, string $message) + { + $this->telemetryInfo = $telemetryInfo; + $this->message = $message; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function message(): string + { + return $this->message; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Test Runner Triggered Notice (%s)', + $this->message, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/NoticeTriggeredSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/NoticeTriggeredSubscriber.php new file mode 100644 index 000000000..be76b2c63 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/NoticeTriggeredSubscriber.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 PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface NoticeTriggeredSubscriber extends Subscriber +{ + public function notify(NoticeTriggered $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/Started.php b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/Started.php new file mode 100644 index 000000000..a58401108 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/Started.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 PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Started implements Event +{ + private Telemetry\Info $telemetryInfo; + + public function __construct(Telemetry\Info $telemetryInfo) + { + $this->telemetryInfo = $telemetryInfo; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return 'Test Runner Started'; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/StartedSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/StartedSubscriber.php new file mode 100644 index 000000000..342407031 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/StartedSubscriber.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 PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface StartedSubscriber extends Subscriber +{ + public function notify(Started $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/StaticAnalysisForCodeCoverageFinished.php b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/StaticAnalysisForCodeCoverageFinished.php new file mode 100644 index 000000000..d484528ec --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/StaticAnalysisForCodeCoverageFinished.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use function sprintf; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class StaticAnalysisForCodeCoverageFinished implements Event +{ + private Telemetry\Info $telemetryInfo; + + /** + * @var non-negative-int + */ + private int $cacheHits; + + /** + * @var non-negative-int + */ + private int $cacheMisses; + + /** + * @param non-negative-int $cacheHits + * @param non-negative-int $cacheMisses + */ + public function __construct(Telemetry\Info $telemetryInfo, int $cacheHits, int $cacheMisses) + { + $this->telemetryInfo = $telemetryInfo; + $this->cacheHits = $cacheHits; + $this->cacheMisses = $cacheMisses; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return non-negative-int + */ + public function cacheHits(): int + { + return $this->cacheHits; + } + + /** + * @return non-negative-int + */ + public function cacheMisses(): int + { + return $this->cacheMisses; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Static Analysis for Code Coverage Finished (%d cache hits, %d cache misses)', + $this->cacheHits, + $this->cacheMisses, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/StaticAnalysisForCodeCoverageFinishedSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/StaticAnalysisForCodeCoverageFinishedSubscriber.php new file mode 100644 index 000000000..eaf4f3485 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/StaticAnalysisForCodeCoverageFinishedSubscriber.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 PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface StaticAnalysisForCodeCoverageFinishedSubscriber extends Subscriber +{ + public function notify(StaticAnalysisForCodeCoverageFinished $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/StaticAnalysisForCodeCoverageStarted.php b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/StaticAnalysisForCodeCoverageStarted.php new file mode 100644 index 000000000..d12109727 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/StaticAnalysisForCodeCoverageStarted.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 PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class StaticAnalysisForCodeCoverageStarted implements Event +{ + private Telemetry\Info $telemetryInfo; + + public function __construct(Telemetry\Info $telemetryInfo) + { + $this->telemetryInfo = $telemetryInfo; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return 'Static Analysis for Code Coverage Started'; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/StaticAnalysisForCodeCoverageStartedSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/StaticAnalysisForCodeCoverageStartedSubscriber.php new file mode 100644 index 000000000..642bf712c --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/StaticAnalysisForCodeCoverageStartedSubscriber.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 PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface StaticAnalysisForCodeCoverageStartedSubscriber extends Subscriber +{ + public function notify(StaticAnalysisForCodeCoverageStarted $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/WarningTriggered.php b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/WarningTriggered.php new file mode 100644 index 000000000..e9df01be9 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/WarningTriggered.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestRunner; + +use function sprintf; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class WarningTriggered implements Event +{ + private Telemetry\Info $telemetryInfo; + + /** + * @var non-empty-string + */ + private string $message; + + /** + * @param non-empty-string $message + */ + public function __construct(Telemetry\Info $telemetryInfo, string $message) + { + $this->telemetryInfo = $telemetryInfo; + $this->message = $message; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + /** + * @return non-empty-string + */ + public function message(): string + { + return $this->message; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Test Runner Triggered Warning (%s)', + $this->message, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/WarningTriggeredSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/WarningTriggeredSubscriber.php new file mode 100644 index 000000000..9afdd18f3 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/TestRunner/WarningTriggeredSubscriber.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 PHPUnit\Event\TestRunner; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface WarningTriggeredSubscriber extends Subscriber +{ + public function notify(WarningTriggered $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/TestSuite/Filtered.php b/app/vendor/phpunit/phpunit/src/Event/Events/TestSuite/Filtered.php new file mode 100644 index 000000000..96d626ce4 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/TestSuite/Filtered.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 PHPUnit\Event\TestSuite; + +use function sprintf; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Filtered implements Event +{ + private Telemetry\Info $telemetryInfo; + private TestSuite $testSuite; + + public function __construct(Telemetry\Info $telemetryInfo, TestSuite $testSuite) + { + $this->telemetryInfo = $telemetryInfo; + $this->testSuite = $testSuite; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function testSuite(): TestSuite + { + return $this->testSuite; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Test Suite Filtered (%d test%s)', + $this->testSuite->count(), + $this->testSuite->count() !== 1 ? 's' : '', + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/TestSuite/FilteredSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/TestSuite/FilteredSubscriber.php new file mode 100644 index 000000000..6bba3ad40 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/TestSuite/FilteredSubscriber.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 PHPUnit\Event\TestSuite; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface FilteredSubscriber extends Subscriber +{ + public function notify(Filtered $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/TestSuite/Finished.php b/app/vendor/phpunit/phpunit/src/Event/Events/TestSuite/Finished.php new file mode 100644 index 000000000..a24ca8692 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/TestSuite/Finished.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestSuite; + +use function sprintf; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Finished implements Event +{ + private Telemetry\Info $telemetryInfo; + private TestSuite $testSuite; + + public function __construct(Telemetry\Info $telemetryInfo, TestSuite $testSuite) + { + $this->telemetryInfo = $telemetryInfo; + $this->testSuite = $testSuite; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function testSuite(): TestSuite + { + return $this->testSuite; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Test Suite Finished (%s, %d test%s)', + $this->testSuite->name(), + $this->testSuite->count(), + $this->testSuite->count() !== 1 ? 's' : '', + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/TestSuite/FinishedSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/TestSuite/FinishedSubscriber.php new file mode 100644 index 000000000..463c62136 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/TestSuite/FinishedSubscriber.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 PHPUnit\Event\TestSuite; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface FinishedSubscriber extends Subscriber +{ + public function notify(Finished $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/TestSuite/Loaded.php b/app/vendor/phpunit/phpunit/src/Event/Events/TestSuite/Loaded.php new file mode 100644 index 000000000..d278c0ddc --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/TestSuite/Loaded.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 PHPUnit\Event\TestSuite; + +use function sprintf; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Loaded implements Event +{ + private Telemetry\Info $telemetryInfo; + private TestSuite $testSuite; + + public function __construct(Telemetry\Info $telemetryInfo, TestSuite $testSuite) + { + $this->telemetryInfo = $telemetryInfo; + $this->testSuite = $testSuite; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function testSuite(): TestSuite + { + return $this->testSuite; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Test Suite Loaded (%d test%s)', + $this->testSuite->count(), + $this->testSuite->count() !== 1 ? 's' : '', + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/TestSuite/LoadedSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/TestSuite/LoadedSubscriber.php new file mode 100644 index 000000000..e43886c40 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/TestSuite/LoadedSubscriber.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 PHPUnit\Event\TestSuite; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface LoadedSubscriber extends Subscriber +{ + public function notify(Loaded $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/TestSuite/Skipped.php b/app/vendor/phpunit/phpunit/src/Event/Events/TestSuite/Skipped.php new file mode 100644 index 000000000..efe9c1fff --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/TestSuite/Skipped.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 PHPUnit\Event\TestSuite; + +use function sprintf; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Skipped implements Event +{ + private Telemetry\Info $telemetryInfo; + private TestSuite $testSuite; + private string $message; + + public function __construct(Telemetry\Info $telemetryInfo, TestSuite $testSuite, string $message) + { + $this->telemetryInfo = $telemetryInfo; + $this->testSuite = $testSuite; + $this->message = $message; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function testSuite(): TestSuite + { + return $this->testSuite; + } + + public function message(): string + { + return $this->message; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Test Suite Skipped (%s, %s)', + $this->testSuite->name(), + $this->message, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/TestSuite/SkippedSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/TestSuite/SkippedSubscriber.php new file mode 100644 index 000000000..30f509fc6 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/TestSuite/SkippedSubscriber.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 PHPUnit\Event\TestSuite; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface SkippedSubscriber extends Subscriber +{ + public function notify(Skipped $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/TestSuite/Sorted.php b/app/vendor/phpunit/phpunit/src/Event/Events/TestSuite/Sorted.php new file mode 100644 index 000000000..a73461db8 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/TestSuite/Sorted.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestSuite; + +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Sorted implements Event +{ + private Telemetry\Info $telemetryInfo; + private int $executionOrder; + private int $executionOrderDefects; + private bool $resolveDependencies; + + public function __construct(Telemetry\Info $telemetryInfo, int $executionOrder, int $executionOrderDefects, bool $resolveDependencies) + { + $this->telemetryInfo = $telemetryInfo; + $this->executionOrder = $executionOrder; + $this->executionOrderDefects = $executionOrderDefects; + $this->resolveDependencies = $resolveDependencies; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function executionOrder(): int + { + return $this->executionOrder; + } + + public function executionOrderDefects(): int + { + return $this->executionOrderDefects; + } + + public function resolveDependencies(): bool + { + return $this->resolveDependencies; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return 'Test Suite Sorted'; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/TestSuite/SortedSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/TestSuite/SortedSubscriber.php new file mode 100644 index 000000000..481eabb04 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/TestSuite/SortedSubscriber.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 PHPUnit\Event\TestSuite; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface SortedSubscriber extends Subscriber +{ + public function notify(Sorted $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/TestSuite/Started.php b/app/vendor/phpunit/phpunit/src/Event/Events/TestSuite/Started.php new file mode 100644 index 000000000..36fee1f00 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/TestSuite/Started.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestSuite; + +use function sprintf; +use PHPUnit\Event\Event; +use PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Started implements Event +{ + private Telemetry\Info $telemetryInfo; + private TestSuite $testSuite; + + public function __construct(Telemetry\Info $telemetryInfo, TestSuite $testSuite) + { + $this->telemetryInfo = $telemetryInfo; + $this->testSuite = $testSuite; + } + + public function telemetryInfo(): Telemetry\Info + { + return $this->telemetryInfo; + } + + public function testSuite(): TestSuite + { + return $this->testSuite; + } + + /** + * @return non-empty-string + */ + public function asString(): string + { + return sprintf( + 'Test Suite Started (%s, %d test%s)', + $this->testSuite->name(), + $this->testSuite->count(), + $this->testSuite->count() !== 1 ? 's' : '', + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Events/TestSuite/StartedSubscriber.php b/app/vendor/phpunit/phpunit/src/Event/Events/TestSuite/StartedSubscriber.php new file mode 100644 index 000000000..66c4e1b2d --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Events/TestSuite/StartedSubscriber.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 PHPUnit\Event\TestSuite; + +use PHPUnit\Event\Subscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface StartedSubscriber extends Subscriber +{ + public function notify(Started $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Exception/EventAlreadyAssignedException.php b/app/vendor/phpunit/phpunit/src/Event/Exception/EventAlreadyAssignedException.php new file mode 100644 index 000000000..a7dba264c --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Exception/EventAlreadyAssignedException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class EventAlreadyAssignedException extends RuntimeException implements Exception +{ +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Exception/EventFacadeIsSealedException.php b/app/vendor/phpunit/phpunit/src/Event/Exception/EventFacadeIsSealedException.php new file mode 100644 index 000000000..96bf949d9 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Exception/EventFacadeIsSealedException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class EventFacadeIsSealedException extends RuntimeException implements Exception +{ +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Exception/Exception.php b/app/vendor/phpunit/phpunit/src/Event/Exception/Exception.php new file mode 100644 index 000000000..25bf06c6b --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Exception/Exception.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface Exception extends \PHPUnit\Exception +{ +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Exception/InvalidArgumentException.php b/app/vendor/phpunit/phpunit/src/Event/Exception/InvalidArgumentException.php new file mode 100644 index 000000000..3fb060cf7 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Exception/InvalidArgumentException.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class InvalidArgumentException extends \InvalidArgumentException implements Exception +{ +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Exception/InvalidEventException.php b/app/vendor/phpunit/phpunit/src/Event/Exception/InvalidEventException.php new file mode 100644 index 000000000..05290372f --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Exception/InvalidEventException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class InvalidEventException extends RuntimeException implements Exception +{ +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Exception/InvalidSubscriberException.php b/app/vendor/phpunit/phpunit/src/Event/Exception/InvalidSubscriberException.php new file mode 100644 index 000000000..d12deb7f1 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Exception/InvalidSubscriberException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class InvalidSubscriberException extends RuntimeException implements Exception +{ +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Exception/MapError.php b/app/vendor/phpunit/phpunit/src/Event/Exception/MapError.php new file mode 100644 index 000000000..b97a18e6b --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Exception/MapError.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class MapError extends RuntimeException implements Exception +{ +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Exception/NoComparisonFailureException.php b/app/vendor/phpunit/phpunit/src/Event/Exception/NoComparisonFailureException.php new file mode 100644 index 000000000..f9926772c --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Exception/NoComparisonFailureException.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 PHPUnit\Event\Test; + +use PHPUnit\Event\Exception; +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class NoComparisonFailureException extends RuntimeException implements Exception +{ +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Exception/NoDataSetFromDataProviderException.php b/app/vendor/phpunit/phpunit/src/Event/Exception/NoDataSetFromDataProviderException.php new file mode 100644 index 000000000..b17a4d154 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Exception/NoDataSetFromDataProviderException.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 PHPUnit\Event\TestData; + +use PHPUnit\Event\Exception; +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class NoDataSetFromDataProviderException extends RuntimeException implements Exception +{ +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Exception/NoPreviousThrowableException.php b/app/vendor/phpunit/phpunit/src/Event/Exception/NoPreviousThrowableException.php new file mode 100644 index 000000000..e339323cd --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Exception/NoPreviousThrowableException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class NoPreviousThrowableException extends RuntimeException implements Exception +{ +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Exception/NoTestCaseObjectOnCallStackException.php b/app/vendor/phpunit/phpunit/src/Event/Exception/NoTestCaseObjectOnCallStackException.php new file mode 100644 index 000000000..35b4c25af --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Exception/NoTestCaseObjectOnCallStackException.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Code; + +use PHPUnit\Event\Exception; +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class NoTestCaseObjectOnCallStackException extends RuntimeException implements Exception +{ + public function __construct() + { + parent::__construct('Cannot find TestCase object on call stack'); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Exception/RuntimeException.php b/app/vendor/phpunit/phpunit/src/Event/Exception/RuntimeException.php new file mode 100644 index 000000000..2a444db2f --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Exception/RuntimeException.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class RuntimeException extends \RuntimeException implements Exception +{ +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Exception/SubscriberTypeAlreadyRegisteredException.php b/app/vendor/phpunit/phpunit/src/Event/Exception/SubscriberTypeAlreadyRegisteredException.php new file mode 100644 index 000000000..ebbbd3fa0 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Exception/SubscriberTypeAlreadyRegisteredException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class SubscriberTypeAlreadyRegisteredException extends RuntimeException implements Exception +{ +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Exception/UnknownEventException.php b/app/vendor/phpunit/phpunit/src/Event/Exception/UnknownEventException.php new file mode 100644 index 000000000..0c1211473 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Exception/UnknownEventException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class UnknownEventException extends RuntimeException implements Exception +{ +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Exception/UnknownEventTypeException.php b/app/vendor/phpunit/phpunit/src/Event/Exception/UnknownEventTypeException.php new file mode 100644 index 000000000..ab9432dec --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Exception/UnknownEventTypeException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class UnknownEventTypeException extends RuntimeException implements Exception +{ +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Exception/UnknownSubscriberException.php b/app/vendor/phpunit/phpunit/src/Event/Exception/UnknownSubscriberException.php new file mode 100644 index 000000000..b9aaedb1d --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Exception/UnknownSubscriberException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class UnknownSubscriberException extends RuntimeException implements Exception +{ +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Exception/UnknownSubscriberTypeException.php b/app/vendor/phpunit/phpunit/src/Event/Exception/UnknownSubscriberTypeException.php new file mode 100644 index 000000000..d44ff0e9c --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Exception/UnknownSubscriberTypeException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class UnknownSubscriberTypeException extends RuntimeException implements Exception +{ +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Facade.php b/app/vendor/phpunit/phpunit/src/Event/Facade.php new file mode 100644 index 000000000..6348fc434 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Facade.php @@ -0,0 +1,274 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +use function assert; +use function interface_exists; +use PHPUnit\Event\Telemetry\HRTime; +use PHPUnit\Event\Telemetry\SystemGarbageCollectorStatusProvider; +use PHPUnit\Runner\DeprecationCollector\Facade as DeprecationCollector; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Facade +{ + private static ?self $instance = null; + private Emitter $emitter; + private ?TypeMap $typeMap = null; + private ?DeferringDispatcher $deferringDispatcher = null; + private bool $sealed = false; + + public static function instance(): self + { + if (self::$instance === null) { + self::$instance = new self; + } + + return self::$instance; + } + + public static function emitter(): Emitter + { + return self::instance()->emitter; + } + + public function __construct() + { + $this->emitter = $this->createDispatchingEmitter(); + } + + /** + * @throws EventFacadeIsSealedException + * @throws UnknownSubscriberTypeException + */ + public function registerSubscribers(Subscriber ...$subscribers): void + { + foreach ($subscribers as $subscriber) { + $this->registerSubscriber($subscriber); + } + } + + /** + * @throws EventFacadeIsSealedException + * @throws UnknownSubscriberTypeException + */ + public function registerSubscriber(Subscriber $subscriber): void + { + if ($this->sealed) { + throw new EventFacadeIsSealedException; + } + + $this->deferredDispatcher()->registerSubscriber($subscriber); + } + + /** + * @throws EventFacadeIsSealedException + */ + public function registerTracer(Tracer\Tracer $tracer): void + { + if ($this->sealed) { + throw new EventFacadeIsSealedException; + } + + $this->deferredDispatcher()->registerTracer($tracer); + } + + /** + * @codeCoverageIgnore + * + * @noinspection PhpUnused + */ + public function initForIsolation(HRTime $offset): CollectingDispatcher + { + DeprecationCollector::initForIsolation(); + + $dispatcher = new CollectingDispatcher( + new DirectDispatcher($this->typeMap()), + ); + + $this->emitter = new DispatchingEmitter( + $dispatcher, + new Telemetry\System( + new Telemetry\SystemStopWatchWithOffset($offset), + new Telemetry\SystemMemoryMeter, + new SystemGarbageCollectorStatusProvider, + ), + ); + + $this->sealed = true; + + return $dispatcher; + } + + public function forward(EventCollection $events): void + { + $dispatcher = $this->deferredDispatcher(); + + foreach ($events as $event) { + $dispatcher->dispatch($event); + } + } + + public function seal(): void + { + $this->deferredDispatcher()->flush(); + + $this->sealed = true; + + $this->emitter->testRunnerEventFacadeSealed(); + } + + private function createDispatchingEmitter(): DispatchingEmitter + { + return new DispatchingEmitter( + $this->deferredDispatcher(), + $this->createTelemetrySystem(), + ); + } + + private function createTelemetrySystem(): Telemetry\System + { + return new Telemetry\System( + new Telemetry\SystemStopWatch, + new Telemetry\SystemMemoryMeter, + new SystemGarbageCollectorStatusProvider, + ); + } + + private function deferredDispatcher(): DeferringDispatcher + { + if ($this->deferringDispatcher === null) { + $this->deferringDispatcher = new DeferringDispatcher( + new DirectDispatcher($this->typeMap()), + ); + } + + return $this->deferringDispatcher; + } + + private function typeMap(): TypeMap + { + if ($this->typeMap === null) { + $typeMap = new TypeMap; + + $this->registerDefaultTypes($typeMap); + + $this->typeMap = $typeMap; + } + + return $this->typeMap; + } + + private function registerDefaultTypes(TypeMap $typeMap): void + { + $defaultEvents = [ + Application\Started::class, + Application\Finished::class, + + Test\DataProviderMethodCalled::class, + Test\DataProviderMethodFinished::class, + Test\MarkedIncomplete::class, + Test\AfterLastTestMethodCalled::class, + Test\AfterLastTestMethodErrored::class, + Test\AfterLastTestMethodFailed::class, + Test\AfterLastTestMethodFinished::class, + Test\AfterTestMethodCalled::class, + Test\AfterTestMethodErrored::class, + Test\AfterTestMethodFailed::class, + Test\AfterTestMethodFinished::class, + Test\BeforeFirstTestMethodCalled::class, + Test\BeforeFirstTestMethodErrored::class, + Test\BeforeFirstTestMethodFailed::class, + Test\BeforeFirstTestMethodFinished::class, + Test\BeforeTestMethodCalled::class, + Test\BeforeTestMethodErrored::class, + Test\BeforeTestMethodFailed::class, + Test\BeforeTestMethodFinished::class, + Test\AdditionalInformationProvided::class, + Test\ComparatorRegistered::class, + Test\ConsideredRisky::class, + Test\DeprecationTriggered::class, + Test\Errored::class, + Test\ErrorTriggered::class, + Test\Failed::class, + Test\Finished::class, + Test\NoticeTriggered::class, + Test\Passed::class, + Test\PhpDeprecationTriggered::class, + Test\PhpNoticeTriggered::class, + Test\PhpunitDeprecationTriggered::class, + Test\PhpunitNoticeTriggered::class, + Test\PhpunitErrorTriggered::class, + Test\PhpunitWarningTriggered::class, + Test\PhpWarningTriggered::class, + Test\PostConditionCalled::class, + Test\PostConditionErrored::class, + Test\PostConditionFailed::class, + Test\PostConditionFinished::class, + Test\PreConditionCalled::class, + Test\PreConditionErrored::class, + Test\PreConditionFailed::class, + Test\PreConditionFinished::class, + Test\PreparationStarted::class, + Test\Prepared::class, + Test\PreparationErrored::class, + Test\PreparationFailed::class, + Test\PrintedUnexpectedOutput::class, + Test\Skipped::class, + Test\WarningTriggered::class, + + Test\MockObjectCreated::class, + Test\MockObjectForIntersectionOfInterfacesCreated::class, + Test\PartialMockObjectCreated::class, + Test\TestStubCreated::class, + Test\TestStubForIntersectionOfInterfacesCreated::class, + + TestRunner\BootstrapFinished::class, + TestRunner\Configured::class, + TestRunner\EventFacadeSealed::class, + TestRunner\ExecutionAborted::class, + TestRunner\ExecutionFinished::class, + TestRunner\ExecutionStarted::class, + TestRunner\ExtensionLoadedFromPhar::class, + TestRunner\ExtensionBootstrapped::class, + TestRunner\Finished::class, + TestRunner\Started::class, + TestRunner\DeprecationTriggered::class, + TestRunner\NoticeTriggered::class, + TestRunner\WarningTriggered::class, + TestRunner\GarbageCollectionDisabled::class, + TestRunner\GarbageCollectionTriggered::class, + TestRunner\GarbageCollectionEnabled::class, + TestRunner\ChildProcessStarted::class, + TestRunner\ChildProcessErrored::class, + TestRunner\ChildProcessFinished::class, + TestRunner\StaticAnalysisForCodeCoverageFinished::class, + TestRunner\StaticAnalysisForCodeCoverageStarted::class, + + TestSuite\Filtered::class, + TestSuite\Finished::class, + TestSuite\Loaded::class, + TestSuite\Skipped::class, + TestSuite\Sorted::class, + TestSuite\Started::class, + ]; + + foreach ($defaultEvents as $eventClass) { + $subscriberInterface = $eventClass . 'Subscriber'; + + assert(interface_exists($subscriberInterface)); + + $typeMap->addMapping($subscriberInterface, $eventClass); + } + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Subscriber.php b/app/vendor/phpunit/phpunit/src/Event/Subscriber.php new file mode 100644 index 000000000..e0455c025 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Subscriber.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface Subscriber +{ +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Tracer.php b/app/vendor/phpunit/phpunit/src/Event/Tracer.php new file mode 100644 index 000000000..3b029fdf2 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Tracer.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 PHPUnit\Event\Tracer; + +use PHPUnit\Event\Event; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface Tracer +{ + public function trace(Event $event): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/TypeMap.php b/app/vendor/phpunit/phpunit/src/Event/TypeMap.php new file mode 100644 index 000000000..e525e446d --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/TypeMap.php @@ -0,0 +1,192 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event; + +use function array_key_exists; +use function class_exists; +use function class_implements; +use function in_array; +use function interface_exists; +use function sprintf; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class TypeMap +{ + /** + * @var array + */ + private array $mapping = []; + + /** + * @param class-string $subscriberInterface + * @param class-string $eventClass + * + * @throws EventAlreadyAssignedException + * @throws InvalidEventException + * @throws InvalidSubscriberException + * @throws SubscriberTypeAlreadyRegisteredException + * @throws UnknownEventException + * @throws UnknownSubscriberException + */ + public function addMapping(string $subscriberInterface, string $eventClass): void + { + $this->ensureSubscriberInterfaceExists($subscriberInterface); + $this->ensureSubscriberInterfaceExtendsInterface($subscriberInterface); + $this->ensureEventClassExists($eventClass); + $this->ensureEventClassImplementsEventInterface($eventClass); + $this->ensureSubscriberWasNotAlreadyRegistered($subscriberInterface); + $this->ensureEventWasNotAlreadyAssigned($eventClass); + + $this->mapping[$subscriberInterface] = $eventClass; + } + + public function isKnownSubscriberType(Subscriber $subscriber): bool + { + foreach (class_implements($subscriber) as $interface) { + if (array_key_exists($interface, $this->mapping)) { + return true; + } + } + + return false; + } + + public function isKnownEventType(Event $event): bool + { + return in_array($event::class, $this->mapping, true); + } + + /** + * @throws MapError + * + * @return class-string + */ + public function map(Subscriber $subscriber): string + { + foreach (class_implements($subscriber) as $interface) { + if (array_key_exists($interface, $this->mapping)) { + return $this->mapping[$interface]; + } + } + + throw new MapError( + sprintf( + 'Subscriber "%s" does not implement a known interface', + $subscriber::class, + ), + ); + } + + /** + * @param class-string $subscriberInterface + * + * @throws UnknownSubscriberException + */ + private function ensureSubscriberInterfaceExists(string $subscriberInterface): void + { + if (!interface_exists($subscriberInterface)) { + throw new UnknownSubscriberException( + sprintf( + 'Subscriber "%s" does not exist or is not an interface', + $subscriberInterface, + ), + ); + } + } + + /** + * @param class-string $eventClass + * + * @throws UnknownEventException + */ + private function ensureEventClassExists(string $eventClass): void + { + if (!class_exists($eventClass)) { + throw new UnknownEventException( + sprintf( + 'Event class "%s" does not exist', + $eventClass, + ), + ); + } + } + + /** + * @param class-string $subscriberInterface + * + * @throws InvalidSubscriberException + */ + private function ensureSubscriberInterfaceExtendsInterface(string $subscriberInterface): void + { + if (!in_array(Subscriber::class, class_implements($subscriberInterface), true)) { + throw new InvalidSubscriberException( + sprintf( + 'Subscriber "%s" does not extend Subscriber interface', + $subscriberInterface, + ), + ); + } + } + + /** + * @param class-string $eventClass + * + * @throws InvalidEventException + */ + private function ensureEventClassImplementsEventInterface(string $eventClass): void + { + if (!in_array(Event::class, class_implements($eventClass), true)) { + throw new InvalidEventException( + sprintf( + 'Event "%s" does not implement Event interface', + $eventClass, + ), + ); + } + } + + /** + * @param class-string $subscriberInterface + * + * @throws SubscriberTypeAlreadyRegisteredException + */ + private function ensureSubscriberWasNotAlreadyRegistered(string $subscriberInterface): void + { + if (array_key_exists($subscriberInterface, $this->mapping)) { + throw new SubscriberTypeAlreadyRegisteredException( + sprintf( + 'Subscriber type "%s" already registered', + $subscriberInterface, + ), + ); + } + } + + /** + * @param class-string $eventClass + * + * @throws EventAlreadyAssignedException + */ + private function ensureEventWasNotAlreadyAssigned(string $eventClass): void + { + if (in_array($eventClass, $this->mapping, true)) { + throw new EventAlreadyAssignedException( + sprintf( + 'Event "%s" already assigned', + $eventClass, + ), + ); + } + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Value/ClassMethod.php b/app/vendor/phpunit/phpunit/src/Event/Value/ClassMethod.php new file mode 100644 index 000000000..2a94033a5 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Value/ClassMethod.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Code; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ClassMethod +{ + /** + * @var class-string + */ + private string $className; + + /** + * @var non-empty-string + */ + private string $methodName; + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public function __construct(string $className, string $methodName) + { + $this->className = $className; + $this->methodName = $methodName; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + /** + * @return non-empty-string + */ + public function methodName(): string + { + return $this->methodName; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Value/ComparisonFailure.php b/app/vendor/phpunit/phpunit/src/Event/Value/ComparisonFailure.php new file mode 100644 index 000000000..c31f93e65 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Value/ComparisonFailure.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Code; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ComparisonFailure +{ + private string $expected; + private string $actual; + private string $diff; + + public function __construct(string $expected, string $actual, string $diff) + { + $this->expected = $expected; + $this->actual = $actual; + $this->diff = $diff; + } + + public function expected(): string + { + return $this->expected; + } + + public function actual(): string + { + return $this->actual; + } + + public function diff(): string + { + return $this->diff; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Value/ComparisonFailureBuilder.php b/app/vendor/phpunit/phpunit/src/Event/Value/ComparisonFailureBuilder.php new file mode 100644 index 000000000..53fe0d09a --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Value/ComparisonFailureBuilder.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 PHPUnit\Event\Code; + +use function is_bool; +use function is_scalar; +use function print_r; +use PHPUnit\Framework\ExpectationFailedException; +use Throwable; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ComparisonFailureBuilder +{ + public static function from(Throwable $t): ?ComparisonFailure + { + if (!$t instanceof ExpectationFailedException) { + return null; + } + + if ($t->getComparisonFailure() === null) { + return null; + } + + $expectedAsString = $t->getComparisonFailure()->getExpectedAsString(); + + if ($expectedAsString === '') { + $expectedAsString = self::mapScalarValueToString($t->getComparisonFailure()->getExpected()); + } + + $actualAsString = $t->getComparisonFailure()->getActualAsString(); + + if ($actualAsString === '') { + $actualAsString = self::mapScalarValueToString($t->getComparisonFailure()->getActual()); + } + + return new ComparisonFailure( + $expectedAsString, + $actualAsString, + $t->getComparisonFailure()->getDiff(), + ); + } + + private static function mapScalarValueToString(mixed $value): string + { + if ($value === null) { + return 'null'; + } + + if (is_bool($value)) { + return $value ? 'true' : 'false'; + } + + if (is_scalar($value)) { + return print_r($value, true); + } + + return ''; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Value/Runtime/OperatingSystem.php b/app/vendor/phpunit/phpunit/src/Event/Value/Runtime/OperatingSystem.php new file mode 100644 index 000000000..508d809f1 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Value/Runtime/OperatingSystem.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 PHPUnit\Event\Runtime; + +use const PHP_OS; +use const PHP_OS_FAMILY; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class OperatingSystem +{ + private string $operatingSystem; + private string $operatingSystemFamily; + + public function __construct() + { + $this->operatingSystem = PHP_OS; + $this->operatingSystemFamily = PHP_OS_FAMILY; + } + + public function operatingSystem(): string + { + return $this->operatingSystem; + } + + public function operatingSystemFamily(): string + { + return $this->operatingSystemFamily; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Value/Runtime/PHP.php b/app/vendor/phpunit/phpunit/src/Event/Value/Runtime/PHP.php new file mode 100644 index 000000000..46004f98c --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Value/Runtime/PHP.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Runtime; + +use const PHP_EXTRA_VERSION; +use const PHP_MAJOR_VERSION; +use const PHP_MINOR_VERSION; +use const PHP_RELEASE_VERSION; +use const PHP_SAPI; +use const PHP_VERSION; +use const PHP_VERSION_ID; +use function array_merge; +use function get_loaded_extensions; +use function sort; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PHP +{ + private string $version; + private int $versionId; + private int $majorVersion; + private int $minorVersion; + private int $releaseVersion; + private string $extraVersion; + private string $sapi; + + /** + * @var list + */ + private array $extensions; + + public function __construct() + { + $this->version = PHP_VERSION; + $this->versionId = PHP_VERSION_ID; + $this->majorVersion = PHP_MAJOR_VERSION; + $this->minorVersion = PHP_MINOR_VERSION; + $this->releaseVersion = PHP_RELEASE_VERSION; + $this->extraVersion = PHP_EXTRA_VERSION; + $this->sapi = PHP_SAPI; + + $extensions = array_merge( + get_loaded_extensions(true), + get_loaded_extensions(), + ); + + sort($extensions); + + $this->extensions = $extensions; + } + + public function version(): string + { + return $this->version; + } + + public function sapi(): string + { + return $this->sapi; + } + + public function majorVersion(): int + { + return $this->majorVersion; + } + + public function minorVersion(): int + { + return $this->minorVersion; + } + + public function releaseVersion(): int + { + return $this->releaseVersion; + } + + public function extraVersion(): string + { + return $this->extraVersion; + } + + public function versionId(): int + { + return $this->versionId; + } + + /** + * @return list + */ + public function extensions(): array + { + return $this->extensions; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Value/Runtime/PHPUnit.php b/app/vendor/phpunit/phpunit/src/Event/Value/Runtime/PHPUnit.php new file mode 100644 index 000000000..7f85133d2 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Value/Runtime/PHPUnit.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 PHPUnit\Event\Runtime; + +use PHPUnit\Runner\Version; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PHPUnit +{ + private string $versionId; + private string $releaseSeries; + + public function __construct() + { + $this->versionId = Version::id(); + $this->releaseSeries = Version::series(); + } + + public function versionId(): string + { + return $this->versionId; + } + + public function releaseSeries(): string + { + return $this->releaseSeries; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Value/Runtime/Runtime.php b/app/vendor/phpunit/phpunit/src/Event/Value/Runtime/Runtime.php new file mode 100644 index 000000000..552ec9887 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Value/Runtime/Runtime.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Runtime; + +use function sprintf; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Runtime +{ + private OperatingSystem $operatingSystem; + private PHP $php; + private PHPUnit $phpunit; + + public function __construct() + { + $this->operatingSystem = new OperatingSystem; + $this->php = new PHP; + $this->phpunit = new PHPUnit; + } + + public function asString(): string + { + $php = $this->php(); + + return sprintf( + 'PHPUnit %s using PHP %s (%s) on %s', + $this->phpunit()->versionId(), + $php->version(), + $php->sapi(), + $this->operatingSystem()->operatingSystem(), + ); + } + + public function operatingSystem(): OperatingSystem + { + return $this->operatingSystem; + } + + public function php(): PHP + { + return $this->php; + } + + public function phpunit(): PHPUnit + { + return $this->phpunit; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Value/Telemetry/Duration.php b/app/vendor/phpunit/phpunit/src/Event/Value/Telemetry/Duration.php new file mode 100644 index 000000000..230150a15 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Value/Telemetry/Duration.php @@ -0,0 +1,148 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Telemetry; + +use function floor; +use function sprintf; +use PHPUnit\Event\InvalidArgumentException; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Duration +{ + private int $seconds; + private int $nanoseconds; + + /** + * @throws InvalidArgumentException + */ + public static function fromSecondsAndNanoseconds(int $seconds, int $nanoseconds): self + { + return new self( + $seconds, + $nanoseconds, + ); + } + + /** + * @throws InvalidArgumentException + */ + private function __construct(int $seconds, int $nanoseconds) + { + $this->ensureNotNegative($seconds, 'seconds'); + $this->ensureNotNegative($nanoseconds, 'nanoseconds'); + $this->ensureNanoSecondsInRange($nanoseconds); + + $this->seconds = $seconds; + $this->nanoseconds = $nanoseconds; + } + + public function seconds(): int + { + return $this->seconds; + } + + public function nanoseconds(): int + { + return $this->nanoseconds; + } + + public function asFloat(): float + { + return $this->seconds() + ($this->nanoseconds() / 1000000000); + } + + public function asString(): string + { + $seconds = $this->seconds(); + $minutes = 0; + $hours = 0; + + if ($seconds > 60 * 60) { + $hours = floor($seconds / 60 / 60); + $seconds -= ($hours * 60 * 60); + } + + if ($seconds > 60) { + $minutes = floor($seconds / 60); + $seconds -= ($minutes * 60); + } + + return sprintf( + '%02d:%02d:%02d.%09d', + $hours, + $minutes, + $seconds, + $this->nanoseconds(), + ); + } + + public function equals(self $other): bool + { + return $this->seconds === $other->seconds && + $this->nanoseconds === $other->nanoseconds; + } + + public function isLessThan(self $other): bool + { + if ($this->seconds < $other->seconds) { + return true; + } + + if ($this->seconds > $other->seconds) { + return false; + } + + return $this->nanoseconds < $other->nanoseconds; + } + + public function isGreaterThan(self $other): bool + { + if ($this->seconds > $other->seconds) { + return true; + } + + if ($this->seconds < $other->seconds) { + return false; + } + + return $this->nanoseconds > $other->nanoseconds; + } + + /** + * @throws InvalidArgumentException + */ + private function ensureNotNegative(int $value, string $type): void + { + if ($value < 0) { + throw new InvalidArgumentException( + sprintf( + 'Value for %s must not be negative.', + $type, + ), + ); + } + } + + /** + * @throws InvalidArgumentException + */ + private function ensureNanoSecondsInRange(int $nanoseconds): void + { + if ($nanoseconds > 999999999) { + throw new InvalidArgumentException( + 'Value for nanoseconds must not be greater than 999999999.', + ); + } + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Value/Telemetry/GarbageCollectorStatus.php b/app/vendor/phpunit/phpunit/src/Event/Value/Telemetry/GarbageCollectorStatus.php new file mode 100644 index 000000000..f8bb77a74 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Value/Telemetry/GarbageCollectorStatus.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class GarbageCollectorStatus +{ + private int $runs; + private int $collected; + private int $threshold; + private int $roots; + private float $applicationTime; + private float $collectorTime; + private float $destructorTime; + private float $freeTime; + private bool $running; + private bool $protected; + private bool $full; + private int $bufferSize; + + public function __construct(int $runs, int $collected, int $threshold, int $roots, float $applicationTime, float $collectorTime, float $destructorTime, float $freeTime, bool $running, bool $protected, bool $full, int $bufferSize) + { + $this->runs = $runs; + $this->collected = $collected; + $this->threshold = $threshold; + $this->roots = $roots; + $this->applicationTime = $applicationTime; + $this->collectorTime = $collectorTime; + $this->destructorTime = $destructorTime; + $this->freeTime = $freeTime; + $this->running = $running; + $this->protected = $protected; + $this->full = $full; + $this->bufferSize = $bufferSize; + } + + public function runs(): int + { + return $this->runs; + } + + public function collected(): int + { + return $this->collected; + } + + public function threshold(): int + { + return $this->threshold; + } + + public function roots(): int + { + return $this->roots; + } + + public function applicationTime(): float + { + return $this->applicationTime; + } + + public function collectorTime(): float + { + return $this->collectorTime; + } + + public function destructorTime(): float + { + return $this->destructorTime; + } + + public function freeTime(): float + { + return $this->freeTime; + } + + public function isRunning(): bool + { + return $this->running; + } + + public function isProtected(): bool + { + return $this->protected; + } + + public function isFull(): bool + { + return $this->full; + } + + public function bufferSize(): int + { + return $this->bufferSize; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Value/Telemetry/GarbageCollectorStatusProvider.php b/app/vendor/phpunit/phpunit/src/Event/Value/Telemetry/GarbageCollectorStatusProvider.php new file mode 100644 index 000000000..09bede2e5 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Value/Telemetry/GarbageCollectorStatusProvider.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 PHPUnit\Event\Telemetry; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This interface is not covered by the backward compatibility promise for PHPUnit + */ +interface GarbageCollectorStatusProvider +{ + public function status(): GarbageCollectorStatus; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Value/Telemetry/HRTime.php b/app/vendor/phpunit/phpunit/src/Event/Value/Telemetry/HRTime.php new file mode 100644 index 000000000..8a7b97ebd --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Value/Telemetry/HRTime.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 PHPUnit\Event\Telemetry; + +use function sprintf; +use PHPUnit\Event\InvalidArgumentException; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class HRTime +{ + private int $seconds; + private int $nanoseconds; + + /** + * @throws InvalidArgumentException + */ + public static function fromSecondsAndNanoseconds(int $seconds, int $nanoseconds): self + { + return new self( + $seconds, + $nanoseconds, + ); + } + + /** + * @throws InvalidArgumentException + */ + private function __construct(int $seconds, int $nanoseconds) + { + $this->ensureNotNegative($seconds, 'seconds'); + $this->ensureNotNegative($nanoseconds, 'nanoseconds'); + $this->ensureNanoSecondsInRange($nanoseconds); + + $this->seconds = $seconds; + $this->nanoseconds = $nanoseconds; + } + + public function seconds(): int + { + return $this->seconds; + } + + public function nanoseconds(): int + { + return $this->nanoseconds; + } + + public function duration(self $start): Duration + { + $seconds = $this->seconds - $start->seconds(); + $nanoseconds = $this->nanoseconds - $start->nanoseconds(); + + if ($nanoseconds < 0) { + $seconds--; + + $nanoseconds += 1000000000; + } + + if ($seconds < 0) { + return Duration::fromSecondsAndNanoseconds(0, 0); + } + + return Duration::fromSecondsAndNanoseconds( + $seconds, + $nanoseconds, + ); + } + + /** + * @throws InvalidArgumentException + */ + private function ensureNotNegative(int $value, string $type): void + { + if ($value < 0) { + throw new InvalidArgumentException( + sprintf( + 'Value for %s must not be negative.', + $type, + ), + ); + } + } + + /** + * @throws InvalidArgumentException + */ + private function ensureNanoSecondsInRange(int $nanoseconds): void + { + if ($nanoseconds > 999999999) { + throw new InvalidArgumentException( + 'Value for nanoseconds must not be greater than 999999999.', + ); + } + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Value/Telemetry/Info.php b/app/vendor/phpunit/phpunit/src/Event/Value/Telemetry/Info.php new file mode 100644 index 000000000..a0d0a99f1 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Value/Telemetry/Info.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 PHPUnit\Event\Telemetry; + +use function sprintf; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Info +{ + private Snapshot $current; + private Duration $durationSinceStart; + private MemoryUsage $memorySinceStart; + private Duration $durationSincePrevious; + private MemoryUsage $memorySincePrevious; + + public function __construct(Snapshot $current, Duration $durationSinceStart, MemoryUsage $memorySinceStart, Duration $durationSincePrevious, MemoryUsage $memorySincePrevious) + { + $this->current = $current; + $this->durationSinceStart = $durationSinceStart; + $this->memorySinceStart = $memorySinceStart; + $this->durationSincePrevious = $durationSincePrevious; + $this->memorySincePrevious = $memorySincePrevious; + } + + public function time(): HRTime + { + return $this->current->time(); + } + + public function memoryUsage(): MemoryUsage + { + return $this->current->memoryUsage(); + } + + public function peakMemoryUsage(): MemoryUsage + { + return $this->current->peakMemoryUsage(); + } + + public function durationSinceStart(): Duration + { + return $this->durationSinceStart; + } + + public function memoryUsageSinceStart(): MemoryUsage + { + return $this->memorySinceStart; + } + + public function durationSincePrevious(): Duration + { + return $this->durationSincePrevious; + } + + public function memoryUsageSincePrevious(): MemoryUsage + { + return $this->memorySincePrevious; + } + + public function garbageCollectorStatus(): GarbageCollectorStatus + { + return $this->current->garbageCollectorStatus(); + } + + public function asString(): string + { + return sprintf( + '[%s / %s] [%d bytes]', + $this->durationSinceStart()->asString(), + $this->durationSincePrevious()->asString(), + $this->peakMemoryUsage()->bytes(), + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Value/Telemetry/MemoryMeter.php b/app/vendor/phpunit/phpunit/src/Event/Value/Telemetry/MemoryMeter.php new file mode 100644 index 000000000..4d116ff3e --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Value/Telemetry/MemoryMeter.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Telemetry; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This interface is not covered by the backward compatibility promise for PHPUnit + */ +interface MemoryMeter +{ + public function memoryUsage(): MemoryUsage; + + public function peakMemoryUsage(): MemoryUsage; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Value/Telemetry/MemoryUsage.php b/app/vendor/phpunit/phpunit/src/Event/Value/Telemetry/MemoryUsage.php new file mode 100644 index 000000000..8ace32ea0 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Value/Telemetry/MemoryUsage.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 PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class MemoryUsage +{ + private int $bytes; + + public static function fromBytes(int $bytes): self + { + return new self($bytes); + } + + private function __construct(int $bytes) + { + $this->bytes = $bytes; + } + + public function bytes(): int + { + return $this->bytes; + } + + public function diff(self $other): self + { + return self::fromBytes($this->bytes - $other->bytes); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Value/Telemetry/Snapshot.php b/app/vendor/phpunit/phpunit/src/Event/Value/Telemetry/Snapshot.php new file mode 100644 index 000000000..0f00f5d80 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Value/Telemetry/Snapshot.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 PHPUnit\Event\Telemetry; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Snapshot +{ + private HRTime $time; + private MemoryUsage $memoryUsage; + private MemoryUsage $peakMemoryUsage; + private GarbageCollectorStatus $garbageCollectorStatus; + + public function __construct(HRTime $time, MemoryUsage $memoryUsage, MemoryUsage $peakMemoryUsage, GarbageCollectorStatus $garbageCollectorStatus) + { + $this->time = $time; + $this->memoryUsage = $memoryUsage; + $this->peakMemoryUsage = $peakMemoryUsage; + $this->garbageCollectorStatus = $garbageCollectorStatus; + } + + public function time(): HRTime + { + return $this->time; + } + + public function memoryUsage(): MemoryUsage + { + return $this->memoryUsage; + } + + public function peakMemoryUsage(): MemoryUsage + { + return $this->peakMemoryUsage; + } + + public function garbageCollectorStatus(): GarbageCollectorStatus + { + return $this->garbageCollectorStatus; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Value/Telemetry/StopWatch.php b/app/vendor/phpunit/phpunit/src/Event/Value/Telemetry/StopWatch.php new file mode 100644 index 000000000..07ce5227f --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Value/Telemetry/StopWatch.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 PHPUnit\Event\Telemetry; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This interface is not covered by the backward compatibility promise for PHPUnit + */ +interface StopWatch +{ + public function current(): HRTime; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Value/Telemetry/System.php b/app/vendor/phpunit/phpunit/src/Event/Value/Telemetry/System.php new file mode 100644 index 000000000..0a178363e --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Value/Telemetry/System.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 PHPUnit\Event\Telemetry; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class System +{ + private StopWatch $stopWatch; + private MemoryMeter $memoryMeter; + private GarbageCollectorStatusProvider $garbageCollectorStatusProvider; + + public function __construct(StopWatch $stopWatch, MemoryMeter $memoryMeter, GarbageCollectorStatusProvider $garbageCollectorStatusProvider) + { + $this->stopWatch = $stopWatch; + $this->memoryMeter = $memoryMeter; + $this->garbageCollectorStatusProvider = $garbageCollectorStatusProvider; + } + + public function snapshot(): Snapshot + { + return new Snapshot( + $this->stopWatch->current(), + $this->memoryMeter->memoryUsage(), + $this->memoryMeter->peakMemoryUsage(), + $this->garbageCollectorStatusProvider->status(), + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Value/Telemetry/SystemGarbageCollectorStatusProvider.php b/app/vendor/phpunit/phpunit/src/Event/Value/Telemetry/SystemGarbageCollectorStatusProvider.php new file mode 100644 index 000000000..3f33d690d --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Value/Telemetry/SystemGarbageCollectorStatusProvider.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 PHPUnit\Event\Telemetry; + +use function gc_status; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class SystemGarbageCollectorStatusProvider implements GarbageCollectorStatusProvider +{ + public function status(): GarbageCollectorStatus + { + $status = gc_status(); + + return new GarbageCollectorStatus( + $status['runs'], + $status['collected'], + $status['threshold'], + $status['roots'], + $status['application_time'], + $status['collector_time'], + $status['destructor_time'], + $status['free_time'], + $status['running'], + $status['protected'], + $status['full'], + $status['buffer_size'], + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Value/Telemetry/SystemMemoryMeter.php b/app/vendor/phpunit/phpunit/src/Event/Value/Telemetry/SystemMemoryMeter.php new file mode 100644 index 000000000..16d895a94 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Value/Telemetry/SystemMemoryMeter.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 PHPUnit\Event\Telemetry; + +use function memory_get_peak_usage; +use function memory_get_usage; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class SystemMemoryMeter implements MemoryMeter +{ + public function memoryUsage(): MemoryUsage + { + return MemoryUsage::fromBytes(memory_get_usage()); + } + + public function peakMemoryUsage(): MemoryUsage + { + return MemoryUsage::fromBytes(memory_get_peak_usage()); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Value/Telemetry/SystemStopWatch.php b/app/vendor/phpunit/phpunit/src/Event/Value/Telemetry/SystemStopWatch.php new file mode 100644 index 000000000..a57c10324 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Value/Telemetry/SystemStopWatch.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 PHPUnit\Event\Telemetry; + +use function hrtime; +use PHPUnit\Event\InvalidArgumentException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class SystemStopWatch implements StopWatch +{ + /** + * @throws InvalidArgumentException + */ + public function current(): HRTime + { + return HRTime::fromSecondsAndNanoseconds(...hrtime()); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Value/Telemetry/SystemStopWatchWithOffset.php b/app/vendor/phpunit/phpunit/src/Event/Value/Telemetry/SystemStopWatchWithOffset.php new file mode 100644 index 000000000..d27fd98c1 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Value/Telemetry/SystemStopWatchWithOffset.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 PHPUnit\Event\Telemetry; + +use function hrtime; +use PHPUnit\Event\InvalidArgumentException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @codeCoverageIgnore + */ +final class SystemStopWatchWithOffset implements StopWatch +{ + private ?HRTime $offset; + + public function __construct(HRTime $offset) + { + $this->offset = $offset; + } + + /** + * @throws InvalidArgumentException + */ + public function current(): HRTime + { + if ($this->offset !== null) { + $offset = $this->offset; + + $this->offset = null; + + return $offset; + } + + return HRTime::fromSecondsAndNanoseconds(...hrtime()); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Value/Test/Issue/DirectTrigger.php b/app/vendor/phpunit/phpunit/src/Event/Value/Test/Issue/DirectTrigger.php new file mode 100644 index 000000000..bbb3c66a8 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Value/Test/Issue/DirectTrigger.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 PHPUnit\Event\Code\IssueTrigger; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class DirectTrigger extends IssueTrigger +{ + /** + * Your own code triggers an issue in third-party code. + */ + public function isDirect(): true + { + return true; + } + + public function asString(): string + { + return 'issue triggered by first-party code calling into third-party code'; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Value/Test/Issue/IndirectTrigger.php b/app/vendor/phpunit/phpunit/src/Event/Value/Test/Issue/IndirectTrigger.php new file mode 100644 index 000000000..81f76b45e --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Value/Test/Issue/IndirectTrigger.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 PHPUnit\Event\Code\IssueTrigger; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class IndirectTrigger extends IssueTrigger +{ + /** + * Third-party code triggers an issue either in your own code or in third-party code. + */ + public function isIndirect(): true + { + return true; + } + + public function asString(): string + { + return 'issue triggered by third-party code'; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Value/Test/Issue/IssueTrigger.php b/app/vendor/phpunit/phpunit/src/Event/Value/Test/Issue/IssueTrigger.php new file mode 100644 index 000000000..93e42c729 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Value/Test/Issue/IssueTrigger.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 PHPUnit\Event\Code\IssueTrigger; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +abstract class IssueTrigger +{ + public static function test(): TestTrigger + { + return new TestTrigger; + } + + public static function self(): SelfTrigger + { + return new SelfTrigger; + } + + public static function direct(): DirectTrigger + { + return new DirectTrigger; + } + + public static function indirect(): IndirectTrigger + { + return new IndirectTrigger; + } + + public static function unknown(): UnknownTrigger + { + return new UnknownTrigger; + } + + final private function __construct() + { + } + + /** + * Your test code triggers an issue. + * + * @phpstan-assert-if-true TestTrigger $this + */ + public function isTest(): bool + { + return false; + } + + /** + * Your own code triggers an issue in your own code. + * + * @phpstan-assert-if-true SelfTrigger $this + */ + public function isSelf(): bool + { + return false; + } + + /** + * Your own code triggers an issue in third-party code. + * + * @phpstan-assert-if-true DirectTrigger $this + */ + public function isDirect(): bool + { + return false; + } + + /** + * Third-party code triggers an issue either in your own code or in third-party code. + * + * @phpstan-assert-if-true IndirectTrigger $this + */ + public function isIndirect(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true UnknownTrigger $this + */ + public function isUnknown(): bool + { + return false; + } + + abstract public function asString(): string; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Value/Test/Issue/SelfTrigger.php b/app/vendor/phpunit/phpunit/src/Event/Value/Test/Issue/SelfTrigger.php new file mode 100644 index 000000000..e569e72f5 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Value/Test/Issue/SelfTrigger.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 PHPUnit\Event\Code\IssueTrigger; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class SelfTrigger extends IssueTrigger +{ + /** + * Your own code triggers an issue in your own code. + */ + public function isSelf(): true + { + return true; + } + + public function asString(): string + { + return 'issue triggered by first-party code calling into first-party code'; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Value/Test/Issue/TestTrigger.php b/app/vendor/phpunit/phpunit/src/Event/Value/Test/Issue/TestTrigger.php new file mode 100644 index 000000000..4768baca6 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Value/Test/Issue/TestTrigger.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 PHPUnit\Event\Code\IssueTrigger; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class TestTrigger extends IssueTrigger +{ + /** + * Your test code triggers an issue. + */ + public function isTest(): true + { + return true; + } + + public function asString(): string + { + return 'issue triggered by test code'; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Value/Test/Issue/UnknownTrigger.php b/app/vendor/phpunit/phpunit/src/Event/Value/Test/Issue/UnknownTrigger.php new file mode 100644 index 000000000..61f811489 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Value/Test/Issue/UnknownTrigger.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Code\IssueTrigger; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class UnknownTrigger extends IssueTrigger +{ + public function isUnknown(): true + { + return true; + } + + public function asString(): string + { + return 'unknown if issue was triggered in first-party code or third-party code'; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Value/Test/Phpt.php b/app/vendor/phpunit/phpunit/src/Event/Value/Test/Phpt.php new file mode 100644 index 000000000..65a3aec82 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Value/Test/Phpt.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 PHPUnit\Event\Code; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Phpt extends Test +{ + public function isPhpt(): true + { + return true; + } + + /** + * @return non-empty-string + */ + public function id(): string + { + return $this->file(); + } + + /** + * @return non-empty-string + */ + public function name(): string + { + return $this->file(); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Value/Test/Test.php b/app/vendor/phpunit/phpunit/src/Event/Value/Test/Test.php new file mode 100644 index 000000000..43ed73eb7 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Value/Test/Test.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 PHPUnit\Event\Code; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +abstract readonly class Test +{ + /** + * @var non-empty-string + */ + private string $file; + + /** + * @param non-empty-string $file + */ + public function __construct(string $file) + { + $this->file = $file; + } + + /** + * @return non-empty-string + */ + public function file(): string + { + return $this->file; + } + + /** + * @phpstan-assert-if-true TestMethod $this + */ + public function isTestMethod(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true Phpt $this + */ + public function isPhpt(): bool + { + return false; + } + + /** + * @return non-empty-string + */ + abstract public function id(): string; + + /** + * @return non-empty-string + */ + abstract public function name(): string; +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Value/Test/TestCollection.php b/app/vendor/phpunit/phpunit/src/Event/Value/Test/TestCollection.php new file mode 100644 index 000000000..1924b64b6 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Value/Test/TestCollection.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 PHPUnit\Event\Code; + +use function count; +use Countable; +use IteratorAggregate; + +/** + * @template-implements IteratorAggregate + * + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestCollection implements Countable, IteratorAggregate +{ + /** + * @var list + */ + private array $tests; + + /** + * @param list $tests + */ + public static function fromArray(array $tests): self + { + return new self(...$tests); + } + + private function __construct(Test ...$tests) + { + $this->tests = $tests; + } + + /** + * @return list + */ + public function asArray(): array + { + return $this->tests; + } + + public function count(): int + { + return count($this->tests); + } + + public function getIterator(): TestCollectionIterator + { + return new TestCollectionIterator($this); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Value/Test/TestCollectionIterator.php b/app/vendor/phpunit/phpunit/src/Event/Value/Test/TestCollectionIterator.php new file mode 100644 index 000000000..7c96f3409 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Value/Test/TestCollectionIterator.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 PHPUnit\Event\Code; + +use function count; +use Iterator; + +/** + * @template-implements Iterator + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class TestCollectionIterator implements Iterator +{ + /** + * @var list + */ + private readonly array $tests; + private int $position = 0; + + public function __construct(TestCollection $tests) + { + $this->tests = $tests->asArray(); + } + + public function rewind(): void + { + $this->position = 0; + } + + public function valid(): bool + { + return $this->position < count($this->tests); + } + + public function key(): int + { + return $this->position; + } + + public function current(): Test + { + return $this->tests[$this->position]; + } + + public function next(): void + { + $this->position++; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Value/Test/TestData/DataFromDataProvider.php b/app/vendor/phpunit/phpunit/src/Event/Value/Test/TestData/DataFromDataProvider.php new file mode 100644 index 000000000..981fd9e4f --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Value/Test/TestData/DataFromDataProvider.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestData; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class DataFromDataProvider extends TestData +{ + private int|string $dataSetName; + private string $dataAsStringForResultOutput; + + public static function from(int|string $dataSetName, string $data, string $dataAsStringForResultOutput): self + { + return new self($dataSetName, $data, $dataAsStringForResultOutput); + } + + protected function __construct(int|string $dataSetName, string $data, string $dataAsStringForResultOutput) + { + $this->dataSetName = $dataSetName; + $this->dataAsStringForResultOutput = $dataAsStringForResultOutput; + + parent::__construct($data); + } + + public function dataSetName(): int|string + { + return $this->dataSetName; + } + + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function dataAsStringForResultOutput(): string + { + return $this->dataAsStringForResultOutput; + } + + public function isFromDataProvider(): true + { + return true; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Value/Test/TestData/DataFromTestDependency.php b/app/vendor/phpunit/phpunit/src/Event/Value/Test/TestData/DataFromTestDependency.php new file mode 100644 index 000000000..9fbf17ebf --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Value/Test/TestData/DataFromTestDependency.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestData; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class DataFromTestDependency extends TestData +{ + public static function from(string $data): self + { + return new self($data); + } + + public function isFromTestDependency(): true + { + return true; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Value/Test/TestData/TestData.php b/app/vendor/phpunit/phpunit/src/Event/Value/Test/TestData/TestData.php new file mode 100644 index 000000000..893444806 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Value/Test/TestData/TestData.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 PHPUnit\Event\TestData; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +abstract readonly class TestData +{ + private string $data; + + protected function __construct(string $data) + { + $this->data = $data; + } + + public function data(): string + { + return $this->data; + } + + /** + * @phpstan-assert-if-true DataFromDataProvider $this + */ + public function isFromDataProvider(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true DataFromTestDependency $this + */ + public function isFromTestDependency(): bool + { + return false; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Value/Test/TestData/TestDataCollection.php b/app/vendor/phpunit/phpunit/src/Event/Value/Test/TestData/TestDataCollection.php new file mode 100644 index 000000000..460f0c0c5 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Value/Test/TestData/TestDataCollection.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestData; + +use function count; +use Countable; +use IteratorAggregate; + +/** + * @template-implements IteratorAggregate + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestDataCollection implements Countable, IteratorAggregate +{ + /** + * @var list + */ + private array $data; + private ?DataFromDataProvider $fromDataProvider; + + /** + * @param list $data + */ + public static function fromArray(array $data): self + { + return new self(...$data); + } + + private function __construct(TestData ...$data) + { + $fromDataProvider = null; + + foreach ($data as $_data) { + if ($_data->isFromDataProvider()) { + $fromDataProvider = $_data; + } + } + + $this->data = $data; + $this->fromDataProvider = $fromDataProvider; + } + + /** + * @return list + */ + public function asArray(): array + { + return $this->data; + } + + public function count(): int + { + return count($this->data); + } + + /** + * @phpstan-assert-if-true !null $this->fromDataProvider + */ + public function hasDataFromDataProvider(): bool + { + return $this->fromDataProvider !== null; + } + + /** + * @throws NoDataSetFromDataProviderException + */ + public function dataFromDataProvider(): DataFromDataProvider + { + if (!$this->hasDataFromDataProvider()) { + throw new NoDataSetFromDataProviderException; + } + + return $this->fromDataProvider; + } + + public function getIterator(): TestDataCollectionIterator + { + return new TestDataCollectionIterator($this); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Value/Test/TestData/TestDataCollectionIterator.php b/app/vendor/phpunit/phpunit/src/Event/Value/Test/TestData/TestDataCollectionIterator.php new file mode 100644 index 000000000..aebed66ca --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Value/Test/TestData/TestDataCollectionIterator.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 PHPUnit\Event\TestData; + +use function count; +use Iterator; + +/** + * @template-implements Iterator + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class TestDataCollectionIterator implements Iterator +{ + /** + * @var list + */ + private readonly array $data; + private int $position = 0; + + public function __construct(TestDataCollection $data) + { + $this->data = $data->asArray(); + } + + public function rewind(): void + { + $this->position = 0; + } + + public function valid(): bool + { + return $this->position < count($this->data); + } + + public function key(): int + { + return $this->position; + } + + public function current(): TestData + { + return $this->data[$this->position]; + } + + public function next(): void + { + $this->position++; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Value/Test/TestDox.php b/app/vendor/phpunit/phpunit/src/Event/Value/Test/TestDox.php new file mode 100644 index 000000000..53604dbfc --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Value/Test/TestDox.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 PHPUnit\Event\Code; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestDox +{ + private string $prettifiedClassName; + private string $prettifiedMethodName; + private string $prettifiedAndColorizedMethodName; + + public function __construct(string $prettifiedClassName, string $prettifiedMethodName, string $prettifiedAndColorizedMethodName) + { + $this->prettifiedClassName = $prettifiedClassName; + $this->prettifiedMethodName = $prettifiedMethodName; + $this->prettifiedAndColorizedMethodName = $prettifiedAndColorizedMethodName; + } + + public function prettifiedClassName(): string + { + return $this->prettifiedClassName; + } + + public function prettifiedMethodName(bool $colorize = false): string + { + if ($colorize) { + return $this->prettifiedAndColorizedMethodName; + } + + return $this->prettifiedMethodName; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Value/Test/TestDoxBuilder.php b/app/vendor/phpunit/phpunit/src/Event/Value/Test/TestDoxBuilder.php new file mode 100644 index 000000000..532dde003 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Value/Test/TestDoxBuilder.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 PHPUnit\Event\Code; + +use PHPUnit\Framework\TestCase; +use PHPUnit\Logging\TestDox\NamePrettifier; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class TestDoxBuilder +{ + private static ?NamePrettifier $namePrettifier = null; + + public static function fromTestCase(TestCase $testCase): TestDox + { + $prettifier = self::namePrettifier(); + + return new TestDox( + $prettifier->prettifyTestClassName($testCase::class), + $prettifier->prettifyTestCase($testCase, false), + $prettifier->prettifyTestCase($testCase, true), + ); + } + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public static function fromClassNameAndMethodName(string $className, string $methodName): TestDox + { + $prettifier = self::namePrettifier(); + + $prettifiedMethodName = $prettifier->prettifyTestMethodName($methodName); + + return new TestDox( + $prettifier->prettifyTestClassName($className), + $prettifiedMethodName, + $prettifiedMethodName, + ); + } + + private static function namePrettifier(): NamePrettifier + { + if (self::$namePrettifier === null) { + self::$namePrettifier = new NamePrettifier; + } + + return self::$namePrettifier; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Value/Test/TestMethod.php b/app/vendor/phpunit/phpunit/src/Event/Value/Test/TestMethod.php new file mode 100644 index 000000000..4c9726454 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Value/Test/TestMethod.php @@ -0,0 +1,151 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Code; + +use function is_int; +use function sprintf; +use PHPUnit\Event\TestData\TestDataCollection; +use PHPUnit\Metadata\MetadataCollection; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestMethod extends Test +{ + /** + * @var class-string + */ + private string $className; + + /** + * @var non-empty-string + */ + private string $methodName; + + /** + * @var non-negative-int + */ + private int $line; + private TestDox $testDox; + private MetadataCollection $metadata; + private TestDataCollection $testData; + + /** + * @param class-string $className + * @param non-empty-string $methodName + * @param non-empty-string $file + * @param non-negative-int $line + */ + public function __construct(string $className, string $methodName, string $file, int $line, TestDox $testDox, MetadataCollection $metadata, TestDataCollection $testData) + { + parent::__construct($file); + + $this->className = $className; + $this->methodName = $methodName; + $this->line = $line; + $this->testDox = $testDox; + $this->metadata = $metadata; + $this->testData = $testData; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + /** + * @return non-empty-string + */ + public function methodName(): string + { + return $this->methodName; + } + + /** + * @return non-negative-int + */ + public function line(): int + { + return $this->line; + } + + public function testDox(): TestDox + { + return $this->testDox; + } + + public function metadata(): MetadataCollection + { + return $this->metadata; + } + + public function testData(): TestDataCollection + { + return $this->testData; + } + + public function isTestMethod(): true + { + return true; + } + + /** + * @return non-empty-string + */ + public function id(): string + { + $buffer = $this->className . '::' . $this->methodName; + + if ($this->testData()->hasDataFromDataProvider()) { + $buffer .= '#' . $this->testData->dataFromDataProvider()->dataSetName(); + } + + return $buffer; + } + + /** + * @return non-empty-string + */ + public function nameWithClass(): string + { + return $this->className . '::' . $this->name(); + } + + /** + * @return non-empty-string + */ + public function name(): string + { + if (!$this->testData->hasDataFromDataProvider()) { + return $this->methodName; + } + + $dataSetName = $this->testData->dataFromDataProvider()->dataSetName(); + + if (is_int($dataSetName)) { + $dataSetName = sprintf( + ' with data set #%d', + $dataSetName, + ); + } else { + $dataSetName = sprintf( + ' with data set "%s"', + $dataSetName, + ); + } + + return $this->methodName . $dataSetName; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Value/Test/TestMethodBuilder.php b/app/vendor/phpunit/phpunit/src/Event/Value/Test/TestMethodBuilder.php new file mode 100644 index 000000000..dc1a32ef8 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Value/Test/TestMethodBuilder.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 PHPUnit\Event\Code; + +use function is_numeric; +use PHPUnit\Event\TestData\DataFromDataProvider; +use PHPUnit\Event\TestData\DataFromTestDependency; +use PHPUnit\Event\TestData\TestDataCollection; +use PHPUnit\Framework\TestCase; +use PHPUnit\Metadata\Parser\Registry as MetadataRegistry; +use PHPUnit\Util\Exporter; +use PHPUnit\Util\Reflection; +use PHPUnit\Util\Test as TestUtil; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestMethodBuilder +{ + public static function fromTestCase(TestCase $testCase, bool $useTestCaseForTestDox = true): TestMethod + { + $methodName = $testCase->name(); + $location = Reflection::sourceLocationFor($testCase::class, $methodName); + + if ($useTestCaseForTestDox) { + $testDox = TestDoxBuilder::fromTestCase($testCase); + } else { + $testDox = TestDoxBuilder::fromClassNameAndMethodName($testCase::class, $testCase->name()); + } + + return new TestMethod( + $testCase::class, + $methodName, + $location['file'], + $location['line'], + $testDox, + MetadataRegistry::parser()->forClassAndMethod($testCase::class, $methodName), + self::dataFor($testCase), + ); + } + + /** + * @throws NoTestCaseObjectOnCallStackException + */ + public static function fromCallStack(): TestMethod + { + return TestUtil::currentTestCase()->valueObjectForEvents(); + } + + private static function dataFor(TestCase $testCase): TestDataCollection + { + $testData = []; + + if ($testCase->usesDataProvider()) { + $dataSetName = $testCase->dataName(); + + if (is_numeric($dataSetName)) { + $dataSetName = (int) $dataSetName; + } + + $testData[] = DataFromDataProvider::from( + $dataSetName, + Exporter::shortenedRecursiveExport($testCase->providedData()), + $testCase->dataSetAsStringWithData(), + ); + } + + if ($testCase->hasDependencyInput()) { + $testData[] = DataFromTestDependency::from( + Exporter::shortenedRecursiveExport($testCase->dependencyInput()), + ); + } + + return TestDataCollection::fromArray($testData); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Value/TestSuite/TestSuite.php b/app/vendor/phpunit/phpunit/src/Event/Value/TestSuite/TestSuite.php new file mode 100644 index 000000000..783de57bd --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Value/TestSuite/TestSuite.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestSuite; + +use PHPUnit\Event\Code\TestCollection; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +abstract readonly class TestSuite +{ + /** + * @var non-empty-string + */ + private string $name; + private int $count; + private TestCollection $tests; + + /** + * @param non-empty-string $name + */ + public function __construct(string $name, int $size, TestCollection $tests) + { + $this->name = $name; + $this->count = $size; + $this->tests = $tests; + } + + /** + * @return non-empty-string + */ + public function name(): string + { + return $this->name; + } + + public function count(): int + { + return $this->count; + } + + public function tests(): TestCollection + { + return $this->tests; + } + + /** + * @phpstan-assert-if-true TestSuiteWithName $this + */ + public function isWithName(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true TestSuiteForTestClass $this + */ + public function isForTestClass(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true TestSuiteForTestMethodWithDataProvider $this + */ + public function isForTestMethodWithDataProvider(): bool + { + return false; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Value/TestSuite/TestSuiteBuilder.php b/app/vendor/phpunit/phpunit/src/Event/Value/TestSuite/TestSuiteBuilder.php new file mode 100644 index 000000000..3192636ba --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Value/TestSuite/TestSuiteBuilder.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 PHPUnit\Event\TestSuite; + +use function assert; +use function class_exists; +use function count; +use function explode; +use function method_exists; +use PHPUnit\Event\Code\Test; +use PHPUnit\Event\Code\TestCollection; +use PHPUnit\Event\RuntimeException; +use PHPUnit\Framework\DataProviderTestSuite; +use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\TestSuite as FrameworkTestSuite; +use PHPUnit\Runner\Phpt\TestCase as PhptTestCase; +use ReflectionClass; +use ReflectionMethod; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestSuiteBuilder +{ + /** + * @throws RuntimeException + */ + public static function from(FrameworkTestSuite $testSuite): TestSuite + { + $tests = []; + + self::process($testSuite, $tests); + + if ($testSuite instanceof DataProviderTestSuite) { + assert(count(explode('::', $testSuite->name())) === 2); + [$className, $methodName] = explode('::', $testSuite->name()); + + assert(class_exists($className)); + assert($methodName !== '' && method_exists($className, $methodName)); + + $reflector = new ReflectionMethod($className, $methodName); + + $file = $reflector->getFileName(); + $line = $reflector->getStartLine(); + + assert($file !== false); + assert($line !== false); + + return new TestSuiteForTestMethodWithDataProvider( + $testSuite->name(), + $testSuite->count(), + TestCollection::fromArray($tests), + $className, + $methodName, + $file, + $line, + ); + } + + if ($testSuite->isForTestClass()) { + $testClassName = $testSuite->name(); + + assert(class_exists($testClassName)); + + $reflector = new ReflectionClass($testClassName); + + $file = $reflector->getFileName(); + $line = $reflector->getStartLine(); + + assert($file !== false); + assert($line !== false); + + return new TestSuiteForTestClass( + $testClassName, + $testSuite->count(), + TestCollection::fromArray($tests), + $file, + $line, + ); + } + + return new TestSuiteWithName( + $testSuite->name(), + $testSuite->count(), + TestCollection::fromArray($tests), + ); + } + + /** + * @param list $tests + */ + private static function process(FrameworkTestSuite $testSuite, array &$tests): void + { + foreach ($testSuite->getIterator() as $test) { + if ($test instanceof FrameworkTestSuite) { + self::process($test, $tests); + + continue; + } + + if ($test instanceof TestCase || $test instanceof PhptTestCase) { + $tests[] = $test->valueObjectForEvents(); + } + } + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Value/TestSuite/TestSuiteForTestClass.php b/app/vendor/phpunit/phpunit/src/Event/Value/TestSuite/TestSuiteForTestClass.php new file mode 100644 index 000000000..34ad9d5ea --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Value/TestSuite/TestSuiteForTestClass.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestSuite; + +use PHPUnit\Event\Code\TestCollection; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestSuiteForTestClass extends TestSuite +{ + /** + * @var class-string + */ + private string $className; + private string $file; + private int $line; + + /** + * @param class-string $name + */ + public function __construct(string $name, int $size, TestCollection $tests, string $file, int $line) + { + parent::__construct($name, $size, $tests); + + $this->className = $name; + $this->file = $file; + $this->line = $line; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + public function file(): string + { + return $this->file; + } + + public function line(): int + { + return $this->line; + } + + public function isForTestClass(): true + { + return true; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Value/TestSuite/TestSuiteForTestMethodWithDataProvider.php b/app/vendor/phpunit/phpunit/src/Event/Value/TestSuite/TestSuiteForTestMethodWithDataProvider.php new file mode 100644 index 000000000..67a94391d --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Value/TestSuite/TestSuiteForTestMethodWithDataProvider.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\TestSuite; + +use PHPUnit\Event\Code\TestCollection; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestSuiteForTestMethodWithDataProvider extends TestSuite +{ + /** + * @var class-string + */ + private string $className; + + /** + * @var non-empty-string + */ + private string $methodName; + private string $file; + private int $line; + + /** + * @param non-empty-string $name + * @param class-string $className + * @param non-empty-string $methodName + */ + public function __construct(string $name, int $size, TestCollection $tests, string $className, string $methodName, string $file, int $line) + { + parent::__construct($name, $size, $tests); + + $this->className = $className; + $this->methodName = $methodName; + $this->file = $file; + $this->line = $line; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + /** + * @return non-empty-string + */ + public function methodName(): string + { + return $this->methodName; + } + + public function file(): string + { + return $this->file; + } + + public function line(): int + { + return $this->line; + } + + public function isForTestMethodWithDataProvider(): true + { + return true; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Value/TestSuite/TestSuiteWithName.php b/app/vendor/phpunit/phpunit/src/Event/Value/TestSuite/TestSuiteWithName.php new file mode 100644 index 000000000..4823fb263 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Value/TestSuite/TestSuiteWithName.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 PHPUnit\Event\TestSuite; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestSuiteWithName extends TestSuite +{ + public function isWithName(): true + { + return true; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Value/Throwable.php b/app/vendor/phpunit/phpunit/src/Event/Value/Throwable.php new file mode 100644 index 000000000..e48cf1a3a --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Value/Throwable.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Code; + +use const PHP_EOL; +use PHPUnit\Event\NoPreviousThrowableException; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Throwable +{ + /** + * @var class-string + */ + private string $className; + private string $message; + private string $description; + private string $stackTrace; + private ?Throwable $previous; + + /** + * @param class-string $className + */ + public function __construct(string $className, string $message, string $description, string $stackTrace, ?self $previous) + { + $this->className = $className; + $this->message = $message; + $this->description = $description; + $this->stackTrace = $stackTrace; + $this->previous = $previous; + } + + /** + * @throws NoPreviousThrowableException + */ + public function asString(): string + { + $buffer = $this->description(); + + if ($this->stackTrace() !== '') { + $buffer .= PHP_EOL . $this->stackTrace(); + } + + if ($this->hasPrevious()) { + $buffer .= PHP_EOL . 'Caused by' . PHP_EOL . $this->previous()->asString(); + } + + return $buffer; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + public function message(): string + { + return $this->message; + } + + public function description(): string + { + return $this->description; + } + + public function stackTrace(): string + { + return $this->stackTrace; + } + + /** + * @phpstan-assert-if-true !null $this->previous + */ + public function hasPrevious(): bool + { + return $this->previous !== null; + } + + /** + * @throws NoPreviousThrowableException + */ + public function previous(): self + { + if ($this->previous === null) { + throw new NoPreviousThrowableException; + } + + return $this->previous; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Event/Value/ThrowableBuilder.php b/app/vendor/phpunit/phpunit/src/Event/Value/ThrowableBuilder.php new file mode 100644 index 000000000..7db4beea7 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Event/Value/ThrowableBuilder.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Event\Code; + +use PHPUnit\Event\NoPreviousThrowableException; +use PHPUnit\Framework\Exception; +use PHPUnit\Util\Filter; +use PHPUnit\Util\ThrowableToStringMapper; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ThrowableBuilder +{ + /** + * @throws Exception + * @throws NoPreviousThrowableException + */ + public static function from(\Throwable $t): Throwable + { + $previous = $t->getPrevious(); + + if ($previous !== null) { + $previous = self::from($previous); + } + + return new Throwable( + $t::class, + $t->getMessage(), + ThrowableToStringMapper::map($t), + Filter::stackTraceFromThrowableAsString($t, false), + $previous, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Exception.php b/app/vendor/phpunit/phpunit/src/Exception.php index 4e7c33353..7a8302e20 100644 --- a/app/vendor/phpunit/phpunit/src/Exception.php +++ b/app/vendor/phpunit/phpunit/src/Exception.php @@ -12,7 +12,7 @@ use Throwable; /** - * @internal This class is not covered by the backward compatibility promise for PHPUnit + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ interface Exception extends Throwable { diff --git a/app/vendor/phpunit/phpunit/src/Framework/Assert.php b/app/vendor/phpunit/phpunit/src/Framework/Assert.php index ea8f17d28..91f940af3 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Assert.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Assert.php @@ -9,39 +9,20 @@ */ namespace PHPUnit\Framework; -use const DEBUG_BACKTRACE_IGNORE_ARGS; -use const PHP_EOL; -use function array_shift; -use function array_unshift; -use function assert; +use function array_combine; +use function array_intersect_key; use function class_exists; use function count; -use function debug_backtrace; -use function explode; use function file_get_contents; -use function func_get_args; -use function implode; use function interface_exists; -use function is_array; use function is_bool; -use function is_int; -use function is_iterable; -use function is_object; -use function is_string; -use function preg_match; -use function preg_split; use function sprintf; -use function strpos; use ArrayAccess; use Countable; -use DOMAttr; -use DOMDocument; -use DOMElement; use Generator; +use PHPUnit\Event\Facade as EventFacade; use PHPUnit\Framework\Constraint\ArrayHasKey; use PHPUnit\Framework\Constraint\Callback; -use PHPUnit\Framework\Constraint\ClassHasAttribute; -use PHPUnit\Framework\Constraint\ClassHasStaticAttribute; use PHPUnit\Framework\Constraint\Constraint; use PHPUnit\Framework\Constraint\Count; use PHPUnit\Framework\Constraint\DirectoryExists; @@ -59,6 +40,7 @@ use PHPUnit\Framework\Constraint\IsInfinite; use PHPUnit\Framework\Constraint\IsInstanceOf; use PHPUnit\Framework\Constraint\IsJson; +use PHPUnit\Framework\Constraint\IsList; use PHPUnit\Framework\Constraint\IsNan; use PHPUnit\Framework\Constraint\IsNull; use PHPUnit\Framework\Constraint\IsReadable; @@ -72,223 +54,933 @@ use PHPUnit\Framework\Constraint\LogicalOr; use PHPUnit\Framework\Constraint\LogicalXor; use PHPUnit\Framework\Constraint\ObjectEquals; -use PHPUnit\Framework\Constraint\ObjectHasAttribute; use PHPUnit\Framework\Constraint\ObjectHasProperty; use PHPUnit\Framework\Constraint\RegularExpression; use PHPUnit\Framework\Constraint\SameSize; use PHPUnit\Framework\Constraint\StringContains; use PHPUnit\Framework\Constraint\StringEndsWith; +use PHPUnit\Framework\Constraint\StringEqualsStringIgnoringLineEndings; use PHPUnit\Framework\Constraint\StringMatchesFormatDescription; use PHPUnit\Framework\Constraint\StringStartsWith; use PHPUnit\Framework\Constraint\TraversableContainsEqual; use PHPUnit\Framework\Constraint\TraversableContainsIdentical; use PHPUnit\Framework\Constraint\TraversableContainsOnly; -use PHPUnit\Util\Type; -use PHPUnit\Util\Xml; use PHPUnit\Util\Xml\Loader as XmlLoader; +use PHPUnit\Util\Xml\XmlException; /** * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ abstract class Assert { + private static int $count = 0; + /** - * @var int + * Asserts that two arrays are equal while only considering a list of keys. + * + * @param array $expected + * @param array $actual + * @param non-empty-list $keysToBeConsidered + * + * @throws Exception + * @throws ExpectationFailedException */ - private static $count = 0; + final public static function assertArrayIsEqualToArrayOnlyConsideringListOfKeys(array $expected, array $actual, array $keysToBeConsidered, string $message = ''): void + { + $filteredExpected = []; + + foreach ($keysToBeConsidered as $key) { + if (isset($expected[$key])) { + $filteredExpected[$key] = $expected[$key]; + } + } + + $filteredActual = []; + + foreach ($keysToBeConsidered as $key) { + if (isset($actual[$key])) { + $filteredActual[$key] = $actual[$key]; + } + } + + self::assertEquals($filteredExpected, $filteredActual, $message); + } /** - * Asserts that an array has a specified key. + * Asserts that two arrays are equal while ignoring a list of keys. * - * @param int|string $key - * @param array|ArrayAccess $array + * @param array $expected + * @param array $actual + * @param non-empty-list $keysToBeIgnored * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws Exception * @throws ExpectationFailedException */ - public static function assertArrayHasKey($key, $array, string $message = ''): void + final public static function assertArrayIsEqualToArrayIgnoringListOfKeys(array $expected, array $actual, array $keysToBeIgnored, string $message = ''): void { - if (!(is_int($key) || is_string($key))) { - throw InvalidArgumentException::create( - 1, - 'integer or string', - ); + foreach ($keysToBeIgnored as $key) { + unset($expected[$key], $actual[$key]); } - if (!(is_array($array) || $array instanceof ArrayAccess)) { - throw InvalidArgumentException::create( - 2, - 'array or ArrayAccess', - ); - } + self::assertEquals($expected, $actual, $message); + } - $constraint = new ArrayHasKey($key); + /** + * Asserts that two arrays are identical while only considering a list of keys. + * + * @param array $expected + * @param array $actual + * @param non-empty-list $keysToBeConsidered + * + * @throws Exception + * @throws ExpectationFailedException + */ + final public static function assertArrayIsIdenticalToArrayOnlyConsideringListOfKeys(array $expected, array $actual, array $keysToBeConsidered, string $message = ''): void + { + $keysToBeConsidered = array_combine($keysToBeConsidered, $keysToBeConsidered); + $expected = array_intersect_key($expected, $keysToBeConsidered); + $actual = array_intersect_key($actual, $keysToBeConsidered); - static::assertThat($array, $constraint, $message); + self::assertSame($expected, $actual, $message); } /** - * Asserts that an array does not have a specified key. + * Asserts that two arrays are equal while ignoring a list of keys. * - * @param int|string $key - * @param array|ArrayAccess $array + * @param array $expected + * @param array $actual + * @param non-empty-list $keysToBeIgnored * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws Exception * @throws ExpectationFailedException */ - public static function assertArrayNotHasKey($key, $array, string $message = ''): void + final public static function assertArrayIsIdenticalToArrayIgnoringListOfKeys(array $expected, array $actual, array $keysToBeIgnored, string $message = ''): void { - if (!(is_int($key) || is_string($key))) { - throw InvalidArgumentException::create( - 1, - 'integer or string', - ); + foreach ($keysToBeIgnored as $key) { + unset($expected[$key], $actual[$key]); } - if (!(is_array($array) || $array instanceof ArrayAccess)) { - throw InvalidArgumentException::create( - 2, - 'array or ArrayAccess', - ); - } + self::assertSame($expected, $actual, $message); + } + /** + * Asserts that an array has a specified key. + * + * @param array|ArrayAccess $array + * + * @throws Exception + * @throws ExpectationFailedException + */ + final public static function assertArrayHasKey(mixed $key, array|ArrayAccess $array, string $message = ''): void + { + $constraint = new ArrayHasKey($key); + + self::assertThat($array, $constraint, $message); + } + + /** + * Asserts that an array does not have a specified key. + * + * @param array|ArrayAccess $array + * + * @throws Exception + * @throws ExpectationFailedException + */ + final public static function assertArrayNotHasKey(mixed $key, array|ArrayAccess $array, string $message = ''): void + { $constraint = new LogicalNot( new ArrayHasKey($key), ); - static::assertThat($array, $constraint, $message); + self::assertThat($array, $constraint, $message); + } + + /** + * @phpstan-assert list $array + * + * @throws ExpectationFailedException + */ + final public static function assertIsList(mixed $array, string $message = ''): void + { + self::assertThat( + $array, + new IsList, + $message, + ); } /** * Asserts that a haystack contains a needle. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @param iterable $haystack + * * @throws Exception * @throws ExpectationFailedException */ - public static function assertContains($needle, iterable $haystack, string $message = ''): void + final public static function assertContains(mixed $needle, iterable $haystack, string $message = ''): void { $constraint = new TraversableContainsIdentical($needle); - static::assertThat($haystack, $constraint, $message); + self::assertThat($haystack, $constraint, $message); } - public static function assertContainsEquals($needle, iterable $haystack, string $message = ''): void + /** + * @param iterable $haystack + * + * @throws ExpectationFailedException + */ + final public static function assertContainsEquals(mixed $needle, iterable $haystack, string $message = ''): void { $constraint = new TraversableContainsEqual($needle); - static::assertThat($haystack, $constraint, $message); + self::assertThat($haystack, $constraint, $message); } /** * Asserts that a haystack does not contain a needle. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @param iterable $haystack + * * @throws Exception * @throws ExpectationFailedException */ - public static function assertNotContains($needle, iterable $haystack, string $message = ''): void + final public static function assertNotContains(mixed $needle, iterable $haystack, string $message = ''): void { $constraint = new LogicalNot( new TraversableContainsIdentical($needle), ); - static::assertThat($haystack, $constraint, $message); + self::assertThat($haystack, $constraint, $message); } - public static function assertNotContainsEquals($needle, iterable $haystack, string $message = ''): void + /** + * @param iterable $haystack + * + * @throws ExpectationFailedException + */ + final public static function assertNotContainsEquals(mixed $needle, iterable $haystack, string $message = ''): void { $constraint = new LogicalNot(new TraversableContainsEqual($needle)); - static::assertThat($haystack, $constraint, $message); + self::assertThat($haystack, $constraint, $message); } /** * Asserts that a haystack contains only values of a given type. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @param 'array'|'bool'|'boolean'|'callable'|'double'|'float'|'int'|'integer'|'iterable'|'null'|'numeric'|'object'|'real'|'resource (closed)'|'resource'|'scalar'|'string' $type + * @param iterable $haystack + * + * @throws Exception * @throws ExpectationFailedException + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/6056 */ - public static function assertContainsOnly(string $type, iterable $haystack, ?bool $isNativeType = null, string $message = ''): void + final public static function assertContainsOnly(string $type, iterable $haystack, ?bool $isNativeType = null, string $message = ''): void { if ($isNativeType === null) { - $isNativeType = Type::isType($type); + $isNativeType = self::isNativeType($type); } - static::assertThat( + if ($isNativeType) { + $replacement = match ($type) { + 'array' => 'assertContainsOnlyArray', + 'bool' => 'assertContainsOnlyBool', + 'boolean' => 'assertContainsOnlyBool', + 'callable' => 'assertContainsOnlyCallable', + 'double' => 'assertContainsOnlyFloat', + 'float' => 'assertContainsOnlyFloat', + 'int' => 'assertContainsOnlyInt', + 'integer' => 'assertContainsOnlyInt', + 'iterable' => 'assertContainsOnlyIterable', + 'null' => 'assertContainsOnlyNull', + 'numeric' => 'assertContainsOnlyNumeric', + 'object' => 'assertContainsOnlyObject', + 'real' => 'assertContainsOnlyFloat', + 'resource' => 'assertContainsOnlyResource', + 'resource (closed)' => 'assertContainsOnlyClosedResource', + 'scalar' => 'assertContainsOnlyScalar', + 'string' => 'assertContainsOnlyString', + }; + + EventFacade::emitter()->testTriggeredPhpunitDeprecation( + null, + sprintf( + 'assertContainsOnly() is deprecated and will be removed in PHPUnit 13. ' . + 'Please use %s($haystack) instead of assertContainsOnly(\'%s\', $haystack).', + $replacement, + $type, + ), + ); + + $constraint = TraversableContainsOnly::forNativeType(self::mapNativeType($type)); + } else { + EventFacade::emitter()->testTriggeredPhpunitDeprecation( + null, + sprintf( + 'assertContainsOnly() is deprecated and will be removed in PHPUnit 13. ' . + 'Please use assertContainsOnlyInstancesOf(\'%s\', $haystack) instead of assertContainsOnly(\'%s\', $haystack).', + $type, + $type, + ), + ); + + /** @phpstan-ignore argument.type */ + $constraint = TraversableContainsOnly::forClassOrInterface($type); + } + + self::assertThat($haystack, $constraint, $message); + } + + /** + * Asserts that a haystack contains only values of type array. + * + * @phpstan-assert iterable> $haystack + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + */ + final public static function assertContainsOnlyArray(iterable $haystack, string $message = ''): void + { + self::assertThat( $haystack, - new TraversableContainsOnly( - $type, - $isNativeType, + TraversableContainsOnly::forNativeType( + NativeType::Array, + ), + $message, + ); + } + + /** + * Asserts that a haystack contains only values of type bool. + * + * @phpstan-assert iterable $haystack + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + */ + final public static function assertContainsOnlyBool(iterable $haystack, string $message = ''): void + { + self::assertThat( + $haystack, + TraversableContainsOnly::forNativeType( + NativeType::Bool, ), $message, ); } /** - * Asserts that a haystack contains only instances of a given class name. + * Asserts that a haystack contains only values of type callable. + * + * @phpstan-assert iterable $haystack + * + * @param iterable $haystack * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException */ - public static function assertContainsOnlyInstancesOf(string $className, iterable $haystack, string $message = ''): void + final public static function assertContainsOnlyCallable(iterable $haystack, string $message = ''): void { - static::assertThat( + self::assertThat( $haystack, - new TraversableContainsOnly( - $className, - false, + TraversableContainsOnly::forNativeType( + NativeType::Callable, ), $message, ); } + /** + * Asserts that a haystack contains only values of type float. + * + * @phpstan-assert iterable $haystack + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + */ + final public static function assertContainsOnlyFloat(iterable $haystack, string $message = ''): void + { + self::assertThat( + $haystack, + TraversableContainsOnly::forNativeType( + NativeType::Float, + ), + $message, + ); + } + + /** + * Asserts that a haystack contains only values of type int. + * + * @phpstan-assert iterable $haystack + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + */ + final public static function assertContainsOnlyInt(iterable $haystack, string $message = ''): void + { + self::assertThat( + $haystack, + TraversableContainsOnly::forNativeType( + NativeType::Int, + ), + $message, + ); + } + + /** + * Asserts that a haystack contains only values of type iterable. + * + * @phpstan-assert iterable> $haystack + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + */ + final public static function assertContainsOnlyIterable(iterable $haystack, string $message = ''): void + { + self::assertThat( + $haystack, + TraversableContainsOnly::forNativeType( + NativeType::Iterable, + ), + $message, + ); + } + + /** + * Asserts that a haystack contains only values of type null. + * + * @phpstan-assert iterable $haystack + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + */ + final public static function assertContainsOnlyNull(iterable $haystack, string $message = ''): void + { + self::assertThat( + $haystack, + TraversableContainsOnly::forNativeType( + NativeType::Null, + ), + $message, + ); + } + + /** + * Asserts that a haystack contains only values of type numeric. + * + * @phpstan-assert iterable $haystack + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + */ + final public static function assertContainsOnlyNumeric(iterable $haystack, string $message = ''): void + { + self::assertThat( + $haystack, + TraversableContainsOnly::forNativeType( + NativeType::Numeric, + ), + $message, + ); + } + + /** + * Asserts that a haystack contains only values of type object. + * + * @phpstan-assert iterable $haystack + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + */ + final public static function assertContainsOnlyObject(iterable $haystack, string $message = ''): void + { + self::assertThat( + $haystack, + TraversableContainsOnly::forNativeType( + NativeType::Object, + ), + $message, + ); + } + + /** + * Asserts that a haystack contains only values of type resource. + * + * @phpstan-assert iterable $haystack + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + */ + final public static function assertContainsOnlyResource(iterable $haystack, string $message = ''): void + { + self::assertThat( + $haystack, + TraversableContainsOnly::forNativeType( + NativeType::Resource, + ), + $message, + ); + } + + /** + * Asserts that a haystack contains only values of type closed resource. + * + * @phpstan-assert iterable $haystack + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + */ + final public static function assertContainsOnlyClosedResource(iterable $haystack, string $message = ''): void + { + self::assertThat( + $haystack, + TraversableContainsOnly::forNativeType( + NativeType::ClosedResource, + ), + $message, + ); + } + + /** + * Asserts that a haystack contains only values of type scalar. + * + * @phpstan-assert iterable $haystack + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + */ + final public static function assertContainsOnlyScalar(iterable $haystack, string $message = ''): void + { + self::assertThat( + $haystack, + TraversableContainsOnly::forNativeType( + NativeType::Scalar, + ), + $message, + ); + } + + /** + * Asserts that a haystack contains only values of type string. + * + * @phpstan-assert iterable $haystack + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + */ + final public static function assertContainsOnlyString(iterable $haystack, string $message = ''): void + { + self::assertThat( + $haystack, + TraversableContainsOnly::forNativeType( + NativeType::String, + ), + $message, + ); + } + + /** + * Asserts that a haystack contains only instances of a specified interface or class name. + * + * @template T + * + * @phpstan-assert iterable $haystack + * + * @param class-string $className + * @param iterable $haystack + * + * @throws Exception + * @throws ExpectationFailedException + */ + final public static function assertContainsOnlyInstancesOf(string $className, iterable $haystack, string $message = ''): void + { + self::assertThat( + $haystack, + TraversableContainsOnly::forClassOrInterface($className), + $message, + ); + } + /** * Asserts that a haystack does not contain only values of a given type. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @param 'array'|'bool'|'boolean'|'callable'|'double'|'float'|'int'|'integer'|'iterable'|'null'|'numeric'|'object'|'real'|'resource (closed)'|'resource'|'scalar'|'string' $type + * @param iterable $haystack + * + * @throws Exception * @throws ExpectationFailedException + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/6056 */ - public static function assertNotContainsOnly(string $type, iterable $haystack, ?bool $isNativeType = null, string $message = ''): void + final public static function assertNotContainsOnly(string $type, iterable $haystack, ?bool $isNativeType = null, string $message = ''): void { if ($isNativeType === null) { - $isNativeType = Type::isType($type); + $isNativeType = self::isNativeType($type); } - static::assertThat( + if ($isNativeType) { + $replacement = match ($type) { + 'array' => 'assertContainsNotOnlyArray', + 'bool' => 'assertContainsNotOnlyBool', + 'boolean' => 'assertContainsNotOnlyBool', + 'callable' => 'assertContainsNotOnlyCallable', + 'double' => 'assertContainsNotOnlyFloat', + 'float' => 'assertContainsNotOnlyFloat', + 'int' => 'assertContainsNotOnlyInt', + 'integer' => 'assertContainsNotOnlyInt', + 'iterable' => 'assertContainsNotOnlyIterable', + 'null' => 'assertContainsNotOnlyNull', + 'numeric' => 'assertContainsNotOnlyNumeric', + 'object' => 'assertContainsNotOnlyObject', + 'real' => 'assertContainsNotOnlyFloat', + 'resource' => 'assertContainsNotOnlyResource', + 'resource (closed)' => 'assertContainsNotOnlyClosedResource', + 'scalar' => 'assertContainsNotOnlyScalar', + 'string' => 'assertContainsNotOnlyString', + }; + + EventFacade::emitter()->testTriggeredPhpunitDeprecation( + null, + sprintf( + 'assertNotContainsOnly() is deprecated and will be removed in PHPUnit 13. ' . + 'Please use %s($haystack) instead of assertNotContainsOnly(\'%s\', $haystack).', + $replacement, + $type, + ), + ); + + $constraint = TraversableContainsOnly::forNativeType(self::mapNativeType($type)); + } else { + EventFacade::emitter()->testTriggeredPhpunitDeprecation( + null, + sprintf( + 'assertNotContainsOnly() is deprecated and will be removed in PHPUnit 13. ' . + 'Please use assertContainsNotOnlyInstancesOf(\'%s\', $haystack) instead of assertNotContainsOnly(\'%s\', $haystack).', + $type, + $type, + ), + ); + + /** @phpstan-ignore argument.type */ + $constraint = TraversableContainsOnly::forClassOrInterface($type); + } + + self::assertThat( + $haystack, + new LogicalNot($constraint), + $message, + ); + } + + /** + * Asserts that a haystack does not contain only values of type array. + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + */ + final public static function assertContainsNotOnlyArray(iterable $haystack, string $message = ''): void + { + self::assertThat( $haystack, new LogicalNot( - new TraversableContainsOnly( - $type, - $isNativeType, + TraversableContainsOnly::forNativeType( + NativeType::Array, + ), + ), + $message, + ); + } + + /** + * Asserts that a haystack does not contain only values of type bool. + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + */ + final public static function assertContainsNotOnlyBool(iterable $haystack, string $message = ''): void + { + self::assertThat( + $haystack, + new LogicalNot( + TraversableContainsOnly::forNativeType( + NativeType::Bool, + ), + ), + $message, + ); + } + + /** + * Asserts that a haystack does not contain only values of type callable. + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + */ + final public static function assertContainsNotOnlyCallable(iterable $haystack, string $message = ''): void + { + self::assertThat( + $haystack, + new LogicalNot( + TraversableContainsOnly::forNativeType( + NativeType::Callable, + ), + ), + $message, + ); + } + + /** + * Asserts that a haystack does not contain only values of type float. + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + */ + final public static function assertContainsNotOnlyFloat(iterable $haystack, string $message = ''): void + { + self::assertThat( + $haystack, + new LogicalNot( + TraversableContainsOnly::forNativeType( + NativeType::Float, + ), + ), + $message, + ); + } + + /** + * Asserts that a haystack does not contain only values of type int. + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + */ + final public static function assertContainsNotOnlyInt(iterable $haystack, string $message = ''): void + { + self::assertThat( + $haystack, + new LogicalNot( + TraversableContainsOnly::forNativeType( + NativeType::Int, + ), + ), + $message, + ); + } + + /** + * Asserts that a haystack does not contain only values of type iterable. + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + */ + final public static function assertContainsNotOnlyIterable(iterable $haystack, string $message = ''): void + { + self::assertThat( + $haystack, + new LogicalNot( + TraversableContainsOnly::forNativeType( + NativeType::Iterable, + ), + ), + $message, + ); + } + + /** + * Asserts that a haystack does not contain only values of type null. + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + */ + final public static function assertContainsNotOnlyNull(iterable $haystack, string $message = ''): void + { + self::assertThat( + $haystack, + new LogicalNot( + TraversableContainsOnly::forNativeType( + NativeType::Null, ), ), $message, ); } + /** + * Asserts that a haystack does not contain only values of type numeric. + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + */ + final public static function assertContainsNotOnlyNumeric(iterable $haystack, string $message = ''): void + { + self::assertThat( + $haystack, + new LogicalNot( + TraversableContainsOnly::forNativeType( + NativeType::Numeric, + ), + ), + $message, + ); + } + + /** + * Asserts that a haystack does not contain only values of type object. + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + */ + final public static function assertContainsNotOnlyObject(iterable $haystack, string $message = ''): void + { + self::assertThat( + $haystack, + new LogicalNot( + TraversableContainsOnly::forNativeType( + NativeType::Object, + ), + ), + $message, + ); + } + + /** + * Asserts that a haystack does not contain only values of type resource. + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + */ + final public static function assertContainsNotOnlyResource(iterable $haystack, string $message = ''): void + { + self::assertThat( + $haystack, + new LogicalNot( + TraversableContainsOnly::forNativeType( + NativeType::Resource, + ), + ), + $message, + ); + } + + /** + * Asserts that a haystack does not contain only values of type closed resource. + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + */ + final public static function assertContainsNotOnlyClosedResource(iterable $haystack, string $message = ''): void + { + self::assertThat( + $haystack, + new LogicalNot( + TraversableContainsOnly::forNativeType( + NativeType::ClosedResource, + ), + ), + $message, + ); + } + + /** + * Asserts that a haystack does not contain only values of type scalar. + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + */ + final public static function assertContainsNotOnlyScalar(iterable $haystack, string $message = ''): void + { + self::assertThat( + $haystack, + new LogicalNot( + TraversableContainsOnly::forNativeType( + NativeType::Scalar, + ), + ), + $message, + ); + } + + /** + * Asserts that a haystack does not contain only values of type string. + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + */ + final public static function assertContainsNotOnlyString(iterable $haystack, string $message = ''): void + { + self::assertThat( + $haystack, + new LogicalNot( + TraversableContainsOnly::forNativeType( + NativeType::String, + ), + ), + $message, + ); + } + + /** + * Asserts that a haystack does not contain only instances of a specified interface or class name. + * + * @param class-string $className + * @param iterable $haystack + * + * @throws Exception + * @throws ExpectationFailedException + */ + final public static function assertContainsNotOnlyInstancesOf(string $className, iterable $haystack, string $message = ''): void + { + self::assertThat( + $haystack, + new LogicalNot( + TraversableContainsOnly::forClassOrInterface($className), + ), + $message, + ); + } + /** * Asserts the number of elements of an array, Countable or Traversable. * - * @param Countable|iterable $haystack + * @param Countable|iterable $haystack * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws Exception * @throws ExpectationFailedException + * @throws GeneratorNotSupportedException */ - public static function assertCount(int $expectedCount, $haystack, string $message = ''): void + final public static function assertCount(int $expectedCount, Countable|iterable $haystack, string $message = ''): void { if ($haystack instanceof Generator) { - self::createWarning('Passing an argument of type Generator for the $haystack parameter is deprecated. Support for this will be removed in PHPUnit 10.'); - } - - if (!$haystack instanceof Countable && !is_iterable($haystack)) { - throw InvalidArgumentException::create(2, 'countable or iterable'); + throw GeneratorNotSupportedException::fromParameterName('$haystack'); } - static::assertThat( + self::assertThat( $haystack, new Count($expectedCount), $message, @@ -298,136 +990,124 @@ public static function assertCount(int $expectedCount, $haystack, string $messag /** * Asserts the number of elements of an array, Countable or Traversable. * - * @param Countable|iterable $haystack + * @param Countable|iterable $haystack * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws Exception * @throws ExpectationFailedException + * @throws GeneratorNotSupportedException */ - public static function assertNotCount(int $expectedCount, $haystack, string $message = ''): void + final public static function assertNotCount(int $expectedCount, Countable|iterable $haystack, string $message = ''): void { if ($haystack instanceof Generator) { - self::createWarning('Passing an argument of type Generator for the $haystack parameter is deprecated. Support for this will be removed in PHPUnit 10.'); - } - - if (!$haystack instanceof Countable && !is_iterable($haystack)) { - throw InvalidArgumentException::create(2, 'countable or iterable'); + throw GeneratorNotSupportedException::fromParameterName('$haystack'); } $constraint = new LogicalNot( new Count($expectedCount), ); - static::assertThat($haystack, $constraint, $message); + self::assertThat($haystack, $constraint, $message); } /** * Asserts that two variables are equal. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException */ - public static function assertEquals($expected, $actual, string $message = ''): void + final public static function assertEquals(mixed $expected, mixed $actual, string $message = ''): void { $constraint = new IsEqual($expected); - static::assertThat($actual, $constraint, $message); + self::assertThat($actual, $constraint, $message); } /** * Asserts that two variables are equal (canonicalizing). * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException */ - public static function assertEqualsCanonicalizing($expected, $actual, string $message = ''): void + final public static function assertEqualsCanonicalizing(mixed $expected, mixed $actual, string $message = ''): void { $constraint = new IsEqualCanonicalizing($expected); - static::assertThat($actual, $constraint, $message); + self::assertThat($actual, $constraint, $message); } /** * Asserts that two variables are equal (ignoring case). * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException */ - public static function assertEqualsIgnoringCase($expected, $actual, string $message = ''): void + final public static function assertEqualsIgnoringCase(mixed $expected, mixed $actual, string $message = ''): void { $constraint = new IsEqualIgnoringCase($expected); - static::assertThat($actual, $constraint, $message); + self::assertThat($actual, $constraint, $message); } /** * Asserts that two variables are equal (with delta). * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException */ - public static function assertEqualsWithDelta($expected, $actual, float $delta, string $message = ''): void + final public static function assertEqualsWithDelta(mixed $expected, mixed $actual, float $delta, string $message = ''): void { $constraint = new IsEqualWithDelta( $expected, $delta, ); - static::assertThat($actual, $constraint, $message); + self::assertThat($actual, $constraint, $message); } /** * Asserts that two variables are not equal. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException */ - public static function assertNotEquals($expected, $actual, string $message = ''): void + final public static function assertNotEquals(mixed $expected, mixed $actual, string $message = ''): void { $constraint = new LogicalNot( new IsEqual($expected), ); - static::assertThat($actual, $constraint, $message); + self::assertThat($actual, $constraint, $message); } /** * Asserts that two variables are not equal (canonicalizing). * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException */ - public static function assertNotEqualsCanonicalizing($expected, $actual, string $message = ''): void + final public static function assertNotEqualsCanonicalizing(mixed $expected, mixed $actual, string $message = ''): void { $constraint = new LogicalNot( new IsEqualCanonicalizing($expected), ); - static::assertThat($actual, $constraint, $message); + self::assertThat($actual, $constraint, $message); } /** * Asserts that two variables are not equal (ignoring case). * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException */ - public static function assertNotEqualsIgnoringCase($expected, $actual, string $message = ''): void + final public static function assertNotEqualsIgnoringCase(mixed $expected, mixed $actual, string $message = ''): void { $constraint = new LogicalNot( new IsEqualIgnoringCase($expected), ); - static::assertThat($actual, $constraint, $message); + self::assertThat($actual, $constraint, $message); } /** * Asserts that two variables are not equal (with delta). * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException */ - public static function assertNotEqualsWithDelta($expected, $actual, float $delta, string $message = ''): void + final public static function assertNotEqualsWithDelta(mixed $expected, mixed $actual, float $delta, string $message = ''): void { $constraint = new LogicalNot( new IsEqualWithDelta( @@ -436,17 +1116,31 @@ public static function assertNotEqualsWithDelta($expected, $actual, float $delta ), ); - static::assertThat($actual, $constraint, $message); + self::assertThat($actual, $constraint, $message); } /** * @throws ExpectationFailedException */ - public static function assertObjectEquals(object $expected, object $actual, string $method = 'equals', string $message = ''): void + final public static function assertObjectEquals(object $expected, object $actual, string $method = 'equals', string $message = ''): void { - static::assertThat( + self::assertThat( $actual, - static::objectEquals($expected, $method), + self::objectEquals($expected, $method), + $message, + ); + } + + /** + * @throws ExpectationFailedException + */ + final public static function assertObjectNotEquals(object $expected, object $actual, string $method = 'equals', string $message = ''): void + { + self::assertThat( + $actual, + self::logicalNot( + self::objectEquals($expected, $method), + ), $message, ); } @@ -454,59 +1148,53 @@ public static function assertObjectEquals(object $expected, object $actual, stri /** * Asserts that a variable is empty. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException - * - * @psalm-assert empty $actual + * @throws GeneratorNotSupportedException */ - public static function assertEmpty($actual, string $message = ''): void + final public static function assertEmpty(mixed $actual, string $message = ''): void { if ($actual instanceof Generator) { - self::createWarning('Passing an argument of type Generator for the $actual parameter is deprecated. Support for this will be removed in PHPUnit 10.'); + throw GeneratorNotSupportedException::fromParameterName('$actual'); } - static::assertThat($actual, static::isEmpty(), $message); + self::assertThat($actual, self::isEmpty(), $message); } /** * Asserts that a variable is not empty. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException - * - * @psalm-assert !empty $actual + * @throws GeneratorNotSupportedException */ - public static function assertNotEmpty($actual, string $message = ''): void + final public static function assertNotEmpty(mixed $actual, string $message = ''): void { if ($actual instanceof Generator) { - self::createWarning('Passing an argument of type Generator for the $actual parameter is deprecated. Support for this will be removed in PHPUnit 10.'); + throw GeneratorNotSupportedException::fromParameterName('$actual'); } - static::assertThat($actual, static::logicalNot(static::isEmpty()), $message); + self::assertThat($actual, self::logicalNot(self::isEmpty()), $message); } /** * Asserts that a value is greater than another value. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException */ - public static function assertGreaterThan($expected, $actual, string $message = ''): void + final public static function assertGreaterThan(mixed $minimum, mixed $actual, string $message = ''): void { - static::assertThat($actual, static::greaterThan($expected), $message); + self::assertThat($actual, self::greaterThan($minimum), $message); } /** * Asserts that a value is greater than or equal to another value. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException */ - public static function assertGreaterThanOrEqual($expected, $actual, string $message = ''): void + final public static function assertGreaterThanOrEqual(mixed $minimum, mixed $actual, string $message = ''): void { - static::assertThat( + self::assertThat( $actual, - static::greaterThanOrEqual($expected), + self::greaterThanOrEqual($minimum), $message, ); } @@ -514,361 +1202,289 @@ public static function assertGreaterThanOrEqual($expected, $actual, string $mess /** * Asserts that a value is smaller than another value. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException */ - public static function assertLessThan($expected, $actual, string $message = ''): void + final public static function assertLessThan(mixed $maximum, mixed $actual, string $message = ''): void { - static::assertThat($actual, static::lessThan($expected), $message); + self::assertThat($actual, self::lessThan($maximum), $message); } /** * Asserts that a value is smaller than or equal to another value. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException */ - public static function assertLessThanOrEqual($expected, $actual, string $message = ''): void + final public static function assertLessThanOrEqual(mixed $maximum, mixed $actual, string $message = ''): void { - static::assertThat($actual, static::lessThanOrEqual($expected), $message); + self::assertThat($actual, self::lessThanOrEqual($maximum), $message); } /** * Asserts that the contents of one file is equal to the contents of another * file. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException */ - public static function assertFileEquals(string $expected, string $actual, string $message = ''): void + final public static function assertFileEquals(string $expected, string $actual, string $message = ''): void { - static::assertFileExists($expected, $message); - static::assertFileExists($actual, $message); + self::assertFileExists($expected, $message); + self::assertFileExists($actual, $message); $constraint = new IsEqual(file_get_contents($expected)); - static::assertThat(file_get_contents($actual), $constraint, $message); + self::assertThat(file_get_contents($actual), $constraint, $message); } /** * Asserts that the contents of one file is equal to the contents of another * file (canonicalizing). * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException */ - public static function assertFileEqualsCanonicalizing(string $expected, string $actual, string $message = ''): void + final public static function assertFileEqualsCanonicalizing(string $expected, string $actual, string $message = ''): void { - static::assertFileExists($expected, $message); - static::assertFileExists($actual, $message); + self::assertFileExists($expected, $message); + self::assertFileExists($actual, $message); $constraint = new IsEqualCanonicalizing( file_get_contents($expected), ); - static::assertThat(file_get_contents($actual), $constraint, $message); + self::assertThat(file_get_contents($actual), $constraint, $message); } /** * Asserts that the contents of one file is equal to the contents of another * file (ignoring case). * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException */ - public static function assertFileEqualsIgnoringCase(string $expected, string $actual, string $message = ''): void + final public static function assertFileEqualsIgnoringCase(string $expected, string $actual, string $message = ''): void { - static::assertFileExists($expected, $message); - static::assertFileExists($actual, $message); + self::assertFileExists($expected, $message); + self::assertFileExists($actual, $message); $constraint = new IsEqualIgnoringCase(file_get_contents($expected)); - static::assertThat(file_get_contents($actual), $constraint, $message); + self::assertThat(file_get_contents($actual), $constraint, $message); } /** * Asserts that the contents of one file is not equal to the contents of * another file. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException */ - public static function assertFileNotEquals(string $expected, string $actual, string $message = ''): void + final public static function assertFileNotEquals(string $expected, string $actual, string $message = ''): void { - static::assertFileExists($expected, $message); - static::assertFileExists($actual, $message); + self::assertFileExists($expected, $message); + self::assertFileExists($actual, $message); $constraint = new LogicalNot( new IsEqual(file_get_contents($expected)), ); - static::assertThat(file_get_contents($actual), $constraint, $message); + self::assertThat(file_get_contents($actual), $constraint, $message); } /** * Asserts that the contents of one file is not equal to the contents of another * file (canonicalizing). * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException */ - public static function assertFileNotEqualsCanonicalizing(string $expected, string $actual, string $message = ''): void + final public static function assertFileNotEqualsCanonicalizing(string $expected, string $actual, string $message = ''): void { - static::assertFileExists($expected, $message); - static::assertFileExists($actual, $message); + self::assertFileExists($expected, $message); + self::assertFileExists($actual, $message); $constraint = new LogicalNot( new IsEqualCanonicalizing(file_get_contents($expected)), ); - static::assertThat(file_get_contents($actual), $constraint, $message); + self::assertThat(file_get_contents($actual), $constraint, $message); } /** * Asserts that the contents of one file is not equal to the contents of another * file (ignoring case). * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException */ - public static function assertFileNotEqualsIgnoringCase(string $expected, string $actual, string $message = ''): void + final public static function assertFileNotEqualsIgnoringCase(string $expected, string $actual, string $message = ''): void { - static::assertFileExists($expected, $message); - static::assertFileExists($actual, $message); + self::assertFileExists($expected, $message); + self::assertFileExists($actual, $message); $constraint = new LogicalNot( new IsEqualIgnoringCase(file_get_contents($expected)), ); - static::assertThat(file_get_contents($actual), $constraint, $message); + self::assertThat(file_get_contents($actual), $constraint, $message); } /** * Asserts that the contents of a string is equal * to the contents of a file. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException */ - public static function assertStringEqualsFile(string $expectedFile, string $actualString, string $message = ''): void + final public static function assertStringEqualsFile(string $expectedFile, string $actualString, string $message = ''): void { - static::assertFileExists($expectedFile, $message); + self::assertFileExists($expectedFile, $message); $constraint = new IsEqual(file_get_contents($expectedFile)); - static::assertThat($actualString, $constraint, $message); + self::assertThat($actualString, $constraint, $message); } /** * Asserts that the contents of a string is equal * to the contents of a file (canonicalizing). * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException */ - public static function assertStringEqualsFileCanonicalizing(string $expectedFile, string $actualString, string $message = ''): void + final public static function assertStringEqualsFileCanonicalizing(string $expectedFile, string $actualString, string $message = ''): void { - static::assertFileExists($expectedFile, $message); + self::assertFileExists($expectedFile, $message); $constraint = new IsEqualCanonicalizing(file_get_contents($expectedFile)); - static::assertThat($actualString, $constraint, $message); + self::assertThat($actualString, $constraint, $message); } /** * Asserts that the contents of a string is equal * to the contents of a file (ignoring case). * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException */ - public static function assertStringEqualsFileIgnoringCase(string $expectedFile, string $actualString, string $message = ''): void + final public static function assertStringEqualsFileIgnoringCase(string $expectedFile, string $actualString, string $message = ''): void { - static::assertFileExists($expectedFile, $message); + self::assertFileExists($expectedFile, $message); $constraint = new IsEqualIgnoringCase(file_get_contents($expectedFile)); - static::assertThat($actualString, $constraint, $message); + self::assertThat($actualString, $constraint, $message); } /** * Asserts that the contents of a string is not equal * to the contents of a file. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException */ - public static function assertStringNotEqualsFile(string $expectedFile, string $actualString, string $message = ''): void + final public static function assertStringNotEqualsFile(string $expectedFile, string $actualString, string $message = ''): void { - static::assertFileExists($expectedFile, $message); + self::assertFileExists($expectedFile, $message); $constraint = new LogicalNot( new IsEqual(file_get_contents($expectedFile)), ); - static::assertThat($actualString, $constraint, $message); + self::assertThat($actualString, $constraint, $message); } /** * Asserts that the contents of a string is not equal * to the contents of a file (canonicalizing). * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException */ - public static function assertStringNotEqualsFileCanonicalizing(string $expectedFile, string $actualString, string $message = ''): void + final public static function assertStringNotEqualsFileCanonicalizing(string $expectedFile, string $actualString, string $message = ''): void { - static::assertFileExists($expectedFile, $message); + self::assertFileExists($expectedFile, $message); $constraint = new LogicalNot( new IsEqualCanonicalizing(file_get_contents($expectedFile)), ); - static::assertThat($actualString, $constraint, $message); + self::assertThat($actualString, $constraint, $message); } /** * Asserts that the contents of a string is not equal * to the contents of a file (ignoring case). * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException */ - public static function assertStringNotEqualsFileIgnoringCase(string $expectedFile, string $actualString, string $message = ''): void + final public static function assertStringNotEqualsFileIgnoringCase(string $expectedFile, string $actualString, string $message = ''): void { - static::assertFileExists($expectedFile, $message); + self::assertFileExists($expectedFile, $message); $constraint = new LogicalNot( new IsEqualIgnoringCase(file_get_contents($expectedFile)), ); - static::assertThat($actualString, $constraint, $message); + self::assertThat($actualString, $constraint, $message); } /** * Asserts that a file/dir is readable. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException */ - public static function assertIsReadable(string $filename, string $message = ''): void + final public static function assertIsReadable(string $filename, string $message = ''): void { - static::assertThat($filename, new IsReadable, $message); + self::assertThat($filename, new IsReadable, $message); } /** * Asserts that a file/dir exists and is not readable. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException */ - public static function assertIsNotReadable(string $filename, string $message = ''): void + final public static function assertIsNotReadable(string $filename, string $message = ''): void { - static::assertThat($filename, new LogicalNot(new IsReadable), $message); - } - - /** - * Asserts that a file/dir exists and is not readable. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4062 - */ - public static function assertNotIsReadable(string $filename, string $message = ''): void - { - self::createWarning('assertNotIsReadable() is deprecated and will be removed in PHPUnit 10. Refactor your code to use assertIsNotReadable() instead.'); - - static::assertThat($filename, new LogicalNot(new IsReadable), $message); + self::assertThat($filename, new LogicalNot(new IsReadable), $message); } /** * Asserts that a file/dir exists and is writable. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - */ - public static function assertIsWritable(string $filename, string $message = ''): void - { - static::assertThat($filename, new IsWritable, $message); - } - - /** - * Asserts that a file/dir exists and is not writable. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException */ - public static function assertIsNotWritable(string $filename, string $message = ''): void + final public static function assertIsWritable(string $filename, string $message = ''): void { - static::assertThat($filename, new LogicalNot(new IsWritable), $message); + self::assertThat($filename, new IsWritable, $message); } /** * Asserts that a file/dir exists and is not writable. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4065 */ - public static function assertNotIsWritable(string $filename, string $message = ''): void + final public static function assertIsNotWritable(string $filename, string $message = ''): void { - self::createWarning('assertNotIsWritable() is deprecated and will be removed in PHPUnit 10. Refactor your code to use assertIsNotWritable() instead.'); - - static::assertThat($filename, new LogicalNot(new IsWritable), $message); + self::assertThat($filename, new LogicalNot(new IsWritable), $message); } /** * Asserts that a directory exists. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - */ - public static function assertDirectoryExists(string $directory, string $message = ''): void - { - static::assertThat($directory, new DirectoryExists, $message); - } - - /** - * Asserts that a directory does not exist. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException */ - public static function assertDirectoryDoesNotExist(string $directory, string $message = ''): void + final public static function assertDirectoryExists(string $directory, string $message = ''): void { - static::assertThat($directory, new LogicalNot(new DirectoryExists), $message); + self::assertThat($directory, new DirectoryExists, $message); } /** * Asserts that a directory does not exist. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4068 */ - public static function assertDirectoryNotExists(string $directory, string $message = ''): void + final public static function assertDirectoryDoesNotExist(string $directory, string $message = ''): void { - self::createWarning('assertDirectoryNotExists() is deprecated and will be removed in PHPUnit 10. Refactor your code to use assertDirectoryDoesNotExist() instead.'); - - static::assertThat($directory, new LogicalNot(new DirectoryExists), $message); + self::assertThat($directory, new LogicalNot(new DirectoryExists), $message); } /** * Asserts that a directory exists and is readable. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException */ - public static function assertDirectoryIsReadable(string $directory, string $message = ''): void + final public static function assertDirectoryIsReadable(string $directory, string $message = ''): void { self::assertDirectoryExists($directory, $message); self::assertIsReadable($directory, $message); @@ -877,29 +1493,10 @@ public static function assertDirectoryIsReadable(string $directory, string $mess /** * Asserts that a directory exists and is not readable. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - */ - public static function assertDirectoryIsNotReadable(string $directory, string $message = ''): void - { - self::assertDirectoryExists($directory, $message); - self::assertIsNotReadable($directory, $message); - } - - /** - * Asserts that a directory exists and is not readable. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4071 */ - public static function assertDirectoryNotIsReadable(string $directory, string $message = ''): void + final public static function assertDirectoryIsNotReadable(string $directory, string $message = ''): void { - self::createWarning('assertDirectoryNotIsReadable() is deprecated and will be removed in PHPUnit 10. Refactor your code to use assertDirectoryIsNotReadable() instead.'); - self::assertDirectoryExists($directory, $message); self::assertIsNotReadable($directory, $message); } @@ -907,10 +1504,9 @@ public static function assertDirectoryNotIsReadable(string $directory, string $m /** * Asserts that a directory exists and is writable. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException */ - public static function assertDirectoryIsWritable(string $directory, string $message = ''): void + final public static function assertDirectoryIsWritable(string $directory, string $message = ''): void { self::assertDirectoryExists($directory, $message); self::assertIsWritable($directory, $message); @@ -919,29 +1515,10 @@ public static function assertDirectoryIsWritable(string $directory, string $mess /** * Asserts that a directory exists and is not writable. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - */ - public static function assertDirectoryIsNotWritable(string $directory, string $message = ''): void - { - self::assertDirectoryExists($directory, $message); - self::assertIsNotWritable($directory, $message); - } - - /** - * Asserts that a directory exists and is not writable. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4074 */ - public static function assertDirectoryNotIsWritable(string $directory, string $message = ''): void + final public static function assertDirectoryIsNotWritable(string $directory, string $message = ''): void { - self::createWarning('assertDirectoryNotIsWritable() is deprecated and will be removed in PHPUnit 10. Refactor your code to use assertDirectoryIsNotWritable() instead.'); - self::assertDirectoryExists($directory, $message); self::assertIsNotWritable($directory, $message); } @@ -949,49 +1526,29 @@ public static function assertDirectoryNotIsWritable(string $directory, string $m /** * Asserts that a file exists. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - */ - public static function assertFileExists(string $filename, string $message = ''): void - { - static::assertThat($filename, new FileExists, $message); - } - - /** - * Asserts that a file does not exist. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException */ - public static function assertFileDoesNotExist(string $filename, string $message = ''): void + final public static function assertFileExists(string $filename, string $message = ''): void { - static::assertThat($filename, new LogicalNot(new FileExists), $message); + self::assertThat($filename, new FileExists, $message); } /** * Asserts that a file does not exist. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4077 */ - public static function assertFileNotExists(string $filename, string $message = ''): void + final public static function assertFileDoesNotExist(string $filename, string $message = ''): void { - self::createWarning('assertFileNotExists() is deprecated and will be removed in PHPUnit 10. Refactor your code to use assertFileDoesNotExist() instead.'); - - static::assertThat($filename, new LogicalNot(new FileExists), $message); + self::assertThat($filename, new LogicalNot(new FileExists), $message); } /** * Asserts that a file exists and is readable. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException */ - public static function assertFileIsReadable(string $file, string $message = ''): void + final public static function assertFileIsReadable(string $file, string $message = ''): void { self::assertFileExists($file, $message); self::assertIsReadable($file, $message); @@ -1000,29 +1557,10 @@ public static function assertFileIsReadable(string $file, string $message = ''): /** * Asserts that a file exists and is not readable. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - */ - public static function assertFileIsNotReadable(string $file, string $message = ''): void - { - self::assertFileExists($file, $message); - self::assertIsNotReadable($file, $message); - } - - /** - * Asserts that a file exists and is not readable. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4080 */ - public static function assertFileNotIsReadable(string $file, string $message = ''): void + final public static function assertFileIsNotReadable(string $file, string $message = ''): void { - self::createWarning('assertFileNotIsReadable() is deprecated and will be removed in PHPUnit 10. Refactor your code to use assertFileIsNotReadable() instead.'); - self::assertFileExists($file, $message); self::assertIsNotReadable($file, $message); } @@ -1030,10 +1568,9 @@ public static function assertFileNotIsReadable(string $file, string $message = ' /** * Asserts that a file exists and is writable. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException */ - public static function assertFileIsWritable(string $file, string $message = ''): void + final public static function assertFileIsWritable(string $file, string $message = ''): void { self::assertFileExists($file, $message); self::assertIsWritable($file, $message); @@ -1042,29 +1579,10 @@ public static function assertFileIsWritable(string $file, string $message = ''): /** * Asserts that a file exists and is not writable. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - */ - public static function assertFileIsNotWritable(string $file, string $message = ''): void - { - self::assertFileExists($file, $message); - self::assertIsNotWritable($file, $message); - } - - /** - * Asserts that a file exists and is not writable. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4083 */ - public static function assertFileNotIsWritable(string $file, string $message = ''): void + final public static function assertFileIsNotWritable(string $file, string $message = ''): void { - self::createWarning('assertFileNotIsWritable() is deprecated and will be removed in PHPUnit 10. Refactor your code to use assertFileIsNotWritable() instead.'); - self::assertFileExists($file, $message); self::assertIsNotWritable($file, $message); } @@ -1072,286 +1590,103 @@ public static function assertFileNotIsWritable(string $file, string $message = ' /** * Asserts that a condition is true. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException * - * @psalm-assert true $condition + * @phpstan-assert true $condition */ - public static function assertTrue($condition, string $message = ''): void + final public static function assertTrue(mixed $condition, string $message = ''): void { - static::assertThat($condition, static::isTrue(), $message); + self::assertThat($condition, self::isTrue(), $message); } /** * Asserts that a condition is not true. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException * - * @psalm-assert !true $condition + * @phpstan-assert !true $condition */ - public static function assertNotTrue($condition, string $message = ''): void + final public static function assertNotTrue(mixed $condition, string $message = ''): void { - static::assertThat($condition, static::logicalNot(static::isTrue()), $message); + self::assertThat($condition, self::logicalNot(self::isTrue()), $message); } /** * Asserts that a condition is false. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException * - * @psalm-assert false $condition + * @phpstan-assert false $condition */ - public static function assertFalse($condition, string $message = ''): void + final public static function assertFalse(mixed $condition, string $message = ''): void { - static::assertThat($condition, static::isFalse(), $message); + self::assertThat($condition, self::isFalse(), $message); } /** * Asserts that a condition is not false. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException * - * @psalm-assert !false $condition + * @phpstan-assert !false $condition */ - public static function assertNotFalse($condition, string $message = ''): void + final public static function assertNotFalse(mixed $condition, string $message = ''): void { - static::assertThat($condition, static::logicalNot(static::isFalse()), $message); + self::assertThat($condition, self::logicalNot(self::isFalse()), $message); } /** * Asserts that a variable is null. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException * - * @psalm-assert null $actual + * @phpstan-assert null $actual */ - public static function assertNull($actual, string $message = ''): void + final public static function assertNull(mixed $actual, string $message = ''): void { - static::assertThat($actual, static::isNull(), $message); + self::assertThat($actual, self::isNull(), $message); } /** * Asserts that a variable is not null. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException * - * @psalm-assert !null $actual + * @phpstan-assert !null $actual */ - public static function assertNotNull($actual, string $message = ''): void + final public static function assertNotNull(mixed $actual, string $message = ''): void { - static::assertThat($actual, static::logicalNot(static::isNull()), $message); + self::assertThat($actual, self::logicalNot(self::isNull()), $message); } /** * Asserts that a variable is finite. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException */ - public static function assertFinite($actual, string $message = ''): void + final public static function assertFinite(mixed $actual, string $message = ''): void { - static::assertThat($actual, static::isFinite(), $message); + self::assertThat($actual, self::isFinite(), $message); } /** * Asserts that a variable is infinite. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException */ - public static function assertInfinite($actual, string $message = ''): void + final public static function assertInfinite(mixed $actual, string $message = ''): void { - static::assertThat($actual, static::isInfinite(), $message); + self::assertThat($actual, self::isInfinite(), $message); } /** * Asserts that a variable is nan. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - */ - public static function assertNan($actual, string $message = ''): void - { - static::assertThat($actual, static::isNan(), $message); - } - - /** - * Asserts that a class has a specified attribute. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * @throws ExpectationFailedException - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4601 - */ - public static function assertClassHasAttribute(string $attributeName, string $className, string $message = ''): void - { - self::createWarning('assertClassHasAttribute() is deprecated and will be removed in PHPUnit 10.'); - - if (!self::isValidClassAttributeName($attributeName)) { - throw InvalidArgumentException::create(1, 'valid attribute name'); - } - - if (!class_exists($className)) { - throw InvalidArgumentException::create(2, 'class name'); - } - - static::assertThat($className, new ClassHasAttribute($attributeName), $message); - } - - /** - * Asserts that a class does not have a specified attribute. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * @throws ExpectationFailedException - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4601 - */ - public static function assertClassNotHasAttribute(string $attributeName, string $className, string $message = ''): void - { - self::createWarning('assertClassNotHasAttribute() is deprecated and will be removed in PHPUnit 10.'); - - if (!self::isValidClassAttributeName($attributeName)) { - throw InvalidArgumentException::create(1, 'valid attribute name'); - } - - if (!class_exists($className)) { - throw InvalidArgumentException::create(2, 'class name'); - } - - static::assertThat( - $className, - new LogicalNot( - new ClassHasAttribute($attributeName), - ), - $message, - ); - } - - /** - * Asserts that a class has a specified static attribute. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * @throws ExpectationFailedException - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4601 - */ - public static function assertClassHasStaticAttribute(string $attributeName, string $className, string $message = ''): void - { - self::createWarning('assertClassHasStaticAttribute() is deprecated and will be removed in PHPUnit 10.'); - - if (!self::isValidClassAttributeName($attributeName)) { - throw InvalidArgumentException::create(1, 'valid attribute name'); - } - - if (!class_exists($className)) { - throw InvalidArgumentException::create(2, 'class name'); - } - - static::assertThat( - $className, - new ClassHasStaticAttribute($attributeName), - $message, - ); - } - - /** - * Asserts that a class does not have a specified static attribute. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * @throws ExpectationFailedException - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4601 - */ - public static function assertClassNotHasStaticAttribute(string $attributeName, string $className, string $message = ''): void - { - self::createWarning('assertClassNotHasStaticAttribute() is deprecated and will be removed in PHPUnit 10.'); - - if (!self::isValidClassAttributeName($attributeName)) { - throw InvalidArgumentException::create(1, 'valid attribute name'); - } - - if (!class_exists($className)) { - throw InvalidArgumentException::create(2, 'class name'); - } - - static::assertThat( - $className, - new LogicalNot( - new ClassHasStaticAttribute($attributeName), - ), - $message, - ); - } - - /** - * Asserts that an object has a specified attribute. - * - * @param object $object - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * @throws ExpectationFailedException - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4601 - */ - public static function assertObjectHasAttribute(string $attributeName, $object, string $message = ''): void - { - self::createWarning('assertObjectHasAttribute() is deprecated and will be removed in PHPUnit 10. Refactor your test to use assertObjectHasProperty() instead.'); - - if (!self::isValidObjectAttributeName($attributeName)) { - throw InvalidArgumentException::create(1, 'valid attribute name'); - } - - if (!is_object($object)) { - throw InvalidArgumentException::create(2, 'object'); - } - - static::assertThat( - $object, - new ObjectHasAttribute($attributeName), - $message, - ); - } - - /** - * Asserts that an object does not have a specified attribute. - * - * @param object $object - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception * @throws ExpectationFailedException - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4601 */ - public static function assertObjectNotHasAttribute(string $attributeName, $object, string $message = ''): void + final public static function assertNan(mixed $actual, string $message = ''): void { - self::createWarning('assertObjectNotHasAttribute() is deprecated and will be removed in PHPUnit 10. Refactor your test to use assertObjectNotHasProperty() instead.'); - - if (!self::isValidObjectAttributeName($attributeName)) { - throw InvalidArgumentException::create(1, 'valid attribute name'); - } - - if (!is_object($object)) { - throw InvalidArgumentException::create(2, 'object'); - } - - static::assertThat( - $object, - new LogicalNot( - new ObjectHasAttribute($attributeName), - ), - $message, - ); + self::assertThat($actual, self::isNan(), $message); } /** @@ -1361,7 +1696,7 @@ public static function assertObjectNotHasAttribute(string $attributeName, $objec */ final public static function assertObjectHasProperty(string $propertyName, object $object, string $message = ''): void { - static::assertThat( + self::assertThat( $object, new ObjectHasProperty($propertyName), $message, @@ -1375,7 +1710,7 @@ final public static function assertObjectHasProperty(string $propertyName, objec */ final public static function assertObjectNotHasProperty(string $propertyName, object $object, string $message = ''): void { - static::assertThat( + self::assertThat( $object, new LogicalNot( new ObjectHasProperty($propertyName), @@ -1389,18 +1724,17 @@ final public static function assertObjectNotHasProperty(string $propertyName, ob * Used on objects, it asserts that two variables reference * the same object. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException + * @template ExpectedType * - * @psalm-template ExpectedType + * @param ExpectedType $expected * - * @psalm-param ExpectedType $expected + * @throws ExpectationFailedException * - * @psalm-assert =ExpectedType $actual + * @phpstan-assert =ExpectedType $actual */ - public static function assertSame($expected, $actual, string $message = ''): void + final public static function assertSame(mixed $expected, mixed $actual, string $message = ''): void { - static::assertThat( + self::assertThat( $actual, new IsIdentical($expected), $message, @@ -1412,16 +1746,15 @@ public static function assertSame($expected, $actual, string $message = ''): voi * Used on objects, it asserts that two variables do not reference * the same object. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException */ - public static function assertNotSame($expected, $actual, string $message = ''): void + final public static function assertNotSame(mixed $expected, mixed $actual, string $message = ''): void { if (is_bool($expected) && is_bool($actual)) { - static::assertNotEquals($expected, $actual, $message); + self::assertNotEquals($expected, $actual, $message); } - static::assertThat( + self::assertThat( $actual, new LogicalNot( new IsIdentical($expected), @@ -1433,23 +1766,23 @@ public static function assertNotSame($expected, $actual, string $message = ''): /** * Asserts that a variable is of a given type. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * @throws ExpectationFailedException + * @template ExpectedType of object * - * @psalm-template ExpectedType of object + * @param class-string $expected * - * @psalm-param class-string $expected + * @throws Exception + * @throws ExpectationFailedException + * @throws UnknownClassOrInterfaceException * - * @psalm-assert =ExpectedType $actual + * @phpstan-assert =ExpectedType $actual */ - public static function assertInstanceOf(string $expected, $actual, string $message = ''): void + final public static function assertInstanceOf(string $expected, mixed $actual, string $message = ''): void { if (!class_exists($expected) && !interface_exists($expected)) { - throw InvalidArgumentException::create(1, 'class or interface name'); + throw new UnknownClassOrInterfaceException($expected); } - static::assertThat( + self::assertThat( $actual, new IsInstanceOf($expected), $message, @@ -1459,23 +1792,22 @@ public static function assertInstanceOf(string $expected, $actual, string $messa /** * Asserts that a variable is not of a given type. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * @throws ExpectationFailedException + * @template ExpectedType of object * - * @psalm-template ExpectedType of object + * @param class-string $expected * - * @psalm-param class-string $expected + * @throws Exception + * @throws ExpectationFailedException * - * @psalm-assert !ExpectedType $actual + * @phpstan-assert !ExpectedType $actual */ - public static function assertNotInstanceOf(string $expected, $actual, string $message = ''): void + final public static function assertNotInstanceOf(string $expected, mixed $actual, string $message = ''): void { if (!class_exists($expected) && !interface_exists($expected)) { - throw InvalidArgumentException::create(1, 'class or interface name'); + throw new UnknownClassOrInterfaceException($expected); } - static::assertThat( + self::assertThat( $actual, new LogicalNot( new IsInstanceOf($expected), @@ -1487,16 +1819,16 @@ public static function assertNotInstanceOf(string $expected, $actual, string $me /** * Asserts that a variable is of type array. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception * @throws ExpectationFailedException * - * @psalm-assert array $actual + * @phpstan-assert array $actual */ - public static function assertIsArray($actual, string $message = ''): void + final public static function assertIsArray(mixed $actual, string $message = ''): void { - static::assertThat( + self::assertThat( $actual, - new IsType(IsType::TYPE_ARRAY), + new IsType(NativeType::Array), $message, ); } @@ -1504,16 +1836,16 @@ public static function assertIsArray($actual, string $message = ''): void /** * Asserts that a variable is of type bool. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception * @throws ExpectationFailedException * - * @psalm-assert bool $actual + * @phpstan-assert bool $actual */ - public static function assertIsBool($actual, string $message = ''): void + final public static function assertIsBool(mixed $actual, string $message = ''): void { - static::assertThat( + self::assertThat( $actual, - new IsType(IsType::TYPE_BOOL), + new IsType(NativeType::Bool), $message, ); } @@ -1521,16 +1853,16 @@ public static function assertIsBool($actual, string $message = ''): void /** * Asserts that a variable is of type float. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception * @throws ExpectationFailedException * - * @psalm-assert float $actual + * @phpstan-assert float $actual */ - public static function assertIsFloat($actual, string $message = ''): void + final public static function assertIsFloat(mixed $actual, string $message = ''): void { - static::assertThat( + self::assertThat( $actual, - new IsType(IsType::TYPE_FLOAT), + new IsType(NativeType::Float), $message, ); } @@ -1538,16 +1870,16 @@ public static function assertIsFloat($actual, string $message = ''): void /** * Asserts that a variable is of type int. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception * @throws ExpectationFailedException * - * @psalm-assert int $actual + * @phpstan-assert int $actual */ - public static function assertIsInt($actual, string $message = ''): void + final public static function assertIsInt(mixed $actual, string $message = ''): void { - static::assertThat( + self::assertThat( $actual, - new IsType(IsType::TYPE_INT), + new IsType(NativeType::Int), $message, ); } @@ -1555,16 +1887,16 @@ public static function assertIsInt($actual, string $message = ''): void /** * Asserts that a variable is of type numeric. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception * @throws ExpectationFailedException * - * @psalm-assert numeric $actual + * @phpstan-assert numeric $actual */ - public static function assertIsNumeric($actual, string $message = ''): void + final public static function assertIsNumeric(mixed $actual, string $message = ''): void { - static::assertThat( + self::assertThat( $actual, - new IsType(IsType::TYPE_NUMERIC), + new IsType(NativeType::Numeric), $message, ); } @@ -1572,16 +1904,16 @@ public static function assertIsNumeric($actual, string $message = ''): void /** * Asserts that a variable is of type object. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception * @throws ExpectationFailedException * - * @psalm-assert object $actual + * @phpstan-assert object $actual */ - public static function assertIsObject($actual, string $message = ''): void + final public static function assertIsObject(mixed $actual, string $message = ''): void { - static::assertThat( + self::assertThat( $actual, - new IsType(IsType::TYPE_OBJECT), + new IsType(NativeType::Object), $message, ); } @@ -1589,16 +1921,16 @@ public static function assertIsObject($actual, string $message = ''): void /** * Asserts that a variable is of type resource. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception * @throws ExpectationFailedException * - * @psalm-assert resource $actual + * @phpstan-assert resource $actual */ - public static function assertIsResource($actual, string $message = ''): void + final public static function assertIsResource(mixed $actual, string $message = ''): void { - static::assertThat( + self::assertThat( $actual, - new IsType(IsType::TYPE_RESOURCE), + new IsType(NativeType::Resource), $message, ); } @@ -1606,16 +1938,16 @@ public static function assertIsResource($actual, string $message = ''): void /** * Asserts that a variable is of type resource and is closed. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception * @throws ExpectationFailedException * - * @psalm-assert resource $actual + * @phpstan-assert resource $actual */ - public static function assertIsClosedResource($actual, string $message = ''): void + final public static function assertIsClosedResource(mixed $actual, string $message = ''): void { - static::assertThat( + self::assertThat( $actual, - new IsType(IsType::TYPE_CLOSED_RESOURCE), + new IsType(NativeType::ClosedResource), $message, ); } @@ -1623,16 +1955,16 @@ public static function assertIsClosedResource($actual, string $message = ''): vo /** * Asserts that a variable is of type string. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception * @throws ExpectationFailedException * - * @psalm-assert string $actual + * @phpstan-assert string $actual */ - public static function assertIsString($actual, string $message = ''): void + final public static function assertIsString(mixed $actual, string $message = ''): void { - static::assertThat( + self::assertThat( $actual, - new IsType(IsType::TYPE_STRING), + new IsType(NativeType::String), $message, ); } @@ -1640,16 +1972,16 @@ public static function assertIsString($actual, string $message = ''): void /** * Asserts that a variable is of type scalar. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception * @throws ExpectationFailedException * - * @psalm-assert scalar $actual + * @phpstan-assert scalar $actual */ - public static function assertIsScalar($actual, string $message = ''): void + final public static function assertIsScalar(mixed $actual, string $message = ''): void { - static::assertThat( + self::assertThat( $actual, - new IsType(IsType::TYPE_SCALAR), + new IsType(NativeType::Scalar), $message, ); } @@ -1657,16 +1989,16 @@ public static function assertIsScalar($actual, string $message = ''): void /** * Asserts that a variable is of type callable. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception * @throws ExpectationFailedException * - * @psalm-assert callable $actual + * @phpstan-assert callable $actual */ - public static function assertIsCallable($actual, string $message = ''): void + final public static function assertIsCallable(mixed $actual, string $message = ''): void { - static::assertThat( + self::assertThat( $actual, - new IsType(IsType::TYPE_CALLABLE), + new IsType(NativeType::Callable), $message, ); } @@ -1674,16 +2006,16 @@ public static function assertIsCallable($actual, string $message = ''): void /** * Asserts that a variable is of type iterable. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception * @throws ExpectationFailedException * - * @psalm-assert iterable $actual + * @phpstan-assert iterable $actual */ - public static function assertIsIterable($actual, string $message = ''): void + final public static function assertIsIterable(mixed $actual, string $message = ''): void { - static::assertThat( + self::assertThat( $actual, - new IsType(IsType::TYPE_ITERABLE), + new IsType(NativeType::Iterable), $message, ); } @@ -1691,16 +2023,16 @@ public static function assertIsIterable($actual, string $message = ''): void /** * Asserts that a variable is not of type array. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception * @throws ExpectationFailedException * - * @psalm-assert !array $actual + * @phpstan-assert !array $actual */ - public static function assertIsNotArray($actual, string $message = ''): void + final public static function assertIsNotArray(mixed $actual, string $message = ''): void { - static::assertThat( + self::assertThat( $actual, - new LogicalNot(new IsType(IsType::TYPE_ARRAY)), + new LogicalNot(new IsType(NativeType::Array)), $message, ); } @@ -1708,16 +2040,16 @@ public static function assertIsNotArray($actual, string $message = ''): void /** * Asserts that a variable is not of type bool. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception * @throws ExpectationFailedException * - * @psalm-assert !bool $actual + * @phpstan-assert !bool $actual */ - public static function assertIsNotBool($actual, string $message = ''): void + final public static function assertIsNotBool(mixed $actual, string $message = ''): void { - static::assertThat( + self::assertThat( $actual, - new LogicalNot(new IsType(IsType::TYPE_BOOL)), + new LogicalNot(new IsType(NativeType::Bool)), $message, ); } @@ -1725,16 +2057,16 @@ public static function assertIsNotBool($actual, string $message = ''): void /** * Asserts that a variable is not of type float. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception * @throws ExpectationFailedException * - * @psalm-assert !float $actual + * @phpstan-assert !float $actual */ - public static function assertIsNotFloat($actual, string $message = ''): void + final public static function assertIsNotFloat(mixed $actual, string $message = ''): void { - static::assertThat( + self::assertThat( $actual, - new LogicalNot(new IsType(IsType::TYPE_FLOAT)), + new LogicalNot(new IsType(NativeType::Float)), $message, ); } @@ -1742,16 +2074,16 @@ public static function assertIsNotFloat($actual, string $message = ''): void /** * Asserts that a variable is not of type int. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception * @throws ExpectationFailedException * - * @psalm-assert !int $actual + * @phpstan-assert !int $actual */ - public static function assertIsNotInt($actual, string $message = ''): void + final public static function assertIsNotInt(mixed $actual, string $message = ''): void { - static::assertThat( + self::assertThat( $actual, - new LogicalNot(new IsType(IsType::TYPE_INT)), + new LogicalNot(new IsType(NativeType::Int)), $message, ); } @@ -1759,16 +2091,16 @@ public static function assertIsNotInt($actual, string $message = ''): void /** * Asserts that a variable is not of type numeric. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception * @throws ExpectationFailedException * - * @psalm-assert !numeric $actual + * @phpstan-assert !numeric $actual */ - public static function assertIsNotNumeric($actual, string $message = ''): void + final public static function assertIsNotNumeric(mixed $actual, string $message = ''): void { - static::assertThat( + self::assertThat( $actual, - new LogicalNot(new IsType(IsType::TYPE_NUMERIC)), + new LogicalNot(new IsType(NativeType::Numeric)), $message, ); } @@ -1776,16 +2108,16 @@ public static function assertIsNotNumeric($actual, string $message = ''): void /** * Asserts that a variable is not of type object. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception * @throws ExpectationFailedException * - * @psalm-assert !object $actual + * @phpstan-assert !object $actual */ - public static function assertIsNotObject($actual, string $message = ''): void + final public static function assertIsNotObject(mixed $actual, string $message = ''): void { - static::assertThat( + self::assertThat( $actual, - new LogicalNot(new IsType(IsType::TYPE_OBJECT)), + new LogicalNot(new IsType(NativeType::Object)), $message, ); } @@ -1793,16 +2125,16 @@ public static function assertIsNotObject($actual, string $message = ''): void /** * Asserts that a variable is not of type resource. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception * @throws ExpectationFailedException * - * @psalm-assert !resource $actual + * @phpstan-assert !resource $actual */ - public static function assertIsNotResource($actual, string $message = ''): void + final public static function assertIsNotResource(mixed $actual, string $message = ''): void { - static::assertThat( + self::assertThat( $actual, - new LogicalNot(new IsType(IsType::TYPE_RESOURCE)), + new LogicalNot(new IsType(NativeType::Resource)), $message, ); } @@ -1810,16 +2142,16 @@ public static function assertIsNotResource($actual, string $message = ''): void /** * Asserts that a variable is not of type resource. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception * @throws ExpectationFailedException * - * @psalm-assert !resource $actual + * @phpstan-assert !resource $actual */ - public static function assertIsNotClosedResource($actual, string $message = ''): void + final public static function assertIsNotClosedResource(mixed $actual, string $message = ''): void { - static::assertThat( + self::assertThat( $actual, - new LogicalNot(new IsType(IsType::TYPE_CLOSED_RESOURCE)), + new LogicalNot(new IsType(NativeType::ClosedResource)), $message, ); } @@ -1827,16 +2159,16 @@ public static function assertIsNotClosedResource($actual, string $message = ''): /** * Asserts that a variable is not of type string. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception * @throws ExpectationFailedException * - * @psalm-assert !string $actual + * @phpstan-assert !string $actual */ - public static function assertIsNotString($actual, string $message = ''): void + final public static function assertIsNotString(mixed $actual, string $message = ''): void { - static::assertThat( + self::assertThat( $actual, - new LogicalNot(new IsType(IsType::TYPE_STRING)), + new LogicalNot(new IsType(NativeType::String)), $message, ); } @@ -1844,16 +2176,16 @@ public static function assertIsNotString($actual, string $message = ''): void /** * Asserts that a variable is not of type scalar. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception * @throws ExpectationFailedException * - * @psalm-assert !scalar $actual + * @phpstan-assert !scalar $actual */ - public static function assertIsNotScalar($actual, string $message = ''): void + final public static function assertIsNotScalar(mixed $actual, string $message = ''): void { - static::assertThat( + self::assertThat( $actual, - new LogicalNot(new IsType(IsType::TYPE_SCALAR)), + new LogicalNot(new IsType(NativeType::Scalar)), $message, ); } @@ -1861,16 +2193,16 @@ public static function assertIsNotScalar($actual, string $message = ''): void /** * Asserts that a variable is not of type callable. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception * @throws ExpectationFailedException * - * @psalm-assert !callable $actual + * @phpstan-assert !callable $actual */ - public static function assertIsNotCallable($actual, string $message = ''): void + final public static function assertIsNotCallable(mixed $actual, string $message = ''): void { - static::assertThat( + self::assertThat( $actual, - new LogicalNot(new IsType(IsType::TYPE_CALLABLE)), + new LogicalNot(new IsType(NativeType::Callable)), $message, ); } @@ -1878,80 +2210,38 @@ public static function assertIsNotCallable($actual, string $message = ''): void /** * Asserts that a variable is not of type iterable. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - * - * @psalm-assert !iterable $actual - */ - public static function assertIsNotIterable($actual, string $message = ''): void - { - static::assertThat( - $actual, - new LogicalNot(new IsType(IsType::TYPE_ITERABLE)), - $message, - ); - } - - /** - * Asserts that a string matches a given regular expression. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - */ - public static function assertMatchesRegularExpression(string $pattern, string $string, string $message = ''): void - { - static::assertThat($string, new RegularExpression($pattern), $message); - } - - /** - * Asserts that a string matches a given regular expression. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @throws Exception * @throws ExpectationFailedException * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4086 + * @phpstan-assert !iterable $actual */ - public static function assertRegExp(string $pattern, string $string, string $message = ''): void + final public static function assertIsNotIterable(mixed $actual, string $message = ''): void { - self::createWarning('assertRegExp() is deprecated and will be removed in PHPUnit 10. Refactor your code to use assertMatchesRegularExpression() instead.'); - - static::assertThat($string, new RegularExpression($pattern), $message); + self::assertThat( + $actual, + new LogicalNot(new IsType(NativeType::Iterable)), + $message, + ); } /** - * Asserts that a string does not match a given regular expression. + * Asserts that a string matches a given regular expression. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException */ - public static function assertDoesNotMatchRegularExpression(string $pattern, string $string, string $message = ''): void + final public static function assertMatchesRegularExpression(string $pattern, string $string, string $message = ''): void { - static::assertThat( - $string, - new LogicalNot( - new RegularExpression($pattern), - ), - $message, - ); + self::assertThat($string, new RegularExpression($pattern), $message); } /** * Asserts that a string does not match a given regular expression. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4089 */ - public static function assertNotRegExp(string $pattern, string $string, string $message = ''): void + final public static function assertDoesNotMatchRegularExpression(string $pattern, string $string, string $message = ''): void { - self::createWarning('assertNotRegExp() is deprecated and will be removed in PHPUnit 10. Refactor your code to use assertDoesNotMatchRegularExpression() instead.'); - - static::assertThat( + self::assertThat( $string, new LogicalNot( new RegularExpression($pattern), @@ -1964,32 +2254,24 @@ public static function assertNotRegExp(string $pattern, string $string, string $ * Assert that the size of two arrays (or `Countable` or `Traversable` objects) * is the same. * - * @param Countable|iterable $expected - * @param Countable|iterable $actual + * @param Countable|iterable $expected + * @param Countable|iterable $actual * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws Exception * @throws ExpectationFailedException + * @throws GeneratorNotSupportedException */ - public static function assertSameSize($expected, $actual, string $message = ''): void + final public static function assertSameSize(Countable|iterable $expected, Countable|iterable $actual, string $message = ''): void { if ($expected instanceof Generator) { - self::createWarning('Passing an argument of type Generator for the $expected parameter is deprecated. Support for this will be removed in PHPUnit 10.'); + throw GeneratorNotSupportedException::fromParameterName('$expected'); } if ($actual instanceof Generator) { - self::createWarning('Passing an argument of type Generator for the $actual parameter is deprecated. Support for this will be removed in PHPUnit 10.'); - } - - if (!$expected instanceof Countable && !is_iterable($expected)) { - throw InvalidArgumentException::create(1, 'countable or iterable'); + throw GeneratorNotSupportedException::fromParameterName('$actual'); } - if (!$actual instanceof Countable && !is_iterable($actual)) { - throw InvalidArgumentException::create(2, 'countable or iterable'); - } - - static::assertThat( + self::assertThat( $actual, new SameSize($expected), $message, @@ -2000,32 +2282,24 @@ public static function assertSameSize($expected, $actual, string $message = ''): * Assert that the size of two arrays (or `Countable` or `Traversable` objects) * is not the same. * - * @param Countable|iterable $expected - * @param Countable|iterable $actual + * @param Countable|iterable $expected + * @param Countable|iterable $actual * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws Exception * @throws ExpectationFailedException + * @throws GeneratorNotSupportedException */ - public static function assertNotSameSize($expected, $actual, string $message = ''): void + final public static function assertNotSameSize(Countable|iterable $expected, Countable|iterable $actual, string $message = ''): void { if ($expected instanceof Generator) { - self::createWarning('Passing an argument of type Generator for the $expected parameter is deprecated. Support for this will be removed in PHPUnit 10.'); + throw GeneratorNotSupportedException::fromParameterName('$expected'); } if ($actual instanceof Generator) { - self::createWarning('Passing an argument of type Generator for the $actual parameter is deprecated. Support for this will be removed in PHPUnit 10.'); - } - - if (!$expected instanceof Countable && !is_iterable($expected)) { - throw InvalidArgumentException::create(1, 'countable or iterable'); + throw GeneratorNotSupportedException::fromParameterName('$actual'); } - if (!$actual instanceof Countable && !is_iterable($actual)) { - throw InvalidArgumentException::create(2, 'countable or iterable'); - } - - static::assertThat( + self::assertThat( $actual, new LogicalNot( new SameSize($expected), @@ -2035,68 +2309,87 @@ public static function assertNotSameSize($expected, $actual, string $message = ' } /** - * Asserts that a string matches a given format string. + * @throws ExpectationFailedException + */ + final public static function assertStringContainsStringIgnoringLineEndings(string $needle, string $haystack, string $message = ''): void + { + self::assertThat($haystack, new StringContains($needle, false, true), $message); + } + + /** + * Asserts that two strings are equal except for line endings. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException */ - public static function assertStringMatchesFormat(string $format, string $string, string $message = ''): void + final public static function assertStringEqualsStringIgnoringLineEndings(string $expected, string $actual, string $message = ''): void { - static::assertThat($string, new StringMatchesFormatDescription($format), $message); + self::assertThat($actual, new StringEqualsStringIgnoringLineEndings($expected), $message); } /** - * Asserts that a string does not match a given format string. + * Asserts that a string matches a given format string. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException */ - public static function assertStringNotMatchesFormat(string $format, string $string, string $message = ''): void + final public static function assertFileMatchesFormat(string $format, string $actualFile, string $message = ''): void { - static::assertThat( - $string, - new LogicalNot( - new StringMatchesFormatDescription($format), - ), + self::assertFileExists($actualFile, $message); + + self::assertThat( + file_get_contents($actualFile), + new StringMatchesFormatDescription($format), $message, ); } /** - * Asserts that a string matches a given format file. + * Asserts that a string matches a given format string. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException */ - public static function assertStringMatchesFormatFile(string $formatFile, string $string, string $message = ''): void + final public static function assertFileMatchesFormatFile(string $formatFile, string $actualFile, string $message = ''): void { - static::assertFileExists($formatFile, $message); + self::assertFileExists($formatFile, $message); + self::assertFileExists($actualFile, $message); - static::assertThat( - $string, - new StringMatchesFormatDescription( - file_get_contents($formatFile), - ), + $formatDescription = file_get_contents($formatFile); + + self::assertIsString($formatDescription); + + self::assertThat( + file_get_contents($actualFile), + new StringMatchesFormatDescription($formatDescription), $message, ); } /** - * Asserts that a string does not match a given format string. + * Asserts that a string matches a given format string. + * + * @throws ExpectationFailedException + */ + final public static function assertStringMatchesFormat(string $format, string $string, string $message = ''): void + { + self::assertThat($string, new StringMatchesFormatDescription($format), $message); + } + + /** + * Asserts that a string matches a given format file. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException */ - public static function assertStringNotMatchesFormatFile(string $formatFile, string $string, string $message = ''): void + final public static function assertStringMatchesFormatFile(string $formatFile, string $string, string $message = ''): void { - static::assertFileExists($formatFile, $message); + self::assertFileExists($formatFile, $message); + + $formatDescription = file_get_contents($formatFile); + + self::assertIsString($formatDescription); - static::assertThat( + self::assertThat( $string, - new LogicalNot( - new StringMatchesFormatDescription( - file_get_contents($formatFile), - ), + new StringMatchesFormatDescription( + $formatDescription, ), $message, ); @@ -2105,26 +2398,27 @@ public static function assertStringNotMatchesFormatFile(string $formatFile, stri /** * Asserts that a string starts with a given prefix. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @param non-empty-string $prefix + * * @throws ExpectationFailedException + * @throws InvalidArgumentException */ - public static function assertStringStartsWith(string $prefix, string $string, string $message = ''): void + final public static function assertStringStartsWith(string $prefix, string $string, string $message = ''): void { - static::assertThat($string, new StringStartsWith($prefix), $message); + self::assertThat($string, new StringStartsWith($prefix), $message); } /** * Asserts that a string starts not with a given prefix. * - * @param string $prefix - * @param string $string + * @param non-empty-string $prefix * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException + * @throws InvalidArgumentException */ - public static function assertStringStartsNotWith($prefix, $string, string $message = ''): void + final public static function assertStringStartsNotWith(string $prefix, string $string, string $message = ''): void { - static::assertThat( + self::assertThat( $string, new LogicalNot( new StringStartsWith($prefix), @@ -2134,69 +2428,69 @@ public static function assertStringStartsNotWith($prefix, $string, string $messa } /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException */ - public static function assertStringContainsString(string $needle, string $haystack, string $message = ''): void + final public static function assertStringContainsString(string $needle, string $haystack, string $message = ''): void { - $constraint = new StringContains($needle, false); + $constraint = new StringContains($needle); - static::assertThat($haystack, $constraint, $message); + self::assertThat($haystack, $constraint, $message); } /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException */ - public static function assertStringContainsStringIgnoringCase(string $needle, string $haystack, string $message = ''): void + final public static function assertStringContainsStringIgnoringCase(string $needle, string $haystack, string $message = ''): void { $constraint = new StringContains($needle, true); - static::assertThat($haystack, $constraint, $message); + self::assertThat($haystack, $constraint, $message); } /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException */ - public static function assertStringNotContainsString(string $needle, string $haystack, string $message = ''): void + final public static function assertStringNotContainsString(string $needle, string $haystack, string $message = ''): void { $constraint = new LogicalNot(new StringContains($needle)); - static::assertThat($haystack, $constraint, $message); + self::assertThat($haystack, $constraint, $message); } /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException */ - public static function assertStringNotContainsStringIgnoringCase(string $needle, string $haystack, string $message = ''): void + final public static function assertStringNotContainsStringIgnoringCase(string $needle, string $haystack, string $message = ''): void { $constraint = new LogicalNot(new StringContains($needle, true)); - static::assertThat($haystack, $constraint, $message); + self::assertThat($haystack, $constraint, $message); } /** * Asserts that a string ends with a given suffix. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @param non-empty-string $suffix + * * @throws ExpectationFailedException + * @throws InvalidArgumentException */ - public static function assertStringEndsWith(string $suffix, string $string, string $message = ''): void + final public static function assertStringEndsWith(string $suffix, string $string, string $message = ''): void { - static::assertThat($string, new StringEndsWith($suffix), $message); + self::assertThat($string, new StringEndsWith($suffix), $message); } /** * Asserts that a string ends not with a given suffix. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException + * @param non-empty-string $suffix + * * @throws ExpectationFailedException + * @throws InvalidArgumentException */ - public static function assertStringEndsNotWith(string $suffix, string $string, string $message = ''): void + final public static function assertStringEndsNotWith(string $suffix, string $string, string $message = ''): void { - static::assertThat( + self::assertThat( $string, new LogicalNot( new StringEndsWith($suffix), @@ -2208,230 +2502,94 @@ public static function assertStringEndsNotWith(string $suffix, string $string, s /** * Asserts that two XML files are equal. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws Exception * @throws ExpectationFailedException + * @throws XmlException */ - public static function assertXmlFileEqualsXmlFile(string $expectedFile, string $actualFile, string $message = ''): void + final public static function assertXmlFileEqualsXmlFile(string $expectedFile, string $actualFile, string $message = ''): void { $expected = (new XmlLoader)->loadFile($expectedFile); $actual = (new XmlLoader)->loadFile($actualFile); - static::assertEquals($expected, $actual, $message); + self::assertEquals($expected, $actual, $message); } /** * Asserts that two XML files are not equal. * * @throws \PHPUnit\Util\Exception - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException */ - public static function assertXmlFileNotEqualsXmlFile(string $expectedFile, string $actualFile, string $message = ''): void + final public static function assertXmlFileNotEqualsXmlFile(string $expectedFile, string $actualFile, string $message = ''): void { $expected = (new XmlLoader)->loadFile($expectedFile); $actual = (new XmlLoader)->loadFile($actualFile); - static::assertNotEquals($expected, $actual, $message); + self::assertNotEquals($expected, $actual, $message); } /** * Asserts that two XML documents are equal. * - * @param DOMDocument|string $actualXml - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException - * @throws Xml\Exception + * @throws XmlException */ - public static function assertXmlStringEqualsXmlFile(string $expectedFile, $actualXml, string $message = ''): void + final public static function assertXmlStringEqualsXmlFile(string $expectedFile, string $actualXml, string $message = ''): void { - if (!is_string($actualXml)) { - self::createWarning('Passing an argument of type DOMDocument for the $actualXml parameter is deprecated. Support for this will be removed in PHPUnit 10.'); - - $actual = $actualXml; - } else { - $actual = (new XmlLoader)->load($actualXml); - } - $expected = (new XmlLoader)->loadFile($expectedFile); + $actual = (new XmlLoader)->load($actualXml); - static::assertEquals($expected, $actual, $message); + self::assertEquals($expected, $actual, $message); } /** * Asserts that two XML documents are not equal. * - * @param DOMDocument|string $actualXml - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException - * @throws Xml\Exception + * @throws XmlException */ - public static function assertXmlStringNotEqualsXmlFile(string $expectedFile, $actualXml, string $message = ''): void + final public static function assertXmlStringNotEqualsXmlFile(string $expectedFile, string $actualXml, string $message = ''): void { - if (!is_string($actualXml)) { - self::createWarning('Passing an argument of type DOMDocument for the $actualXml parameter is deprecated. Support for this will be removed in PHPUnit 10.'); - - $actual = $actualXml; - } else { - $actual = (new XmlLoader)->load($actualXml); - } - $expected = (new XmlLoader)->loadFile($expectedFile); + $actual = (new XmlLoader)->load($actualXml); - static::assertNotEquals($expected, $actual, $message); + self::assertNotEquals($expected, $actual, $message); } /** * Asserts that two XML documents are equal. * - * @param DOMDocument|string $expectedXml - * @param DOMDocument|string $actualXml - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException - * @throws Xml\Exception + * @throws XmlException */ - public static function assertXmlStringEqualsXmlString($expectedXml, $actualXml, string $message = ''): void + final public static function assertXmlStringEqualsXmlString(string $expectedXml, string $actualXml, string $message = ''): void { - if (!is_string($expectedXml)) { - self::createWarning('Passing an argument of type DOMDocument for the $expectedXml parameter is deprecated. Support for this will be removed in PHPUnit 10.'); - - $expected = $expectedXml; - } else { - $expected = (new XmlLoader)->load($expectedXml); - } + $expected = (new XmlLoader)->load($expectedXml); + $actual = (new XmlLoader)->load($actualXml); - if (!is_string($actualXml)) { - self::createWarning('Passing an argument of type DOMDocument for the $actualXml parameter is deprecated. Support for this will be removed in PHPUnit 10.'); - - $actual = $actualXml; - } else { - $actual = (new XmlLoader)->load($actualXml); - } - - static::assertEquals($expected, $actual, $message); + self::assertEquals($expected, $actual, $message); } /** * Asserts that two XML documents are not equal. * - * @param DOMDocument|string $expectedXml - * @param DOMDocument|string $actualXml - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - * @throws Xml\Exception - */ - public static function assertXmlStringNotEqualsXmlString($expectedXml, $actualXml, string $message = ''): void - { - if (!is_string($expectedXml)) { - self::createWarning('Passing an argument of type DOMDocument for the $expectedXml parameter is deprecated. Support for this will be removed in PHPUnit 10.'); - - $expected = $expectedXml; - } else { - $expected = (new XmlLoader)->load($expectedXml); - } - - if (!is_string($actualXml)) { - self::createWarning('Passing an argument of type DOMDocument for the $actualXml parameter is deprecated. Support for this will be removed in PHPUnit 10.'); - - $actual = $actualXml; - } else { - $actual = (new XmlLoader)->load($actualXml); - } - - static::assertNotEquals($expected, $actual, $message); - } - - /** - * Asserts that a hierarchy of DOMElements matches. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws AssertionFailedError * @throws ExpectationFailedException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4091 + * @throws XmlException */ - public static function assertEqualXMLStructure(DOMElement $expectedElement, DOMElement $actualElement, bool $checkAttributes = false, string $message = ''): void + final public static function assertXmlStringNotEqualsXmlString(string $expectedXml, string $actualXml, string $message = ''): void { - self::createWarning('assertEqualXMLStructure() is deprecated and will be removed in PHPUnit 10.'); - - $expectedElement = Xml::import($expectedElement); - $actualElement = Xml::import($actualElement); + $expected = (new XmlLoader)->load($expectedXml); + $actual = (new XmlLoader)->load($actualXml); - static::assertSame( - $expectedElement->tagName, - $actualElement->tagName, - $message, - ); - - if ($checkAttributes) { - static::assertSame( - $expectedElement->attributes->length, - $actualElement->attributes->length, - sprintf( - '%s%sNumber of attributes on node "%s" does not match', - $message, - !empty($message) ? "\n" : '', - $expectedElement->tagName, - ), - ); - - for ($i = 0; $i < $expectedElement->attributes->length; $i++) { - $expectedAttribute = $expectedElement->attributes->item($i); - $actualAttribute = $actualElement->attributes->getNamedItem($expectedAttribute->name); - - assert($expectedAttribute instanceof DOMAttr); - - if (!$actualAttribute) { - static::fail( - sprintf( - '%s%sCould not find attribute "%s" on node "%s"', - $message, - !empty($message) ? "\n" : '', - $expectedAttribute->name, - $expectedElement->tagName, - ), - ); - } - } - } - - Xml::removeCharacterDataNodes($expectedElement); - Xml::removeCharacterDataNodes($actualElement); - - static::assertSame( - $expectedElement->childNodes->length, - $actualElement->childNodes->length, - sprintf( - '%s%sNumber of child nodes of "%s" differs', - $message, - !empty($message) ? "\n" : '', - $expectedElement->tagName, - ), - ); - - for ($i = 0; $i < $expectedElement->childNodes->length; $i++) { - static::assertEqualXMLStructure( - $expectedElement->childNodes->item($i), - $actualElement->childNodes->item($i), - $checkAttributes, - $message, - ); - } + self::assertNotEquals($expected, $actual, $message); } /** * Evaluates a PHPUnit\Framework\Constraint matcher object. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException */ - public static function assertThat($value, Constraint $constraint, string $message = ''): void + final public static function assertThat(mixed $value, Constraint $constraint, string $message = ''): void { self::$count += count($constraint); @@ -2441,43 +2599,37 @@ public static function assertThat($value, Constraint $constraint, string $messag /** * Asserts that a string is a valid JSON string. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException */ - public static function assertJson(string $actualJson, string $message = ''): void + final public static function assertJson(string $actual, string $message = ''): void { - static::assertThat($actualJson, static::isJson(), $message); + self::assertThat($actual, self::isJson(), $message); } /** * Asserts that two given JSON encoded objects or arrays are equal. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException */ - public static function assertJsonStringEqualsJsonString(string $expectedJson, string $actualJson, string $message = ''): void + final public static function assertJsonStringEqualsJsonString(string $expectedJson, string $actualJson, string $message = ''): void { - static::assertJson($expectedJson, $message); - static::assertJson($actualJson, $message); + self::assertJson($expectedJson, $message); + self::assertJson($actualJson, $message); - static::assertThat($actualJson, new JsonMatches($expectedJson), $message); + self::assertThat($actualJson, new JsonMatches($expectedJson), $message); } /** * Asserts that two given JSON encoded objects or arrays are not equal. * - * @param string $expectedJson - * @param string $actualJson - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException */ - public static function assertJsonStringNotEqualsJsonString($expectedJson, $actualJson, string $message = ''): void + final public static function assertJsonStringNotEqualsJsonString(string $expectedJson, string $actualJson, string $message = ''): void { - static::assertJson($expectedJson, $message); - static::assertJson($actualJson, $message); + self::assertJson($expectedJson, $message); + self::assertJson($actualJson, $message); - static::assertThat( + self::assertThat( $actualJson, new LogicalNot( new JsonMatches($expectedJson), @@ -2489,35 +2641,37 @@ public static function assertJsonStringNotEqualsJsonString($expectedJson, $actua /** * Asserts that the generated JSON encoded object and the content of the given file are equal. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException */ - public static function assertJsonStringEqualsJsonFile(string $expectedFile, string $actualJson, string $message = ''): void + final public static function assertJsonStringEqualsJsonFile(string $expectedFile, string $actualJson, string $message = ''): void { - static::assertFileExists($expectedFile, $message); + self::assertFileExists($expectedFile, $message); + $expectedJson = file_get_contents($expectedFile); - static::assertJson($expectedJson, $message); - static::assertJson($actualJson, $message); + self::assertIsString($expectedJson); + self::assertJson($expectedJson, $message); + self::assertJson($actualJson, $message); - static::assertThat($actualJson, new JsonMatches($expectedJson), $message); + self::assertThat($actualJson, new JsonMatches($expectedJson), $message); } /** * Asserts that the generated JSON encoded object and the content of the given file are not equal. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException */ - public static function assertJsonStringNotEqualsJsonFile(string $expectedFile, string $actualJson, string $message = ''): void + final public static function assertJsonStringNotEqualsJsonFile(string $expectedFile, string $actualJson, string $message = ''): void { - static::assertFileExists($expectedFile, $message); + self::assertFileExists($expectedFile, $message); + $expectedJson = file_get_contents($expectedFile); - static::assertJson($expectedJson, $message); - static::assertJson($actualJson, $message); + self::assertIsString($expectedJson); + self::assertJson($expectedJson, $message); + self::assertJson($actualJson, $message); - static::assertThat( + self::assertThat( $actualJson, new LogicalNot( new JsonMatches($expectedJson), @@ -2529,322 +2683,467 @@ public static function assertJsonStringNotEqualsJsonFile(string $expectedFile, s /** * Asserts that two JSON files are equal. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException */ - public static function assertJsonFileEqualsJsonFile(string $expectedFile, string $actualFile, string $message = ''): void + final public static function assertJsonFileEqualsJsonFile(string $expectedFile, string $actualFile, string $message = ''): void { - static::assertFileExists($expectedFile, $message); - static::assertFileExists($actualFile, $message); + self::assertFileExists($expectedFile, $message); - $actualJson = file_get_contents($actualFile); $expectedJson = file_get_contents($expectedFile); - static::assertJson($expectedJson, $message); - static::assertJson($actualJson, $message); + self::assertIsString($expectedJson); + self::assertJson($expectedJson, $message); - $constraintExpected = new JsonMatches( - $expectedJson, - ); + self::assertFileExists($actualFile, $message); + + $actualJson = file_get_contents($actualFile); - $constraintActual = new JsonMatches($actualJson); + self::assertIsString($actualJson); + self::assertJson($actualJson, $message); - static::assertThat($expectedJson, $constraintActual, $message); - static::assertThat($actualJson, $constraintExpected, $message); + self::assertThat($actualJson, new JsonMatches($expectedJson), $message); } /** * Asserts that two JSON files are not equal. * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ExpectationFailedException */ - public static function assertJsonFileNotEqualsJsonFile(string $expectedFile, string $actualFile, string $message = ''): void + final public static function assertJsonFileNotEqualsJsonFile(string $expectedFile, string $actualFile, string $message = ''): void { - static::assertFileExists($expectedFile, $message); - static::assertFileExists($actualFile, $message); + self::assertFileExists($expectedFile, $message); - $actualJson = file_get_contents($actualFile); $expectedJson = file_get_contents($expectedFile); - static::assertJson($expectedJson, $message); - static::assertJson($actualJson, $message); + self::assertIsString($expectedJson); + self::assertJson($expectedJson, $message); - $constraintExpected = new JsonMatches( - $expectedJson, - ); + self::assertFileExists($actualFile, $message); + + $actualJson = file_get_contents($actualFile); - $constraintActual = new JsonMatches($actualJson); + self::assertIsString($actualJson); + self::assertJson($actualJson, $message); - static::assertThat($expectedJson, new LogicalNot($constraintActual), $message); - static::assertThat($actualJson, new LogicalNot($constraintExpected), $message); + self::assertThat($actualJson, self::logicalNot(new JsonMatches($expectedJson)), $message); } /** * @throws Exception */ - public static function logicalAnd(): LogicalAnd + final public static function logicalAnd(mixed ...$constraints): LogicalAnd { - $constraints = func_get_args(); - - $constraint = new LogicalAnd; - $constraint->setConstraints($constraints); - - return $constraint; + return LogicalAnd::fromConstraints(...$constraints); } - public static function logicalOr(): LogicalOr + final public static function logicalOr(mixed ...$constraints): LogicalOr { - $constraints = func_get_args(); - - $constraint = new LogicalOr; - $constraint->setConstraints($constraints); - - return $constraint; + return LogicalOr::fromConstraints(...$constraints); } - public static function logicalNot(Constraint $constraint): LogicalNot + final public static function logicalNot(Constraint $constraint): LogicalNot { return new LogicalNot($constraint); } - public static function logicalXor(): LogicalXor + final public static function logicalXor(mixed ...$constraints): LogicalXor { - $constraints = func_get_args(); - - $constraint = new LogicalXor; - $constraint->setConstraints($constraints); - - return $constraint; + return LogicalXor::fromConstraints(...$constraints); } - public static function anything(): IsAnything + final public static function anything(): IsAnything { return new IsAnything; } - public static function isTrue(): IsTrue + final public static function isTrue(): IsTrue { return new IsTrue; } /** - * @psalm-template CallbackInput of mixed + * @template CallbackInput of mixed * - * @psalm-param callable(CallbackInput $callback): bool $callback + * @param callable(CallbackInput $callback): bool $callback * - * @psalm-return Callback + * @return Callback */ - public static function callback(callable $callback): Callback + final public static function callback(callable $callback): Callback { return new Callback($callback); } - public static function isFalse(): IsFalse + final public static function isFalse(): IsFalse { return new IsFalse; } - public static function isJson(): IsJson + final public static function isJson(): IsJson { return new IsJson; } - public static function isNull(): IsNull + final public static function isNull(): IsNull { return new IsNull; } - public static function isFinite(): IsFinite + final public static function isFinite(): IsFinite { return new IsFinite; } - public static function isInfinite(): IsInfinite + final public static function isInfinite(): IsInfinite { return new IsInfinite; } - public static function isNan(): IsNan + final public static function isNan(): IsNan { return new IsNan; } - public static function containsEqual($value): TraversableContainsEqual + final public static function containsEqual(mixed $value): TraversableContainsEqual { return new TraversableContainsEqual($value); } - public static function containsIdentical($value): TraversableContainsIdentical + final public static function containsIdentical(mixed $value): TraversableContainsIdentical { return new TraversableContainsIdentical($value); } - public static function containsOnly(string $type): TraversableContainsOnly + /** + * @param 'array'|'bool'|'boolean'|'callable'|'double'|'float'|'int'|'integer'|'iterable'|'null'|'numeric'|'object'|'real'|'resource (closed)'|'resource'|'scalar'|'string' $type + * + * @throws Exception + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/6060 + */ + final public static function containsOnly(string $type): TraversableContainsOnly + { + return TraversableContainsOnly::forNativeType(self::mapNativeType($type)); + } + + final public static function containsOnlyArray(): TraversableContainsOnly + { + return TraversableContainsOnly::forNativeType(NativeType::Array); + } + + final public static function containsOnlyBool(): TraversableContainsOnly + { + return TraversableContainsOnly::forNativeType(NativeType::Bool); + } + + final public static function containsOnlyCallable(): TraversableContainsOnly + { + return TraversableContainsOnly::forNativeType(NativeType::Callable); + } + + final public static function containsOnlyFloat(): TraversableContainsOnly + { + return TraversableContainsOnly::forNativeType(NativeType::Float); + } + + final public static function containsOnlyInt(): TraversableContainsOnly + { + return TraversableContainsOnly::forNativeType(NativeType::Int); + } + + final public static function containsOnlyIterable(): TraversableContainsOnly + { + return TraversableContainsOnly::forNativeType(NativeType::Iterable); + } + + final public static function containsOnlyNull(): TraversableContainsOnly + { + return TraversableContainsOnly::forNativeType(NativeType::Null); + } + + final public static function containsOnlyNumeric(): TraversableContainsOnly + { + return TraversableContainsOnly::forNativeType(NativeType::Numeric); + } + + final public static function containsOnlyObject(): TraversableContainsOnly + { + return TraversableContainsOnly::forNativeType(NativeType::Object); + } + + final public static function containsOnlyResource(): TraversableContainsOnly + { + return TraversableContainsOnly::forNativeType(NativeType::Resource); + } + + final public static function containsOnlyClosedResource(): TraversableContainsOnly + { + return TraversableContainsOnly::forNativeType(NativeType::ClosedResource); + } + + final public static function containsOnlyScalar(): TraversableContainsOnly { - return new TraversableContainsOnly($type); + return TraversableContainsOnly::forNativeType(NativeType::Scalar); } - public static function containsOnlyInstancesOf(string $className): TraversableContainsOnly + final public static function containsOnlyString(): TraversableContainsOnly { - return new TraversableContainsOnly($className, false); + return TraversableContainsOnly::forNativeType(NativeType::String); } /** - * @param int|string $key + * @param class-string $className + * + * @throws Exception */ - public static function arrayHasKey($key): ArrayHasKey + final public static function containsOnlyInstancesOf(string $className): TraversableContainsOnly + { + return TraversableContainsOnly::forClassOrInterface($className); + } + + final public static function arrayHasKey(mixed $key): ArrayHasKey { return new ArrayHasKey($key); } - public static function equalTo($value): IsEqual + final public static function isList(): IsList + { + return new IsList; + } + + final public static function equalTo(mixed $value): IsEqual { - return new IsEqual($value, 0.0, false, false); + return new IsEqual($value); } - public static function equalToCanonicalizing($value): IsEqualCanonicalizing + final public static function equalToCanonicalizing(mixed $value): IsEqualCanonicalizing { return new IsEqualCanonicalizing($value); } - public static function equalToIgnoringCase($value): IsEqualIgnoringCase + final public static function equalToIgnoringCase(mixed $value): IsEqualIgnoringCase { return new IsEqualIgnoringCase($value); } - public static function equalToWithDelta($value, float $delta): IsEqualWithDelta + final public static function equalToWithDelta(mixed $value, float $delta): IsEqualWithDelta { return new IsEqualWithDelta($value, $delta); } - public static function isEmpty(): IsEmpty + final public static function isEmpty(): IsEmpty { return new IsEmpty; } - public static function isWritable(): IsWritable + final public static function isWritable(): IsWritable { return new IsWritable; } - public static function isReadable(): IsReadable + final public static function isReadable(): IsReadable { return new IsReadable; } - public static function directoryExists(): DirectoryExists + final public static function directoryExists(): DirectoryExists { return new DirectoryExists; } - public static function fileExists(): FileExists + final public static function fileExists(): FileExists { return new FileExists; } - public static function greaterThan($value): GreaterThan + final public static function greaterThan(mixed $value): GreaterThan { return new GreaterThan($value); } - public static function greaterThanOrEqual($value): LogicalOr + final public static function greaterThanOrEqual(mixed $value): LogicalOr { - return static::logicalOr( + return self::logicalOr( new IsEqual($value), new GreaterThan($value), ); } + final public static function identicalTo(mixed $value): IsIdentical + { + return new IsIdentical($value); + } + /** - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4601 + * @throws UnknownClassOrInterfaceException */ - public static function classHasAttribute(string $attributeName): ClassHasAttribute + final public static function isInstanceOf(string $className): IsInstanceOf { - self::createWarning('classHasAttribute() is deprecated and will be removed in PHPUnit 10.'); + return new IsInstanceOf($className); + } - return new ClassHasAttribute($attributeName); + final public static function isArray(): IsType + { + return new IsType(NativeType::Array); } - /** - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4601 - */ - public static function classHasStaticAttribute(string $attributeName): ClassHasStaticAttribute + final public static function isBool(): IsType { - self::createWarning('classHasStaticAttribute() is deprecated and will be removed in PHPUnit 10.'); + return new IsType(NativeType::Bool); + } - return new ClassHasStaticAttribute($attributeName); + final public static function isCallable(): IsType + { + return new IsType(NativeType::Callable); } - /** - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4601 - */ - public static function objectHasAttribute($attributeName): ObjectHasAttribute + final public static function isFloat(): IsType + { + return new IsType(NativeType::Float); + } + + final public static function isInt(): IsType { - self::createWarning('objectHasAttribute() is deprecated and will be removed in PHPUnit 10.'); + return new IsType(NativeType::Int); + } - return new ObjectHasAttribute($attributeName); + final public static function isIterable(): IsType + { + return new IsType(NativeType::Iterable); } - public static function identicalTo($value): IsIdentical + final public static function isNumeric(): IsType { - return new IsIdentical($value); + return new IsType(NativeType::Numeric); } - public static function isInstanceOf(string $className): IsInstanceOf + final public static function isObject(): IsType { - return new IsInstanceOf($className); + return new IsType(NativeType::Object); + } + + final public static function isResource(): IsType + { + return new IsType(NativeType::Resource); + } + + final public static function isClosedResource(): IsType + { + return new IsType(NativeType::ClosedResource); + } + + final public static function isScalar(): IsType + { + return new IsType(NativeType::Scalar); + } + + final public static function isString(): IsType + { + return new IsType(NativeType::String); } - public static function isType(string $type): IsType + /** + * @param 'array'|'bool'|'boolean'|'callable'|'double'|'float'|'int'|'integer'|'iterable'|'null'|'numeric'|'object'|'real'|'resource (closed)'|'resource'|'scalar'|'string' $type + * + * @throws UnknownNativeTypeException + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/6053 + */ + final public static function isType(string $type): IsType { - return new IsType($type); + $constraint = new IsType(self::mapNativeType($type)); + + $replacement = match ($type) { + 'array' => 'isArray', + 'bool' => 'isBool', + 'boolean' => 'isBool', + 'callable' => 'isCallable', + 'double' => 'isFloat', + 'float' => 'isFloat', + 'int' => 'isInt', + 'integer' => 'isInt', + 'iterable' => 'isIterable', + 'null' => 'isNull', + 'numeric' => 'isNumeric', + 'object' => 'isObject', + 'real' => 'isFloat', + 'resource' => 'isResource', + 'resource (closed)' => 'isClosedResource', + 'scalar' => 'isScalar', + 'string' => 'isString', + }; + + EventFacade::emitter()->testTriggeredPhpunitDeprecation( + null, + sprintf( + 'isType(\'%s\') is deprecated and will be removed in PHPUnit 13. ' . + 'Please use the %s() method instead.', + $type, + $replacement, + ), + ); + + return $constraint; } - public static function lessThan($value): LessThan + final public static function lessThan(mixed $value): LessThan { return new LessThan($value); } - public static function lessThanOrEqual($value): LogicalOr + final public static function lessThanOrEqual(mixed $value): LogicalOr { - return static::logicalOr( + return self::logicalOr( new IsEqual($value), new LessThan($value), ); } - public static function matchesRegularExpression(string $pattern): RegularExpression + final public static function matchesRegularExpression(string $pattern): RegularExpression { return new RegularExpression($pattern); } - public static function matches(string $string): StringMatchesFormatDescription + final public static function matches(string $string): StringMatchesFormatDescription { return new StringMatchesFormatDescription($string); } - public static function stringStartsWith($prefix): StringStartsWith + /** + * @param non-empty-string $prefix + * + * @throws InvalidArgumentException + */ + final public static function stringStartsWith(string $prefix): StringStartsWith { return new StringStartsWith($prefix); } - public static function stringContains(string $string, bool $case = true): StringContains + final public static function stringContains(string $string, bool $case = true): StringContains { return new StringContains($string, $case); } - public static function stringEndsWith(string $suffix): StringEndsWith + /** + * @param non-empty-string $suffix + * + * @throws InvalidArgumentException + */ + final public static function stringEndsWith(string $suffix): StringEndsWith { return new StringEndsWith($suffix); } - public static function countOf(int $count): Count + final public static function stringEqualsStringIgnoringLineEndings(string $string): StringEqualsStringIgnoringLineEndings + { + return new StringEqualsStringIgnoringLineEndings($string); + } + + final public static function countOf(int $count): Count { return new Count($count); } - public static function objectEquals(object $object, string $method = 'equals'): ObjectEquals + final public static function objectEquals(object $object, string $method = 'equals'): ObjectEquals { return new ObjectEquals($object, $method); } @@ -2853,10 +3152,8 @@ public static function objectEquals(object $object, string $method = 'equals'): * Fails a test with the given message. * * @throws AssertionFailedError - * - * @psalm-return never-return */ - public static function fail(string $message = ''): void + final public static function fail(string $message = ''): never { self::$count++; @@ -2867,10 +3164,8 @@ public static function fail(string $message = ''): void * Mark the test as incomplete. * * @throws IncompleteTestError - * - * @psalm-return never-return */ - public static function markTestIncomplete(string $message = ''): void + final public static function markTestIncomplete(string $message = ''): never { throw new IncompleteTestError($message); } @@ -2878,27 +3173,17 @@ public static function markTestIncomplete(string $message = ''): void /** * Mark the test as skipped. * - * @throws SkippedTestError - * @throws SyntheticSkippedError - * - * @psalm-return never-return + * @throws SkippedWithMessageException */ - public static function markTestSkipped(string $message = ''): void + final public static function markTestSkipped(string $message = ''): never { - if ($hint = self::detectLocationHint($message)) { - $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); - array_unshift($trace, $hint); - - throw new SyntheticSkippedError($hint['message'], 0, $hint['file'], (int) $hint['line'], $trace); - } - - throw new SkippedTestError($message); + throw new SkippedWithMessageException($message); } /** * Return the current assertion count. */ - public static function getCount(): int + final public static function getCount(): int { return self::$count; } @@ -2906,58 +3191,60 @@ public static function getCount(): int /** * Reset the assertion counter. */ - public static function resetCount(): void + final public static function resetCount(): void { self::$count = 0; } - private static function detectLocationHint(string $message): ?array - { - $hint = null; - $lines = preg_split('/\r\n|\r|\n/', $message); - - while (strpos($lines[0], '__OFFSET') !== false) { - $offset = explode('=', array_shift($lines)); - - if ($offset[0] === '__OFFSET_FILE') { - $hint['file'] = $offset[1]; - } - - if ($offset[0] === '__OFFSET_LINE') { - $hint['line'] = $offset[1]; - } - } - - if ($hint) { - $hint['message'] = implode(PHP_EOL, $lines); - } - - return $hint; - } - - private static function isValidObjectAttributeName(string $attributeName): bool - { - return (bool) preg_match('/[^\x00-\x1f\x7f-\x9f]+/', $attributeName); - } - - private static function isValidClassAttributeName(string $attributeName): bool + private static function isNativeType(string $type): bool { - return (bool) preg_match('/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/', $attributeName); + return $type === 'array' || + $type === 'bool' || + $type === 'boolean' || + $type === 'callable' || + $type === 'double' || + $type === 'float' || + $type === 'int' || + $type === 'integer' || + $type === 'iterable' || + $type === 'null' || + $type === 'numeric' || + $type === 'object' || + $type === 'real' || + $type === 'resource' || + $type === 'resource (closed)' || + $type === 'scalar' || + $type === 'string'; } /** - * @codeCoverageIgnore + * @throws UnknownNativeTypeException */ - private static function createWarning(string $warning): void + private static function mapNativeType(string $type): NativeType { - foreach (debug_backtrace() as $step) { - if (isset($step['object']) && $step['object'] instanceof TestCase) { - assert($step['object'] instanceof TestCase); - - $step['object']->addWarning($warning); - - break; - } + if (!self::isNativeType($type)) { + throw new UnknownNativeTypeException($type); } + + /** @phpstan-ignore match.unhandled */ + return match ($type) { + 'array' => NativeType::Array, + 'bool' => NativeType::Bool, + 'boolean' => NativeType::Bool, + 'callable' => NativeType::Callable, + 'double' => NativeType::Float, + 'float' => NativeType::Float, + 'int' => NativeType::Int, + 'integer' => NativeType::Int, + 'iterable' => NativeType::Iterable, + 'null' => NativeType::Null, + 'numeric' => NativeType::Numeric, + 'object' => NativeType::Object, + 'real' => NativeType::Float, + 'resource' => NativeType::Resource, + 'resource (closed)' => NativeType::ClosedResource, + 'scalar' => NativeType::Scalar, + 'string' => NativeType::String, + }; } } diff --git a/app/vendor/phpunit/phpunit/src/Framework/Assert/Functions.php b/app/vendor/phpunit/phpunit/src/Framework/Assert/Functions.php index 5b6cc850c..0645fd262 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Assert/Functions.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Assert/Functions.php @@ -13,12 +13,8 @@ use function function_exists; use ArrayAccess; use Countable; -use DOMDocument; -use DOMElement; use PHPUnit\Framework\Constraint\ArrayHasKey; use PHPUnit\Framework\Constraint\Callback; -use PHPUnit\Framework\Constraint\ClassHasAttribute; -use PHPUnit\Framework\Constraint\ClassHasStaticAttribute; use PHPUnit\Framework\Constraint\Constraint; use PHPUnit\Framework\Constraint\Count; use PHPUnit\Framework\Constraint\DirectoryExists; @@ -36,6 +32,7 @@ use PHPUnit\Framework\Constraint\IsInfinite; use PHPUnit\Framework\Constraint\IsInstanceOf; use PHPUnit\Framework\Constraint\IsJson; +use PHPUnit\Framework\Constraint\IsList; use PHPUnit\Framework\Constraint\IsNan; use PHPUnit\Framework\Constraint\IsNull; use PHPUnit\Framework\Constraint\IsReadable; @@ -48,171 +45,811 @@ use PHPUnit\Framework\Constraint\LogicalOr; use PHPUnit\Framework\Constraint\LogicalXor; use PHPUnit\Framework\Constraint\ObjectEquals; -use PHPUnit\Framework\Constraint\ObjectHasAttribute; use PHPUnit\Framework\Constraint\RegularExpression; use PHPUnit\Framework\Constraint\StringContains; use PHPUnit\Framework\Constraint\StringEndsWith; +use PHPUnit\Framework\Constraint\StringEqualsStringIgnoringLineEndings; use PHPUnit\Framework\Constraint\StringMatchesFormatDescription; use PHPUnit\Framework\Constraint\StringStartsWith; use PHPUnit\Framework\Constraint\TraversableContainsEqual; use PHPUnit\Framework\Constraint\TraversableContainsIdentical; use PHPUnit\Framework\Constraint\TraversableContainsOnly; use PHPUnit\Framework\MockObject\Rule\AnyInvokedCount as AnyInvokedCountMatcher; -use PHPUnit\Framework\MockObject\Rule\InvokedAtIndex as InvokedAtIndexMatcher; use PHPUnit\Framework\MockObject\Rule\InvokedAtLeastCount as InvokedAtLeastCountMatcher; use PHPUnit\Framework\MockObject\Rule\InvokedAtLeastOnce as InvokedAtLeastOnceMatcher; use PHPUnit\Framework\MockObject\Rule\InvokedAtMostCount as InvokedAtMostCountMatcher; use PHPUnit\Framework\MockObject\Rule\InvokedCount as InvokedCountMatcher; -use PHPUnit\Framework\MockObject\Stub\ConsecutiveCalls as ConsecutiveCallsStub; use PHPUnit\Framework\MockObject\Stub\Exception as ExceptionStub; -use PHPUnit\Framework\MockObject\Stub\ReturnArgument as ReturnArgumentStub; -use PHPUnit\Framework\MockObject\Stub\ReturnCallback as ReturnCallbackStub; -use PHPUnit\Framework\MockObject\Stub\ReturnSelf as ReturnSelfStub; -use PHPUnit\Framework\MockObject\Stub\ReturnStub; -use PHPUnit\Framework\MockObject\Stub\ReturnValueMap as ReturnValueMapStub; -use SebastianBergmann\RecursionContext\InvalidArgumentException; +use PHPUnit\Util\Xml\XmlException; use Throwable; +if (!function_exists('PHPUnit\Framework\assertArrayIsEqualToArrayOnlyConsideringListOfKeys')) { + /** + * Asserts that two arrays are equal while only considering a list of keys. + * + * @param array $expected + * @param array $actual + * @param non-empty-list $keysToBeConsidered + * + * @throws Exception + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertArrayIsEqualToArrayOnlyConsideringListOfKeys + */ + function assertArrayIsEqualToArrayOnlyConsideringListOfKeys(array $expected, array $actual, array $keysToBeConsidered, string $message = ''): void + { + Assert::assertArrayIsEqualToArrayOnlyConsideringListOfKeys(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertArrayIsEqualToArrayIgnoringListOfKeys')) { + /** + * Asserts that two arrays are equal while ignoring a list of keys. + * + * @param array $expected + * @param array $actual + * @param non-empty-list $keysToBeIgnored + * + * @throws Exception + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertArrayIsEqualToArrayIgnoringListOfKeys + */ + function assertArrayIsEqualToArrayIgnoringListOfKeys(array $expected, array $actual, array $keysToBeIgnored, string $message = ''): void + { + Assert::assertArrayIsEqualToArrayIgnoringListOfKeys(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertArrayIsIdenticalToArrayOnlyConsideringListOfKeys')) { + /** + * Asserts that two arrays are identical while only considering a list of keys. + * + * @param array $expected + * @param array $actual + * @param non-empty-list $keysToBeConsidered + * + * @throws Exception + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertArrayIsIdenticalToArrayOnlyConsideringListOfKeys + */ + function assertArrayIsIdenticalToArrayOnlyConsideringListOfKeys(array $expected, array $actual, array $keysToBeConsidered, string $message = ''): void + { + Assert::assertArrayIsIdenticalToArrayOnlyConsideringListOfKeys(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertArrayIsIdenticalToArrayIgnoringListOfKeys')) { + /** + * Asserts that two arrays are equal while ignoring a list of keys. + * + * @param array $expected + * @param array $actual + * @param non-empty-list $keysToBeIgnored + * + * @throws Exception + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertArrayIsIdenticalToArrayIgnoringListOfKeys + */ + function assertArrayIsIdenticalToArrayIgnoringListOfKeys(array $expected, array $actual, array $keysToBeIgnored, string $message = ''): void + { + Assert::assertArrayIsIdenticalToArrayIgnoringListOfKeys(...func_get_args()); + } +} + if (!function_exists('PHPUnit\Framework\assertArrayHasKey')) { /** - * Asserts that an array has a specified key. + * Asserts that an array has a specified key. + * + * @param array|ArrayAccess $array + * + * @throws Exception + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertArrayHasKey + */ + function assertArrayHasKey(mixed $key, array|ArrayAccess $array, string $message = ''): void + { + Assert::assertArrayHasKey(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertArrayNotHasKey')) { + /** + * Asserts that an array does not have a specified key. + * + * @param array|ArrayAccess $array + * + * @throws Exception + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertArrayNotHasKey + */ + function assertArrayNotHasKey(mixed $key, array|ArrayAccess $array, string $message = ''): void + { + Assert::assertArrayNotHasKey(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertIsList')) { + /** + * @phpstan-assert list $array + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertIsList + */ + function assertIsList(mixed $array, string $message = ''): void + { + Assert::assertIsList(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContains')) { + /** + * Asserts that a haystack contains a needle. + * + * @param iterable $haystack + * + * @throws Exception + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContains + */ + function assertContains(mixed $needle, iterable $haystack, string $message = ''): void + { + Assert::assertContains(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContainsEquals')) { + /** + * @param iterable $haystack + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContainsEquals + */ + function assertContainsEquals(mixed $needle, iterable $haystack, string $message = ''): void + { + Assert::assertContainsEquals(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertNotContains')) { + /** + * Asserts that a haystack does not contain a needle. + * + * @param iterable $haystack + * + * @throws Exception + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNotContains + */ + function assertNotContains(mixed $needle, iterable $haystack, string $message = ''): void + { + Assert::assertNotContains(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertNotContainsEquals')) { + /** + * @param iterable $haystack + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNotContainsEquals + */ + function assertNotContainsEquals(mixed $needle, iterable $haystack, string $message = ''): void + { + Assert::assertNotContainsEquals(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContainsOnly')) { + /** + * Asserts that a haystack contains only values of a given type. + * + * @param 'array'|'bool'|'boolean'|'callable'|'double'|'float'|'int'|'integer'|'iterable'|'null'|'numeric'|'object'|'real'|'resource (closed)'|'resource'|'scalar'|'string' $type + * @param iterable $haystack + * + * @throws Exception + * @throws ExpectationFailedException + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/6056 + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContainsOnly + */ + function assertContainsOnly(string $type, iterable $haystack, ?bool $isNativeType = null, string $message = ''): void + { + Assert::assertContainsOnly(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContainsOnlyArray')) { + /** + * Asserts that a haystack contains only values of type array. + * + * @phpstan-assert iterable> $haystack + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContainsOnlyArray + */ + function assertContainsOnlyArray(iterable $haystack, string $message = ''): void + { + Assert::assertContainsOnlyArray(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContainsOnlyBool')) { + /** + * Asserts that a haystack contains only values of type bool. + * + * @phpstan-assert iterable $haystack + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContainsOnlyBool + */ + function assertContainsOnlyBool(iterable $haystack, string $message = ''): void + { + Assert::assertContainsOnlyBool(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContainsOnlyCallable')) { + /** + * Asserts that a haystack contains only values of type callable. + * + * @phpstan-assert iterable $haystack + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContainsOnlyCallable + */ + function assertContainsOnlyCallable(iterable $haystack, string $message = ''): void + { + Assert::assertContainsOnlyCallable(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContainsOnlyFloat')) { + /** + * Asserts that a haystack contains only values of type float. + * + * @phpstan-assert iterable $haystack + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContainsOnlyFloat + */ + function assertContainsOnlyFloat(iterable $haystack, string $message = ''): void + { + Assert::assertContainsOnlyFloat(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContainsOnlyInt')) { + /** + * Asserts that a haystack contains only values of type int. + * + * @phpstan-assert iterable $haystack + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContainsOnlyInt + */ + function assertContainsOnlyInt(iterable $haystack, string $message = ''): void + { + Assert::assertContainsOnlyInt(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContainsOnlyIterable')) { + /** + * Asserts that a haystack contains only values of type iterable. + * + * @phpstan-assert iterable> $haystack + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContainsOnlyIterable + */ + function assertContainsOnlyIterable(iterable $haystack, string $message = ''): void + { + Assert::assertContainsOnlyIterable(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContainsOnlyNull')) { + /** + * Asserts that a haystack contains only values of type null. + * + * @phpstan-assert iterable $haystack + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContainsOnlyNull + */ + function assertContainsOnlyNull(iterable $haystack, string $message = ''): void + { + Assert::assertContainsOnlyNull(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContainsOnlyNumeric')) { + /** + * Asserts that a haystack contains only values of type numeric. + * + * @phpstan-assert iterable $haystack + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContainsOnlyNumeric + */ + function assertContainsOnlyNumeric(iterable $haystack, string $message = ''): void + { + Assert::assertContainsOnlyNumeric(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContainsOnlyObject')) { + /** + * Asserts that a haystack contains only values of type object. + * + * @phpstan-assert iterable $haystack + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContainsOnlyObject + */ + function assertContainsOnlyObject(iterable $haystack, string $message = ''): void + { + Assert::assertContainsOnlyObject(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContainsOnlyResource')) { + /** + * Asserts that a haystack contains only values of type resource. + * + * @phpstan-assert iterable $haystack + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContainsOnlyResource + */ + function assertContainsOnlyResource(iterable $haystack, string $message = ''): void + { + Assert::assertContainsOnlyResource(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContainsOnlyClosedResource')) { + /** + * Asserts that a haystack contains only values of type closed resource. + * + * @phpstan-assert iterable $haystack + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContainsOnlyClosedResource + */ + function assertContainsOnlyClosedResource(iterable $haystack, string $message = ''): void + { + Assert::assertContainsOnlyClosedResource(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContainsOnlyScalar')) { + /** + * Asserts that a haystack contains only values of type scalar. + * + * @phpstan-assert iterable $haystack + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContainsOnlyScalar + */ + function assertContainsOnlyScalar(iterable $haystack, string $message = ''): void + { + Assert::assertContainsOnlyScalar(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContainsOnlyString')) { + /** + * Asserts that a haystack contains only values of type string. + * + * @phpstan-assert iterable $haystack + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContainsOnlyString + */ + function assertContainsOnlyString(iterable $haystack, string $message = ''): void + { + Assert::assertContainsOnlyString(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContainsOnlyInstancesOf')) { + /** + * Asserts that a haystack contains only instances of a specified interface or class name. + * + * @template T + * + * @phpstan-assert iterable $haystack + * + * @param class-string $className + * @param iterable $haystack + * + * @throws Exception + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContainsOnlyInstancesOf + */ + function assertContainsOnlyInstancesOf(string $className, iterable $haystack, string $message = ''): void + { + Assert::assertContainsOnlyInstancesOf(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertNotContainsOnly')) { + /** + * Asserts that a haystack does not contain only values of a given type. + * + * @param 'array'|'bool'|'boolean'|'callable'|'double'|'float'|'int'|'integer'|'iterable'|'null'|'numeric'|'object'|'real'|'resource (closed)'|'resource'|'scalar'|'string' $type + * @param iterable $haystack + * + * @throws Exception + * @throws ExpectationFailedException + * + * @deprecated https://github.com/sebastianbergmann/phpunit/issues/6056 + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertNotContainsOnly + */ + function assertNotContainsOnly(string $type, iterable $haystack, ?bool $isNativeType = null, string $message = ''): void + { + Assert::assertNotContainsOnly(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContainsNotOnlyArray')) { + /** + * Asserts that a haystack does not contain only values of type array. * - * @param int|string $key - * @param array|ArrayAccess $array + * @param iterable $haystack * * @throws ExpectationFailedException - * @throws InvalidArgumentException - * @throws Exception * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * - * @see Assert::assertArrayHasKey + * @see Assert::assertContainsNotOnlyArray */ - function assertArrayHasKey($key, $array, string $message = ''): void + function assertContainsNotOnlyArray(iterable $haystack, string $message = ''): void { - Assert::assertArrayHasKey(...func_get_args()); + Assert::assertContainsNotOnlyArray(...func_get_args()); } } -if (!function_exists('PHPUnit\Framework\assertArrayNotHasKey')) { +if (!function_exists('PHPUnit\Framework\assertContainsNotOnlyBool')) { /** - * Asserts that an array does not have a specified key. + * Asserts that a haystack does not contain only values of type bool. * - * @param int|string $key - * @param array|ArrayAccess $array + * @param iterable $haystack * * @throws ExpectationFailedException - * @throws InvalidArgumentException - * @throws Exception * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * - * @see Assert::assertArrayNotHasKey + * @see Assert::assertContainsNotOnlyBool */ - function assertArrayNotHasKey($key, $array, string $message = ''): void + function assertContainsNotOnlyBool(iterable $haystack, string $message = ''): void { - Assert::assertArrayNotHasKey(...func_get_args()); + Assert::assertContainsNotOnlyBool(...func_get_args()); } } -if (!function_exists('PHPUnit\Framework\assertContains')) { +if (!function_exists('PHPUnit\Framework\assertContainsNotOnlyCallable')) { /** - * Asserts that a haystack contains a needle. + * Asserts that a haystack does not contain only values of type callable. + * + * @param iterable $haystack * * @throws ExpectationFailedException - * @throws InvalidArgumentException - * @throws Exception * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * - * @see Assert::assertContains + * @see Assert::assertContainsNotOnlyCallable */ - function assertContains($needle, iterable $haystack, string $message = ''): void + function assertContainsNotOnlyCallable(iterable $haystack, string $message = ''): void { - Assert::assertContains(...func_get_args()); + Assert::assertContainsNotOnlyCallable(...func_get_args()); } } -if (!function_exists('PHPUnit\Framework\assertContainsEquals')) { - function assertContainsEquals($needle, iterable $haystack, string $message = ''): void +if (!function_exists('PHPUnit\Framework\assertContainsNotOnlyFloat')) { + /** + * Asserts that a haystack does not contain only values of type float. + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContainsNotOnlyFloat + */ + function assertContainsNotOnlyFloat(iterable $haystack, string $message = ''): void { - Assert::assertContainsEquals(...func_get_args()); + Assert::assertContainsNotOnlyFloat(...func_get_args()); } } -if (!function_exists('PHPUnit\Framework\assertNotContains')) { +if (!function_exists('PHPUnit\Framework\assertContainsNotOnlyInt')) { /** - * Asserts that a haystack does not contain a needle. + * Asserts that a haystack does not contain only values of type int. + * + * @param iterable $haystack * * @throws ExpectationFailedException - * @throws InvalidArgumentException - * @throws Exception * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * - * @see Assert::assertNotContains + * @see Assert::assertContainsNotOnlyInt */ - function assertNotContains($needle, iterable $haystack, string $message = ''): void + function assertContainsNotOnlyInt(iterable $haystack, string $message = ''): void { - Assert::assertNotContains(...func_get_args()); + Assert::assertContainsNotOnlyInt(...func_get_args()); } } -if (!function_exists('PHPUnit\Framework\assertNotContainsEquals')) { - function assertNotContainsEquals($needle, iterable $haystack, string $message = ''): void +if (!function_exists('PHPUnit\Framework\assertContainsNotOnlyIterable')) { + /** + * Asserts that a haystack does not contain only values of type iterable. + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContainsNotOnlyIterable + */ + function assertContainsNotOnlyIterable(iterable $haystack, string $message = ''): void { - Assert::assertNotContainsEquals(...func_get_args()); + Assert::assertContainsNotOnlyIterable(...func_get_args()); } } -if (!function_exists('PHPUnit\Framework\assertContainsOnly')) { +if (!function_exists('PHPUnit\Framework\assertContainsNotOnlyNull')) { /** - * Asserts that a haystack contains only values of a given type. + * Asserts that a haystack does not contain only values of type null. + * + * @param iterable $haystack * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * - * @see Assert::assertContainsOnly + * @see Assert::assertContainsNotOnlyNull */ - function assertContainsOnly(string $type, iterable $haystack, ?bool $isNativeType = null, string $message = ''): void + function assertContainsNotOnlyNull(iterable $haystack, string $message = ''): void { - Assert::assertContainsOnly(...func_get_args()); + Assert::assertContainsNotOnlyNull(...func_get_args()); } } -if (!function_exists('PHPUnit\Framework\assertContainsOnlyInstancesOf')) { +if (!function_exists('PHPUnit\Framework\assertContainsNotOnlyNumeric')) { /** - * Asserts that a haystack contains only instances of a given class name. + * Asserts that a haystack does not contain only values of type numeric. + * + * @param iterable $haystack * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * - * @see Assert::assertContainsOnlyInstancesOf + * @see Assert::assertContainsNotOnlyNumeric */ - function assertContainsOnlyInstancesOf(string $className, iterable $haystack, string $message = ''): void + function assertContainsNotOnlyNumeric(iterable $haystack, string $message = ''): void { - Assert::assertContainsOnlyInstancesOf(...func_get_args()); + Assert::assertContainsNotOnlyNumeric(...func_get_args()); } } -if (!function_exists('PHPUnit\Framework\assertNotContainsOnly')) { +if (!function_exists('PHPUnit\Framework\assertContainsNotOnlyObject')) { /** - * Asserts that a haystack does not contain only values of a given type. + * Asserts that a haystack does not contain only values of type object. + * + * @param iterable $haystack * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * - * @see Assert::assertNotContainsOnly + * @see Assert::assertContainsNotOnlyObject */ - function assertNotContainsOnly(string $type, iterable $haystack, ?bool $isNativeType = null, string $message = ''): void + function assertContainsNotOnlyObject(iterable $haystack, string $message = ''): void { - Assert::assertNotContainsOnly(...func_get_args()); + Assert::assertContainsNotOnlyObject(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContainsNotOnlyResource')) { + /** + * Asserts that a haystack does not contain only values of type resource. + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContainsNotOnlyResource + */ + function assertContainsNotOnlyResource(iterable $haystack, string $message = ''): void + { + Assert::assertContainsNotOnlyResource(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContainsNotOnlyClosedResource')) { + /** + * Asserts that a haystack does not contain only values of type closed resource. + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContainsNotOnlyClosedResource + */ + function assertContainsNotOnlyClosedResource(iterable $haystack, string $message = ''): void + { + Assert::assertContainsNotOnlyClosedResource(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContainsNotOnlyScalar')) { + /** + * Asserts that a haystack does not contain only values of type scalar. + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContainsNotOnlyScalar + */ + function assertContainsNotOnlyScalar(iterable $haystack, string $message = ''): void + { + Assert::assertContainsNotOnlyScalar(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContainsNotOnlyString')) { + /** + * Asserts that a haystack does not contain only values of type string. + * + * @param iterable $haystack + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContainsNotOnlyString + */ + function assertContainsNotOnlyString(iterable $haystack, string $message = ''): void + { + Assert::assertContainsNotOnlyString(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertContainsNotOnlyInstancesOf')) { + /** + * Asserts that a haystack does not contain only instances of a specified interface or class name. + * + * @param class-string $className + * @param iterable $haystack + * + * @throws Exception + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertContainsNotOnlyInstancesOf + */ + function assertContainsNotOnlyInstancesOf(string $className, iterable $haystack, string $message = ''): void + { + Assert::assertContainsNotOnlyInstancesOf(...func_get_args()); } } @@ -220,17 +857,17 @@ function assertNotContainsOnly(string $type, iterable $haystack, ?bool $isNative /** * Asserts the number of elements of an array, Countable or Traversable. * - * @param Countable|iterable $haystack + * @param Countable|iterable $haystack * - * @throws ExpectationFailedException - * @throws InvalidArgumentException * @throws Exception + * @throws ExpectationFailedException + * @throws GeneratorNotSupportedException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertCount */ - function assertCount(int $expectedCount, $haystack, string $message = ''): void + function assertCount(int $expectedCount, Countable|iterable $haystack, string $message = ''): void { Assert::assertCount(...func_get_args()); } @@ -240,17 +877,17 @@ function assertCount(int $expectedCount, $haystack, string $message = ''): void /** * Asserts the number of elements of an array, Countable or Traversable. * - * @param Countable|iterable $haystack + * @param Countable|iterable $haystack * - * @throws ExpectationFailedException - * @throws InvalidArgumentException * @throws Exception + * @throws ExpectationFailedException + * @throws GeneratorNotSupportedException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertNotCount */ - function assertNotCount(int $expectedCount, $haystack, string $message = ''): void + function assertNotCount(int $expectedCount, Countable|iterable $haystack, string $message = ''): void { Assert::assertNotCount(...func_get_args()); } @@ -261,13 +898,12 @@ function assertNotCount(int $expectedCount, $haystack, string $message = ''): vo * Asserts that two variables are equal. * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertEquals */ - function assertEquals($expected, $actual, string $message = ''): void + function assertEquals(mixed $expected, mixed $actual, string $message = ''): void { Assert::assertEquals(...func_get_args()); } @@ -278,13 +914,12 @@ function assertEquals($expected, $actual, string $message = ''): void * Asserts that two variables are equal (canonicalizing). * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertEqualsCanonicalizing */ - function assertEqualsCanonicalizing($expected, $actual, string $message = ''): void + function assertEqualsCanonicalizing(mixed $expected, mixed $actual, string $message = ''): void { Assert::assertEqualsCanonicalizing(...func_get_args()); } @@ -295,13 +930,12 @@ function assertEqualsCanonicalizing($expected, $actual, string $message = ''): v * Asserts that two variables are equal (ignoring case). * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertEqualsIgnoringCase */ - function assertEqualsIgnoringCase($expected, $actual, string $message = ''): void + function assertEqualsIgnoringCase(mixed $expected, mixed $actual, string $message = ''): void { Assert::assertEqualsIgnoringCase(...func_get_args()); } @@ -312,13 +946,12 @@ function assertEqualsIgnoringCase($expected, $actual, string $message = ''): voi * Asserts that two variables are equal (with delta). * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertEqualsWithDelta */ - function assertEqualsWithDelta($expected, $actual, float $delta, string $message = ''): void + function assertEqualsWithDelta(mixed $expected, mixed $actual, float $delta, string $message = ''): void { Assert::assertEqualsWithDelta(...func_get_args()); } @@ -329,13 +962,12 @@ function assertEqualsWithDelta($expected, $actual, float $delta, string $message * Asserts that two variables are not equal. * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertNotEquals */ - function assertNotEquals($expected, $actual, string $message = ''): void + function assertNotEquals(mixed $expected, mixed $actual, string $message = ''): void { Assert::assertNotEquals(...func_get_args()); } @@ -346,13 +978,12 @@ function assertNotEquals($expected, $actual, string $message = ''): void * Asserts that two variables are not equal (canonicalizing). * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertNotEqualsCanonicalizing */ - function assertNotEqualsCanonicalizing($expected, $actual, string $message = ''): void + function assertNotEqualsCanonicalizing(mixed $expected, mixed $actual, string $message = ''): void { Assert::assertNotEqualsCanonicalizing(...func_get_args()); } @@ -363,13 +994,12 @@ function assertNotEqualsCanonicalizing($expected, $actual, string $message = '') * Asserts that two variables are not equal (ignoring case). * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertNotEqualsIgnoringCase */ - function assertNotEqualsIgnoringCase($expected, $actual, string $message = ''): void + function assertNotEqualsIgnoringCase(mixed $expected, mixed $actual, string $message = ''): void { Assert::assertNotEqualsIgnoringCase(...func_get_args()); } @@ -380,13 +1010,12 @@ function assertNotEqualsIgnoringCase($expected, $actual, string $message = ''): * Asserts that two variables are not equal (with delta). * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertNotEqualsWithDelta */ - function assertNotEqualsWithDelta($expected, $actual, float $delta, string $message = ''): void + function assertNotEqualsWithDelta(mixed $expected, mixed $actual, float $delta, string $message = ''): void { Assert::assertNotEqualsWithDelta(...func_get_args()); } @@ -406,20 +1035,32 @@ function assertObjectEquals(object $expected, object $actual, string $method = ' } } +if (!function_exists('PHPUnit\Framework\assertObjectNotEquals')) { + /** + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertObjectNotEquals + */ + function assertObjectNotEquals(object $expected, object $actual, string $method = 'equals', string $message = ''): void + { + Assert::assertObjectNotEquals(...func_get_args()); + } +} + if (!function_exists('PHPUnit\Framework\assertEmpty')) { /** * Asserts that a variable is empty. * * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @psalm-assert empty $actual + * @throws GeneratorNotSupportedException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertEmpty */ - function assertEmpty($actual, string $message = ''): void + function assertEmpty(mixed $actual, string $message = ''): void { Assert::assertEmpty(...func_get_args()); } @@ -430,15 +1071,13 @@ function assertEmpty($actual, string $message = ''): void * Asserts that a variable is not empty. * * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @psalm-assert !empty $actual + * @throws GeneratorNotSupportedException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertNotEmpty */ - function assertNotEmpty($actual, string $message = ''): void + function assertNotEmpty(mixed $actual, string $message = ''): void { Assert::assertNotEmpty(...func_get_args()); } @@ -449,13 +1088,12 @@ function assertNotEmpty($actual, string $message = ''): void * Asserts that a value is greater than another value. * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertGreaterThan */ - function assertGreaterThan($expected, $actual, string $message = ''): void + function assertGreaterThan(mixed $minimum, mixed $actual, string $message = ''): void { Assert::assertGreaterThan(...func_get_args()); } @@ -466,13 +1104,12 @@ function assertGreaterThan($expected, $actual, string $message = ''): void * Asserts that a value is greater than or equal to another value. * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertGreaterThanOrEqual */ - function assertGreaterThanOrEqual($expected, $actual, string $message = ''): void + function assertGreaterThanOrEqual(mixed $minimum, mixed $actual, string $message = ''): void { Assert::assertGreaterThanOrEqual(...func_get_args()); } @@ -483,13 +1120,12 @@ function assertGreaterThanOrEqual($expected, $actual, string $message = ''): voi * Asserts that a value is smaller than another value. * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertLessThan */ - function assertLessThan($expected, $actual, string $message = ''): void + function assertLessThan(mixed $maximum, mixed $actual, string $message = ''): void { Assert::assertLessThan(...func_get_args()); } @@ -500,13 +1136,12 @@ function assertLessThan($expected, $actual, string $message = ''): void * Asserts that a value is smaller than or equal to another value. * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertLessThanOrEqual */ - function assertLessThanOrEqual($expected, $actual, string $message = ''): void + function assertLessThanOrEqual(mixed $maximum, mixed $actual, string $message = ''): void { Assert::assertLessThanOrEqual(...func_get_args()); } @@ -518,7 +1153,6 @@ function assertLessThanOrEqual($expected, $actual, string $message = ''): void * file. * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * @@ -536,7 +1170,6 @@ function assertFileEquals(string $expected, string $actual, string $message = '' * file (canonicalizing). * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * @@ -554,7 +1187,6 @@ function assertFileEqualsCanonicalizing(string $expected, string $actual, string * file (ignoring case). * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * @@ -572,7 +1204,6 @@ function assertFileEqualsIgnoringCase(string $expected, string $actual, string $ * another file. * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * @@ -590,7 +1221,6 @@ function assertFileNotEquals(string $expected, string $actual, string $message = * file (canonicalizing). * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * @@ -608,7 +1238,6 @@ function assertFileNotEqualsCanonicalizing(string $expected, string $actual, str * file (ignoring case). * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * @@ -626,7 +1255,6 @@ function assertFileNotEqualsIgnoringCase(string $expected, string $actual, strin * to the contents of a file. * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * @@ -644,7 +1272,6 @@ function assertStringEqualsFile(string $expectedFile, string $actualString, stri * to the contents of a file (canonicalizing). * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * @@ -662,7 +1289,6 @@ function assertStringEqualsFileCanonicalizing(string $expectedFile, string $actu * to the contents of a file (ignoring case). * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * @@ -680,7 +1306,6 @@ function assertStringEqualsFileIgnoringCase(string $expectedFile, string $actual * to the contents of a file. * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * @@ -698,7 +1323,6 @@ function assertStringNotEqualsFile(string $expectedFile, string $actualString, s * to the contents of a file (canonicalizing). * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * @@ -716,7 +1340,6 @@ function assertStringNotEqualsFileCanonicalizing(string $expectedFile, string $a * to the contents of a file (ignoring case). * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * @@ -733,7 +1356,6 @@ function assertStringNotEqualsFileIgnoringCase(string $expectedFile, string $act * Asserts that a file/dir is readable. * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * @@ -750,7 +1372,6 @@ function assertIsReadable(string $filename, string $message = ''): void * Asserts that a file/dir exists and is not readable. * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * @@ -762,33 +1383,11 @@ function assertIsNotReadable(string $filename, string $message = ''): void } } -if (!function_exists('PHPUnit\Framework\assertNotIsReadable')) { - /** - * Asserts that a file/dir exists and is not readable. - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4062 - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertNotIsReadable - */ - function assertNotIsReadable(string $filename, string $message = ''): void - { - Assert::assertNotIsReadable(...func_get_args()); - } -} - if (!function_exists('PHPUnit\Framework\assertIsWritable')) { /** * Asserts that a file/dir exists and is writable. * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * @@ -805,7 +1404,6 @@ function assertIsWritable(string $filename, string $message = ''): void * Asserts that a file/dir exists and is not writable. * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * @@ -817,33 +1415,11 @@ function assertIsNotWritable(string $filename, string $message = ''): void } } -if (!function_exists('PHPUnit\Framework\assertNotIsWritable')) { - /** - * Asserts that a file/dir exists and is not writable. - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4065 - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertNotIsWritable - */ - function assertNotIsWritable(string $filename, string $message = ''): void - { - Assert::assertNotIsWritable(...func_get_args()); - } -} - if (!function_exists('PHPUnit\Framework\assertDirectoryExists')) { /** * Asserts that a directory exists. * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * @@ -860,7 +1436,6 @@ function assertDirectoryExists(string $directory, string $message = ''): void * Asserts that a directory does not exist. * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * @@ -872,33 +1447,11 @@ function assertDirectoryDoesNotExist(string $directory, string $message = ''): v } } -if (!function_exists('PHPUnit\Framework\assertDirectoryNotExists')) { - /** - * Asserts that a directory does not exist. - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4068 - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertDirectoryNotExists - */ - function assertDirectoryNotExists(string $directory, string $message = ''): void - { - Assert::assertDirectoryNotExists(...func_get_args()); - } -} - if (!function_exists('PHPUnit\Framework\assertDirectoryIsReadable')) { /** * Asserts that a directory exists and is readable. * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * @@ -915,7 +1468,6 @@ function assertDirectoryIsReadable(string $directory, string $message = ''): voi * Asserts that a directory exists and is not readable. * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * @@ -927,79 +1479,35 @@ function assertDirectoryIsNotReadable(string $directory, string $message = ''): } } -if (!function_exists('PHPUnit\Framework\assertDirectoryNotIsReadable')) { - /** - * Asserts that a directory exists and is not readable. - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4071 - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertDirectoryNotIsReadable - */ - function assertDirectoryNotIsReadable(string $directory, string $message = ''): void - { - Assert::assertDirectoryNotIsReadable(...func_get_args()); - } -} - if (!function_exists('PHPUnit\Framework\assertDirectoryIsWritable')) { /** * Asserts that a directory exists and is writable. * * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertDirectoryIsWritable - */ - function assertDirectoryIsWritable(string $directory, string $message = ''): void - { - Assert::assertDirectoryIsWritable(...func_get_args()); - } -} - -if (!function_exists('PHPUnit\Framework\assertDirectoryIsNotWritable')) { - /** - * Asserts that a directory exists and is not writable. - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * - * @see Assert::assertDirectoryIsNotWritable + * @see Assert::assertDirectoryIsWritable */ - function assertDirectoryIsNotWritable(string $directory, string $message = ''): void + function assertDirectoryIsWritable(string $directory, string $message = ''): void { - Assert::assertDirectoryIsNotWritable(...func_get_args()); + Assert::assertDirectoryIsWritable(...func_get_args()); } } -if (!function_exists('PHPUnit\Framework\assertDirectoryNotIsWritable')) { +if (!function_exists('PHPUnit\Framework\assertDirectoryIsNotWritable')) { /** * Asserts that a directory exists and is not writable. * * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4074 * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * - * @see Assert::assertDirectoryNotIsWritable + * @see Assert::assertDirectoryIsNotWritable */ - function assertDirectoryNotIsWritable(string $directory, string $message = ''): void + function assertDirectoryIsNotWritable(string $directory, string $message = ''): void { - Assert::assertDirectoryNotIsWritable(...func_get_args()); + Assert::assertDirectoryIsNotWritable(...func_get_args()); } } @@ -1008,7 +1516,6 @@ function assertDirectoryNotIsWritable(string $directory, string $message = ''): * Asserts that a file exists. * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * @@ -1025,7 +1532,6 @@ function assertFileExists(string $filename, string $message = ''): void * Asserts that a file does not exist. * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * @@ -1037,33 +1543,11 @@ function assertFileDoesNotExist(string $filename, string $message = ''): void } } -if (!function_exists('PHPUnit\Framework\assertFileNotExists')) { - /** - * Asserts that a file does not exist. - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4077 - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertFileNotExists - */ - function assertFileNotExists(string $filename, string $message = ''): void - { - Assert::assertFileNotExists(...func_get_args()); - } -} - if (!function_exists('PHPUnit\Framework\assertFileIsReadable')) { /** * Asserts that a file exists and is readable. * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * @@ -1080,7 +1564,6 @@ function assertFileIsReadable(string $file, string $message = ''): void * Asserts that a file exists and is not readable. * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * @@ -1092,33 +1575,11 @@ function assertFileIsNotReadable(string $file, string $message = ''): void } } -if (!function_exists('PHPUnit\Framework\assertFileNotIsReadable')) { - /** - * Asserts that a file exists and is not readable. - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4080 - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertFileNotIsReadable - */ - function assertFileNotIsReadable(string $file, string $message = ''): void - { - Assert::assertFileNotIsReadable(...func_get_args()); - } -} - if (!function_exists('PHPUnit\Framework\assertFileIsWritable')) { /** * Asserts that a file exists and is writable. * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * @@ -1135,7 +1596,6 @@ function assertFileIsWritable(string $file, string $message = ''): void * Asserts that a file exists and is not writable. * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * @@ -1147,41 +1607,19 @@ function assertFileIsNotWritable(string $file, string $message = ''): void } } -if (!function_exists('PHPUnit\Framework\assertFileNotIsWritable')) { - /** - * Asserts that a file exists and is not writable. - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4083 - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertFileNotIsWritable - */ - function assertFileNotIsWritable(string $file, string $message = ''): void - { - Assert::assertFileNotIsWritable(...func_get_args()); - } -} - if (!function_exists('PHPUnit\Framework\assertTrue')) { /** * Asserts that a condition is true. * * @throws ExpectationFailedException - * @throws InvalidArgumentException * - * @psalm-assert true $condition + * @phpstan-assert true $condition * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertTrue */ - function assertTrue($condition, string $message = ''): void + function assertTrue(mixed $condition, string $message = ''): void { Assert::assertTrue(...func_get_args()); } @@ -1192,15 +1630,14 @@ function assertTrue($condition, string $message = ''): void * Asserts that a condition is not true. * * @throws ExpectationFailedException - * @throws InvalidArgumentException * - * @psalm-assert !true $condition + * @phpstan-assert !true $condition * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertNotTrue */ - function assertNotTrue($condition, string $message = ''): void + function assertNotTrue(mixed $condition, string $message = ''): void { Assert::assertNotTrue(...func_get_args()); } @@ -1211,15 +1648,14 @@ function assertNotTrue($condition, string $message = ''): void * Asserts that a condition is false. * * @throws ExpectationFailedException - * @throws InvalidArgumentException * - * @psalm-assert false $condition + * @phpstan-assert false $condition * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertFalse */ - function assertFalse($condition, string $message = ''): void + function assertFalse(mixed $condition, string $message = ''): void { Assert::assertFalse(...func_get_args()); } @@ -1230,15 +1666,14 @@ function assertFalse($condition, string $message = ''): void * Asserts that a condition is not false. * * @throws ExpectationFailedException - * @throws InvalidArgumentException * - * @psalm-assert !false $condition + * @phpstan-assert !false $condition * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertNotFalse */ - function assertNotFalse($condition, string $message = ''): void + function assertNotFalse(mixed $condition, string $message = ''): void { Assert::assertNotFalse(...func_get_args()); } @@ -1249,15 +1684,14 @@ function assertNotFalse($condition, string $message = ''): void * Asserts that a variable is null. * * @throws ExpectationFailedException - * @throws InvalidArgumentException * - * @psalm-assert null $actual + * @phpstan-assert null $actual * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertNull */ - function assertNull($actual, string $message = ''): void + function assertNull(mixed $actual, string $message = ''): void { Assert::assertNull(...func_get_args()); } @@ -1268,15 +1702,14 @@ function assertNull($actual, string $message = ''): void * Asserts that a variable is not null. * * @throws ExpectationFailedException - * @throws InvalidArgumentException * - * @psalm-assert !null $actual + * @phpstan-assert !null $actual * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertNotNull */ - function assertNotNull($actual, string $message = ''): void + function assertNotNull(mixed $actual, string $message = ''): void { Assert::assertNotNull(...func_get_args()); } @@ -1287,13 +1720,12 @@ function assertNotNull($actual, string $message = ''): void * Asserts that a variable is finite. * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertFinite */ - function assertFinite($actual, string $message = ''): void + function assertFinite(mixed $actual, string $message = ''): void { Assert::assertFinite(...func_get_args()); } @@ -1304,13 +1736,12 @@ function assertFinite($actual, string $message = ''): void * Asserts that a variable is infinite. * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertInfinite */ - function assertInfinite($actual, string $message = ''): void + function assertInfinite(mixed $actual, string $message = ''): void { Assert::assertInfinite(...func_get_args()); } @@ -1321,143 +1752,28 @@ function assertInfinite($actual, string $message = ''): void * Asserts that a variable is nan. * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertNan */ - function assertNan($actual, string $message = ''): void + function assertNan(mixed $actual, string $message = ''): void { Assert::assertNan(...func_get_args()); } } -if (!function_exists('PHPUnit\Framework\assertClassHasAttribute')) { - /** - * Asserts that a class has a specified attribute. - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * @throws Exception - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertClassHasAttribute - */ - function assertClassHasAttribute(string $attributeName, string $className, string $message = ''): void - { - Assert::assertClassHasAttribute(...func_get_args()); - } -} - -if (!function_exists('PHPUnit\Framework\assertClassNotHasAttribute')) { - /** - * Asserts that a class does not have a specified attribute. - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * @throws Exception - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertClassNotHasAttribute - */ - function assertClassNotHasAttribute(string $attributeName, string $className, string $message = ''): void - { - Assert::assertClassNotHasAttribute(...func_get_args()); - } -} - -if (!function_exists('PHPUnit\Framework\assertClassHasStaticAttribute')) { - /** - * Asserts that a class has a specified static attribute. - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * @throws Exception - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertClassHasStaticAttribute - */ - function assertClassHasStaticAttribute(string $attributeName, string $className, string $message = ''): void - { - Assert::assertClassHasStaticAttribute(...func_get_args()); - } -} - -if (!function_exists('PHPUnit\Framework\assertClassNotHasStaticAttribute')) { - /** - * Asserts that a class does not have a specified static attribute. - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * @throws Exception - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertClassNotHasStaticAttribute - */ - function assertClassNotHasStaticAttribute(string $attributeName, string $className, string $message = ''): void - { - Assert::assertClassNotHasStaticAttribute(...func_get_args()); - } -} - -if (!function_exists('PHPUnit\Framework\assertObjectHasAttribute')) { - /** - * Asserts that an object has a specified attribute. - * - * @param object $object - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * @throws Exception - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertObjectHasAttribute - */ - function assertObjectHasAttribute(string $attributeName, $object, string $message = ''): void - { - Assert::assertObjectHasAttribute(...func_get_args()); - } -} - -if (!function_exists('PHPUnit\Framework\assertObjectNotHasAttribute')) { - /** - * Asserts that an object does not have a specified attribute. - * - * @param object $object - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * @throws Exception - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertObjectNotHasAttribute - */ - function assertObjectNotHasAttribute(string $attributeName, $object, string $message = ''): void - { - Assert::assertObjectNotHasAttribute(...func_get_args()); - } -} - if (!function_exists('PHPUnit\Framework\assertObjectHasProperty')) { /** * Asserts that an object has a specified property. * * @throws ExpectationFailedException - * @throws InvalidArgumentException - * @throws Exception * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertObjectHasProperty */ - function assertObjectHasProperty(string $attributeName, object $object, string $message = ''): void + function assertObjectHasProperty(string $propertyName, object $object, string $message = ''): void { Assert::assertObjectHasProperty(...func_get_args()); } @@ -1468,14 +1784,12 @@ function assertObjectHasProperty(string $attributeName, object $object, string $ * Asserts that an object does not have a specified property. * * @throws ExpectationFailedException - * @throws InvalidArgumentException - * @throws Exception * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertObjectNotHasProperty */ - function assertObjectNotHasProperty(string $attributeName, object $object, string $message = ''): void + function assertObjectNotHasProperty(string $propertyName, object $object, string $message = ''): void { Assert::assertObjectNotHasProperty(...func_get_args()); } @@ -1487,20 +1801,19 @@ function assertObjectNotHasProperty(string $attributeName, object $object, strin * Used on objects, it asserts that two variables reference * the same object. * - * @throws ExpectationFailedException - * @throws InvalidArgumentException + * @template ExpectedType * - * @psalm-template ExpectedType + * @param ExpectedType $expected * - * @psalm-param ExpectedType $expected + * @throws ExpectationFailedException * - * @psalm-assert =ExpectedType $actual + * @phpstan-assert =ExpectedType $actual * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertSame */ - function assertSame($expected, $actual, string $message = ''): void + function assertSame(mixed $expected, mixed $actual, string $message = ''): void { Assert::assertSame(...func_get_args()); } @@ -1513,13 +1826,12 @@ function assertSame($expected, $actual, string $message = ''): void * the same object. * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertNotSame */ - function assertNotSame($expected, $actual, string $message = ''): void + function assertNotSame(mixed $expected, mixed $actual, string $message = ''): void { Assert::assertNotSame(...func_get_args()); } @@ -1529,21 +1841,21 @@ function assertNotSame($expected, $actual, string $message = ''): void /** * Asserts that a variable is of a given type. * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * @throws Exception + * @template ExpectedType of object * - * @psalm-template ExpectedType of object + * @param class-string $expected * - * @psalm-param class-string $expected + * @throws Exception + * @throws ExpectationFailedException + * @throws UnknownClassOrInterfaceException * - * @psalm-assert =ExpectedType $actual + * @phpstan-assert =ExpectedType $actual * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertInstanceOf */ - function assertInstanceOf(string $expected, $actual, string $message = ''): void + function assertInstanceOf(string $expected, mixed $actual, string $message = ''): void { Assert::assertInstanceOf(...func_get_args()); } @@ -1553,21 +1865,20 @@ function assertInstanceOf(string $expected, $actual, string $message = ''): void /** * Asserts that a variable is not of a given type. * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * @throws Exception + * @template ExpectedType of object * - * @psalm-template ExpectedType of object + * @param class-string $expected * - * @psalm-param class-string $expected + * @throws Exception + * @throws ExpectationFailedException * - * @psalm-assert !ExpectedType $actual + * @phpstan-assert !ExpectedType $actual * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertNotInstanceOf */ - function assertNotInstanceOf(string $expected, $actual, string $message = ''): void + function assertNotInstanceOf(string $expected, mixed $actual, string $message = ''): void { Assert::assertNotInstanceOf(...func_get_args()); } @@ -1577,16 +1888,16 @@ function assertNotInstanceOf(string $expected, $actual, string $message = ''): v /** * Asserts that a variable is of type array. * + * @throws Exception * @throws ExpectationFailedException - * @throws InvalidArgumentException * - * @psalm-assert array $actual + * @phpstan-assert array $actual * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertIsArray */ - function assertIsArray($actual, string $message = ''): void + function assertIsArray(mixed $actual, string $message = ''): void { Assert::assertIsArray(...func_get_args()); } @@ -1596,16 +1907,16 @@ function assertIsArray($actual, string $message = ''): void /** * Asserts that a variable is of type bool. * + * @throws Exception * @throws ExpectationFailedException - * @throws InvalidArgumentException * - * @psalm-assert bool $actual + * @phpstan-assert bool $actual * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertIsBool */ - function assertIsBool($actual, string $message = ''): void + function assertIsBool(mixed $actual, string $message = ''): void { Assert::assertIsBool(...func_get_args()); } @@ -1615,16 +1926,16 @@ function assertIsBool($actual, string $message = ''): void /** * Asserts that a variable is of type float. * + * @throws Exception * @throws ExpectationFailedException - * @throws InvalidArgumentException * - * @psalm-assert float $actual + * @phpstan-assert float $actual * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertIsFloat */ - function assertIsFloat($actual, string $message = ''): void + function assertIsFloat(mixed $actual, string $message = ''): void { Assert::assertIsFloat(...func_get_args()); } @@ -1634,16 +1945,16 @@ function assertIsFloat($actual, string $message = ''): void /** * Asserts that a variable is of type int. * + * @throws Exception * @throws ExpectationFailedException - * @throws InvalidArgumentException * - * @psalm-assert int $actual + * @phpstan-assert int $actual * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertIsInt */ - function assertIsInt($actual, string $message = ''): void + function assertIsInt(mixed $actual, string $message = ''): void { Assert::assertIsInt(...func_get_args()); } @@ -1653,16 +1964,16 @@ function assertIsInt($actual, string $message = ''): void /** * Asserts that a variable is of type numeric. * + * @throws Exception * @throws ExpectationFailedException - * @throws InvalidArgumentException * - * @psalm-assert numeric $actual + * @phpstan-assert numeric $actual * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertIsNumeric */ - function assertIsNumeric($actual, string $message = ''): void + function assertIsNumeric(mixed $actual, string $message = ''): void { Assert::assertIsNumeric(...func_get_args()); } @@ -1672,16 +1983,16 @@ function assertIsNumeric($actual, string $message = ''): void /** * Asserts that a variable is of type object. * + * @throws Exception * @throws ExpectationFailedException - * @throws InvalidArgumentException * - * @psalm-assert object $actual + * @phpstan-assert object $actual * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertIsObject */ - function assertIsObject($actual, string $message = ''): void + function assertIsObject(mixed $actual, string $message = ''): void { Assert::assertIsObject(...func_get_args()); } @@ -1691,16 +2002,16 @@ function assertIsObject($actual, string $message = ''): void /** * Asserts that a variable is of type resource. * + * @throws Exception * @throws ExpectationFailedException - * @throws InvalidArgumentException * - * @psalm-assert resource $actual + * @phpstan-assert resource $actual * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertIsResource */ - function assertIsResource($actual, string $message = ''): void + function assertIsResource(mixed $actual, string $message = ''): void { Assert::assertIsResource(...func_get_args()); } @@ -1710,16 +2021,16 @@ function assertIsResource($actual, string $message = ''): void /** * Asserts that a variable is of type resource and is closed. * + * @throws Exception * @throws ExpectationFailedException - * @throws InvalidArgumentException * - * @psalm-assert resource $actual + * @phpstan-assert resource $actual * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertIsClosedResource */ - function assertIsClosedResource($actual, string $message = ''): void + function assertIsClosedResource(mixed $actual, string $message = ''): void { Assert::assertIsClosedResource(...func_get_args()); } @@ -1729,16 +2040,16 @@ function assertIsClosedResource($actual, string $message = ''): void /** * Asserts that a variable is of type string. * + * @throws Exception * @throws ExpectationFailedException - * @throws InvalidArgumentException * - * @psalm-assert string $actual + * @phpstan-assert string $actual * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertIsString */ - function assertIsString($actual, string $message = ''): void + function assertIsString(mixed $actual, string $message = ''): void { Assert::assertIsString(...func_get_args()); } @@ -1748,16 +2059,16 @@ function assertIsString($actual, string $message = ''): void /** * Asserts that a variable is of type scalar. * + * @throws Exception * @throws ExpectationFailedException - * @throws InvalidArgumentException * - * @psalm-assert scalar $actual + * @phpstan-assert scalar $actual * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertIsScalar */ - function assertIsScalar($actual, string $message = ''): void + function assertIsScalar(mixed $actual, string $message = ''): void { Assert::assertIsScalar(...func_get_args()); } @@ -1767,16 +2078,16 @@ function assertIsScalar($actual, string $message = ''): void /** * Asserts that a variable is of type callable. * + * @throws Exception * @throws ExpectationFailedException - * @throws InvalidArgumentException * - * @psalm-assert callable $actual + * @phpstan-assert callable $actual * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertIsCallable */ - function assertIsCallable($actual, string $message = ''): void + function assertIsCallable(mixed $actual, string $message = ''): void { Assert::assertIsCallable(...func_get_args()); } @@ -1786,16 +2097,16 @@ function assertIsCallable($actual, string $message = ''): void /** * Asserts that a variable is of type iterable. * + * @throws Exception * @throws ExpectationFailedException - * @throws InvalidArgumentException * - * @psalm-assert iterable $actual + * @phpstan-assert iterable $actual * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertIsIterable */ - function assertIsIterable($actual, string $message = ''): void + function assertIsIterable(mixed $actual, string $message = ''): void { Assert::assertIsIterable(...func_get_args()); } @@ -1805,16 +2116,16 @@ function assertIsIterable($actual, string $message = ''): void /** * Asserts that a variable is not of type array. * + * @throws Exception * @throws ExpectationFailedException - * @throws InvalidArgumentException * - * @psalm-assert !array $actual + * @phpstan-assert !array $actual * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertIsNotArray */ - function assertIsNotArray($actual, string $message = ''): void + function assertIsNotArray(mixed $actual, string $message = ''): void { Assert::assertIsNotArray(...func_get_args()); } @@ -1824,16 +2135,16 @@ function assertIsNotArray($actual, string $message = ''): void /** * Asserts that a variable is not of type bool. * + * @throws Exception * @throws ExpectationFailedException - * @throws InvalidArgumentException * - * @psalm-assert !bool $actual + * @phpstan-assert !bool $actual * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertIsNotBool */ - function assertIsNotBool($actual, string $message = ''): void + function assertIsNotBool(mixed $actual, string $message = ''): void { Assert::assertIsNotBool(...func_get_args()); } @@ -1843,16 +2154,16 @@ function assertIsNotBool($actual, string $message = ''): void /** * Asserts that a variable is not of type float. * + * @throws Exception * @throws ExpectationFailedException - * @throws InvalidArgumentException * - * @psalm-assert !float $actual + * @phpstan-assert !float $actual * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertIsNotFloat */ - function assertIsNotFloat($actual, string $message = ''): void + function assertIsNotFloat(mixed $actual, string $message = ''): void { Assert::assertIsNotFloat(...func_get_args()); } @@ -1862,16 +2173,16 @@ function assertIsNotFloat($actual, string $message = ''): void /** * Asserts that a variable is not of type int. * + * @throws Exception * @throws ExpectationFailedException - * @throws InvalidArgumentException * - * @psalm-assert !int $actual + * @phpstan-assert !int $actual * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertIsNotInt */ - function assertIsNotInt($actual, string $message = ''): void + function assertIsNotInt(mixed $actual, string $message = ''): void { Assert::assertIsNotInt(...func_get_args()); } @@ -1881,16 +2192,16 @@ function assertIsNotInt($actual, string $message = ''): void /** * Asserts that a variable is not of type numeric. * + * @throws Exception * @throws ExpectationFailedException - * @throws InvalidArgumentException * - * @psalm-assert !numeric $actual + * @phpstan-assert !numeric $actual * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertIsNotNumeric */ - function assertIsNotNumeric($actual, string $message = ''): void + function assertIsNotNumeric(mixed $actual, string $message = ''): void { Assert::assertIsNotNumeric(...func_get_args()); } @@ -1900,16 +2211,16 @@ function assertIsNotNumeric($actual, string $message = ''): void /** * Asserts that a variable is not of type object. * + * @throws Exception * @throws ExpectationFailedException - * @throws InvalidArgumentException * - * @psalm-assert !object $actual + * @phpstan-assert !object $actual * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertIsNotObject */ - function assertIsNotObject($actual, string $message = ''): void + function assertIsNotObject(mixed $actual, string $message = ''): void { Assert::assertIsNotObject(...func_get_args()); } @@ -1919,16 +2230,16 @@ function assertIsNotObject($actual, string $message = ''): void /** * Asserts that a variable is not of type resource. * + * @throws Exception * @throws ExpectationFailedException - * @throws InvalidArgumentException * - * @psalm-assert !resource $actual + * @phpstan-assert !resource $actual * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertIsNotResource */ - function assertIsNotResource($actual, string $message = ''): void + function assertIsNotResource(mixed $actual, string $message = ''): void { Assert::assertIsNotResource(...func_get_args()); } @@ -1938,16 +2249,16 @@ function assertIsNotResource($actual, string $message = ''): void /** * Asserts that a variable is not of type resource. * + * @throws Exception * @throws ExpectationFailedException - * @throws InvalidArgumentException * - * @psalm-assert !resource $actual + * @phpstan-assert !resource $actual * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertIsNotClosedResource */ - function assertIsNotClosedResource($actual, string $message = ''): void + function assertIsNotClosedResource(mixed $actual, string $message = ''): void { Assert::assertIsNotClosedResource(...func_get_args()); } @@ -1957,16 +2268,16 @@ function assertIsNotClosedResource($actual, string $message = ''): void /** * Asserts that a variable is not of type string. * + * @throws Exception * @throws ExpectationFailedException - * @throws InvalidArgumentException * - * @psalm-assert !string $actual + * @phpstan-assert !string $actual * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertIsNotString */ - function assertIsNotString($actual, string $message = ''): void + function assertIsNotString(mixed $actual, string $message = ''): void { Assert::assertIsNotString(...func_get_args()); } @@ -1976,16 +2287,16 @@ function assertIsNotString($actual, string $message = ''): void /** * Asserts that a variable is not of type scalar. * + * @throws Exception * @throws ExpectationFailedException - * @throws InvalidArgumentException * - * @psalm-assert !scalar $actual + * @phpstan-assert !scalar $actual * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertIsNotScalar */ - function assertIsNotScalar($actual, string $message = ''): void + function assertIsNotScalar(mixed $actual, string $message = ''): void { Assert::assertIsNotScalar(...func_get_args()); } @@ -1995,16 +2306,16 @@ function assertIsNotScalar($actual, string $message = ''): void /** * Asserts that a variable is not of type callable. * + * @throws Exception * @throws ExpectationFailedException - * @throws InvalidArgumentException * - * @psalm-assert !callable $actual + * @phpstan-assert !callable $actual * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertIsNotCallable */ - function assertIsNotCallable($actual, string $message = ''): void + function assertIsNotCallable(mixed $actual, string $message = ''): void { Assert::assertIsNotCallable(...func_get_args()); } @@ -2014,16 +2325,16 @@ function assertIsNotCallable($actual, string $message = ''): void /** * Asserts that a variable is not of type iterable. * + * @throws Exception * @throws ExpectationFailedException - * @throws InvalidArgumentException * - * @psalm-assert !iterable $actual + * @phpstan-assert !iterable $actual * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertIsNotIterable */ - function assertIsNotIterable($actual, string $message = ''): void + function assertIsNotIterable(mixed $actual, string $message = ''): void { Assert::assertIsNotIterable(...func_get_args()); } @@ -2034,7 +2345,6 @@ function assertIsNotIterable($actual, string $message = ''): void * Asserts that a string matches a given regular expression. * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * @@ -2046,62 +2356,19 @@ function assertMatchesRegularExpression(string $pattern, string $string, string } } -if (!function_exists('PHPUnit\Framework\assertRegExp')) { - /** - * Asserts that a string matches a given regular expression. - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4086 - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertRegExp - */ - function assertRegExp(string $pattern, string $string, string $message = ''): void - { - Assert::assertRegExp(...func_get_args()); - } -} - if (!function_exists('PHPUnit\Framework\assertDoesNotMatchRegularExpression')) { /** * Asserts that a string does not match a given regular expression. * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertDoesNotMatchRegularExpression - */ - function assertDoesNotMatchRegularExpression(string $pattern, string $string, string $message = ''): void - { - Assert::assertDoesNotMatchRegularExpression(...func_get_args()); - } -} - -if (!function_exists('PHPUnit\Framework\assertNotRegExp')) { - /** - * Asserts that a string does not match a given regular expression. - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4089 + * @throws ExpectationFailedException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * - * @see Assert::assertNotRegExp + * @see Assert::assertDoesNotMatchRegularExpression */ - function assertNotRegExp(string $pattern, string $string, string $message = ''): void + function assertDoesNotMatchRegularExpression(string $pattern, string $string, string $message = ''): void { - Assert::assertNotRegExp(...func_get_args()); + Assert::assertDoesNotMatchRegularExpression(...func_get_args()); } } @@ -2110,18 +2377,18 @@ function assertNotRegExp(string $pattern, string $string, string $message = ''): * Assert that the size of two arrays (or `Countable` or `Traversable` objects) * is the same. * - * @param Countable|iterable $expected - * @param Countable|iterable $actual + * @param Countable|iterable $expected + * @param Countable|iterable $actual * - * @throws ExpectationFailedException - * @throws InvalidArgumentException * @throws Exception + * @throws ExpectationFailedException + * @throws GeneratorNotSupportedException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertSameSize */ - function assertSameSize($expected, $actual, string $message = ''): void + function assertSameSize(Countable|iterable $expected, Countable|iterable $actual, string $message = ''): void { Assert::assertSameSize(...func_get_args()); } @@ -2132,88 +2399,114 @@ function assertSameSize($expected, $actual, string $message = ''): void * Assert that the size of two arrays (or `Countable` or `Traversable` objects) * is not the same. * - * @param Countable|iterable $expected - * @param Countable|iterable $actual + * @param Countable|iterable $expected + * @param Countable|iterable $actual * - * @throws ExpectationFailedException - * @throws InvalidArgumentException * @throws Exception + * @throws ExpectationFailedException + * @throws GeneratorNotSupportedException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertNotSameSize */ - function assertNotSameSize($expected, $actual, string $message = ''): void + function assertNotSameSize(Countable|iterable $expected, Countable|iterable $actual, string $message = ''): void { Assert::assertNotSameSize(...func_get_args()); } } -if (!function_exists('PHPUnit\Framework\assertStringMatchesFormat')) { +if (!function_exists('PHPUnit\Framework\assertStringContainsStringIgnoringLineEndings')) { + /** + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertStringContainsStringIgnoringLineEndings + */ + function assertStringContainsStringIgnoringLineEndings(string $needle, string $haystack, string $message = ''): void + { + Assert::assertStringContainsStringIgnoringLineEndings(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertStringEqualsStringIgnoringLineEndings')) { + /** + * Asserts that two strings are equal except for line endings. + * + * @throws ExpectationFailedException + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @see Assert::assertStringEqualsStringIgnoringLineEndings + */ + function assertStringEqualsStringIgnoringLineEndings(string $expected, string $actual, string $message = ''): void + { + Assert::assertStringEqualsStringIgnoringLineEndings(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\assertFileMatchesFormat')) { /** * Asserts that a string matches a given format string. * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * - * @see Assert::assertStringMatchesFormat + * @see Assert::assertFileMatchesFormat */ - function assertStringMatchesFormat(string $format, string $string, string $message = ''): void + function assertFileMatchesFormat(string $format, string $actualFile, string $message = ''): void { - Assert::assertStringMatchesFormat(...func_get_args()); + Assert::assertFileMatchesFormat(...func_get_args()); } } -if (!function_exists('PHPUnit\Framework\assertStringNotMatchesFormat')) { +if (!function_exists('PHPUnit\Framework\assertFileMatchesFormatFile')) { /** - * Asserts that a string does not match a given format string. + * Asserts that a string matches a given format string. * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * - * @see Assert::assertStringNotMatchesFormat + * @see Assert::assertFileMatchesFormatFile */ - function assertStringNotMatchesFormat(string $format, string $string, string $message = ''): void + function assertFileMatchesFormatFile(string $formatFile, string $actualFile, string $message = ''): void { - Assert::assertStringNotMatchesFormat(...func_get_args()); + Assert::assertFileMatchesFormatFile(...func_get_args()); } } -if (!function_exists('PHPUnit\Framework\assertStringMatchesFormatFile')) { +if (!function_exists('PHPUnit\Framework\assertStringMatchesFormat')) { /** - * Asserts that a string matches a given format file. + * Asserts that a string matches a given format string. * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * - * @see Assert::assertStringMatchesFormatFile + * @see Assert::assertStringMatchesFormat */ - function assertStringMatchesFormatFile(string $formatFile, string $string, string $message = ''): void + function assertStringMatchesFormat(string $format, string $string, string $message = ''): void { - Assert::assertStringMatchesFormatFile(...func_get_args()); + Assert::assertStringMatchesFormat(...func_get_args()); } } -if (!function_exists('PHPUnit\Framework\assertStringNotMatchesFormatFile')) { +if (!function_exists('PHPUnit\Framework\assertStringMatchesFormatFile')) { /** - * Asserts that a string does not match a given format string. + * Asserts that a string matches a given format file. * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * - * @see Assert::assertStringNotMatchesFormatFile + * @see Assert::assertStringMatchesFormatFile */ - function assertStringNotMatchesFormatFile(string $formatFile, string $string, string $message = ''): void + function assertStringMatchesFormatFile(string $formatFile, string $string, string $message = ''): void { - Assert::assertStringNotMatchesFormatFile(...func_get_args()); + Assert::assertStringMatchesFormatFile(...func_get_args()); } } @@ -2221,6 +2514,8 @@ function assertStringNotMatchesFormatFile(string $formatFile, string $string, st /** * Asserts that a string starts with a given prefix. * + * @param non-empty-string $prefix + * * @throws ExpectationFailedException * @throws InvalidArgumentException * @@ -2238,8 +2533,7 @@ function assertStringStartsWith(string $prefix, string $string, string $message /** * Asserts that a string starts not with a given prefix. * - * @param string $prefix - * @param string $string + * @param non-empty-string $prefix * * @throws ExpectationFailedException * @throws InvalidArgumentException @@ -2248,7 +2542,7 @@ function assertStringStartsWith(string $prefix, string $string, string $message * * @see Assert::assertStringStartsNotWith */ - function assertStringStartsNotWith($prefix, $string, string $message = ''): void + function assertStringStartsNotWith(string $prefix, string $string, string $message = ''): void { Assert::assertStringStartsNotWith(...func_get_args()); } @@ -2257,7 +2551,6 @@ function assertStringStartsNotWith($prefix, $string, string $message = ''): void if (!function_exists('PHPUnit\Framework\assertStringContainsString')) { /** * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * @@ -2272,7 +2565,6 @@ function assertStringContainsString(string $needle, string $haystack, string $me if (!function_exists('PHPUnit\Framework\assertStringContainsStringIgnoringCase')) { /** * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * @@ -2287,7 +2579,6 @@ function assertStringContainsStringIgnoringCase(string $needle, string $haystack if (!function_exists('PHPUnit\Framework\assertStringNotContainsString')) { /** * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * @@ -2302,7 +2593,6 @@ function assertStringNotContainsString(string $needle, string $haystack, string if (!function_exists('PHPUnit\Framework\assertStringNotContainsStringIgnoringCase')) { /** * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * @@ -2318,6 +2608,8 @@ function assertStringNotContainsStringIgnoringCase(string $needle, string $hayst /** * Asserts that a string ends with a given suffix. * + * @param non-empty-string $suffix + * * @throws ExpectationFailedException * @throws InvalidArgumentException * @@ -2335,6 +2627,8 @@ function assertStringEndsWith(string $suffix, string $string, string $message = /** * Asserts that a string ends not with a given suffix. * + * @param non-empty-string $suffix + * * @throws ExpectationFailedException * @throws InvalidArgumentException * @@ -2352,9 +2646,9 @@ function assertStringEndsNotWith(string $suffix, string $string, string $message /** * Asserts that two XML files are equal. * - * @throws ExpectationFailedException - * @throws InvalidArgumentException * @throws Exception + * @throws ExpectationFailedException + * @throws XmlException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * @@ -2370,9 +2664,8 @@ function assertXmlFileEqualsXmlFile(string $expectedFile, string $actualFile, st /** * Asserts that two XML files are not equal. * - * @throws ExpectationFailedException - * @throws InvalidArgumentException * @throws \PHPUnit\Util\Exception + * @throws ExpectationFailedException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * @@ -2388,17 +2681,14 @@ function assertXmlFileNotEqualsXmlFile(string $expectedFile, string $actualFile, /** * Asserts that two XML documents are equal. * - * @param DOMDocument|string $actualXml - * * @throws ExpectationFailedException - * @throws InvalidArgumentException - * @throws \PHPUnit\Util\Xml\Exception + * @throws XmlException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertXmlStringEqualsXmlFile */ - function assertXmlStringEqualsXmlFile(string $expectedFile, $actualXml, string $message = ''): void + function assertXmlStringEqualsXmlFile(string $expectedFile, string $actualXml, string $message = ''): void { Assert::assertXmlStringEqualsXmlFile(...func_get_args()); } @@ -2408,17 +2698,14 @@ function assertXmlStringEqualsXmlFile(string $expectedFile, $actualXml, string $ /** * Asserts that two XML documents are not equal. * - * @param DOMDocument|string $actualXml - * * @throws ExpectationFailedException - * @throws InvalidArgumentException - * @throws \PHPUnit\Util\Xml\Exception + * @throws XmlException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertXmlStringNotEqualsXmlFile */ - function assertXmlStringNotEqualsXmlFile(string $expectedFile, $actualXml, string $message = ''): void + function assertXmlStringNotEqualsXmlFile(string $expectedFile, string $actualXml, string $message = ''): void { Assert::assertXmlStringNotEqualsXmlFile(...func_get_args()); } @@ -2428,18 +2715,14 @@ function assertXmlStringNotEqualsXmlFile(string $expectedFile, $actualXml, strin /** * Asserts that two XML documents are equal. * - * @param DOMDocument|string $expectedXml - * @param DOMDocument|string $actualXml - * * @throws ExpectationFailedException - * @throws InvalidArgumentException - * @throws \PHPUnit\Util\Xml\Exception + * @throws XmlException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertXmlStringEqualsXmlString */ - function assertXmlStringEqualsXmlString($expectedXml, $actualXml, string $message = ''): void + function assertXmlStringEqualsXmlString(string $expectedXml, string $actualXml, string $message = ''): void { Assert::assertXmlStringEqualsXmlString(...func_get_args()); } @@ -2449,57 +2732,30 @@ function assertXmlStringEqualsXmlString($expectedXml, $actualXml, string $messag /** * Asserts that two XML documents are not equal. * - * @param DOMDocument|string $expectedXml - * @param DOMDocument|string $actualXml - * * @throws ExpectationFailedException - * @throws InvalidArgumentException - * @throws \PHPUnit\Util\Xml\Exception + * @throws XmlException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertXmlStringNotEqualsXmlString */ - function assertXmlStringNotEqualsXmlString($expectedXml, $actualXml, string $message = ''): void + function assertXmlStringNotEqualsXmlString(string $expectedXml, string $actualXml, string $message = ''): void { Assert::assertXmlStringNotEqualsXmlString(...func_get_args()); } } -if (!function_exists('PHPUnit\Framework\assertEqualXMLStructure')) { - /** - * Asserts that a hierarchy of DOMElements matches. - * - * @throws AssertionFailedError - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4091 - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see Assert::assertEqualXMLStructure - */ - function assertEqualXMLStructure(DOMElement $expectedElement, DOMElement $actualElement, bool $checkAttributes = false, string $message = ''): void - { - Assert::assertEqualXMLStructure(...func_get_args()); - } -} - if (!function_exists('PHPUnit\Framework\assertThat')) { /** * Evaluates a PHPUnit\Framework\Constraint matcher object. * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertThat */ - function assertThat($value, Constraint $constraint, string $message = ''): void + function assertThat(mixed $value, Constraint $constraint, string $message = ''): void { Assert::assertThat(...func_get_args()); } @@ -2510,13 +2766,12 @@ function assertThat($value, Constraint $constraint, string $message = ''): void * Asserts that a string is a valid JSON string. * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertJson */ - function assertJson(string $actualJson, string $message = ''): void + function assertJson(string $actual, string $message = ''): void { Assert::assertJson(...func_get_args()); } @@ -2527,7 +2782,6 @@ function assertJson(string $actualJson, string $message = ''): void * Asserts that two given JSON encoded objects or arrays are equal. * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * @@ -2543,17 +2797,13 @@ function assertJsonStringEqualsJsonString(string $expectedJson, string $actualJs /** * Asserts that two given JSON encoded objects or arrays are not equal. * - * @param string $expectedJson - * @param string $actualJson - * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @see Assert::assertJsonStringNotEqualsJsonString */ - function assertJsonStringNotEqualsJsonString($expectedJson, $actualJson, string $message = ''): void + function assertJsonStringNotEqualsJsonString(string $expectedJson, string $actualJson, string $message = ''): void { Assert::assertJsonStringNotEqualsJsonString(...func_get_args()); } @@ -2564,7 +2814,6 @@ function assertJsonStringNotEqualsJsonString($expectedJson, $actualJson, string * Asserts that the generated JSON encoded object and the content of the given file are equal. * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * @@ -2581,7 +2830,6 @@ function assertJsonStringEqualsJsonFile(string $expectedFile, string $actualJson * Asserts that the generated JSON encoded object and the content of the given file are not equal. * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * @@ -2598,7 +2846,6 @@ function assertJsonStringNotEqualsJsonFile(string $expectedFile, string $actualJ * Asserts that two JSON files are equal. * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * @@ -2615,7 +2862,6 @@ function assertJsonFileEqualsJsonFile(string $expectedFile, string $actualFile, * Asserts that two JSON files are not equal. * * @throws ExpectationFailedException - * @throws InvalidArgumentException * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * @@ -2628,14 +2874,14 @@ function assertJsonFileNotEqualsJsonFile(string $expectedFile, string $actualFil } if (!function_exists('PHPUnit\Framework\logicalAnd')) { - function logicalAnd(): LogicalAnd + function logicalAnd(mixed ...$constraints): LogicalAnd { return Assert::logicalAnd(...func_get_args()); } } if (!function_exists('PHPUnit\Framework\logicalOr')) { - function logicalOr(): LogicalOr + function logicalOr(mixed ...$constraints): LogicalOr { return Assert::logicalOr(...func_get_args()); } @@ -2649,7 +2895,7 @@ function logicalNot(Constraint $constraint): LogicalNot } if (!function_exists('PHPUnit\Framework\logicalXor')) { - function logicalXor(): LogicalXor + function logicalXor(mixed ...$constraints): LogicalXor { return Assert::logicalXor(...func_get_args()); } @@ -2669,13 +2915,6 @@ function isTrue(): IsTrue } } -if (!function_exists('PHPUnit\Framework\callback')) { - function callback(callable $callback): Callback - { - return Assert::callback(...func_get_args()); - } -} - if (!function_exists('PHPUnit\Framework\isFalse')) { function isFalse(): IsFalse { @@ -2719,14 +2958,14 @@ function isNan(): IsNan } if (!function_exists('PHPUnit\Framework\containsEqual')) { - function containsEqual($value): TraversableContainsEqual + function containsEqual(mixed $value): TraversableContainsEqual { return Assert::containsEqual(...func_get_args()); } } if (!function_exists('PHPUnit\Framework\containsIdentical')) { - function containsIdentical($value): TraversableContainsIdentical + function containsIdentical(mixed $value): TraversableContainsIdentical { return Assert::containsIdentical(...func_get_args()); } @@ -2739,6 +2978,97 @@ function containsOnly(string $type): TraversableContainsOnly } } +if (!function_exists('PHPUnit\Framework\containsOnlyArray')) { + function containsOnlyArray(): TraversableContainsOnly + { + return Assert::containsOnlyArray(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\containsOnlyBool')) { + function containsOnlyBool(): TraversableContainsOnly + { + return Assert::containsOnlyBool(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\containsOnlyCallable')) { + function containsOnlyCallable(): TraversableContainsOnly + { + return Assert::containsOnlyCallable(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\containsOnlyFloat')) { + function containsOnlyFloat(): TraversableContainsOnly + { + return Assert::containsOnlyFloat(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\containsOnlyInt')) { + function containsOnlyInt(): TraversableContainsOnly + { + return Assert::containsOnlyInt(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\containsOnlyIterable')) { + function containsOnlyIterable(): TraversableContainsOnly + { + return Assert::containsOnlyIterable(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\containsOnlyNull')) { + function containsOnlyNull(): TraversableContainsOnly + { + return Assert::containsOnlyNull(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\containsOnlyNumeric')) { + function containsOnlyNumeric(): TraversableContainsOnly + { + return Assert::containsOnlyNumeric(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\containsOnlyObject')) { + function containsOnlyObject(): TraversableContainsOnly + { + return Assert::containsOnlyObject(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\containsOnlyResource')) { + function containsOnlyResource(): TraversableContainsOnly + { + return Assert::containsOnlyResource(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\containsOnlyClosedResource')) { + function containsOnlyClosedResource(): TraversableContainsOnly + { + return Assert::containsOnlyClosedResource(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\containsOnlyScalar')) { + function containsOnlyScalar(): TraversableContainsOnly + { + return Assert::containsOnlyScalar(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\containsOnlyString')) { + function containsOnlyString(): TraversableContainsOnly + { + return Assert::containsOnlyString(...func_get_args()); + } +} + if (!function_exists('PHPUnit\Framework\containsOnlyInstancesOf')) { function containsOnlyInstancesOf(string $className): TraversableContainsOnly { @@ -2747,35 +3077,42 @@ function containsOnlyInstancesOf(string $className): TraversableContainsOnly } if (!function_exists('PHPUnit\Framework\arrayHasKey')) { - function arrayHasKey($key): ArrayHasKey + function arrayHasKey(mixed $key): ArrayHasKey { return Assert::arrayHasKey(...func_get_args()); } } +if (!function_exists('PHPUnit\Framework\isList')) { + function isList(): IsList + { + return Assert::isList(...func_get_args()); + } +} + if (!function_exists('PHPUnit\Framework\equalTo')) { - function equalTo($value): IsEqual + function equalTo(mixed $value): IsEqual { return Assert::equalTo(...func_get_args()); } } if (!function_exists('PHPUnit\Framework\equalToCanonicalizing')) { - function equalToCanonicalizing($value): IsEqualCanonicalizing + function equalToCanonicalizing(mixed $value): IsEqualCanonicalizing { return Assert::equalToCanonicalizing(...func_get_args()); } } if (!function_exists('PHPUnit\Framework\equalToIgnoringCase')) { - function equalToIgnoringCase($value): IsEqualIgnoringCase + function equalToIgnoringCase(mixed $value): IsEqualIgnoringCase { return Assert::equalToIgnoringCase(...func_get_args()); } } if (!function_exists('PHPUnit\Framework\equalToWithDelta')) { - function equalToWithDelta($value, float $delta): IsEqualWithDelta + function equalToWithDelta(mixed $value, float $delta): IsEqualWithDelta { return Assert::equalToWithDelta(...func_get_args()); } @@ -2817,51 +3154,114 @@ function fileExists(): FileExists } if (!function_exists('PHPUnit\Framework\greaterThan')) { - function greaterThan($value): GreaterThan + function greaterThan(mixed $value): GreaterThan { return Assert::greaterThan(...func_get_args()); } } if (!function_exists('PHPUnit\Framework\greaterThanOrEqual')) { - function greaterThanOrEqual($value): LogicalOr + function greaterThanOrEqual(mixed $value): LogicalOr { return Assert::greaterThanOrEqual(...func_get_args()); } } -if (!function_exists('PHPUnit\Framework\classHasAttribute')) { - function classHasAttribute(string $attributeName): ClassHasAttribute +if (!function_exists('PHPUnit\Framework\identicalTo')) { + function identicalTo(mixed $value): IsIdentical { - return Assert::classHasAttribute(...func_get_args()); + return Assert::identicalTo(...func_get_args()); } } -if (!function_exists('PHPUnit\Framework\classHasStaticAttribute')) { - function classHasStaticAttribute(string $attributeName): ClassHasStaticAttribute +if (!function_exists('PHPUnit\Framework\isInstanceOf')) { + function isInstanceOf(string $className): IsInstanceOf { - return Assert::classHasStaticAttribute(...func_get_args()); + return Assert::isInstanceOf(...func_get_args()); } } -if (!function_exists('PHPUnit\Framework\objectHasAttribute')) { - function objectHasAttribute($attributeName): ObjectHasAttribute +if (!function_exists('PHPUnit\Framework\isArray')) { + function isArray(): IsType { - return Assert::objectHasAttribute(...func_get_args()); + return Assert::isArray(...func_get_args()); } } -if (!function_exists('PHPUnit\Framework\identicalTo')) { - function identicalTo($value): IsIdentical +if (!function_exists('PHPUnit\Framework\isBool')) { + function isBool(): IsType { - return Assert::identicalTo(...func_get_args()); + return Assert::isBool(...func_get_args()); } } -if (!function_exists('PHPUnit\Framework\isInstanceOf')) { - function isInstanceOf(string $className): IsInstanceOf +if (!function_exists('PHPUnit\Framework\isCallable')) { + function isCallable(): IsType { - return Assert::isInstanceOf(...func_get_args()); + return Assert::isCallable(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\isFloat')) { + function isFloat(): IsType + { + return Assert::isFloat(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\isInt')) { + function isInt(): IsType + { + return Assert::isInt(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\isIterable')) { + function isIterable(): IsType + { + return Assert::isIterable(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\isNumeric')) { + function isNumeric(): IsType + { + return Assert::isNumeric(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\isObject')) { + function isObject(): IsType + { + return Assert::isObject(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\isResource')) { + function isResource(): IsType + { + return Assert::isResource(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\isClosedResource')) { + function isClosedResource(): IsType + { + return Assert::isClosedResource(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\isScalar')) { + function isScalar(): IsType + { + return Assert::isScalar(...func_get_args()); + } +} + +if (!function_exists('PHPUnit\Framework\isString')) { + function isString(): IsType + { + return Assert::isString(...func_get_args()); } } @@ -2873,14 +3273,14 @@ function isType(string $type): IsType } if (!function_exists('PHPUnit\Framework\lessThan')) { - function lessThan($value): LessThan + function lessThan(mixed $value): LessThan { return Assert::lessThan(...func_get_args()); } } if (!function_exists('PHPUnit\Framework\lessThanOrEqual')) { - function lessThanOrEqual($value): LogicalOr + function lessThanOrEqual(mixed $value): LogicalOr { return Assert::lessThanOrEqual(...func_get_args()); } @@ -2901,7 +3301,7 @@ function matches(string $string): StringMatchesFormatDescription } if (!function_exists('PHPUnit\Framework\stringStartsWith')) { - function stringStartsWith($prefix): StringStartsWith + function stringStartsWith(string $prefix): StringStartsWith { return Assert::stringStartsWith(...func_get_args()); } @@ -2921,6 +3321,13 @@ function stringEndsWith(string $suffix): StringEndsWith } } +if (!function_exists('PHPUnit\Framework\stringEqualsStringIgnoringLineEndings')) { + function stringEqualsStringIgnoringLineEndings(string $string): StringEqualsStringIgnoringLineEndings + { + return Assert::stringEqualsStringIgnoringLineEndings(...func_get_args()); + } +} + if (!function_exists('PHPUnit\Framework\countOf')) { function countOf(int $count): Count { @@ -2935,6 +3342,20 @@ function objectEquals(object $object, string $method = 'equals'): ObjectEquals } } +if (!function_exists('PHPUnit\Framework\callback')) { + /** + * @template CallbackInput of mixed + * + * @param callable(CallbackInput $callback): bool $callback + * + * @return Callback + */ + function callback(callable $callback): Callback + { + return Assert::callback($callback); + } +} + if (!function_exists('PHPUnit\Framework\any')) { /** * Returns a matcher that matches when the method is executed @@ -3011,69 +3432,9 @@ function atMost(int $allowedInvocations): InvokedAtMostCountMatcher } } -if (!function_exists('PHPUnit\Framework\at')) { - /** - * Returns a matcher that matches when the method is executed - * at the given index. - */ - function at(int $index): InvokedAtIndexMatcher - { - return new InvokedAtIndexMatcher($index); - } -} - -if (!function_exists('PHPUnit\Framework\returnValue')) { - function returnValue($value): ReturnStub - { - return new ReturnStub($value); - } -} - -if (!function_exists('PHPUnit\Framework\returnValueMap')) { - function returnValueMap(array $valueMap): ReturnValueMapStub - { - return new ReturnValueMapStub($valueMap); - } -} - -if (!function_exists('PHPUnit\Framework\returnArgument')) { - function returnArgument(int $argumentIndex): ReturnArgumentStub - { - return new ReturnArgumentStub($argumentIndex); - } -} - -if (!function_exists('PHPUnit\Framework\returnCallback')) { - function returnCallback($callback): ReturnCallbackStub - { - return new ReturnCallbackStub($callback); - } -} - -if (!function_exists('PHPUnit\Framework\returnSelf')) { - /** - * Returns the current object. - * - * This method is useful when mocking a fluent interface. - */ - function returnSelf(): ReturnSelfStub - { - return new ReturnSelfStub; - } -} - if (!function_exists('PHPUnit\Framework\throwException')) { function throwException(Throwable $exception): ExceptionStub { return new ExceptionStub($exception); } } - -if (!function_exists('PHPUnit\Framework\onConsecutiveCalls')) { - function onConsecutiveCalls(): ConsecutiveCallsStub - { - $args = func_get_args(); - - return new ConsecutiveCallsStub($args); - } -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/After.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/After.php new file mode 100644 index 000000000..6d36d2913 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/After.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_METHOD)] +final readonly class After +{ + private int $priority; + + public function __construct(int $priority = 0) + { + $this->priority = $priority; + } + + public function priority(): int + { + return $this->priority; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/AfterClass.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/AfterClass.php new file mode 100644 index 000000000..d4a9d6f4c --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/AfterClass.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_METHOD)] +final readonly class AfterClass +{ + private int $priority; + + public function __construct(int $priority = 0) + { + $this->priority = $priority; + } + + public function priority(): int + { + return $this->priority; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/BackupGlobals.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/BackupGlobals.php new file mode 100644 index 000000000..f9526ca28 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/BackupGlobals.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)] +final readonly class BackupGlobals +{ + private bool $enabled; + + public function __construct(bool $enabled) + { + $this->enabled = $enabled; + } + + public function enabled(): bool + { + return $this->enabled; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/BackupStaticProperties.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/BackupStaticProperties.php new file mode 100644 index 000000000..e633cf88a --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/BackupStaticProperties.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)] +final readonly class BackupStaticProperties +{ + private bool $enabled; + + public function __construct(bool $enabled) + { + $this->enabled = $enabled; + } + + public function enabled(): bool + { + return $this->enabled; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/Before.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/Before.php new file mode 100644 index 000000000..c2af00b7f --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/Before.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_METHOD)] +final readonly class Before +{ + private int $priority; + + public function __construct(int $priority = 0) + { + $this->priority = $priority; + } + + public function priority(): int + { + return $this->priority; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/BeforeClass.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/BeforeClass.php new file mode 100644 index 000000000..2bb5a07b5 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/BeforeClass.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_METHOD)] +final readonly class BeforeClass +{ + private int $priority; + + public function __construct(int $priority = 0) + { + $this->priority = $priority; + } + + public function priority(): int + { + return $this->priority; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/CoversClass.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/CoversClass.php new file mode 100644 index 000000000..2cf0b2dd2 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/CoversClass.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 PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)] +final readonly class CoversClass +{ + /** + * @var class-string + */ + private string $className; + + /** + * @param class-string $className + */ + public function __construct(string $className) + { + $this->className = $className; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/CoversClassesThatExtendClass.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/CoversClassesThatExtendClass.php new file mode 100644 index 000000000..486fc5b0a --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/CoversClassesThatExtendClass.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 PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)] +final readonly class CoversClassesThatExtendClass +{ + /** + * @var class-string + */ + private string $className; + + /** + * @param class-string $className + */ + public function __construct(string $className) + { + $this->className = $className; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/CoversClassesThatImplementInterface.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/CoversClassesThatImplementInterface.php new file mode 100644 index 000000000..69bcd8460 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/CoversClassesThatImplementInterface.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 PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)] +final readonly class CoversClassesThatImplementInterface +{ + /** + * @var class-string + */ + private string $interfaceName; + + /** + * @param class-string $interfaceName + */ + public function __construct(string $interfaceName) + { + $this->interfaceName = $interfaceName; + } + + /** + * @return class-string + */ + public function interfaceName(): string + { + return $this->interfaceName; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/CoversFunction.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/CoversFunction.php new file mode 100644 index 000000000..3b58b1914 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/CoversFunction.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 PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)] +final readonly class CoversFunction +{ + /** + * @var non-empty-string + */ + private string $functionName; + + /** + * @param non-empty-string $functionName + */ + public function __construct(string $functionName) + { + $this->functionName = $functionName; + } + + /** + * @return non-empty-string + */ + public function functionName(): string + { + return $this->functionName; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/CoversMethod.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/CoversMethod.php new file mode 100644 index 000000000..4181239b9 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/CoversMethod.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 PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)] +final readonly class CoversMethod +{ + /** + * @var class-string + */ + private string $className; + + /** + * @var non-empty-string + */ + private string $methodName; + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public function __construct(string $className, string $methodName) + { + $this->className = $className; + $this->methodName = $methodName; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + /** + * @return non-empty-string + */ + public function methodName(): string + { + return $this->methodName; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/CoversNamespace.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/CoversNamespace.php new file mode 100644 index 000000000..50d82b994 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/CoversNamespace.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 PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)] +final readonly class CoversNamespace +{ + /** + * @var non-empty-string + */ + private string $namespace; + + /** + * @param non-empty-string $namespace + */ + public function __construct(string $namespace) + { + $this->namespace = $namespace; + } + + /** + * @return non-empty-string + */ + public function namespace(): string + { + return $this->namespace; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/CoversNothing.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/CoversNothing.php new file mode 100644 index 000000000..33ce31f30 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/CoversNothing.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)] +final readonly class CoversNothing +{ +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/CoversTrait.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/CoversTrait.php new file mode 100644 index 000000000..899278550 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/CoversTrait.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 PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)] +final readonly class CoversTrait +{ + /** + * @var trait-string + */ + private string $traitName; + + /** + * @param trait-string $traitName + */ + public function __construct(string $traitName) + { + $this->traitName = $traitName; + } + + /** + * @return trait-string + */ + public function traitName(): string + { + return $this->traitName; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/DataProvider.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/DataProvider.php new file mode 100644 index 000000000..0e95c5020 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/DataProvider.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 PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] +final readonly class DataProvider +{ + /** + * @var non-empty-string + */ + private string $methodName; + + /** + * @param non-empty-string $methodName + */ + public function __construct(string $methodName) + { + $this->methodName = $methodName; + } + + /** + * @return non-empty-string + */ + public function methodName(): string + { + return $this->methodName; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/DataProviderExternal.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/DataProviderExternal.php new file mode 100644 index 000000000..9d05cef27 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/DataProviderExternal.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 PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] +final readonly class DataProviderExternal +{ + /** + * @var class-string + */ + private string $className; + + /** + * @var non-empty-string + */ + private string $methodName; + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public function __construct(string $className, string $methodName) + { + $this->className = $className; + $this->methodName = $methodName; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + /** + * @return non-empty-string + */ + public function methodName(): string + { + return $this->methodName; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/Depends.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/Depends.php new file mode 100644 index 000000000..1375ae5bd --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/Depends.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 PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] +final readonly class Depends +{ + /** + * @var non-empty-string + */ + private string $methodName; + + /** + * @param non-empty-string $methodName + */ + public function __construct(string $methodName) + { + $this->methodName = $methodName; + } + + /** + * @return non-empty-string + */ + public function methodName(): string + { + return $this->methodName; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/DependsExternal.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/DependsExternal.php new file mode 100644 index 000000000..c2f9e39f0 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/DependsExternal.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 PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] +final readonly class DependsExternal +{ + /** + * @var class-string + */ + private string $className; + + /** + * @var non-empty-string + */ + private string $methodName; + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public function __construct(string $className, string $methodName) + { + $this->className = $className; + $this->methodName = $methodName; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + /** + * @return non-empty-string + */ + public function methodName(): string + { + return $this->methodName; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/DependsExternalUsingDeepClone.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/DependsExternalUsingDeepClone.php new file mode 100644 index 000000000..d71452b88 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/DependsExternalUsingDeepClone.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 PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] +final readonly class DependsExternalUsingDeepClone +{ + /** + * @var class-string + */ + private string $className; + + /** + * @var non-empty-string + */ + private string $methodName; + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public function __construct(string $className, string $methodName) + { + $this->className = $className; + $this->methodName = $methodName; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + /** + * @return non-empty-string + */ + public function methodName(): string + { + return $this->methodName; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/DependsExternalUsingShallowClone.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/DependsExternalUsingShallowClone.php new file mode 100644 index 000000000..9fb8fbf7d --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/DependsExternalUsingShallowClone.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 PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] +final readonly class DependsExternalUsingShallowClone +{ + /** + * @var class-string + */ + private string $className; + + /** + * @var non-empty-string + */ + private string $methodName; + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public function __construct(string $className, string $methodName) + { + $this->className = $className; + $this->methodName = $methodName; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + /** + * @return non-empty-string + */ + public function methodName(): string + { + return $this->methodName; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/DependsOnClass.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/DependsOnClass.php new file mode 100644 index 000000000..14465c77e --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/DependsOnClass.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 PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] +final readonly class DependsOnClass +{ + /** + * @var class-string + */ + private string $className; + + /** + * @param class-string $className + */ + public function __construct(string $className) + { + $this->className = $className; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/DependsOnClassUsingDeepClone.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/DependsOnClassUsingDeepClone.php new file mode 100644 index 000000000..dc46c39df --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/DependsOnClassUsingDeepClone.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 PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] +final readonly class DependsOnClassUsingDeepClone +{ + /** + * @var class-string + */ + private string $className; + + /** + * @param class-string $className + */ + public function __construct(string $className) + { + $this->className = $className; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/DependsOnClassUsingShallowClone.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/DependsOnClassUsingShallowClone.php new file mode 100644 index 000000000..5201f045d --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/DependsOnClassUsingShallowClone.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 PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] +final readonly class DependsOnClassUsingShallowClone +{ + /** + * @var class-string + */ + private string $className; + + /** + * @param class-string $className + */ + public function __construct(string $className) + { + $this->className = $className; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/DependsUsingDeepClone.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/DependsUsingDeepClone.php new file mode 100644 index 000000000..173188ec6 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/DependsUsingDeepClone.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 PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] +final readonly class DependsUsingDeepClone +{ + /** + * @var non-empty-string + */ + private string $methodName; + + /** + * @param non-empty-string $methodName + */ + public function __construct(string $methodName) + { + $this->methodName = $methodName; + } + + /** + * @return non-empty-string + */ + public function methodName(): string + { + return $this->methodName; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/DependsUsingShallowClone.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/DependsUsingShallowClone.php new file mode 100644 index 000000000..8aff52a3a --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/DependsUsingShallowClone.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 PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] +final readonly class DependsUsingShallowClone +{ + /** + * @var non-empty-string + */ + private string $methodName; + + /** + * @param non-empty-string $methodName + */ + public function __construct(string $methodName) + { + $this->methodName = $methodName; + } + + /** + * @return non-empty-string + */ + public function methodName(): string + { + return $this->methodName; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/DisableReturnValueGenerationForTestDoubles.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/DisableReturnValueGenerationForTestDoubles.php new file mode 100644 index 000000000..2709f569e --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/DisableReturnValueGenerationForTestDoubles.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS)] +final readonly class DisableReturnValueGenerationForTestDoubles +{ +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/DoesNotPerformAssertions.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/DoesNotPerformAssertions.php new file mode 100644 index 000000000..f193e5af3 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/DoesNotPerformAssertions.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)] +final readonly class DoesNotPerformAssertions +{ +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/ExcludeGlobalVariableFromBackup.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/ExcludeGlobalVariableFromBackup.php new file mode 100644 index 000000000..5d1ac72c8 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/ExcludeGlobalVariableFromBackup.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 PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] +final readonly class ExcludeGlobalVariableFromBackup +{ + /** + * @var non-empty-string + */ + private string $globalVariableName; + + /** + * @param non-empty-string $globalVariableName + */ + public function __construct(string $globalVariableName) + { + $this->globalVariableName = $globalVariableName; + } + + /** + * @return non-empty-string + */ + public function globalVariableName(): string + { + return $this->globalVariableName; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/ExcludeStaticPropertyFromBackup.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/ExcludeStaticPropertyFromBackup.php new file mode 100644 index 000000000..ea5725491 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/ExcludeStaticPropertyFromBackup.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 PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] +final readonly class ExcludeStaticPropertyFromBackup +{ + /** + * @var class-string + */ + private string $className; + + /** + * @var non-empty-string + */ + private string $propertyName; + + /** + * @param class-string $className + * @param non-empty-string $propertyName + */ + public function __construct(string $className, string $propertyName) + { + $this->className = $className; + $this->propertyName = $propertyName; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + /** + * @return non-empty-string + */ + public function propertyName(): string + { + return $this->propertyName; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/Group.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/Group.php new file mode 100644 index 000000000..5a6942bb8 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/Group.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 PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] +final readonly class Group +{ + /** + * @var non-empty-string + */ + private string $name; + + /** + * @param non-empty-string $name + */ + public function __construct(string $name) + { + $this->name = $name; + } + + /** + * @return non-empty-string + */ + public function name(): string + { + return $this->name; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/IgnoreDeprecations.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/IgnoreDeprecations.php new file mode 100644 index 000000000..6cde66b09 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/IgnoreDeprecations.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)] +final readonly class IgnoreDeprecations +{ +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/IgnorePhpunitDeprecations.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/IgnorePhpunitDeprecations.php new file mode 100644 index 000000000..1cebec04d --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/IgnorePhpunitDeprecations.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 PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)] +final readonly class IgnorePhpunitDeprecations +{ +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/IgnorePhpunitWarnings.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/IgnorePhpunitWarnings.php new file mode 100644 index 000000000..af1d22bda --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/IgnorePhpunitWarnings.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 PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_METHOD)] +final readonly class IgnorePhpunitWarnings +{ + /** @var null|non-empty-string */ + private ?string $messagePattern; + + /** + * @param null|non-empty-string $messagePattern + */ + public function __construct(null|string $messagePattern = null) + { + $this->messagePattern = $messagePattern; + } + + /** + * @return null|non-empty-string + */ + public function messagePattern(): ?string + { + return $this->messagePattern; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/Large.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/Large.php new file mode 100644 index 000000000..a751a9343 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/Large.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS)] +final readonly class Large +{ +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/Medium.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/Medium.php new file mode 100644 index 000000000..debc4b0d3 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/Medium.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS)] +final readonly class Medium +{ +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/PostCondition.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/PostCondition.php new file mode 100644 index 000000000..8eb40fe03 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/PostCondition.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_METHOD)] +final readonly class PostCondition +{ + private int $priority; + + public function __construct(int $priority = 0) + { + $this->priority = $priority; + } + + public function priority(): int + { + return $this->priority; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/PreCondition.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/PreCondition.php new file mode 100644 index 000000000..5f47fc599 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/PreCondition.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_METHOD)] +final readonly class PreCondition +{ + private int $priority; + + public function __construct(int $priority = 0) + { + $this->priority = $priority; + } + + public function priority(): int + { + return $this->priority; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/PreserveGlobalState.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/PreserveGlobalState.php new file mode 100644 index 000000000..fcd9c6372 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/PreserveGlobalState.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)] +final readonly class PreserveGlobalState +{ + private bool $enabled; + + public function __construct(bool $enabled) + { + $this->enabled = $enabled; + } + + public function enabled(): bool + { + return $this->enabled; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/RequiresEnvironmentVariable.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/RequiresEnvironmentVariable.php new file mode 100644 index 000000000..7e460b99a --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/RequiresEnvironmentVariable.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 PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] +final readonly class RequiresEnvironmentVariable +{ + private string $environmentVariableName; + private null|string $value; + + public function __construct(string $environmentVariableName, null|string $value = null) + { + $this->environmentVariableName = $environmentVariableName; + $this->value = $value; + } + + public function environmentVariableName(): string + { + return $this->environmentVariableName; + } + + public function value(): null|string + { + return $this->value; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/RequiresFunction.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/RequiresFunction.php new file mode 100644 index 000000000..e358bdf11 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/RequiresFunction.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 PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] +final readonly class RequiresFunction +{ + /** + * @var non-empty-string + */ + private string $functionName; + + /** + * @param non-empty-string $functionName + */ + public function __construct(string $functionName) + { + $this->functionName = $functionName; + } + + /** + * @return non-empty-string + */ + public function functionName(): string + { + return $this->functionName; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/RequiresMethod.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/RequiresMethod.php new file mode 100644 index 000000000..713c1ee91 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/RequiresMethod.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 PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] +final readonly class RequiresMethod +{ + /** + * @var class-string + */ + private string $className; + + /** + * @var non-empty-string + */ + private string $methodName; + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public function __construct(string $className, string $methodName) + { + $this->className = $className; + $this->methodName = $methodName; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + /** + * @return non-empty-string + */ + public function methodName(): string + { + return $this->methodName; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/RequiresOperatingSystem.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/RequiresOperatingSystem.php new file mode 100644 index 000000000..066d7d3c7 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/RequiresOperatingSystem.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 PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)] +final readonly class RequiresOperatingSystem +{ + /** + * @var non-empty-string + */ + private string $regularExpression; + + /** + * @param non-empty-string $regularExpression + */ + public function __construct(string $regularExpression) + { + $this->regularExpression = $regularExpression; + } + + /** + * @return non-empty-string + */ + public function regularExpression(): string + { + return $this->regularExpression; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/RequiresOperatingSystemFamily.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/RequiresOperatingSystemFamily.php new file mode 100644 index 000000000..088ba85ce --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/RequiresOperatingSystemFamily.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 PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)] +final readonly class RequiresOperatingSystemFamily +{ + /** + * @var non-empty-string + */ + private string $operatingSystemFamily; + + /** + * @param non-empty-string $operatingSystemFamily + */ + public function __construct(string $operatingSystemFamily) + { + $this->operatingSystemFamily = $operatingSystemFamily; + } + + /** + * @return non-empty-string + */ + public function operatingSystemFamily(): string + { + return $this->operatingSystemFamily; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/RequiresPhp.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/RequiresPhp.php new file mode 100644 index 000000000..94fbe51b8 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/RequiresPhp.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 PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)] +final readonly class RequiresPhp +{ + /** + * @var non-empty-string + */ + private string $versionRequirement; + + /** + * @param non-empty-string $versionRequirement + */ + public function __construct(string $versionRequirement) + { + $this->versionRequirement = $versionRequirement; + } + + /** + * @return non-empty-string + */ + public function versionRequirement(): string + { + return $this->versionRequirement; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/RequiresPhpExtension.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/RequiresPhpExtension.php new file mode 100644 index 000000000..61a81ec47 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/RequiresPhpExtension.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 PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] +final readonly class RequiresPhpExtension +{ + /** + * @var non-empty-string + */ + private string $extension; + + /** + * @var null|non-empty-string + */ + private ?string $versionRequirement; + + /** + * @param non-empty-string $extension + * @param null|non-empty-string $versionRequirement + */ + public function __construct(string $extension, ?string $versionRequirement = null) + { + $this->extension = $extension; + $this->versionRequirement = $versionRequirement; + } + + /** + * @return non-empty-string + */ + public function extension(): string + { + return $this->extension; + } + + /** + * @return null|non-empty-string + */ + public function versionRequirement(): ?string + { + return $this->versionRequirement; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/RequiresPhpunit.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/RequiresPhpunit.php new file mode 100644 index 000000000..8b26405b7 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/RequiresPhpunit.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 PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)] +final readonly class RequiresPhpunit +{ + /** + * @var non-empty-string + */ + private string $versionRequirement; + + /** + * @param non-empty-string $versionRequirement + */ + public function __construct(string $versionRequirement) + { + $this->versionRequirement = $versionRequirement; + } + + /** + * @return non-empty-string + */ + public function versionRequirement(): string + { + return $this->versionRequirement; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/RequiresPhpunitExtension.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/RequiresPhpunitExtension.php new file mode 100644 index 000000000..9b6a164f2 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/RequiresPhpunitExtension.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 PHPUnit\Framework\Attributes; + +use Attribute; +use PHPUnit\Runner\Extension\Extension; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] +final readonly class RequiresPhpunitExtension +{ + /** + * @var class-string + */ + private string $extensionClass; + + /** + * @param class-string $extensionClass + */ + public function __construct(string $extensionClass) + { + $this->extensionClass = $extensionClass; + } + + /** + * @return class-string + */ + public function extensionClass(): string + { + return $this->extensionClass; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/RequiresSetting.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/RequiresSetting.php new file mode 100644 index 000000000..86828dd0f --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/RequiresSetting.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 PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] +final readonly class RequiresSetting +{ + /** + * @var non-empty-string + */ + private string $setting; + + /** + * @var non-empty-string + */ + private string $value; + + /** + * @param non-empty-string $setting + * @param non-empty-string $value + */ + public function __construct(string $setting, string $value) + { + $this->setting = $setting; + $this->value = $value; + } + + /** + * @return non-empty-string + */ + public function setting(): string + { + return $this->setting; + } + + /** + * @return non-empty-string + */ + public function value(): string + { + return $this->value; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/RunClassInSeparateProcess.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/RunClassInSeparateProcess.php new file mode 100644 index 000000000..749ea3d9b --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/RunClassInSeparateProcess.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS)] +final readonly class RunClassInSeparateProcess +{ +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/RunInSeparateProcess.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/RunInSeparateProcess.php new file mode 100644 index 000000000..740d6f387 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/RunInSeparateProcess.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_METHOD)] +final readonly class RunInSeparateProcess +{ +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/RunTestsInSeparateProcesses.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/RunTestsInSeparateProcesses.php new file mode 100644 index 000000000..0f8d43200 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/RunTestsInSeparateProcesses.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS)] +final readonly class RunTestsInSeparateProcesses +{ +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/Small.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/Small.php new file mode 100644 index 000000000..5ef284ab1 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/Small.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS)] +final readonly class Small +{ +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/Test.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/Test.php new file mode 100644 index 000000000..a15f7f557 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/Test.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_METHOD)] +final readonly class Test +{ +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/TestDox.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/TestDox.php new file mode 100644 index 000000000..b04785068 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/TestDox.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 PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)] +final readonly class TestDox +{ + /** + * @var non-empty-string + */ + private string $text; + + /** + * @param non-empty-string $text + */ + public function __construct(string $text) + { + $this->text = $text; + } + + /** + * @return non-empty-string + */ + public function text(): string + { + return $this->text; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/TestDoxFormatter.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/TestDoxFormatter.php new file mode 100644 index 000000000..0456d843f --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/TestDoxFormatter.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 PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_METHOD)] +final readonly class TestDoxFormatter +{ + /** + * @var non-empty-string + */ + private string $methodName; + + /** + * @param non-empty-string $methodName + */ + public function __construct(string $methodName) + { + $this->methodName = $methodName; + } + + /** + * @return non-empty-string + */ + public function methodName(): string + { + return $this->methodName; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/TestDoxFormatterExternal.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/TestDoxFormatterExternal.php new file mode 100644 index 000000000..de5cf701e --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/TestDoxFormatterExternal.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 PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_METHOD)] +final readonly class TestDoxFormatterExternal +{ + /** + * @var class-string + */ + private string $className; + + /** + * @var non-empty-string + */ + private string $methodName; + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public function __construct(string $className, string $methodName) + { + $this->className = $className; + $this->methodName = $methodName; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + /** + * @return non-empty-string + */ + public function methodName(): string + { + return $this->methodName; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/TestWith.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/TestWith.php new file mode 100644 index 000000000..b5c33a43e --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/TestWith.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 PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] +final readonly class TestWith +{ + /** + * @var array + */ + private array $data; + + /** + * @var ?non-empty-string + */ + private ?string $name; + + /** + * @param array $data + * @param ?non-empty-string $name + */ + public function __construct(array $data, ?string $name = null) + { + $this->data = $data; + $this->name = $name; + } + + /** + * @return array + */ + public function data(): array + { + return $this->data; + } + + /** + * @return ?non-empty-string + */ + public function name(): ?string + { + return $this->name; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/TestWithJson.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/TestWithJson.php new file mode 100644 index 000000000..bfe9c0922 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/TestWithJson.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 PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] +final readonly class TestWithJson +{ + /** + * @var non-empty-string + */ + private string $json; + + /** + * @var ?non-empty-string + */ + private ?string $name; + + /** + * @param non-empty-string $json + * @param ?non-empty-string $name + */ + public function __construct(string $json, ?string $name = null) + { + $this->json = $json; + $this->name = $name; + } + + /** + * @return non-empty-string + */ + public function json(): string + { + return $this->json; + } + + /** + * @return ?non-empty-string + */ + public function name(): ?string + { + return $this->name; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/Ticket.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/Ticket.php new file mode 100644 index 000000000..e463247e2 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/Ticket.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 PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] +final readonly class Ticket +{ + /** + * @var non-empty-string + */ + private string $text; + + /** + * @param non-empty-string $text + */ + public function __construct(string $text) + { + $this->text = $text; + } + + /** + * @return non-empty-string + */ + public function text(): string + { + return $this->text; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/UsesClass.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/UsesClass.php new file mode 100644 index 000000000..f7078bce6 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/UsesClass.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 PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)] +final readonly class UsesClass +{ + /** + * @var class-string + */ + private string $className; + + /** + * @param class-string $className + */ + public function __construct(string $className) + { + $this->className = $className; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/UsesClassesThatExtendClass.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/UsesClassesThatExtendClass.php new file mode 100644 index 000000000..d1aa73faa --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/UsesClassesThatExtendClass.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 PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)] +final readonly class UsesClassesThatExtendClass +{ + /** + * @var class-string + */ + private string $className; + + /** + * @param class-string $className + */ + public function __construct(string $className) + { + $this->className = $className; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/UsesClassesThatImplementInterface.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/UsesClassesThatImplementInterface.php new file mode 100644 index 000000000..0f2241c86 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/UsesClassesThatImplementInterface.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 PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)] +final readonly class UsesClassesThatImplementInterface +{ + /** + * @var class-string + */ + private string $interfaceName; + + /** + * @param class-string $interfaceName + */ + public function __construct(string $interfaceName) + { + $this->interfaceName = $interfaceName; + } + + /** + * @return class-string + */ + public function interfaceName(): string + { + return $this->interfaceName; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/UsesFunction.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/UsesFunction.php new file mode 100644 index 000000000..12cc408b9 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/UsesFunction.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 PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)] +final readonly class UsesFunction +{ + /** + * @var non-empty-string + */ + private string $functionName; + + /** + * @param non-empty-string $functionName + */ + public function __construct(string $functionName) + { + $this->functionName = $functionName; + } + + /** + * @return non-empty-string + */ + public function functionName(): string + { + return $this->functionName; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/UsesMethod.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/UsesMethod.php new file mode 100644 index 000000000..b1ee19b54 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/UsesMethod.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 PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)] +final readonly class UsesMethod +{ + /** + * @var class-string + */ + private string $className; + + /** + * @var non-empty-string + */ + private string $methodName; + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public function __construct(string $className, string $methodName) + { + $this->className = $className; + $this->methodName = $methodName; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + /** + * @return non-empty-string + */ + public function methodName(): string + { + return $this->methodName; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/UsesNamespace.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/UsesNamespace.php new file mode 100644 index 000000000..ad929cdb1 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/UsesNamespace.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 PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)] +final readonly class UsesNamespace +{ + /** + * @var non-empty-string + */ + private string $namespace; + + /** + * @param non-empty-string $namespace + */ + public function __construct(string $namespace) + { + $this->namespace = $namespace; + } + + /** + * @return non-empty-string + */ + public function namespace(): string + { + return $this->namespace; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/UsesTrait.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/UsesTrait.php new file mode 100644 index 000000000..469e79c68 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/UsesTrait.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 PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)] +final readonly class UsesTrait +{ + /** + * @var trait-string + */ + private string $traitName; + + /** + * @param trait-string $traitName + */ + public function __construct(string $traitName) + { + $this->traitName = $traitName; + } + + /** + * @return trait-string + */ + public function traitName(): string + { + return $this->traitName; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/WithEnvironmentVariable.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/WithEnvironmentVariable.php new file mode 100644 index 000000000..6d8e2d305 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/WithEnvironmentVariable.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 PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] +final readonly class WithEnvironmentVariable +{ + /** + * @var non-empty-string + */ + private string $environmentVariableName; + private null|string $value; + + /** + * @param non-empty-string $environmentVariableName + */ + public function __construct(string $environmentVariableName, null|string $value = null) + { + $this->environmentVariableName = $environmentVariableName; + $this->value = $value; + } + + /** + * @return non-empty-string + */ + public function environmentVariableName(): string + { + return $this->environmentVariableName; + } + + public function value(): null|string + { + return $this->value; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Attributes/WithoutErrorHandler.php b/app/vendor/phpunit/phpunit/src/Framework/Attributes/WithoutErrorHandler.php new file mode 100644 index 000000000..a10f0fc2f --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Attributes/WithoutErrorHandler.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Attributes; + +use Attribute; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +#[Attribute(Attribute::TARGET_METHOD)] +final readonly class WithoutErrorHandler +{ +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Boolean/IsFalse.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Boolean/IsFalse.php index 212e2bcb4..5846cf19c 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Boolean/IsFalse.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Boolean/IsFalse.php @@ -25,10 +25,8 @@ public function toString(): string /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate */ - protected function matches($other): bool + protected function matches(mixed $other): bool { return $other === false; } diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Boolean/IsTrue.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Boolean/IsTrue.php index e1d6b2691..be589523b 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Boolean/IsTrue.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Boolean/IsTrue.php @@ -25,10 +25,8 @@ public function toString(): string /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate */ - protected function matches($other): bool + protected function matches(mixed $other): bool { return $other === true; } diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Callback.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Callback.php index b7cf95a12..8646f5a16 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Callback.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Callback.php @@ -9,21 +9,24 @@ */ namespace PHPUnit\Framework\Constraint; +use Closure; +use ReflectionFunction; + /** - * @psalm-template CallbackInput of mixed + * @template CallbackInput of mixed * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ final class Callback extends Constraint { /** - * @var callable - * - * @psalm-var callable(CallbackInput $input): bool + * @var callable(CallbackInput): bool */ - private $callback; + private readonly mixed $callback; - /** @psalm-param callable(CallbackInput $input): bool $callback */ + /** + * @param callable(CallbackInput $input): bool $callback + */ public function __construct(callable $callback) { $this->callback = $callback; @@ -37,16 +40,23 @@ public function toString(): string return 'is accepted by specified callback'; } + public function isVariadic(): bool + { + return (new ReflectionFunction(Closure::fromCallable($this->callback)))->isVariadic(); + } + /** * Evaluates the constraint for parameter $value. Returns true if the * constraint is met, false otherwise. * - * @param mixed $other value or object to evaluate - * - * @psalm-param CallbackInput $other + * @param CallbackInput $other */ - protected function matches($other): bool + protected function matches(mixed $other): bool { + if ($this->isVariadic()) { + return ($this->callback)(...$other); + } + return ($this->callback)($other); } } diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Cardinality/Count.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Cardinality/Count.php index ff04a6981..2ec012a0e 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Cardinality/Count.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Cardinality/Count.php @@ -10,15 +10,15 @@ namespace PHPUnit\Framework\Constraint; use function count; -use function is_array; +use function is_countable; use function iterator_count; use function sprintf; -use Countable; use EmptyIterator; use Generator; use Iterator; use IteratorAggregate; use PHPUnit\Framework\Exception; +use PHPUnit\Framework\GeneratorNotSupportedException; use Traversable; /** @@ -26,10 +26,7 @@ */ class Count extends Constraint { - /** - * @var int - */ - private $expectedCount; + private readonly int $expectedCount; public function __construct(int $expected) { @@ -50,7 +47,7 @@ public function toString(): string * * @throws Exception */ - protected function matches($other): bool + protected function matches(mixed $other): bool { return $this->expectedCount === $this->getCountOf($other); } @@ -58,9 +55,9 @@ protected function matches($other): bool /** * @throws Exception */ - protected function getCountOf($other): ?int + protected function getCountOf(mixed $other): ?int { - if ($other instanceof Countable || is_array($other)) { + if (is_countable($other)) { return count($other); } @@ -84,7 +81,7 @@ protected function getCountOf($other): ?int $iterator = $other; if ($iterator instanceof Generator) { - return $this->getCountOfGenerator($iterator); + throw new GeneratorNotSupportedException; } if (!$iterator instanceof Iterator) { @@ -110,28 +107,15 @@ protected function getCountOf($other): ?int return null; } - /** - * Returns the total number of iterations from a generator. - * This will fully exhaust the generator. - */ - protected function getCountOfGenerator(Generator $generator): int - { - for ($count = 0; $generator->valid(); $generator->next()) { - $count++; - } - - return $count; - } - /** * Returns the description of the failure. * * The beginning of failure messages is "Failed asserting that" in most * cases. This method should return the second part of that sentence. * - * @param mixed $other evaluated value or object + * @throws Exception */ - protected function failureDescription($other): string + protected function failureDescription(mixed $other): string { return sprintf( 'actual size %d matches expected size %d', diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Cardinality/GreaterThan.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Cardinality/GreaterThan.php index 7d079f508..7b252dd9c 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Cardinality/GreaterThan.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Cardinality/GreaterThan.php @@ -9,43 +9,33 @@ */ namespace PHPUnit\Framework\Constraint; -use SebastianBergmann\RecursionContext\InvalidArgumentException; +use PHPUnit\Util\Exporter; /** * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ final class GreaterThan extends Constraint { - /** - * @var float|int - */ - private $value; + private readonly mixed $value; - /** - * @param float|int $value - */ - public function __construct($value) + public function __construct(mixed $value) { $this->value = $value; } /** * Returns a string representation of the constraint. - * - * @throws InvalidArgumentException */ public function toString(): string { - return 'is greater than ' . $this->exporter()->export($this->value); + return 'is greater than ' . Exporter::export($this->value); } /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate */ - protected function matches($other): bool + protected function matches(mixed $other): bool { return $this->value < $other; } diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Cardinality/IsEmpty.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Cardinality/IsEmpty.php index ee01e93da..31942d9f0 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Cardinality/IsEmpty.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Cardinality/IsEmpty.php @@ -12,7 +12,7 @@ use function count; use function gettype; use function sprintf; -use function strpos; +use function str_starts_with; use Countable; use EmptyIterator; @@ -32,10 +32,8 @@ public function toString(): string /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate */ - protected function matches($other): bool + protected function matches(mixed $other): bool { if ($other instanceof EmptyIterator) { return true; @@ -45,6 +43,7 @@ protected function matches($other): bool return count($other) === 0; } + /** @phpstan-ignore empty.notAllowed */ return empty($other); } @@ -53,16 +52,14 @@ protected function matches($other): bool * * The beginning of failure messages is "Failed asserting that" in most * cases. This method should return the second part of that sentence. - * - * @param mixed $other evaluated value or object */ - protected function failureDescription($other): string + protected function failureDescription(mixed $other): string { $type = gettype($other); return sprintf( '%s %s %s', - strpos($type, 'a') === 0 || strpos($type, 'o') === 0 ? 'an' : 'a', + str_starts_with($type, 'a') || str_starts_with($type, 'o') ? 'an' : 'a', $type, $this->toString(), ); diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Cardinality/LessThan.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Cardinality/LessThan.php index 4d0184a25..122dd7347 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Cardinality/LessThan.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Cardinality/LessThan.php @@ -9,43 +9,33 @@ */ namespace PHPUnit\Framework\Constraint; -use SebastianBergmann\RecursionContext\InvalidArgumentException; +use PHPUnit\Util\Exporter; /** * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ final class LessThan extends Constraint { - /** - * @var float|int - */ - private $value; + private readonly mixed $value; - /** - * @param float|int $value - */ - public function __construct($value) + public function __construct(mixed $value) { $this->value = $value; } /** * Returns a string representation of the constraint. - * - * @throws InvalidArgumentException */ public function toString(): string { - return 'is less than ' . $this->exporter()->export($this->value); + return 'is less than ' . Exporter::export($this->value); } /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate */ - protected function matches($other): bool + protected function matches(mixed $other): bool { return $this->value > $other; } diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Cardinality/SameSize.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Cardinality/SameSize.php index a54679425..a2417a28d 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Cardinality/SameSize.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Cardinality/SameSize.php @@ -9,12 +9,20 @@ */ namespace PHPUnit\Framework\Constraint; +use Countable; +use PHPUnit\Framework\Exception; + /** * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ final class SameSize extends Count { - public function __construct(iterable $expected) + /** + * @param Countable|iterable $expected + * + * @throws Exception + */ + public function __construct(Countable|iterable $expected) { parent::__construct((int) $this->getCountOf($expected)); } diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Constraint.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Constraint.php index 2ee73973c..3a3483e3f 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Constraint.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Constraint.php @@ -9,24 +9,27 @@ */ namespace PHPUnit\Framework\Constraint; +use function assert; +use function gettype; +use function is_int; +use function is_object; use function sprintf; +use function str_replace; +use function strpos; +use function strtolower; +use function substr; use Countable; use PHPUnit\Framework\ExpectationFailedException; use PHPUnit\Framework\SelfDescribing; +use PHPUnit\Util\Exporter; +use ReflectionObject; use SebastianBergmann\Comparator\ComparisonFailure; -use SebastianBergmann\Exporter\Exporter; -use SebastianBergmann\RecursionContext\InvalidArgumentException; /** * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ abstract class Constraint implements Countable, SelfDescribing { - /** - * @var ?Exporter - */ - private $exporter; - /** * Evaluates the constraint for parameter $other. * @@ -38,9 +41,8 @@ abstract class Constraint implements Countable, SelfDescribing * failure. * * @throws ExpectationFailedException - * @throws InvalidArgumentException */ - public function evaluate($other, string $description = '', bool $returnResult = false): ?bool + public function evaluate(mixed $other, string $description = '', bool $returnResult = false): ?bool { $success = false; @@ -67,26 +69,13 @@ public function count(): int return 1; } - protected function exporter(): Exporter - { - if ($this->exporter === null) { - $this->exporter = new Exporter; - } - - return $this->exporter; - } - /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. * * This method can be overridden to implement the evaluation algorithm. - * - * @param mixed $other value or object to evaluate - * - * @codeCoverageIgnore */ - protected function matches($other): bool + protected function matches(mixed $other): bool { return false; } @@ -94,15 +83,9 @@ protected function matches($other): bool /** * Throws an exception for the given compared value and test description. * - * @param mixed $other evaluated value or object - * @param string $description Additional information about the test - * * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @psalm-return never-return */ - protected function fail($other, $description, ?ComparisonFailure $comparisonFailure = null): void + protected function fail(mixed $other, string $description, ?ComparisonFailure $comparisonFailure = null): never { $failureDescription = sprintf( 'Failed asserting that %s.', @@ -111,11 +94,11 @@ protected function fail($other, $description, ?ComparisonFailure $comparisonFail $additionalFailureDescription = $this->additionalFailureDescription($other); - if ($additionalFailureDescription) { + if ($additionalFailureDescription !== '') { $failureDescription .= "\n" . $additionalFailureDescription; } - if (!empty($description)) { + if ($description !== '') { $failureDescription = $description . "\n" . $failureDescription; } @@ -130,10 +113,8 @@ protected function fail($other, $description, ?ComparisonFailure $comparisonFail * * The function can be overridden to provide additional failure * information like a diff - * - * @param mixed $other evaluated value or object */ - protected function additionalFailureDescription($other): string + protected function additionalFailureDescription(mixed $other): string { return ''; } @@ -146,14 +127,10 @@ protected function additionalFailureDescription($other): string * * To provide additional failure information additionalFailureDescription * can be used. - * - * @param mixed $other evaluated value or object - * - * @throws InvalidArgumentException */ - protected function failureDescription($other): string + protected function failureDescription(mixed $other): string { - return $this->exporter()->export($other) . ' ' . $this->toString(); + return Exporter::export($other) . ' ' . $this->toString(); } /** @@ -167,11 +144,8 @@ protected function failureDescription($other): string * * The method shall return empty string, when it does not handle * customization by itself. - * - * @param Operator $operator the $operator of the expression - * @param mixed $role role of $this constraint in the $operator expression */ - protected function toStringInContext(Operator $operator, $role): string + protected function toStringInContext(Operator $operator, mixed $role): string { return ''; } @@ -187,12 +161,8 @@ protected function toStringInContext(Operator $operator, $role): string * * The method shall return empty string, when it does not handle * customization by itself. - * - * @param Operator $operator the $operator of the expression - * @param mixed $role role of $this constraint in the $operator expression - * @param mixed $other evaluated value or object */ - protected function failureDescriptionInContext(Operator $operator, $role, $other): string + protected function failureDescriptionInContext(Operator $operator, mixed $role, mixed $other): string { $string = $this->toStringInContext($operator, $role); @@ -200,7 +170,7 @@ protected function failureDescriptionInContext(Operator $operator, $role, $other return ''; } - return $this->exporter()->export($other) . ' ' . $string; + return Exporter::export($other) . ' ' . $string; } /** @@ -267,4 +237,45 @@ protected function reduce(): self { return $this; } + + /** + * @return non-empty-string + */ + protected function valueToTypeStringFragment(mixed $value): string + { + if (is_object($value)) { + $reflector = new ReflectionObject($value); + + if ($reflector->isAnonymous()) { + $name = str_replace('class@anonymous', '', $reflector->getName()); + + $length = strpos($name, '$'); + + assert(is_int($length)); + + $name = substr($name, 0, $length); + + return 'an instance of anonymous class created at ' . $name . ' '; + } + + return 'an instance of class ' . $reflector->getName() . ' '; + } + + $type = strtolower(gettype($value)); + + if ($type === 'double') { + $type = 'float'; + } + + if ($type === 'resource (closed)') { + $type = 'closed resource'; + } + + return match ($type) { + 'array', 'integer' => 'an ' . $type . ' ', + 'boolean', 'closed resource', 'float', 'resource', 'string' => 'a ' . $type . ' ', + 'null' => 'null ', + default => 'a value of ' . $type . ' ', + }; + } } diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Equality/IsEqual.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Equality/IsEqual.php index 04bfe4e86..2051bfce3 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Equality/IsEqual.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Equality/IsEqual.php @@ -11,44 +11,23 @@ use function is_string; use function sprintf; -use function strpos; +use function str_contains; use function trim; use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Util\Exporter; use SebastianBergmann\Comparator\ComparisonFailure; use SebastianBergmann\Comparator\Factory as ComparatorFactory; -use SebastianBergmann\RecursionContext\InvalidArgumentException; /** * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ final class IsEqual extends Constraint { - /** - * @var mixed - */ - private $value; - - /** - * @var float - */ - private $delta; - - /** - * @var bool - */ - private $canonicalize; + private readonly mixed $value; - /** - * @var bool - */ - private $ignoreCase; - - public function __construct($value, float $delta = 0.0, bool $canonicalize = false, bool $ignoreCase = false) + public function __construct(mixed $value) { - $this->value = $value; - $this->delta = $delta; - $this->canonicalize = $canonicalize; - $this->ignoreCase = $ignoreCase; + $this->value = $value; } /** @@ -63,7 +42,7 @@ public function __construct($value, float $delta = 0.0, bool $canonicalize = fal * * @throws ExpectationFailedException */ - public function evaluate($other, string $description = '', bool $returnResult = false): ?bool + public function evaluate(mixed $other, string $description = '', bool $returnResult = false): bool { // If $this->value and $other are identical, they are also equal. // This is the most common path and will allow us to skip @@ -83,9 +62,6 @@ public function evaluate($other, string $description = '', bool $returnResult = $comparator->assertEquals( $this->value, $other, - $this->delta, - $this->canonicalize, - $this->ignoreCase, ); } catch (ComparisonFailure $f) { if ($returnResult) { @@ -103,15 +79,13 @@ public function evaluate($other, string $description = '', bool $returnResult = /** * Returns a string representation of the constraint. - * - * @throws InvalidArgumentException */ public function toString(): string { $delta = ''; if (is_string($this->value)) { - if (strpos($this->value, "\n") !== false) { + if (str_contains($this->value, "\n")) { return 'is equal to '; } @@ -121,16 +95,9 @@ public function toString(): string ); } - if ($this->delta != 0) { - $delta = sprintf( - ' with delta <%F>', - $this->delta, - ); - } - return sprintf( 'is equal to %s%s', - $this->exporter()->export($this->value), + Exporter::export($this->value), $delta, ); } diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Equality/IsEqualCanonicalizing.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Equality/IsEqualCanonicalizing.php index 6d49c350e..b826464db 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Equality/IsEqualCanonicalizing.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Equality/IsEqualCanonicalizing.php @@ -11,24 +11,21 @@ use function is_string; use function sprintf; -use function strpos; +use function str_contains; use function trim; use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Util\Exporter; use SebastianBergmann\Comparator\ComparisonFailure; use SebastianBergmann\Comparator\Factory as ComparatorFactory; -use SebastianBergmann\RecursionContext\InvalidArgumentException; /** * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ final class IsEqualCanonicalizing extends Constraint { - /** - * @var mixed - */ - private $value; + private readonly mixed $value; - public function __construct($value) + public function __construct(mixed $value) { $this->value = $value; } @@ -45,7 +42,7 @@ public function __construct($value) * * @throws ExpectationFailedException */ - public function evaluate($other, string $description = '', bool $returnResult = false): ?bool + public function evaluate(mixed $other, string $description = '', bool $returnResult = false): bool { // If $this->value and $other are identical, they are also equal. // This is the most common path and will allow us to skip @@ -67,7 +64,6 @@ public function evaluate($other, string $description = '', bool $returnResult = $other, 0.0, true, - false, ); } catch (ComparisonFailure $f) { if ($returnResult) { @@ -85,13 +81,11 @@ public function evaluate($other, string $description = '', bool $returnResult = /** * Returns a string representation of the constraint. - * - * @throws InvalidArgumentException */ public function toString(): string { if (is_string($this->value)) { - if (strpos($this->value, "\n") !== false) { + if (str_contains($this->value, "\n")) { return 'is equal to '; } @@ -103,7 +97,7 @@ public function toString(): string return sprintf( 'is equal to %s', - $this->exporter()->export($this->value), + Exporter::export($this->value), ); } } diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Equality/IsEqualIgnoringCase.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Equality/IsEqualIgnoringCase.php index 932b7318d..c7b2c31da 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Equality/IsEqualIgnoringCase.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Equality/IsEqualIgnoringCase.php @@ -11,24 +11,21 @@ use function is_string; use function sprintf; -use function strpos; +use function str_contains; use function trim; use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Util\Exporter; use SebastianBergmann\Comparator\ComparisonFailure; use SebastianBergmann\Comparator\Factory as ComparatorFactory; -use SebastianBergmann\RecursionContext\InvalidArgumentException; /** * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ final class IsEqualIgnoringCase extends Constraint { - /** - * @var mixed - */ - private $value; + private readonly mixed $value; - public function __construct($value) + public function __construct(mixed $value) { $this->value = $value; } @@ -45,7 +42,7 @@ public function __construct($value) * * @throws ExpectationFailedException */ - public function evaluate($other, string $description = '', bool $returnResult = false): ?bool + public function evaluate(mixed $other, string $description = '', bool $returnResult = false): bool { // If $this->value and $other are identical, they are also equal. // This is the most common path and will allow us to skip @@ -85,13 +82,11 @@ public function evaluate($other, string $description = '', bool $returnResult = /** * Returns a string representation of the constraint. - * - * @throws InvalidArgumentException */ public function toString(): string { if (is_string($this->value)) { - if (strpos($this->value, "\n") !== false) { + if (str_contains($this->value, "\n")) { return 'is equal to '; } @@ -103,7 +98,7 @@ public function toString(): string return sprintf( 'is equal to %s', - $this->exporter()->export($this->value), + Exporter::export($this->value), ); } } diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Equality/IsEqualWithDelta.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Equality/IsEqualWithDelta.php index 0a04ffc8b..7f18b5bbe 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Equality/IsEqualWithDelta.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Equality/IsEqualWithDelta.php @@ -12,26 +12,19 @@ use function sprintf; use function trim; use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Util\Exporter; use SebastianBergmann\Comparator\ComparisonFailure; use SebastianBergmann\Comparator\Factory as ComparatorFactory; -use SebastianBergmann\RecursionContext\InvalidArgumentException; /** * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ final class IsEqualWithDelta extends Constraint { - /** - * @var mixed - */ - private $value; - - /** - * @var float - */ - private $delta; + private readonly mixed $value; + private readonly float $delta; - public function __construct($value, float $delta) + public function __construct(mixed $value, float $delta) { $this->value = $value; $this->delta = $delta; @@ -49,7 +42,7 @@ public function __construct($value, float $delta) * * @throws ExpectationFailedException */ - public function evaluate($other, string $description = '', bool $returnResult = false): ?bool + public function evaluate(mixed $other, string $description = '', bool $returnResult = false): bool { // If $this->value and $other are identical, they are also equal. // This is the most common path and will allow us to skip @@ -87,14 +80,12 @@ public function evaluate($other, string $description = '', bool $returnResult = /** * Returns a string representation of the constraint. - * - * @throws InvalidArgumentException */ public function toString(): string { return sprintf( 'is equal to %s with delta <%F>', - $this->exporter()->export($this->value), + Exporter::export($this->value), $this->delta, ); } diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Exception/Exception.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Exception/Exception.php index bbaab4af5..a84f6eb98 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Exception/Exception.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Exception/Exception.php @@ -9,20 +9,18 @@ */ namespace PHPUnit\Framework\Constraint; -use function get_class; use function sprintf; use PHPUnit\Util\Filter; use Throwable; /** * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class Exception extends Constraint { - /** - * @var string - */ - private $className; + private readonly string $className; public function __construct(string $className) { @@ -43,10 +41,8 @@ public function toString(): string /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate */ - protected function matches($other): bool + protected function matches(mixed $other): bool { return $other instanceof $this->className; } @@ -57,29 +53,29 @@ protected function matches($other): bool * The beginning of failure messages is "Failed asserting that" in most * cases. This method should return the second part of that sentence. * - * @param mixed $other evaluated value or object + * @throws \PHPUnit\Framework\Exception */ - protected function failureDescription($other): string + protected function failureDescription(mixed $other): string { - if ($other !== null) { - $message = ''; - - if ($other instanceof Throwable) { - $message = '. Message was: "' . $other->getMessage() . '" at' - . "\n" . Filter::getFilteredStacktrace($other); - } - + if ($other === null) { return sprintf( - 'exception of type "%s" matches expected exception "%s"%s', - get_class($other), + 'exception of type "%s" is thrown', $this->className, - $message, ); } + $message = ''; + + if ($other instanceof Throwable) { + $message = '. Message was: "' . $other->getMessage() . '" at' + . "\n" . Filter::stackTraceFromThrowableAsString($other); + } + return sprintf( - 'exception of type "%s" is thrown', + 'exception of type "%s" matches expected exception "%s"%s', + $other::class, $this->className, + $message, ); } } diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Exception/ExceptionCode.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Exception/ExceptionCode.php index 4d65e03b9..666f73372 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Exception/ExceptionCode.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Exception/ExceptionCode.php @@ -10,41 +10,34 @@ namespace PHPUnit\Framework\Constraint; use function sprintf; -use SebastianBergmann\RecursionContext\InvalidArgumentException; -use Throwable; +use PHPUnit\Util\Exporter; /** * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class ExceptionCode extends Constraint { - /** - * @var int|string - */ - private $expectedCode; + private readonly int|string $expectedCode; - /** - * @param int|string $expected - */ - public function __construct($expected) + public function __construct(int|string $expected) { $this->expectedCode = $expected; } public function toString(): string { - return 'exception code is '; + return 'exception code is ' . $this->expectedCode; } /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. - * - * @param Throwable $other */ - protected function matches($other): bool + protected function matches(mixed $other): bool { - return (string) $other->getCode() === (string) $this->expectedCode; + return (string) $other === (string) $this->expectedCode; } /** @@ -52,17 +45,13 @@ protected function matches($other): bool * * The beginning of failure messages is "Failed asserting that" in most * cases. This method should return the second part of that sentence. - * - * @param mixed $other evaluated value or object - * - * @throws InvalidArgumentException */ - protected function failureDescription($other): string + protected function failureDescription(mixed $other): string { return sprintf( '%s is equal to expected exception code %s', - $this->exporter()->export($other->getCode()), - $this->exporter()->export($this->expectedCode), + Exporter::export($other), + Exporter::export($this->expectedCode), ); } } diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Exception/ExceptionMessage.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Exception/ExceptionMessage.php deleted file mode 100644 index 5139e7200..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Exception/ExceptionMessage.php +++ /dev/null @@ -1,78 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use function sprintf; -use function strpos; -use Throwable; - -/** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - */ -final class ExceptionMessage extends Constraint -{ - /** - * @var string - */ - private $expectedMessage; - - public function __construct(string $expected) - { - $this->expectedMessage = $expected; - } - - public function toString(): string - { - if ($this->expectedMessage === '') { - return 'exception message is empty'; - } - - return 'exception message contains '; - } - - /** - * Evaluates the constraint for parameter $other. Returns true if the - * constraint is met, false otherwise. - * - * @param Throwable $other - */ - protected function matches($other): bool - { - if ($this->expectedMessage === '') { - return $other->getMessage() === ''; - } - - return strpos((string) $other->getMessage(), $this->expectedMessage) !== false; - } - - /** - * Returns the description of the failure. - * - * The beginning of failure messages is "Failed asserting that" in most - * cases. This method should return the second part of that sentence. - * - * @param mixed $other evaluated value or object - */ - protected function failureDescription($other): string - { - if ($this->expectedMessage === '') { - return sprintf( - "exception message is empty but is '%s'", - $other->getMessage(), - ); - } - - return sprintf( - "exception message '%s' contains '%s'", - $other->getMessage(), - $this->expectedMessage, - ); - } -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Exception/ExceptionMessageIsOrContains.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Exception/ExceptionMessageIsOrContains.php new file mode 100644 index 000000000..ae9209039 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Exception/ExceptionMessageIsOrContains.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 PHPUnit\Framework\Constraint; + +use function sprintf; +use function str_contains; +use PHPUnit\Util\Exporter; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ExceptionMessageIsOrContains extends Constraint +{ + private readonly string $expectedMessage; + + public function __construct(string $expectedMessage) + { + $this->expectedMessage = $expectedMessage; + } + + public function toString(): string + { + if ($this->expectedMessage === '') { + return 'exception message is empty'; + } + + return 'exception message contains ' . Exporter::export($this->expectedMessage); + } + + protected function matches(mixed $other): bool + { + if ($this->expectedMessage === '') { + return $other === ''; + } + + return str_contains((string) $other, $this->expectedMessage); + } + + /** + * Returns the description of the failure. + * + * The beginning of failure messages is "Failed asserting that" in most + * cases. This method should return the second part of that sentence. + */ + protected function failureDescription(mixed $other): string + { + if ($this->expectedMessage === '') { + return sprintf( + "exception message is empty but is '%s'", + $other, + ); + } + + return sprintf( + "exception message '%s' contains '%s'", + $other, + $this->expectedMessage, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Exception/ExceptionMessageMatchesRegularExpression.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Exception/ExceptionMessageMatchesRegularExpression.php new file mode 100644 index 000000000..611af0374 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Exception/ExceptionMessageMatchesRegularExpression.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\Constraint; + +use function preg_match; +use function sprintf; +use Exception; +use PHPUnit\Util\Exporter; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ExceptionMessageMatchesRegularExpression extends Constraint +{ + private readonly string $regularExpression; + + public function __construct(string $regularExpression) + { + $this->regularExpression = $regularExpression; + } + + public function toString(): string + { + return 'exception message matches ' . Exporter::export($this->regularExpression); + } + + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @throws \PHPUnit\Framework\Exception + * @throws Exception + */ + protected function matches(mixed $other): bool + { + $match = @preg_match($this->regularExpression, (string) $other); + + if ($match === false) { + throw new \PHPUnit\Framework\Exception( + sprintf( + 'Invalid expected exception message regular expression given: %s', + $this->regularExpression, + ), + ); + } + + return $match === 1; + } + + /** + * Returns the description of the failure. + * + * The beginning of failure messages is "Failed asserting that" in most + * cases. This method should return the second part of that sentence. + */ + protected function failureDescription(mixed $other): string + { + return sprintf( + "exception message '%s' matches '%s'", + $other, + $this->regularExpression, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Exception/ExceptionMessageRegularExpression.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Exception/ExceptionMessageRegularExpression.php deleted file mode 100644 index bc7377091..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Exception/ExceptionMessageRegularExpression.php +++ /dev/null @@ -1,74 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use function sprintf; -use Exception; -use PHPUnit\Util\RegularExpression as RegularExpressionUtil; - -/** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - */ -final class ExceptionMessageRegularExpression extends Constraint -{ - /** - * @var string - */ - private $expectedMessageRegExp; - - public function __construct(string $expected) - { - $this->expectedMessageRegExp = $expected; - } - - public function toString(): string - { - return 'exception message matches '; - } - - /** - * Evaluates the constraint for parameter $other. Returns true if the - * constraint is met, false otherwise. - * - * @param \PHPUnit\Framework\Exception $other - * - * @throws \PHPUnit\Framework\Exception - * @throws Exception - */ - protected function matches($other): bool - { - $match = RegularExpressionUtil::safeMatch($this->expectedMessageRegExp, $other->getMessage()); - - if ($match === false) { - throw new \PHPUnit\Framework\Exception( - "Invalid expected exception message regex given: '{$this->expectedMessageRegExp}'", - ); - } - - return $match === 1; - } - - /** - * Returns the description of the failure. - * - * The beginning of failure messages is "Failed asserting that" in most - * cases. This method should return the second part of that sentence. - * - * @param mixed $other evaluated value or object - */ - protected function failureDescription($other): string - { - return sprintf( - "exception message '%s' matches '%s'", - $other->getMessage(), - $this->expectedMessageRegExp, - ); - } -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Filesystem/DirectoryExists.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Filesystem/DirectoryExists.php index 24268c7d3..83b991e1a 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Filesystem/DirectoryExists.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Filesystem/DirectoryExists.php @@ -28,10 +28,8 @@ public function toString(): string /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate */ - protected function matches($other): bool + protected function matches(mixed $other): bool { return is_dir($other); } @@ -41,10 +39,8 @@ protected function matches($other): bool * * The beginning of failure messages is "Failed asserting that" in most * cases. This method should return the second part of that sentence. - * - * @param mixed $other evaluated value or object */ - protected function failureDescription($other): string + protected function failureDescription(mixed $other): string { return sprintf( 'directory "%s" exists', diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Filesystem/FileExists.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Filesystem/FileExists.php index 6cae95024..cfc3b1b6f 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Filesystem/FileExists.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Filesystem/FileExists.php @@ -28,10 +28,8 @@ public function toString(): string /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate */ - protected function matches($other): bool + protected function matches(mixed $other): bool { return file_exists($other); } @@ -41,10 +39,8 @@ protected function matches($other): bool * * The beginning of failure messages is "Failed asserting that" in most * cases. This method should return the second part of that sentence. - * - * @param mixed $other evaluated value or object */ - protected function failureDescription($other): string + protected function failureDescription(mixed $other): string { return sprintf( 'file "%s" exists', diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Filesystem/IsReadable.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Filesystem/IsReadable.php index 124369386..1a32546ce 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Filesystem/IsReadable.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Filesystem/IsReadable.php @@ -28,10 +28,8 @@ public function toString(): string /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate */ - protected function matches($other): bool + protected function matches(mixed $other): bool { return is_readable($other); } @@ -41,10 +39,8 @@ protected function matches($other): bool * * The beginning of failure messages is "Failed asserting that" in most * cases. This method should return the second part of that sentence. - * - * @param mixed $other evaluated value or object */ - protected function failureDescription($other): string + protected function failureDescription(mixed $other): string { return sprintf( '"%s" is readable', diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Filesystem/IsWritable.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Filesystem/IsWritable.php index 8da020764..24e94f821 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Filesystem/IsWritable.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Filesystem/IsWritable.php @@ -28,10 +28,8 @@ public function toString(): string /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate */ - protected function matches($other): bool + protected function matches(mixed $other): bool { return is_writable($other); } @@ -41,10 +39,8 @@ protected function matches($other): bool * * The beginning of failure messages is "Failed asserting that" in most * cases. This method should return the second part of that sentence. - * - * @param mixed $other evaluated value or object */ - protected function failureDescription($other): string + protected function failureDescription(mixed $other): string { return sprintf( '"%s" is writable', diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/IsAnything.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/IsAnything.php index db84a7431..85df5f92f 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Constraint/IsAnything.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Constraint/IsAnything.php @@ -9,8 +9,6 @@ */ namespace PHPUnit\Framework\Constraint; -use PHPUnit\Framework\ExpectationFailedException; - /** * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ @@ -25,10 +23,8 @@ final class IsAnything extends Constraint * If $returnResult is true, the result of the evaluation is returned as * a boolean value instead: true in case of success, false in case of a * failure. - * - * @throws ExpectationFailedException */ - public function evaluate($other, string $description = '', bool $returnResult = false): ?bool + public function evaluate(mixed $other, string $description = '', bool $returnResult = false): ?bool { return $returnResult ? true : null; } diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/IsIdentical.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/IsIdentical.php index 9eb44a992..03ba16f77 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Constraint/IsIdentical.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Constraint/IsIdentical.php @@ -9,26 +9,25 @@ */ namespace PHPUnit\Framework\Constraint; -use function get_class; +use function explode; +use function gettype; use function is_array; use function is_object; use function is_string; use function sprintf; use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Util\Exporter; use SebastianBergmann\Comparator\ComparisonFailure; -use SebastianBergmann\RecursionContext\InvalidArgumentException; +use UnitEnum; /** * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ final class IsIdentical extends Constraint { - /** - * @var mixed - */ - private $value; + private readonly mixed $value; - public function __construct($value) + public function __construct(mixed $value) { $this->value = $value; } @@ -44,9 +43,8 @@ public function __construct($value) * failure. * * @throws ExpectationFailedException - * @throws InvalidArgumentException */ - public function evaluate($other, string $description = '', bool $returnResult = false): ?bool + public function evaluate(mixed $other, string $description = '', bool $returnResult = false): ?bool { $success = $this->value === $other; @@ -67,13 +65,13 @@ public function evaluate($other, string $description = '', bool $returnResult = ); } - // if both values are array, make sure a diff is generated - if (is_array($this->value) && is_array($other)) { + // if both values are array or enums, make sure a diff is generated + if ((is_array($this->value) && is_array($other)) || ($this->value instanceof UnitEnum && $other instanceof UnitEnum)) { $f = new ComparisonFailure( $this->value, $other, - $this->exporter()->export($this->value), - $this->exporter()->export($other), + Exporter::export($this->value), + Exporter::export($other), ); } @@ -85,17 +83,15 @@ public function evaluate($other, string $description = '', bool $returnResult = /** * Returns a string representation of the constraint. - * - * @throws InvalidArgumentException */ public function toString(): string { if (is_object($this->value)) { return 'is identical to an object of class "' . - get_class($this->value) . '"'; + $this->value::class . '"'; } - return 'is identical to ' . $this->exporter()->export($this->value); + return 'is identical to ' . Exporter::export($this->value); } /** @@ -103,17 +99,17 @@ public function toString(): string * * The beginning of failure messages is "Failed asserting that" in most * cases. This method should return the second part of that sentence. - * - * @param mixed $other evaluated value or object - * - * @throws InvalidArgumentException */ - protected function failureDescription($other): string + protected function failureDescription(mixed $other): string { if (is_object($this->value) && is_object($other)) { return 'two variables reference the same object'; } + if (explode(' ', gettype($this->value), 2)[0] === 'resource' && explode(' ', gettype($other), 2)[0] === 'resource') { + return 'two variables reference the same resource'; + } + if (is_string($this->value) && is_string($other)) { return 'two strings are identical'; } diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/JsonMatches.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/JsonMatches.php index 6fbd38c3b..3fd6736a4 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Constraint/JsonMatches.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Constraint/JsonMatches.php @@ -11,21 +11,17 @@ use function json_decode; use function sprintf; -use PHPUnit\Framework\Exception; use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Util\InvalidJsonException; use PHPUnit\Util\Json; use SebastianBergmann\Comparator\ComparisonFailure; -use SebastianBergmann\RecursionContext\InvalidArgumentException; /** * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ final class JsonMatches extends Constraint { - /** - * @var string - */ - private $value; + private readonly string $value; public function __construct(string $value) { @@ -48,10 +44,8 @@ public function toString(): string * constraint is met, false otherwise. * * This method can be overridden to implement the evaluation algorithm. - * - * @param mixed $other value or object to evaluate */ - protected function matches($other): bool + protected function matches(mixed $other): bool { [$error, $recodedOther] = Json::canonicalize($other); @@ -65,22 +59,16 @@ protected function matches($other): bool return false; } - return $recodedOther == $recodedValue; + return $recodedOther === $recodedValue; } /** * Throws an exception for the given compared value and test description. * - * @param mixed $other evaluated value or object - * @param string $description Additional information about the test - * - * @throws Exception * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @psalm-return never-return + * @throws InvalidJsonException */ - protected function fail($other, $description, ?ComparisonFailure $comparisonFailure = null): void + protected function fail(mixed $other, string $description, ?ComparisonFailure $comparisonFailure = null): never { if ($comparisonFailure === null) { [$error, $recodedOther] = Json::canonicalize($other); @@ -100,7 +88,6 @@ protected function fail($other, $description, ?ComparisonFailure $comparisonFail json_decode($other), Json::prettify($recodedValue), Json::prettify($recodedOther), - false, 'Failed asserting that two json values are equal.', ); } diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/JsonMatchesErrorMessageProvider.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/JsonMatchesErrorMessageProvider.php deleted file mode 100644 index 4bf19e27d..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/Constraint/JsonMatchesErrorMessageProvider.php +++ /dev/null @@ -1,78 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use const JSON_ERROR_CTRL_CHAR; -use const JSON_ERROR_DEPTH; -use const JSON_ERROR_NONE; -use const JSON_ERROR_STATE_MISMATCH; -use const JSON_ERROR_SYNTAX; -use const JSON_ERROR_UTF8; -use function strtolower; - -/** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - */ -final class JsonMatchesErrorMessageProvider -{ - /** - * Translates JSON error to a human readable string. - */ - public static function determineJsonError(string $error, string $prefix = ''): ?string - { - switch ($error) { - case JSON_ERROR_NONE: - return null; - - case JSON_ERROR_DEPTH: - return $prefix . 'Maximum stack depth exceeded'; - - case JSON_ERROR_STATE_MISMATCH: - return $prefix . 'Underflow or the modes mismatch'; - - case JSON_ERROR_CTRL_CHAR: - return $prefix . 'Unexpected control character found'; - - case JSON_ERROR_SYNTAX: - return $prefix . 'Syntax error, malformed JSON'; - - case JSON_ERROR_UTF8: - return $prefix . 'Malformed UTF-8 characters, possibly incorrectly encoded'; - - default: - return $prefix . 'Unknown error'; - } - } - - /** - * Translates a given type to a human readable message prefix. - */ - public static function translateTypeToPrefix(string $type): string - { - switch (strtolower($type)) { - case 'expected': - $prefix = 'Expected value JSON decode error - '; - - break; - - case 'actual': - $prefix = 'Actual value JSON decode error - '; - - break; - - default: - $prefix = ''; - - break; - } - - return $prefix; - } -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Math/IsFinite.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Math/IsFinite.php index 9a2f32866..b70de503d 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Math/IsFinite.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Math/IsFinite.php @@ -27,10 +27,8 @@ public function toString(): string /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate */ - protected function matches($other): bool + protected function matches(mixed $other): bool { return is_finite($other); } diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Math/IsInfinite.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Math/IsInfinite.php index c718514c2..dbf4803bc 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Math/IsInfinite.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Math/IsInfinite.php @@ -27,10 +27,8 @@ public function toString(): string /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate */ - protected function matches($other): bool + protected function matches(mixed $other): bool { return is_infinite($other); } diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Math/IsNan.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Math/IsNan.php index 0062c5b5c..f9c47219e 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Math/IsNan.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Math/IsNan.php @@ -27,10 +27,8 @@ public function toString(): string /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate */ - protected function matches($other): bool + protected function matches(mixed $other): bool { return is_nan($other); } diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Object/ClassHasAttribute.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Object/ClassHasAttribute.php deleted file mode 100644 index 40e1d614e..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Object/ClassHasAttribute.php +++ /dev/null @@ -1,90 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use function get_class; -use function is_object; -use function sprintf; -use PHPUnit\Framework\Exception; -use ReflectionClass; -use ReflectionException; - -/** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4601 - */ -class ClassHasAttribute extends Constraint -{ - /** - * @var string - */ - private $attributeName; - - public function __construct(string $attributeName) - { - $this->attributeName = $attributeName; - } - - /** - * Returns a string representation of the constraint. - */ - public function toString(): string - { - return sprintf( - 'has attribute "%s"', - $this->attributeName, - ); - } - - /** - * Evaluates the constraint for parameter $other. Returns true if the - * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate - */ - protected function matches($other): bool - { - try { - return (new ReflectionClass($other))->hasProperty($this->attributeName); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - $e->getCode(), - $e, - ); - } - // @codeCoverageIgnoreEnd - } - - /** - * Returns the description of the failure. - * - * The beginning of failure messages is "Failed asserting that" in most - * cases. This method should return the second part of that sentence. - * - * @param mixed $other evaluated value or object - */ - protected function failureDescription($other): string - { - return sprintf( - '%sclass "%s" %s', - is_object($other) ? 'object of ' : '', - is_object($other) ? get_class($other) : $other, - $this->toString(), - ); - } - - protected function attributeName(): string - { - return $this->attributeName; - } -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Object/ClassHasStaticAttribute.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Object/ClassHasStaticAttribute.php deleted file mode 100644 index bd5eefe49..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Object/ClassHasStaticAttribute.php +++ /dev/null @@ -1,61 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use function sprintf; -use PHPUnit\Framework\Exception; -use ReflectionClass; -use ReflectionException; - -/** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4601 - */ -final class ClassHasStaticAttribute extends ClassHasAttribute -{ - /** - * Returns a string representation of the constraint. - */ - public function toString(): string - { - return sprintf( - 'has static attribute "%s"', - $this->attributeName(), - ); - } - - /** - * Evaluates the constraint for parameter $other. Returns true if the - * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate - */ - protected function matches($other): bool - { - try { - $class = new ReflectionClass($other); - - if ($class->hasProperty($this->attributeName())) { - return $class->getProperty($this->attributeName())->isStatic(); - } - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - $e->getCode(), - $e, - ); - } - // @codeCoverageIgnoreEnd - - return false; - } -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Object/ObjectEquals.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Object/ObjectEquals.php index b837b4cd1..2c78ea427 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Object/ObjectEquals.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Object/ObjectEquals.php @@ -9,7 +9,8 @@ */ namespace PHPUnit\Framework\Constraint; -use function get_class; +use function assert; +use function count; use function is_object; use PHPUnit\Framework\ActualValueIsNotAnObjectException; use PHPUnit\Framework\ComparisonMethodDoesNotAcceptParameterTypeException; @@ -25,15 +26,8 @@ */ final class ObjectEquals extends Constraint { - /** - * @var object - */ - private $expected; - - /** - * @var string - */ - private $method; + private readonly object $expected; + private readonly string $method; public function __construct(object $object, string $method = 'equals') { @@ -54,7 +48,7 @@ public function toString(): string * @throws ComparisonMethodDoesNotDeclareParameterTypeException * @throws ComparisonMethodDoesNotExistException */ - protected function matches($other): bool + protected function matches(mixed $other): bool { if (!is_object($other)) { throw new ActualValueIsNotAnObjectException; @@ -64,17 +58,16 @@ protected function matches($other): bool if (!$object->hasMethod($this->method)) { throw new ComparisonMethodDoesNotExistException( - get_class($other), + $other::class, $this->method, ); } - /** @noinspection PhpUnhandledExceptionInspection */ $method = $object->getMethod($this->method); if (!$method->hasReturnType()) { throw new ComparisonMethodDoesNotDeclareBoolReturnTypeException( - get_class($other), + $other::class, $this->method, ); } @@ -83,37 +76,38 @@ protected function matches($other): bool if (!$returnType instanceof ReflectionNamedType) { throw new ComparisonMethodDoesNotDeclareBoolReturnTypeException( - get_class($other), + $other::class, $this->method, ); } if ($returnType->allowsNull()) { throw new ComparisonMethodDoesNotDeclareBoolReturnTypeException( - get_class($other), + $other::class, $this->method, ); } if ($returnType->getName() !== 'bool') { throw new ComparisonMethodDoesNotDeclareBoolReturnTypeException( - get_class($other), + $other::class, $this->method, ); } if ($method->getNumberOfParameters() !== 1 || $method->getNumberOfRequiredParameters() !== 1) { throw new ComparisonMethodDoesNotDeclareExactlyOneParameterException( - get_class($other), + $other::class, $this->method, ); } + assert(count($method->getParameters()) > 0); $parameter = $method->getParameters()[0]; if (!$parameter->hasType()) { throw new ComparisonMethodDoesNotDeclareParameterTypeException( - get_class($other), + $other::class, $this->method, ); } @@ -122,7 +116,7 @@ protected function matches($other): bool if (!$type instanceof ReflectionNamedType) { throw new ComparisonMethodDoesNotDeclareParameterTypeException( - get_class($other), + $other::class, $this->method, ); } @@ -130,21 +124,22 @@ protected function matches($other): bool $typeName = $type->getName(); if ($typeName === 'self') { - $typeName = get_class($other); + $typeName = $other::class; } if (!$this->expected instanceof $typeName) { throw new ComparisonMethodDoesNotAcceptParameterTypeException( - get_class($other), + $other::class, $this->method, - get_class($this->expected), + $this->expected::class, ); } + /** @phpstan-ignore method.dynamicName */ return $other->{$this->method}($this->expected); } - protected function failureDescription($other): string + protected function failureDescription(mixed $other): string { return $this->toString(); } diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Object/ObjectHasAttribute.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Object/ObjectHasAttribute.php deleted file mode 100644 index 602cb05de..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Object/ObjectHasAttribute.php +++ /dev/null @@ -1,31 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Constraint; - -use ReflectionObject; - -/** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4601 - */ -final class ObjectHasAttribute extends ClassHasAttribute -{ - /** - * Evaluates the constraint for parameter $other. Returns true if the - * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate - */ - protected function matches($other): bool - { - return (new ReflectionObject($other))->hasProperty($this->attributeName()); - } -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Object/ObjectHasProperty.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Object/ObjectHasProperty.php index c41d21a14..74c40c75b 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Object/ObjectHasProperty.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Object/ObjectHasProperty.php @@ -9,7 +9,6 @@ */ namespace PHPUnit\Framework\Constraint; -use function get_class; use function gettype; use function is_object; use function sprintf; @@ -20,10 +19,7 @@ */ final class ObjectHasProperty extends Constraint { - /** - * @var string - */ - private $propertyName; + private readonly string $propertyName; public function __construct(string $propertyName) { @@ -47,7 +43,7 @@ public function toString(): string * * @param mixed $other value or object to evaluate */ - protected function matches($other): bool + protected function matches(mixed $other): bool { if (!is_object($other)) { return false; @@ -64,12 +60,12 @@ protected function matches($other): bool * * @param mixed $other evaluated value or object */ - protected function failureDescription($other): string + protected function failureDescription(mixed $other): string { if (is_object($other)) { return sprintf( 'object of class "%s" %s', - get_class($other), + $other::class, $this->toString(), ); } diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Operator/BinaryOperator.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Operator/BinaryOperator.php index 11c86b526..feb6d0408 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Operator/BinaryOperator.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Operator/BinaryOperator.php @@ -10,7 +10,6 @@ namespace PHPUnit\Framework\Constraint; use function array_map; -use function array_values; use function count; /** @@ -19,28 +18,16 @@ abstract class BinaryOperator extends Operator { /** - * @var Constraint[] + * @var list */ - private $constraints = []; + private readonly array $constraints; - public static function fromConstraints(Constraint ...$constraints): self + protected function __construct(mixed ...$constraints) { - $constraint = new static; - - $constraint->constraints = $constraints; - - return $constraint; - } - - /** - * @param mixed[] $constraints - */ - public function setConstraints(array $constraints): void - { - $this->constraints = array_map(function ($constraint): Constraint - { - return $this->checkConstraint($constraint); - }, array_values($constraints)); + $this->constraints = array_map( + fn (mixed $constraint): Constraint => $this->checkConstraint($constraint), + $constraints, + ); } /** @@ -88,7 +75,7 @@ public function count(): int } /** - * Returns the nested constraints. + * @return list */ final protected function constraints(): array { @@ -112,7 +99,7 @@ final protected function constraintNeedsParentheses(Constraint $constraint): boo */ protected function reduce(): Constraint { - if ($this->arity() === 1 && $this->constraints[0] instanceof Operator) { + if (count($this->constraints) === 1 && $this->constraints[0] instanceof Operator) { return $this->constraints[0]->reduce(); } @@ -121,9 +108,6 @@ protected function reduce(): Constraint /** * Returns string representation of given operand in context of this operator. - * - * @param Constraint $constraint operand constraint - * @param int $position position of $constraint in this expression */ private function constraintToString(Constraint $constraint, int $position): string { diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Operator/LogicalAnd.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Operator/LogicalAnd.php index a1af4dd32..e0a00a0ea 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Operator/LogicalAnd.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Operator/LogicalAnd.php @@ -14,6 +14,11 @@ */ final class LogicalAnd extends BinaryOperator { + public static function fromConstraints(mixed ...$constraints): self + { + return new self(...$constraints); + } + /** * Returns the name of this operator. */ @@ -35,10 +40,8 @@ public function precedence(): int /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate */ - protected function matches($other): bool + protected function matches(mixed $other): bool { foreach ($this->constraints() as $constraint) { if (!$constraint->evaluate($other, '', true)) { diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Operator/LogicalNot.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Operator/LogicalNot.php index 586abc505..224260909 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Operator/LogicalNot.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Operator/LogicalNot.php @@ -14,6 +14,7 @@ use function preg_match; use function preg_quote; use function preg_replace; +use PHPUnit\Framework\ExpectationFailedException; /** * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit @@ -55,14 +56,11 @@ public static function negate(string $string): string } $positives = array_map( - static function (string $s) - { - return '/\\b' . preg_quote($s, '/') . '/'; - }, + static fn (string $s) => '/\\b' . preg_quote($s, '/') . '/', $positives, ); - if (count($matches) > 0) { + if (count($matches) >= 3) { $nonInput = $matches[2]; $negatedString = preg_replace( @@ -107,9 +105,9 @@ public function precedence(): int * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. * - * @param mixed $other value or object to evaluate + * @throws ExpectationFailedException */ - protected function matches($other): bool + protected function matches(mixed $other): bool { return !$this->constraint()->evaluate($other, '', true); } diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Operator/LogicalOr.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Operator/LogicalOr.php index 2932de675..cbd87b9c2 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Operator/LogicalOr.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Operator/LogicalOr.php @@ -14,6 +14,11 @@ */ final class LogicalOr extends BinaryOperator { + public static function fromConstraints(mixed ...$constraints): self + { + return new self(...$constraints); + } + /** * Returns the name of this operator. */ @@ -35,10 +40,8 @@ public function precedence(): int /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate */ - public function matches($other): bool + public function matches(mixed $other): bool { foreach ($this->constraints() as $constraint) { if ($constraint->evaluate($other, '', true)) { diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Operator/LogicalXor.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Operator/LogicalXor.php index ee1b1c293..3b40a1261 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Operator/LogicalXor.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Operator/LogicalXor.php @@ -11,12 +11,18 @@ use function array_reduce; use function array_shift; +use PHPUnit\Framework\ExpectationFailedException; /** * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ final class LogicalXor extends BinaryOperator { + public static function fromConstraints(mixed ...$constraints): self + { + return new self(...$constraints); + } + /** * Returns the name of this operator. */ @@ -39,9 +45,9 @@ public function precedence(): int * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. * - * @param mixed $other value or object to evaluate + * @throws ExpectationFailedException */ - public function matches($other): bool + public function matches(mixed $other): bool { $constraints = $this->constraints(); @@ -53,10 +59,7 @@ public function matches($other): bool return array_reduce( $constraints, - static function (bool $matches, Constraint $constraint) use ($other): bool - { - return $matches xor $constraint->evaluate($other, '', true); - }, + static fn (?bool $matches, Constraint $constraint): bool => $matches xor $constraint->evaluate($other, '', true), $initial->evaluate($other, '', true), ); } diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Operator/Operator.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Operator/Operator.php index 3f51a0f40..1195156e0 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Operator/Operator.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Operator/Operator.php @@ -34,7 +34,7 @@ abstract public function arity(): int; /** * Validates $constraint argument. */ - protected function checkConstraint($constraint): Constraint + protected function checkConstraint(mixed $constraint): Constraint { if (!$constraint instanceof Constraint) { return new IsEqual($constraint); diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Operator/UnaryOperator.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Operator/UnaryOperator.php index f8c24a52a..d6ac6e3f6 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Operator/UnaryOperator.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Operator/UnaryOperator.php @@ -10,22 +10,15 @@ namespace PHPUnit\Framework\Constraint; use function count; -use SebastianBergmann\RecursionContext\InvalidArgumentException; /** * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ abstract class UnaryOperator extends Operator { - /** - * @var Constraint - */ - private $constraint; + private readonly Constraint $constraint; - /** - * @param Constraint|mixed $constraint - */ - public function __construct($constraint) + public function __construct(mixed $constraint) { $this->constraint = $this->checkConstraint($constraint); } @@ -77,12 +70,8 @@ public function count(): int * * The beginning of failure messages is "Failed asserting that" in most * cases. This method should return the second part of that sentence. - * - * @param mixed $other evaluated value or object - * - * @throws InvalidArgumentException */ - protected function failureDescription($other): string + protected function failureDescription(mixed $other): string { $reduced = $this->reduce(); @@ -113,8 +102,6 @@ protected function failureDescription($other): string * The method may be overwritten in a subclass to apply default * transformation in case the operand constraint does not provide its own * custom strings via toStringInContext() or failureDescriptionInContext(). - * - * @param string $string the string to be transformed */ protected function transformString(string $string): string { diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/String/IsJson.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/String/IsJson.php index bdf363326..582b58e8b 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Constraint/String/IsJson.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Constraint/String/IsJson.php @@ -9,10 +9,16 @@ */ namespace PHPUnit\Framework\Constraint; +use const JSON_ERROR_CTRL_CHAR; +use const JSON_ERROR_DEPTH; +use const JSON_ERROR_NONE; +use const JSON_ERROR_STATE_MISMATCH; +use const JSON_ERROR_SYNTAX; +use const JSON_ERROR_UTF8; +use function is_string; use function json_decode; use function json_last_error; use function sprintf; -use SebastianBergmann\RecursionContext\InvalidArgumentException; /** * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit @@ -30,18 +36,16 @@ public function toString(): string /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate */ - protected function matches($other): bool + protected function matches(mixed $other): bool { - if ($other === '') { + if (!is_string($other) || $other === '') { return false; } json_decode($other); - if (json_last_error()) { + if (json_last_error() !== JSON_ERROR_NONE) { return false; } @@ -53,26 +57,35 @@ protected function matches($other): bool * * The beginning of failure messages is "Failed asserting that" in most * cases. This method should return the second part of that sentence. - * - * @param mixed $other evaluated value or object - * - * @throws InvalidArgumentException */ - protected function failureDescription($other): string + protected function failureDescription(mixed $other): string { + if (!is_string($other)) { + return $this->valueToTypeStringFragment($other) . 'is valid JSON'; + } + if ($other === '') { return 'an empty string is valid JSON'; } - json_decode($other); - $error = (string) JsonMatchesErrorMessageProvider::determineJsonError( - (string) json_last_error(), - ); - return sprintf( - '%s is valid JSON (%s)', - $this->exporter()->shortenedExport($other), - $error, + 'a string is valid JSON (%s)', + $this->determineJsonError($other), ); } + + private function determineJsonError(string $json): string + { + json_decode($json); + + return match (json_last_error()) { + JSON_ERROR_NONE => '', + 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_SYNTAX => 'Syntax error, malformed JSON', + JSON_ERROR_UTF8 => 'Malformed UTF-8 characters, possibly incorrectly encoded', + default => 'Unknown error', + }; + } } diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/String/RegularExpression.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/String/RegularExpression.php index 9ccfb9bd5..03b0e4ea1 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Constraint/String/RegularExpression.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Constraint/String/RegularExpression.php @@ -15,12 +15,9 @@ /** * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ -class RegularExpression extends Constraint +final class RegularExpression extends Constraint { - /** - * @var string - */ - private $pattern; + private readonly string $pattern; public function __construct(string $pattern) { @@ -41,10 +38,8 @@ public function toString(): string /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate */ - protected function matches($other): bool + protected function matches(mixed $other): bool { return preg_match($this->pattern, $other) > 0; } diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/String/StringContains.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/String/StringContains.php index 5aa2c8e5a..0f801a605 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Constraint/String/StringContains.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Constraint/String/StringContains.php @@ -9,30 +9,34 @@ */ namespace PHPUnit\Framework\Constraint; +use function is_string; +use function mb_detect_encoding; use function mb_stripos; use function mb_strtolower; use function sprintf; -use function strpos; +use function str_contains; +use function strlen; +use function strtr; +use PHPUnit\Util\Exporter; /** * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ final class StringContains extends Constraint { - /** - * @var string - */ - private $string; - - /** - * @var bool - */ - private $ignoreCase; + private readonly string $needle; + private readonly bool $ignoreCase; + private readonly bool $ignoreLineEndings; - public function __construct(string $string, bool $ignoreCase = false) + public function __construct(string $needle, bool $ignoreCase = false, bool $ignoreLineEndings = false) { - $this->string = $string; - $this->ignoreCase = $ignoreCase; + if ($ignoreLineEndings) { + $needle = $this->normalizeLineEndings($needle); + } + + $this->needle = $needle; + $this->ignoreCase = $ignoreCase; + $this->ignoreLineEndings = $ignoreLineEndings; } /** @@ -40,46 +44,117 @@ public function __construct(string $string, bool $ignoreCase = false) */ public function toString(): string { + $needle = $this->needle; + if ($this->ignoreCase) { - $string = mb_strtolower($this->string, 'UTF-8'); - } else { - $string = $this->string; + $needle = mb_strtolower($this->needle, 'UTF-8'); } return sprintf( - 'contains "%s"', - $string, + 'contains "%s" [%s](length: %s)', + $needle, + $this->detectedEncoding($needle), + strlen($needle), + ); + } + + public function failureDescription(mixed $other): string + { + $stringifiedHaystack = Exporter::export($other); + $haystackEncoding = $this->detectedEncoding($other); + $haystackLength = $this->haystackLength($other); + + $haystackInformation = sprintf( + '%s [%s](length: %s) ', + $stringifiedHaystack, + $haystackEncoding, + $haystackLength, ); + + $needleInformation = $this->toString(); + + return $haystackInformation . $needleInformation; } /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate */ - protected function matches($other): bool + protected function matches(mixed $other): bool { - if ('' === $this->string) { + $haystack = $other; + + if ('' === $this->needle) { return true; } + if (!is_string($haystack)) { + return false; + } + + if ($this->ignoreLineEndings) { + $haystack = $this->normalizeLineEndings($haystack); + } + if ($this->ignoreCase) { /* - * We must use the multi byte safe version so we can accurately compare non latin upper characters with + * We must use the multibyte-safe version, so we can accurately compare non-latin uppercase characters with * their lowercase equivalents. */ - return mb_stripos($other, $this->string, 0, 'UTF-8') !== false; + return mb_stripos($haystack, $this->needle, 0, 'UTF-8') !== false; } /* - * Use the non multi byte safe functions to see if the string is contained in $other. + * Use the non-multibyte safe functions to see if the string is contained in $other. * - * This function is very fast and we don't care about the character position in the string. + * This function is very fast, and we don't care about the character position in the string. * - * Additionally, we want this method to be binary safe so we can check if some binary data is in other binary + * Additionally, we want this method to be binary safe, so we can check if some binary data is in other binary * data. */ - return strpos($other, $this->string) !== false; + return str_contains($haystack, $this->needle); + } + + private function detectedEncoding(mixed $other): string + { + if ($this->ignoreCase) { + return 'Encoding ignored'; + } + + if (!is_string($other)) { + return 'Encoding detection failed'; + } + + $detectedEncoding = mb_detect_encoding($other, null, true); + + if ($detectedEncoding === false) { + return 'Encoding detection failed'; + } + + return $detectedEncoding; + } + + private function haystackLength(mixed $haystack): int + { + if (!is_string($haystack)) { + return 0; + } + + if ($this->ignoreLineEndings) { + $haystack = $this->normalizeLineEndings($haystack); + } + + return strlen($haystack); + } + + private function normalizeLineEndings(string $string): string + { + return strtr( + $string, + [ + "\r\n" => "\n", + "\r" => "\n", + ], + ); } } diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/String/StringEndsWith.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/String/StringEndsWith.php index bb4ce23be..1dd43b84b 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Constraint/String/StringEndsWith.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Constraint/String/StringEndsWith.php @@ -9,21 +9,25 @@ */ namespace PHPUnit\Framework\Constraint; -use function strlen; -use function substr; +use function str_ends_with; +use PHPUnit\Framework\EmptyStringException; /** * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ final class StringEndsWith extends Constraint { + private readonly string $suffix; + /** - * @var string + * @throws EmptyStringException */ - private $suffix; - public function __construct(string $suffix) { + if ($suffix === '') { + throw new EmptyStringException; + } + $this->suffix = $suffix; } @@ -38,11 +42,9 @@ public function toString(): string /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate */ - protected function matches($other): bool + protected function matches(mixed $other): bool { - return substr($other, 0 - strlen($this->suffix)) === $this->suffix; + return str_ends_with((string) $other, $this->suffix); } } diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/String/StringEqualsStringIgnoringLineEndings.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/String/StringEqualsStringIgnoringLineEndings.php new file mode 100644 index 000000000..56c59943e --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Constraint/String/StringEqualsStringIgnoringLineEndings.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 PHPUnit\Framework\Constraint; + +use function sprintf; +use function strtr; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class StringEqualsStringIgnoringLineEndings extends Constraint +{ + private readonly string $string; + + public function __construct(string $string) + { + $this->string = $this->normalizeLineEndings($string); + } + + /** + * Returns a string representation of the constraint. + */ + public function toString(): string + { + return sprintf( + 'is equal to "%s" ignoring line endings', + $this->string, + ); + } + + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + */ + protected function matches(mixed $other): bool + { + return $this->string === $this->normalizeLineEndings((string) $other); + } + + private function normalizeLineEndings(string $string): string + { + return strtr( + $string, + [ + "\r\n" => "\n", + "\r" => "\n", + ], + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/String/StringMatchesFormatDescription.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/String/StringMatchesFormatDescription.php index 9c01ecb9c..4df81715f 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Constraint/String/StringMatchesFormatDescription.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Constraint/String/StringMatchesFormatDescription.php @@ -10,6 +10,7 @@ namespace PHPUnit\Framework\Constraint; use const DIRECTORY_SEPARATOR; +use const PHP_EOL; use function explode; use function implode; use function preg_match; @@ -22,80 +23,100 @@ /** * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ -final class StringMatchesFormatDescription extends RegularExpression +final class StringMatchesFormatDescription extends Constraint { - /** - * @var string - */ - private $string; + private readonly string $formatDescription; - public function __construct(string $string) + public function __construct(string $formatDescription) { - parent::__construct( - $this->createPatternFromFormat( - $this->convertNewlines($string), - ), - ); + $this->formatDescription = $formatDescription; + } - $this->string = $string; + public function toString(): string + { + return 'matches format description:' . PHP_EOL . $this->formatDescription; } /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate */ - protected function matches($other): bool + protected function matches(mixed $other): bool { - return parent::matches( - $this->convertNewlines($other), + $other = $this->convertNewlines($other); + + $matches = preg_match( + $this->regularExpressionForFormatDescription( + $this->convertNewlines($this->formatDescription), + ), + $other, ); + + return $matches > 0; } - protected function failureDescription($other): string + protected function failureDescription(mixed $other): string { return 'string matches format description'; } - protected function additionalFailureDescription($other): string + /** + * Returns a cleaned up diff. + * + * The expected string can contain placeholders like %s and %d. + * By using 'diff' such placeholders compared to the real output will + * always be different, although we don't want to show them as different. + * This method removes the expected differences by figuring out if a difference + * is allowed by the use of a placeholder. + * + * The problem here are %A and %a multiline placeholders since we look at the + * expected and actual output line by line. If differences allowed by those placeholders + * stretch over multiple lines they will still end up in the final diff. + * And since they mess up the line sync between the expected and actual output + * all following allowed changes will not be detected/removed anymore. + */ + protected function additionalFailureDescription(mixed $other): string { - $from = explode("\n", $this->string); + $from = explode("\n", $this->formatDescription); $to = explode("\n", $this->convertNewlines($other)); foreach ($from as $index => $line) { + // is the expected output line different from the actual output line if (isset($to[$index]) && $line !== $to[$index]) { - $line = $this->createPatternFromFormat($line); + $line = $this->regularExpressionForFormatDescription($line); + // if the difference is allowed by a placeholder + // overwrite the expected line with the actual line to prevent it from showing up in the diff if (preg_match($line, $to[$index]) > 0) { $from[$index] = $to[$index]; } } } - $this->string = implode("\n", $from); - $other = implode("\n", $to); + $from = implode("\n", $from); + $to = implode("\n", $to); - return (new Differ(new UnifiedDiffOutputBuilder("--- Expected\n+++ Actual\n")))->diff($this->string, $other); + return $this->differ()->diff($from, $to); } - private function createPatternFromFormat(string $string): string + private function regularExpressionForFormatDescription(string $string): string { $string = strtr( preg_quote($string, '/'), [ '%%' => '%', - '%e' => '\\' . DIRECTORY_SEPARATOR, + '%e' => preg_quote(DIRECTORY_SEPARATOR, '/'), '%s' => '[^\r\n]+', '%S' => '[^\r\n]*', - '%a' => '.+', - '%A' => '.*', + '%a' => '.+?', + '%A' => '.*?', '%w' => '\s*', '%i' => '[+-]?\d+', '%d' => '\d+', '%x' => '[0-9a-fA-F]+', - '%f' => '[+-]?\.?\d+\.?\d*(?:[Ee][+-]?\d+)?', + '%f' => '[+-]?(?:\d+|(?=\.\d))(?:\.\d+)?(?:[Ee][+-]?\d+)?', '%c' => '.', + '%0' => '\x00', ], ); @@ -106,4 +127,9 @@ private function convertNewlines(string $text): string { return preg_replace('/\r\n/', "\n", $text); } + + private function differ(): Differ + { + return new Differ(new UnifiedDiffOutputBuilder("--- Expected\n+++ Actual\n")); + } } diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/String/StringStartsWith.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/String/StringStartsWith.php index 8683e2722..eee545c78 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Constraint/String/StringStartsWith.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Constraint/String/StringStartsWith.php @@ -9,23 +9,23 @@ */ namespace PHPUnit\Framework\Constraint; -use function strpos; -use PHPUnit\Framework\InvalidArgumentException; +use function str_starts_with; +use PHPUnit\Framework\EmptyStringException; /** * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ final class StringStartsWith extends Constraint { + private readonly string $prefix; + /** - * @var string + * @throws EmptyStringException */ - private $prefix; - public function __construct(string $prefix) { if ($prefix === '') { - throw InvalidArgumentException::create(1, 'non-empty string'); + throw new EmptyStringException; } $this->prefix = $prefix; @@ -42,11 +42,9 @@ public function toString(): string /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate */ - protected function matches($other): bool + protected function matches(mixed $other): bool { - return strpos((string) $other, $this->prefix) === 0; + return str_starts_with((string) $other, $this->prefix); } } diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Traversable/ArrayHasKey.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Traversable/ArrayHasKey.php index 98a757a73..fa8d27441 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Traversable/ArrayHasKey.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Traversable/ArrayHasKey.php @@ -12,43 +12,33 @@ use function array_key_exists; use function is_array; use ArrayAccess; -use SebastianBergmann\RecursionContext\InvalidArgumentException; +use PHPUnit\Util\Exporter; /** * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ final class ArrayHasKey extends Constraint { - /** - * @var int|string - */ - private $key; + private readonly mixed $key; - /** - * @param int|string $key - */ - public function __construct($key) + public function __construct(mixed $key) { $this->key = $key; } /** * Returns a string representation of the constraint. - * - * @throws InvalidArgumentException */ public function toString(): string { - return 'has the key ' . $this->exporter()->export($this->key); + return 'has the key ' . Exporter::export($this->key); } /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate */ - protected function matches($other): bool + protected function matches(mixed $other): bool { if (is_array($other)) { return array_key_exists($this->key, $other); @@ -66,12 +56,8 @@ protected function matches($other): bool * * The beginning of failure messages is "Failed asserting that" in most * cases. This method should return the second part of that sentence. - * - * @param mixed $other evaluated value or object - * - * @throws InvalidArgumentException */ - protected function failureDescription($other): string + protected function failureDescription(mixed $other): string { return 'an array ' . $this->toString(); } diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Traversable/IsList.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Traversable/IsList.php new file mode 100644 index 000000000..f6ac30618 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Traversable/IsList.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 PHPUnit\Framework\Constraint; + +use function array_is_list; +use function is_array; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class IsList extends Constraint +{ + /** + * Returns a string representation of the constraint. + */ + public function toString(): string + { + return 'is a list'; + } + + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + */ + protected function matches(mixed $other): bool + { + if (!is_array($other)) { + return false; + } + + return array_is_list($other); + } + + /** + * Returns the description of the failure. + * + * The beginning of failure messages is "Failed asserting that" in most + * cases. This method should return the second part of that sentence. + */ + protected function failureDescription(mixed $other): string + { + return $this->valueToTypeStringFragment($other) . $this->toString(); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Traversable/TraversableContains.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Traversable/TraversableContains.php index 0f934396e..6f1a298bf 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Traversable/TraversableContains.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Traversable/TraversableContains.php @@ -11,31 +11,26 @@ use function is_array; use function sprintf; -use SebastianBergmann\RecursionContext\InvalidArgumentException; +use PHPUnit\Util\Exporter; /** * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ abstract class TraversableContains extends Constraint { - /** - * @var mixed - */ - private $value; + private readonly mixed $value; - public function __construct($value) + public function __construct(mixed $value) { $this->value = $value; } /** * Returns a string representation of the constraint. - * - * @throws InvalidArgumentException */ public function toString(): string { - return 'contains ' . $this->exporter()->export($this->value); + return 'contains ' . Exporter::export($this->value); } /** @@ -43,12 +38,8 @@ public function toString(): string * * The beginning of failure messages is "Failed asserting that" in most * cases. This method should return the second part of that sentence. - * - * @param mixed $other evaluated value or object - * - * @throws InvalidArgumentException */ - protected function failureDescription($other): string + protected function failureDescription(mixed $other): string { return sprintf( '%s %s', @@ -57,7 +48,7 @@ protected function failureDescription($other): string ); } - protected function value() + protected function value(): mixed { return $this->value; } diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Traversable/TraversableContainsEqual.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Traversable/TraversableContainsEqual.php index c315e709a..f89835163 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Traversable/TraversableContainsEqual.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Traversable/TraversableContainsEqual.php @@ -19,17 +19,15 @@ final class TraversableContainsEqual extends TraversableContains /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate */ - protected function matches($other): bool + protected function matches(mixed $other): bool { if ($other instanceof SplObjectStorage) { - return $other->contains($this->value()); + return $other->offsetExists($this->value()); } foreach ($other as $element) { - /* @noinspection TypeUnsafeComparisonInspection */ + /** @phpstan-ignore equal.notAllowed */ if ($this->value() == $element) { return true; } diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Traversable/TraversableContainsIdentical.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Traversable/TraversableContainsIdentical.php index a3437dbc9..b4e295376 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Traversable/TraversableContainsIdentical.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Traversable/TraversableContainsIdentical.php @@ -19,13 +19,11 @@ final class TraversableContainsIdentical extends TraversableContains /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate */ - protected function matches($other): bool + protected function matches(mixed $other): bool { if ($other instanceof SplObjectStorage) { - return $other->contains($this->value()); + return $other->offsetExists($this->value()); } foreach ($other as $element) { diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Traversable/TraversableContainsOnly.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Traversable/TraversableContainsOnly.php index 4f34f72a3..50da87f29 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Traversable/TraversableContainsOnly.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Traversable/TraversableContainsOnly.php @@ -9,40 +9,34 @@ */ namespace PHPUnit\Framework\Constraint; -use PHPUnit\Framework\Exception; use PHPUnit\Framework\ExpectationFailedException; -use SebastianBergmann\RecursionContext\InvalidArgumentException; -use Traversable; +use PHPUnit\Framework\NativeType; /** * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ final class TraversableContainsOnly extends Constraint { - /** - * @var Constraint - */ - private $constraint; + private readonly Constraint $constraint; + private readonly string $type; - /** - * @var string - */ - private $type; + public static function forNativeType(NativeType $type): self + { + return new self(new IsType($type), $type->value); + } /** - * @throws Exception + * @param class-string $type */ - public function __construct(string $type, bool $isNativeType = true) + public static function forClassOrInterface(string $type): self { - if ($isNativeType) { - $this->constraint = new IsType($type); - } else { - $this->constraint = new IsInstanceOf( - $type, - ); - } + return new self(new IsInstanceOf($type), $type); + } - $this->type = $type; + private function __construct(IsInstanceOf|IsType $constraint, string $type) + { + $this->constraint = $constraint; + $this->type = $type; } /** @@ -55,12 +49,9 @@ public function __construct(string $type, bool $isNativeType = true) * a boolean value instead: true in case of success, false in case of a * failure. * - * @param mixed|Traversable $other - * * @throws ExpectationFailedException - * @throws InvalidArgumentException */ - public function evaluate($other, string $description = '', bool $returnResult = false): ?bool + public function evaluate(mixed $other, string $description = '', bool $returnResult = false): bool { $success = true; @@ -72,15 +63,11 @@ public function evaluate($other, string $description = '', bool $returnResult = } } - if ($returnResult) { - return $success; - } - - if (!$success) { + if (!$success && !$returnResult) { $this->fail($other, $description); } - return null; + return $success; } /** diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Type/IsInstanceOf.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Type/IsInstanceOf.php index c1b73a837..df7ebf1e2 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Type/IsInstanceOf.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Type/IsInstanceOf.php @@ -9,10 +9,10 @@ */ namespace PHPUnit\Framework\Constraint; +use function class_exists; +use function interface_exists; use function sprintf; -use ReflectionClass; -use ReflectionException; -use SebastianBergmann\RecursionContext\InvalidArgumentException; +use PHPUnit\Framework\UnknownClassOrInterfaceException; /** * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit @@ -20,13 +20,29 @@ final class IsInstanceOf extends Constraint { /** - * @var string + * @var class-string */ - private $className; + private readonly string $name; - public function __construct(string $className) + /** + * @var 'class'|'interface' + */ + private readonly string $type; + + /** + * @throws UnknownClassOrInterfaceException + */ + public function __construct(string $name) { - $this->className = $className; + if (class_exists($name)) { + $this->type = 'class'; + } elseif (interface_exists($name)) { + $this->type = 'interface'; + } else { + throw new UnknownClassOrInterfaceException($name); + } + + $this->name = $name; } /** @@ -35,21 +51,19 @@ public function __construct(string $className) public function toString(): string { return sprintf( - 'is instance of %s "%s"', - $this->getType(), - $this->className, + 'is an instance of %s %s', + $this->type, + $this->name, ); } /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate */ - protected function matches($other): bool + protected function matches(mixed $other): bool { - return $other instanceof $this->className; + return $other instanceof $this->name; } /** @@ -57,32 +71,9 @@ protected function matches($other): bool * * The beginning of failure messages is "Failed asserting that" in most * cases. This method should return the second part of that sentence. - * - * @param mixed $other evaluated value or object - * - * @throws InvalidArgumentException */ - protected function failureDescription($other): string + protected function failureDescription(mixed $other): string { - return sprintf( - '%s is an instance of %s "%s"', - $this->exporter()->shortenedExport($other), - $this->getType(), - $this->className, - ); - } - - private function getType(): string - { - try { - $reflection = new ReflectionClass($this->className); - - if ($reflection->isInterface()) { - return 'interface'; - } - } catch (ReflectionException $e) { - } - - return 'class'; + return $this->valueToTypeStringFragment($other) . $this->toString(); } } diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Type/IsNull.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Type/IsNull.php index b9fcdd7a7..37c89f7a0 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Type/IsNull.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Type/IsNull.php @@ -25,10 +25,8 @@ public function toString(): string /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate */ - protected function matches($other): bool + protected function matches(mixed $other): bool { return $other === null; } diff --git a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Type/IsType.php b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Type/IsType.php index 285b74a77..c2bff8eda 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Constraint/Type/IsType.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Constraint/Type/IsType.php @@ -21,121 +21,17 @@ use function is_scalar; use function is_string; use function sprintf; -use PHPUnit\Framework\Exception; +use PHPUnit\Framework\NativeType; /** * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ final class IsType extends Constraint { - /** - * @var string - */ - public const TYPE_ARRAY = 'array'; - - /** - * @var string - */ - public const TYPE_BOOL = 'bool'; - - /** - * @var string - */ - public const TYPE_FLOAT = 'float'; - - /** - * @var string - */ - public const TYPE_INT = 'int'; - - /** - * @var string - */ - public const TYPE_NULL = 'null'; - - /** - * @var string - */ - public const TYPE_NUMERIC = 'numeric'; - - /** - * @var string - */ - public const TYPE_OBJECT = 'object'; - - /** - * @var string - */ - public const TYPE_RESOURCE = 'resource'; - - /** - * @var string - */ - public const TYPE_CLOSED_RESOURCE = 'resource (closed)'; - - /** - * @var string - */ - public const TYPE_STRING = 'string'; - - /** - * @var string - */ - public const TYPE_SCALAR = 'scalar'; - - /** - * @var string - */ - public const TYPE_CALLABLE = 'callable'; - - /** - * @var string - */ - public const TYPE_ITERABLE = 'iterable'; - - /** - * @var array - */ - private const KNOWN_TYPES = [ - 'array' => true, - 'boolean' => true, - 'bool' => true, - 'double' => true, - 'float' => true, - 'integer' => true, - 'int' => true, - 'null' => true, - 'numeric' => true, - 'object' => true, - 'real' => true, - 'resource' => true, - 'resource (closed)' => true, - 'string' => true, - 'scalar' => true, - 'callable' => true, - 'iterable' => true, - ]; - - /** - * @var string - */ - private $type; + private readonly NativeType $type; - /** - * @throws Exception - */ - public function __construct(string $type) + public function __construct(NativeType $type) { - if (!isset(self::KNOWN_TYPES[$type])) { - throw new Exception( - sprintf( - 'Type specified for PHPUnit\Framework\Constraint\IsType <%s> ' . - 'is not a valid type.', - $type, - ), - ); - } - $this->type = $type; } @@ -145,63 +41,57 @@ public function __construct(string $type) public function toString(): string { return sprintf( - 'is of type "%s"', - $this->type, + 'is of type %s', + $this->type->value, ); } /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. - * - * @param mixed $other value or object to evaluate */ - protected function matches($other): bool + protected function matches(mixed $other): bool { switch ($this->type) { - case 'numeric': + case NativeType::Numeric: return is_numeric($other); - case 'integer': - case 'int': + case NativeType::Int: return is_int($other); - case 'double': - case 'float': - case 'real': + case NativeType::Float: return is_float($other); - case 'string': + case NativeType::String: return is_string($other); - case 'boolean': - case 'bool': + case NativeType::Bool: return is_bool($other); - case 'null': + case NativeType::Null: return null === $other; - case 'array': + case NativeType::Array: return is_array($other); - case 'object': + case NativeType::Object: return is_object($other); - case 'resource': + case NativeType::Resource: $type = gettype($other); return $type === 'resource' || $type === 'resource (closed)'; - case 'resource (closed)': + case NativeType::ClosedResource: return gettype($other) === 'resource (closed)'; - case 'scalar': + case NativeType::Scalar: return is_scalar($other); - case 'callable': + case NativeType::Callable: return is_callable($other); - case 'iterable': + case NativeType::Iterable: return is_iterable($other); default: diff --git a/app/vendor/phpunit/phpunit/src/Framework/DataProviderTestSuite.php b/app/vendor/phpunit/phpunit/src/Framework/DataProviderTestSuite.php index 4d7ab8597..262530b7a 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/DataProviderTestSuite.php +++ b/app/vendor/phpunit/phpunit/src/Framework/DataProviderTestSuite.php @@ -9,11 +9,16 @@ */ namespace PHPUnit\Framework; +use function assert; +use function class_exists; +use function count; use function explode; -use PHPUnit\Util\Test as TestUtil; -use SebastianBergmann\RecursionContext\InvalidArgumentException; +use PHPUnit\Framework\TestSize\TestSize; +use PHPUnit\Metadata\Api\Groups; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class DataProviderTestSuite extends TestSuite @@ -21,7 +26,12 @@ final class DataProviderTestSuite extends TestSuite /** * @var list */ - private $dependencies = []; + private array $dependencies = []; + + /** + * @var ?non-empty-list + */ + private ?array $providedTests = null; /** * @param list $dependencies @@ -30,23 +40,22 @@ public function setDependencies(array $dependencies): void { $this->dependencies = $dependencies; - foreach ($this->tests as $test) { + foreach ($this->tests() as $test) { if (!$test instanceof TestCase) { - // @codeCoverageIgnoreStart continue; - // @codeCoverageIgnoreStart } + $test->setDependencies($dependencies); } } /** - * @return list + * @return non-empty-list */ public function provides(): array { if ($this->providedTests === null) { - $this->providedTests = [new ExecutionOrderDependency($this->getName())]; + $this->providedTests = [new ExecutionOrderDependency($this->name())]; } return $this->providedTests; @@ -63,14 +72,16 @@ public function requires(): array } /** - * Returns the size of the each test created using the data provider(s). - * - * @throws InvalidArgumentException + * Returns the size of each test created using the data provider(s). */ - public function getSize(): int + public function size(): TestSize { - [$className, $methodName] = explode('::', $this->getName()); + assert(count(explode('::', $this->name())) === 2); + [$className, $methodName] = explode('::', $this->name()); + + assert(class_exists($className)); + assert($methodName !== ''); - return TestUtil::getSize($className, $methodName); + return (new Groups)->size($className, $methodName); } } diff --git a/app/vendor/phpunit/phpunit/src/Framework/Error/Deprecated.php b/app/vendor/phpunit/phpunit/src/Framework/Error/Deprecated.php deleted file mode 100644 index db62195f8..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/Error/Deprecated.php +++ /dev/null @@ -1,17 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Error; - -/** - * @internal - */ -final class Deprecated extends Error -{ -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Error/Error.php b/app/vendor/phpunit/phpunit/src/Framework/Error/Error.php deleted file mode 100644 index 3163a8670..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/Error/Error.php +++ /dev/null @@ -1,26 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Error; - -use PHPUnit\Framework\Exception; - -/** - * @internal - */ -class Error extends Exception -{ - public function __construct(string $message, int $code, string $file, int $line, ?\Exception $previous = null) - { - parent::__construct($message, $code, $previous); - - $this->file = $file; - $this->line = $line; - } -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Error/Notice.php b/app/vendor/phpunit/phpunit/src/Framework/Error/Notice.php deleted file mode 100644 index 54e5e31ea..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/Error/Notice.php +++ /dev/null @@ -1,17 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Error; - -/** - * @internal - */ -final class Notice extends Error -{ -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Error/Warning.php b/app/vendor/phpunit/phpunit/src/Framework/Error/Warning.php deleted file mode 100644 index 0c0c0064f..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/Error/Warning.php +++ /dev/null @@ -1,17 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\Error; - -/** - * @internal - */ -final class Warning extends Error -{ -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/ErrorTestCase.php b/app/vendor/phpunit/phpunit/src/Framework/ErrorTestCase.php deleted file mode 100644 index 245f0336c..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/ErrorTestCase.php +++ /dev/null @@ -1,66 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class ErrorTestCase extends TestCase -{ - /** - * @var ?bool - */ - protected $backupGlobals = false; - - /** - * @var ?bool - */ - protected $backupStaticAttributes = false; - - /** - * @var ?bool - */ - protected $runTestInSeparateProcess = false; - - /** - * @var string - */ - private $message; - - public function __construct(string $message = '') - { - $this->message = $message; - - parent::__construct('Error'); - } - - public function getMessage(): string - { - return $this->message; - } - - /** - * Returns a string representation of the test case. - */ - public function toString(): string - { - return 'Error'; - } - - /** - * @throws Exception - * - * @psalm-return never-return - */ - protected function runTest(): void - { - throw new Error($this->message); - } -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Exception/AssertionFailedError.php b/app/vendor/phpunit/phpunit/src/Framework/Exception/AssertionFailedError.php index 0ba25286f..6bd59c4ae 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Exception/AssertionFailedError.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Exception/AssertionFailedError.php @@ -10,6 +10,8 @@ namespace PHPUnit\Framework; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ class AssertionFailedError extends Exception implements SelfDescribing diff --git a/app/vendor/phpunit/phpunit/src/Framework/Exception/CodeCoverageException.php b/app/vendor/phpunit/phpunit/src/Framework/Exception/CodeCoverageException.php deleted file mode 100644 index 36b072313..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/Exception/CodeCoverageException.php +++ /dev/null @@ -1,17 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -class CodeCoverageException extends Exception -{ -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Exception/CoveredCodeNotExecutedException.php b/app/vendor/phpunit/phpunit/src/Framework/Exception/CoveredCodeNotExecutedException.php deleted file mode 100644 index 78f89bc39..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/Exception/CoveredCodeNotExecutedException.php +++ /dev/null @@ -1,17 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class CoveredCodeNotExecutedException extends RiskyTestError -{ -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Exception/EmptyStringException.php b/app/vendor/phpunit/phpunit/src/Framework/Exception/EmptyStringException.php new file mode 100644 index 000000000..f5980cd71 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Exception/EmptyStringException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class EmptyStringException extends InvalidArgumentException +{ +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Exception/Error.php b/app/vendor/phpunit/phpunit/src/Framework/Exception/Error.php deleted file mode 100644 index d43e42186..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/Exception/Error.php +++ /dev/null @@ -1,24 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class Error extends Exception implements SelfDescribing -{ - /** - * Wrapper for getMessage() which is declared as final. - */ - public function toString(): string - { - return $this->getMessage(); - } -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Exception/ErrorLogNotWritableException.php b/app/vendor/phpunit/phpunit/src/Framework/Exception/ErrorLogNotWritableException.php new file mode 100644 index 000000000..602a3e49d --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Exception/ErrorLogNotWritableException.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 PHPUnit\Framework; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ErrorLogNotWritableException extends Exception +{ + public function __construct() + { + parent::__construct('Could not create writable file for error_log()'); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Exception/Exception.php b/app/vendor/phpunit/phpunit/src/Framework/Exception/Exception.php index 3ed049274..e35b0d505 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Exception/Exception.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Exception/Exception.php @@ -11,7 +11,8 @@ use function array_keys; use function get_object_vars; -use PHPUnit\Util\Filter; +use function is_int; +use function sprintf; use RuntimeException; use Throwable; @@ -35,17 +36,31 @@ * * @see http://fabien.potencier.org/article/9/php-serialization-stack-traces-and-exceptions * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ class Exception extends RuntimeException implements \PHPUnit\Exception { /** - * @var array + * @var list */ - protected $serializableTrace; + protected array $serializableTrace; - public function __construct($message = '', $code = 0, ?Throwable $previous = null) + public function __construct(string $message = '', int|string $code = 0, ?Throwable $previous = null) { + /** + * @see https://github.com/sebastianbergmann/phpunit/issues/5965 + */ + if (!is_int($code)) { + $message .= sprintf( + ' (exception code: %s)', + $code, + ); + + $code = 0; + } + parent::__construct($message, $code, $previous); $this->serializableTrace = $this->getTrace(); @@ -55,17 +70,6 @@ public function __construct($message = '', $code = 0, ?Throwable $previous = nul } } - public function __toString(): string - { - $string = TestFailure::exceptionToString($this); - - if ($trace = Filter::getFilteredStacktrace($this)) { - $string .= "\n" . $trace; - } - - return $string; - } - public function __sleep(): array { return array_keys(get_object_vars($this)); @@ -73,6 +77,8 @@ public function __sleep(): array /** * Returns the serializable trace (without 'args'). + * + * @return list */ public function getSerializableTrace(): array { diff --git a/app/vendor/phpunit/phpunit/src/Framework/Exception/ExpectationFailedException.php b/app/vendor/phpunit/phpunit/src/Framework/Exception/ExpectationFailedException.php index a81d7535f..c46a27beb 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Exception/ExpectationFailedException.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Exception/ExpectationFailedException.php @@ -19,14 +19,11 @@ * SebastianBergmann\Comparator\ComparisonFailure which is used to * generate diff output of the failed expectations. * - * @internal This class is not covered by the backward compatibility promise for PHPUnit + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ final class ExpectationFailedException extends AssertionFailedError { - /** - * @var ComparisonFailure - */ - protected $comparisonFailure; + protected ?ComparisonFailure $comparisonFailure = null; public function __construct(string $message, ?ComparisonFailure $comparisonFailure = null, ?Exception $previous = null) { diff --git a/app/vendor/phpunit/phpunit/src/Framework/Exception/GeneratorNotSupportedException.php b/app/vendor/phpunit/phpunit/src/Framework/Exception/GeneratorNotSupportedException.php new file mode 100644 index 000000000..b3b179531 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Exception/GeneratorNotSupportedException.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function sprintf; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class GeneratorNotSupportedException extends InvalidArgumentException +{ + public static function fromParameterName(string $parameterName): self + { + return new self( + sprintf( + 'Passing an argument of type Generator for the %s parameter is not supported', + $parameterName, + ), + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/IncompleteTest.php b/app/vendor/phpunit/phpunit/src/Framework/Exception/Incomplete/IncompleteTest.php similarity index 80% rename from app/vendor/phpunit/phpunit/src/Framework/IncompleteTest.php rename to app/vendor/phpunit/phpunit/src/Framework/Exception/Incomplete/IncompleteTest.php index b77b1afff..4492ef226 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/IncompleteTest.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Exception/Incomplete/IncompleteTest.php @@ -12,6 +12,8 @@ use Throwable; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ interface IncompleteTest extends Throwable diff --git a/app/vendor/phpunit/phpunit/src/Framework/Exception/IncompleteTestError.php b/app/vendor/phpunit/phpunit/src/Framework/Exception/Incomplete/IncompleteTestError.php similarity index 81% rename from app/vendor/phpunit/phpunit/src/Framework/Exception/IncompleteTestError.php rename to app/vendor/phpunit/phpunit/src/Framework/Exception/Incomplete/IncompleteTestError.php index 65f9c8bc3..a45564da8 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Exception/IncompleteTestError.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Exception/Incomplete/IncompleteTestError.php @@ -10,6 +10,8 @@ namespace PHPUnit\Framework; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class IncompleteTestError extends AssertionFailedError implements IncompleteTest diff --git a/app/vendor/phpunit/phpunit/src/Framework/Exception/InvalidArgumentException.php b/app/vendor/phpunit/phpunit/src/Framework/Exception/InvalidArgumentException.php index 4181b3206..700abf038 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Exception/InvalidArgumentException.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Exception/InvalidArgumentException.php @@ -9,38 +9,11 @@ */ namespace PHPUnit\Framework; -use function debug_backtrace; -use function in_array; -use function lcfirst; -use function sprintf; - /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ -final class InvalidArgumentException extends Exception +abstract class InvalidArgumentException extends Exception { - public static function create(int $argument, string $type): self - { - $stack = debug_backtrace(); - $function = $stack[1]['function']; - - if (isset($stack[1]['class'])) { - $function = sprintf('%s::%s', $stack[1]['class'], $stack[1]['function']); - } - - return new self( - sprintf( - 'Argument #%d of %s() must be %s %s', - $argument, - $function, - in_array(lcfirst($type)[0], ['a', 'e', 'i', 'o', 'u'], true) ? 'an' : 'a', - $type, - ), - ); - } - - private function __construct(string $message = '', int $code = 0, ?\Exception $previous = null) - { - parent::__construct($message, $code, $previous); - } } diff --git a/app/vendor/phpunit/phpunit/src/Framework/Exception/InvalidCoversTargetException.php b/app/vendor/phpunit/phpunit/src/Framework/Exception/InvalidCoversTargetException.php deleted file mode 100644 index ebf2994a9..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/Exception/InvalidCoversTargetException.php +++ /dev/null @@ -1,17 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class InvalidCoversTargetException extends CodeCoverageException -{ -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Exception/InvalidDataProviderException.php b/app/vendor/phpunit/phpunit/src/Framework/Exception/InvalidDataProviderException.php index 7e2ef24c6..b7aa4ae88 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Exception/InvalidDataProviderException.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Exception/InvalidDataProviderException.php @@ -9,9 +9,31 @@ */ namespace PHPUnit\Framework; +use Throwable; + /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class InvalidDataProviderException extends Exception { + private ?string $providerLabel = null; + + public static function forException(Throwable $e, string $providerLabel): self + { + $exception = new self( + $e->getMessage(), + $e->getCode(), + $e, + ); + $exception->providerLabel = $providerLabel; + + return $exception; + } + + public function getProviderLabel(): ?string + { + return $this->providerLabel; + } } diff --git a/app/vendor/phpunit/phpunit/src/Framework/Exception/InvalidDependencyException.php b/app/vendor/phpunit/phpunit/src/Framework/Exception/InvalidDependencyException.php new file mode 100644 index 000000000..8a636fd48 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Exception/InvalidDependencyException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class InvalidDependencyException extends AssertionFailedError implements SkippedTest +{ +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Exception/MissingCoversAnnotationException.php b/app/vendor/phpunit/phpunit/src/Framework/Exception/MissingCoversAnnotationException.php deleted file mode 100644 index 567a6c4c5..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/Exception/MissingCoversAnnotationException.php +++ /dev/null @@ -1,17 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class MissingCoversAnnotationException extends RiskyTestError -{ -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Exception/NoChildTestSuiteException.php b/app/vendor/phpunit/phpunit/src/Framework/Exception/NoChildTestSuiteException.php index 7ef4153b0..e59df81df 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Exception/NoChildTestSuiteException.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Exception/NoChildTestSuiteException.php @@ -10,6 +10,8 @@ namespace PHPUnit\Framework; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class NoChildTestSuiteException extends Exception diff --git a/app/vendor/phpunit/phpunit/src/Framework/Exception/ActualValueIsNotAnObjectException.php b/app/vendor/phpunit/phpunit/src/Framework/Exception/ObjectEquals/ActualValueIsNotAnObjectException.php similarity index 79% rename from app/vendor/phpunit/phpunit/src/Framework/Exception/ActualValueIsNotAnObjectException.php rename to app/vendor/phpunit/phpunit/src/Framework/Exception/ObjectEquals/ActualValueIsNotAnObjectException.php index 4364788cf..258b940ae 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Exception/ActualValueIsNotAnObjectException.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Exception/ObjectEquals/ActualValueIsNotAnObjectException.php @@ -9,9 +9,9 @@ */ namespace PHPUnit\Framework; -use const PHP_EOL; - /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class ActualValueIsNotAnObjectException extends Exception @@ -20,13 +20,6 @@ public function __construct() { parent::__construct( 'Actual value is not an object', - 0, - null, ); } - - public function __toString(): string - { - return $this->getMessage() . PHP_EOL; - } } diff --git a/app/vendor/phpunit/phpunit/src/Framework/Exception/ComparisonMethodDoesNotAcceptParameterTypeException.php b/app/vendor/phpunit/phpunit/src/Framework/Exception/ObjectEquals/ComparisonMethodDoesNotAcceptParameterTypeException.php similarity index 84% rename from app/vendor/phpunit/phpunit/src/Framework/Exception/ComparisonMethodDoesNotAcceptParameterTypeException.php rename to app/vendor/phpunit/phpunit/src/Framework/Exception/ObjectEquals/ComparisonMethodDoesNotAcceptParameterTypeException.php index 0c2c1afe6..74d00a17d 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Exception/ComparisonMethodDoesNotAcceptParameterTypeException.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Exception/ObjectEquals/ComparisonMethodDoesNotAcceptParameterTypeException.php @@ -9,10 +9,11 @@ */ namespace PHPUnit\Framework; -use const PHP_EOL; use function sprintf; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class ComparisonMethodDoesNotAcceptParameterTypeException extends Exception @@ -26,13 +27,6 @@ public function __construct(string $className, string $methodName, string $type) $className, $methodName, ), - 0, - null, ); } - - public function __toString(): string - { - return $this->getMessage() . PHP_EOL; - } } diff --git a/app/vendor/phpunit/phpunit/src/Framework/Exception/ComparisonMethodDoesNotDeclareBoolReturnTypeException.php b/app/vendor/phpunit/phpunit/src/Framework/Exception/ObjectEquals/ComparisonMethodDoesNotDeclareBoolReturnTypeException.php similarity index 83% rename from app/vendor/phpunit/phpunit/src/Framework/Exception/ComparisonMethodDoesNotDeclareBoolReturnTypeException.php rename to app/vendor/phpunit/phpunit/src/Framework/Exception/ObjectEquals/ComparisonMethodDoesNotDeclareBoolReturnTypeException.php index 4eb9a2fde..62dc7e8cb 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Exception/ComparisonMethodDoesNotDeclareBoolReturnTypeException.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Exception/ObjectEquals/ComparisonMethodDoesNotDeclareBoolReturnTypeException.php @@ -9,10 +9,11 @@ */ namespace PHPUnit\Framework; -use const PHP_EOL; use function sprintf; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class ComparisonMethodDoesNotDeclareBoolReturnTypeException extends Exception @@ -25,13 +26,6 @@ public function __construct(string $className, string $methodName) $className, $methodName, ), - 0, - null, ); } - - public function __toString(): string - { - return $this->getMessage() . PHP_EOL; - } } diff --git a/app/vendor/phpunit/phpunit/src/Framework/Exception/ComparisonMethodDoesNotDeclareExactlyOneParameterException.php b/app/vendor/phpunit/phpunit/src/Framework/Exception/ObjectEquals/ComparisonMethodDoesNotDeclareExactlyOneParameterException.php similarity index 84% rename from app/vendor/phpunit/phpunit/src/Framework/Exception/ComparisonMethodDoesNotDeclareExactlyOneParameterException.php rename to app/vendor/phpunit/phpunit/src/Framework/Exception/ObjectEquals/ComparisonMethodDoesNotDeclareExactlyOneParameterException.php index e8cd9787b..d57607447 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Exception/ComparisonMethodDoesNotDeclareExactlyOneParameterException.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Exception/ObjectEquals/ComparisonMethodDoesNotDeclareExactlyOneParameterException.php @@ -9,10 +9,11 @@ */ namespace PHPUnit\Framework; -use const PHP_EOL; use function sprintf; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class ComparisonMethodDoesNotDeclareExactlyOneParameterException extends Exception @@ -25,13 +26,6 @@ public function __construct(string $className, string $methodName) $className, $methodName, ), - 0, - null, ); } - - public function __toString(): string - { - return $this->getMessage() . PHP_EOL; - } } diff --git a/app/vendor/phpunit/phpunit/src/Framework/Exception/ComparisonMethodDoesNotDeclareParameterTypeException.php b/app/vendor/phpunit/phpunit/src/Framework/Exception/ObjectEquals/ComparisonMethodDoesNotDeclareParameterTypeException.php similarity index 83% rename from app/vendor/phpunit/phpunit/src/Framework/Exception/ComparisonMethodDoesNotDeclareParameterTypeException.php rename to app/vendor/phpunit/phpunit/src/Framework/Exception/ObjectEquals/ComparisonMethodDoesNotDeclareParameterTypeException.php index 68616ba18..657186829 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Exception/ComparisonMethodDoesNotDeclareParameterTypeException.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Exception/ObjectEquals/ComparisonMethodDoesNotDeclareParameterTypeException.php @@ -9,10 +9,11 @@ */ namespace PHPUnit\Framework; -use const PHP_EOL; use function sprintf; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class ComparisonMethodDoesNotDeclareParameterTypeException extends Exception @@ -25,13 +26,6 @@ public function __construct(string $className, string $methodName) $className, $methodName, ), - 0, - null, ); } - - public function __toString(): string - { - return $this->getMessage() . PHP_EOL; - } } diff --git a/app/vendor/phpunit/phpunit/src/Framework/Exception/ComparisonMethodDoesNotExistException.php b/app/vendor/phpunit/phpunit/src/Framework/Exception/ObjectEquals/ComparisonMethodDoesNotExistException.php similarity index 83% rename from app/vendor/phpunit/phpunit/src/Framework/Exception/ComparisonMethodDoesNotExistException.php rename to app/vendor/phpunit/phpunit/src/Framework/Exception/ObjectEquals/ComparisonMethodDoesNotExistException.php index 0f1adcbcc..94590b510 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Exception/ComparisonMethodDoesNotExistException.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Exception/ObjectEquals/ComparisonMethodDoesNotExistException.php @@ -9,10 +9,11 @@ */ namespace PHPUnit\Framework; -use const PHP_EOL; use function sprintf; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class ComparisonMethodDoesNotExistException extends Exception @@ -25,13 +26,6 @@ public function __construct(string $className, string $methodName) $className, $methodName, ), - 0, - null, ); } - - public function __toString(): string - { - return $this->getMessage() . PHP_EOL; - } } diff --git a/app/vendor/phpunit/phpunit/src/Framework/Exception/OutputError.php b/app/vendor/phpunit/phpunit/src/Framework/Exception/OutputError.php deleted file mode 100644 index 1c8b37e56..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/Exception/OutputError.php +++ /dev/null @@ -1,17 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class OutputError extends AssertionFailedError -{ -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Exception/PHPTAssertionFailedError.php b/app/vendor/phpunit/phpunit/src/Framework/Exception/PHPTAssertionFailedError.php deleted file mode 100644 index 17126139f..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/Exception/PHPTAssertionFailedError.php +++ /dev/null @@ -1,32 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class PHPTAssertionFailedError extends SyntheticError -{ - /** - * @var string - */ - private $diff; - - public function __construct(string $message, int $code, string $file, int $line, array $trace, string $diff) - { - parent::__construct($message, $code, $file, $line, $trace); - $this->diff = $diff; - } - - public function getDiff(): string - { - return $this->diff; - } -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Exception/PhptAssertionFailedError.php b/app/vendor/phpunit/phpunit/src/Framework/Exception/PhptAssertionFailedError.php new file mode 100644 index 000000000..3742abbbc --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Exception/PhptAssertionFailedError.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 PHPUnit\Framework; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class PhptAssertionFailedError extends AssertionFailedError +{ + private readonly string $syntheticFile; + private readonly int $syntheticLine; + + /** + * @var list + */ + private readonly array $syntheticTrace; + private readonly string $diff; + + /** + * @param list $trace + */ + public function __construct(string $message, int $code, string $file, int $line, array $trace, string $diff) + { + parent::__construct($message, $code); + + $this->syntheticFile = $file; + $this->syntheticLine = $line; + $this->syntheticTrace = $trace; + $this->diff = $diff; + } + + public function syntheticFile(): string + { + return $this->syntheticFile; + } + + public function syntheticLine(): int + { + return $this->syntheticLine; + } + + /** + * @return list + */ + public function syntheticTrace(): array + { + return $this->syntheticTrace; + } + + public function diff(): string + { + return $this->diff; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Exception/ProcessIsolationException.php b/app/vendor/phpunit/phpunit/src/Framework/Exception/ProcessIsolationException.php new file mode 100644 index 000000000..e59c9c603 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Exception/ProcessIsolationException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ProcessIsolationException extends Exception +{ +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Exception/RiskyTestError.php b/app/vendor/phpunit/phpunit/src/Framework/Exception/RiskyTestError.php deleted file mode 100644 index a66552c0d..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/Exception/RiskyTestError.php +++ /dev/null @@ -1,17 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -class RiskyTestError extends AssertionFailedError -{ -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/SkippedTest.php b/app/vendor/phpunit/phpunit/src/Framework/Exception/Skipped/SkippedTest.php similarity index 80% rename from app/vendor/phpunit/phpunit/src/Framework/SkippedTest.php rename to app/vendor/phpunit/phpunit/src/Framework/Exception/Skipped/SkippedTest.php index a12aa402d..ab2f67496 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/SkippedTest.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Exception/Skipped/SkippedTest.php @@ -12,6 +12,8 @@ use Throwable; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ interface SkippedTest extends Throwable diff --git a/app/vendor/phpunit/phpunit/src/Framework/Exception/SkippedTestSuiteError.php b/app/vendor/phpunit/phpunit/src/Framework/Exception/Skipped/SkippedTestSuiteError.php similarity index 81% rename from app/vendor/phpunit/phpunit/src/Framework/Exception/SkippedTestSuiteError.php rename to app/vendor/phpunit/phpunit/src/Framework/Exception/Skipped/SkippedTestSuiteError.php index 5448508a1..d3a4788b0 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Exception/SkippedTestSuiteError.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Exception/Skipped/SkippedTestSuiteError.php @@ -10,6 +10,8 @@ namespace PHPUnit\Framework; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class SkippedTestSuiteError extends AssertionFailedError implements SkippedTest diff --git a/app/vendor/phpunit/phpunit/src/Framework/Exception/Skipped/SkippedWithMessageException.php b/app/vendor/phpunit/phpunit/src/Framework/Exception/Skipped/SkippedWithMessageException.php new file mode 100644 index 000000000..d09a760a3 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Exception/Skipped/SkippedWithMessageException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class SkippedWithMessageException extends AssertionFailedError implements SkippedTest +{ +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Exception/SkippedTestError.php b/app/vendor/phpunit/phpunit/src/Framework/Exception/SkippedTestError.php deleted file mode 100644 index 7d553dcf3..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/Exception/SkippedTestError.php +++ /dev/null @@ -1,17 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class SkippedTestError extends AssertionFailedError implements SkippedTest -{ -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Exception/SyntheticError.php b/app/vendor/phpunit/phpunit/src/Framework/Exception/SyntheticError.php deleted file mode 100644 index c3124ba0c..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/Exception/SyntheticError.php +++ /dev/null @@ -1,61 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -class SyntheticError extends AssertionFailedError -{ - /** - * The synthetic file. - * - * @var string - */ - protected $syntheticFile = ''; - - /** - * The synthetic line number. - * - * @var int - */ - protected $syntheticLine = 0; - - /** - * The synthetic trace. - * - * @var array - */ - protected $syntheticTrace = []; - - public function __construct(string $message, int $code, string $file, int $line, array $trace) - { - parent::__construct($message, $code); - - $this->syntheticFile = $file; - $this->syntheticLine = $line; - $this->syntheticTrace = $trace; - } - - public function getSyntheticFile(): string - { - return $this->syntheticFile; - } - - public function getSyntheticLine(): int - { - return $this->syntheticLine; - } - - public function getSyntheticTrace(): array - { - return $this->syntheticTrace; - } -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Exception/SyntheticSkippedError.php b/app/vendor/phpunit/phpunit/src/Framework/Exception/SyntheticSkippedError.php deleted file mode 100644 index f6e155d7b..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/Exception/SyntheticSkippedError.php +++ /dev/null @@ -1,17 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class SyntheticSkippedError extends SyntheticError implements SkippedTest -{ -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Exception/UnintentionallyCoveredCodeError.php b/app/vendor/phpunit/phpunit/src/Framework/Exception/UnintentionallyCoveredCodeError.php deleted file mode 100644 index fcd1d8249..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/Exception/UnintentionallyCoveredCodeError.php +++ /dev/null @@ -1,17 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class UnintentionallyCoveredCodeError extends RiskyTestError -{ -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Exception/UnknownClassOrInterfaceException.php b/app/vendor/phpunit/phpunit/src/Framework/Exception/UnknownClassOrInterfaceException.php new file mode 100644 index 000000000..6a10f97fb --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Exception/UnknownClassOrInterfaceException.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function sprintf; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class UnknownClassOrInterfaceException extends InvalidArgumentException +{ + public function __construct(string $name) + { + parent::__construct( + sprintf( + 'Class or interface "%s" does not exist', + $name, + ), + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Exception/UnknownNativeTypeException.php b/app/vendor/phpunit/phpunit/src/Framework/Exception/UnknownNativeTypeException.php new file mode 100644 index 000000000..09da46093 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/Exception/UnknownNativeTypeException.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function sprintf; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class UnknownNativeTypeException extends InvalidArgumentException +{ + public function __construct(string $type) + { + parent::__construct( + sprintf( + 'Native type "%s" is not known', + $type, + ), + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Exception/Warning.php b/app/vendor/phpunit/phpunit/src/Framework/Exception/Warning.php deleted file mode 100644 index 35e94493c..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/Exception/Warning.php +++ /dev/null @@ -1,24 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class Warning extends Exception implements SelfDescribing -{ - /** - * Wrapper for getMessage() which is declared as final. - */ - public function toString(): string - { - return $this->getMessage(); - } -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/ExceptionWrapper.php b/app/vendor/phpunit/phpunit/src/Framework/ExceptionWrapper.php deleted file mode 100644 index d30e90b2f..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/ExceptionWrapper.php +++ /dev/null @@ -1,138 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -use const PHP_VERSION_ID; -use function array_keys; -use function get_class; -use function spl_object_hash; -use PHPUnit\Util\Filter; -use Throwable; -use WeakReference; - -/** - * Wraps Exceptions thrown by code under test. - * - * Re-instantiates Exceptions thrown by user-space code to retain their original - * class names, properties, and stack traces (but without arguments). - * - * Unlike PHPUnit\Framework\Exception, the complete stack of previous Exceptions - * is processed. - * - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class ExceptionWrapper extends Exception -{ - /** - * @var string - */ - protected $className; - - /** - * @var null|ExceptionWrapper - */ - protected $previous; - - /** - * @var null|WeakReference - */ - private $originalException; - - public function __construct(Throwable $t) - { - // PDOException::getCode() is a string. - // @see https://php.net/manual/en/class.pdoexception.php#95812 - parent::__construct($t->getMessage(), (int) $t->getCode()); - - $this->setOriginalException($t); - } - - public function __toString(): string - { - $string = TestFailure::exceptionToString($this); - - if ($trace = Filter::getFilteredStacktrace($this)) { - $string .= "\n" . $trace; - } - - if ($this->previous) { - $string .= "\nCaused by\n" . $this->previous; - } - - return $string; - } - - public function getClassName(): string - { - return $this->className; - } - - public function getPreviousWrapped(): ?self - { - return $this->previous; - } - - public function setClassName(string $className): void - { - $this->className = $className; - } - - public function setOriginalException(Throwable $t): void - { - $this->originalException($t); - - $this->className = get_class($t); - $this->file = $t->getFile(); - $this->line = $t->getLine(); - - $this->serializableTrace = $t->getTrace(); - - foreach (array_keys($this->serializableTrace) as $key) { - unset($this->serializableTrace[$key]['args']); - } - - if ($t->getPrevious()) { - $this->previous = new self($t->getPrevious()); - } - } - - public function getOriginalException(): ?Throwable - { - return $this->originalException(); - } - - /** - * Method to contain static originalException to exclude it from stacktrace to prevent the stacktrace contents, - * which can be quite big, from being garbage-collected, thus blocking memory until shutdown. - * - * Approach works both for var_dump() and var_export() and print_r(). - */ - private function originalException(?Throwable $exceptionToStore = null): ?Throwable - { - // drop once PHP 7.3 support is removed - if (PHP_VERSION_ID < 70400) { - static $originalExceptions; - - $instanceId = spl_object_hash($this); - - if ($exceptionToStore) { - $originalExceptions[$instanceId] = $exceptionToStore; - } - - return $originalExceptions[$instanceId] ?? null; - } - - if ($exceptionToStore) { - $this->originalException = WeakReference::create($exceptionToStore); - } - - return $this->originalException !== null ? $this->originalException->get() : null; - } -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/ExecutionOrderDependency.php b/app/vendor/phpunit/phpunit/src/Framework/ExecutionOrderDependency.php index 89ecc5e2e..896d2560a 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/ExecutionOrderDependency.php +++ b/app/vendor/phpunit/phpunit/src/Framework/ExecutionOrderDependency.php @@ -12,98 +12,93 @@ use function array_filter; use function array_map; use function array_values; +use function assert; use function count; use function explode; use function in_array; -use function strpos; -use function trim; +use function str_contains; +use PHPUnit\Metadata\DependsOnClass; +use PHPUnit\Metadata\DependsOnMethod; +use Stringable; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ -final class ExecutionOrderDependency +final class ExecutionOrderDependency implements Stringable { - /** - * @var string - */ - private $className = ''; - - /** - * @var string - */ - private $methodName = ''; + private string $className = ''; + private string $methodName = ''; + private readonly bool $shallowClone; + private readonly bool $deepClone; - /** - * @var bool - */ - private $useShallowClone = false; - - /** - * @var bool - */ - private $useDeepClone = false; - - public static function createFromDependsAnnotation(string $className, string $annotation): self + public static function invalid(): self { - // Split clone option and target - $parts = explode(' ', trim($annotation), 2); - - if (count($parts) === 1) { - $cloneOption = ''; - $target = $parts[0]; - } else { - $cloneOption = $parts[0]; - $target = $parts[1]; - } + return new self( + '', + '', + false, + false, + ); + } - // Prefix provided class for targets assumed to be in scope - if ($target !== '' && strpos($target, '::') === false) { - $target = $className . '::' . $target; - } + public static function forClass(DependsOnClass $metadata): self + { + return new self( + $metadata->className(), + 'class', + $metadata->deepClone(), + $metadata->shallowClone(), + ); + } - return new self($target, null, $cloneOption); + public static function forMethod(DependsOnMethod $metadata): self + { + return new self( + $metadata->className(), + $metadata->methodName(), + $metadata->deepClone(), + $metadata->shallowClone(), + ); } /** - * @psalm-param list $dependencies + * @param list $dependencies * - * @psalm-return list + * @return list */ public static function filterInvalid(array $dependencies): array { return array_values( array_filter( $dependencies, - static function (self $d) - { - return $d->isValid(); - }, + static fn (self $d) => $d->isValid(), ), ); } /** - * @psalm-param list $existing - * @psalm-param list $additional + * @param list $existing + * @param list $additional * - * @psalm-return list + * @return list */ public static function mergeUnique(array $existing, array $additional): array { $existingTargets = array_map( - static function ($dependency) - { - return $dependency->getTarget(); - }, + static fn (ExecutionOrderDependency $dependency) => $dependency->getTarget(), $existing, ); foreach ($additional as $dependency) { - if (in_array($dependency->getTarget(), $existingTargets, true)) { + $additionalTarget = $dependency->getTarget(); + + if (in_array($additionalTarget, $existingTargets, true)) { continue; } - $existingTargets[] = $dependency->getTarget(); + $existingTargets[] = $additionalTarget; $existing[] = $dependency; } @@ -111,10 +106,10 @@ static function ($dependency) } /** - * @psalm-param list $left - * @psalm-param list $right + * @param list $left + * @param list $right * - * @psalm-return list + * @return list */ public static function diff(array $left, array $right): array { @@ -128,10 +123,7 @@ public static function diff(array $left, array $right): array $diff = []; $rightTargets = array_map( - static function ($dependency) - { - return $dependency->getTarget(); - }, + static fn (ExecutionOrderDependency $dependency) => $dependency->getTarget(), $right, ); @@ -146,23 +138,21 @@ static function ($dependency) return $diff; } - public function __construct(string $classOrCallableName, ?string $methodName = null, ?string $option = null) + public function __construct(string $classOrCallableName, ?string $methodName = null, bool $deepClone = false, bool $shallowClone = false) { + $this->deepClone = $deepClone; + $this->shallowClone = $shallowClone; + if ($classOrCallableName === '') { return; } - if (strpos($classOrCallableName, '::') !== false) { + if (str_contains($classOrCallableName, '::')) { + assert(count(explode('::', $classOrCallableName)) === 2); [$this->className, $this->methodName] = explode('::', $classOrCallableName); } else { $this->className = $classOrCallableName; - $this->methodName = !empty($methodName) ? $methodName : 'class'; - } - - if ($option === 'clone') { - $this->useDeepClone = true; - } elseif ($option === 'shallowClone') { - $this->useShallowClone = true; + $this->methodName = $methodName !== null && $methodName !== '' ? $methodName : 'class'; } } @@ -171,20 +161,23 @@ public function __toString(): string return $this->getTarget(); } + /** + * @phpstan-assert-if-true non-empty-string $this->getTarget() + */ public function isValid(): bool { // Invalid dependencies can be declared and are skipped by the runner return $this->className !== '' && $this->methodName !== ''; } - public function useShallowClone(): bool + public function shallowClone(): bool { - return $this->useShallowClone; + return $this->shallowClone; } - public function useDeepClone(): bool + public function deepClone(): bool { - return $this->useDeepClone; + return $this->deepClone; } public function targetIsClass(): bool diff --git a/app/vendor/phpunit/phpunit/src/Framework/IncompleteTestCase.php b/app/vendor/phpunit/phpunit/src/Framework/IncompleteTestCase.php deleted file mode 100644 index 8da567942..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/IncompleteTestCase.php +++ /dev/null @@ -1,68 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -use SebastianBergmann\RecursionContext\InvalidArgumentException; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class IncompleteTestCase extends TestCase -{ - /** - * @var ?bool - */ - protected $backupGlobals = false; - - /** - * @var ?bool - */ - protected $backupStaticAttributes = false; - - /** - * @var ?bool - */ - protected $runTestInSeparateProcess = false; - - /** - * @var string - */ - private $message; - - public function __construct(string $className, string $methodName, string $message = '') - { - parent::__construct($className . '::' . $methodName); - - $this->message = $message; - } - - public function getMessage(): string - { - return $this->message; - } - - /** - * Returns a string representation of the test case. - * - * @throws InvalidArgumentException - */ - public function toString(): string - { - return $this->getName(); - } - - /** - * @throws Exception - */ - protected function runTest(): void - { - $this->markTestIncomplete($this->message); - } -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/InvalidParameterGroupException.php b/app/vendor/phpunit/phpunit/src/Framework/InvalidParameterGroupException.php deleted file mode 100644 index feb9cc989..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/InvalidParameterGroupException.php +++ /dev/null @@ -1,17 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class InvalidParameterGroupException extends Exception -{ -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Api/Api.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Api/Api.php deleted file mode 100644 index 56e6b69bb..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Api/Api.php +++ /dev/null @@ -1,97 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -use PHPUnit\Framework\MockObject\Builder\InvocationMocker as InvocationMockerBuilder; -use PHPUnit\Framework\MockObject\Rule\InvocationOrder; - -/** - * @internal This trait is not covered by the backward compatibility promise for PHPUnit - */ -trait Api -{ - /** - * @var ConfigurableMethod[] - */ - private static $__phpunit_configurableMethods; - - /** - * @var object - */ - private $__phpunit_originalObject; - - /** - * @var bool - */ - private $__phpunit_returnValueGeneration = true; - - /** - * @var InvocationHandler - */ - private $__phpunit_invocationMocker; - - /** @noinspection MagicMethodsValidityInspection */ - public static function __phpunit_initConfigurableMethods(ConfigurableMethod ...$configurableMethods): void - { - if (isset(static::$__phpunit_configurableMethods)) { - throw new ConfigurableMethodsAlreadyInitializedException( - 'Configurable methods is already initialized and can not be reinitialized', - ); - } - - static::$__phpunit_configurableMethods = $configurableMethods; - } - - /** @noinspection MagicMethodsValidityInspection */ - public function __phpunit_setOriginalObject($originalObject): void - { - $this->__phpunit_originalObject = $originalObject; - } - - /** @noinspection MagicMethodsValidityInspection */ - public function __phpunit_setReturnValueGeneration(bool $returnValueGeneration): void - { - $this->__phpunit_returnValueGeneration = $returnValueGeneration; - } - - /** @noinspection MagicMethodsValidityInspection */ - public function __phpunit_getInvocationHandler(): InvocationHandler - { - if ($this->__phpunit_invocationMocker === null) { - $this->__phpunit_invocationMocker = new InvocationHandler( - static::$__phpunit_configurableMethods, - $this->__phpunit_returnValueGeneration, - ); - } - - return $this->__phpunit_invocationMocker; - } - - /** @noinspection MagicMethodsValidityInspection */ - public function __phpunit_hasMatchers(): bool - { - return $this->__phpunit_getInvocationHandler()->hasMatchers(); - } - - /** @noinspection MagicMethodsValidityInspection */ - public function __phpunit_verify(bool $unsetInvocationMocker = true): void - { - $this->__phpunit_getInvocationHandler()->verify(); - - if ($unsetInvocationMocker) { - $this->__phpunit_invocationMocker = null; - } - } - - public function expects(InvocationOrder $matcher): InvocationMockerBuilder - { - return $this->__phpunit_getInvocationHandler()->expects($matcher); - } -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Api/Method.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Api/Method.php deleted file mode 100644 index f8be3808c..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Api/Method.php +++ /dev/null @@ -1,30 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -use function call_user_func_array; -use function func_get_args; -use PHPUnit\Framework\MockObject\Rule\AnyInvokedCount; - -/** - * @internal This trait is not covered by the backward compatibility promise for PHPUnit - */ -trait Method -{ - public function method() - { - $expects = $this->expects(new AnyInvokedCount); - - return call_user_func_array( - [$expects, 'method'], - func_get_args(), - ); - } -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Builder/Identity.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Builder/Identity.php deleted file mode 100644 index a68bfadf9..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Builder/Identity.php +++ /dev/null @@ -1,25 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Builder; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -interface Identity -{ - /** - * Sets the identification of the expectation to $id. - * - * @note The identifier is unique per mock object. - * - * @param string $id unique identification of expectation - */ - public function id($id); -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Builder/InvocationMocker.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Builder/InvocationMocker.php deleted file mode 100644 index b23e5e389..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Builder/InvocationMocker.php +++ /dev/null @@ -1,309 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Builder; - -use function array_map; -use function array_merge; -use function count; -use function in_array; -use function is_string; -use function strtolower; -use PHPUnit\Framework\Constraint\Constraint; -use PHPUnit\Framework\InvalidArgumentException; -use PHPUnit\Framework\MockObject\ConfigurableMethod; -use PHPUnit\Framework\MockObject\IncompatibleReturnValueException; -use PHPUnit\Framework\MockObject\InvocationHandler; -use PHPUnit\Framework\MockObject\Matcher; -use PHPUnit\Framework\MockObject\MatcherAlreadyRegisteredException; -use PHPUnit\Framework\MockObject\MethodCannotBeConfiguredException; -use PHPUnit\Framework\MockObject\MethodNameAlreadyConfiguredException; -use PHPUnit\Framework\MockObject\MethodNameNotConfiguredException; -use PHPUnit\Framework\MockObject\MethodParametersAlreadyConfiguredException; -use PHPUnit\Framework\MockObject\Rule; -use PHPUnit\Framework\MockObject\Stub\ConsecutiveCalls; -use PHPUnit\Framework\MockObject\Stub\Exception; -use PHPUnit\Framework\MockObject\Stub\ReturnArgument; -use PHPUnit\Framework\MockObject\Stub\ReturnCallback; -use PHPUnit\Framework\MockObject\Stub\ReturnReference; -use PHPUnit\Framework\MockObject\Stub\ReturnSelf; -use PHPUnit\Framework\MockObject\Stub\ReturnStub; -use PHPUnit\Framework\MockObject\Stub\ReturnValueMap; -use PHPUnit\Framework\MockObject\Stub\Stub; -use Throwable; - -/** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - */ -final class InvocationMocker implements InvocationStubber, MethodNameMatch -{ - /** - * @var InvocationHandler - */ - private $invocationHandler; - - /** - * @var Matcher - */ - private $matcher; - - /** - * @var ConfigurableMethod[] - */ - private $configurableMethods; - - public function __construct(InvocationHandler $handler, Matcher $matcher, ConfigurableMethod ...$configurableMethods) - { - $this->invocationHandler = $handler; - $this->matcher = $matcher; - $this->configurableMethods = $configurableMethods; - } - - /** - * @throws MatcherAlreadyRegisteredException - * - * @return $this - */ - public function id($id): self - { - $this->invocationHandler->registerMatcher($id, $this->matcher); - - return $this; - } - - /** - * @return $this - */ - public function will(Stub $stub): Identity - { - $this->matcher->setStub($stub); - - return $this; - } - - /** - * @param mixed $value - * @param mixed[] $nextValues - * - * @throws IncompatibleReturnValueException - */ - public function willReturn($value, ...$nextValues): self - { - if (count($nextValues) === 0) { - $this->ensureTypeOfReturnValues([$value]); - - $stub = $value instanceof Stub ? $value : new ReturnStub($value); - } else { - $values = array_merge([$value], $nextValues); - - $this->ensureTypeOfReturnValues($values); - - $stub = new ConsecutiveCalls($values); - } - - return $this->will($stub); - } - - public function willReturnReference(&$reference): self - { - $stub = new ReturnReference($reference); - - return $this->will($stub); - } - - public function willReturnMap(array $valueMap): self - { - $stub = new ReturnValueMap($valueMap); - - return $this->will($stub); - } - - public function willReturnArgument($argumentIndex): self - { - $stub = new ReturnArgument($argumentIndex); - - return $this->will($stub); - } - - public function willReturnCallback($callback): self - { - $stub = new ReturnCallback($callback); - - return $this->will($stub); - } - - public function willReturnSelf(): self - { - $stub = new ReturnSelf; - - return $this->will($stub); - } - - public function willReturnOnConsecutiveCalls(...$values): self - { - $stub = new ConsecutiveCalls($values); - - return $this->will($stub); - } - - public function willThrowException(Throwable $exception): self - { - $stub = new Exception($exception); - - return $this->will($stub); - } - - /** - * @return $this - */ - public function after($id): self - { - $this->matcher->setAfterMatchBuilderId($id); - - return $this; - } - - /** - * @param mixed[] $arguments - * - * @throws \PHPUnit\Framework\Exception - * @throws MethodNameNotConfiguredException - * @throws MethodParametersAlreadyConfiguredException - * - * @return $this - */ - public function with(...$arguments): self - { - $this->ensureParametersCanBeConfigured(); - - $this->matcher->setParametersRule(new Rule\Parameters($arguments)); - - return $this; - } - - /** - * @param array ...$arguments - * - * @throws \PHPUnit\Framework\Exception - * @throws MethodNameNotConfiguredException - * @throws MethodParametersAlreadyConfiguredException - * - * @return $this - * - * @deprecated - */ - public function withConsecutive(...$arguments): self - { - $this->ensureParametersCanBeConfigured(); - - $this->matcher->setParametersRule(new Rule\ConsecutiveParameters($arguments)); - - return $this; - } - - /** - * @throws MethodNameNotConfiguredException - * @throws MethodParametersAlreadyConfiguredException - * - * @return $this - */ - public function withAnyParameters(): self - { - $this->ensureParametersCanBeConfigured(); - - $this->matcher->setParametersRule(new Rule\AnyParameters); - - return $this; - } - - /** - * @param Constraint|string $constraint - * - * @throws InvalidArgumentException - * @throws MethodCannotBeConfiguredException - * @throws MethodNameAlreadyConfiguredException - * - * @return $this - */ - public function method($constraint): self - { - if ($this->matcher->hasMethodNameRule()) { - throw new MethodNameAlreadyConfiguredException; - } - - $configurableMethodNames = array_map( - static function (ConfigurableMethod $configurable) - { - return strtolower($configurable->getName()); - }, - $this->configurableMethods, - ); - - if (is_string($constraint) && !in_array(strtolower($constraint), $configurableMethodNames, true)) { - throw new MethodCannotBeConfiguredException($constraint); - } - - $this->matcher->setMethodNameRule(new Rule\MethodName($constraint)); - - return $this; - } - - /** - * @throws MethodNameNotConfiguredException - * @throws MethodParametersAlreadyConfiguredException - */ - private function ensureParametersCanBeConfigured(): void - { - if (!$this->matcher->hasMethodNameRule()) { - throw new MethodNameNotConfiguredException; - } - - if ($this->matcher->hasParametersRule()) { - throw new MethodParametersAlreadyConfiguredException; - } - } - - private function getConfiguredMethod(): ?ConfigurableMethod - { - $configuredMethod = null; - - foreach ($this->configurableMethods as $configurableMethod) { - if ($this->matcher->getMethodNameRule()->matchesName($configurableMethod->getName())) { - if ($configuredMethod !== null) { - return null; - } - - $configuredMethod = $configurableMethod; - } - } - - return $configuredMethod; - } - - /** - * @throws IncompatibleReturnValueException - */ - private function ensureTypeOfReturnValues(array $values): void - { - $configuredMethod = $this->getConfiguredMethod(); - - if ($configuredMethod === null) { - return; - } - - foreach ($values as $value) { - if (!$configuredMethod->mayReturn($value)) { - throw new IncompatibleReturnValueException( - $configuredMethod, - $value, - ); - } - } - } -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Builder/InvocationStubber.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Builder/InvocationStubber.php deleted file mode 100644 index 1756cfc0a..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Builder/InvocationStubber.php +++ /dev/null @@ -1,65 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Builder; - -use PHPUnit\Framework\MockObject\Stub\Stub; -use Throwable; - -/** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - */ -interface InvocationStubber -{ - public function will(Stub $stub): Identity; - - /** @return self */ - public function willReturn($value, ...$nextValues)/* : self */; - - /** - * @param mixed $reference - * - * @return self - */ - public function willReturnReference(&$reference)/* : self */; - - /** - * @param array> $valueMap - * - * @return self - */ - public function willReturnMap(array $valueMap)/* : self */; - - /** - * @param int $argumentIndex - * - * @return self - */ - public function willReturnArgument($argumentIndex)/* : self */; - - /** - * @param callable $callback - * - * @return self - */ - public function willReturnCallback($callback)/* : self */; - - /** @return self */ - public function willReturnSelf()/* : self */; - - /** - * @param mixed $values - * - * @return self - */ - public function willReturnOnConsecutiveCalls(...$values)/* : self */; - - /** @return self */ - public function willThrowException(Throwable $exception)/* : self */; -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Builder/MethodNameMatch.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Builder/MethodNameMatch.php deleted file mode 100644 index a71caf797..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Builder/MethodNameMatch.php +++ /dev/null @@ -1,28 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Builder; - -use PHPUnit\Framework\Constraint\Constraint; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -interface MethodNameMatch extends ParametersMatch -{ - /** - * Adds a new method name match and returns the parameter match object for - * further matching possibilities. - * - * @param Constraint $constraint Constraint for matching method, if a string is passed it will use the PHPUnit_Framework_Constraint_IsEqual - * - * @return ParametersMatch - */ - public function method($constraint); -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Builder/ParametersMatch.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Builder/ParametersMatch.php deleted file mode 100644 index 707d82551..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Builder/ParametersMatch.php +++ /dev/null @@ -1,58 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Builder; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -interface ParametersMatch extends Stub -{ - /** - * Defines the expectation which must occur before the current is valid. - * - * @param string $id the identification of the expectation that should - * occur before this one - * - * @return Stub - */ - public function after($id); - - /** - * Sets the parameters to match for, each parameter to this function will - * be part of match. To perform specific matches or constraints create a - * new PHPUnit\Framework\Constraint\Constraint and use it for the parameter. - * If the parameter value is not a constraint it will use the - * PHPUnit\Framework\Constraint\IsEqual for the value. - * - * Some examples: - * - * // match first parameter with value 2 - * $b->with(2); - * // match first parameter with value 'smock' and second identical to 42 - * $b->with('smock', new PHPUnit\Framework\Constraint\IsEqual(42)); - * - * - * @return ParametersMatch - */ - public function with(...$arguments); - - /** - * Sets a rule which allows any kind of parameters. - * - * Some examples: - * - * // match any number of parameters - * $b->withAnyParameters(); - * - * - * @return ParametersMatch - */ - public function withAnyParameters(); -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Builder/Stub.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Builder/Stub.php deleted file mode 100644 index d7cb78fc4..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Builder/Stub.php +++ /dev/null @@ -1,24 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Builder; - -use PHPUnit\Framework\MockObject\Stub\Stub as BaseStub; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -interface Stub extends Identity -{ - /** - * Stubs the matching method with the stub object $stub. Any invocations of - * the matched method will now be handled by the stub instead. - */ - public function will(BaseStub $stub): Identity; -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/ConfigurableMethod.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/ConfigurableMethod.php index 4757dc637..7cce22c38 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/ConfigurableMethod.php +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/ConfigurableMethod.php @@ -12,41 +12,71 @@ use SebastianBergmann\Type\Type; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ -final class ConfigurableMethod +final readonly class ConfigurableMethod { /** - * @var string + * @var non-empty-string */ - private $name; + private string $name; /** - * @var Type + * @var array */ - private $returnType; + private array $defaultParameterValues; - public function __construct(string $name, Type $returnType) + /** + * @var non-negative-int + */ + private int $numberOfParameters; + private Type $returnType; + + /** + * @param non-empty-string $name + * @param array $defaultParameterValues + * @param non-negative-int $numberOfParameters + */ + public function __construct(string $name, array $defaultParameterValues, int $numberOfParameters, Type $returnType) { - $this->name = $name; - $this->returnType = $returnType; + $this->name = $name; + $this->defaultParameterValues = $defaultParameterValues; + $this->numberOfParameters = $numberOfParameters; + $this->returnType = $returnType; } - public function getName(): string + /** + * @return non-empty-string + */ + public function name(): string { return $this->name; } - public function mayReturn($value): bool + /** + * @return array + */ + public function defaultParameterValues(): array + { + return $this->defaultParameterValues; + } + + /** + * @return non-negative-int + */ + public function numberOfParameters(): int { - if ($value === null && $this->returnType->allowsNull()) { - return true; - } + return $this->numberOfParameters; + } + public function mayReturn(mixed $value): bool + { return $this->returnType->isAssignable(Type::fromValue($value, false)); } - public function getReturnTypeDeclaration(): string + public function returnTypeDeclaration(): string { return $this->returnType->asString(); } diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/BadMethodCallException.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/BadMethodCallException.php index 7e655e235..e8ddadda5 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/BadMethodCallException.php +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/BadMethodCallException.php @@ -10,6 +10,8 @@ namespace PHPUnit\Framework\MockObject; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class BadMethodCallException extends \BadMethodCallException implements Exception diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/CannotUseAddMethodsException.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/CannotUseAddMethodsException.php deleted file mode 100644 index 848746b52..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/CannotUseAddMethodsException.php +++ /dev/null @@ -1,29 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -use function sprintf; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class CannotUseAddMethodsException extends \PHPUnit\Framework\Exception implements Exception -{ - public function __construct(string $type, string $methodName) - { - parent::__construct( - sprintf( - 'Trying to configure method "%s" with addMethods(), but it exists in class "%s". Use onlyMethods() for methods that exist in the class', - $methodName, - $type, - ), - ); - } -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/CannotUseOnlyMethodsException.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/CannotUseOnlyMethodsException.php index 0efcd02a3..6cb399e53 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/CannotUseOnlyMethodsException.php +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/CannotUseOnlyMethodsException.php @@ -12,6 +12,8 @@ use function sprintf; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class CannotUseOnlyMethodsException extends \PHPUnit\Framework\Exception implements Exception @@ -20,7 +22,7 @@ public function __construct(string $type, string $methodName) { parent::__construct( sprintf( - 'Trying to configure method "%s" with onlyMethods(), but it does not exist in class "%s". Use addMethods() for methods that do not exist in the class', + 'Trying to configure method "%s" with onlyMethods(), but it does not exist in class "%s"', $methodName, $type, ), diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/ClassAlreadyExistsException.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/ClassAlreadyExistsException.php deleted file mode 100644 index 8c9c0a523..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/ClassAlreadyExistsException.php +++ /dev/null @@ -1,28 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -use function sprintf; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class ClassAlreadyExistsException extends \PHPUnit\Framework\Exception implements Exception -{ - public function __construct(string $className) - { - parent::__construct( - sprintf( - 'Class "%s" already exists', - $className, - ), - ); - } -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/ClassIsReadonlyException.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/ClassIsReadonlyException.php deleted file mode 100644 index f73570e1d..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/ClassIsReadonlyException.php +++ /dev/null @@ -1,28 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -use function sprintf; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class ClassIsReadonlyException extends \PHPUnit\Framework\Exception implements Exception -{ - public function __construct(string $className) - { - parent::__construct( - sprintf( - 'Class "%s" is declared "readonly" and cannot be doubled', - $className, - ), - ); - } -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/ConfigurableMethodsAlreadyInitializedException.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/ConfigurableMethodsAlreadyInitializedException.php deleted file mode 100644 index d12ac9973..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/ConfigurableMethodsAlreadyInitializedException.php +++ /dev/null @@ -1,17 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class ConfigurableMethodsAlreadyInitializedException extends \PHPUnit\Framework\Exception implements Exception -{ -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/Exception.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/Exception.php index 5880bc033..f7994f20f 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/Exception.php +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/Exception.php @@ -12,6 +12,8 @@ use Throwable; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ interface Exception extends Throwable diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/IncompatibleReturnValueException.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/IncompatibleReturnValueException.php index 1ca8e9c94..faf8a498d 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/IncompatibleReturnValueException.php +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/IncompatibleReturnValueException.php @@ -9,27 +9,24 @@ */ namespace PHPUnit\Framework\MockObject; -use function get_class; -use function gettype; -use function is_object; +use function get_debug_type; use function sprintf; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class IncompatibleReturnValueException extends \PHPUnit\Framework\Exception implements Exception { - /** - * @param mixed $value - */ - public function __construct(ConfigurableMethod $method, $value) + public function __construct(ConfigurableMethod $method, mixed $value) { parent::__construct( sprintf( 'Method %s may not return value of type %s, its declared return type is "%s"', - $method->getName(), - is_object($value) ? get_class($value) : gettype($value), - $method->getReturnTypeDeclaration(), + $method->name(), + get_debug_type($value), + $method->returnTypeDeclaration(), ), ); } diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/MatchBuilderNotFoundException.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/MatchBuilderNotFoundException.php index f2e1a31e8..8bf8967b0 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/MatchBuilderNotFoundException.php +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/MatchBuilderNotFoundException.php @@ -12,6 +12,8 @@ use function sprintf; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class MatchBuilderNotFoundException extends \PHPUnit\Framework\Exception implements Exception diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/MatcherAlreadyRegisteredException.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/MatcherAlreadyRegisteredException.php index 0972ffaf8..de62b8679 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/MatcherAlreadyRegisteredException.php +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/MatcherAlreadyRegisteredException.php @@ -12,6 +12,8 @@ use function sprintf; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class MatcherAlreadyRegisteredException extends \PHPUnit\Framework\Exception implements Exception diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/MethodCannotBeConfiguredException.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/MethodCannotBeConfiguredException.php index 2f0bb5a65..4d39b5d92 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/MethodCannotBeConfiguredException.php +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/MethodCannotBeConfiguredException.php @@ -12,6 +12,8 @@ use function sprintf; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class MethodCannotBeConfiguredException extends \PHPUnit\Framework\Exception implements Exception diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/MethodNameAlreadyConfiguredException.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/MethodNameAlreadyConfiguredException.php index 1e9f2c04c..e4a375927 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/MethodNameAlreadyConfiguredException.php +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/MethodNameAlreadyConfiguredException.php @@ -10,6 +10,8 @@ namespace PHPUnit\Framework\MockObject; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class MethodNameAlreadyConfiguredException extends \PHPUnit\Framework\Exception implements Exception diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/MethodNameNotConfiguredException.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/MethodNameNotConfiguredException.php index 89565b77e..25c113416 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/MethodNameNotConfiguredException.php +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/MethodNameNotConfiguredException.php @@ -10,6 +10,8 @@ namespace PHPUnit\Framework\MockObject; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class MethodNameNotConfiguredException extends \PHPUnit\Framework\Exception implements Exception diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/MethodParametersAlreadyConfiguredException.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/MethodParametersAlreadyConfiguredException.php index 1609c6ffb..fba96cf45 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/MethodParametersAlreadyConfiguredException.php +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/MethodParametersAlreadyConfiguredException.php @@ -10,6 +10,8 @@ namespace PHPUnit\Framework\MockObject; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class MethodParametersAlreadyConfiguredException extends \PHPUnit\Framework\Exception implements Exception diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/NeverReturningMethodException.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/NeverReturningMethodException.php new file mode 100644 index 000000000..3e565cea8 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/NeverReturningMethodException.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 PHPUnit\Framework\MockObject; + +use function sprintf; +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class NeverReturningMethodException extends RuntimeException implements Exception +{ + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public function __construct(string $className, string $methodName) + { + parent::__construct( + sprintf( + 'Method %s::%s() is declared to never return', + $className, + $methodName, + ), + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/NoMoreReturnValuesConfiguredException.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/NoMoreReturnValuesConfiguredException.php new file mode 100644 index 000000000..c4b181653 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/NoMoreReturnValuesConfiguredException.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 PHPUnit\Framework\MockObject; + +use function sprintf; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class NoMoreReturnValuesConfiguredException extends \PHPUnit\Framework\Exception implements Exception +{ + public function __construct(Invocation $invocation, int $numberOfConfiguredReturnValues) + { + parent::__construct( + sprintf( + 'Only %d return values have been configured for %s::%s()', + $numberOfConfiguredReturnValues, + $invocation->className(), + $invocation->methodName(), + ), + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/OriginalConstructorInvocationRequiredException.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/OriginalConstructorInvocationRequiredException.php deleted file mode 100644 index ecb9b63cf..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/OriginalConstructorInvocationRequiredException.php +++ /dev/null @@ -1,21 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class OriginalConstructorInvocationRequiredException extends \PHPUnit\Framework\Exception implements Exception -{ - public function __construct() - { - parent::__construct('Proxying to original methods requires invoking the original constructor'); - } -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/ReflectionException.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/ReflectionException.php deleted file mode 100644 index d6319c694..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/ReflectionException.php +++ /dev/null @@ -1,19 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -use RuntimeException; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class ReflectionException extends RuntimeException implements Exception -{ -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/ReturnValueNotConfiguredException.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/ReturnValueNotConfiguredException.php index 2bc4e8829..cf193f10c 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/ReturnValueNotConfiguredException.php +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/ReturnValueNotConfiguredException.php @@ -12,6 +12,8 @@ use function sprintf; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class ReturnValueNotConfiguredException extends \PHPUnit\Framework\Exception implements Exception @@ -20,9 +22,9 @@ public function __construct(Invocation $invocation) { parent::__construct( sprintf( - 'Return value inference disabled and no expectation set up for %s::%s()', - $invocation->getClassName(), - $invocation->getMethodName(), + 'No return value is configured for %s::%s() and return value generation is disabled', + $invocation->className(), + $invocation->methodName(), ), ); } diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/RuntimeException.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/RuntimeException.php index 33b6a5be3..b99a903e5 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/RuntimeException.php +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/RuntimeException.php @@ -10,6 +10,8 @@ namespace PHPUnit\Framework\MockObject; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class RuntimeException extends \RuntimeException implements Exception diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/SoapExtensionNotAvailableException.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/SoapExtensionNotAvailableException.php deleted file mode 100644 index 6ec5057ab..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/SoapExtensionNotAvailableException.php +++ /dev/null @@ -1,23 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class SoapExtensionNotAvailableException extends \PHPUnit\Framework\Exception implements Exception -{ - public function __construct() - { - parent::__construct( - 'The SOAP extension is required to generate a test double from WSDL', - ); - } -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/UnknownClassException.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/UnknownClassException.php deleted file mode 100644 index b08dead05..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/UnknownClassException.php +++ /dev/null @@ -1,28 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -use function sprintf; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class UnknownClassException extends \PHPUnit\Framework\Exception implements Exception -{ - public function __construct(string $className) - { - parent::__construct( - sprintf( - 'Class "%s" does not exist', - $className, - ), - ); - } -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/UnknownTraitException.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/UnknownTraitException.php deleted file mode 100644 index c689dae92..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/UnknownTraitException.php +++ /dev/null @@ -1,28 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -use function sprintf; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class UnknownTraitException extends \PHPUnit\Framework\Exception implements Exception -{ - public function __construct(string $traitName) - { - parent::__construct( - sprintf( - 'Trait "%s" does not exist', - $traitName, - ), - ); - } -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator.php deleted file mode 100644 index 49211ad7e..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator.php +++ /dev/null @@ -1,1160 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -use const DIRECTORY_SEPARATOR; -use const PHP_EOL; -use const PHP_MAJOR_VERSION; -use const PREG_OFFSET_CAPTURE; -use const WSDL_CACHE_NONE; -use function array_merge; -use function array_pop; -use function array_unique; -use function class_exists; -use function count; -use function explode; -use function extension_loaded; -use function implode; -use function in_array; -use function interface_exists; -use function is_array; -use function is_object; -use function md5; -use function method_exists; -use function mt_rand; -use function preg_match; -use function preg_match_all; -use function range; -use function serialize; -use function sort; -use function sprintf; -use function str_replace; -use function strlen; -use function strpos; -use function strtolower; -use function substr; -use function trait_exists; -use Doctrine\Instantiator\Exception\ExceptionInterface as InstantiatorException; -use Doctrine\Instantiator\Instantiator; -use Exception; -use Iterator; -use IteratorAggregate; -use PHPUnit\Framework\InvalidArgumentException; -use ReflectionClass; -use ReflectionMethod; -use SebastianBergmann\Template\Exception as TemplateException; -use SebastianBergmann\Template\Template; -use SoapClient; -use SoapFault; -use Throwable; -use Traversable; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class Generator -{ - private const MOCKED_CLONE_METHOD_WITH_VOID_RETURN_TYPE_TRAIT = <<<'EOT' -namespace PHPUnit\Framework\MockObject; - -trait MockedCloneMethodWithVoidReturnType -{ - public function __clone(): void - { - $this->__phpunit_invocationMocker = clone $this->__phpunit_getInvocationHandler(); - } -} -EOT; - private const MOCKED_CLONE_METHOD_WITHOUT_RETURN_TYPE_TRAIT = <<<'EOT' -namespace PHPUnit\Framework\MockObject; - -trait MockedCloneMethodWithoutReturnType -{ - public function __clone() - { - $this->__phpunit_invocationMocker = clone $this->__phpunit_getInvocationHandler(); - } -} -EOT; - private const UNMOCKED_CLONE_METHOD_WITH_VOID_RETURN_TYPE_TRAIT = <<<'EOT' -namespace PHPUnit\Framework\MockObject; - -trait UnmockedCloneMethodWithVoidReturnType -{ - public function __clone(): void - { - $this->__phpunit_invocationMocker = clone $this->__phpunit_getInvocationHandler(); - - parent::__clone(); - } -} -EOT; - private const UNMOCKED_CLONE_METHOD_WITHOUT_RETURN_TYPE_TRAIT = <<<'EOT' -namespace PHPUnit\Framework\MockObject; - -trait UnmockedCloneMethodWithoutReturnType -{ - public function __clone() - { - $this->__phpunit_invocationMocker = clone $this->__phpunit_getInvocationHandler(); - - parent::__clone(); - } -} -EOT; - - /** - * @var array - */ - private const EXCLUDED_METHOD_NAMES = [ - '__CLASS__' => true, - '__DIR__' => true, - '__FILE__' => true, - '__FUNCTION__' => true, - '__LINE__' => true, - '__METHOD__' => true, - '__NAMESPACE__' => true, - '__TRAIT__' => true, - '__clone' => true, - '__halt_compiler' => true, - ]; - - /** - * @var array - */ - private static $cache = []; - - /** - * @var Template[] - */ - private static $templates = []; - - /** - * Returns a mock object for the specified class. - * - * @param null|array $methods - * - * @throws ClassAlreadyExistsException - * @throws ClassIsFinalException - * @throws ClassIsReadonlyException - * @throws DuplicateMethodException - * @throws InvalidArgumentException - * @throws InvalidMethodNameException - * @throws OriginalConstructorInvocationRequiredException - * @throws ReflectionException - * @throws RuntimeException - * @throws UnknownTypeException - */ - public function getMock(string $type, $methods = [], array $arguments = [], string $mockClassName = '', bool $callOriginalConstructor = true, bool $callOriginalClone = true, bool $callAutoload = true, bool $cloneArguments = true, bool $callOriginalMethods = false, ?object $proxyTarget = null, bool $allowMockingUnknownTypes = true, bool $returnValueGeneration = true): MockObject - { - if (!is_array($methods) && null !== $methods) { - throw InvalidArgumentException::create(2, 'array'); - } - - if ($type === 'Traversable' || $type === '\\Traversable') { - $type = 'Iterator'; - } - - if (!$allowMockingUnknownTypes && !class_exists($type, $callAutoload) && !interface_exists($type, $callAutoload)) { - throw new UnknownTypeException($type); - } - - if (null !== $methods) { - foreach ($methods as $method) { - if (!preg_match('~[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*~', (string) $method)) { - throw new InvalidMethodNameException((string) $method); - } - } - - if ($methods !== array_unique($methods)) { - throw new DuplicateMethodException($methods); - } - } - - if ($mockClassName !== '' && class_exists($mockClassName, false)) { - try { - $reflector = new ReflectionClass($mockClassName); - // @codeCoverageIgnoreStart - } catch (\ReflectionException $e) { - throw new ReflectionException( - $e->getMessage(), - $e->getCode(), - $e, - ); - } - // @codeCoverageIgnoreEnd - - if (!$reflector->implementsInterface(MockObject::class)) { - throw new ClassAlreadyExistsException($mockClassName); - } - } - - if (!$callOriginalConstructor && $callOriginalMethods) { - throw new OriginalConstructorInvocationRequiredException; - } - - $mock = $this->generate( - $type, - $methods, - $mockClassName, - $callOriginalClone, - $callAutoload, - $cloneArguments, - $callOriginalMethods, - ); - - return $this->getObject( - $mock, - $type, - $callOriginalConstructor, - $callAutoload, - $arguments, - $callOriginalMethods, - $proxyTarget, - $returnValueGeneration, - ); - } - - /** - * @psalm-param list $interfaces - * - * @throws RuntimeException - * @throws UnknownTypeException - */ - public function getMockForInterfaces(array $interfaces, bool $callAutoload = true): MockObject - { - if (count($interfaces) < 2) { - throw new RuntimeException('At least two interfaces must be specified'); - } - - foreach ($interfaces as $interface) { - if (!interface_exists($interface, $callAutoload)) { - throw new UnknownTypeException($interface); - } - } - - sort($interfaces); - - $methods = []; - - foreach ($interfaces as $interface) { - $methods = array_merge($methods, $this->getClassMethods($interface)); - } - - if (count(array_unique($methods)) < count($methods)) { - throw new RuntimeException('Interfaces must not declare the same method'); - } - - $unqualifiedNames = []; - - foreach ($interfaces as $interface) { - $parts = explode('\\', $interface); - $unqualifiedNames[] = array_pop($parts); - } - - sort($unqualifiedNames); - - do { - $intersectionName = sprintf( - 'Intersection_%s_%s', - implode('_', $unqualifiedNames), - substr(md5((string) mt_rand()), 0, 8), - ); - } while (interface_exists($intersectionName, false)); - - $template = $this->getTemplate('intersection.tpl'); - - $template->setVar( - [ - 'intersection' => $intersectionName, - 'interfaces' => implode(', ', $interfaces), - ], - ); - - eval($template->render()); - - return $this->getMock($intersectionName); - } - - /** - * Returns a mock object for the specified abstract class with all abstract - * methods of the class mocked. - * - * Concrete methods to mock can be specified with the $mockedMethods parameter. - * - * @psalm-template RealInstanceType of object - * - * @psalm-param class-string $originalClassName - * - * @psalm-return MockObject&RealInstanceType - * - * @throws ClassAlreadyExistsException - * @throws ClassIsFinalException - * @throws ClassIsReadonlyException - * @throws DuplicateMethodException - * @throws InvalidArgumentException - * @throws InvalidMethodNameException - * @throws OriginalConstructorInvocationRequiredException - * @throws ReflectionException - * @throws RuntimeException - * @throws UnknownClassException - * @throws UnknownTypeException - */ - public function getMockForAbstractClass(string $originalClassName, array $arguments = [], string $mockClassName = '', bool $callOriginalConstructor = true, bool $callOriginalClone = true, bool $callAutoload = true, ?array $mockedMethods = null, bool $cloneArguments = true): MockObject - { - if (class_exists($originalClassName, $callAutoload) || - interface_exists($originalClassName, $callAutoload)) { - try { - $reflector = new ReflectionClass($originalClassName); - // @codeCoverageIgnoreStart - } catch (\ReflectionException $e) { - throw new ReflectionException( - $e->getMessage(), - $e->getCode(), - $e, - ); - } - // @codeCoverageIgnoreEnd - - $methods = $mockedMethods; - - foreach ($reflector->getMethods() as $method) { - if ($method->isAbstract() && !in_array($method->getName(), $methods ?? [], true)) { - $methods[] = $method->getName(); - } - } - - if (empty($methods)) { - $methods = null; - } - - return $this->getMock( - $originalClassName, - $methods, - $arguments, - $mockClassName, - $callOriginalConstructor, - $callOriginalClone, - $callAutoload, - $cloneArguments, - ); - } - - throw new UnknownClassException($originalClassName); - } - - /** - * Returns a mock object for the specified trait with all abstract methods - * of the trait mocked. Concrete methods to mock can be specified with the - * `$mockedMethods` parameter. - * - * @psalm-param trait-string $traitName - * - * @throws ClassAlreadyExistsException - * @throws ClassIsFinalException - * @throws ClassIsReadonlyException - * @throws DuplicateMethodException - * @throws InvalidArgumentException - * @throws InvalidMethodNameException - * @throws OriginalConstructorInvocationRequiredException - * @throws ReflectionException - * @throws RuntimeException - * @throws UnknownClassException - * @throws UnknownTraitException - * @throws UnknownTypeException - */ - public function getMockForTrait(string $traitName, array $arguments = [], string $mockClassName = '', bool $callOriginalConstructor = true, bool $callOriginalClone = true, bool $callAutoload = true, ?array $mockedMethods = null, bool $cloneArguments = true): MockObject - { - if (!trait_exists($traitName, $callAutoload)) { - throw new UnknownTraitException($traitName); - } - - $className = $this->generateClassName( - $traitName, - '', - 'Trait_', - ); - - $classTemplate = $this->getTemplate('trait_class.tpl'); - - $classTemplate->setVar( - [ - 'prologue' => 'abstract ', - 'class_name' => $className['className'], - 'trait_name' => $traitName, - ], - ); - - $mockTrait = new MockTrait($classTemplate->render(), $className['className']); - $mockTrait->generate(); - - return $this->getMockForAbstractClass($className['className'], $arguments, $mockClassName, $callOriginalConstructor, $callOriginalClone, $callAutoload, $mockedMethods, $cloneArguments); - } - - /** - * Returns an object for the specified trait. - * - * @psalm-param trait-string $traitName - * - * @throws ReflectionException - * @throws RuntimeException - * @throws UnknownTraitException - */ - public function getObjectForTrait(string $traitName, string $traitClassName = '', bool $callAutoload = true, bool $callOriginalConstructor = false, array $arguments = []): object - { - if (!trait_exists($traitName, $callAutoload)) { - throw new UnknownTraitException($traitName); - } - - $className = $this->generateClassName( - $traitName, - $traitClassName, - 'Trait_', - ); - - $classTemplate = $this->getTemplate('trait_class.tpl'); - - $classTemplate->setVar( - [ - 'prologue' => '', - 'class_name' => $className['className'], - 'trait_name' => $traitName, - ], - ); - - return $this->getObject( - new MockTrait( - $classTemplate->render(), - $className['className'], - ), - '', - $callOriginalConstructor, - $callAutoload, - $arguments, - ); - } - - /** - * @throws ClassIsFinalException - * @throws ClassIsReadonlyException - * @throws ReflectionException - * @throws RuntimeException - */ - public function generate(string $type, ?array $methods = null, string $mockClassName = '', bool $callOriginalClone = true, bool $callAutoload = true, bool $cloneArguments = true, bool $callOriginalMethods = false): MockClass - { - if ($mockClassName !== '') { - return $this->generateMock( - $type, - $methods, - $mockClassName, - $callOriginalClone, - $callAutoload, - $cloneArguments, - $callOriginalMethods, - ); - } - - $key = md5( - $type . - serialize($methods) . - serialize($callOriginalClone) . - serialize($cloneArguments) . - serialize($callOriginalMethods), - ); - - if (!isset(self::$cache[$key])) { - self::$cache[$key] = $this->generateMock( - $type, - $methods, - $mockClassName, - $callOriginalClone, - $callAutoload, - $cloneArguments, - $callOriginalMethods, - ); - } - - return self::$cache[$key]; - } - - /** - * @throws RuntimeException - * @throws SoapExtensionNotAvailableException - */ - public function generateClassFromWsdl(string $wsdlFile, string $className, array $methods = [], array $options = []): string - { - if (!extension_loaded('soap')) { - throw new SoapExtensionNotAvailableException; - } - - $options = array_merge($options, ['cache_wsdl' => WSDL_CACHE_NONE]); - - try { - $client = new SoapClient($wsdlFile, $options); - $_methods = array_unique($client->__getFunctions()); - unset($client); - } catch (SoapFault $e) { - throw new RuntimeException( - $e->getMessage(), - $e->getCode(), - $e, - ); - } - - sort($_methods); - - $methodTemplate = $this->getTemplate('wsdl_method.tpl'); - $methodsBuffer = ''; - - foreach ($_methods as $method) { - preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*\(/', $method, $matches, PREG_OFFSET_CAPTURE); - $lastFunction = array_pop($matches[0]); - $nameStart = $lastFunction[1]; - $nameEnd = $nameStart + strlen($lastFunction[0]) - 1; - $name = str_replace('(', '', $lastFunction[0]); - - if (empty($methods) || in_array($name, $methods, true)) { - $args = explode( - ',', - str_replace(')', '', substr($method, $nameEnd + 1)), - ); - - foreach (range(0, count($args) - 1) as $i) { - $parameterStart = strpos($args[$i], '$'); - - if (!$parameterStart) { - continue; - } - - $args[$i] = substr($args[$i], $parameterStart); - } - - $methodTemplate->setVar( - [ - 'method_name' => $name, - 'arguments' => implode(', ', $args), - ], - ); - - $methodsBuffer .= $methodTemplate->render(); - } - } - - $optionsBuffer = '['; - - foreach ($options as $key => $value) { - $optionsBuffer .= $key . ' => ' . $value; - } - - $optionsBuffer .= ']'; - - $classTemplate = $this->getTemplate('wsdl_class.tpl'); - $namespace = ''; - - if (strpos($className, '\\') !== false) { - $parts = explode('\\', $className); - $className = array_pop($parts); - $namespace = 'namespace ' . implode('\\', $parts) . ';' . "\n\n"; - } - - $classTemplate->setVar( - [ - 'namespace' => $namespace, - 'class_name' => $className, - 'wsdl' => $wsdlFile, - 'options' => $optionsBuffer, - 'methods' => $methodsBuffer, - ], - ); - - return $classTemplate->render(); - } - - /** - * @throws ReflectionException - * - * @return string[] - */ - public function getClassMethods(string $className): array - { - try { - $class = new ReflectionClass($className); - // @codeCoverageIgnoreStart - } catch (\ReflectionException $e) { - throw new ReflectionException( - $e->getMessage(), - $e->getCode(), - $e, - ); - } - // @codeCoverageIgnoreEnd - - $methods = []; - - foreach ($class->getMethods() as $method) { - if ($method->isPublic() || $method->isAbstract()) { - $methods[] = $method->getName(); - } - } - - return $methods; - } - - /** - * @throws ReflectionException - * - * @return MockMethod[] - */ - public function mockClassMethods(string $className, bool $callOriginalMethods, bool $cloneArguments): array - { - try { - $class = new ReflectionClass($className); - // @codeCoverageIgnoreStart - } catch (\ReflectionException $e) { - throw new ReflectionException( - $e->getMessage(), - $e->getCode(), - $e, - ); - } - // @codeCoverageIgnoreEnd - - $methods = []; - - foreach ($class->getMethods() as $method) { - if (($method->isPublic() || $method->isAbstract()) && $this->canMockMethod($method)) { - $methods[] = MockMethod::fromReflection($method, $callOriginalMethods, $cloneArguments); - } - } - - return $methods; - } - - /** - * @throws ReflectionException - * - * @return MockMethod[] - */ - public function mockInterfaceMethods(string $interfaceName, bool $cloneArguments): array - { - try { - $class = new ReflectionClass($interfaceName); - // @codeCoverageIgnoreStart - } catch (\ReflectionException $e) { - throw new ReflectionException( - $e->getMessage(), - $e->getCode(), - $e, - ); - } - // @codeCoverageIgnoreEnd - - $methods = []; - - foreach ($class->getMethods() as $method) { - $methods[] = MockMethod::fromReflection($method, false, $cloneArguments); - } - - return $methods; - } - - /** - * @psalm-param class-string $interfaceName - * - * @throws ReflectionException - * - * @return ReflectionMethod[] - */ - private function userDefinedInterfaceMethods(string $interfaceName): array - { - try { - // @codeCoverageIgnoreStart - $interface = new ReflectionClass($interfaceName); - } catch (\ReflectionException $e) { - throw new ReflectionException( - $e->getMessage(), - $e->getCode(), - $e, - ); - } - // @codeCoverageIgnoreEnd - - $methods = []; - - foreach ($interface->getMethods() as $method) { - if (!$method->isUserDefined()) { - continue; - } - - $methods[] = $method; - } - - return $methods; - } - - /** - * @throws ReflectionException - * @throws RuntimeException - */ - private function getObject(MockType $mockClass, $type = '', bool $callOriginalConstructor = false, bool $callAutoload = false, array $arguments = [], bool $callOriginalMethods = false, ?object $proxyTarget = null, bool $returnValueGeneration = true) - { - $className = $mockClass->generate(); - - if ($callOriginalConstructor) { - if (count($arguments) === 0) { - $object = new $className; - } else { - try { - $class = new ReflectionClass($className); - // @codeCoverageIgnoreStart - } catch (\ReflectionException $e) { - throw new ReflectionException( - $e->getMessage(), - $e->getCode(), - $e, - ); - } - // @codeCoverageIgnoreEnd - - $object = $class->newInstanceArgs($arguments); - } - } else { - try { - $object = (new Instantiator)->instantiate($className); - } catch (InstantiatorException $e) { - throw new RuntimeException($e->getMessage()); - } - } - - if ($callOriginalMethods) { - if (!is_object($proxyTarget)) { - if (count($arguments) === 0) { - $proxyTarget = new $type; - } else { - try { - $class = new ReflectionClass($type); - // @codeCoverageIgnoreStart - } catch (\ReflectionException $e) { - throw new ReflectionException( - $e->getMessage(), - $e->getCode(), - $e, - ); - } - // @codeCoverageIgnoreEnd - - $proxyTarget = $class->newInstanceArgs($arguments); - } - } - - $object->__phpunit_setOriginalObject($proxyTarget); - } - - if ($object instanceof MockObject) { - $object->__phpunit_setReturnValueGeneration($returnValueGeneration); - } - - return $object; - } - - /** - * @throws ClassIsFinalException - * @throws ClassIsReadonlyException - * @throws ReflectionException - * @throws RuntimeException - */ - private function generateMock(string $type, ?array $explicitMethods, string $mockClassName, bool $callOriginalClone, bool $callAutoload, bool $cloneArguments, bool $callOriginalMethods): MockClass - { - $classTemplate = $this->getTemplate('mocked_class.tpl'); - $additionalInterfaces = []; - $mockedCloneMethod = false; - $unmockedCloneMethod = false; - $isClass = false; - $isInterface = false; - $class = null; - $mockMethods = new MockMethodSet; - - $_mockClassName = $this->generateClassName( - $type, - $mockClassName, - 'Mock_', - ); - - if (class_exists($_mockClassName['fullClassName'], $callAutoload)) { - $isClass = true; - } elseif (interface_exists($_mockClassName['fullClassName'], $callAutoload)) { - $isInterface = true; - } - - if (!$isClass && !$isInterface) { - $prologue = 'class ' . $_mockClassName['originalClassName'] . "\n{\n}\n\n"; - - if (!empty($_mockClassName['namespaceName'])) { - $prologue = 'namespace ' . $_mockClassName['namespaceName'] . - " {\n\n" . $prologue . "}\n\n" . - "namespace {\n\n"; - - $epilogue = "\n\n}"; - } - - $mockedCloneMethod = true; - } else { - try { - $class = new ReflectionClass($_mockClassName['fullClassName']); - // @codeCoverageIgnoreStart - } catch (\ReflectionException $e) { - throw new ReflectionException( - $e->getMessage(), - $e->getCode(), - $e, - ); - } - // @codeCoverageIgnoreEnd - - if ($class->isFinal()) { - throw new ClassIsFinalException($_mockClassName['fullClassName']); - } - - if (method_exists($class, 'isReadOnly') && $class->isReadOnly()) { - throw new ClassIsReadonlyException($_mockClassName['fullClassName']); - } - - // @see https://github.com/sebastianbergmann/phpunit/issues/2995 - if ($isInterface && $class->implementsInterface(Throwable::class)) { - $actualClassName = Exception::class; - $additionalInterfaces[] = $class->getName(); - $isInterface = false; - - try { - $class = new ReflectionClass($actualClassName); - // @codeCoverageIgnoreStart - } catch (\ReflectionException $e) { - throw new ReflectionException( - $e->getMessage(), - $e->getCode(), - $e, - ); - } - // @codeCoverageIgnoreEnd - - foreach ($this->userDefinedInterfaceMethods($_mockClassName['fullClassName']) as $method) { - $methodName = $method->getName(); - - if ($class->hasMethod($methodName)) { - try { - $classMethod = $class->getMethod($methodName); - // @codeCoverageIgnoreStart - } catch (\ReflectionException $e) { - throw new ReflectionException( - $e->getMessage(), - $e->getCode(), - $e, - ); - } - // @codeCoverageIgnoreEnd - - if (!$this->canMockMethod($classMethod)) { - continue; - } - } - - $mockMethods->addMethods( - MockMethod::fromReflection($method, $callOriginalMethods, $cloneArguments), - ); - } - - $_mockClassName = $this->generateClassName( - $actualClassName, - $_mockClassName['className'], - 'Mock_', - ); - } - - // @see https://github.com/sebastianbergmann/phpunit-mock-objects/issues/103 - if ($isInterface && $class->implementsInterface(Traversable::class) && - !$class->implementsInterface(Iterator::class) && - !$class->implementsInterface(IteratorAggregate::class)) { - $additionalInterfaces[] = Iterator::class; - - $mockMethods->addMethods( - ...$this->mockClassMethods(Iterator::class, $callOriginalMethods, $cloneArguments), - ); - } - - if ($class->hasMethod('__clone')) { - try { - $cloneMethod = $class->getMethod('__clone'); - // @codeCoverageIgnoreStart - } catch (\ReflectionException $e) { - throw new ReflectionException( - $e->getMessage(), - $e->getCode(), - $e, - ); - } - // @codeCoverageIgnoreEnd - - if (!$cloneMethod->isFinal()) { - if ($callOriginalClone && !$isInterface) { - $unmockedCloneMethod = true; - } else { - $mockedCloneMethod = true; - } - } - } else { - $mockedCloneMethod = true; - } - } - - if ($isClass && $explicitMethods === []) { - $mockMethods->addMethods( - ...$this->mockClassMethods($_mockClassName['fullClassName'], $callOriginalMethods, $cloneArguments), - ); - } - - if ($isInterface && ($explicitMethods === [] || $explicitMethods === null)) { - $mockMethods->addMethods( - ...$this->mockInterfaceMethods($_mockClassName['fullClassName'], $cloneArguments), - ); - } - - if (is_array($explicitMethods)) { - foreach ($explicitMethods as $methodName) { - if ($class !== null && $class->hasMethod($methodName)) { - try { - $method = $class->getMethod($methodName); - // @codeCoverageIgnoreStart - } catch (\ReflectionException $e) { - throw new ReflectionException( - $e->getMessage(), - $e->getCode(), - $e, - ); - } - // @codeCoverageIgnoreEnd - - if ($this->canMockMethod($method)) { - $mockMethods->addMethods( - MockMethod::fromReflection($method, $callOriginalMethods, $cloneArguments), - ); - } - } else { - $mockMethods->addMethods( - MockMethod::fromName( - $_mockClassName['fullClassName'], - $methodName, - $cloneArguments, - ), - ); - } - } - } - - $mockedMethods = ''; - $configurable = []; - - foreach ($mockMethods->asArray() as $mockMethod) { - $mockedMethods .= $mockMethod->generateCode(); - $configurable[] = new ConfigurableMethod($mockMethod->getName(), $mockMethod->getReturnType()); - } - - $method = ''; - - if (!$mockMethods->hasMethod('method') && (!isset($class) || !$class->hasMethod('method'))) { - $method = PHP_EOL . ' use \PHPUnit\Framework\MockObject\Method;'; - } - - $cloneTrait = ''; - - if ($mockedCloneMethod) { - $cloneTrait = $this->mockedCloneMethod(); - } - - if ($unmockedCloneMethod) { - $cloneTrait = $this->unmockedCloneMethod(); - } - - $classTemplate->setVar( - [ - 'prologue' => $prologue ?? '', - 'epilogue' => $epilogue ?? '', - 'class_declaration' => $this->generateMockClassDeclaration( - $_mockClassName, - $isInterface, - $additionalInterfaces, - ), - 'clone' => $cloneTrait, - 'mock_class_name' => $_mockClassName['className'], - 'mocked_methods' => $mockedMethods, - 'method' => $method, - ], - ); - - return new MockClass( - $classTemplate->render(), - $_mockClassName['className'], - $configurable, - ); - } - - private function generateClassName(string $type, string $className, string $prefix): array - { - if ($type[0] === '\\') { - $type = substr($type, 1); - } - - $classNameParts = explode('\\', $type); - - if (count($classNameParts) > 1) { - $type = array_pop($classNameParts); - $namespaceName = implode('\\', $classNameParts); - $fullClassName = $namespaceName . '\\' . $type; - } else { - $namespaceName = ''; - $fullClassName = $type; - } - - if ($className === '') { - do { - $className = $prefix . $type . '_' . - substr(md5((string) mt_rand()), 0, 8); - } while (class_exists($className, false)); - } - - return [ - 'className' => $className, - 'originalClassName' => $type, - 'fullClassName' => $fullClassName, - 'namespaceName' => $namespaceName, - ]; - } - - private function generateMockClassDeclaration(array $mockClassName, bool $isInterface, array $additionalInterfaces = []): string - { - $buffer = 'class '; - - $additionalInterfaces[] = MockObject::class; - $interfaces = implode(', ', $additionalInterfaces); - - if ($isInterface) { - $buffer .= sprintf( - '%s implements %s', - $mockClassName['className'], - $interfaces, - ); - - if (!in_array($mockClassName['originalClassName'], $additionalInterfaces, true)) { - $buffer .= ', '; - - if (!empty($mockClassName['namespaceName'])) { - $buffer .= $mockClassName['namespaceName'] . '\\'; - } - - $buffer .= $mockClassName['originalClassName']; - } - } else { - $buffer .= sprintf( - '%s extends %s%s implements %s', - $mockClassName['className'], - !empty($mockClassName['namespaceName']) ? $mockClassName['namespaceName'] . '\\' : '', - $mockClassName['originalClassName'], - $interfaces, - ); - } - - return $buffer; - } - - private function canMockMethod(ReflectionMethod $method): bool - { - return !($this->isConstructor($method) || $method->isFinal() || $method->isPrivate() || $this->isMethodNameExcluded($method->getName())); - } - - private function isMethodNameExcluded(string $name): bool - { - return isset(self::EXCLUDED_METHOD_NAMES[$name]); - } - - /** - * @throws RuntimeException - */ - private function getTemplate(string $template): Template - { - $filename = __DIR__ . DIRECTORY_SEPARATOR . 'Generator' . DIRECTORY_SEPARATOR . $template; - - if (!isset(self::$templates[$filename])) { - try { - self::$templates[$filename] = new Template($filename); - } catch (TemplateException $e) { - throw new RuntimeException( - $e->getMessage(), - $e->getCode(), - $e, - ); - } - } - - return self::$templates[$filename]; - } - - /** - * @see https://github.com/sebastianbergmann/phpunit/issues/4139#issuecomment-605409765 - */ - private function isConstructor(ReflectionMethod $method): bool - { - $methodName = strtolower($method->getName()); - - if ($methodName === '__construct') { - return true; - } - - if (PHP_MAJOR_VERSION >= 8) { - return false; - } - - $className = strtolower($method->getDeclaringClass()->getName()); - - return $methodName === $className; - } - - private function mockedCloneMethod(): string - { - if (PHP_MAJOR_VERSION >= 8) { - if (!trait_exists('\PHPUnit\Framework\MockObject\MockedCloneMethodWithVoidReturnType')) { - eval(self::MOCKED_CLONE_METHOD_WITH_VOID_RETURN_TYPE_TRAIT); - } - - return PHP_EOL . ' use \PHPUnit\Framework\MockObject\MockedCloneMethodWithVoidReturnType;'; - } - - if (!trait_exists('\PHPUnit\Framework\MockObject\MockedCloneMethodWithoutReturnType')) { - eval(self::MOCKED_CLONE_METHOD_WITHOUT_RETURN_TYPE_TRAIT); - } - - return PHP_EOL . ' use \PHPUnit\Framework\MockObject\MockedCloneMethodWithoutReturnType;'; - } - - private function unmockedCloneMethod(): string - { - if (PHP_MAJOR_VERSION >= 8) { - if (!trait_exists('\PHPUnit\Framework\MockObject\UnmockedCloneMethodWithVoidReturnType')) { - eval(self::UNMOCKED_CLONE_METHOD_WITH_VOID_RETURN_TYPE_TRAIT); - } - - return PHP_EOL . ' use \PHPUnit\Framework\MockObject\UnmockedCloneMethodWithVoidReturnType;'; - } - - if (!trait_exists('\PHPUnit\Framework\MockObject\UnmockedCloneMethodWithoutReturnType')) { - eval(self::UNMOCKED_CLONE_METHOD_WITHOUT_RETURN_TYPE_TRAIT); - } - - return PHP_EOL . ' use \PHPUnit\Framework\MockObject\UnmockedCloneMethodWithoutReturnType;'; - } -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/DoubledClass.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/DoubledClass.php new file mode 100644 index 000000000..3d88c72e3 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/DoubledClass.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 PHPUnit\Framework\MockObject\Generator; + +use function class_exists; +use PHPUnit\Framework\MockObject\ConfigurableMethod; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class DoubledClass +{ + private string $classCode; + + /** + * @var class-string + */ + private string $mockName; + + /** + * @var list + */ + private array $configurableMethods; + + /** + * @param class-string $mockName + * @param list $configurableMethods + */ + public function __construct(string $classCode, string $mockName, array $configurableMethods) + { + $this->classCode = $classCode; + $this->mockName = $mockName; + $this->configurableMethods = $configurableMethods; + } + + /** + * @return class-string + */ + public function generate(): string + { + if (!class_exists($this->mockName, false)) { + eval($this->classCode); + } + + return $this->mockName; + } + + public function classCode(): string + { + return $this->classCode; + } + + /** + * @return list + */ + public function configurableMethods(): array + { + return $this->configurableMethods; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/DoubledMethod.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/DoubledMethod.php new file mode 100644 index 000000000..09006b257 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/DoubledMethod.php @@ -0,0 +1,395 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Generator; + +use function array_key_exists; +use function assert; +use function count; +use function explode; +use function implode; +use function is_object; +use function is_string; +use function preg_match; +use function preg_replace; +use function str_contains; +use function strlen; +use function strpos; +use function substr; +use function substr_count; +use function trim; +use function var_export; +use ReflectionMethod; +use ReflectionParameter; +use SebastianBergmann\Type\ReflectionMapper; +use SebastianBergmann\Type\Type; +use SebastianBergmann\Type\UnknownType; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class DoubledMethod +{ + use TemplateLoader; + + /** + * @var class-string + */ + private readonly string $className; + + /** + * @var non-empty-string + */ + private readonly string $methodName; + private readonly string $modifier; + private readonly string $argumentsForDeclaration; + private readonly string $argumentsForCall; + private readonly Type $returnType; + private readonly string $reference; + private readonly bool $static; + private readonly ?string $deprecation; + + /** + * @var array + */ + private readonly array $defaultParameterValues; + + /** + * @var non-negative-int + */ + private readonly int $numberOfParameters; + + /** + * @throws ReflectionException + * @throws RuntimeException + */ + public static function fromReflection(ReflectionMethod $method): self + { + if ($method->isPrivate()) { + $modifier = 'private'; + } elseif ($method->isProtected()) { + $modifier = 'protected'; + } else { + $modifier = 'public'; + } + + if ($method->isStatic()) { + $modifier .= ' static'; + } + + if ($method->returnsReference()) { + $reference = '&'; + } else { + $reference = ''; + } + + $docComment = $method->getDocComment(); + + if (is_string($docComment) && + preg_match('#\*[ \t]*+@deprecated[ \t]*+(.*?)\r?+\n[ \t]*+\*(?:[ \t]*+@|/$)#s', $docComment, $deprecation) > 0 + ) { + $deprecation = trim(preg_replace('#[ \t]*\r?\n[ \t]*+\*[ \t]*+#', ' ', $deprecation[1])); + } else { + $deprecation = null; + } + + return new self( + $method->getDeclaringClass()->getName(), + $method->getName(), + $modifier, + self::methodParametersForDeclaration($method), + self::methodParametersForCall($method), + self::methodParametersDefaultValues($method), + count($method->getParameters()), + (new ReflectionMapper)->fromReturnType($method), + $reference, + $method->isStatic(), + $deprecation, + ); + } + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public static function fromName(string $className, string $methodName): self + { + return new self( + $className, + $methodName, + 'public', + '', + '', + [], + 0, + new UnknownType, + '', + false, + null, + ); + } + + /** + * @param class-string $className + * @param non-empty-string $methodName + * @param array $defaultParameterValues + * @param non-negative-int $numberOfParameters + */ + private function __construct(string $className, string $methodName, string $modifier, string $argumentsForDeclaration, string $argumentsForCall, array $defaultParameterValues, int $numberOfParameters, Type $returnType, string $reference, bool $static, ?string $deprecation) + { + $this->className = $className; + $this->methodName = $methodName; + $this->modifier = $modifier; + $this->argumentsForDeclaration = $argumentsForDeclaration; + $this->argumentsForCall = $argumentsForCall; + $this->defaultParameterValues = $defaultParameterValues; + $this->numberOfParameters = $numberOfParameters; + $this->returnType = $returnType; + $this->reference = $reference; + $this->static = $static; + $this->deprecation = $deprecation; + } + + /** + * @return non-empty-string + */ + public function methodName(): string + { + return $this->methodName; + } + + /** + * @throws RuntimeException + */ + public function generateCode(): string + { + if ($this->static) { + $templateFile = 'doubled_static_method.tpl'; + } else { + $templateFile = 'doubled_method.tpl'; + } + + $deprecation = $this->deprecation; + $returnResult = ''; + + if (!$this->returnType->isNever() && !$this->returnType->isVoid()) { + $returnResult = <<<'EOT' + + + return $__phpunit_result; +EOT; + } + + if (null !== $this->deprecation) { + $deprecation = "The {$this->className}::{$this->methodName} method is deprecated ({$this->deprecation})."; + $deprecationTemplate = $this->loadTemplate('deprecation.tpl'); + + $deprecationTemplate->setVar( + [ + 'deprecation' => var_export($deprecation, true), + ], + ); + + $deprecation = $deprecationTemplate->render(); + } + + $template = $this->loadTemplate($templateFile); + + $argumentsCount = 0; + + if (str_contains($this->argumentsForCall, '...')) { + $argumentsCount = null; + } elseif ($this->argumentsForCall !== '') { + $argumentsCount = substr_count($this->argumentsForCall, ',') + 1; + } + + $returnDeclaration = ''; + $returnTypeAsString = $this->returnType->asString(); + + if ($returnTypeAsString !== '') { + $returnDeclaration = ': ' . $returnTypeAsString; + } + + $template->setVar( + [ + 'arguments_decl' => $this->argumentsForDeclaration, + 'arguments_call' => $this->argumentsForCall, + 'return_declaration' => $returnDeclaration, + 'return_type' => $this->returnType->asString(), + 'arguments_count' => (string) $argumentsCount, + 'class_name' => $this->className, + 'method_name' => $this->methodName, + 'modifier' => $this->modifier, + 'reference' => $this->reference, + 'deprecation' => $deprecation, + 'return_result' => $returnResult, + ], + ); + + return $template->render(); + } + + public function returnType(): Type + { + return $this->returnType; + } + + /** + * @return array + */ + public function defaultParameterValues(): array + { + return $this->defaultParameterValues; + } + + /** + * @return non-negative-int + */ + public function numberOfParameters(): int + { + return $this->numberOfParameters; + } + + /** + * Returns the parameters of a function or method. + * + * @throws RuntimeException + */ + private static function methodParametersForDeclaration(ReflectionMethod $method): string + { + $parameters = []; + $types = (new ReflectionMapper)->fromParameterTypes($method); + + foreach ($method->getParameters() as $i => $parameter) { + $name = '$' . $parameter->getName(); + + /* Note: PHP extensions may use empty names for reference arguments + * or "..." for methods taking a variable number of arguments. + */ + if ($name === '$' || $name === '$...') { + $name = '$arg' . $i; + } + + $default = ''; + $reference = ''; + $typeDeclaration = ''; + + assert(array_key_exists($i, $types)); + + if (!$types[$i]->type()->isUnknown()) { + $typeDeclaration = $types[$i]->type()->asString() . ' '; + } + + if ($parameter->isPassedByReference()) { + $reference = '&'; + } + + if ($parameter->isVariadic()) { + $name = '...' . $name; + } elseif ($parameter->isDefaultValueAvailable()) { + $default = ' = ' . self::exportDefaultValue($parameter); + } elseif ($parameter->isOptional()) { + $default = ' = null'; + } + + $parameters[] = $typeDeclaration . $reference . $name . $default; + } + + return implode(', ', $parameters); + } + + /** + * Returns the parameters of a function or method. + * + * @throws ReflectionException + */ + private static function methodParametersForCall(ReflectionMethod $method): string + { + $parameters = []; + + foreach ($method->getParameters() as $i => $parameter) { + $name = '$' . $parameter->getName(); + + /* Note: PHP extensions may use empty names for reference arguments + * or "..." for methods taking a variable number of arguments. + */ + if ($name === '$' || $name === '$...') { + $name = '$arg' . $i; + } + + if ($parameter->isVariadic()) { + continue; + } + + if ($parameter->isPassedByReference()) { + $parameters[] = '&' . $name; + } else { + $parameters[] = $name; + } + } + + return implode(', ', $parameters); + } + + /** + * @throws ReflectionException + */ + private static function exportDefaultValue(ReflectionParameter $parameter): string + { + try { + $defaultValue = $parameter->getDefaultValue(); + + if (!is_object($defaultValue)) { + return var_export($defaultValue, true); + } + + $parameterAsString = $parameter->__toString(); + + return explode( + ' = ', + substr( + substr( + $parameterAsString, + strpos($parameterAsString, ' ') + strlen(' '), + ), + 0, + -2, + ), + )[1]; + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new ReflectionException( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + // @codeCoverageIgnoreEnd + } + + /** + * @return array + */ + private static function methodParametersDefaultValues(ReflectionMethod $method): array + { + $result = []; + + foreach ($method->getParameters() as $i => $parameter) { + if (!$parameter->isDefaultValueAvailable()) { + continue; + } + + $result[$i] = $parameter->getDefaultValue(); + } + + return $result; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/DoubledMethodSet.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/DoubledMethodSet.php new file mode 100644 index 000000000..6a2d29f4f --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/DoubledMethodSet.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 PHPUnit\Framework\MockObject\Generator; + +use function array_key_exists; +use function array_values; +use function strtolower; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class DoubledMethodSet +{ + /** + * @var array + */ + private array $methods = []; + + public function addMethods(DoubledMethod ...$methods): void + { + foreach ($methods as $method) { + $this->methods[strtolower($method->methodName())] = $method; + } + } + + /** + * @return list + */ + public function asArray(): array + { + return array_values($this->methods); + } + + public function hasMethod(string $methodName): bool + { + return array_key_exists(strtolower($methodName), $this->methods); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/Exception/ClassIsEnumerationException.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/Exception/ClassIsEnumerationException.php new file mode 100644 index 000000000..e2cde18b6 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/Exception/ClassIsEnumerationException.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Generator; + +use function sprintf; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ClassIsEnumerationException extends \PHPUnit\Framework\Exception implements Exception +{ + public function __construct(string $className) + { + parent::__construct( + sprintf( + 'Class "%s" is an enumeration and cannot be doubled', + $className, + ), + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/ClassIsFinalException.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/Exception/ClassIsFinalException.php similarity index 81% rename from app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/ClassIsFinalException.php rename to app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/Exception/ClassIsFinalException.php index 2bce2d882..f10100b90 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/ClassIsFinalException.php +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/Exception/ClassIsFinalException.php @@ -7,11 +7,13 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework\MockObject; +namespace PHPUnit\Framework\MockObject\Generator; use function sprintf; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class ClassIsFinalException extends \PHPUnit\Framework\Exception implements Exception diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/DuplicateMethodException.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/Exception/DuplicateMethodException.php similarity index 82% rename from app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/DuplicateMethodException.php rename to app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/Exception/DuplicateMethodException.php index f96a04ac9..b18ed4f56 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/DuplicateMethodException.php +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/Exception/DuplicateMethodException.php @@ -7,7 +7,7 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework\MockObject; +namespace PHPUnit\Framework\MockObject\Generator; use function array_diff_assoc; use function array_unique; @@ -15,12 +15,14 @@ use function sprintf; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class DuplicateMethodException extends \PHPUnit\Framework\Exception implements Exception { /** - * @psalm-param list $methods + * @param list $methods */ public function __construct(array $methods) { diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/Exception/Exception.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/Exception/Exception.php new file mode 100644 index 000000000..8d62606fc --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/Exception/Exception.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 PHPUnit\Framework\MockObject\Generator; + +use PHPUnit\Framework\MockObject\Exception as BaseException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This interface is not covered by the backward compatibility promise for PHPUnit + */ +interface Exception extends BaseException +{ +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/InvalidMethodNameException.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/Exception/InvalidMethodNameException.php similarity index 81% rename from app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/InvalidMethodNameException.php rename to app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/Exception/InvalidMethodNameException.php index 0ab74cbb6..32296ce39 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/InvalidMethodNameException.php +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/Exception/InvalidMethodNameException.php @@ -7,11 +7,13 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework\MockObject; +namespace PHPUnit\Framework\MockObject\Generator; use function sprintf; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class InvalidMethodNameException extends \PHPUnit\Framework\Exception implements Exception diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/Exception/MethodNamedMethodException.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/Exception/MethodNamedMethodException.php new file mode 100644 index 000000000..78586fe10 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/Exception/MethodNamedMethodException.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 PHPUnit\Framework\MockObject\Generator; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class MethodNamedMethodException extends \PHPUnit\Framework\Exception implements Exception +{ + public function __construct() + { + parent::__construct('Doubling interfaces (or classes) that have a method named "method" is not supported.'); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/Exception/NameAlreadyInUseException.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/Exception/NameAlreadyInUseException.php new file mode 100644 index 000000000..eec81c065 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/Exception/NameAlreadyInUseException.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Generator; + +use function sprintf; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class NameAlreadyInUseException extends \PHPUnit\Framework\Exception implements Exception +{ + /** + * @param class-string|trait-string $name + */ + public function __construct(string $name) + { + parent::__construct( + sprintf( + 'The name "%s" is already in use', + $name, + ), + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/Exception/ReflectionException.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/Exception/ReflectionException.php new file mode 100644 index 000000000..f4a84f18e --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/Exception/ReflectionException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Generator; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ReflectionException extends \PHPUnit\Framework\Exception implements Exception +{ +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/Exception/RuntimeException.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/Exception/RuntimeException.php new file mode 100644 index 000000000..eed41c37a --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/Exception/RuntimeException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Generator; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class RuntimeException extends \PHPUnit\Framework\Exception implements Exception +{ +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/Exception/UnknownInterfaceException.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/Exception/UnknownInterfaceException.php new file mode 100644 index 000000000..95f03e732 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/Exception/UnknownInterfaceException.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Generator; + +use function sprintf; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class UnknownInterfaceException extends \PHPUnit\Framework\Exception implements Exception +{ + public function __construct(string $interfaceName) + { + parent::__construct( + sprintf( + 'Interface "%s" does not exist', + $interfaceName, + ), + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/UnknownTypeException.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/Exception/UnknownTypeException.php similarity index 81% rename from app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/UnknownTypeException.php rename to app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/Exception/UnknownTypeException.php index c50b69116..cd1e1e072 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Exception/UnknownTypeException.php +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/Exception/UnknownTypeException.php @@ -7,11 +7,13 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Framework\MockObject; +namespace PHPUnit\Framework\MockObject\Generator; use function sprintf; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class UnknownTypeException extends \PHPUnit\Framework\Exception implements Exception diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/Generator.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/Generator.php new file mode 100644 index 000000000..f52ba60ca --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/Generator.php @@ -0,0 +1,880 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Generator; + +use const PHP_EOL; +use const PHP_VERSION; +use function array_merge; +use function array_pop; +use function array_unique; +use function assert; +use function class_exists; +use function count; +use function explode; +use function implode; +use function in_array; +use function interface_exists; +use function is_array; +use function md5; +use function mt_rand; +use function preg_match; +use function serialize; +use function sort; +use function sprintf; +use function substr; +use function trait_exists; +use function version_compare; +use Exception; +use Iterator; +use IteratorAggregate; +use PHPUnit\Framework\MockObject\ConfigurableMethod; +use PHPUnit\Framework\MockObject\DoubledCloneMethod; +use PHPUnit\Framework\MockObject\Method; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\MockObject\MockObjectApi; +use PHPUnit\Framework\MockObject\MockObjectInternal; +use PHPUnit\Framework\MockObject\ProxiedCloneMethod; +use PHPUnit\Framework\MockObject\Stub; +use PHPUnit\Framework\MockObject\StubApi; +use PHPUnit\Framework\MockObject\StubInternal; +use PHPUnit\Framework\MockObject\TestDoubleState; +use PropertyHookType; +use ReflectionClass; +use ReflectionMethod; +use ReflectionObject; +use SebastianBergmann\Type\ReflectionMapper; +use SebastianBergmann\Type\Type; +use Throwable; +use Traversable; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Generator +{ + use TemplateLoader; + + /** + * @var non-empty-array + */ + private const array EXCLUDED_METHOD_NAMES = [ + '__CLASS__' => true, + '__DIR__' => true, + '__FILE__' => true, + '__FUNCTION__' => true, + '__LINE__' => true, + '__METHOD__' => true, + '__NAMESPACE__' => true, + '__TRAIT__' => true, + '__clone' => true, + '__halt_compiler' => true, + ]; + + /** + * @var array + */ + private static array $cache = []; + + /** + * Returns a test double for the specified class. + * + * @param class-string $type + * @param ?list $methods + * @param array $arguments + * + * @throws ClassIsEnumerationException + * @throws ClassIsFinalException + * @throws DuplicateMethodException + * @throws InvalidMethodNameException + * @throws NameAlreadyInUseException + * @throws ReflectionException + * @throws RuntimeException + * @throws UnknownTypeException + */ + public function testDouble(string $type, bool $mockObject, ?array $methods = [], array $arguments = [], string $mockClassName = '', bool $callOriginalConstructor = true, bool $callOriginalClone = true, bool $returnValueGeneration = true): MockObject|Stub + { + if ($type === Traversable::class) { + $type = Iterator::class; + } + + $this->ensureKnownType($type); + $this->ensureValidMethods($methods); + $this->ensureNameForTestDoubleClassIsAvailable($mockClassName); + + $mock = $this->generate( + $type, + $mockObject, + $methods, + $mockClassName, + $callOriginalClone, + ); + + $object = $this->instantiate( + $mock, + $callOriginalConstructor, + $arguments, + $returnValueGeneration, + ); + + assert($object instanceof $type); + + if ($mockObject) { + assert($object instanceof MockObject); + } else { + assert($object instanceof Stub); + } + + return $object; + } + + /** + * @param list $interfaces + * + * @throws RuntimeException + * @throws UnknownInterfaceException + */ + public function testDoubleForInterfaceIntersection(array $interfaces, bool $mockObject, bool $returnValueGeneration = true): MockObject|Stub + { + if (count($interfaces) < 2) { + throw new RuntimeException('At least two interfaces must be specified'); + } + + foreach ($interfaces as $interface) { + if (!interface_exists($interface)) { + throw new UnknownInterfaceException($interface); + } + } + + sort($interfaces); + + $methods = []; + + foreach ($interfaces as $interface) { + $methods = array_merge($methods, $this->namesOfMethodsIn($interface)); + } + + if (count(array_unique($methods)) < count($methods)) { + throw new RuntimeException('Interfaces must not declare the same method'); + } + + $unqualifiedNames = []; + + foreach ($interfaces as $interface) { + $parts = explode('\\', $interface); + $unqualifiedNames[] = array_pop($parts); + } + + sort($unqualifiedNames); + + do { + $intersectionName = sprintf( + 'Intersection_%s_%s', + implode('_', $unqualifiedNames), + substr(md5((string) mt_rand()), 0, 8), + ); + } while (interface_exists($intersectionName, false)); + + $template = $this->loadTemplate('intersection.tpl'); + + $template->setVar( + [ + 'intersection' => $intersectionName, + 'interfaces' => implode(', ', $interfaces), + ], + ); + + eval($template->render()); + + assert(interface_exists($intersectionName)); + + return $this->testDouble( + $intersectionName, + $mockObject, + returnValueGeneration: $returnValueGeneration, + ); + } + + /** + * @param class-string $type + * @param ?list $methods + * + * @throws ClassIsEnumerationException + * @throws ClassIsFinalException + * @throws ReflectionException + * @throws RuntimeException + * + * @todo This method is only public because it is used to test generated code in PHPT tests + * + * @see https://github.com/sebastianbergmann/phpunit/issues/5476 + */ + public function generate(string $type, bool $mockObject, ?array $methods = null, string $mockClassName = '', bool $callOriginalClone = true): DoubledClass + { + if ($mockClassName !== '') { + return $this->generateCodeForTestDoubleClass( + $type, + $mockObject, + $methods, + $mockClassName, + $callOriginalClone, + ); + } + + $key = md5( + $type . + ($mockObject ? 'MockObject' : 'TestStub') . + serialize($methods) . + serialize($callOriginalClone), + ); + + if (!isset(self::$cache[$key])) { + self::$cache[$key] = $this->generateCodeForTestDoubleClass( + $type, + $mockObject, + $methods, + $mockClassName, + $callOriginalClone, + ); + } + + return self::$cache[$key]; + } + + /** + * @param class-string $className + * + * @throws ReflectionException + * + * @return list + */ + private function mockClassMethods(string $className): array + { + $class = $this->reflectClass($className); + $methods = []; + + foreach ($class->getMethods() as $method) { + if (($method->isPublic() || $method->isAbstract()) && $this->canMethodBeDoubled($method)) { + $methods[] = DoubledMethod::fromReflection($method); + } + } + + return $methods; + } + + /** + * @param class-string $interfaceName + * + * @throws ReflectionException + * + * @return list + */ + private function userDefinedInterfaceMethods(string $interfaceName): array + { + $interface = $this->reflectClass($interfaceName); + $methods = []; + + foreach ($interface->getMethods() as $method) { + if (!$method->isUserDefined()) { + continue; + } + + $methods[] = $method; + } + + return $methods; + } + + /** + * @param array $arguments + * + * @throws ReflectionException + * @throws RuntimeException + */ + private function instantiate(DoubledClass $mockClass, bool $callOriginalConstructor = false, array $arguments = [], bool $returnValueGeneration = true): object + { + $className = $mockClass->generate(); + + try { + $object = (new ReflectionClass($className))->newInstanceWithoutConstructor(); + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new ReflectionException( + $e->getMessage(), + $e->getCode(), + $e, + ); + // @codeCoverageIgnoreEnd + } + + $reflector = new ReflectionObject($object); + + /** + * @noinspection PhpUnhandledExceptionInspection + */ + $reflector->getProperty('__phpunit_state')->setValue( + $object, + new TestDoubleState($mockClass->configurableMethods(), $returnValueGeneration), + ); + + if ($callOriginalConstructor && $reflector->getConstructor() !== null) { + try { + $reflector->getConstructor()->invokeArgs($object, $arguments); + // @codeCoverageIgnoreStart + } catch (\ReflectionException $e) { + throw new ReflectionException( + $e->getMessage(), + $e->getCode(), + $e, + ); + // @codeCoverageIgnoreEnd + } + } + + return $object; + } + + /** + * @param class-string $type + * @param ?list $explicitMethods + * + * @throws ClassIsEnumerationException + * @throws ClassIsFinalException + * @throws MethodNamedMethodException + * @throws ReflectionException + * @throws RuntimeException + */ + private function generateCodeForTestDoubleClass(string $type, bool $mockObject, ?array $explicitMethods, string $mockClassName, bool $callOriginalClone): DoubledClass + { + $classTemplate = $this->loadTemplate('test_double_class.tpl'); + $additionalInterfaces = []; + $doubledCloneMethod = false; + $proxiedCloneMethod = false; + $isClass = false; + $isReadonly = false; + $isInterface = false; + $mockMethods = new DoubledMethodSet; + $testDoubleClassPrefix = $mockObject ? 'MockObject_' : 'TestStub_'; + + $_mockClassName = $this->generateClassName( + $type, + $mockClassName, + $testDoubleClassPrefix, + ); + + if (class_exists($_mockClassName['fullClassName'])) { + $isClass = true; + } elseif (interface_exists($_mockClassName['fullClassName'])) { + $isInterface = true; + } + + $class = $this->reflectClass($_mockClassName['fullClassName']); + + if ($class->isEnum()) { + throw new ClassIsEnumerationException($_mockClassName['fullClassName']); + } + + if ($class->isFinal()) { + throw new ClassIsFinalException($_mockClassName['fullClassName']); + } + + if ($class->isReadOnly()) { + $isReadonly = true; + } + + // @see https://github.com/sebastianbergmann/phpunit/issues/2995 + if ($isInterface && $class->implementsInterface(Throwable::class)) { + $actualClassName = Exception::class; + $additionalInterfaces[] = $class->getName(); + $isInterface = false; + $class = $this->reflectClass($actualClassName); + + foreach ($this->userDefinedInterfaceMethods($_mockClassName['fullClassName']) as $method) { + $methodName = $method->getName(); + + if ($class->hasMethod($methodName)) { + $classMethod = $class->getMethod($methodName); + + if (!$this->canMethodBeDoubled($classMethod)) { + continue; + } + } + + $mockMethods->addMethods( + DoubledMethod::fromReflection($method), + ); + } + + $_mockClassName = $this->generateClassName( + $actualClassName, + $_mockClassName['className'], + $testDoubleClassPrefix, + ); + } + + // @see https://github.com/sebastianbergmann/phpunit-mock-objects/issues/103 + if ($isInterface && $class->implementsInterface(Traversable::class) && + !$class->implementsInterface(Iterator::class) && + !$class->implementsInterface(IteratorAggregate::class)) { + $additionalInterfaces[] = Iterator::class; + + $mockMethods->addMethods( + ...$this->mockClassMethods(Iterator::class), + ); + } + + if ($class->hasMethod('__clone')) { + $cloneMethod = $class->getMethod('__clone'); + + if (!$cloneMethod->isFinal()) { + if ($callOriginalClone && !$isInterface) { + $proxiedCloneMethod = true; + } else { + $doubledCloneMethod = true; + } + } + } else { + $doubledCloneMethod = true; + } + + if ($isClass && $explicitMethods === []) { + $mockMethods->addMethods( + ...$this->mockClassMethods($_mockClassName['fullClassName']), + ); + } + + if ($isInterface && ($explicitMethods === [] || $explicitMethods === null)) { + $mockMethods->addMethods( + ...$this->interfaceMethods($_mockClassName['fullClassName']), + ); + } + + if (is_array($explicitMethods)) { + foreach ($explicitMethods as $methodName) { + if ($class->hasMethod($methodName)) { + $method = $class->getMethod($methodName); + + if ($this->canMethodBeDoubled($method)) { + $mockMethods->addMethods( + DoubledMethod::fromReflection($method), + ); + } + } else { + $mockMethods->addMethods( + DoubledMethod::fromName( + $_mockClassName['fullClassName'], + $methodName, + ), + ); + } + } + } + + $propertiesWithHooks = $this->properties($class); + $configurableMethods = $this->configurableMethods($mockMethods, $propertiesWithHooks); + + $mockedMethods = ''; + + foreach ($mockMethods->asArray() as $mockMethod) { + $mockedMethods .= $mockMethod->generateCode(); + } + + /** @var trait-string[] $traits */ + $traits = [StubApi::class]; + + if ($mockObject) { + $traits[] = MockObjectApi::class; + } + + if ($mockMethods->hasMethod('method') || $class->hasMethod('method')) { + throw new MethodNamedMethodException; + } + + $traits[] = Method::class; + + if ($doubledCloneMethod) { + $traits[] = DoubledCloneMethod::class; + } elseif ($proxiedCloneMethod) { + $traits[] = ProxiedCloneMethod::class; + } + + $useStatements = ''; + + foreach ($traits as $trait) { + $useStatements .= sprintf( + ' use %s;' . PHP_EOL, + $trait, + ); + } + + unset($traits); + + $classTemplate->setVar( + [ + 'class_declaration' => $this->generateTestDoubleClassDeclaration( + $mockObject, + $_mockClassName, + $isInterface, + $additionalInterfaces, + $isReadonly, + ), + 'use_statements' => $useStatements, + 'mock_class_name' => $_mockClassName['className'], + 'methods' => $mockedMethods, + 'property_hooks' => (new HookedPropertyGenerator)->generate( + $_mockClassName['className'], + $propertiesWithHooks, + ), + ], + ); + + return new DoubledClass( + $classTemplate->render(), + $_mockClassName['className'], + $configurableMethods, + ); + } + + /** + * @param class-string $type + * + * @return array{className: class-string, originalClassName: class-string, fullClassName: class-string, namespaceName: string} + */ + private function generateClassName(string $type, string $className, string $prefix): array + { + if ($type[0] === '\\') { + $type = substr($type, 1); + } + + $classNameParts = explode('\\', $type); + + if (count($classNameParts) > 1) { + $type = array_pop($classNameParts); + $namespaceName = implode('\\', $classNameParts); + $fullClassName = $namespaceName . '\\' . $type; + } else { + $namespaceName = ''; + $fullClassName = $type; + } + + if ($className === '') { + do { + $className = $prefix . $type . '_' . + substr(md5((string) mt_rand()), 0, 8); + } while (class_exists($className, false)); + } + + return [ + 'className' => $className, + 'originalClassName' => $type, + 'fullClassName' => $fullClassName, + 'namespaceName' => $namespaceName, + ]; + } + + /** + * @param array{className: non-empty-string, originalClassName: non-empty-string, fullClassName: non-empty-string, namespaceName: string} $mockClassName + * @param list $additionalInterfaces + */ + private function generateTestDoubleClassDeclaration(bool $mockObject, array $mockClassName, bool $isInterface, array $additionalInterfaces, bool $isReadonly): string + { + if ($mockObject) { + $additionalInterfaces[] = MockObjectInternal::class; + } else { + $additionalInterfaces[] = StubInternal::class; + } + + if ($isReadonly) { + $buffer = 'readonly class '; + } else { + $buffer = 'class '; + } + + $interfaces = implode(', ', $additionalInterfaces); + + if ($isInterface) { + $buffer .= sprintf( + '%s implements %s', + $mockClassName['className'], + $interfaces, + ); + + if (!in_array($mockClassName['originalClassName'], $additionalInterfaces, true)) { + $buffer .= ', '; + + if ($mockClassName['namespaceName'] !== '') { + $buffer .= $mockClassName['namespaceName'] . '\\'; + } + + $buffer .= $mockClassName['originalClassName']; + } + } else { + $buffer .= sprintf( + '%s extends %s%s implements %s', + $mockClassName['className'], + $mockClassName['namespaceName'] !== '' ? $mockClassName['namespaceName'] . '\\' : '', + $mockClassName['originalClassName'], + $interfaces, + ); + } + + return $buffer; + } + + private function canMethodBeDoubled(ReflectionMethod $method): bool + { + if ($method->isConstructor()) { + return false; + } + + if ($method->isDestructor()) { + return false; + } + + if ($method->isFinal()) { + return false; + } + + if ($method->isPrivate()) { + return false; + } + + return !$this->isMethodNameExcluded($method->getName()); + } + + private function isMethodNameExcluded(string $name): bool + { + return isset(self::EXCLUDED_METHOD_NAMES[$name]); + } + + /** + * @throws UnknownTypeException + */ + private function ensureKnownType(string $type): void + { + if (!class_exists($type) && !interface_exists($type)) { + throw new UnknownTypeException($type); + } + } + + /** + * @param ?list $methods + * + * @throws DuplicateMethodException + * @throws InvalidMethodNameException + */ + private function ensureValidMethods(?array $methods): void + { + if ($methods === null) { + return; + } + + foreach ($methods as $method) { + if (!preg_match('~[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*~', (string) $method)) { + throw new InvalidMethodNameException((string) $method); + } + } + + if ($methods !== array_unique($methods)) { + throw new DuplicateMethodException($methods); + } + } + + /** + * @throws NameAlreadyInUseException + * @throws ReflectionException + */ + private function ensureNameForTestDoubleClassIsAvailable(string $className): void + { + if ($className === '') { + return; + } + + if (class_exists($className, false) || + interface_exists($className, false) || + trait_exists($className, false)) { + throw new NameAlreadyInUseException($className); + } + } + + /** + * @param class-string $className + * + * @throws ReflectionException + * + * @phpstan-ignore missingType.generics, throws.unusedType + */ + private function reflectClass(string $className): ReflectionClass + { + try { + $class = new ReflectionClass($className); + + // @codeCoverageIgnoreStart + /** @phpstan-ignore catch.neverThrown */ + } catch (\ReflectionException $e) { + throw new ReflectionException( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + // @codeCoverageIgnoreEnd + + return $class; + } + + /** + * @param class-string $classOrInterfaceName + * + * @throws ReflectionException + * + * @return list + */ + private function namesOfMethodsIn(string $classOrInterfaceName): array + { + $class = $this->reflectClass($classOrInterfaceName); + $methods = []; + + foreach ($class->getMethods() as $method) { + if ($method->isPublic() || $method->isAbstract()) { + $methods[] = $method->getName(); + } + } + + return $methods; + } + + /** + * @param class-string $interfaceName + * + * @throws ReflectionException + * + * @return list + */ + private function interfaceMethods(string $interfaceName): array + { + $class = $this->reflectClass($interfaceName); + $methods = []; + + foreach ($class->getMethods() as $method) { + $methods[] = DoubledMethod::fromReflection($method); + } + + return $methods; + } + + /** + * @param list $propertiesWithHooks + * + * @return list + */ + private function configurableMethods(DoubledMethodSet $methods, array $propertiesWithHooks): array + { + $configurable = []; + + foreach ($methods->asArray() as $method) { + $configurable[] = new ConfigurableMethod( + $method->methodName(), + $method->defaultParameterValues(), + $method->numberOfParameters(), + $method->returnType(), + ); + } + + foreach ($propertiesWithHooks as $property) { + if ($property->hasGetHook()) { + $configurable[] = new ConfigurableMethod( + sprintf( + '$%s::get', + $property->name(), + ), + [], + 0, + $property->type(), + ); + } + + if ($property->hasSetHook()) { + $configurable[] = new ConfigurableMethod( + sprintf( + '$%s::set', + $property->name(), + ), + [], + 1, + Type::fromName('void', false), + ); + } + } + + return $configurable; + } + + /** + * @param ?ReflectionClass $class + * + * @return list + */ + private function properties(?ReflectionClass $class): array + { + if (version_compare('8.4.1', PHP_VERSION, '>')) { + // @codeCoverageIgnoreStart + return []; + // @codeCoverageIgnoreEnd + } + + if ($class === null) { + return []; + } + + $mapper = new ReflectionMapper; + $properties = []; + + foreach ($class->getProperties() as $property) { + if (!$property->isPublic()) { + continue; + } + + if ($property->isFinal()) { + continue; + } + + if (!$property->hasHooks()) { + continue; + } + + $hasGetHook = false; + $hasSetHook = false; + + if ($property->hasHook(PropertyHookType::Get) && + !$property->getHook(PropertyHookType::Get)->isFinal()) { + $hasGetHook = true; + } + + if ($property->hasHook(PropertyHookType::Set) && + !$property->getHook(PropertyHookType::Set)->isFinal()) { + $hasSetHook = true; + } + + if (!$hasGetHook && !$hasSetHook) { + continue; + } + + $properties[] = new HookedProperty( + $property->getName(), + $mapper->fromPropertyType($property), + $hasGetHook, + $hasSetHook, + ); + } + + return $properties; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/HookedProperty.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/HookedProperty.php new file mode 100644 index 000000000..e43d589d7 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/HookedProperty.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Generator; + +use SebastianBergmann\Type\Type; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class HookedProperty +{ + /** + * @var non-empty-string + */ + private string $name; + private Type $type; + private bool $getHook; + private bool $setHook; + + /** + * @param non-empty-string $name + */ + public function __construct(string $name, Type $type, bool $getHook, bool $setHook) + { + $this->name = $name; + $this->type = $type; + $this->getHook = $getHook; + $this->setHook = $setHook; + } + + public function name(): string + { + return $this->name; + } + + public function type(): Type + { + return $this->type; + } + + public function hasGetHook(): bool + { + return $this->getHook; + } + + public function hasSetHook(): bool + { + return $this->setHook; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/HookedPropertyGenerator.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/HookedPropertyGenerator.php new file mode 100644 index 000000000..4fcff6c82 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/HookedPropertyGenerator.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 PHPUnit\Framework\MockObject\Generator; + +use function sprintf; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class HookedPropertyGenerator +{ + /** + * @param class-string $className + * @param list $properties + */ + public function generate(string $className, array $properties): string + { + $code = ''; + + foreach ($properties as $property) { + $code .= sprintf( + <<<'EOT' + + public %s $%s { +EOT, + $property->type()->asString(), + $property->name(), + ); + + if ($property->hasGetHook()) { + $code .= sprintf( + <<<'EOT' + + get { + return $this->__phpunit_getInvocationHandler()->invoke( + new \PHPUnit\Framework\MockObject\Invocation( + '%s', '$%s::get', [], '%s', $this + ) + ); + } + +EOT, + $className, + $property->name(), + $property->type()->asString(), + ); + } + + if ($property->hasSetHook()) { + $code .= sprintf( + <<<'EOT' + + set (%s $value) { + $this->__phpunit_getInvocationHandler()->invoke( + new \PHPUnit\Framework\MockObject\Invocation( + '%s', '$%s::set', [$value], 'void', $this + ) + ); + } + +EOT, + $property->type()->asString(), + $className, + $property->name(), + ); + } + + $code .= <<<'EOT' + } + +EOT; + } + + return $code; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/TemplateLoader.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/TemplateLoader.php new file mode 100644 index 000000000..8106ce59c --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/TemplateLoader.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Generator; + +use SebastianBergmann\Template\Template; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This trait is not covered by the backward compatibility promise for PHPUnit + */ +trait TemplateLoader +{ + /** + * @var array + */ + private static array $templates = []; + + private function loadTemplate(string $template): Template + { + $filename = __DIR__ . '/templates/' . $template; + + if (!isset(self::$templates[$filename])) { + self::$templates[$filename] = new Template($filename); + } + + return self::$templates[$filename]; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/mocked_class.tpl b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/mocked_class.tpl deleted file mode 100644 index 593119fb2..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/mocked_class.tpl +++ /dev/null @@ -1,6 +0,0 @@ -declare(strict_types=1); - -{prologue}{class_declaration} -{ - use \PHPUnit\Framework\MockObject\Api;{method}{clone} -{mocked_methods}}{epilogue} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/mocked_method.tpl b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/mocked_method.tpl deleted file mode 100644 index 114ff8d0d..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/mocked_method.tpl +++ /dev/null @@ -1,22 +0,0 @@ - - {modifier} function {reference}{method_name}({arguments_decl}){return_declaration} - {{deprecation} - $__phpunit_arguments = [{arguments_call}]; - $__phpunit_count = func_num_args(); - - if ($__phpunit_count > {arguments_count}) { - $__phpunit_arguments_tmp = func_get_args(); - - for ($__phpunit_i = {arguments_count}; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { - $__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i]; - } - } - - $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( - new \PHPUnit\Framework\MockObject\Invocation( - '{class_name}', '{method_name}', $__phpunit_arguments, '{return_type}', $this, {clone_arguments} - ) - ); - - return $__phpunit_result; - } diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/mocked_method_never_or_void.tpl b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/mocked_method_never_or_void.tpl deleted file mode 100644 index 390202201..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/mocked_method_never_or_void.tpl +++ /dev/null @@ -1,20 +0,0 @@ - - {modifier} function {reference}{method_name}({arguments_decl}){return_declaration} - {{deprecation} - $__phpunit_arguments = [{arguments_call}]; - $__phpunit_count = func_num_args(); - - if ($__phpunit_count > {arguments_count}) { - $__phpunit_arguments_tmp = func_get_args(); - - for ($__phpunit_i = {arguments_count}; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { - $__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i]; - } - } - - $this->__phpunit_getInvocationHandler()->invoke( - new \PHPUnit\Framework\MockObject\Invocation( - '{class_name}', '{method_name}', $__phpunit_arguments, '{return_type}', $this, {clone_arguments} - ) - ); - } diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/proxied_method.tpl b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/proxied_method.tpl deleted file mode 100644 index 91bef463d..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/proxied_method.tpl +++ /dev/null @@ -1,22 +0,0 @@ - - {modifier} function {reference}{method_name}({arguments_decl}){return_declaration} - { - $__phpunit_arguments = [{arguments_call}]; - $__phpunit_count = func_num_args(); - - if ($__phpunit_count > {arguments_count}) { - $__phpunit_arguments_tmp = func_get_args(); - - for ($__phpunit_i = {arguments_count}; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { - $__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i]; - } - } - - $this->__phpunit_getInvocationHandler()->invoke( - new \PHPUnit\Framework\MockObject\Invocation( - '{class_name}', '{method_name}', $__phpunit_arguments, '{return_type}', $this, {clone_arguments}, true - ) - ); - - return call_user_func_array(array($this->__phpunit_originalObject, "{method_name}"), $__phpunit_arguments); - } diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/proxied_method_never_or_void.tpl b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/proxied_method_never_or_void.tpl deleted file mode 100644 index cce198826..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/proxied_method_never_or_void.tpl +++ /dev/null @@ -1,22 +0,0 @@ - - {modifier} function {reference}{method_name}({arguments_decl}){return_declaration} - { - $__phpunit_arguments = [{arguments_call}]; - $__phpunit_count = func_num_args(); - - if ($__phpunit_count > {arguments_count}) { - $__phpunit_arguments_tmp = func_get_args(); - - for ($__phpunit_i = {arguments_count}; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { - $__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i]; - } - } - - $this->__phpunit_getInvocationHandler()->invoke( - new \PHPUnit\Framework\MockObject\Invocation( - '{class_name}', '{method_name}', $__phpunit_arguments, '{return_type}', $this, {clone_arguments}, true - ) - ); - - call_user_func_array(array($this->__phpunit_originalObject, "{method_name}"), $__phpunit_arguments); - } diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/deprecation.tpl b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/templates/deprecation.tpl similarity index 100% rename from app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/deprecation.tpl rename to app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/templates/deprecation.tpl diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/templates/doubled_method.tpl b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/templates/doubled_method.tpl new file mode 100644 index 000000000..bb6fb761d --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/templates/doubled_method.tpl @@ -0,0 +1,35 @@ + + {modifier} function {reference}{method_name}({arguments_decl}){return_declaration} + {{deprecation} + $__phpunit_definedVariables = get_defined_vars(); + $__phpunit_namedVariadicParameters = []; + + foreach ($__phpunit_definedVariables as $__phpunit_definedVariableName => $__phpunit_definedVariableValue) { + if ((new ReflectionParameter([__CLASS__, __FUNCTION__], $__phpunit_definedVariableName))->isVariadic()) { + foreach ($__phpunit_definedVariableValue as $__phpunit_key => $__phpunit_namedValue) { + if (is_string($__phpunit_key)) { + $__phpunit_namedVariadicParameters[$__phpunit_key] = $__phpunit_namedValue; + } + } + } + } + + $__phpunit_arguments = [{arguments_call}]; + $__phpunit_count = func_num_args(); + + if ({arguments_count} !== null && $__phpunit_count > {arguments_count}) { + $__phpunit_arguments_tmp = func_get_args(); + + for ($__phpunit_i = {arguments_count}; $__phpunit_i < $__phpunit_count; $__phpunit_i++) { + $__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i]; + } + } + + $__phpunit_arguments = array_merge($__phpunit_arguments, $__phpunit_namedVariadicParameters); + + $__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke( + new \PHPUnit\Framework\MockObject\Invocation( + '{class_name}', '{method_name}', $__phpunit_arguments, '{return_type}', $this + ) + );{return_result} + } diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/mocked_static_method.tpl b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/templates/doubled_static_method.tpl similarity index 100% rename from app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/mocked_static_method.tpl rename to app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/templates/doubled_static_method.tpl diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/intersection.tpl b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/templates/intersection.tpl similarity index 100% rename from app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/intersection.tpl rename to app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/templates/intersection.tpl diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/templates/test_double_class.tpl b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/templates/test_double_class.tpl new file mode 100644 index 000000000..5d015e3f9 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/templates/test_double_class.tpl @@ -0,0 +1,5 @@ +declare(strict_types=1); + +{class_declaration} +{ +{use_statements}{property_hooks}{methods}} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/trait_class.tpl b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/trait_class.tpl deleted file mode 100644 index a8fe470fd..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/trait_class.tpl +++ /dev/null @@ -1,6 +0,0 @@ -declare(strict_types=1); - -{prologue}class {class_name} -{ - use {trait_name}; -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/wsdl_class.tpl b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/wsdl_class.tpl deleted file mode 100644 index b3100b414..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/wsdl_class.tpl +++ /dev/null @@ -1,9 +0,0 @@ -declare(strict_types=1); - -{namespace}class {class_name} extends \SoapClient -{ - public function __construct($wsdl, array $options) - { - parent::__construct('{wsdl}', $options); - } -{methods}} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/wsdl_method.tpl b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/wsdl_method.tpl deleted file mode 100644 index bb16e763e..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Generator/wsdl_method.tpl +++ /dev/null @@ -1,4 +0,0 @@ - - public function {method_name}({arguments}) - { - } diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Invocation.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Invocation.php deleted file mode 100644 index ed8c4e92d..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Invocation.php +++ /dev/null @@ -1,301 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -use function array_map; -use function explode; -use function get_class; -use function implode; -use function in_array; -use function interface_exists; -use function is_object; -use function sprintf; -use function strpos; -use function strtolower; -use function substr; -use Doctrine\Instantiator\Instantiator; -use PHPUnit\Framework\SelfDescribing; -use PHPUnit\Util\Cloner; -use SebastianBergmann\Exporter\Exporter; -use stdClass; -use Throwable; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class Invocation implements SelfDescribing -{ - /** - * @var string - */ - private $className; - - /** - * @var string - */ - private $methodName; - - /** - * @var array - */ - private $parameters; - - /** - * @var string - */ - private $returnType; - - /** - * @var bool - */ - private $isReturnTypeNullable = false; - - /** - * @var bool - */ - private $proxiedCall; - - /** - * @var object - */ - private $object; - - public function __construct(string $className, string $methodName, array $parameters, string $returnType, object $object, bool $cloneObjects = false, bool $proxiedCall = false) - { - $this->className = $className; - $this->methodName = $methodName; - $this->parameters = $parameters; - $this->object = $object; - $this->proxiedCall = $proxiedCall; - - if (strtolower($methodName) === '__tostring') { - $returnType = 'string'; - } - - if (strpos($returnType, '?') === 0) { - $returnType = substr($returnType, 1); - $this->isReturnTypeNullable = true; - } - - $this->returnType = $returnType; - - if (!$cloneObjects) { - return; - } - - foreach ($this->parameters as $key => $value) { - if (is_object($value)) { - $this->parameters[$key] = Cloner::clone($value); - } - } - } - - public function getClassName(): string - { - return $this->className; - } - - public function getMethodName(): string - { - return $this->methodName; - } - - public function getParameters(): array - { - return $this->parameters; - } - - /** - * @throws RuntimeException - * - * @return mixed Mocked return value - */ - public function generateReturnValue() - { - if ($this->isReturnTypeNullable || $this->proxiedCall) { - return null; - } - - $intersection = false; - $union = false; - $unionContainsIntersections = false; - - if (strpos($this->returnType, '|') !== false) { - $types = explode('|', $this->returnType); - $union = true; - - if (strpos($this->returnType, '(') !== false) { - $unionContainsIntersections = true; - } - } elseif (strpos($this->returnType, '&') !== false) { - $types = explode('&', $this->returnType); - $intersection = true; - } else { - $types = [$this->returnType]; - } - - $types = array_map('strtolower', $types); - - if (!$intersection && !$unionContainsIntersections) { - if (in_array('', $types, true) || - in_array('null', $types, true) || - in_array('mixed', $types, true) || - in_array('void', $types, true)) { - return null; - } - - if (in_array('true', $types, true)) { - return true; - } - - if (in_array('false', $types, true) || - in_array('bool', $types, true)) { - return false; - } - - if (in_array('float', $types, true)) { - return 0.0; - } - - if (in_array('int', $types, true)) { - return 0; - } - - if (in_array('string', $types, true)) { - return ''; - } - - if (in_array('array', $types, true)) { - return []; - } - - if (in_array('static', $types, true)) { - try { - return (new Instantiator)->instantiate(get_class($this->object)); - } catch (Throwable $t) { - throw new RuntimeException( - $t->getMessage(), - (int) $t->getCode(), - $t, - ); - } - } - - if (in_array('object', $types, true)) { - return new stdClass; - } - - if (in_array('callable', $types, true) || - in_array('closure', $types, true)) { - return static function (): void - { - }; - } - - if (in_array('traversable', $types, true) || - in_array('generator', $types, true) || - in_array('iterable', $types, true)) { - $generator = static function (): \Generator - { - yield from []; - }; - - return $generator(); - } - - if (!$union) { - try { - return (new Generator)->getMock($this->returnType, [], [], '', false); - } catch (Throwable $t) { - if ($t instanceof Exception) { - throw $t; - } - - throw new RuntimeException( - $t->getMessage(), - (int) $t->getCode(), - $t, - ); - } - } - } - - if ($intersection && $this->onlyInterfaces($types)) { - try { - return (new Generator)->getMockForInterfaces($types); - } catch (Throwable $t) { - throw new RuntimeException( - sprintf( - 'Return value for %s::%s() cannot be generated: %s', - $this->className, - $this->methodName, - $t->getMessage(), - ), - (int) $t->getCode(), - ); - } - } - - $reason = ''; - - if ($union) { - $reason = ' because the declared return type is a union'; - } elseif ($intersection) { - $reason = ' because the declared return type is an intersection'; - } - - throw new RuntimeException( - sprintf( - 'Return value for %s::%s() cannot be generated%s, please configure a return value for this method', - $this->className, - $this->methodName, - $reason, - ), - ); - } - - public function toString(): string - { - $exporter = new Exporter; - - return sprintf( - '%s::%s(%s)%s', - $this->className, - $this->methodName, - implode( - ', ', - array_map( - [$exporter, 'shortenedExport'], - $this->parameters, - ), - ), - $this->returnType ? sprintf(': %s', $this->returnType) : '', - ); - } - - public function getObject(): object - { - return $this->object; - } - - /** - * @psalm-param non-empty-list $types - */ - private function onlyInterfaces(array $types): bool - { - foreach ($types as $type) { - if (!interface_exists($type)) { - return false; - } - } - - return true; - } -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/InvocationHandler.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/InvocationHandler.php deleted file mode 100644 index f8ee16e0c..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/InvocationHandler.php +++ /dev/null @@ -1,186 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -use function strtolower; -use Exception; -use PHPUnit\Framework\MockObject\Builder\InvocationMocker; -use PHPUnit\Framework\MockObject\Rule\InvocationOrder; -use Throwable; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class InvocationHandler -{ - /** - * @var Matcher[] - */ - private $matchers = []; - - /** - * @var Matcher[] - */ - private $matcherMap = []; - - /** - * @var ConfigurableMethod[] - */ - private $configurableMethods; - - /** - * @var bool - */ - private $returnValueGeneration; - - /** - * @var Throwable - */ - private $deferredError; - - public function __construct(array $configurableMethods, bool $returnValueGeneration) - { - $this->configurableMethods = $configurableMethods; - $this->returnValueGeneration = $returnValueGeneration; - } - - public function hasMatchers(): bool - { - foreach ($this->matchers as $matcher) { - if ($matcher->hasMatchers()) { - return true; - } - } - - return false; - } - - /** - * Looks up the match builder with identification $id and returns it. - * - * @param string $id The identification of the match builder - */ - public function lookupMatcher(string $id): ?Matcher - { - if (isset($this->matcherMap[$id])) { - return $this->matcherMap[$id]; - } - - return null; - } - - /** - * Registers a matcher with the identification $id. The matcher can later be - * looked up using lookupMatcher() to figure out if it has been invoked. - * - * @param string $id The identification of the matcher - * @param Matcher $matcher The builder which is being registered - * - * @throws MatcherAlreadyRegisteredException - */ - public function registerMatcher(string $id, Matcher $matcher): void - { - if (isset($this->matcherMap[$id])) { - throw new MatcherAlreadyRegisteredException($id); - } - - $this->matcherMap[$id] = $matcher; - } - - public function expects(InvocationOrder $rule): InvocationMocker - { - $matcher = new Matcher($rule); - $this->addMatcher($matcher); - - return new InvocationMocker( - $this, - $matcher, - ...$this->configurableMethods, - ); - } - - /** - * @throws Exception - * @throws RuntimeException - */ - public function invoke(Invocation $invocation) - { - $exception = null; - $hasReturnValue = false; - $returnValue = null; - - foreach ($this->matchers as $match) { - try { - if ($match->matches($invocation)) { - $value = $match->invoked($invocation); - - if (!$hasReturnValue) { - $returnValue = $value; - $hasReturnValue = true; - } - } - } catch (Exception $e) { - $exception = $e; - } - } - - if ($exception !== null) { - throw $exception; - } - - if ($hasReturnValue) { - return $returnValue; - } - - if (!$this->returnValueGeneration) { - $exception = new ReturnValueNotConfiguredException($invocation); - - if (strtolower($invocation->getMethodName()) === '__tostring') { - $this->deferredError = $exception; - - return ''; - } - - throw $exception; - } - - return $invocation->generateReturnValue(); - } - - public function matches(Invocation $invocation): bool - { - foreach ($this->matchers as $matcher) { - if (!$matcher->matches($invocation)) { - return false; - } - } - - return true; - } - - /** - * @throws Throwable - */ - public function verify(): void - { - foreach ($this->matchers as $matcher) { - $matcher->verify(); - } - - if ($this->deferredError) { - throw $this->deferredError; - } - } - - private function addMatcher(Matcher $matcher): void - { - $this->matchers[] = $matcher; - } -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Matcher.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Matcher.php deleted file mode 100644 index cc8be0585..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Matcher.php +++ /dev/null @@ -1,275 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -use function assert; -use function implode; -use function sprintf; -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Framework\MockObject\Rule\AnyInvokedCount; -use PHPUnit\Framework\MockObject\Rule\AnyParameters; -use PHPUnit\Framework\MockObject\Rule\InvocationOrder; -use PHPUnit\Framework\MockObject\Rule\InvokedAtMostCount; -use PHPUnit\Framework\MockObject\Rule\InvokedCount; -use PHPUnit\Framework\MockObject\Rule\MethodName; -use PHPUnit\Framework\MockObject\Rule\ParametersRule; -use PHPUnit\Framework\MockObject\Stub\Stub; -use PHPUnit\Framework\TestFailure; -use SebastianBergmann\RecursionContext\InvalidArgumentException; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class Matcher -{ - /** - * @var InvocationOrder - */ - private $invocationRule; - - /** - * @var mixed - */ - private $afterMatchBuilderId; - - /** - * @var bool - */ - private $afterMatchBuilderIsInvoked = false; - - /** - * @var MethodName - */ - private $methodNameRule; - - /** - * @var ParametersRule - */ - private $parametersRule; - - /** - * @var Stub - */ - private $stub; - - public function __construct(InvocationOrder $rule) - { - $this->invocationRule = $rule; - } - - public function hasMatchers(): bool - { - return !$this->invocationRule instanceof AnyInvokedCount; - } - - public function hasMethodNameRule(): bool - { - return $this->methodNameRule !== null; - } - - public function getMethodNameRule(): MethodName - { - return $this->methodNameRule; - } - - public function setMethodNameRule(MethodName $rule): void - { - $this->methodNameRule = $rule; - } - - public function hasParametersRule(): bool - { - return $this->parametersRule !== null; - } - - public function setParametersRule(ParametersRule $rule): void - { - $this->parametersRule = $rule; - } - - public function setStub(Stub $stub): void - { - $this->stub = $stub; - } - - public function setAfterMatchBuilderId(string $id): void - { - $this->afterMatchBuilderId = $id; - } - - /** - * @throws ExpectationFailedException - * @throws MatchBuilderNotFoundException - * @throws MethodNameNotConfiguredException - * @throws RuntimeException - */ - public function invoked(Invocation $invocation) - { - if ($this->methodNameRule === null) { - throw new MethodNameNotConfiguredException; - } - - if ($this->afterMatchBuilderId !== null) { - $matcher = $invocation->getObject() - ->__phpunit_getInvocationHandler() - ->lookupMatcher($this->afterMatchBuilderId); - - if (!$matcher) { - throw new MatchBuilderNotFoundException($this->afterMatchBuilderId); - } - - assert($matcher instanceof self); - - if ($matcher->invocationRule->hasBeenInvoked()) { - $this->afterMatchBuilderIsInvoked = true; - } - } - - $this->invocationRule->invoked($invocation); - - try { - if ($this->parametersRule !== null) { - $this->parametersRule->apply($invocation); - } - } catch (ExpectationFailedException $e) { - throw new ExpectationFailedException( - sprintf( - "Expectation failed for %s when %s\n%s", - $this->methodNameRule->toString(), - $this->invocationRule->toString(), - $e->getMessage(), - ), - $e->getComparisonFailure(), - ); - } - - if ($this->stub) { - return $this->stub->invoke($invocation); - } - - return $invocation->generateReturnValue(); - } - - /** - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * @throws MatchBuilderNotFoundException - * @throws MethodNameNotConfiguredException - * @throws RuntimeException - */ - public function matches(Invocation $invocation): bool - { - if ($this->afterMatchBuilderId !== null) { - $matcher = $invocation->getObject() - ->__phpunit_getInvocationHandler() - ->lookupMatcher($this->afterMatchBuilderId); - - if (!$matcher) { - throw new MatchBuilderNotFoundException($this->afterMatchBuilderId); - } - - assert($matcher instanceof self); - - if (!$matcher->invocationRule->hasBeenInvoked()) { - return false; - } - } - - if ($this->methodNameRule === null) { - throw new MethodNameNotConfiguredException; - } - - if (!$this->invocationRule->matches($invocation)) { - return false; - } - - try { - if (!$this->methodNameRule->matches($invocation)) { - return false; - } - } catch (ExpectationFailedException $e) { - throw new ExpectationFailedException( - sprintf( - "Expectation failed for %s when %s\n%s", - $this->methodNameRule->toString(), - $this->invocationRule->toString(), - $e->getMessage(), - ), - $e->getComparisonFailure(), - ); - } - - return true; - } - - /** - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * @throws MethodNameNotConfiguredException - */ - public function verify(): void - { - if ($this->methodNameRule === null) { - throw new MethodNameNotConfiguredException; - } - - try { - $this->invocationRule->verify(); - - if ($this->parametersRule === null) { - $this->parametersRule = new AnyParameters; - } - - $invocationIsAny = $this->invocationRule instanceof AnyInvokedCount; - $invocationIsNever = $this->invocationRule instanceof InvokedCount && $this->invocationRule->isNever(); - $invocationIsAtMost = $this->invocationRule instanceof InvokedAtMostCount; - - if (!$invocationIsAny && !$invocationIsNever && !$invocationIsAtMost) { - $this->parametersRule->verify(); - } - } catch (ExpectationFailedException $e) { - throw new ExpectationFailedException( - sprintf( - "Expectation failed for %s when %s.\n%s", - $this->methodNameRule->toString(), - $this->invocationRule->toString(), - TestFailure::exceptionToString($e), - ), - ); - } - } - - public function toString(): string - { - $list = []; - - if ($this->invocationRule !== null) { - $list[] = $this->invocationRule->toString(); - } - - if ($this->methodNameRule !== null) { - $list[] = 'where ' . $this->methodNameRule->toString(); - } - - if ($this->parametersRule !== null) { - $list[] = 'and ' . $this->parametersRule->toString(); - } - - if ($this->afterMatchBuilderId !== null) { - $list[] = 'after ' . $this->afterMatchBuilderId; - } - - if ($this->stub !== null) { - $list[] = 'will ' . $this->stub->toString(); - } - - return implode(' ', $list); - } -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/MethodNameConstraint.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/MethodNameConstraint.php deleted file mode 100644 index e5c955d3d..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/MethodNameConstraint.php +++ /dev/null @@ -1,48 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -use function is_string; -use function sprintf; -use function strtolower; -use PHPUnit\Framework\Constraint\Constraint; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class MethodNameConstraint extends Constraint -{ - /** - * @var string - */ - private $methodName; - - public function __construct(string $methodName) - { - $this->methodName = $methodName; - } - - public function toString(): string - { - return sprintf( - 'is "%s"', - $this->methodName, - ); - } - - protected function matches($other): bool - { - if (!is_string($other)) { - return false; - } - - return strtolower($this->methodName) === strtolower($other); - } -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/MockBuilder.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/MockBuilder.php index 487ea16ad..02620db59 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/MockBuilder.php +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/MockBuilder.php @@ -9,101 +9,59 @@ */ namespace PHPUnit\Framework\MockObject; -use function array_diff; use function array_merge; -use PHPUnit\Framework\Exception; +use function assert; use PHPUnit\Framework\InvalidArgumentException; +use PHPUnit\Framework\MockObject\Generator\ClassIsEnumerationException; +use PHPUnit\Framework\MockObject\Generator\ClassIsFinalException; +use PHPUnit\Framework\MockObject\Generator\DuplicateMethodException; +use PHPUnit\Framework\MockObject\Generator\Generator; +use PHPUnit\Framework\MockObject\Generator\InvalidMethodNameException; +use PHPUnit\Framework\MockObject\Generator\NameAlreadyInUseException; +use PHPUnit\Framework\MockObject\Generator\ReflectionException; +use PHPUnit\Framework\MockObject\Generator\RuntimeException; +use PHPUnit\Framework\MockObject\Generator\UnknownTypeException; use PHPUnit\Framework\TestCase; use ReflectionClass; /** - * @psalm-template MockedType + * @template MockedType * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ final class MockBuilder { - /** - * @var TestCase - */ - private $testCase; - - /** - * @var string - */ - private $type; - - /** - * @var null|string[] - */ - private $methods = []; - - /** - * @var bool - */ - private $emptyMethodsArray = false; - - /** - * @var string - */ - private $mockClassName = ''; - - /** - * @var array - */ - private $constructorArgs = []; - - /** - * @var bool - */ - private $originalConstructor = true; - - /** - * @var bool - */ - private $originalClone = true; - - /** - * @var bool - */ - private $autoload = true; + private readonly TestCase $testCase; /** - * @var bool + * @var class-string|trait-string */ - private $cloneArguments = false; + private readonly string $type; /** - * @var bool + * @var list */ - private $callOriginalMethods = false; + private array $methods = []; + private bool $emptyMethodsArray = false; /** - * @var ?object + * @var ?class-string */ - private $proxyTarget; + private ?string $mockClassName = null; /** - * @var bool + * @var array */ - private $allowMockingUnknownTypes = true; + private array $constructorArgs = []; + private bool $originalConstructor = true; + private bool $originalClone = true; + private bool $returnValueGeneration = true; + private readonly Generator $generator; /** - * @var bool + * @param class-string|trait-string $type */ - private $returnValueGeneration = true; - - /** - * @var Generator - */ - private $generator; - - /** - * @param string|string[] $type - * - * @psalm-param class-string|string|string[] $type - */ - public function __construct(TestCase $testCase, $type) + public function __construct(TestCase $testCase, string $type) { $this->testCase = $testCase; $this->type = $type; @@ -113,117 +71,43 @@ public function __construct(TestCase $testCase, $type) /** * Creates a mock object using a fluent interface. * - * @throws ClassAlreadyExistsException + * @throws ClassIsEnumerationException * @throws ClassIsFinalException - * @throws ClassIsReadonlyException * @throws DuplicateMethodException * @throws InvalidArgumentException * @throws InvalidMethodNameException - * @throws OriginalConstructorInvocationRequiredException + * @throws NameAlreadyInUseException * @throws ReflectionException * @throws RuntimeException * @throws UnknownTypeException * - * @psalm-return MockObject&MockedType + * @return MockedType&MockObject */ public function getMock(): MockObject { - $object = $this->generator->getMock( + $object = $this->generator->testDouble( $this->type, + true, !$this->emptyMethodsArray ? $this->methods : null, $this->constructorArgs, - $this->mockClassName, + $this->mockClassName ?? '', $this->originalConstructor, $this->originalClone, - $this->autoload, - $this->cloneArguments, - $this->callOriginalMethods, - $this->proxyTarget, - $this->allowMockingUnknownTypes, $this->returnValueGeneration, ); - $this->testCase->registerMockObject($object); - - return $object; - } - - /** - * Creates a mock object for an abstract class using a fluent interface. - * - * @psalm-return MockObject&MockedType - * - * @throws Exception - * @throws ReflectionException - * @throws RuntimeException - */ - public function getMockForAbstractClass(): MockObject - { - $object = $this->generator->getMockForAbstractClass( - $this->type, - $this->constructorArgs, - $this->mockClassName, - $this->originalConstructor, - $this->originalClone, - $this->autoload, - $this->methods, - $this->cloneArguments, - ); - - $this->testCase->registerMockObject($object); - - return $object; - } - - /** - * Creates a mock object for a trait using a fluent interface. - * - * @psalm-return MockObject&MockedType - * - * @throws Exception - * @throws ReflectionException - * @throws RuntimeException - */ - public function getMockForTrait(): MockObject - { - $object = $this->generator->getMockForTrait( - $this->type, - $this->constructorArgs, - $this->mockClassName, - $this->originalConstructor, - $this->originalClone, - $this->autoload, - $this->methods, - $this->cloneArguments, - ); + assert($object instanceof $this->type); + assert($object instanceof MockObject); $this->testCase->registerMockObject($object); return $object; } - /** - * Specifies the subset of methods to mock. Default is to mock none of them. - * - * @deprecated https://github.com/sebastianbergmann/phpunit/pull/3687 - * - * @return $this - */ - public function setMethods(?array $methods = null): self - { - if ($methods === null) { - $this->methods = $methods; - } else { - $this->methods = array_merge($this->methods ?? [], $methods); - } - - return $this; - } - /** * Specifies the subset of methods to mock, requiring each to exist in the class. * - * @param string[] $methods + * @param list $methods * * @throws CannotUseOnlyMethodsException * @throws ReflectionException @@ -232,7 +116,7 @@ public function setMethods(?array $methods = null): self */ public function onlyMethods(array $methods): self { - if (empty($methods)) { + if ($methods === []) { $this->emptyMethodsArray = true; return $this; @@ -240,15 +124,17 @@ public function onlyMethods(array $methods): self try { $reflector = new ReflectionClass($this->type); + // @codeCoverageIgnoreStart + /** @phpstan-ignore catch.neverThrown */ } catch (\ReflectionException $e) { throw new ReflectionException( $e->getMessage(), $e->getCode(), $e, ); + // @codeCoverageIgnoreEnd } - // @codeCoverageIgnoreEnd foreach ($methods as $method) { if (!$reflector->hasMethod($method)) { @@ -256,78 +142,21 @@ public function onlyMethods(array $methods): self } } - $this->methods = array_merge($this->methods ?? [], $methods); + $this->methods = array_merge($this->methods, $methods); return $this; } - /** - * Specifies methods that don't exist in the class which you want to mock. - * - * @param string[] $methods - * - * @throws CannotUseAddMethodsException - * @throws ReflectionException - * @throws RuntimeException - * - * @return $this - */ - public function addMethods(array $methods): self - { - if (empty($methods)) { - $this->emptyMethodsArray = true; - - return $this; - } - - try { - $reflector = new ReflectionClass($this->type); - // @codeCoverageIgnoreStart - } catch (\ReflectionException $e) { - throw new ReflectionException( - $e->getMessage(), - $e->getCode(), - $e, - ); - } - // @codeCoverageIgnoreEnd - - foreach ($methods as $method) { - if ($reflector->hasMethod($method)) { - throw new CannotUseAddMethodsException($this->type, $method); - } - } - - $this->methods = array_merge($this->methods ?? [], $methods); - - return $this; - } - - /** - * Specifies the subset of methods to not mock. Default is to mock all of them. - * - * @deprecated https://github.com/sebastianbergmann/phpunit/pull/3687 - * - * @throws ReflectionException - */ - public function setMethodsExcept(array $methods = []): self - { - return $this->setMethods( - array_diff( - $this->generator->getClassMethods($this->type), - $methods, - ), - ); - } - /** * Specifies the arguments for the constructor. * + * @param array $arguments + * * @return $this */ - public function setConstructorArgs(array $args): self + public function setConstructorArgs(array $arguments): self { - $this->constructorArgs = $args; + $this->constructorArgs = $arguments; return $this; } @@ -335,6 +164,8 @@ public function setConstructorArgs(array $args): self /** * Specifies the name for the mock class. * + * @param class-string $name + * * @return $this */ public function setMockClassName(string $name): self @@ -392,111 +223,6 @@ public function enableOriginalClone(): self return $this; } - /** - * Disables the use of class autoloading while creating the mock object. - * - * @return $this - */ - public function disableAutoload(): self - { - $this->autoload = false; - - return $this; - } - - /** - * Enables the use of class autoloading while creating the mock object. - * - * @return $this - */ - public function enableAutoload(): self - { - $this->autoload = true; - - return $this; - } - - /** - * Disables the cloning of arguments passed to mocked methods. - * - * @return $this - */ - public function disableArgumentCloning(): self - { - $this->cloneArguments = false; - - return $this; - } - - /** - * Enables the cloning of arguments passed to mocked methods. - * - * @return $this - */ - public function enableArgumentCloning(): self - { - $this->cloneArguments = true; - - return $this; - } - - /** - * Enables the invocation of the original methods. - * - * @return $this - */ - public function enableProxyingToOriginalMethods(): self - { - $this->callOriginalMethods = true; - - return $this; - } - - /** - * Disables the invocation of the original methods. - * - * @return $this - */ - public function disableProxyingToOriginalMethods(): self - { - $this->callOriginalMethods = false; - $this->proxyTarget = null; - - return $this; - } - - /** - * Sets the proxy target. - * - * @return $this - */ - public function setProxyTarget(object $object): self - { - $this->proxyTarget = $object; - - return $this; - } - - /** - * @return $this - */ - public function allowMockingUnknownTypes(): self - { - $this->allowMockingUnknownTypes = true; - - return $this; - } - - /** - * @return $this - */ - public function disallowMockingUnknownTypes(): self - { - $this->allowMockingUnknownTypes = false; - - return $this; - } - /** * @return $this */ diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/MockClass.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/MockClass.php deleted file mode 100644 index 8f5c276d5..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/MockClass.php +++ /dev/null @@ -1,69 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -use function call_user_func; -use function class_exists; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class MockClass implements MockType -{ - /** - * @var string - */ - private $classCode; - - /** - * @var class-string - */ - private $mockName; - - /** - * @var ConfigurableMethod[] - */ - private $configurableMethods; - - /** - * @psalm-param class-string $mockName - */ - public function __construct(string $classCode, string $mockName, array $configurableMethods) - { - $this->classCode = $classCode; - $this->mockName = $mockName; - $this->configurableMethods = $configurableMethods; - } - - /** - * @psalm-return class-string - */ - public function generate(): string - { - if (!class_exists($this->mockName, false)) { - eval($this->classCode); - - call_user_func( - [ - $this->mockName, - '__phpunit_initConfigurableMethods', - ], - ...$this->configurableMethods, - ); - } - - return $this->mockName; - } - - public function getClassCode(): string - { - return $this->classCode; - } -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/MockMethod.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/MockMethod.php deleted file mode 100644 index 88462dfe0..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/MockMethod.php +++ /dev/null @@ -1,380 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -use const DIRECTORY_SEPARATOR; -use function explode; -use function implode; -use function is_object; -use function is_string; -use function preg_match; -use function preg_replace; -use function sprintf; -use function strlen; -use function strpos; -use function substr; -use function substr_count; -use function trim; -use function var_export; -use ReflectionMethod; -use ReflectionParameter; -use SebastianBergmann\Template\Exception as TemplateException; -use SebastianBergmann\Template\Template; -use SebastianBergmann\Type\ReflectionMapper; -use SebastianBergmann\Type\Type; -use SebastianBergmann\Type\UnknownType; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class MockMethod -{ - /** - * @var Template[] - */ - private static $templates = []; - - /** - * @var string - */ - private $className; - - /** - * @var string - */ - private $methodName; - - /** - * @var bool - */ - private $cloneArguments; - - /** - * @var string string - */ - private $modifier; - - /** - * @var string - */ - private $argumentsForDeclaration; - - /** - * @var string - */ - private $argumentsForCall; - - /** - * @var Type - */ - private $returnType; - - /** - * @var string - */ - private $reference; - - /** - * @var bool - */ - private $callOriginalMethod; - - /** - * @var bool - */ - private $static; - - /** - * @var ?string - */ - private $deprecation; - - /** - * @throws ReflectionException - * @throws RuntimeException - */ - public static function fromReflection(ReflectionMethod $method, bool $callOriginalMethod, bool $cloneArguments): self - { - if ($method->isPrivate()) { - $modifier = 'private'; - } elseif ($method->isProtected()) { - $modifier = 'protected'; - } else { - $modifier = 'public'; - } - - if ($method->isStatic()) { - $modifier .= ' static'; - } - - if ($method->returnsReference()) { - $reference = '&'; - } else { - $reference = ''; - } - - $docComment = $method->getDocComment(); - - if (is_string($docComment) && - preg_match('#\*[ \t]*+@deprecated[ \t]*+(.*?)\r?+\n[ \t]*+\*(?:[ \t]*+@|/$)#s', $docComment, $deprecation)) { - $deprecation = trim(preg_replace('#[ \t]*\r?\n[ \t]*+\*[ \t]*+#', ' ', $deprecation[1])); - } else { - $deprecation = null; - } - - return new self( - $method->getDeclaringClass()->getName(), - $method->getName(), - $cloneArguments, - $modifier, - self::getMethodParametersForDeclaration($method), - self::getMethodParametersForCall($method), - (new ReflectionMapper)->fromReturnType($method), - $reference, - $callOriginalMethod, - $method->isStatic(), - $deprecation, - ); - } - - public static function fromName(string $fullClassName, string $methodName, bool $cloneArguments): self - { - return new self( - $fullClassName, - $methodName, - $cloneArguments, - 'public', - '', - '', - new UnknownType, - '', - false, - false, - null, - ); - } - - public function __construct(string $className, string $methodName, bool $cloneArguments, string $modifier, string $argumentsForDeclaration, string $argumentsForCall, Type $returnType, string $reference, bool $callOriginalMethod, bool $static, ?string $deprecation) - { - $this->className = $className; - $this->methodName = $methodName; - $this->cloneArguments = $cloneArguments; - $this->modifier = $modifier; - $this->argumentsForDeclaration = $argumentsForDeclaration; - $this->argumentsForCall = $argumentsForCall; - $this->returnType = $returnType; - $this->reference = $reference; - $this->callOriginalMethod = $callOriginalMethod; - $this->static = $static; - $this->deprecation = $deprecation; - } - - public function getName(): string - { - return $this->methodName; - } - - /** - * @throws RuntimeException - */ - public function generateCode(): string - { - if ($this->static) { - $templateFile = 'mocked_static_method.tpl'; - } elseif ($this->returnType->isNever() || $this->returnType->isVoid()) { - $templateFile = sprintf( - '%s_method_never_or_void.tpl', - $this->callOriginalMethod ? 'proxied' : 'mocked', - ); - } else { - $templateFile = sprintf( - '%s_method.tpl', - $this->callOriginalMethod ? 'proxied' : 'mocked', - ); - } - - $deprecation = $this->deprecation; - - if (null !== $this->deprecation) { - $deprecation = "The {$this->className}::{$this->methodName} method is deprecated ({$this->deprecation})."; - $deprecationTemplate = $this->getTemplate('deprecation.tpl'); - - $deprecationTemplate->setVar( - [ - 'deprecation' => var_export($deprecation, true), - ], - ); - - $deprecation = $deprecationTemplate->render(); - } - - $template = $this->getTemplate($templateFile); - - $template->setVar( - [ - 'arguments_decl' => $this->argumentsForDeclaration, - 'arguments_call' => $this->argumentsForCall, - 'return_declaration' => !empty($this->returnType->asString()) ? (': ' . $this->returnType->asString()) : '', - 'return_type' => $this->returnType->asString(), - 'arguments_count' => !empty($this->argumentsForCall) ? substr_count($this->argumentsForCall, ',') + 1 : 0, - 'class_name' => $this->className, - 'method_name' => $this->methodName, - 'modifier' => $this->modifier, - 'reference' => $this->reference, - 'clone_arguments' => $this->cloneArguments ? 'true' : 'false', - 'deprecation' => $deprecation, - ], - ); - - return $template->render(); - } - - public function getReturnType(): Type - { - return $this->returnType; - } - - /** - * @throws RuntimeException - */ - private function getTemplate(string $template): Template - { - $filename = __DIR__ . DIRECTORY_SEPARATOR . 'Generator' . DIRECTORY_SEPARATOR . $template; - - if (!isset(self::$templates[$filename])) { - try { - self::$templates[$filename] = new Template($filename); - } catch (TemplateException $e) { - throw new RuntimeException( - $e->getMessage(), - $e->getCode(), - $e, - ); - } - } - - return self::$templates[$filename]; - } - - /** - * Returns the parameters of a function or method. - * - * @throws RuntimeException - */ - private static function getMethodParametersForDeclaration(ReflectionMethod $method): string - { - $parameters = []; - $types = (new ReflectionMapper)->fromParameterTypes($method); - - foreach ($method->getParameters() as $i => $parameter) { - $name = '$' . $parameter->getName(); - - /* Note: PHP extensions may use empty names for reference arguments - * or "..." for methods taking a variable number of arguments. - */ - if ($name === '$' || $name === '$...') { - $name = '$arg' . $i; - } - - $default = ''; - $reference = ''; - $typeDeclaration = ''; - - if (!$types[$i]->type()->isUnknown()) { - $typeDeclaration = $types[$i]->type()->asString() . ' '; - } - - if ($parameter->isPassedByReference()) { - $reference = '&'; - } - - if ($parameter->isVariadic()) { - $name = '...' . $name; - } elseif ($parameter->isDefaultValueAvailable()) { - $default = ' = ' . self::exportDefaultValue($parameter); - } elseif ($parameter->isOptional()) { - $default = ' = null'; - } - - $parameters[] = $typeDeclaration . $reference . $name . $default; - } - - return implode(', ', $parameters); - } - - /** - * Returns the parameters of a function or method. - * - * @throws ReflectionException - */ - private static function getMethodParametersForCall(ReflectionMethod $method): string - { - $parameters = []; - - foreach ($method->getParameters() as $i => $parameter) { - $name = '$' . $parameter->getName(); - - /* Note: PHP extensions may use empty names for reference arguments - * or "..." for methods taking a variable number of arguments. - */ - if ($name === '$' || $name === '$...') { - $name = '$arg' . $i; - } - - if ($parameter->isVariadic()) { - continue; - } - - if ($parameter->isPassedByReference()) { - $parameters[] = '&' . $name; - } else { - $parameters[] = $name; - } - } - - return implode(', ', $parameters); - } - - /** - * @throws ReflectionException - */ - private static function exportDefaultValue(ReflectionParameter $parameter): string - { - try { - $defaultValue = $parameter->getDefaultValue(); - - if (!is_object($defaultValue)) { - return (string) var_export($defaultValue, true); - } - - $parameterAsString = $parameter->__toString(); - - return (string) explode( - ' = ', - substr( - substr( - $parameterAsString, - strpos($parameterAsString, ' ') + strlen(' '), - ), - 0, - -2, - ), - )[1]; - // @codeCoverageIgnoreStart - } catch (\ReflectionException $e) { - throw new ReflectionException( - $e->getMessage(), - $e->getCode(), - $e, - ); - } - // @codeCoverageIgnoreEnd - } -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/MockMethodSet.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/MockMethodSet.php deleted file mode 100644 index 1c78963c0..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/MockMethodSet.php +++ /dev/null @@ -1,45 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -use function array_key_exists; -use function array_values; -use function strtolower; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class MockMethodSet -{ - /** - * @var MockMethod[] - */ - private $methods = []; - - public function addMethods(MockMethod ...$methods): void - { - foreach ($methods as $method) { - $this->methods[strtolower($method->getName())] = $method; - } - } - - /** - * @return MockMethod[] - */ - public function asArray(): array - { - return array_values($this->methods); - } - - public function hasMethod(string $methodName): bool - { - return array_key_exists(strtolower($methodName), $this->methods); - } -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/MockObject.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/MockObject.php deleted file mode 100644 index 094decf43..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/MockObject.php +++ /dev/null @@ -1,27 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -use PHPUnit\Framework\MockObject\Builder\InvocationMocker as BuilderInvocationMocker; -use PHPUnit\Framework\MockObject\Rule\InvocationOrder; - -/** - * @method BuilderInvocationMocker method($constraint) - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - */ -interface MockObject extends Stub -{ - public function __phpunit_setOriginalObject($originalObject): void; - - public function __phpunit_verify(bool $unsetInvocationMocker = true): void; - - public function expects(InvocationOrder $invocationRule): BuilderInvocationMocker; -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/MockTrait.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/MockTrait.php deleted file mode 100644 index 7c326988f..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/MockTrait.php +++ /dev/null @@ -1,54 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -use function class_exists; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class MockTrait implements MockType -{ - /** - * @var string - */ - private $classCode; - - /** - * @var class-string - */ - private $mockName; - - /** - * @psalm-param class-string $mockName - */ - public function __construct(string $classCode, string $mockName) - { - $this->classCode = $classCode; - $this->mockName = $mockName; - } - - /** - * @psalm-return class-string - */ - public function generate(): string - { - if (!class_exists($this->mockName, false)) { - eval($this->classCode); - } - - return $this->mockName; - } - - public function getClassCode(): string - { - return $this->classCode; - } -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/MockType.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/MockType.php deleted file mode 100644 index 6a03fb51a..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/MockType.php +++ /dev/null @@ -1,21 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -interface MockType -{ - /** - * @psalm-return class-string - */ - public function generate(): string; -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/ConsecutiveParameters.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/ConsecutiveParameters.php deleted file mode 100644 index 9d3ab7203..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/ConsecutiveParameters.php +++ /dev/null @@ -1,134 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Rule; - -use function count; -use function gettype; -use function is_iterable; -use function sprintf; -use PHPUnit\Framework\Constraint\Constraint; -use PHPUnit\Framework\Constraint\IsEqual; -use PHPUnit\Framework\Exception; -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Framework\InvalidParameterGroupException; -use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; -use SebastianBergmann\RecursionContext\InvalidArgumentException; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @deprecated - */ -final class ConsecutiveParameters implements ParametersRule -{ - /** - * @var array - */ - private $parameterGroups = []; - - /** - * @var array - */ - private $invocations = []; - - /** - * @throws Exception - */ - public function __construct(array $parameterGroups) - { - foreach ($parameterGroups as $index => $parameters) { - if (!is_iterable($parameters)) { - throw new InvalidParameterGroupException( - sprintf( - 'Parameter group #%d must be an array or Traversable, got %s', - $index, - gettype($parameters), - ), - ); - } - - foreach ($parameters as $parameter) { - if (!$parameter instanceof Constraint) { - $parameter = new IsEqual($parameter); - } - - $this->parameterGroups[$index][] = $parameter; - } - } - } - - public function toString(): string - { - return 'with consecutive parameters'; - } - - /** - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - public function apply(BaseInvocation $invocation): void - { - $this->invocations[] = $invocation; - $callIndex = count($this->invocations) - 1; - - $this->verifyInvocation($invocation, $callIndex); - } - - /** - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - public function verify(): void - { - foreach ($this->invocations as $callIndex => $invocation) { - $this->verifyInvocation($invocation, $callIndex); - } - } - - /** - * Verify a single invocation. - * - * @param int $callIndex - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - private function verifyInvocation(BaseInvocation $invocation, $callIndex): void - { - if (!isset($this->parameterGroups[$callIndex])) { - // no parameter assertion for this call index - return; - } - - $parameters = $this->parameterGroups[$callIndex]; - - if (count($invocation->getParameters()) < count($parameters)) { - throw new ExpectationFailedException( - sprintf( - 'Parameter count for invocation %s is too low.', - $invocation->toString(), - ), - ); - } - - foreach ($parameters as $i => $parameter) { - $parameter->evaluate( - $invocation->getParameters()[$i], - sprintf( - 'Parameter %s for invocation #%d %s does not match expected ' . - 'value.', - $i, - $callIndex, - $invocation->toString(), - ), - ); - } - } -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/InvocationOrder.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/InvocationOrder.php deleted file mode 100644 index 90aa49350..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/InvocationOrder.php +++ /dev/null @@ -1,47 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Rule; - -use function count; -use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; -use PHPUnit\Framework\MockObject\Verifiable; -use PHPUnit\Framework\SelfDescribing; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -abstract class InvocationOrder implements SelfDescribing, Verifiable -{ - /** - * @var BaseInvocation[] - */ - private $invocations = []; - - public function getInvocationCount(): int - { - return count($this->invocations); - } - - public function hasBeenInvoked(): bool - { - return count($this->invocations) > 0; - } - - final public function invoked(BaseInvocation $invocation) - { - $this->invocations[] = $invocation; - - return $this->invokedDo($invocation); - } - - abstract public function matches(BaseInvocation $invocation): bool; - - abstract protected function invokedDo(BaseInvocation $invocation); -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/InvokedAtIndex.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/InvokedAtIndex.php deleted file mode 100644 index d56618cbc..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/InvokedAtIndex.php +++ /dev/null @@ -1,76 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Rule; - -use function sprintf; -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4297 - * - * @codeCoverageIgnore - */ -final class InvokedAtIndex extends InvocationOrder -{ - /** - * @var int - */ - private $sequenceIndex; - - /** - * @var int - */ - private $currentIndex = -1; - - /** - * @param int $sequenceIndex - */ - public function __construct($sequenceIndex) - { - $this->sequenceIndex = $sequenceIndex; - } - - public function toString(): string - { - return 'invoked at sequence index ' . $this->sequenceIndex; - } - - public function matches(BaseInvocation $invocation): bool - { - $this->currentIndex++; - - return $this->currentIndex == $this->sequenceIndex; - } - - /** - * Verifies that the current expectation is valid. If everything is OK the - * code should just return, if not it must throw an exception. - * - * @throws ExpectationFailedException - */ - public function verify(): void - { - if ($this->currentIndex < $this->sequenceIndex) { - throw new ExpectationFailedException( - sprintf( - 'The expected invocation at index %s was never reached.', - $this->sequenceIndex, - ), - ); - } - } - - protected function invokedDo(BaseInvocation $invocation): void - { - } -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/InvokedAtLeastCount.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/InvokedAtLeastCount.php deleted file mode 100644 index afc880e1f..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/InvokedAtLeastCount.php +++ /dev/null @@ -1,64 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Rule; - -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class InvokedAtLeastCount extends InvocationOrder -{ - /** - * @var int - */ - private $requiredInvocations; - - /** - * @param int $requiredInvocations - */ - public function __construct($requiredInvocations) - { - $this->requiredInvocations = $requiredInvocations; - } - - public function toString(): string - { - return 'invoked at least ' . $this->requiredInvocations . ' times'; - } - - /** - * Verifies that the current expectation is valid. If everything is OK the - * code should just return, if not it must throw an exception. - * - * @throws ExpectationFailedException - */ - public function verify(): void - { - $count = $this->getInvocationCount(); - - if ($count < $this->requiredInvocations) { - throw new ExpectationFailedException( - 'Expected invocation at least ' . $this->requiredInvocations . - ' times but it occurred ' . $count . ' time(s).', - ); - } - } - - public function matches(BaseInvocation $invocation): bool - { - return true; - } - - protected function invokedDo(BaseInvocation $invocation): void - { - } -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/InvokedAtMostCount.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/InvokedAtMostCount.php deleted file mode 100644 index df81a6131..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/InvokedAtMostCount.php +++ /dev/null @@ -1,64 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Rule; - -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class InvokedAtMostCount extends InvocationOrder -{ - /** - * @var int - */ - private $allowedInvocations; - - /** - * @param int $allowedInvocations - */ - public function __construct($allowedInvocations) - { - $this->allowedInvocations = $allowedInvocations; - } - - public function toString(): string - { - return 'invoked at most ' . $this->allowedInvocations . ' times'; - } - - /** - * Verifies that the current expectation is valid. If everything is OK the - * code should just return, if not it must throw an exception. - * - * @throws ExpectationFailedException - */ - public function verify(): void - { - $count = $this->getInvocationCount(); - - if ($count > $this->allowedInvocations) { - throw new ExpectationFailedException( - 'Expected invocation at most ' . $this->allowedInvocations . - ' times but it occurred ' . $count . ' time(s).', - ); - } - } - - public function matches(BaseInvocation $invocation): bool - { - return true; - } - - protected function invokedDo(BaseInvocation $invocation): void - { - } -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/InvokedCount.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/InvokedCount.php deleted file mode 100644 index a962118eb..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/InvokedCount.php +++ /dev/null @@ -1,102 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Rule; - -use function sprintf; -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class InvokedCount extends InvocationOrder -{ - /** - * @var int - */ - private $expectedCount; - - /** - * @param int $expectedCount - */ - public function __construct($expectedCount) - { - $this->expectedCount = $expectedCount; - } - - public function isNever(): bool - { - return $this->expectedCount === 0; - } - - public function toString(): string - { - return 'invoked ' . $this->expectedCount . ' time(s)'; - } - - public function matches(BaseInvocation $invocation): bool - { - return true; - } - - /** - * Verifies that the current expectation is valid. If everything is OK the - * code should just return, if not it must throw an exception. - * - * @throws ExpectationFailedException - */ - public function verify(): void - { - $count = $this->getInvocationCount(); - - if ($count !== $this->expectedCount) { - throw new ExpectationFailedException( - sprintf( - 'Method was expected to be called %d times, ' . - 'actually called %d times.', - $this->expectedCount, - $count, - ), - ); - } - } - - /** - * @throws ExpectationFailedException - */ - protected function invokedDo(BaseInvocation $invocation): void - { - $count = $this->getInvocationCount(); - - if ($count > $this->expectedCount) { - $message = $invocation->toString() . ' '; - - switch ($this->expectedCount) { - case 0: - $message .= 'was not expected to be called.'; - - break; - - case 1: - $message .= 'was not expected to be called more than once.'; - - break; - - default: - $message .= sprintf( - 'was not expected to be called more than %d times.', - $this->expectedCount, - ); - } - - throw new ExpectationFailedException($message); - } - } -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/MethodName.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/MethodName.php deleted file mode 100644 index 53c638876..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/MethodName.php +++ /dev/null @@ -1,69 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Rule; - -use function is_string; -use PHPUnit\Framework\Constraint\Constraint; -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Framework\InvalidArgumentException; -use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; -use PHPUnit\Framework\MockObject\MethodNameConstraint; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class MethodName -{ - /** - * @var Constraint - */ - private $constraint; - - /** - * @param Constraint|string $constraint - * - * @throws InvalidArgumentException - */ - public function __construct($constraint) - { - if (is_string($constraint)) { - $constraint = new MethodNameConstraint($constraint); - } - - if (!$constraint instanceof Constraint) { - throw InvalidArgumentException::create(1, 'PHPUnit\Framework\Constraint\Constraint object or string'); - } - - $this->constraint = $constraint; - } - - public function toString(): string - { - return 'method name ' . $this->constraint->toString(); - } - - /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - */ - public function matches(BaseInvocation $invocation): bool - { - return $this->matchesName($invocation->getMethodName()); - } - - /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws ExpectationFailedException - */ - public function matchesName(string $methodName): bool - { - return (bool) $this->constraint->evaluate($methodName, '', true); - } -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Api/DoubledCloneMethod.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Api/DoubledCloneMethod.php new file mode 100644 index 000000000..4da35c0c1 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Api/DoubledCloneMethod.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 PHPUnit\Framework\MockObject; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This trait is not covered by the backward compatibility promise for PHPUnit + */ +trait DoubledCloneMethod +{ + public function __clone(): void + { + $this->__phpunit_state = clone $this->__phpunit_state; + + $this->__phpunit_state()->cloneInvocationHandler(); + } + + abstract public function __phpunit_state(): TestDoubleState; +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Api/Method.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Api/Method.php new file mode 100644 index 000000000..c9b4e42e5 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Api/Method.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 PHPUnit\Framework\MockObject; + +use PHPUnit\Framework\Constraint\Constraint; +use PHPUnit\Framework\MockObject\Rule\AnyInvokedCount; +use PHPUnit\Framework\MockObject\Runtime\PropertyHook; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This trait is not covered by the backward compatibility promise for PHPUnit + */ +trait Method +{ + abstract public function __phpunit_getInvocationHandler(): InvocationHandler; + + public function method(Constraint|PropertyHook|string $constraint): InvocationStubber + { + return $this + ->__phpunit_getInvocationHandler() + ->expects(new AnyInvokedCount) + ->method($constraint); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Api/MockObjectApi.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Api/MockObjectApi.php new file mode 100644 index 000000000..a73695676 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Api/MockObjectApi.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 PHPUnit\Framework\MockObject; + +use PHPUnit\Framework\MockObject\Rule\InvocationOrder; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This trait is not covered by the backward compatibility promise for PHPUnit + */ +trait MockObjectApi +{ + /** @noinspection MagicMethodsValidityInspection */ + public function __phpunit_hasMatchers(): bool + { + return $this->__phpunit_getInvocationHandler()->hasMatchers(); + } + + /** @noinspection MagicMethodsValidityInspection */ + public function __phpunit_verify(bool $unsetInvocationMocker = true): void + { + $this->__phpunit_getInvocationHandler()->verify(); + + if ($unsetInvocationMocker) { + $this->__phpunit_unsetInvocationMocker(); + } + } + + abstract public function __phpunit_state(): TestDoubleState; + + abstract public function __phpunit_getInvocationHandler(): InvocationHandler; + + abstract public function __phpunit_unsetInvocationMocker(): void; + + public function expects(InvocationOrder $matcher): InvocationStubber + { + return $this->__phpunit_getInvocationHandler()->expects($matcher); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Api/ProxiedCloneMethod.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Api/ProxiedCloneMethod.php new file mode 100644 index 000000000..88797884d --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Api/ProxiedCloneMethod.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 PHPUnit\Framework\MockObject; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This trait is not covered by the backward compatibility promise for PHPUnit + */ +trait ProxiedCloneMethod +{ + public function __clone(): void + { + $this->__phpunit_state = clone $this->__phpunit_state; + + $this->__phpunit_state()->cloneInvocationHandler(); + + parent::__clone(); + } + + abstract public function __phpunit_state(): TestDoubleState; +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Api/StubApi.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Api/StubApi.php new file mode 100644 index 000000000..faba8d40f --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Api/StubApi.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 PHPUnit\Framework\MockObject; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This trait is not covered by the backward compatibility promise for PHPUnit + */ +trait StubApi +{ + private readonly TestDoubleState $__phpunit_state; + + public function __phpunit_state(): TestDoubleState + { + return $this->__phpunit_state; + } + + /** @noinspection MagicMethodsValidityInspection */ + public function __phpunit_getInvocationHandler(): InvocationHandler + { + return $this->__phpunit_state()->invocationHandler(); + } + + /** @noinspection MagicMethodsValidityInspection */ + public function __phpunit_unsetInvocationMocker(): void + { + $this->__phpunit_state()->unsetInvocationHandler(); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Api/TestDoubleState.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Api/TestDoubleState.php new file mode 100644 index 000000000..93b10c14d --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Api/TestDoubleState.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 PHPUnit\Framework\MockObject; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class TestDoubleState +{ + /** + * @var list + */ + private readonly array $configurableMethods; + private readonly bool $generateReturnValues; + private ?InvocationHandler $invocationHandler = null; + + /** + * @param list $configurableMethods + */ + public function __construct(array $configurableMethods, bool $generateReturnValues) + { + $this->configurableMethods = $configurableMethods; + $this->generateReturnValues = $generateReturnValues; + } + + public function invocationHandler(): InvocationHandler + { + if ($this->invocationHandler !== null) { + return $this->invocationHandler; + } + + $this->invocationHandler = new InvocationHandler( + $this->configurableMethods, + $this->generateReturnValues, + ); + + return $this->invocationHandler; + } + + public function cloneInvocationHandler(): void + { + if ($this->invocationHandler === null) { + return; + } + + $this->invocationHandler = clone $this->invocationHandler; + } + + public function unsetInvocationHandler(): void + { + $this->invocationHandler = null; + } + + /** + * @return list + */ + public function configurableMethods(): array + { + return $this->configurableMethods; + } + + public function generateReturnValues(): bool + { + return $this->generateReturnValues; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Interface/InvocationStubber.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Interface/InvocationStubber.php new file mode 100644 index 000000000..337c82fb3 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Interface/InvocationStubber.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 PHPUnit\Framework\MockObject; + +use PHPUnit\Framework\Constraint\Constraint; +use PHPUnit\Framework\MockObject\Runtime\PropertyHook; +use PHPUnit\Framework\MockObject\Stub\Stub; +use Throwable; + +interface InvocationStubber +{ + /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @param Constraint|non-empty-string|PropertyHook $constraint + * + * @return $this + */ + public function method(Constraint|PropertyHook|string $constraint): self; + + /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @param non-empty-string $id + * + * @return $this + */ + public function id(string $id): self; + + /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @param non-empty-string $id + * + * @return $this + */ + public function after(string $id): self; + + /** + * @return $this + */ + public function with(mixed ...$arguments): self; + + /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @return $this + */ + public function withAnyParameters(): self; + + /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @return $this + */ + public function will(Stub $stub): self; + + /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @return $this + */ + public function willReturn(mixed $value, mixed ...$nextValues): self; + + /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @return $this + */ + public function willReturnReference(mixed &$reference): self; + + /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @param array> $valueMap + * + * @return $this + */ + public function willReturnMap(array $valueMap): self; + + /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @return $this + */ + public function willReturnArgument(int $argumentIndex): self; + + /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @return $this + */ + public function willReturnCallback(callable $callback): self; + + /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @return $this + */ + public function willReturnSelf(): self; + + /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @return $this + */ + public function willReturnOnConsecutiveCalls(mixed ...$values): self; + + /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @return $this + */ + public function willThrowException(Throwable $exception): self; +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Interface/MockObject.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Interface/MockObject.php new file mode 100644 index 000000000..0ddc46dc4 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Interface/MockObject.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 PHPUnit\Framework\MockObject; + +use PHPUnit\Framework\MockObject\Rule\InvocationOrder; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface MockObject extends Stub +{ + public function expects(InvocationOrder $invocationRule): InvocationStubber; +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Interface/MockObjectInternal.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Interface/MockObjectInternal.php new file mode 100644 index 000000000..167d2466d --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Interface/MockObjectInternal.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This interface is not covered by the backward compatibility promise for PHPUnit + */ +interface MockObjectInternal extends MockObject, StubInternal +{ + public function __phpunit_hasMatchers(): bool; + + public function __phpunit_verify(bool $unsetInvocationMocker = true): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Interface/Stub.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Interface/Stub.php new file mode 100644 index 000000000..6321c98bb --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Interface/Stub.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 PHPUnit\Framework\MockObject; + +use PHPUnit\Framework\Constraint\Constraint; +use PHPUnit\Framework\MockObject\Runtime\PropertyHook; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface Stub +{ + public function method(Constraint|PropertyHook|string $constraint): InvocationStubber; +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Interface/StubInternal.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Interface/StubInternal.php new file mode 100644 index 000000000..6e428ea5e --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Interface/StubInternal.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 PHPUnit\Framework\MockObject; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This interface is not covered by the backward compatibility promise for PHPUnit + */ +interface StubInternal extends Stub +{ + public function __phpunit_state(): TestDoubleState; + + public function __phpunit_getInvocationHandler(): InvocationHandler; + + public function __phpunit_unsetInvocationMocker(): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Invocation.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Invocation.php new file mode 100644 index 000000000..d9e3c3f66 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Invocation.php @@ -0,0 +1,141 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use function array_map; +use function implode; +use function sprintf; +use function str_starts_with; +use function strtolower; +use function substr; +use PHPUnit\Framework\SelfDescribing; +use PHPUnit\Util\Exporter; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Invocation implements SelfDescribing +{ + /** + * @var class-string + */ + private string $className; + + /** + * @var non-empty-string + */ + private string $methodName; + + /** + * @var array + */ + private array $parameters; + private string $returnType; + private bool $isReturnTypeNullable; + private MockObjectInternal|StubInternal $object; + + /** + * @param class-string $className + * @param non-empty-string $methodName + * @param array $parameters + */ + public function __construct(string $className, string $methodName, array $parameters, string $returnType, MockObjectInternal|StubInternal $object) + { + $this->className = $className; + $this->methodName = $methodName; + $this->parameters = $parameters; + $this->object = $object; + + if (strtolower($methodName) === '__tostring') { + $returnType = 'string'; + } + + if (str_starts_with($returnType, '?')) { + $returnType = substr($returnType, 1); + $this->isReturnTypeNullable = true; + } else { + $this->isReturnTypeNullable = false; + } + + $this->returnType = $returnType; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + /** + * @return non-empty-string + */ + public function methodName(): string + { + return $this->methodName; + } + + /** + * @return array + */ + public function parameters(): array + { + return $this->parameters; + } + + /** + * @throws Exception + */ + public function generateReturnValue(): mixed + { + if ($this->returnType === 'never') { + throw new NeverReturningMethodException( + $this->className, + $this->methodName, + ); + } + + if ($this->isReturnTypeNullable) { + return null; + } + + return (new ReturnValueGenerator)->generate( + $this->className, + $this->methodName, + $this->object, + $this->returnType, + ); + } + + public function toString(): string + { + return sprintf( + '%s::%s(%s)%s', + $this->className, + $this->methodName, + implode( + ', ', + array_map( + [Exporter::class, 'shortenedExport'], + $this->parameters, + ), + ), + $this->returnType !== '' ? sprintf(': %s', $this->returnType) : '', + ); + } + + public function object(): MockObjectInternal|StubInternal + { + return $this->object; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/InvocationHandler.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/InvocationHandler.php new file mode 100644 index 000000000..50f8283ad --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/InvocationHandler.php @@ -0,0 +1,157 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use function strtolower; +use Exception; +use PHPUnit\Framework\MockObject\Rule\InvocationOrder; +use Throwable; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class InvocationHandler +{ + /** + * @var list + */ + private array $matchers = []; + + /** + * @var array + */ + private array $matcherMap = []; + + /** + * @var list + */ + private readonly array $configurableMethods; + private readonly bool $returnValueGeneration; + + /** + * @param list $configurableMethods + */ + public function __construct(array $configurableMethods, bool $returnValueGeneration) + { + $this->configurableMethods = $configurableMethods; + $this->returnValueGeneration = $returnValueGeneration; + } + + public function hasMatchers(): bool + { + foreach ($this->matchers as $matcher) { + if ($matcher->hasMatchers()) { + return true; + } + } + + return false; + } + + /** + * Looks up the match builder with identification $id and returns it. + * + * @param non-empty-string $id + */ + public function lookupMatcher(string $id): ?Matcher + { + return $this->matcherMap[$id] ?? null; + } + + /** + * Registers a matcher with the identification $id. The matcher can later be + * looked up using lookupMatcher() to figure out if it has been invoked. + * + * @param non-empty-string $id + * + * @throws MatcherAlreadyRegisteredException + */ + public function registerMatcher(string $id, Matcher $matcher): void + { + if (isset($this->matcherMap[$id])) { + throw new MatcherAlreadyRegisteredException($id); + } + + $this->matcherMap[$id] = $matcher; + } + + public function expects(InvocationOrder $rule): InvocationStubber + { + $matcher = new Matcher($rule); + $this->addMatcher($matcher); + + return new InvocationStubberImplementation( + $this, + $matcher, + ...$this->configurableMethods, + ); + } + + /** + * @throws \PHPUnit\Framework\MockObject\Exception + * @throws Exception + */ + public function invoke(Invocation $invocation): mixed + { + $exception = null; + $hasReturnValue = false; + $returnValue = null; + + foreach ($this->matchers as $match) { + try { + if ($match->matches($invocation)) { + $value = $match->invoked($invocation); + + if (!$hasReturnValue) { + $returnValue = $value; + $hasReturnValue = true; + } + } + } catch (Exception $e) { + $exception = $e; + } + } + + if ($exception !== null) { + throw $exception; + } + + if ($hasReturnValue) { + return $returnValue; + } + + if (!$this->returnValueGeneration) { + if (strtolower($invocation->methodName()) === '__tostring') { + return ''; + } + + throw new ReturnValueNotConfiguredException($invocation); + } + + return $invocation->generateReturnValue(); + } + + /** + * @throws Throwable + */ + public function verify(): void + { + foreach ($this->matchers as $matcher) { + $matcher->verify(); + } + } + + private function addMatcher(Matcher $matcher): void + { + $this->matchers[] = $matcher; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/InvocationStubberImplementation.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/InvocationStubberImplementation.php new file mode 100644 index 000000000..3aca45a29 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/InvocationStubberImplementation.php @@ -0,0 +1,330 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use function array_flip; +use function array_key_exists; +use function array_map; +use function array_merge; +use function array_pop; +use function assert; +use function count; +use function is_string; +use function range; +use function strtolower; +use PHPUnit\Framework\Constraint\Constraint; +use PHPUnit\Framework\InvalidArgumentException; +use PHPUnit\Framework\MockObject\Runtime\PropertyHook; +use PHPUnit\Framework\MockObject\Stub\ConsecutiveCalls; +use PHPUnit\Framework\MockObject\Stub\Exception; +use PHPUnit\Framework\MockObject\Stub\ReturnArgument; +use PHPUnit\Framework\MockObject\Stub\ReturnCallback; +use PHPUnit\Framework\MockObject\Stub\ReturnReference; +use PHPUnit\Framework\MockObject\Stub\ReturnSelf; +use PHPUnit\Framework\MockObject\Stub\ReturnStub; +use PHPUnit\Framework\MockObject\Stub\ReturnValueMap; +use PHPUnit\Framework\MockObject\Stub\Stub; +use Throwable; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class InvocationStubberImplementation implements InvocationStubber +{ + private readonly InvocationHandler $invocationHandler; + private readonly Matcher $matcher; + + /** + * @var list + */ + private readonly array $configurableMethods; + + /** + * @var ?array + */ + private ?array $configurableMethodNames = null; + + public function __construct(InvocationHandler $handler, Matcher $matcher, ConfigurableMethod ...$configurableMethods) + { + $this->invocationHandler = $handler; + $this->matcher = $matcher; + $this->configurableMethods = $configurableMethods; + } + + /** + * @param Constraint|non-empty-string|PropertyHook $constraint + * + * @throws InvalidArgumentException + * @throws MethodCannotBeConfiguredException + * @throws MethodNameAlreadyConfiguredException + * + * @return $this + */ + public function method(Constraint|PropertyHook|string $constraint): InvocationStubber + { + if ($this->matcher->hasMethodNameRule()) { + throw new MethodNameAlreadyConfiguredException; + } + + if ($constraint instanceof PropertyHook) { + $constraint = $constraint->asString(); + } + + if (is_string($constraint)) { + $this->configurableMethodNames ??= array_flip( + array_map( + static fn (ConfigurableMethod $configurable) => strtolower($configurable->name()), + $this->configurableMethods, + ), + ); + + if (!array_key_exists(strtolower($constraint), $this->configurableMethodNames)) { + throw new MethodCannotBeConfiguredException($constraint); + } + } + + $this->matcher->setMethodNameRule(new Rule\MethodName($constraint)); + + return $this; + } + + /** + * @param non-empty-string $id + * + * @throws MatcherAlreadyRegisteredException + * + * @return $this + */ + public function id(string $id): InvocationStubber + { + $this->invocationHandler->registerMatcher($id, $this->matcher); + + return $this; + } + + /** + * @param non-empty-string $id + * + * @return $this + */ + public function after(string $id): InvocationStubber + { + $this->matcher->setAfterMatchBuilderId($id); + + return $this; + } + + /** + * @throws \PHPUnit\Framework\Exception + * @throws MethodNameNotConfiguredException + * @throws MethodParametersAlreadyConfiguredException + * + * @return $this + */ + public function with(mixed ...$arguments): InvocationStubber + { + $this->ensureParametersCanBeConfigured(); + + $this->matcher->setParametersRule(new Rule\Parameters($arguments)); + + return $this; + } + + /** + * @throws MethodNameNotConfiguredException + * @throws MethodParametersAlreadyConfiguredException + * + * @return $this + */ + public function withAnyParameters(): InvocationStubber + { + $this->ensureParametersCanBeConfigured(); + + $this->matcher->setParametersRule(new Rule\AnyParameters); + + return $this; + } + + /** + * @return $this + */ + public function will(Stub $stub): InvocationStubber + { + $this->matcher->setStub($stub); + + return $this; + } + + /** + * @throws IncompatibleReturnValueException + */ + public function willReturn(mixed $value, mixed ...$nextValues): InvocationStubber + { + if (count($nextValues) === 0) { + $this->ensureTypeOfReturnValues([$value]); + + $stub = $value instanceof Stub ? $value : new ReturnStub($value); + + return $this->will($stub); + } + + $values = array_merge([$value], $nextValues); + + $this->ensureTypeOfReturnValues($values); + + $stub = new ConsecutiveCalls($values); + + return $this->will($stub); + } + + public function willReturnReference(mixed &$reference): InvocationStubber + { + $stub = new ReturnReference($reference); + + return $this->will($stub); + } + + public function willReturnMap(array $valueMap): InvocationStubber + { + $method = $this->configuredMethod(); + + assert($method instanceof ConfigurableMethod); + + $numberOfParameters = $method->numberOfParameters(); + $defaultValues = $method->defaultParameterValues(); + $hasDefaultValues = $defaultValues !== []; + + $_valueMap = []; + + foreach ($valueMap as $mapping) { + $numberOfConfiguredParameters = count($mapping) - 1; + + if ($numberOfConfiguredParameters === $numberOfParameters || !$hasDefaultValues) { + $_valueMap[] = $mapping; + + continue; + } + + $_mapping = []; + $returnValue = array_pop($mapping); + + foreach (range(0, $numberOfParameters - 1) as $i) { + if (array_key_exists($i, $mapping)) { + $_mapping[] = $mapping[$i]; + + continue; + } + + if (array_key_exists($i, $defaultValues)) { + $_mapping[] = $defaultValues[$i]; + } + } + + $_mapping[] = $returnValue; + $_valueMap[] = $_mapping; + } + + $stub = new ReturnValueMap($_valueMap); + + return $this->will($stub); + } + + public function willReturnArgument(int $argumentIndex): InvocationStubber + { + $stub = new ReturnArgument($argumentIndex); + + return $this->will($stub); + } + + public function willReturnCallback(callable $callback): InvocationStubber + { + $stub = new ReturnCallback($callback); + + return $this->will($stub); + } + + public function willReturnSelf(): InvocationStubber + { + $stub = new ReturnSelf; + + return $this->will($stub); + } + + public function willReturnOnConsecutiveCalls(mixed ...$values): InvocationStubber + { + $stub = new ConsecutiveCalls($values); + + return $this->will($stub); + } + + public function willThrowException(Throwable $exception): InvocationStubber + { + $stub = new Exception($exception); + + return $this->will($stub); + } + + /** + * @throws MethodNameNotConfiguredException + * @throws MethodParametersAlreadyConfiguredException + */ + private function ensureParametersCanBeConfigured(): void + { + if (!$this->matcher->hasMethodNameRule()) { + throw new MethodNameNotConfiguredException; + } + + if ($this->matcher->hasParametersRule()) { + throw new MethodParametersAlreadyConfiguredException; + } + } + + private function configuredMethod(): ?ConfigurableMethod + { + $configuredMethod = null; + + foreach ($this->configurableMethods as $configurableMethod) { + if ($this->matcher->methodNameRule()->matchesName($configurableMethod->name())) { + if ($configuredMethod !== null) { + return null; + } + + $configuredMethod = $configurableMethod; + } + } + + return $configuredMethod; + } + + /** + * @param array $values + * + * @throws IncompatibleReturnValueException + */ + private function ensureTypeOfReturnValues(array $values): void + { + $configuredMethod = $this->configuredMethod(); + + if ($configuredMethod === null) { + return; + } + + foreach ($values as $value) { + if (!$configuredMethod->mayReturn($value)) { + throw new IncompatibleReturnValueException( + $configuredMethod, + $value, + ); + } + } + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Matcher.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Matcher.php new file mode 100644 index 000000000..f1587f3b3 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Matcher.php @@ -0,0 +1,219 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use function sprintf; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\MockObject\Rule\AnyInvokedCount; +use PHPUnit\Framework\MockObject\Rule\AnyParameters; +use PHPUnit\Framework\MockObject\Rule\InvocationOrder; +use PHPUnit\Framework\MockObject\Rule\InvokedAtMostCount; +use PHPUnit\Framework\MockObject\Rule\InvokedCount; +use PHPUnit\Framework\MockObject\Rule\MethodName; +use PHPUnit\Framework\MockObject\Rule\ParametersRule; +use PHPUnit\Framework\MockObject\Stub\Stub; +use PHPUnit\Util\ThrowableToStringMapper; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Matcher +{ + private readonly InvocationOrder $invocationRule; + + /** + * @var ?non-empty-string + */ + private ?string $afterMatchBuilderId = null; + private ?MethodName $methodNameRule = null; + private ?ParametersRule $parametersRule = null; + private ?Stub $stub = null; + + public function __construct(InvocationOrder $rule) + { + $this->invocationRule = $rule; + } + + public function hasMatchers(): bool + { + return !$this->invocationRule instanceof AnyInvokedCount; + } + + public function hasMethodNameRule(): bool + { + return $this->methodNameRule !== null; + } + + public function methodNameRule(): MethodName + { + return $this->methodNameRule; + } + + public function setMethodNameRule(MethodName $rule): void + { + $this->methodNameRule = $rule; + } + + public function hasParametersRule(): bool + { + return $this->parametersRule !== null; + } + + public function setParametersRule(ParametersRule $rule): void + { + $this->parametersRule = $rule; + } + + public function setStub(Stub $stub): void + { + $this->stub = $stub; + } + + /** + * @param non-empty-string $id + */ + public function setAfterMatchBuilderId(string $id): void + { + $this->afterMatchBuilderId = $id; + } + + /** + * @throws Exception + * @throws ExpectationFailedException + * @throws MatchBuilderNotFoundException + * @throws MethodNameNotConfiguredException + * @throws RuntimeException + */ + public function invoked(Invocation $invocation): mixed + { + if ($this->methodNameRule === null) { + throw new MethodNameNotConfiguredException; + } + + if ($this->afterMatchBuilderId !== null) { + $matcher = $invocation->object() + ->__phpunit_getInvocationHandler() + ->lookupMatcher($this->afterMatchBuilderId); + + if ($matcher === null) { + throw new MatchBuilderNotFoundException($this->afterMatchBuilderId); + } + } + + $this->invocationRule->invoked($invocation); + + try { + $this->parametersRule?->apply($invocation); + } catch (ExpectationFailedException $e) { + throw new ExpectationFailedException( + sprintf( + "Expectation failed for %s when %s\n%s", + $this->methodNameRule->toString(), + $this->invocationRule->toString(), + $e->getMessage(), + ), + $e->getComparisonFailure(), + ); + } + + if ($this->stub !== null) { + return $this->stub->invoke($invocation); + } + + return $invocation->generateReturnValue(); + } + + /** + * @throws ExpectationFailedException + * @throws MatchBuilderNotFoundException + * @throws MethodNameNotConfiguredException + * @throws RuntimeException + */ + public function matches(Invocation $invocation): bool + { + if ($this->afterMatchBuilderId !== null) { + $matcher = $invocation->object() + ->__phpunit_getInvocationHandler() + ->lookupMatcher($this->afterMatchBuilderId); + + if ($matcher === null) { + throw new MatchBuilderNotFoundException($this->afterMatchBuilderId); + } + + if (!$matcher->invocationRule->hasBeenInvoked()) { + return false; + } + } + + if ($this->methodNameRule === null) { + throw new MethodNameNotConfiguredException; + } + + if (!$this->invocationRule->matches($invocation)) { + return false; + } + + try { + if (!$this->methodNameRule->matches($invocation)) { + return false; + } + } catch (ExpectationFailedException $e) { + throw new ExpectationFailedException( + sprintf( + "Expectation failed for %s when %s\n%s", + $this->methodNameRule->toString(), + $this->invocationRule->toString(), + $e->getMessage(), + ), + $e->getComparisonFailure(), + ); + } + + return true; + } + + /** + * @throws ExpectationFailedException + * @throws MethodNameNotConfiguredException + */ + public function verify(): void + { + if ($this->methodNameRule === null) { + throw new MethodNameNotConfiguredException; + } + + try { + $this->invocationRule->verify(); + + if ($this->parametersRule === null) { + $this->parametersRule = new AnyParameters; + } + + $invocationIsAny = $this->invocationRule instanceof AnyInvokedCount; + $invocationIsNever = $this->invocationRule instanceof InvokedCount && $this->invocationRule->isNever(); + $invocationIsAtMost = $this->invocationRule instanceof InvokedAtMostCount; + + if (!$invocationIsAny && !$invocationIsNever && !$invocationIsAtMost) { + $this->parametersRule->verify(); + } + } catch (ExpectationFailedException $e) { + throw new ExpectationFailedException( + sprintf( + "Expectation failed for %s when %s.\n%s", + $this->methodNameRule->toString(), + $this->invocationRule->toString(), + ThrowableToStringMapper::map($e), + ), + ); + } + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/MethodNameConstraint.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/MethodNameConstraint.php new file mode 100644 index 000000000..bb6bf60bd --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/MethodNameConstraint.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 PHPUnit\Framework\MockObject; + +use function sprintf; +use function strtolower; +use PHPUnit\Framework\Constraint\Constraint; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class MethodNameConstraint extends Constraint +{ + private string $methodName; + + public function __construct(string $methodName) + { + $this->methodName = $methodName; + } + + public function toString(): string + { + return sprintf( + 'is "%s"', + $this->methodName, + ); + } + + protected function matches(mixed $other): bool + { + return strtolower($this->methodName) === strtolower((string) $other); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/PropertyHook/PropertyGetHook.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/PropertyHook/PropertyGetHook.php new file mode 100644 index 000000000..14266f557 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/PropertyHook/PropertyGetHook.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 PHPUnit\Framework\MockObject\Runtime; + +use function sprintf; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PropertyGetHook extends PropertyHook +{ + /** + * @return non-empty-string + * + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function asString(): string + { + return sprintf( + '$%s::get', + $this->propertyName(), + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/PropertyHook/PropertyHook.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/PropertyHook/PropertyHook.php new file mode 100644 index 000000000..a7664f8ed --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/PropertyHook/PropertyHook.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 PHPUnit\Framework\MockObject\Runtime; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +abstract readonly class PropertyHook +{ + /** + * @var non-empty-string + */ + private string $propertyName; + + /** + * @param non-empty-string $propertyName + */ + public static function get(string $propertyName): PropertyGetHook + { + return new PropertyGetHook($propertyName); + } + + /** + * @param non-empty-string $propertyName + */ + public static function set(string $propertyName): PropertySetHook + { + return new PropertySetHook($propertyName); + } + + /** + * @param non-empty-string $propertyName + */ + protected function __construct(string $propertyName) + { + $this->propertyName = $propertyName; + } + + /** + * @return non-empty-string + */ + public function propertyName(): string + { + return $this->propertyName; + } + + /** + * @return non-empty-string + * + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + abstract public function asString(): string; +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/PropertyHook/PropertySetHook.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/PropertyHook/PropertySetHook.php new file mode 100644 index 000000000..7d4918eb7 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/PropertyHook/PropertySetHook.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 PHPUnit\Framework\MockObject\Runtime; + +use function sprintf; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PropertySetHook extends PropertyHook +{ + /** + * @return non-empty-string + * + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function asString(): string + { + return sprintf( + '$%s::set', + $this->propertyName(), + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/ReturnValueGenerator.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/ReturnValueGenerator.php new file mode 100644 index 000000000..e429bc984 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/ReturnValueGenerator.php @@ -0,0 +1,259 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject; + +use function array_map; +use function explode; +use function in_array; +use function interface_exists; +use function sprintf; +use function str_contains; +use function str_ends_with; +use function str_starts_with; +use function substr; +use PHPUnit\Framework\MockObject\Generator\Generator; +use ReflectionClass; +use ReflectionObject; +use stdClass; +use Throwable; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ReturnValueGenerator +{ + /** + * @param class-string $className + * @param non-empty-string $methodName + * + * @throws Exception + */ + public function generate(string $className, string $methodName, StubInternal $testStub, string $returnType): mixed + { + $intersection = false; + $union = false; + + if (str_contains($returnType, '|')) { + $types = explode('|', $returnType); + $union = true; + + foreach ($types as $key => $type) { + if (str_starts_with($type, '(') && str_ends_with($type, ')')) { + $types[$key] = substr($type, 1, -1); + } + } + } elseif (str_contains($returnType, '&')) { + $types = explode('&', $returnType); + $intersection = true; + } else { + $types = [$returnType]; + } + + if (!$intersection) { + $lowerTypes = array_map('strtolower', $types); + + if (in_array('', $lowerTypes, true) || + in_array('null', $lowerTypes, true) || + in_array('mixed', $lowerTypes, true) || + in_array('void', $lowerTypes, true)) { + return null; + } + + if (in_array('true', $lowerTypes, true)) { + return true; + } + + if (in_array('false', $lowerTypes, true) || + in_array('bool', $lowerTypes, true)) { + return false; + } + + if (in_array('float', $lowerTypes, true)) { + return 0.0; + } + + if (in_array('int', $lowerTypes, true)) { + return 0; + } + + if (in_array('string', $lowerTypes, true)) { + return ''; + } + + if (in_array('array', $lowerTypes, true)) { + return []; + } + + if (in_array('static', $lowerTypes, true)) { + return $this->newInstanceOf($testStub, $className, $methodName); + } + + if (in_array('object', $lowerTypes, true)) { + return new stdClass; + } + + if (in_array('callable', $lowerTypes, true) || + in_array('closure', $lowerTypes, true)) { + return static function (): void + { + }; + } + + if (in_array('traversable', $lowerTypes, true) || + in_array('generator', $lowerTypes, true) || + in_array('iterable', $lowerTypes, true)) { + $generator = static function (): \Generator + { + yield from []; + }; + + return $generator(); + } + + if (!$union) { + return $this->testDoubleFor($returnType, $className, $methodName); + } + } + + if ($union) { + foreach ($types as $type) { + if (str_contains($type, '&')) { + $_types = explode('&', $type); + + if ($this->onlyInterfaces($_types)) { + return $this->testDoubleForIntersectionOfInterfaces($_types, $className, $methodName); + } + } + } + } + + if ($intersection && $this->onlyInterfaces($types)) { + return $this->testDoubleForIntersectionOfInterfaces($types, $className, $methodName); + } + + $reason = ''; + + if ($union) { + $reason = ' because the declared return type is a union'; + } elseif ($intersection) { + $reason = ' because the declared return type is an intersection'; + } + + throw new RuntimeException( + sprintf( + 'Return value for %s::%s() cannot be generated%s, please configure a return value for this method', + $className, + $methodName, + $reason, + ), + ); + } + + /** + * @param non-empty-list $types + */ + private function onlyInterfaces(array $types): bool + { + foreach ($types as $type) { + if (!interface_exists($type)) { + return false; + } + } + + return true; + } + + /** + * @param class-string $className + * @param non-empty-string $methodName + * + * @throws RuntimeException + */ + private function newInstanceOf(StubInternal $testStub, string $className, string $methodName): Stub + { + try { + $object = (new ReflectionClass($testStub::class))->newInstanceWithoutConstructor(); + $reflector = new ReflectionObject($object); + + $reflector->getProperty('__phpunit_state')->setValue( + $object, + new TestDoubleState( + $testStub->__phpunit_state()->configurableMethods(), + $testStub->__phpunit_state()->generateReturnValues(), + ), + ); + + return $object; + // @codeCoverageIgnoreStart + } catch (Throwable $t) { + throw new RuntimeException( + sprintf( + 'Return value for %s::%s() cannot be generated: %s', + $className, + $methodName, + $t->getMessage(), + ), + ); + // @codeCoverageIgnoreEnd + } + } + + /** + * @param class-string $type + * @param class-string $className + * @param non-empty-string $methodName + * + * @throws RuntimeException + */ + private function testDoubleFor(string $type, string $className, string $methodName): Stub + { + try { + return (new Generator)->testDouble($type, false, [], [], '', false); + // @codeCoverageIgnoreStart + } catch (Throwable $t) { + throw new RuntimeException( + sprintf( + 'Return value for %s::%s() cannot be generated: %s', + $className, + $methodName, + $t->getMessage(), + ), + ); + // @codeCoverageIgnoreEnd + } + } + + /** + * @param non-empty-list $types + * @param class-string $className + * @param non-empty-string $methodName + * + * @throws RuntimeException + */ + private function testDoubleForIntersectionOfInterfaces(array $types, string $className, string $methodName): Stub + { + try { + return (new Generator)->testDoubleForInterfaceIntersection($types, false); + // @codeCoverageIgnoreStart + } catch (Throwable $t) { + throw new RuntimeException( + sprintf( + 'Return value for %s::%s() cannot be generated: %s', + $className, + $methodName, + $t->getMessage(), + ), + ); + // @codeCoverageIgnoreEnd + } + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/AnyInvokedCount.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Rule/AnyInvokedCount.php similarity index 87% rename from app/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/AnyInvokedCount.php rename to app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Rule/AnyInvokedCount.php index f93e5686b..382f93081 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/AnyInvokedCount.php +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Rule/AnyInvokedCount.php @@ -12,6 +12,8 @@ use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class AnyInvokedCount extends InvocationOrder @@ -29,8 +31,4 @@ public function matches(BaseInvocation $invocation): bool { return true; } - - protected function invokedDo(BaseInvocation $invocation): void - { - } } diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/AnyParameters.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Rule/AnyParameters.php similarity index 85% rename from app/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/AnyParameters.php rename to app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Rule/AnyParameters.php index 61de78878..01a54d1b4 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/AnyParameters.php +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Rule/AnyParameters.php @@ -12,15 +12,12 @@ use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class AnyParameters implements ParametersRule { - public function toString(): string - { - return 'with any parameters'; - } - public function apply(BaseInvocation $invocation): void { } diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Rule/InvocationOrder.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Rule/InvocationOrder.php new file mode 100644 index 000000000..c3fb8f29d --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Rule/InvocationOrder.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Rule; + +use function count; +use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; +use PHPUnit\Framework\SelfDescribing; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +abstract class InvocationOrder implements SelfDescribing +{ + /** + * @var list + */ + private array $invocations = []; + + public function numberOfInvocations(): int + { + return count($this->invocations); + } + + public function hasBeenInvoked(): bool + { + return count($this->invocations) > 0; + } + + final public function invoked(BaseInvocation $invocation): void + { + $this->invocations[] = $invocation; + + $this->invokedDo($invocation); + } + + abstract public function matches(BaseInvocation $invocation): bool; + + abstract public function verify(): void; + + protected function invokedDo(BaseInvocation $invocation): void + { + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Rule/InvokedAtLeastCount.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Rule/InvokedAtLeastCount.php new file mode 100644 index 000000000..a78d933d1 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Rule/InvokedAtLeastCount.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 PHPUnit\Framework\MockObject\Rule; + +use function sprintf; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class InvokedAtLeastCount extends InvocationOrder +{ + private readonly int $requiredInvocations; + + public function __construct(int $requiredInvocations) + { + $this->requiredInvocations = $requiredInvocations; + } + + public function toString(): string + { + return sprintf( + 'invoked at least %d time%s', + $this->requiredInvocations, + $this->requiredInvocations !== 1 ? 's' : '', + ); + } + + /** + * Verifies that the current expectation is valid. If everything is OK the + * code should just return, if not it must throw an exception. + * + * @throws ExpectationFailedException + */ + public function verify(): void + { + $actualInvocations = $this->numberOfInvocations(); + + if ($actualInvocations < $this->requiredInvocations) { + throw new ExpectationFailedException( + sprintf( + 'Expected invocation at least %d time%s but it occurred %d time%s.', + $this->requiredInvocations, + $this->requiredInvocations !== 1 ? 's' : '', + $actualInvocations, + $actualInvocations !== 1 ? 's' : '', + ), + ); + } + } + + public function matches(BaseInvocation $invocation): bool + { + return true; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/InvokedAtLeastOnce.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Rule/InvokedAtLeastOnce.php similarity index 88% rename from app/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/InvokedAtLeastOnce.php rename to app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Rule/InvokedAtLeastOnce.php index 645ed309a..91026f53d 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/InvokedAtLeastOnce.php +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Rule/InvokedAtLeastOnce.php @@ -13,6 +13,8 @@ use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class InvokedAtLeastOnce extends InvocationOrder @@ -30,7 +32,7 @@ public function toString(): string */ public function verify(): void { - $count = $this->getInvocationCount(); + $count = $this->numberOfInvocations(); if ($count < 1) { throw new ExpectationFailedException( @@ -43,8 +45,4 @@ public function matches(BaseInvocation $invocation): bool { return true; } - - protected function invokedDo(BaseInvocation $invocation): void - { - } } diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Rule/InvokedAtMostCount.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Rule/InvokedAtMostCount.php new file mode 100644 index 000000000..0cfda5e18 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Rule/InvokedAtMostCount.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 PHPUnit\Framework\MockObject\Rule; + +use function sprintf; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class InvokedAtMostCount extends InvocationOrder +{ + private readonly int $allowedInvocations; + + public function __construct(int $allowedInvocations) + { + $this->allowedInvocations = $allowedInvocations; + } + + public function toString(): string + { + return sprintf( + 'invoked at most %d time%s', + $this->allowedInvocations, + $this->allowedInvocations !== 1 ? 's' : '', + ); + } + + /** + * Verifies that the current expectation is valid. If everything is OK the + * code should just return, if not it must throw an exception. + * + * @throws ExpectationFailedException + */ + public function verify(): void + { + $actualInvocations = $this->numberOfInvocations(); + + if ($actualInvocations > $this->allowedInvocations) { + throw new ExpectationFailedException( + sprintf( + 'Expected invocation at most %d time%s but it occurred %d time%s.', + $this->allowedInvocations, + $this->allowedInvocations !== 1 ? 's' : '', + $actualInvocations, + $actualInvocations !== 1 ? 's' : '', + ), + ); + } + } + + public function matches(BaseInvocation $invocation): bool + { + return true; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Rule/InvokedCount.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Rule/InvokedCount.php new file mode 100644 index 000000000..3f0e505a0 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Rule/InvokedCount.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 PHPUnit\Framework\MockObject\Rule; + +use function sprintf; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class InvokedCount extends InvocationOrder +{ + private readonly int $expectedCount; + + public function __construct(int $expectedCount) + { + $this->expectedCount = $expectedCount; + } + + public function isNever(): bool + { + return $this->expectedCount === 0; + } + + public function toString(): string + { + return sprintf( + 'invoked %d time%s', + $this->expectedCount, + $this->expectedCount !== 1 ? 's' : '', + ); + } + + public function matches(BaseInvocation $invocation): bool + { + return true; + } + + /** + * Verifies that the current expectation is valid. If everything is OK the + * code should just return, if not it must throw an exception. + * + * @throws ExpectationFailedException + */ + public function verify(): void + { + $actualCount = $this->numberOfInvocations(); + + if ($actualCount !== $this->expectedCount) { + throw new ExpectationFailedException( + sprintf( + 'Method was expected to be called %d time%s, actually called %d time%s.', + $this->expectedCount, + $this->expectedCount !== 1 ? 's' : '', + $actualCount, + $actualCount !== 1 ? 's' : '', + ), + ); + } + } + + /** + * @throws ExpectationFailedException + */ + protected function invokedDo(BaseInvocation $invocation): void + { + $count = $this->numberOfInvocations(); + + if ($count > $this->expectedCount) { + $message = $invocation->toString() . ' '; + + $message .= match ($this->expectedCount) { + 0 => 'was not expected to be called.', + 1 => 'was not expected to be called more than once.', + default => sprintf( + 'was not expected to be called more than %d times.', + $this->expectedCount, + ), + }; + + throw new ExpectationFailedException($message); + } + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Rule/MethodName.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Rule/MethodName.php new file mode 100644 index 000000000..219a8087d --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Rule/MethodName.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 PHPUnit\Framework\MockObject\Rule; + +use function is_string; +use PHPUnit\Framework\Constraint\Constraint; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\InvalidArgumentException; +use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; +use PHPUnit\Framework\MockObject\MethodNameConstraint; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class MethodName +{ + private Constraint $constraint; + + /** + * @throws InvalidArgumentException + */ + public function __construct(Constraint|string $constraint) + { + if (is_string($constraint)) { + $constraint = new MethodNameConstraint($constraint); + } + + $this->constraint = $constraint; + } + + public function toString(): string + { + return 'method name ' . $this->constraint->toString(); + } + + /** + * @throws ExpectationFailedException + */ + public function matches(BaseInvocation $invocation): bool + { + return $this->matchesName($invocation->methodName()); + } + + /** + * @throws ExpectationFailedException + */ + public function matchesName(string $methodName): bool + { + return (bool) $this->constraint->evaluate($methodName, '', true); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/Parameters.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Rule/Parameters.php similarity index 75% rename from app/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/Parameters.php rename to app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Rule/Parameters.php index c03671c72..17df3f0a7 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/Parameters.php +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Rule/Parameters.php @@ -10,43 +10,39 @@ namespace PHPUnit\Framework\MockObject\Rule; use function count; -use function get_class; use function sprintf; use Exception; +use PHPUnit\Framework\Constraint\Callback; use PHPUnit\Framework\Constraint\Constraint; use PHPUnit\Framework\Constraint\IsAnything; use PHPUnit\Framework\Constraint\IsEqual; use PHPUnit\Framework\ExpectationFailedException; use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; -use SebastianBergmann\RecursionContext\InvalidArgumentException; +use PHPUnit\Util\Test; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class Parameters implements ParametersRule { /** - * @var Constraint[] - */ - private $parameters = []; - - /** - * @var BaseInvocation - */ - private $invocation; - - /** - * @var bool|ExpectationFailedException + * @var list */ - private $parameterVerificationResult; + private array $parameters = []; + private ?BaseInvocation $invocation = null; + private null|bool|ExpectationFailedException $parameterVerificationResult; /** + * @param array $parameters + * * @throws \PHPUnit\Framework\Exception */ public function __construct(array $parameters) { foreach ($parameters as $parameter) { - if (!($parameter instanceof Constraint)) { + if (!$parameter instanceof Constraint) { $parameter = new IsEqual( $parameter, ); @@ -56,21 +52,6 @@ public function __construct(array $parameters) } } - public function toString(): string - { - $text = 'with parameter'; - - foreach ($this->parameters as $index => $parameter) { - if ($index > 0) { - $text .= ' and'; - } - - $text .= ' ' . $index . ' ' . $parameter->toString(); - } - - return $text; - } - /** * @throws Exception */ @@ -94,7 +75,6 @@ public function apply(BaseInvocation $invocation): void * if an expectation is met. * * @throws ExpectationFailedException - * @throws InvalidArgumentException */ public function verify(): void { @@ -103,7 +83,6 @@ public function verify(): void /** * @throws ExpectationFailedException - * @throws InvalidArgumentException */ private function doVerify(): bool { @@ -112,10 +91,10 @@ private function doVerify(): bool } if ($this->invocation === null) { - throw new ExpectationFailedException('Mocked method does not exist.'); + throw new ExpectationFailedException('Doubled method does not exist.'); } - if (count($this->invocation->getParameters()) < count($this->parameters)) { + if (count($this->invocation->parameters()) < count($this->parameters)) { $message = 'Parameter count for invocation %s is too low.'; // The user called `->with($this->anything())`, but may have meant @@ -123,21 +102,30 @@ private function doVerify(): bool // // @see https://github.com/sebastianbergmann/phpunit-mock-objects/issues/199 if (count($this->parameters) === 1 && - get_class($this->parameters[0]) === IsAnything::class) { + $this->parameters[0]::class === IsAnything::class) { $message .= "\nTo allow 0 or more parameters with any value, omit ->with() or use ->withAnyParameters() instead."; } + $this->incrementAssertionCount(); + throw new ExpectationFailedException( sprintf($message, $this->invocation->toString()), ); } foreach ($this->parameters as $i => $parameter) { + if ($parameter instanceof Callback && $parameter->isVariadic()) { + $other = $this->invocation->parameters(); + } else { + $other = $this->invocation->parameters()[$i]; + } + + $this->incrementAssertionCount(); + $parameter->evaluate( - $this->invocation->getParameters()[$i], + $other, sprintf( - 'Parameter %s for invocation %s does not match expected ' . - 'value.', + 'Parameter %s for invocation %s does not match expected value.', $i, $this->invocation->toString(), ), @@ -158,4 +146,9 @@ private function guardAgainstDuplicateEvaluationOfParameterConstraints(): bool return (bool) $this->parameterVerificationResult; } + + private function incrementAssertionCount(): void + { + Test::currentTestCase()->addToAssertionCount(1); + } } diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/ParametersRule.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Rule/ParametersRule.php similarity index 83% rename from app/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/ParametersRule.php rename to app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Rule/ParametersRule.php index 70c47fe32..03cfe2a48 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Rule/ParametersRule.php +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Rule/ParametersRule.php @@ -11,13 +11,11 @@ use PHPUnit\Framework\ExpectationFailedException; use PHPUnit\Framework\MockObject\Invocation as BaseInvocation; -use PHPUnit\Framework\MockObject\Verifiable; -use PHPUnit\Framework\SelfDescribing; /** * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit */ -interface ParametersRule extends SelfDescribing, Verifiable +interface ParametersRule { /** * @throws ExpectationFailedException if the invocation violates the rule diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Stub/ConsecutiveCalls.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Stub/ConsecutiveCalls.php new file mode 100644 index 000000000..b518f1cf4 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Stub/ConsecutiveCalls.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Stub; + +use function array_shift; +use function count; +use PHPUnit\Framework\MockObject\Invocation; +use PHPUnit\Framework\MockObject\NoMoreReturnValuesConfiguredException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ConsecutiveCalls implements Stub +{ + /** + * @var array + */ + private array $stack; + private int $numberOfConfiguredReturnValues; + + /** + * @param array $stack + */ + public function __construct(array $stack) + { + $this->stack = $stack; + $this->numberOfConfiguredReturnValues = count($stack); + } + + /** + * @throws NoMoreReturnValuesConfiguredException + */ + public function invoke(Invocation $invocation): mixed + { + if ($this->stack === []) { + throw new NoMoreReturnValuesConfiguredException( + $invocation, + $this->numberOfConfiguredReturnValues, + ); + } + + $value = array_shift($this->stack); + + if ($value instanceof Stub) { + $value = $value->invoke($invocation); + } + + return $value; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Stub/Exception.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Stub/Exception.php new file mode 100644 index 000000000..ce0a6804a --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Stub/Exception.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Stub; + +use PHPUnit\Framework\MockObject\Invocation; +use Throwable; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Exception implements Stub +{ + private Throwable $exception; + + public function __construct(Throwable $exception) + { + $this->exception = $exception; + } + + /** + * @throws Throwable + */ + public function invoke(Invocation $invocation): never + { + throw $this->exception; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Stub/ReturnArgument.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Stub/ReturnArgument.php new file mode 100644 index 000000000..daca5099d --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Stub/ReturnArgument.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 PHPUnit\Framework\MockObject\Stub; + +use PHPUnit\Framework\MockObject\Invocation; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ReturnArgument implements Stub +{ + private int $argumentIndex; + + public function __construct(int $argumentIndex) + { + $this->argumentIndex = $argumentIndex; + } + + public function invoke(Invocation $invocation): mixed + { + return $invocation->parameters()[$this->argumentIndex] ?? null; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Stub/ReturnCallback.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Stub/ReturnCallback.php new file mode 100644 index 000000000..4e4cd5310 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Stub/ReturnCallback.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Stub; + +use function call_user_func_array; +use PHPUnit\Framework\MockObject\Invocation; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ReturnCallback implements Stub +{ + /** + * @var callable + */ + private $callback; + + public function __construct(callable $callback) + { + $this->callback = $callback; + } + + public function invoke(Invocation $invocation): mixed + { + return call_user_func_array($this->callback, $invocation->parameters()); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Stub/ReturnReference.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Stub/ReturnReference.php new file mode 100644 index 000000000..448df4527 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Stub/ReturnReference.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 PHPUnit\Framework\MockObject\Stub; + +use PHPUnit\Framework\MockObject\Invocation; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ReturnReference implements Stub +{ + private mixed $reference; + + public function __construct(mixed &$reference) + { + $this->reference = &$reference; + } + + public function invoke(Invocation $invocation): mixed + { + return $this->reference; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Stub/ReturnSelf.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Stub/ReturnSelf.php new file mode 100644 index 000000000..4101d71ac --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Stub/ReturnSelf.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 PHPUnit\Framework\MockObject\Stub; + +use PHPUnit\Framework\MockObject\Invocation; +use PHPUnit\Framework\MockObject\RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ReturnSelf implements Stub +{ + /** + * @throws RuntimeException + */ + public function invoke(Invocation $invocation): object + { + return $invocation->object(); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Stub/ReturnStub.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Stub/ReturnStub.php new file mode 100644 index 000000000..a2d881c33 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Stub/ReturnStub.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 PHPUnit\Framework\MockObject\Stub; + +use PHPUnit\Framework\MockObject\Invocation; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ReturnStub implements Stub +{ + private mixed $value; + + public function __construct(mixed $value) + { + $this->value = $value; + } + + public function invoke(Invocation $invocation): mixed + { + return $this->value; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Stub/ReturnValueMap.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Stub/ReturnValueMap.php new file mode 100644 index 000000000..c54abc353 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Stub/ReturnValueMap.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 PHPUnit\Framework\MockObject\Stub; + +use function array_pop; +use function count; +use function is_array; +use PHPUnit\Framework\MockObject\Invocation; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ReturnValueMap implements Stub +{ + /** + * @var array + */ + private array $valueMap; + + /** + * @param array $valueMap + */ + public function __construct(array $valueMap) + { + $this->valueMap = $valueMap; + } + + public function invoke(Invocation $invocation): mixed + { + $parameterCount = count($invocation->parameters()); + + foreach ($this->valueMap as $map) { + if (!is_array($map) || $parameterCount !== (count($map) - 1)) { + continue; + } + + $return = array_pop($map); + + if ($invocation->parameters() === $map) { + return $return; + } + } + + return null; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Stub/Stub.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Stub/Stub.php new file mode 100644 index 000000000..46d9e53a9 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Runtime/Stub/Stub.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\MockObject\Stub; + +use PHPUnit\Framework\MockObject\Invocation; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +interface Stub +{ + /** + * Fakes the processing of the invocation $invocation by returning a + * specific value. + */ + public function invoke(Invocation $invocation): mixed; +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Stub.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Stub.php deleted file mode 100644 index 2b032e2dc..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Stub.php +++ /dev/null @@ -1,26 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -use PHPUnit\Framework\MockObject\Builder\InvocationStubber; - -/** - * @method InvocationStubber method($constraint) - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - */ -interface Stub -{ - public function __phpunit_getInvocationHandler(): InvocationHandler; - - public function __phpunit_hasMatchers(): bool; - - public function __phpunit_setReturnValueGeneration(bool $returnValueGeneration): void; -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Stub/ConsecutiveCalls.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Stub/ConsecutiveCalls.php deleted file mode 100644 index 8b01656f6..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Stub/ConsecutiveCalls.php +++ /dev/null @@ -1,57 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Stub; - -use function array_shift; -use function sprintf; -use PHPUnit\Framework\MockObject\Invocation; -use SebastianBergmann\Exporter\Exporter; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class ConsecutiveCalls implements Stub -{ - /** - * @var array - */ - private $stack; - - /** - * @var mixed - */ - private $value; - - public function __construct(array $stack) - { - $this->stack = $stack; - } - - public function invoke(Invocation $invocation) - { - $this->value = array_shift($this->stack); - - if ($this->value instanceof Stub) { - $this->value = $this->value->invoke($invocation); - } - - return $this->value; - } - - public function toString(): string - { - $exporter = new Exporter; - - return sprintf( - 'return user-specified value %s', - $exporter->export($this->value), - ); - } -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Stub/Exception.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Stub/Exception.php deleted file mode 100644 index aa9074eb6..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Stub/Exception.php +++ /dev/null @@ -1,46 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Stub; - -use function sprintf; -use PHPUnit\Framework\MockObject\Invocation; -use SebastianBergmann\Exporter\Exporter; -use Throwable; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class Exception implements Stub -{ - private $exception; - - public function __construct(Throwable $exception) - { - $this->exception = $exception; - } - - /** - * @throws Throwable - */ - public function invoke(Invocation $invocation): void - { - throw $this->exception; - } - - public function toString(): string - { - $exporter = new Exporter; - - return sprintf( - 'raise user-specified exception %s', - $exporter->export($this->exception), - ); - } -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnArgument.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnArgument.php deleted file mode 100644 index c7b3f8f41..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnArgument.php +++ /dev/null @@ -1,41 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Stub; - -use function sprintf; -use PHPUnit\Framework\MockObject\Invocation; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class ReturnArgument implements Stub -{ - /** - * @var int - */ - private $argumentIndex; - - public function __construct($argumentIndex) - { - $this->argumentIndex = $argumentIndex; - } - - public function invoke(Invocation $invocation) - { - if (isset($invocation->getParameters()[$this->argumentIndex])) { - return $invocation->getParameters()[$this->argumentIndex]; - } - } - - public function toString(): string - { - return sprintf('return argument #%d', $this->argumentIndex); - } -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnCallback.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnCallback.php deleted file mode 100644 index 0f24aafc5..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnCallback.php +++ /dev/null @@ -1,59 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Stub; - -use function call_user_func_array; -use function get_class; -use function is_array; -use function is_object; -use function sprintf; -use PHPUnit\Framework\MockObject\Invocation; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class ReturnCallback implements Stub -{ - private $callback; - - public function __construct($callback) - { - $this->callback = $callback; - } - - public function invoke(Invocation $invocation) - { - return call_user_func_array($this->callback, $invocation->getParameters()); - } - - public function toString(): string - { - if (is_array($this->callback)) { - if (is_object($this->callback[0])) { - $class = get_class($this->callback[0]); - $type = '->'; - } else { - $class = $this->callback[0]; - $type = '::'; - } - - return sprintf( - 'return result of user defined callback %s%s%s() with the ' . - 'passed arguments', - $class, - $type, - $this->callback[1], - ); - } - - return 'return result of user defined callback ' . $this->callback . - ' with the passed arguments'; - } -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnReference.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnReference.php deleted file mode 100644 index ea2bb735a..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnReference.php +++ /dev/null @@ -1,45 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Stub; - -use function sprintf; -use PHPUnit\Framework\MockObject\Invocation; -use SebastianBergmann\Exporter\Exporter; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class ReturnReference implements Stub -{ - /** - * @var mixed - */ - private $reference; - - public function __construct(&$reference) - { - $this->reference = &$reference; - } - - public function invoke(Invocation $invocation) - { - return $this->reference; - } - - public function toString(): string - { - $exporter = new Exporter; - - return sprintf( - 'return user-specified reference %s', - $exporter->export($this->reference), - ); - } -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnSelf.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnSelf.php deleted file mode 100644 index 6d2137bfb..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnSelf.php +++ /dev/null @@ -1,32 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Stub; - -use PHPUnit\Framework\MockObject\Invocation; -use PHPUnit\Framework\MockObject\RuntimeException; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class ReturnSelf implements Stub -{ - /** - * @throws RuntimeException - */ - public function invoke(Invocation $invocation) - { - return $invocation->getObject(); - } - - public function toString(): string - { - return 'return the current object'; - } -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnStub.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnStub.php deleted file mode 100644 index 4ecbc3b92..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnStub.php +++ /dev/null @@ -1,45 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Stub; - -use function sprintf; -use PHPUnit\Framework\MockObject\Invocation; -use SebastianBergmann\Exporter\Exporter; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class ReturnStub implements Stub -{ - /** - * @var mixed - */ - private $value; - - public function __construct($value) - { - $this->value = $value; - } - - public function invoke(Invocation $invocation) - { - return $this->value; - } - - public function toString(): string - { - $exporter = new Exporter; - - return sprintf( - 'return user-specified value %s', - $exporter->export($this->value), - ); - } -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnValueMap.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnValueMap.php deleted file mode 100644 index 5fcd3a09a..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Stub/ReturnValueMap.php +++ /dev/null @@ -1,53 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Stub; - -use function array_pop; -use function count; -use function is_array; -use PHPUnit\Framework\MockObject\Invocation; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class ReturnValueMap implements Stub -{ - /** - * @var array - */ - private $valueMap; - - public function __construct(array $valueMap) - { - $this->valueMap = $valueMap; - } - - public function invoke(Invocation $invocation) - { - $parameterCount = count($invocation->getParameters()); - - foreach ($this->valueMap as $map) { - if (!is_array($map) || $parameterCount !== (count($map) - 1)) { - continue; - } - - $return = array_pop($map); - - if ($invocation->getParameters() === $map) { - return $return; - } - } - } - - public function toString(): string - { - return 'return value from a map'; - } -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Stub/Stub.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Stub/Stub.php deleted file mode 100644 index 15cfce5c3..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Stub/Stub.php +++ /dev/null @@ -1,27 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject\Stub; - -use PHPUnit\Framework\MockObject\Invocation; -use PHPUnit\Framework\SelfDescribing; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -interface Stub extends SelfDescribing -{ - /** - * Fakes the processing of the invocation $invocation by returning a - * specific value. - * - * @param Invocation $invocation The invocation which was mocked and matched by the current method and argument matchers - */ - public function invoke(Invocation $invocation); -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Verifiable.php b/app/vendor/phpunit/phpunit/src/Framework/MockObject/Verifiable.php deleted file mode 100644 index 8c9a82c5a..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/MockObject/Verifiable.php +++ /dev/null @@ -1,26 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework\MockObject; - -use PHPUnit\Framework\ExpectationFailedException; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -interface Verifiable -{ - /** - * Verifies that the current expectation is valid. If everything is OK the - * code should just return, if not it must throw an exception. - * - * @throws ExpectationFailedException - */ - public function verify(): void; -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/NativeType.php b/app/vendor/phpunit/phpunit/src/Framework/NativeType.php new file mode 100644 index 000000000..0220e8845 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/NativeType.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +enum NativeType: string +{ + case Array = 'array'; + case Bool = 'bool'; + case Callable = 'callable'; + case ClosedResource = 'resource (closed)'; + case Float = 'float'; + case Int = 'int'; + case Iterable = 'iterable'; + case Null = 'null'; + case Numeric = 'numeric'; + case Object = 'object'; + case Resource = 'resource'; + case Scalar = 'scalar'; + case String = 'string'; +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Reorderable.php b/app/vendor/phpunit/phpunit/src/Framework/Reorderable.php index 34951f8dc..10f6e9431 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Reorderable.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Reorderable.php @@ -10,6 +10,8 @@ namespace PHPUnit\Framework; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ interface Reorderable diff --git a/app/vendor/phpunit/phpunit/src/Framework/SelfDescribing.php b/app/vendor/phpunit/phpunit/src/Framework/SelfDescribing.php index 6513f1439..bdf91a4d1 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/SelfDescribing.php +++ b/app/vendor/phpunit/phpunit/src/Framework/SelfDescribing.php @@ -10,6 +10,8 @@ namespace PHPUnit\Framework; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This interface is not covered by the backward compatibility promise for PHPUnit */ interface SelfDescribing diff --git a/app/vendor/phpunit/phpunit/src/Framework/SkippedTestCase.php b/app/vendor/phpunit/phpunit/src/Framework/SkippedTestCase.php deleted file mode 100644 index 49d73759b..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/SkippedTestCase.php +++ /dev/null @@ -1,68 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -use SebastianBergmann\RecursionContext\InvalidArgumentException; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class SkippedTestCase extends TestCase -{ - /** - * @var ?bool - */ - protected $backupGlobals = false; - - /** - * @var ?bool - */ - protected $backupStaticAttributes = false; - - /** - * @var ?bool - */ - protected $runTestInSeparateProcess = false; - - /** - * @var string - */ - private $message; - - public function __construct(string $className, string $methodName, string $message = '') - { - parent::__construct($className . '::' . $methodName); - - $this->message = $message; - } - - public function getMessage(): string - { - return $this->message; - } - - /** - * Returns a string representation of the test case. - * - * @throws InvalidArgumentException - */ - public function toString(): string - { - return $this->getName(); - } - - /** - * @throws Exception - */ - protected function runTest(): void - { - $this->markTestSkipped($this->message); - } -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/Test.php b/app/vendor/phpunit/phpunit/src/Framework/Test.php index 2f218ad9f..b3e862f7c 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/Test.php +++ b/app/vendor/phpunit/phpunit/src/Framework/Test.php @@ -16,8 +16,5 @@ */ interface Test extends Countable { - /** - * Runs a test and collects its result in a TestResult instance. - */ - public function run(?TestResult $result = null): TestResult; + public function run(): void; } diff --git a/app/vendor/phpunit/phpunit/src/Framework/TestBuilder.php b/app/vendor/phpunit/phpunit/src/Framework/TestBuilder.php index 77404df08..8e484c6d1 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/TestBuilder.php +++ b/app/vendor/phpunit/phpunit/src/Framework/TestBuilder.php @@ -9,231 +9,287 @@ */ namespace PHPUnit\Framework; +use function array_merge; use function assert; -use function count; -use function get_class; -use function sprintf; -use function trim; -use PHPUnit\Util\Filter; -use PHPUnit\Util\InvalidDataSetException; -use PHPUnit\Util\Test as TestUtil; +use PHPUnit\Metadata\Api\DataProvider; +use PHPUnit\Metadata\Api\Groups; +use PHPUnit\Metadata\Api\ProvidedData; +use PHPUnit\Metadata\Api\Requirements; +use PHPUnit\Metadata\BackupGlobals; +use PHPUnit\Metadata\BackupStaticProperties; +use PHPUnit\Metadata\ExcludeGlobalVariableFromBackup; +use PHPUnit\Metadata\ExcludeStaticPropertyFromBackup; +use PHPUnit\Metadata\Parser\Registry as MetadataRegistry; +use PHPUnit\Metadata\PreserveGlobalState; +use PHPUnit\Runner\ErrorHandler; +use PHPUnit\TextUI\Configuration\Registry as ConfigurationRegistry; use ReflectionClass; -use Throwable; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ -final class TestBuilder +final readonly class TestBuilder { - public function build(ReflectionClass $theClass, string $methodName): Test + /** + * @param ReflectionClass $theClass + * @param non-empty-string $methodName + * @param list $groups + * + * @throws InvalidDataProviderException + */ + public function build(ReflectionClass $theClass, string $methodName, array $groups = []): Test { $className = $theClass->getName(); - if (!$theClass->isInstantiable()) { - return new ErrorTestCase( - sprintf('Cannot instantiate class "%s".', $className), + $data = null; + + if ($this->requirementsSatisfied($className, $methodName)) { + try { + ErrorHandler::instance()->enterTestCaseContext($className, $methodName); + + $data = (new DataProvider)->providedData($className, $methodName); + } finally { + ErrorHandler::instance()->leaveTestCaseContext(); + } + } + + if ($data !== null) { + return $this->buildDataProviderTestSuite( + $methodName, + $className, + $data, + $this->shouldTestMethodBeRunInSeparateProcess($className, $methodName), + $this->shouldGlobalStateBePreserved($className, $methodName), + $this->shouldAllTestMethodsOfTestClassBeRunInSingleSeparateProcess($className), + $this->backupSettings($className, $methodName), + $groups, ); } - $backupSettings = TestUtil::getBackupSettings( - $className, - $methodName, - ); + $test = new $className($methodName); - $preserveGlobalState = TestUtil::getPreserveGlobalStateSettings( - $className, - $methodName, + $this->configureTestCase( + $test, + $this->shouldTestMethodBeRunInSeparateProcess($className, $methodName), + $this->shouldGlobalStateBePreserved($className, $methodName), + $this->shouldAllTestMethodsOfTestClassBeRunInSingleSeparateProcess($className), + $this->backupSettings($className, $methodName), ); - $runTestInSeparateProcess = TestUtil::getProcessIsolationSettings( - $className, - $methodName, - ); + return $test; + } - $runClassInSeparateProcess = TestUtil::getClassProcessIsolationSettings( - $className, - $methodName, + /** + * @param non-empty-string $methodName + * @param class-string $className + * @param array $data + * @param array{backupGlobals: ?bool, backupGlobalsExcludeList: list, backupStaticProperties: ?bool, backupStaticPropertiesExcludeList: array>} $backupSettings + * @param list $groups + */ + private function buildDataProviderTestSuite(string $methodName, string $className, array $data, bool $runTestInSeparateProcess, ?bool $preserveGlobalState, bool $runClassInSeparateProcess, array $backupSettings, array $groups): DataProviderTestSuite + { + $dataProviderTestSuite = DataProviderTestSuite::empty( + $className . '::' . $methodName, ); - $constructor = $theClass->getConstructor(); + $groups = array_merge( + $groups, + (new Groups)->groups($className, $methodName), + ); - if ($constructor === null) { - throw new Exception('No valid test provided.'); - } + foreach ($data as $_dataName => $_data) { + $_test = new $className($methodName); - $parameters = $constructor->getParameters(); + $_test->setData($_dataName, $_data->value()); - // TestCase() or TestCase($name) - if (count($parameters) < 2) { - $test = $this->buildTestWithoutData($className); - } // TestCase($name, $data) - else { - try { - $data = TestUtil::getProvidedData( - $className, - $methodName, - ); - } catch (IncompleteTestError $e) { - $message = sprintf( - "Test for %s::%s marked incomplete by data provider\n%s", - $className, - $methodName, - $this->throwableToString($e), - ); - - $data = new IncompleteTestCase($className, $methodName, $message); - } catch (SkippedTestError $e) { - $message = sprintf( - "Test for %s::%s skipped by data provider\n%s", - $className, - $methodName, - $this->throwableToString($e), - ); - - $data = new SkippedTestCase($className, $methodName, $message); - } catch (Throwable $t) { - $message = sprintf( - "The data provider specified for %s::%s is invalid.\n%s", - $className, - $methodName, - $this->throwableToString($t), - ); - - $data = new ErrorTestCase($message); - } - - // Test method with @dataProvider. - if (isset($data)) { - $test = $this->buildDataProviderTestSuite( - $methodName, - $className, - $data, - $runTestInSeparateProcess, - $preserveGlobalState, - $runClassInSeparateProcess, - $backupSettings, - ); - } else { - $test = $this->buildTestWithoutData($className); - } - } - - if ($test instanceof TestCase) { - $test->setName($methodName); $this->configureTestCase( - $test, + $_test, $runTestInSeparateProcess, $preserveGlobalState, $runClassInSeparateProcess, $backupSettings, ); + + $dataProviderTestSuite->addTest($_test, $groups); } - return $test; + return $dataProviderTestSuite; } - /** @psalm-param class-string $className */ - private function buildTestWithoutData(string $className) + /** + * @param array{backupGlobals: ?bool, backupGlobalsExcludeList: list, backupStaticProperties: ?bool, backupStaticPropertiesExcludeList: array>} $backupSettings + */ + private function configureTestCase(TestCase $test, bool $runTestInSeparateProcess, ?bool $preserveGlobalState, bool $runClassInSeparateProcess, array $backupSettings): void { - return new $className; - } + if ($runTestInSeparateProcess) { + $test->setRunTestInSeparateProcess(true); + } - /** @psalm-param class-string $className */ - private function buildDataProviderTestSuite( - string $methodName, - string $className, - $data, - bool $runTestInSeparateProcess, - ?bool $preserveGlobalState, - bool $runClassInSeparateProcess, - array $backupSettings - ): DataProviderTestSuite { - $dataProviderTestSuite = new DataProviderTestSuite( - $className . '::' . $methodName, - ); + if ($runClassInSeparateProcess) { + $test->setRunClassInSeparateProcess(true); + } - $groups = TestUtil::getGroups($className, $methodName); + if ($preserveGlobalState !== null) { + $test->setPreserveGlobalState($preserveGlobalState); + } + + if ($backupSettings['backupGlobals'] !== null) { + $test->setBackupGlobals($backupSettings['backupGlobals']); + } else { + $test->setBackupGlobals(ConfigurationRegistry::get()->backupGlobals()); + } + + $test->setBackupGlobalsExcludeList($backupSettings['backupGlobalsExcludeList']); - if ($data instanceof ErrorTestCase || - $data instanceof SkippedTestCase || - $data instanceof IncompleteTestCase) { - $dataProviderTestSuite->addTest($data, $groups); + if ($backupSettings['backupStaticProperties'] !== null) { + $test->setBackupStaticProperties($backupSettings['backupStaticProperties']); } else { - foreach ($data as $_dataName => $_data) { - $_test = new $className($methodName, $_data, $_dataName); + $test->setBackupStaticProperties(ConfigurationRegistry::get()->backupStaticProperties()); + } - assert($_test instanceof TestCase); + $test->setBackupStaticPropertiesExcludeList($backupSettings['backupStaticPropertiesExcludeList']); + } - $this->configureTestCase( - $_test, - $runTestInSeparateProcess, - $preserveGlobalState, - $runClassInSeparateProcess, - $backupSettings, - ); + /** + * @param class-string $className + * @param non-empty-string $methodName + * + * @return array{backupGlobals: ?bool, backupGlobalsExcludeList: list, backupStaticProperties: ?bool, backupStaticPropertiesExcludeList: array>} + */ + private function backupSettings(string $className, string $methodName): array + { + $metadataForClass = MetadataRegistry::parser()->forClass($className); + $metadataForMethod = MetadataRegistry::parser()->forMethod($className, $methodName); + $metadataForClassAndMethod = MetadataRegistry::parser()->forClassAndMethod($className, $methodName); - $dataProviderTestSuite->addTest($_test, $groups); + $backupGlobals = null; + $backupGlobalsExcludeList = []; + + if ($metadataForMethod->isBackupGlobals()->isNotEmpty()) { + $metadata = $metadataForMethod->isBackupGlobals()->asArray()[0]; + + assert($metadata instanceof BackupGlobals); + + if ($metadata->enabled()) { + $backupGlobals = true; + } + } elseif ($metadataForClass->isBackupGlobals()->isNotEmpty()) { + $metadata = $metadataForClass->isBackupGlobals()->asArray()[0]; + + assert($metadata instanceof BackupGlobals); + + if ($metadata->enabled()) { + $backupGlobals = true; } } - return $dataProviderTestSuite; - } + foreach ($metadataForClassAndMethod->isExcludeGlobalVariableFromBackup() as $metadata) { + assert($metadata instanceof ExcludeGlobalVariableFromBackup); - private function configureTestCase( - TestCase $test, - bool $runTestInSeparateProcess, - ?bool $preserveGlobalState, - bool $runClassInSeparateProcess, - array $backupSettings - ): void { - if ($runTestInSeparateProcess) { - $test->setRunTestInSeparateProcess(true); + $backupGlobalsExcludeList[] = $metadata->globalVariableName(); + } - if ($preserveGlobalState !== null) { - $test->setPreserveGlobalState($preserveGlobalState); + $backupStaticProperties = null; + $backupStaticPropertiesExcludeList = []; + + if ($metadataForMethod->isBackupStaticProperties()->isNotEmpty()) { + $metadata = $metadataForMethod->isBackupStaticProperties()->asArray()[0]; + + assert($metadata instanceof BackupStaticProperties); + + if ($metadata->enabled()) { + $backupStaticProperties = true; + } + } elseif ($metadataForClass->isBackupStaticProperties()->isNotEmpty()) { + $metadata = $metadataForClass->isBackupStaticProperties()->asArray()[0]; + + assert($metadata instanceof BackupStaticProperties); + + if ($metadata->enabled()) { + $backupStaticProperties = true; } } - if ($runClassInSeparateProcess) { - $test->setRunClassInSeparateProcess(true); + foreach ($metadataForClassAndMethod->isExcludeStaticPropertyFromBackup() as $metadata) { + assert($metadata instanceof ExcludeStaticPropertyFromBackup); - if ($preserveGlobalState !== null) { - $test->setPreserveGlobalState($preserveGlobalState); + if (!isset($backupStaticPropertiesExcludeList[$metadata->className()])) { + $backupStaticPropertiesExcludeList[$metadata->className()] = []; } + + $backupStaticPropertiesExcludeList[$metadata->className()][] = $metadata->propertyName(); } - if ($backupSettings['backupGlobals'] !== null) { - $test->setBackupGlobals($backupSettings['backupGlobals']); + return [ + 'backupGlobals' => $backupGlobals, + 'backupGlobalsExcludeList' => $backupGlobalsExcludeList, + 'backupStaticProperties' => $backupStaticProperties, + 'backupStaticPropertiesExcludeList' => $backupStaticPropertiesExcludeList, + ]; + } + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + private function shouldGlobalStateBePreserved(string $className, string $methodName): ?bool + { + $metadataForMethod = MetadataRegistry::parser()->forMethod($className, $methodName); + + if ($metadataForMethod->isPreserveGlobalState()->isNotEmpty()) { + $metadata = $metadataForMethod->isPreserveGlobalState()->asArray()[0]; + + assert($metadata instanceof PreserveGlobalState); + + return $metadata->enabled(); } - if ($backupSettings['backupStaticAttributes'] !== null) { - $test->setBackupStaticAttributes( - $backupSettings['backupStaticAttributes'], - ); + $metadataForClass = MetadataRegistry::parser()->forClass($className); + + if ($metadataForClass->isPreserveGlobalState()->isNotEmpty()) { + $metadata = $metadataForClass->isPreserveGlobalState()->asArray()[0]; + + assert($metadata instanceof PreserveGlobalState); + + return $metadata->enabled(); } + + return null; } - private function throwableToString(Throwable $t): string + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + private function shouldTestMethodBeRunInSeparateProcess(string $className, string $methodName): bool { - $message = $t->getMessage(); - - if (empty(trim($message))) { - $message = ''; + if (MetadataRegistry::parser()->forClass($className)->isRunTestsInSeparateProcesses()->isNotEmpty()) { + return true; } - if ($t instanceof InvalidDataSetException) { - return sprintf( - "%s\n%s", - $message, - Filter::getFilteredStacktrace($t), - ); + if (MetadataRegistry::parser()->forMethod($className, $methodName)->isRunInSeparateProcess()->isNotEmpty()) { + return true; } - return sprintf( - "%s: %s\n%s", - get_class($t), - $message, - Filter::getFilteredStacktrace($t), - ); + return false; + } + + /** + * @param class-string $className + */ + private function shouldAllTestMethodsOfTestClassBeRunInSingleSeparateProcess(string $className): bool + { + return MetadataRegistry::parser()->forClass($className)->isRunClassInSeparateProcess()->isNotEmpty(); + } + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + private function requirementsSatisfied(string $className, string $methodName): bool + { + return (new Requirements)->requirementsNotSatisfiedFor($className, $methodName) === []; } } diff --git a/app/vendor/phpunit/phpunit/src/Framework/TestCase.php b/app/vendor/phpunit/phpunit/src/Framework/TestCase.php index 30dd5b954..97282664e 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/TestCase.php +++ b/app/vendor/phpunit/phpunit/src/Framework/TestCase.php @@ -9,116 +9,100 @@ */ namespace PHPUnit\Framework; -use const LC_ALL; -use const LC_COLLATE; -use const LC_CTYPE; -use const LC_MONETARY; -use const LC_NUMERIC; -use const LC_TIME; -use const PATHINFO_FILENAME; use const PHP_EOL; -use const PHP_URL_PATH; -use function array_filter; -use function array_flip; use function array_keys; use function array_merge; -use function array_pop; -use function array_search; -use function array_unique; +use function array_reverse; use function array_values; -use function basename; -use function call_user_func; +use function assert; use function chdir; use function class_exists; use function clearstatcache; use function count; -use function debug_backtrace; use function defined; +use function error_clear_last; use function explode; -use function get_class; -use function get_include_path; +use function fclose; use function getcwd; use function implode; use function in_array; +use function ini_get; use function ini_set; use function is_array; use function is_callable; use function is_int; use function is_object; use function is_string; +use function is_writable; use function libxml_clear_errors; use function method_exists; use function ob_end_clean; +use function ob_get_clean; use function ob_get_contents; use function ob_get_level; use function ob_start; -use function parse_url; -use function pathinfo; +use function preg_match; use function preg_replace; -use function serialize; -use function setlocale; +use function putenv; +use function restore_error_handler; +use function restore_exception_handler; +use function set_error_handler; +use function set_exception_handler; use function sprintf; -use function strpos; -use function substr; -use function sys_get_temp_dir; -use function tempnam; +use function str_contains; +use function stream_get_contents; +use function stream_get_meta_data; +use function tmpfile; use function trim; -use function var_export; +use AssertionError; use DeepCopy\DeepCopy; +use PHPUnit\Event; +use PHPUnit\Event\NoPreviousThrowableException; use PHPUnit\Framework\Constraint\Exception as ExceptionConstraint; use PHPUnit\Framework\Constraint\ExceptionCode; -use PHPUnit\Framework\Constraint\ExceptionMessage; -use PHPUnit\Framework\Constraint\ExceptionMessageRegularExpression; -use PHPUnit\Framework\Constraint\LogicalOr; -use PHPUnit\Framework\Error\Deprecated; -use PHPUnit\Framework\Error\Error; -use PHPUnit\Framework\Error\Notice; -use PHPUnit\Framework\Error\Warning as WarningError; -use PHPUnit\Framework\MockObject\Generator as MockGenerator; +use PHPUnit\Framework\Constraint\ExceptionMessageIsOrContains; +use PHPUnit\Framework\Constraint\ExceptionMessageMatchesRegularExpression; +use PHPUnit\Framework\MockObject\Exception as MockObjectException; +use PHPUnit\Framework\MockObject\Generator\Generator as MockGenerator; use PHPUnit\Framework\MockObject\MockBuilder; use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\MockObject\MockObjectInternal; use PHPUnit\Framework\MockObject\Rule\AnyInvokedCount as AnyInvokedCountMatcher; -use PHPUnit\Framework\MockObject\Rule\InvokedAtIndex as InvokedAtIndexMatcher; use PHPUnit\Framework\MockObject\Rule\InvokedAtLeastCount as InvokedAtLeastCountMatcher; use PHPUnit\Framework\MockObject\Rule\InvokedAtLeastOnce as InvokedAtLeastOnceMatcher; use PHPUnit\Framework\MockObject\Rule\InvokedAtMostCount as InvokedAtMostCountMatcher; +use PHPUnit\Framework\MockObject\Rule\InvokedCount; use PHPUnit\Framework\MockObject\Rule\InvokedCount as InvokedCountMatcher; use PHPUnit\Framework\MockObject\Stub; -use PHPUnit\Framework\MockObject\Stub\ConsecutiveCalls as ConsecutiveCallsStub; use PHPUnit\Framework\MockObject\Stub\Exception as ExceptionStub; -use PHPUnit\Framework\MockObject\Stub\ReturnArgument as ReturnArgumentStub; -use PHPUnit\Framework\MockObject\Stub\ReturnCallback as ReturnCallbackStub; -use PHPUnit\Framework\MockObject\Stub\ReturnSelf as ReturnSelfStub; -use PHPUnit\Framework\MockObject\Stub\ReturnStub; -use PHPUnit\Framework\MockObject\Stub\ReturnValueMap as ReturnValueMapStub; -use PHPUnit\Runner\BaseTestRunner; -use PHPUnit\Runner\PhptTestCase; -use PHPUnit\Util\Cloner; -use PHPUnit\Util\Exception as UtilException; -use PHPUnit\Util\GlobalState; -use PHPUnit\Util\PHP\AbstractPhpProcess; +use PHPUnit\Framework\TestSize\TestSize; +use PHPUnit\Framework\TestStatus\TestStatus; +use PHPUnit\Metadata\Api\Groups; +use PHPUnit\Metadata\Api\HookMethods; +use PHPUnit\Metadata\Api\Requirements; +use PHPUnit\Metadata\Parser\Registry as MetadataRegistry; +use PHPUnit\Metadata\WithEnvironmentVariable; +use PHPUnit\Runner\BackedUpEnvironmentVariable; +use PHPUnit\Runner\DeprecationCollector\Facade as DeprecationCollector; +use PHPUnit\Runner\HookMethodCollection; +use PHPUnit\Runner\ShutdownHandler; +use PHPUnit\TestRunner\TestResult\PassedTests; +use PHPUnit\TextUI\Configuration\Registry as ConfigurationRegistry; +use PHPUnit\Util\Exporter; use PHPUnit\Util\Test as TestUtil; -use Prophecy\Exception\Doubler\ClassNotFoundException; -use Prophecy\Exception\Doubler\DoubleException; -use Prophecy\Exception\Doubler\InterfaceNotFoundException; -use Prophecy\Exception\Prediction\PredictionException; -use Prophecy\Prophecy\MethodProphecy; -use Prophecy\Prophecy\ObjectProphecy; -use Prophecy\Prophet; use ReflectionClass; use ReflectionException; +use ReflectionObject; use SebastianBergmann\CodeCoverage\UnintentionallyCoveredCodeException; use SebastianBergmann\Comparator\Comparator; use SebastianBergmann\Comparator\Factory as ComparatorFactory; use SebastianBergmann\Diff\Differ; -use SebastianBergmann\Exporter\Exporter; -use SebastianBergmann\GlobalState\ExcludeList; +use SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder; +use SebastianBergmann\GlobalState\ExcludeList as GlobalStateExcludeList; use SebastianBergmann\GlobalState\Restorer; use SebastianBergmann\GlobalState\Snapshot; +use SebastianBergmann\Invoker\TimeoutException; use SebastianBergmann\ObjectEnumerator\Enumerator; -use SebastianBergmann\RecursionContext\InvalidArgumentException; -use SebastianBergmann\Template\Template; -use SoapClient; use Throwable; /** @@ -126,2553 +110,2380 @@ */ abstract class TestCase extends Assert implements Reorderable, SelfDescribing, Test { - private const LOCALE_CATEGORIES = [LC_ALL, LC_COLLATE, LC_CTYPE, LC_MONETARY, LC_NUMERIC, LC_TIME]; + private ?bool $backupGlobals = null; /** - * @var ?bool + * @var list */ - protected $backupGlobals; + private array $backupGlobalsExcludeList = []; + private ?bool $backupStaticProperties = null; /** - * @var string[] + * @var array> */ - protected $backupGlobalsExcludeList = []; + private array $backupStaticPropertiesExcludeList = []; + private ?Snapshot $snapshot = null; /** - * @var string[] - * - * @deprecated Use $backupGlobalsExcludeList instead - */ - protected $backupGlobalsBlacklist = []; - - /** - * @var ?bool - */ - protected $backupStaticAttributes; - - /** - * @var array> - */ - protected $backupStaticAttributesExcludeList = []; - - /** - * @var array> - * - * @deprecated Use $backupStaticAttributesExcludeList instead + * @var list */ - protected $backupStaticAttributesBlacklist = []; + private ?array $backupGlobalErrorHandlers = null; /** - * @var ?bool + * @var list */ - protected $runTestInSeparateProcess; + private ?array $backupGlobalExceptionHandlers = null; + private ?bool $runClassInSeparateProcess = null; + private ?bool $runTestInSeparateProcess = null; + private bool $preserveGlobalState = false; + private bool $inIsolation = false; + private ?string $expectedException = null; + private ?string $expectedExceptionMessage = null; + private ?string $expectedExceptionMessageRegExp = null; + private null|int|string $expectedExceptionCode = null; /** - * @var bool + * @var list */ - protected $preserveGlobalState = true; + private array $backupEnvironmentVariables = []; /** * @var list */ - protected $providedTests = []; - - /** - * @var ?bool - */ - private $runClassInSeparateProcess; - - /** - * @var bool - */ - private $inIsolation = false; - - /** - * @var array - */ - private $data; - - /** - * @var int|string - */ - private $dataName; - - /** - * @var null|string - */ - private $expectedException; - - /** - * @var null|string - */ - private $expectedExceptionMessage; + private array $providedTests = []; /** - * @var null|string + * @var array */ - private $expectedExceptionMessageRegExp; + private array $data = []; + private int|string $dataName = ''; /** - * @var null|int|string + * @var non-empty-string */ - private $expectedExceptionCode; + private string $methodName; /** - * @var string + * @var list */ - private $name = ''; + private array $groups = []; /** * @var list */ - private $dependencies = []; - - /** - * @var array - */ - private $dependencyInput = []; - - /** - * @var array - */ - private $iniSettings = []; - - /** - * @var array - */ - private $locale = []; - - /** - * @var MockObject[] - */ - private $mockObjects = []; - - /** - * @var MockGenerator - */ - private $mockObjectGenerator; - - /** - * @var int - */ - private $status = BaseTestRunner::STATUS_UNKNOWN; - - /** - * @var string - */ - private $statusMessage = ''; - - /** - * @var int - */ - private $numAssertions = 0; - - /** - * @var TestResult - */ - private $result; - - /** - * @var mixed - */ - private $testResult; - - /** - * @var string - */ - private $output = ''; - - /** - * @var ?string - */ - private $outputExpectedRegex; - - /** - * @var ?string - */ - private $outputExpectedString; + private array $dependencies = []; /** - * @var mixed + * @var array> */ - private $outputCallback = false; + private array $dependencyInput = []; /** - * @var bool + * @var list */ - private $outputBufferingActive = false; + private array $mockObjects = []; + private TestStatus $status; /** - * @var int + * @var 0|positive-int */ - private $outputBufferingLevel; + private int $numberOfAssertionsPerformed = 0; + private mixed $testResult = null; + private string $output = ''; + private ?string $outputExpectedRegex = null; + private ?string $outputExpectedString = null; + private bool $outputBufferingActive = false; + private int $outputBufferingLevel; + private bool $outputRetrievedForAssertion = false; + private bool $doesNotPerformAssertions = false; + private bool $expectErrorLog = false; /** - * @var bool + * @var list */ - private $outputRetrievedForAssertion = false; + private array $customComparators = []; + private ?Event\Code\TestMethod $testValueObjectForEvents = null; + private bool $wasPrepared = false; /** - * @var ?Snapshot + * @var array */ - private $snapshot; + private array $failureTypes = []; /** - * @var Prophet + * @var list */ - private $prophet; + private array $expectedUserDeprecationMessage = []; /** - * @var bool + * @var list */ - private $beStrictAboutChangesToGlobalState = false; + private array $expectedUserDeprecationMessageRegularExpression = []; /** - * @var bool + * @var false|resource */ - private $registerMockObjectsFromTestArgumentsRecursively = false; + private mixed $errorLogCapture = false; + private false|string $previousErrorLogTarget = false; /** - * @var string[] - */ - private $warnings = []; - - /** - * @var string[] + * @param non-empty-string $name + * + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - private $groups = []; + final public function __construct(string $name) + { + $this->methodName = $name; + $this->status = TestStatus::unknown(); - /** - * @var bool - */ - private $doesNotPerformAssertions = false; + if (is_callable($this->sortId(), true)) { + $this->providedTests = [new ExecutionOrderDependency($this->sortId())]; + } + } /** - * @var Comparator[] + * This method is called before the first test of this test class is run. + * + * @codeCoverageIgnore */ - private $customComparators = []; + public static function setUpBeforeClass(): void + { + } /** - * @var string[] + * This method is called after the last test of this test class is run. + * + * @codeCoverageIgnore */ - private $doubledTypes = []; + public static function tearDownAfterClass(): void + { + } /** - * Returns a matcher that matches when the method is executed - * zero or more times. + * This method is called before each test. + * + * @codeCoverageIgnore */ - public static function any(): AnyInvokedCountMatcher + protected function setUp(): void { - return new AnyInvokedCountMatcher; } /** - * Returns a matcher that matches when the method is never executed. + * Performs assertions shared by all tests of a test case. + * + * This method is called between setUp() and test. + * + * @codeCoverageIgnore */ - public static function never(): InvokedCountMatcher + protected function assertPreConditions(): void { - return new InvokedCountMatcher(0); } /** - * Returns a matcher that matches when the method is executed - * at least N times. + * Performs assertions shared by all tests of a test case. + * + * This method is called between test and tearDown(). + * + * @codeCoverageIgnore */ - public static function atLeast(int $requiredInvocations): InvokedAtLeastCountMatcher + protected function assertPostConditions(): void { - return new InvokedAtLeastCountMatcher( - $requiredInvocations, - ); } /** - * Returns a matcher that matches when the method is executed at least once. + * This method is called after each test. + * + * @codeCoverageIgnore */ - public static function atLeastOnce(): InvokedAtLeastOnceMatcher + protected function tearDown(): void { - return new InvokedAtLeastOnceMatcher; } /** - * Returns a matcher that matches when the method is executed exactly once. + * Returns a string representation of the test case. + * + * @throws Exception + * + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public static function once(): InvokedCountMatcher + public function toString(): string { - return new InvokedCountMatcher(1); + $buffer = sprintf( + '%s::%s', + (new ReflectionClass($this))->getName(), + $this->methodName, + ); + + return $buffer . $this->dataSetAsStringWithData(); } /** - * Returns a matcher that matches when the method is executed - * exactly $count times. + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public static function exactly(int $count): InvokedCountMatcher + final public function count(): int { - return new InvokedCountMatcher($count); + return 1; } /** - * Returns a matcher that matches when the method is executed - * at most N times. + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public static function atMost(int $allowedInvocations): InvokedAtMostCountMatcher + final public function status(): TestStatus { - return new InvokedAtMostCountMatcher($allowedInvocations); + return $this->status; } /** - * Returns a matcher that matches when the method is executed - * at the given index. - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4297 + * @throws \PHPUnit\Runner\Exception + * @throws \PHPUnit\Util\Exception + * @throws \SebastianBergmann\CodeCoverage\InvalidArgumentException + * @throws \SebastianBergmann\Template\InvalidArgumentException + * @throws Exception + * @throws NoPreviousThrowableException + * @throws ProcessIsolationException + * @throws UnintentionallyCoveredCodeException * - * @codeCoverageIgnore + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public static function at(int $index): InvokedAtIndexMatcher + final public function run(): void { - $stack = debug_backtrace(); - - while (!empty($stack)) { - $frame = array_pop($stack); - - if (isset($frame['object']) && $frame['object'] instanceof self) { - $frame['object']->addWarning( - 'The at() matcher has been deprecated. It will be removed in PHPUnit 10. Please refactor your test to not rely on the order in which methods are invoked.', - ); - - break; - } + if (!$this->handleDependencies()) { + return; } - return new InvokedAtIndexMatcher($index); - } - - public static function returnValue($value): ReturnStub - { - return new ReturnStub($value); - } - - public static function returnValueMap(array $valueMap): ReturnValueMapStub - { - return new ReturnValueMapStub($valueMap); - } + if (!$this->shouldRunInSeparateProcess() || $this->requirementsNotSatisfied()) { + try { + ShutdownHandler::setMessage(sprintf('Fatal error: Premature end of PHP process when running %s.', $this->toString())); + (new TestRunner)->run($this); + } finally { + ShutdownHandler::resetMessage(); + } - public static function returnArgument(int $argumentIndex): ReturnArgumentStub - { - return new ReturnArgumentStub($argumentIndex); - } + return; + } - public static function returnCallback($callback): ReturnCallbackStub - { - return new ReturnCallbackStub($callback); + IsolatedTestRunnerRegistry::run( + $this, + $this->runClassInSeparateProcess && !$this->runTestInSeparateProcess, + $this->preserveGlobalState, + ); } /** - * Returns the current object. + * @return list * - * This method is useful when mocking a fluent interface. + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public static function returnSelf(): ReturnSelfStub - { - return new ReturnSelfStub; - } - - public static function throwException(Throwable $exception): ExceptionStub + final public function groups(): array { - return new ExceptionStub($exception); + return $this->groups; } - public static function onConsecutiveCalls(...$args): ConsecutiveCallsStub + /** + * @param list $groups + * + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + final public function setGroups(array $groups): void { - return new ConsecutiveCallsStub($args); + $this->groups = $groups; } /** - * @param int|string $dataName - * * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function __construct(?string $name = null, array $data = [], $dataName = '') + final public function nameWithDataSet(): string { - if ($name !== null) { - $this->setName($name); - } - - $this->data = $data; - $this->dataName = $dataName; + return $this->methodName . $this->dataSetAsString(); } /** - * This method is called before the first test of this test class is run. + * @return non-empty-string + * + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public static function setUpBeforeClass(): void + final public function name(): string { + return $this->methodName; } /** - * This method is called after the last test of this test class is run. + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public static function tearDownAfterClass(): void + final public function size(): TestSize { + return (new Groups)->size( + static::class, + $this->methodName, + ); } /** - * This method is called before each test. + * @internal This method is not covered by the backward compatibility promise for PHPUnit + * + * @phpstan-assert-if-true non-empty-string $this->output() */ - protected function setUp(): void + final public function hasUnexpectedOutput(): bool { + if ($this->output === '') { + return false; + } + + if ($this->expectsOutput()) { + return false; + } + + return true; } /** - * Performs assertions shared by all tests of a test case. - * - * This method is called between setUp() and test. + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - protected function assertPreConditions(): void + final public function output(): string { + if (!$this->outputBufferingActive) { + return $this->output; + } + + return (string) ob_get_contents(); } /** - * Performs assertions shared by all tests of a test case. - * - * This method is called between test and tearDown(). + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - protected function assertPostConditions(): void + final public function doesNotPerformAssertions(): bool { + return $this->doesNotPerformAssertions; } /** - * This method is called after each test. + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - protected function tearDown(): void + final public function expectsOutput(): bool { + return $this->hasExpectationOnOutput() || $this->outputRetrievedForAssertion; } /** - * Returns a string representation of the test case. + * @throws Throwable * - * @throws Exception - * @throws InvalidArgumentException + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function toString(): string + final public function runBare(): void { - try { - $class = new ReflectionClass($this); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - $e->getCode(), - $e, - ); - } - // @codeCoverageIgnoreEnd + $emitter = Event\Facade::emitter(); - $buffer = sprintf( - '%s::%s', - $class->name, - $this->getName(false), - ); + error_clear_last(); + clearstatcache(); - return $buffer . $this->getDataSetAsString(); - } + $emitter->testPreparationStarted( + $this->valueObjectForEvents(), + ); - public function count(): int - { - return 1; - } + $this->snapshotGlobalState(); + $this->snapshotGlobalErrorExceptionHandlers(); + $this->handleEnvironmentVariables(); + $this->startOutputBuffering(); - public function getActualOutputForAssertion(): string - { - $this->outputRetrievedForAssertion = true; + $hookMethods = (new HookMethods)->hookMethods(static::class); + $hasMetRequirements = false; + $this->numberOfAssertionsPerformed = 0; + $currentWorkingDirectory = getcwd(); - return $this->getActualOutput(); - } + try { + $this->checkRequirements(); + $hasMetRequirements = true; - public function expectOutputRegex(string $expectedRegex): void - { - $this->outputExpectedRegex = $expectedRegex; - } + if ($this->inIsolation) { + // @codeCoverageIgnoreStart + $this->invokeBeforeClassHookMethods($hookMethods, $emitter); + // @codeCoverageIgnoreEnd + } - public function expectOutputString(string $expectedString): void - { - $this->outputExpectedString = $expectedString; - } + if (method_exists(static::class, $this->methodName) && + MetadataRegistry::parser()->forClassAndMethod(static::class, $this->methodName)->isDoesNotPerformAssertions()->isNotEmpty()) { + $this->doesNotPerformAssertions = true; + } - /** - * @psalm-param class-string $exception - */ - public function expectException(string $exception): void - { - // @codeCoverageIgnoreStart - switch ($exception) { - case Deprecated::class: - $this->addWarning('Expecting E_DEPRECATED and E_USER_DEPRECATED is deprecated and will no longer be possible in PHPUnit 10.'); + $this->invokeBeforeTestHookMethods($hookMethods, $emitter); + $this->invokePreConditionHookMethods($hookMethods, $emitter); - break; + $emitter->testPrepared( + $this->valueObjectForEvents(), + ); - case Error::class: - $this->addWarning('Expecting E_ERROR and E_USER_ERROR is deprecated and will no longer be possible in PHPUnit 10.'); + $this->wasPrepared = true; + $this->testResult = $this->runTest(); - break; + $this->verifyDeprecationExpectations(); + $this->verifyMockObjects(); + $this->invokePostConditionHookMethods($hookMethods, $emitter); - case Notice::class: - $this->addWarning('Expecting E_STRICT, E_NOTICE, and E_USER_NOTICE is deprecated and will no longer be possible in PHPUnit 10.'); + $this->status = TestStatus::success(); + } catch (IncompleteTest $e) { + $this->status = TestStatus::incomplete($e->getMessage()); - break; + $emitter->testMarkedAsIncomplete( + $this->valueObjectForEvents(), + Event\Code\ThrowableBuilder::from($e), + ); + } catch (SkippedTest $e) { + $this->status = TestStatus::skipped($e->getMessage()); - case WarningError::class: - $this->addWarning('Expecting E_WARNING and E_USER_WARNING is deprecated and will no longer be possible in PHPUnit 10.'); + $emitter->testSkipped( + $this->valueObjectForEvents(), + $e->getMessage(), + ); + } catch (AssertionError|AssertionFailedError $e) { + $this->handleExceptionFromInvokedCountMockObjectRule($e); - break; + if (!$this->wasPrepared) { + $this->wasPrepared = true; + + $emitter->testPreparationFailed( + $this->valueObjectForEvents(), + Event\Code\ThrowableBuilder::from($e), + ); + } + + $this->status = TestStatus::failure($e->getMessage()); + + $emitter->testFailed( + $this->valueObjectForEvents(), + Event\Code\ThrowableBuilder::from($e), + Event\Code\ComparisonFailureBuilder::from($e), + ); + } catch (TimeoutException $e) { + } catch (Throwable $_e) { + if ($this->isRegisteredFailure($_e)) { + $this->status = TestStatus::failure($_e->getMessage()); + + $emitter->testFailed( + $this->valueObjectForEvents(), + Event\Code\ThrowableBuilder::from($_e), + null, + ); + } else { + $e = $this->transformException($_e); + + $this->status = TestStatus::error($e->getMessage()); + + if (!$this->wasPrepared) { + if ($e instanceof AssertionFailedError) { + $emitter->testPreparationFailed( + $this->valueObjectForEvents(), + Event\Code\ThrowableBuilder::from($e), + ); + } else { + $emitter->testPreparationErrored( + $this->valueObjectForEvents(), + Event\Code\ThrowableBuilder::from($e), + ); + } + } + + $emitter->testErrored( + $this->valueObjectForEvents(), + Event\Code\ThrowableBuilder::from($e), + ); + } } - // @codeCoverageIgnoreEnd - $this->expectedException = $exception; + $outputBufferingStopped = false; + + if (!isset($e) && + $this->hasExpectationOnOutput() && + $this->stopOutputBuffering()) { + $outputBufferingStopped = true; + + $this->performAssertionsOnOutput(); + } + + try { + $this->mockObjects = []; + + /** @phpstan-ignore catch.neverThrown */ + } catch (Throwable $e) { + Event\Facade::emitter()->testErrored( + $this->valueObjectForEvents(), + Event\Code\ThrowableBuilder::from($e), + ); + } + + // Tear down the fixture. An exception raised in tearDown() will be + // caught and passed on when no exception was raised before. + try { + if ($hasMetRequirements) { + $this->invokeAfterTestHookMethods($hookMethods, $emitter); + + if ($this->inIsolation) { + // @codeCoverageIgnoreStart + $this->invokeAfterClassHookMethods($hookMethods, $emitter); + // @codeCoverageIgnoreEnd + } + } + } catch (AssertionError|AssertionFailedError $e) { + $this->status = TestStatus::failure($e->getMessage()); + + $emitter->testFailed( + $this->valueObjectForEvents(), + Event\Code\ThrowableBuilder::from($e), + Event\Code\ComparisonFailureBuilder::from($e), + ); + } catch (Throwable $exceptionRaisedDuringTearDown) { + if (!isset($e) || $e instanceof SkippedWithMessageException) { + $this->status = TestStatus::error($exceptionRaisedDuringTearDown->getMessage()); + $e = $exceptionRaisedDuringTearDown; + + $emitter->testErrored( + $this->valueObjectForEvents(), + Event\Code\ThrowableBuilder::from($exceptionRaisedDuringTearDown), + ); + } + } + + if (!isset($e) && !isset($_e)) { + $emitter->testPassed( + $this->valueObjectForEvents(), + ); + + if (!$this->usesDataProvider()) { + PassedTests::instance()->testMethodPassed( + $this->valueObjectForEvents(), + $this->testResult, + ); + } + } + + if (!$outputBufferingStopped) { + $this->stopOutputBuffering(); + } + + clearstatcache(); + + if ($currentWorkingDirectory !== false && $currentWorkingDirectory !== getcwd()) { + chdir($currentWorkingDirectory); + } + + $this->restoreEnvironmentVariables(); + $this->restoreGlobalErrorExceptionHandlers(); + $this->restoreGlobalState(); + $this->unregisterCustomComparators(); + libxml_clear_errors(); + + $this->testValueObjectForEvents = null; + + if (isset($e)) { + $this->onNotSuccessfulTest($e); + } } /** - * @param int|string $code + * @param list $dependencies + * + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function expectExceptionCode($code): void + final public function setDependencies(array $dependencies): void { - $this->expectedExceptionCode = $code; + $this->dependencies = $dependencies; } - public function expectExceptionMessage(string $message): void + /** + * @param array> $dependencyInput + * + * @internal This method is not covered by the backward compatibility promise for PHPUnit + * + * @codeCoverageIgnore + */ + final public function setDependencyInput(array $dependencyInput): void { - $this->expectedExceptionMessage = $message; + $this->dependencyInput = $dependencyInput; } - public function expectExceptionMessageMatches(string $regularExpression): void + /** + * @return array> + * + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + final public function dependencyInput(): array { - $this->expectedExceptionMessageRegExp = $regularExpression; + return $this->dependencyInput; } /** - * Sets up an expectation for an exception to be raised by the code under test. - * Information for expected exception class, expected exception message, and - * expected exception code are retrieved from a given Exception object. + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function expectExceptionObject(\Exception $exception): void + final public function hasDependencyInput(): bool { - $this->expectException(get_class($exception)); - $this->expectExceptionMessage($exception->getMessage()); - $this->expectExceptionCode($exception->getCode()); + return $this->dependencyInput !== []; } - public function expectNotToPerformAssertions(): void + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + final public function setBackupGlobals(bool $backupGlobals): void { - $this->doesNotPerformAssertions = true; + $this->backupGlobals = $backupGlobals; } /** - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/5062 + * @param list $backupGlobalsExcludeList + * + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function expectDeprecation(): void + final public function setBackupGlobalsExcludeList(array $backupGlobalsExcludeList): void { - $this->addWarning('Expecting E_DEPRECATED and E_USER_DEPRECATED is deprecated and will no longer be possible in PHPUnit 10.'); - - $this->expectedException = Deprecated::class; + $this->backupGlobalsExcludeList = $backupGlobalsExcludeList; } /** - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/5062 + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function expectDeprecationMessage(string $message): void + final public function setBackupStaticProperties(bool $backupStaticProperties): void { - $this->addWarning('Expecting E_DEPRECATED and E_USER_DEPRECATED is deprecated and will no longer be possible in PHPUnit 10.'); - - $this->expectExceptionMessage($message); + $this->backupStaticProperties = $backupStaticProperties; } /** - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/5062 + * @param array> $backupStaticPropertiesExcludeList + * + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function expectDeprecationMessageMatches(string $regularExpression): void + final public function setBackupStaticPropertiesExcludeList(array $backupStaticPropertiesExcludeList): void { - $this->addWarning('Expecting E_DEPRECATED and E_USER_DEPRECATED is deprecated and will no longer be possible in PHPUnit 10.'); - - $this->expectExceptionMessageMatches($regularExpression); + $this->backupStaticPropertiesExcludeList = $backupStaticPropertiesExcludeList; } /** - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/5062 + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function expectNotice(): void + final public function setRunTestInSeparateProcess(bool $runTestInSeparateProcess): void { - $this->addWarning('Expecting E_STRICT, E_NOTICE, and E_USER_NOTICE is deprecated and will no longer be possible in PHPUnit 10.'); - - $this->expectedException = Notice::class; + if ($this->runTestInSeparateProcess === null) { + $this->runTestInSeparateProcess = $runTestInSeparateProcess; + } } /** - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/5062 + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function expectNoticeMessage(string $message): void + final public function setRunClassInSeparateProcess(bool $runClassInSeparateProcess): void { - $this->addWarning('Expecting E_STRICT, E_NOTICE, and E_USER_NOTICE is deprecated and will no longer be possible in PHPUnit 10.'); - - $this->expectExceptionMessage($message); + $this->runClassInSeparateProcess = $runClassInSeparateProcess; } /** - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/5062 + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function expectNoticeMessageMatches(string $regularExpression): void + final public function setPreserveGlobalState(bool $preserveGlobalState): void { - $this->addWarning('Expecting E_STRICT, E_NOTICE, and E_USER_NOTICE is deprecated and will no longer be possible in PHPUnit 10.'); - - $this->expectExceptionMessageMatches($regularExpression); + $this->preserveGlobalState = $preserveGlobalState; } /** - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/5062 + * @internal This method is not covered by the backward compatibility promise for PHPUnit + * + * @codeCoverageIgnore */ - public function expectWarning(): void + final public function setInIsolation(bool $inIsolation): void { - $this->addWarning('Expecting E_WARNING and E_USER_WARNING is deprecated and will no longer be possible in PHPUnit 10.'); - - $this->expectedException = WarningError::class; + $this->inIsolation = $inIsolation; } /** - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/5062 + * @internal This method is not covered by the backward compatibility promise for PHPUnit + * + * @codeCoverageIgnore */ - public function expectWarningMessage(string $message): void + final public function result(): mixed { - $this->addWarning('Expecting E_WARNING and E_USER_WARNING is deprecated and will no longer be possible in PHPUnit 10.'); - - $this->expectExceptionMessage($message); + return $this->testResult; } /** - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/5062 + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function expectWarningMessageMatches(string $regularExpression): void + final public function setResult(mixed $result): void { - $this->addWarning('Expecting E_WARNING and E_USER_WARNING is deprecated and will no longer be possible in PHPUnit 10.'); - - $this->expectExceptionMessageMatches($regularExpression); + $this->testResult = $result; } /** - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/5062 + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function expectError(): void + final public function registerMockObject(MockObject $mockObject): void { - $this->addWarning('Expecting E_ERROR and E_USER_ERROR is deprecated and will no longer be possible in PHPUnit 10.'); + assert($mockObject instanceof MockObjectInternal); - $this->expectedException = Error::class; + $this->mockObjects[] = $mockObject; } /** - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/5062 + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function expectErrorMessage(string $message): void + final public function addToAssertionCount(int $count): void { - $this->addWarning('Expecting E_ERROR and E_USER_ERROR is deprecated and will no longer be possible in PHPUnit 10.'); - - $this->expectExceptionMessage($message); + $this->numberOfAssertionsPerformed += $count; } /** - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/5062 + * @internal This method is not covered by the backward compatibility promise for PHPUnit + * + * @return 0|positive-int */ - public function expectErrorMessageMatches(string $regularExpression): void + final public function numberOfAssertionsPerformed(): int { - $this->addWarning('Expecting E_ERROR and E_USER_ERROR is deprecated and will no longer be possible in PHPUnit 10.'); - - $this->expectExceptionMessageMatches($regularExpression); + return $this->numberOfAssertionsPerformed; } - public function getStatus(): int + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + final public function usesDataProvider(): bool { - return $this->status; + return $this->data !== []; } - public function markAsRisky(): void + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + final public function dataName(): int|string { - $this->status = BaseTestRunner::STATUS_RISKY; + return $this->dataName; } - public function getStatusMessage(): string + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + final public function dataSetAsString(): string { - return $this->statusMessage; - } + if ($this->data !== []) { + if (is_int($this->dataName)) { + return sprintf(' with data set %s', $this->dataSetAsFilterString()); + } - public function hasFailed(): bool - { - $status = $this->getStatus(); + return sprintf(' with data set "%s"', $this->dataSetAsFilterString()); + } - return $status === BaseTestRunner::STATUS_FAILURE || $status === BaseTestRunner::STATUS_ERROR; + return ''; } /** - * Runs the test case and collects the results in a TestResult object. - * If no TestResult object is passed a new one will be created. - * - * @throws \SebastianBergmann\CodeCoverage\InvalidArgumentException - * @throws CodeCoverageException - * @throws InvalidArgumentException - * @throws UnintentionallyCoveredCodeException - * @throws UtilException + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function run(?TestResult $result = null): TestResult + final public function dataSetAsStringWithData(): string { - if ($result === null) { - $result = $this->createResult(); - } - - if (!$this instanceof ErrorTestCase && !$this instanceof WarningTestCase) { - $this->setTestResultObject($result); - } - - if (!$this instanceof ErrorTestCase && - !$this instanceof WarningTestCase && - !$this instanceof SkippedTestCase && - !$this->handleDependencies()) { - return $result; - } - - if ($this->runInSeparateProcess()) { - $runEntireClass = $this->runClassInSeparateProcess && !$this->runTestInSeparateProcess; - - try { - $class = new ReflectionClass($this); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - $e->getCode(), - $e, - ); - } - // @codeCoverageIgnoreEnd - - if ($runEntireClass) { - $template = new Template( - __DIR__ . '/../Util/PHP/Template/TestCaseClass.tpl', - ); - } else { - $template = new Template( - __DIR__ . '/../Util/PHP/Template/TestCaseMethod.tpl', - ); - } - - if ($this->preserveGlobalState) { - $constants = GlobalState::getConstantsAsString(); - $globals = GlobalState::getGlobalsAsString(); - $includedFiles = GlobalState::getIncludedFilesAsString(); - $iniSettings = GlobalState::getIniSettingsAsString(); - } else { - $constants = ''; - - if (!empty($GLOBALS['__PHPUNIT_BOOTSTRAP'])) { - $globals = '$GLOBALS[\'__PHPUNIT_BOOTSTRAP\'] = ' . var_export($GLOBALS['__PHPUNIT_BOOTSTRAP'], true) . ";\n"; - } else { - $globals = ''; - } - - $includedFiles = ''; - $iniSettings = ''; - } - - $coverage = $result->getCollectCodeCoverageInformation() ? 'true' : 'false'; - $isStrictAboutTestsThatDoNotTestAnything = $result->isStrictAboutTestsThatDoNotTestAnything() ? 'true' : 'false'; - $isStrictAboutOutputDuringTests = $result->isStrictAboutOutputDuringTests() ? 'true' : 'false'; - $enforcesTimeLimit = $result->enforcesTimeLimit() ? 'true' : 'false'; - $isStrictAboutTodoAnnotatedTests = $result->isStrictAboutTodoAnnotatedTests() ? 'true' : 'false'; - $isStrictAboutResourceUsageDuringSmallTests = $result->isStrictAboutResourceUsageDuringSmallTests() ? 'true' : 'false'; - - if (defined('PHPUNIT_COMPOSER_INSTALL')) { - $composerAutoload = var_export(PHPUNIT_COMPOSER_INSTALL, true); - } else { - $composerAutoload = '\'\''; - } - - if (defined('__PHPUNIT_PHAR__')) { - $phar = var_export(__PHPUNIT_PHAR__, true); - } else { - $phar = '\'\''; - } - - $codeCoverage = $result->getCodeCoverage(); - $codeCoverageFilter = null; - $cachesStaticAnalysis = 'false'; - $codeCoverageCacheDirectory = null; - $driverMethod = 'forLineCoverage'; - - if ($codeCoverage) { - $codeCoverageFilter = $codeCoverage->filter(); - - if ($codeCoverage->collectsBranchAndPathCoverage()) { - $driverMethod = 'forLineAndPathCoverage'; - } - - if ($codeCoverage->cachesStaticAnalysis()) { - $cachesStaticAnalysis = 'true'; - $codeCoverageCacheDirectory = $codeCoverage->cacheDirectory(); - } - } - - $data = var_export(serialize($this->data), true); - $dataName = var_export($this->dataName, true); - $dependencyInput = var_export(serialize($this->dependencyInput), true); - $includePath = var_export(get_include_path(), true); - $codeCoverageFilter = var_export(serialize($codeCoverageFilter), true); - $codeCoverageCacheDirectory = var_export(serialize($codeCoverageCacheDirectory), true); - // must do these fixes because TestCaseMethod.tpl has unserialize('{data}') in it, and we can't break BC - // the lines above used to use addcslashes() rather than var_export(), which breaks null byte escape sequences - $data = "'." . $data . ".'"; - $dataName = "'.(" . $dataName . ").'"; - $dependencyInput = "'." . $dependencyInput . ".'"; - $includePath = "'." . $includePath . ".'"; - $codeCoverageFilter = "'." . $codeCoverageFilter . ".'"; - $codeCoverageCacheDirectory = "'." . $codeCoverageCacheDirectory . ".'"; - - $configurationFilePath = $GLOBALS['__PHPUNIT_CONFIGURATION_FILE'] ?? ''; - $processResultFile = tempnam(sys_get_temp_dir(), 'phpunit_'); - - $var = [ - 'composerAutoload' => $composerAutoload, - 'phar' => $phar, - 'filename' => $class->getFileName(), - 'className' => $class->getName(), - 'collectCodeCoverageInformation' => $coverage, - 'cachesStaticAnalysis' => $cachesStaticAnalysis, - 'codeCoverageCacheDirectory' => $codeCoverageCacheDirectory, - 'driverMethod' => $driverMethod, - 'data' => $data, - 'dataName' => $dataName, - 'dependencyInput' => $dependencyInput, - 'constants' => $constants, - 'globals' => $globals, - 'include_path' => $includePath, - 'included_files' => $includedFiles, - 'iniSettings' => $iniSettings, - 'isStrictAboutTestsThatDoNotTestAnything' => $isStrictAboutTestsThatDoNotTestAnything, - 'isStrictAboutOutputDuringTests' => $isStrictAboutOutputDuringTests, - 'enforcesTimeLimit' => $enforcesTimeLimit, - 'isStrictAboutTodoAnnotatedTests' => $isStrictAboutTodoAnnotatedTests, - 'isStrictAboutResourceUsageDuringSmallTests' => $isStrictAboutResourceUsageDuringSmallTests, - 'codeCoverageFilter' => $codeCoverageFilter, - 'configurationFilePath' => $configurationFilePath, - 'name' => $this->getName(false), - 'processResultFile' => $processResultFile, - ]; - - if (!$runEntireClass) { - $var['methodName'] = $this->name; - } - - $template->setVar($var); - - $php = AbstractPhpProcess::factory(); - $php->runTestJob($template->render(), $this, $result, $processResultFile); - } else { - $result->run($this); + if ($this->data === []) { + return ''; } - $this->result = null; - - return $result; + return sprintf( + '%s with data (%s)', + $this->dataSetAsFilterString(), + Exporter::shortenedRecursiveExport($this->data), + ); } /** - * Returns a builder object to create mock objects using a fluent interface. + * @return array * - * @psalm-template RealInstanceType of object - * - * @psalm-param class-string $className - * - * @psalm-return MockBuilder + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function getMockBuilder(string $className): MockBuilder + final public function providedData(): array { - $this->recordDoubledType($className); - - return new MockBuilder($this, $className); + return $this->data; } - public function registerComparator(Comparator $comparator): void + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + final public function sortId(): string { - ComparatorFactory::getInstance()->register($comparator); + $id = $this->methodName; - $this->customComparators[] = $comparator; + if (!str_contains($id, '::')) { + $id = static::class . '::' . $id; + } + + if ($this->usesDataProvider()) { + $id .= $this->dataSetAsString(); + } + + return $id; } /** - * @return string[] + * @return list * * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function doubledTypes(): array + final public function provides(): array { - return array_unique($this->doubledTypes); + return $this->providedTests; } /** + * @return list + * * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function getGroups(): array + final public function requires(): array { - return $this->groups; + return $this->dependencies; } /** + * @param array $data + * * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function setGroups(array $groups): void + final public function setData(int|string $dataName, array $data): void { - $this->groups = $groups; + $this->dataName = $dataName; + $this->data = $data; } /** - * @throws InvalidArgumentException - * * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function getName(bool $withDataSet = true): string + final public function valueObjectForEvents(): Event\Code\TestMethod { - if ($withDataSet) { - return $this->name . $this->getDataSetAsString(false); + if ($this->testValueObjectForEvents !== null) { + return $this->testValueObjectForEvents; } - return $this->name; + $this->testValueObjectForEvents = Event\Code\TestMethodBuilder::fromTestCase($this); + + return $this->testValueObjectForEvents; } /** - * Returns the size of the test. - * - * @throws InvalidArgumentException - * * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function getSize(): int + final public function wasPrepared(): bool { - return TestUtil::getSize( - static::class, - $this->getName(false), - ); + return $this->wasPrepared; } /** - * @throws InvalidArgumentException - * - * @internal This method is not covered by the backward compatibility promise for PHPUnit + * Returns a matcher that matches when the method is executed + * zero or more times. */ - public function hasSize(): bool + final protected function any(): AnyInvokedCountMatcher { - return $this->getSize() !== TestUtil::UNKNOWN; + return new AnyInvokedCountMatcher; } /** - * @throws InvalidArgumentException - * - * @internal This method is not covered by the backward compatibility promise for PHPUnit + * Returns a matcher that matches when the method is never executed. */ - public function isSmall(): bool + final protected function never(): InvokedCountMatcher { - return $this->getSize() === TestUtil::SMALL; + return new InvokedCountMatcher(0); } /** - * @throws InvalidArgumentException - * - * @internal This method is not covered by the backward compatibility promise for PHPUnit + * Returns a matcher that matches when the method is executed + * at least N times. */ - public function isMedium(): bool + final protected function atLeast(int $requiredInvocations): InvokedAtLeastCountMatcher { - return $this->getSize() === TestUtil::MEDIUM; + return new InvokedAtLeastCountMatcher( + $requiredInvocations, + ); } /** - * @throws InvalidArgumentException - * - * @internal This method is not covered by the backward compatibility promise for PHPUnit + * Returns a matcher that matches when the method is executed at least once. */ - public function isLarge(): bool + final protected function atLeastOnce(): InvokedAtLeastOnceMatcher { - return $this->getSize() === TestUtil::LARGE; + return new InvokedAtLeastOnceMatcher; } /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit + * Returns a matcher that matches when the method is executed exactly once. */ - public function getActualOutput(): string + final protected function once(): InvokedCountMatcher { - if (!$this->outputBufferingActive) { - return $this->output; - } - - return (string) ob_get_contents(); + return new InvokedCountMatcher(1); } /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit + * Returns a matcher that matches when the method is executed + * exactly $count times. */ - public function hasOutput(): bool + final protected function exactly(int $count): InvokedCountMatcher { - if ($this->output === '') { - return false; - } - - if ($this->hasExpectationOnOutput()) { - return false; - } - - return true; + return new InvokedCountMatcher($count); } /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit + * Returns a matcher that matches when the method is executed + * at most N times. */ - public function doesNotPerformAssertions(): bool + final protected function atMost(int $allowedInvocations): InvokedAtMostCountMatcher { - return $this->doesNotPerformAssertions; + return new InvokedAtMostCountMatcher($allowedInvocations); } - /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function hasExpectationOnOutput(): bool + final protected function throwException(Throwable $exception): ExceptionStub { - return is_string($this->outputExpectedString) || is_string($this->outputExpectedRegex) || $this->outputRetrievedForAssertion; + return new ExceptionStub($exception); } - /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function getExpectedException(): ?string + final protected function getActualOutputForAssertion(): string { - return $this->expectedException; + $this->outputRetrievedForAssertion = true; + + return $this->output(); } - /** - * @return null|int|string - * - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function getExpectedExceptionCode() + final protected function expectOutputRegex(string $expectedRegex): void { - return $this->expectedExceptionCode; + $this->outputExpectedRegex = $expectedRegex; } - /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function getExpectedExceptionMessage(): ?string + final protected function expectOutputString(string $expectedString): void { - return $this->expectedExceptionMessage; + $this->outputExpectedString = $expectedString; } - /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function getExpectedExceptionMessageRegExp(): ?string + final protected function expectErrorLog(): void { - return $this->expectedExceptionMessageRegExp; + $this->expectErrorLog = true; } /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit + * @param class-string $exception */ - public function setRegisterMockObjectsFromTestArgumentsRecursively(bool $flag): void + final protected function expectException(string $exception): void { - $this->registerMockObjectsFromTestArgumentsRecursively = $flag; + $this->expectedException = $exception; } - /** - * @throws Throwable - * - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function runBare(): void + final protected function expectExceptionCode(int|string $code): void { - $this->numAssertions = 0; - - $this->snapshotGlobalState(); - $this->startOutputBuffering(); - clearstatcache(); - $currentWorkingDirectory = getcwd(); - - $hookMethods = TestUtil::getHookMethods(static::class); - - $hasMetRequirements = false; - - try { - $this->checkRequirements(); - $hasMetRequirements = true; - - if ($this->inIsolation) { - foreach ($hookMethods['beforeClass'] as $method) { - $this->{$method}(); - } - } - - $this->setDoesNotPerformAssertionsFromAnnotation(); - - foreach ($hookMethods['before'] as $method) { - $this->{$method}(); - } - - foreach ($hookMethods['preCondition'] as $method) { - $this->{$method}(); - } - - $this->testResult = $this->runTest(); - $this->verifyMockObjects(); - - foreach ($hookMethods['postCondition'] as $method) { - $this->{$method}(); - } - - if (!empty($this->warnings)) { - throw new Warning( - implode( - "\n", - array_unique($this->warnings), - ), - ); - } - - $this->status = BaseTestRunner::STATUS_PASSED; - } catch (IncompleteTest $e) { - $this->status = BaseTestRunner::STATUS_INCOMPLETE; - $this->statusMessage = $e->getMessage(); - } catch (SkippedTest $e) { - $this->status = BaseTestRunner::STATUS_SKIPPED; - $this->statusMessage = $e->getMessage(); - } catch (Warning $e) { - $this->status = BaseTestRunner::STATUS_WARNING; - $this->statusMessage = $e->getMessage(); - } catch (AssertionFailedError $e) { - $this->status = BaseTestRunner::STATUS_FAILURE; - $this->statusMessage = $e->getMessage(); - } catch (PredictionException $e) { - $this->status = BaseTestRunner::STATUS_FAILURE; - $this->statusMessage = $e->getMessage(); - } catch (Throwable $_e) { - $e = $_e; - $this->status = BaseTestRunner::STATUS_ERROR; - $this->statusMessage = $_e->getMessage(); - } - - $this->mockObjects = []; - $this->prophet = null; - - // Tear down the fixture. An exception raised in tearDown() will be - // caught and passed on when no exception was raised before. - try { - if ($hasMetRequirements) { - foreach ($hookMethods['after'] as $method) { - $this->{$method}(); - } - - if ($this->inIsolation) { - foreach ($hookMethods['afterClass'] as $method) { - $this->{$method}(); - } - } - } - } catch (Throwable $_e) { - $e = $e ?? $_e; - } - - try { - $this->stopOutputBuffering(); - } catch (RiskyTestError $_e) { - $e = $e ?? $_e; - } - - if (isset($_e)) { - $this->status = BaseTestRunner::STATUS_ERROR; - $this->statusMessage = $_e->getMessage(); - } - - clearstatcache(); - - if ($currentWorkingDirectory !== getcwd()) { - chdir($currentWorkingDirectory); - } - - $this->restoreGlobalState(); - $this->unregisterCustomComparators(); - $this->cleanupIniSettings(); - $this->cleanupLocaleSettings(); - libxml_clear_errors(); - - // Perform assertion on output. - if (!isset($e)) { - try { - if ($this->outputExpectedRegex !== null) { - $this->assertMatchesRegularExpression($this->outputExpectedRegex, $this->output); - } elseif ($this->outputExpectedString !== null) { - $this->assertEquals($this->outputExpectedString, $this->output); - } - } catch (Throwable $_e) { - $e = $_e; - } - } + $this->expectedExceptionCode = $code; + } - // Workaround for missing "finally". - if (isset($e)) { - if ($e instanceof PredictionException) { - $e = new AssertionFailedError($e->getMessage()); - } + final protected function expectExceptionMessage(string $message): void + { + $this->expectedExceptionMessage = $message; + } - $this->onNotSuccessfulTest($e); - } + final protected function expectExceptionMessageMatches(string $regularExpression): void + { + $this->expectedExceptionMessageRegExp = $regularExpression; } /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit + * Sets up an expectation for an exception to be raised by the code under test. + * Information for expected exception class, expected exception message, and + * expected exception code are retrieved from a given Exception object. */ - public function setName(string $name): void + final protected function expectExceptionObject(\Exception $exception): void { - $this->name = $name; + $this->expectException($exception::class); + $this->expectExceptionMessage($exception->getMessage()); + $this->expectExceptionCode($exception->getCode()); + } - if (is_callable($this->sortId(), true)) { - $this->providedTests = [new ExecutionOrderDependency($this->sortId())]; - } + final protected function expectNotToPerformAssertions(): void + { + $this->doesNotPerformAssertions = true; } /** - * @param list $dependencies - * - * @internal This method is not covered by the backward compatibility promise for PHPUnit + * @param non-empty-string $expectedUserDeprecationMessage */ - public function setDependencies(array $dependencies): void + final protected function expectUserDeprecationMessage(string $expectedUserDeprecationMessage): void { - $this->dependencies = $dependencies; + $this->expectedUserDeprecationMessage[] = $expectedUserDeprecationMessage; } /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit + * @param non-empty-string $expectedUserDeprecationMessageRegularExpression */ - public function setDependencyInput(array $dependencyInput): void + final protected function expectUserDeprecationMessageMatches(string $expectedUserDeprecationMessageRegularExpression): void { - $this->dependencyInput = $dependencyInput; + $this->expectedUserDeprecationMessageRegularExpression[] = $expectedUserDeprecationMessageRegularExpression; } /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit + * Returns a builder object to create mock objects using a fluent interface. + * + * @template RealInstanceType of object + * + * @param class-string $className + * + * @return MockBuilder */ - public function setBeStrictAboutChangesToGlobalState(?bool $beStrictAboutChangesToGlobalState): void + final protected function getMockBuilder(string $className): MockBuilder + { + return new MockBuilder($this, $className); + } + + final protected function registerComparator(Comparator $comparator): void { - $this->beStrictAboutChangesToGlobalState = $beStrictAboutChangesToGlobalState; + ComparatorFactory::getInstance()->register($comparator); + + Event\Facade::emitter()->testRegisteredComparator($comparator::class); + + $this->customComparators[] = $comparator; } /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit + * @param class-string $classOrInterface */ - public function setBackupGlobals(?bool $backupGlobals): void + final protected function registerFailureType(string $classOrInterface): void { - if ($this->backupGlobals === null && $backupGlobals !== null) { - $this->backupGlobals = $backupGlobals; - } + $this->failureTypes[$classOrInterface] = true; } /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit + * Creates a mock object for the specified interface or class. + * + * @template RealInstanceType of object + * + * @param class-string $type + * + * @throws InvalidArgumentException + * @throws MockObjectException + * @throws NoPreviousThrowableException + * + * @return MockObject&RealInstanceType */ - public function setBackupStaticAttributes(?bool $backupStaticAttributes): void + final protected function createMock(string $type): MockObject { - if ($this->backupStaticAttributes === null && $backupStaticAttributes !== null) { - $this->backupStaticAttributes = $backupStaticAttributes; - } + $mock = (new MockGenerator)->testDouble( + $type, + true, + callOriginalConstructor: false, + callOriginalClone: false, + returnValueGeneration: self::generateReturnValuesForTestDoubles(), + ); + + assert($mock instanceof $type); + assert($mock instanceof MockObject); + + $this->registerMockObject($mock); + + Event\Facade::emitter()->testCreatedMockObject($type); + + return $mock; } /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit + * @param list $interfaces + * + * @throws MockObjectException */ - public function setRunTestInSeparateProcess(bool $runTestInSeparateProcess): void + final protected function createMockForIntersectionOfInterfaces(array $interfaces): MockObject { - if ($this->runTestInSeparateProcess === null) { - $this->runTestInSeparateProcess = $runTestInSeparateProcess; - } + $mock = (new MockGenerator)->testDoubleForInterfaceIntersection( + $interfaces, + true, + returnValueGeneration: self::generateReturnValuesForTestDoubles(), + ); + + assert($mock instanceof MockObject); + + $this->registerMockObject($mock); + + Event\Facade::emitter()->testCreatedMockObjectForIntersectionOfInterfaces($interfaces); + + return $mock; } /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit + * Creates (and configures) a mock object for the specified interface or class. + * + * @template RealInstanceType of object + * + * @param class-string $type + * @param array $configuration + * + * @throws InvalidArgumentException + * @throws MockObjectException + * @throws NoPreviousThrowableException + * + * @return MockObject&RealInstanceType */ - public function setRunClassInSeparateProcess(bool $runClassInSeparateProcess): void + final protected function createConfiguredMock(string $type, array $configuration): MockObject { - if ($this->runClassInSeparateProcess === null) { - $this->runClassInSeparateProcess = $runClassInSeparateProcess; + $o = $this->createMock($type); + + foreach ($configuration as $method => $return) { + $o->method($method)->willReturn($return); } + + return $o; } /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit + * Creates a partial mock object for the specified interface or class. + * + * @param class-string $type + * @param list $methods + * + * @template RealInstanceType of object + * + * @throws InvalidArgumentException + * @throws MockObjectException + * + * @return MockObject&RealInstanceType */ - public function setPreserveGlobalState(bool $preserveGlobalState): void + final protected function createPartialMock(string $type, array $methods): MockObject { - $this->preserveGlobalState = $preserveGlobalState; + $mockBuilder = $this->getMockBuilder($type) + ->disableOriginalConstructor() + ->disableOriginalClone() + ->onlyMethods($methods); + + if (!self::generateReturnValuesForTestDoubles()) { + $mockBuilder->disableAutoReturnValueGeneration(); + } + + $partialMock = $mockBuilder->getMock(); + + Event\Facade::emitter()->testCreatedPartialMockObject( + $type, + ...$methods, + ); + + return $partialMock; } /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit + * @param non-empty-string $additionalInformation */ - public function setInIsolation(bool $inIsolation): void + final protected function provideAdditionalInformation(string $additionalInformation): void { - $this->inIsolation = $inIsolation; + Event\Facade::emitter()->testProvidedAdditionalInformation( + $this->valueObjectForEvents(), + $additionalInformation, + ); } - /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function isInIsolation(): bool + protected function transformException(Throwable $t): Throwable { - return $this->inIsolation; + return $t; } /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit + * This method is called when a test method did not execute successfully. + * + * @throws Throwable */ - public function getResult() + protected function onNotSuccessfulTest(Throwable $t): never { - return $this->testResult; + throw $t; } /** + * Returns the data set as a string compatible with the --filter CLI option. + * * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - public function setResult($result): void + private function dataSetAsFilterString(): string { - $this->testResult = $result; - } + if ($this->data !== []) { + if (is_int($this->dataName)) { + return sprintf('#%d', $this->dataName); + } - /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function setOutputCallback(callable $callback): void - { - $this->outputCallback = $callback; - } + return sprintf('@%s', $this->dataName); + } - /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function getTestResultObject(): ?TestResult - { - return $this->result; + return ''; } /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit + * @throws AssertionFailedError + * @throws Exception + * @throws ExpectationFailedException + * @throws Throwable */ - public function setTestResultObject(TestResult $result): void + private function runTest(): mixed { - $this->result = $result; - } + $testArguments = array_merge($this->data, array_values($this->dependencyInput)); - /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function registerMockObject(MockObject $mockObject): void - { - $this->mockObjects[] = $mockObject; + $this->startErrorLogCapture(); + + try { + /** @phpstan-ignore method.dynamicName */ + $testResult = $this->{$this->methodName}(...$testArguments); + + $this->verifyErrorLogExpectation(); + } catch (Throwable $exception) { + $this->handleErrorLogError(); + + if (!$this->shouldExceptionExpectationsBeVerified($exception)) { + throw $exception; + } + + $this->verifyExceptionExpectations($exception); + + return null; + } finally { + $this->stopErrorLogCapture(); + } + + $this->expectedExceptionWasNotRaised(); + + return $testResult; } - /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function addToAssertionCount(int $count): void + private function stripDateFromErrorLog(string $log): string { - $this->numAssertions += $count; + // https://github.com/php/php-src/blob/c696087e323263e941774ebbf902ac249774ec9f/main/main.c#L905 + return preg_replace('/\[\d+-\w+-\d+ \d+:\d+:\d+ [^\r\n[\]]+?\] /', '', $log); } /** - * Returns the number of assertions performed by this test. - * - * @internal This method is not covered by the backward compatibility promise for PHPUnit + * @throws ExpectationFailedException */ - public function getNumAssertions(): int + private function verifyDeprecationExpectations(): void { - return $this->numAssertions; + foreach ($this->expectedUserDeprecationMessage as $deprecationExpectation) { + $this->numberOfAssertionsPerformed++; + + if (!in_array($deprecationExpectation, DeprecationCollector::deprecations(), true)) { + throw new ExpectationFailedException( + sprintf( + 'Expected deprecation with message "%s" was not triggered', + $deprecationExpectation, + ), + ); + } + } + + foreach ($this->expectedUserDeprecationMessageRegularExpression as $deprecationExpectation) { + $this->numberOfAssertionsPerformed++; + + $expectedDeprecationTriggered = false; + + foreach (DeprecationCollector::deprecations() as $deprecation) { + if (@preg_match($deprecationExpectation, $deprecation) > 0) { + $expectedDeprecationTriggered = true; + + break; + } + } + + if (!$expectedDeprecationTriggered) { + throw new ExpectationFailedException( + sprintf( + 'Expected deprecation with message matching regular expression "%s" was not triggered', + $deprecationExpectation, + ), + ); + } + } } /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit + * @throws Throwable */ - public function usesDataProvider(): bool + private function verifyMockObjects(): void { - return !empty($this->data); + foreach ($this->mockObjects as $mockObject) { + if ($mockObject->__phpunit_hasMatchers()) { + $this->numberOfAssertionsPerformed++; + } + + $mockObject->__phpunit_verify( + $this->shouldInvocationMockerBeReset($mockObject), + ); + } } /** - * @return int|string - * - * @internal This method is not covered by the backward compatibility promise for PHPUnit + * @throws SkippedTest */ - public function dataName() + private function checkRequirements(): void { - return $this->dataName; + if ($this->methodName === '' || !method_exists($this, $this->methodName)) { + return; + } + + $missingRequirements = (new Requirements)->requirementsNotSatisfiedFor( + static::class, + $this->methodName, + ); + + if ($missingRequirements !== []) { + $this->markTestSkipped(implode(PHP_EOL, $missingRequirements)); + } } - /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function getDataSetAsString(bool $includeData = true): string + private function handleDependencies(): bool { - $buffer = ''; + if ([] === $this->dependencies || $this->inIsolation) { + return true; + } - if (!empty($this->data)) { - if (is_int($this->dataName)) { - $buffer .= sprintf(' with data set #%d', $this->dataName); - } else { - $buffer .= sprintf(' with data set "%s"', $this->dataName); + $passedTests = PassedTests::instance(); + + foreach ($this->dependencies as $dependency) { + if (!$dependency->isValid()) { + $this->markErrorForInvalidDependency(); + + return false; + } + + if ($dependency->targetIsClass()) { + $dependencyClassName = $dependency->getTargetClassName(); + + if (!class_exists($dependencyClassName)) { + $this->markErrorForInvalidDependency($dependency); + + return false; + } + + if (!$passedTests->hasTestClassPassed($dependencyClassName)) { + $this->markSkippedForMissingDependency($dependency); + + return false; + } + + continue; } - if ($includeData) { - $exporter = new Exporter; + $dependencyTarget = $dependency->getTarget(); + + if (!$passedTests->hasTestMethodPassed($dependencyTarget)) { + if (!$this->isCallableTestMethod($dependencyTarget)) { + $this->markErrorForInvalidDependency($dependency); + } else { + $this->markSkippedForMissingDependency($dependency); + } - $buffer .= sprintf(' (%s)', $exporter->shortenedRecursiveExport($this->data)); + return false; + } + + if ($passedTests->isGreaterThan($dependencyTarget, $this->size())) { + Event\Facade::emitter()->testConsideredRisky( + $this->valueObjectForEvents(), + 'This test depends on a test that is larger than itself', + ); + + return true; + } + + if (!$passedTests->hasReturnValue($dependencyTarget)) { + return true; + } + + $returnValue = $passedTests->returnValue($dependencyTarget); + + if ($dependency->deepClone()) { + $deepCopy = new DeepCopy; + $deepCopy->skipUncloneable(false); + + $this->dependencyInput[$dependencyTarget] = $deepCopy->copy($returnValue); + } elseif ($dependency->shallowClone()) { + $this->dependencyInput[$dependencyTarget] = clone $returnValue; + } else { + $this->dependencyInput[$dependencyTarget] = $returnValue; } } - return $buffer; - } + $this->testValueObjectForEvents = null; - /** - * Gets the data set of a TestCase. - * - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - public function getProvidedData(): array - { - return $this->data; + return true; } /** - * @internal This method is not covered by the backward compatibility promise for PHPUnit + * @throws Exception + * @throws NoPreviousThrowableException */ - public function addWarning(string $warning): void - { - $this->warnings[] = $warning; - } - - public function sortId(): string + private function markErrorForInvalidDependency(?ExecutionOrderDependency $dependency = null): void { - $id = $this->name; + $message = 'This test has an invalid dependency'; - if (strpos($id, '::') === false) { - $id = static::class . '::' . $id; + if ($dependency !== null) { + $message = sprintf( + 'This test depends on "%s" which does not exist', + $dependency->targetIsClass() ? $dependency->getTargetClassName() : $dependency->getTarget(), + ); } - if ($this->usesDataProvider()) { - $id .= $this->getDataSetAsString(false); - } + $exception = new InvalidDependencyException($message); - return $id; + Event\Facade::emitter()->testErrored( + $this->valueObjectForEvents(), + Event\Code\ThrowableBuilder::from($exception), + ); + + $this->status = TestStatus::error($message); } - /** - * Returns the normalized test name as class::method. - * - * @return list - */ - public function provides(): array + private function markSkippedForMissingDependency(ExecutionOrderDependency $dependency): void { - return $this->providedTests; + $message = sprintf( + 'This test depends on "%s" to pass', + $dependency->getTarget(), + ); + + Event\Facade::emitter()->testSkipped( + $this->valueObjectForEvents(), + $message, + ); + + $this->status = TestStatus::skipped($message); } - /** - * Returns a list of normalized dependency names, class::method. - * - * This list can differ from the raw dependencies as the resolver has - * no need for the [!][shallow]clone prefix that is filtered out - * during normalization. - * - * @return list - */ - public function requires(): array + private function startOutputBuffering(): void { - return $this->dependencies; + ob_start(); + + $this->outputBufferingActive = true; + $this->outputBufferingLevel = ob_get_level(); } - /** - * Override to run the test and assert its state. - * - * @throws \SebastianBergmann\ObjectEnumerator\InvalidArgumentException - * @throws AssertionFailedError - * @throws Exception - * @throws ExpectationFailedException - * @throws Throwable - */ - protected function runTest() + private function stopOutputBuffering(): bool { - if (trim($this->name) === '') { - throw new Exception( - 'PHPUnit\Framework\TestCase::$name must be a non-blank string.', + $bufferingLevel = ob_get_level(); + + if ($bufferingLevel !== $this->outputBufferingLevel) { + if ($bufferingLevel > $this->outputBufferingLevel) { + $message = 'Test code or tested code did not close its own output buffers'; + } else { + $message = 'Test code or tested code closed output buffers other than its own'; + } + + while (ob_get_level() >= $this->outputBufferingLevel) { + ob_end_clean(); + } + + Event\Facade::emitter()->testConsideredRisky( + $this->valueObjectForEvents(), + $message, ); + + return false; } - $testArguments = array_merge($this->data, $this->dependencyInput); + $this->output = ob_get_clean(); + + $this->outputBufferingActive = false; + $this->outputBufferingLevel = ob_get_level(); - $this->registerMockObjectsFromTestArguments($testArguments); + return true; + } - try { - $testResult = $this->{$this->name}(...array_values($testArguments)); - } catch (Throwable $exception) { - if (!$this->checkExceptionExpectations($exception)) { - throw $exception; - } + private function snapshotGlobalErrorExceptionHandlers(): void + { + $this->backupGlobalErrorHandlers = $this->activeErrorHandlers(); + $this->backupGlobalExceptionHandlers = $this->activeExceptionHandlers(); + } - if ($this->expectedException !== null) { - if ($this->expectedException === Error::class) { - $this->assertThat( - $exception, - LogicalOr::fromConstraints( - new ExceptionConstraint(Error::class), - new ExceptionConstraint(\Error::class), - ), - ); - } else { - $this->assertThat( - $exception, - new ExceptionConstraint( - $this->expectedException, - ), - ); - } - } + private function restoreGlobalErrorExceptionHandlers(): void + { + $activeErrorHandlers = $this->activeErrorHandlers(); + $activeExceptionHandlers = $this->activeExceptionHandlers(); - if ($this->expectedExceptionMessage !== null) { - $this->assertThat( - $exception, - new ExceptionMessage( - $this->expectedExceptionMessage, - ), - ); - } + $message = null; - if ($this->expectedExceptionMessageRegExp !== null) { - $this->assertThat( - $exception, - new ExceptionMessageRegularExpression( - $this->expectedExceptionMessageRegExp, - ), - ); + if ($activeErrorHandlers !== $this->backupGlobalErrorHandlers) { + if (count($activeErrorHandlers) > count($this->backupGlobalErrorHandlers)) { + if (!$this->inIsolation) { + $message = 'Test code or tested code did not remove its own error handlers'; + } + } else { + $message = 'Test code or tested code removed error handlers other than its own'; } - if ($this->expectedExceptionCode !== null) { - $this->assertThat( - $exception, - new ExceptionCode( - $this->expectedExceptionCode, - ), - ); + foreach ($activeErrorHandlers as $handler) { + restore_error_handler(); } - return; + foreach ($this->backupGlobalErrorHandlers as $handler) { + set_error_handler($handler); + } } - if ($this->expectedException !== null) { - $this->assertThat( - null, - new ExceptionConstraint( - $this->expectedException, - ), + if ($message !== null) { + Event\Facade::emitter()->testConsideredRisky( + $this->valueObjectForEvents(), + $message, ); - } elseif ($this->expectedExceptionMessage !== null) { - $this->numAssertions++; + } - throw new AssertionFailedError( - sprintf( - 'Failed asserting that exception with message "%s" is thrown', - $this->expectedExceptionMessage, - ), - ); - } elseif ($this->expectedExceptionMessageRegExp !== null) { - $this->numAssertions++; + $message = null; - throw new AssertionFailedError( - sprintf( - 'Failed asserting that exception with message matching "%s" is thrown', - $this->expectedExceptionMessageRegExp, - ), - ); - } elseif ($this->expectedExceptionCode !== null) { - $this->numAssertions++; + if ($activeExceptionHandlers !== $this->backupGlobalExceptionHandlers) { + if (count($activeExceptionHandlers) > count($this->backupGlobalExceptionHandlers)) { + if (!$this->inIsolation) { + $message = 'Test code or tested code did not remove its own exception handlers'; + } + } else { + $message = 'Test code or tested code removed exception handlers other than its own'; + } - throw new AssertionFailedError( - sprintf( - 'Failed asserting that exception with code "%s" is thrown', - $this->expectedExceptionCode, - ), - ); - } + foreach ($activeExceptionHandlers as $handler) { + restore_exception_handler(); + } - return $testResult; - } + foreach ($this->backupGlobalExceptionHandlers as $handler) { + set_exception_handler($handler); + } + } - /** - * This method is a wrapper for the ini_set() function that automatically - * resets the modified php.ini setting to its original value after the - * test is run. - * - * @throws Exception - */ - protected function iniSet(string $varName, string $newValue): void - { - $currentValue = ini_set($varName, $newValue); + $this->backupGlobalErrorHandlers = null; + $this->backupGlobalExceptionHandlers = null; - if ($currentValue !== false) { - $this->iniSettings[$varName] = $currentValue; - } else { - throw new Exception( - sprintf( - 'INI setting "%s" could not be set to "%s".', - $varName, - $newValue, - ), + if ($message !== null) { + Event\Facade::emitter()->testConsideredRisky( + $this->valueObjectForEvents(), + $message, ); } } /** - * This method is a wrapper for the setlocale() function that automatically - * resets the locale to its original value after the test is run. - * - * @throws Exception + * @return list */ - protected function setLocale(...$args): void + private function activeErrorHandlers(): array { - if (count($args) < 2) { - throw new Exception; - } + $activeErrorHandlers = []; - [$category, $locale] = $args; + while (true) { + $previousHandler = set_error_handler(static fn () => false); - if (!in_array($category, self::LOCALE_CATEGORIES, true)) { - throw new Exception; - } + restore_error_handler(); + + if ($previousHandler === null) { + break; + } + + $activeErrorHandlers[] = $previousHandler; - if (!is_array($locale) && !is_string($locale)) { - throw new Exception; + restore_error_handler(); } - $this->locale[$category] = setlocale($category, 0); + $activeErrorHandlers = array_reverse($activeErrorHandlers); + $invalidErrorHandlerStack = false; + + foreach ($activeErrorHandlers as $handler) { + if (!is_callable($handler)) { + $invalidErrorHandlerStack = true; + + continue; + } + + set_error_handler($handler); + } - $result = setlocale(...$args); + if ($invalidErrorHandlerStack) { + $message = 'At least one error handler is not callable outside the scope it was registered in'; - if ($result === false) { - throw new Exception( - 'The locale functionality is not implemented on your platform, ' . - 'the specified locale does not exist or the category name is ' . - 'invalid.', + Event\Facade::emitter()->testConsideredRisky( + $this->valueObjectForEvents(), + $message, ); } - } - /** - * Makes configurable stub for the specified class. - * - * @psalm-template RealInstanceType of object - * - * @psalm-param class-string $originalClassName - * - * @psalm-return Stub&RealInstanceType - */ - protected function createStub(string $originalClassName): Stub - { - return $this->createMockObject($originalClassName); + return $activeErrorHandlers; } /** - * Returns a mock object for the specified class. - * - * @psalm-template RealInstanceType of object - * - * @psalm-param class-string $originalClassName - * - * @psalm-return MockObject&RealInstanceType + * @return list */ - protected function createMock(string $originalClassName): MockObject + private function activeExceptionHandlers(): array { - return $this->createMockObject($originalClassName); + $res = []; + + while (true) { + $previousHandler = set_exception_handler(static fn () => null); + restore_exception_handler(); + + if ($previousHandler === null) { + break; + } + $res[] = $previousHandler; + restore_exception_handler(); + } + $res = array_reverse($res); + + foreach ($res as $handler) { + set_exception_handler($handler); + } + + return $res; } - /** - * Returns a configured mock object for the specified class. - * - * @psalm-template RealInstanceType of object - * - * @psalm-param class-string $originalClassName - * - * @psalm-return MockObject&RealInstanceType - */ - protected function createConfiguredMock(string $originalClassName, array $configuration): MockObject + private function snapshotGlobalState(): void { - $o = $this->createMockObject($originalClassName); - - foreach ($configuration as $method => $return) { - $o->method($method)->willReturn($return); + if ($this->runTestInSeparateProcess || $this->inIsolation || + (!$this->backupGlobals && !$this->backupStaticProperties)) { + return; } - return $o; + $snapshot = $this->createGlobalStateSnapshot($this->backupGlobals === true); + + $this->snapshot = $snapshot; } - /** - * Returns a partial mock object for the specified class. - * - * @param string[] $methods - * - * @psalm-template RealInstanceType of object - * - * @psalm-param class-string $originalClassName - * - * @psalm-return MockObject&RealInstanceType - */ - protected function createPartialMock(string $originalClassName, array $methods): MockObject + private function restoreGlobalState(): void { - try { - $reflector = new ReflectionClass($originalClassName); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - $e->getCode(), - $e, + if (!$this->snapshot instanceof Snapshot) { + return; + } + + if (ConfigurationRegistry::get()->beStrictAboutChangesToGlobalState()) { + $this->compareGlobalStateSnapshots( + $this->snapshot, + $this->createGlobalStateSnapshot($this->backupGlobals === true), ); } - // @codeCoverageIgnoreEnd - $mockedMethodsThatDontExist = array_filter( - $methods, - static function (string $method) use ($reflector) - { - return !$reflector->hasMethod($method); - }, - ); + $restorer = new Restorer; - if ($mockedMethodsThatDontExist) { - $this->addWarning( - sprintf( - 'createPartialMock() called with method(s) %s that do not exist in %s. This will not be allowed in future versions of PHPUnit.', - implode(', ', $mockedMethodsThatDontExist), - $originalClassName, - ), - ); + if ($this->backupGlobals) { + $restorer->restoreGlobalVariables($this->snapshot); } - return $this->getMockBuilder($originalClassName) - ->disableOriginalConstructor() - ->disableOriginalClone() - ->disableArgumentCloning() - ->disallowMockingUnknownTypes() - ->setMethods(empty($methods) ? null : $methods) - ->getMock(); - } + if ($this->backupStaticProperties) { + $restorer->restoreStaticProperties($this->snapshot); + } - /** - * Returns a test proxy for the specified class. - * - * @psalm-template RealInstanceType of object - * - * @psalm-param class-string $originalClassName - * - * @psalm-return MockObject&RealInstanceType - */ - protected function createTestProxy(string $originalClassName, array $constructorArguments = []): MockObject - { - return $this->getMockBuilder($originalClassName) - ->setConstructorArgs($constructorArguments) - ->enableProxyingToOriginalMethods() - ->getMock(); + $this->snapshot = null; } - /** - * Mocks the specified class and returns the name of the mocked class. - * - * @param null|array $methods $methods - * - * @psalm-template RealInstanceType of object - * - * @psalm-param class-string|string $originalClassName - * - * @psalm-return class-string - * - * @deprecated - */ - protected function getMockClass(string $originalClassName, $methods = [], array $arguments = [], string $mockClassName = '', bool $callOriginalConstructor = false, bool $callOriginalClone = true, bool $callAutoload = true, bool $cloneArguments = false): string + private function createGlobalStateSnapshot(bool $backupGlobals): Snapshot { - $this->addWarning('PHPUnit\Framework\TestCase::getMockClass() is deprecated and will be removed in PHPUnit 10.'); + $excludeList = new GlobalStateExcludeList; - $this->recordDoubledType($originalClassName); + foreach ($this->backupGlobalsExcludeList as $globalVariable) { + $excludeList->addGlobalVariable($globalVariable); + } - $mock = $this->getMockObjectGenerator()->getMock( - $originalClassName, - $methods, - $arguments, - $mockClassName, - $callOriginalConstructor, - $callOriginalClone, - $callAutoload, - $cloneArguments, - ); + if (!defined('PHPUNIT_TESTSUITE')) { + $excludeList->addClassNamePrefix('PHPUnit'); + $excludeList->addClassNamePrefix('SebastianBergmann\CodeCoverage'); + $excludeList->addClassNamePrefix('SebastianBergmann\FileIterator'); + $excludeList->addClassNamePrefix('SebastianBergmann\Invoker'); + $excludeList->addClassNamePrefix('SebastianBergmann\Template'); + $excludeList->addClassNamePrefix('SebastianBergmann\Timer'); + $excludeList->addStaticProperty(ComparatorFactory::class, 'instance'); - return get_class($mock); + foreach ($this->backupStaticPropertiesExcludeList as $class => $properties) { + foreach ($properties as $property) { + $excludeList->addStaticProperty($class, $property); + } + } + } + + return new Snapshot( + $excludeList, + $backupGlobals, + (bool) $this->backupStaticProperties, + false, + false, + false, + false, + false, + false, + false, + ); } - /** - * Returns a mock object for the specified abstract class with all abstract - * methods of the class mocked. Concrete methods are not mocked by default. - * To mock concrete methods, use the 7th parameter ($mockedMethods). - * - * @psalm-template RealInstanceType of object - * - * @psalm-param class-string $originalClassName - * - * @psalm-return MockObject&RealInstanceType - */ - protected function getMockForAbstractClass(string $originalClassName, array $arguments = [], string $mockClassName = '', bool $callOriginalConstructor = true, bool $callOriginalClone = true, bool $callAutoload = true, array $mockedMethods = [], bool $cloneArguments = false): MockObject + private function compareGlobalStateSnapshots(Snapshot $before, Snapshot $after): void { - $this->recordDoubledType($originalClassName); + $backupGlobals = $this->backupGlobals === null || $this->backupGlobals; - $mockObject = $this->getMockObjectGenerator()->getMockForAbstractClass( - $originalClassName, - $arguments, - $mockClassName, - $callOriginalConstructor, - $callOriginalClone, - $callAutoload, - $mockedMethods, - $cloneArguments, - ); + if ($backupGlobals) { + $this->compareGlobalStateSnapshotPart( + $before->globalVariables(), + $after->globalVariables(), + "--- Global variables before the test\n+++ Global variables after the test\n", + ); - $this->registerMockObject($mockObject); + $this->compareGlobalStateSnapshotPart( + $before->superGlobalVariables(), + $after->superGlobalVariables(), + "--- Super-global variables before the test\n+++ Super-global variables after the test\n", + ); + } - return $mockObject; + if ($this->backupStaticProperties) { + $this->compareGlobalStateSnapshotPart( + $before->staticProperties(), + $after->staticProperties(), + "--- Static properties before the test\n+++ Static properties after the test\n", + ); + } } /** - * Returns a mock object based on the given WSDL file. - * - * @psalm-template RealInstanceType of object - * - * @psalm-param class-string|string $originalClassName - * - * @psalm-return MockObject&RealInstanceType + * @param array $before + * @param array $after */ - protected function getMockFromWsdl(string $wsdlFile, string $originalClassName = '', string $mockClassName = '', array $methods = [], bool $callOriginalConstructor = true, array $options = []): MockObject + private function compareGlobalStateSnapshotPart(array $before, array $after, string $header): void { - $this->recordDoubledType(SoapClient::class); - - if ($originalClassName === '') { - $fileName = pathinfo(basename(parse_url($wsdlFile, PHP_URL_PATH)), PATHINFO_FILENAME); - $originalClassName = preg_replace('/\W/', '', $fileName); - } + if ($before !== $after) { + $differ = new Differ(new UnifiedDiffOutputBuilder($header)); - if (!class_exists($originalClassName)) { - eval( - $this->getMockObjectGenerator()->generateClassFromWsdl( - $wsdlFile, - $originalClassName, - $methods, - $options, - ) + Event\Facade::emitter()->testConsideredRisky( + $this->valueObjectForEvents(), + 'This test modified global state but was not expected to do so' . PHP_EOL . + trim( + $differ->diff( + Exporter::export($before), + Exporter::export($after), + ), + ), ); } - - $mockObject = $this->getMockObjectGenerator()->getMock( - $originalClassName, - $methods, - ['', $options], - $mockClassName, - $callOriginalConstructor, - false, - false, - ); - - $this->registerMockObject($mockObject); - - return $mockObject; } - /** - * Returns a mock object for the specified trait with all abstract methods - * of the trait mocked. Concrete methods to mock can be specified with the - * `$mockedMethods` parameter. - * - * @psalm-param trait-string $traitName - */ - protected function getMockForTrait(string $traitName, array $arguments = [], string $mockClassName = '', bool $callOriginalConstructor = true, bool $callOriginalClone = true, bool $callAutoload = true, array $mockedMethods = [], bool $cloneArguments = false): MockObject + private function handleEnvironmentVariables(): void { - $this->recordDoubledType($traitName); + $withEnvironmentVariables = MetadataRegistry::parser()->forClassAndMethod(static::class, $this->methodName)->isWithEnvironmentVariable(); - $mockObject = $this->getMockObjectGenerator()->getMockForTrait( - $traitName, - $arguments, - $mockClassName, - $callOriginalConstructor, - $callOriginalClone, - $callAutoload, - $mockedMethods, - $cloneArguments, - ); + $environmentVariables = []; - $this->registerMockObject($mockObject); + foreach ($withEnvironmentVariables as $metadata) { + assert($metadata instanceof WithEnvironmentVariable); - return $mockObject; + $environmentVariables[$metadata->environmentVariableName()] = $metadata->value(); + } + + foreach ($environmentVariables as $environmentVariableName => $environmentVariableValue) { + $this->backupEnvironmentVariables = [...$this->backupEnvironmentVariables, ...BackedUpEnvironmentVariable::create($environmentVariableName)]; + + if ($environmentVariableValue === null) { + unset($_ENV[$environmentVariableName]); + putenv($environmentVariableName); + } else { + $_ENV[$environmentVariableName] = $environmentVariableValue; + putenv("{$environmentVariableName}={$environmentVariableValue}"); + } + } } - /** - * Returns an object for the specified trait. - * - * @psalm-param trait-string $traitName - */ - protected function getObjectForTrait(string $traitName, array $arguments = [], string $traitClassName = '', bool $callOriginalConstructor = true, bool $callOriginalClone = true, bool $callAutoload = true): object + private function restoreEnvironmentVariables(): void { - $this->recordDoubledType($traitName); + foreach ($this->backupEnvironmentVariables as $backupEnvironmentVariable) { + $backupEnvironmentVariable->restore(); + } - return $this->getMockObjectGenerator()->getObjectForTrait( - $traitName, - $traitClassName, - $callAutoload, - $callOriginalConstructor, - $arguments, - ); + $this->backupEnvironmentVariables = []; } - /** - * @throws ClassNotFoundException - * @throws DoubleException - * @throws InterfaceNotFoundException - * - * @psalm-param class-string|null $classOrInterface - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4141 - */ - protected function prophesize(?string $classOrInterface = null): ObjectProphecy + private function shouldInvocationMockerBeReset(MockObject $mock): bool { - if (!class_exists(Prophet::class)) { - throw new Exception('This test uses TestCase::prophesize(), but phpspec/prophecy is not installed. Please run "composer require --dev phpspec/prophecy".'); - } + $enumerator = new Enumerator; - $this->addWarning('PHPUnit\Framework\TestCase::prophesize() is deprecated and will be removed in PHPUnit 10. Please use the trait provided by phpspec/prophecy-phpunit.'); + if (in_array($mock, $enumerator->enumerate($this->dependencyInput), true)) { + return false; + } - if (is_string($classOrInterface)) { - $this->recordDoubledType($classOrInterface); + if (!is_array($this->testResult) && !is_object($this->testResult)) { + return true; } - return $this->getProphet()->prophesize($classOrInterface); + return !in_array($mock, $enumerator->enumerate($this->testResult), true); } - /** - * Creates a default TestResult object. - * - * @internal This method is not covered by the backward compatibility promise for PHPUnit - */ - protected function createResult(): TestResult + private function unregisterCustomComparators(): void { - return new TestResult; - } + $factory = ComparatorFactory::getInstance(); - /** - * This method is called when a test method did not execute successfully. - * - * @throws Throwable - */ - protected function onNotSuccessfulTest(Throwable $t): void - { - throw $t; - } + foreach ($this->customComparators as $comparator) { + $factory->unregister($comparator); + } - protected function recordDoubledType(string $originalClassName): void - { - $this->doubledTypes[] = $originalClassName; + $this->customComparators = []; } /** - * @throws Throwable + * @throws Exception */ - private function verifyMockObjects(): void + private function shouldExceptionExpectationsBeVerified(Throwable $throwable): bool { - foreach ($this->mockObjects as $mockObject) { - if ($mockObject->__phpunit_hasMatchers()) { - $this->numAssertions++; - } + $result = false; - $mockObject->__phpunit_verify( - $this->shouldInvocationMockerBeReset($mockObject), - ); + if ($this->expectedException !== null || $this->expectedExceptionCode !== null || $this->expectedExceptionMessage !== null || $this->expectedExceptionMessageRegExp !== null) { + $result = true; + } + + if ($throwable instanceof Exception) { + $result = false; } - if ($this->prophet !== null) { + if (is_string($this->expectedException)) { try { - $this->prophet->checkPredictions(); - } finally { - foreach ($this->prophet->getProphecies() as $objectProphecy) { - foreach ($objectProphecy->getMethodProphecies() as $methodProphecies) { - foreach ($methodProphecies as $methodProphecy) { - /* @var MethodProphecy $methodProphecy */ - $this->numAssertions += count($methodProphecy->getCheckedPredictions()); - } - } - } + $reflector = new ReflectionClass($this->expectedException); + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new Exception( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + // @codeCoverageIgnoreEnd + + if ($this->expectedException === 'PHPUnit\Framework\Exception' || + $this->expectedException === '\PHPUnit\Framework\Exception' || + $reflector->isSubclassOf(Exception::class)) { + $result = true; } } + + return $result; } - /** - * @throws SkippedTestError - * @throws SyntheticSkippedError - * @throws Warning - */ - private function checkRequirements(): void + private function shouldRunInSeparateProcess(): bool { - if (!$this->name || !method_exists($this, $this->name)) { - return; + if ($this->inIsolation) { + return false; } - $missingRequirements = TestUtil::getMissingRequirements( - static::class, - $this->name, - ); - - if (!empty($missingRequirements)) { - $this->markTestSkipped(implode(PHP_EOL, $missingRequirements)); + if ($this->runTestInSeparateProcess) { + return true; } - } - private function handleDependencies(): bool - { - if ([] === $this->dependencies || $this->inIsolation) { + if ($this->runClassInSeparateProcess) { return true; } - $passed = $this->result->passed(); - $passedKeys = array_keys($passed); - $numKeys = count($passedKeys); + return ConfigurationRegistry::get()->processIsolation(); + } - for ($i = 0; $i < $numKeys; $i++) { - $pos = strpos($passedKeys[$i], ' with data set'); + private function isCallableTestMethod(string $dependency): bool + { + [$className, $methodName] = explode('::', $dependency); - if ($pos !== false) { - $passedKeys[$i] = substr($passedKeys[$i], 0, $pos); - } + if (!class_exists($className)) { + return false; } - $passedKeys = array_flip(array_unique($passedKeys)); - - foreach ($this->dependencies as $dependency) { - if (!$dependency->isValid()) { - $this->markSkippedForNotSpecifyingDependency(); - - return false; - } - - if ($dependency->targetIsClass()) { - $dependencyClassName = $dependency->getTargetClassName(); - - if (array_search($dependencyClassName, $this->result->passedClasses(), true) === false) { - $this->markSkippedForMissingDependency($dependency); - - return false; - } - - continue; - } - - $dependencyTarget = $dependency->getTarget(); - - if (!isset($passedKeys[$dependencyTarget])) { - if (!$this->isCallableTestMethod($dependencyTarget)) { - $this->markWarningForUncallableDependency($dependency); - } else { - $this->markSkippedForMissingDependency($dependency); - } - - return false; - } - - if (isset($passed[$dependencyTarget])) { - if ($passed[$dependencyTarget]['size'] != TestUtil::UNKNOWN && - $this->getSize() != TestUtil::UNKNOWN && - $passed[$dependencyTarget]['size'] > $this->getSize()) { - $this->result->addError( - $this, - new SkippedTestError( - 'This test depends on a test that is larger than itself.', - ), - 0, - ); - - return false; - } + $class = new ReflectionClass($className); - if ($dependency->useDeepClone()) { - $deepCopy = new DeepCopy; - $deepCopy->skipUncloneable(false); + if (!$class->isSubclassOf(__CLASS__)) { + return false; + } - $this->dependencyInput[$dependencyTarget] = $deepCopy->copy($passed[$dependencyTarget]['result']); - } elseif ($dependency->useShallowClone()) { - $this->dependencyInput[$dependencyTarget] = clone $passed[$dependencyTarget]['result']; - } else { - $this->dependencyInput[$dependencyTarget] = $passed[$dependencyTarget]['result']; - } - } else { - $this->dependencyInput[$dependencyTarget] = null; - } + if (!$class->hasMethod($methodName)) { + return false; } - return true; + return TestUtil::isTestMethod( + $class->getMethod($methodName), + ); } - private function markSkippedForNotSpecifyingDependency(): void + /** + * @throws Exception + * @throws ExpectationFailedException + * @throws NoPreviousThrowableException + */ + private function performAssertionsOnOutput(): void { - $this->status = BaseTestRunner::STATUS_SKIPPED; - - $this->result->startTest($this); + try { + if ($this->outputExpectedRegex !== null) { + $this->assertMatchesRegularExpression($this->outputExpectedRegex, $this->output); + } elseif ($this->outputExpectedString !== null) { + $this->assertSame($this->outputExpectedString, $this->output); + } + } catch (ExpectationFailedException $e) { + $this->status = TestStatus::failure($e->getMessage()); - $this->result->addError( - $this, - new SkippedTestError( - 'This method has an invalid @depends annotation.', - ), - 0, - ); + Event\Facade::emitter()->testFailed( + $this->valueObjectForEvents(), + Event\Code\ThrowableBuilder::from($e), + Event\Code\ComparisonFailureBuilder::from($e), + ); - $this->result->endTest($this, 0); + throw $e; + } } - private function markSkippedForMissingDependency(ExecutionOrderDependency $dependency): void + /** + * @param array{beforeClass: HookMethodCollection, before: HookMethodCollection, preCondition: HookMethodCollection, postCondition: HookMethodCollection, after: HookMethodCollection, afterClass: HookMethodCollection} $hookMethods + * + * @throws Throwable + * + * @codeCoverageIgnore + */ + private function invokeBeforeClassHookMethods(array $hookMethods, Event\Emitter $emitter): void { - $this->status = BaseTestRunner::STATUS_SKIPPED; - - $this->result->startTest($this); - - $this->result->addError( - $this, - new SkippedTestError( - sprintf( - 'This test depends on "%s" to pass.', - $dependency->getTarget(), - ), - ), - 0, + $this->invokeHookMethods( + $hookMethods['beforeClass'], + $emitter, + 'beforeFirstTestMethodCalled', + 'beforeFirstTestMethodErrored', + 'beforeFirstTestMethodFailed', + 'beforeFirstTestMethodFinished', + false, ); - - $this->result->endTest($this, 0); } - private function markWarningForUncallableDependency(ExecutionOrderDependency $dependency): void + /** + * @param array{beforeClass: HookMethodCollection, before: HookMethodCollection, preCondition: HookMethodCollection, postCondition: HookMethodCollection, after: HookMethodCollection, afterClass: HookMethodCollection} $hookMethods + * + * @throws Throwable + */ + private function invokeBeforeTestHookMethods(array $hookMethods, Event\Emitter $emitter): void { - $this->status = BaseTestRunner::STATUS_WARNING; - - $this->result->startTest($this); - - $this->result->addWarning( - $this, - new Warning( - sprintf( - 'This test depends on "%s" which does not exist.', - $dependency->getTarget(), - ), - ), - 0, + $this->invokeHookMethods( + $hookMethods['before'], + $emitter, + 'beforeTestMethodCalled', + 'beforeTestMethodErrored', + 'beforeTestMethodFailed', + 'beforeTestMethodFinished', ); - - $this->result->endTest($this, 0); } /** - * Get the mock object generator, creating it if it doesn't exist. + * @param array{beforeClass: HookMethodCollection, before: HookMethodCollection, preCondition: HookMethodCollection, postCondition: HookMethodCollection, after: HookMethodCollection, afterClass: HookMethodCollection} $hookMethods + * + * @throws Throwable */ - private function getMockObjectGenerator(): MockGenerator + private function invokePreConditionHookMethods(array $hookMethods, Event\Emitter $emitter): void { - if ($this->mockObjectGenerator === null) { - $this->mockObjectGenerator = new MockGenerator; - } - - return $this->mockObjectGenerator; + $this->invokeHookMethods( + $hookMethods['preCondition'], + $emitter, + 'preConditionCalled', + 'preConditionErrored', + 'preConditionFailed', + 'preConditionFinished', + ); } - private function startOutputBuffering(): void + /** + * @param array{beforeClass: HookMethodCollection, before: HookMethodCollection, preCondition: HookMethodCollection, postCondition: HookMethodCollection, after: HookMethodCollection, afterClass: HookMethodCollection} $hookMethods + * + * @throws Throwable + */ + private function invokePostConditionHookMethods(array $hookMethods, Event\Emitter $emitter): void { - ob_start(); - - $this->outputBufferingActive = true; - $this->outputBufferingLevel = ob_get_level(); + $this->invokeHookMethods( + $hookMethods['postCondition'], + $emitter, + 'postConditionCalled', + 'postConditionErrored', + 'postConditionFailed', + 'postConditionFinished', + ); } /** - * @throws RiskyTestError + * @param array{beforeClass: HookMethodCollection, before: HookMethodCollection, preCondition: HookMethodCollection, postCondition: HookMethodCollection, after: HookMethodCollection, afterClass: HookMethodCollection} $hookMethods + * + * @throws Throwable */ - private function stopOutputBuffering(): void + private function invokeAfterTestHookMethods(array $hookMethods, Event\Emitter $emitter): void { - if (ob_get_level() !== $this->outputBufferingLevel) { - while (ob_get_level() >= $this->outputBufferingLevel) { - ob_end_clean(); - } - - throw new RiskyTestError( - 'Test code or tested code did not (only) close its own output buffers', - ); - } - - $this->output = ob_get_contents(); - - if ($this->outputCallback !== false) { - $this->output = (string) call_user_func($this->outputCallback, $this->output); - } - - ob_end_clean(); - - $this->outputBufferingActive = false; - $this->outputBufferingLevel = ob_get_level(); + $this->invokeHookMethods( + $hookMethods['after'], + $emitter, + 'afterTestMethodCalled', + 'afterTestMethodErrored', + 'afterTestMethodFailed', + 'afterTestMethodFinished', + ); } - private function snapshotGlobalState(): void + /** + * @param array{beforeClass: HookMethodCollection, before: HookMethodCollection, preCondition: HookMethodCollection, postCondition: HookMethodCollection, after: HookMethodCollection, afterClass: HookMethodCollection} $hookMethods + * + * @throws Throwable + * + * @codeCoverageIgnore + */ + private function invokeAfterClassHookMethods(array $hookMethods, Event\Emitter $emitter): void { - if ($this->runTestInSeparateProcess || $this->inIsolation || - (!$this->backupGlobals && !$this->backupStaticAttributes)) { - return; - } - - $this->snapshot = $this->createGlobalStateSnapshot($this->backupGlobals === true); + $this->invokeHookMethods( + $hookMethods['afterClass'], + $emitter, + 'afterLastTestMethodCalled', + 'afterLastTestMethodErrored', + 'afterLastTestMethodFailed', + 'afterLastTestMethodFinished', + false, + ); } /** - * @throws InvalidArgumentException - * @throws RiskyTestError + * @param 'afterLastTestMethodCalled'|'afterTestMethodCalled'|'beforeFirstTestMethodCalled'|'beforeTestMethodCalled'|'postConditionCalled'|'preConditionCalled' $calledMethod + * @param 'afterLastTestMethodErrored'|'afterTestMethodErrored'|'beforeFirstTestMethodErrored'|'beforeTestMethodErrored'|'postConditionErrored'|'preConditionErrored' $erroredMethod + * @param 'afterLastTestMethodFailed'|'afterTestMethodFailed'|'beforeFirstTestMethodFailed'|'beforeTestMethodFailed'|'postConditionFailed'|'preConditionFailed' $failedMethod + * @param 'afterLastTestMethodFinished'|'afterTestMethodFinished'|'beforeFirstTestMethodFinished'|'beforeTestMethodFinished'|'postConditionFinished'|'preConditionFinished' $finishedMethod + * + * @throws Throwable */ - private function restoreGlobalState(): void + private function invokeHookMethods(HookMethodCollection $hookMethods, Event\Emitter $emitter, string $calledMethod, string $erroredMethod, string $failedMethod, string $finishedMethod, bool $forTestCase = true): void { - if (!$this->snapshot instanceof Snapshot) { - return; - } - - if ($this->beStrictAboutChangesToGlobalState) { - try { - $this->compareGlobalStateSnapshots( - $this->snapshot, - $this->createGlobalStateSnapshot($this->backupGlobals === true), - ); - } catch (RiskyTestError $rte) { - // Intentionally left empty - } + if ($forTestCase) { + $test = $this->valueObjectForEvents(); + } else { + $test = static::class; } - $restorer = new Restorer; + $methodsInvoked = []; - if ($this->backupGlobals) { - $restorer->restoreGlobalVariables($this->snapshot); - } + foreach ($hookMethods->methodNamesSortedByPriority() as $methodName) { + if ($this->methodDoesNotExistOrIsDeclaredInTestCase($methodName)) { + continue; + } - if ($this->backupStaticAttributes) { - $restorer->restoreStaticAttributes($this->snapshot); - } + $methodInvoked = new Event\Code\ClassMethod( + static::class, + $methodName, + ); - $this->snapshot = null; + try { + /** @phpstan-ignore method.dynamicName */ + $this->{$methodName}(); + } catch (Throwable $t) { + } - if (isset($rte)) { - throw $rte; - } - } + /** @phpstan-ignore method.dynamicName */ + $emitter->{$calledMethod}( + $test, + $methodInvoked + ); - private function createGlobalStateSnapshot(bool $backupGlobals): Snapshot - { - $excludeList = new ExcludeList; + $methodsInvoked[] = $methodInvoked; - foreach ($this->backupGlobalsExcludeList as $globalVariable) { - $excludeList->addGlobalVariable($globalVariable); - } + if (isset($t) && !$t instanceof SkippedTest) { + if ($t instanceof AssertionFailedError) { + $method = $failedMethod; + } else { + $method = $erroredMethod; + } - if (!empty($this->backupGlobalsBlacklist)) { - $this->addWarning('PHPUnit\Framework\TestCase::$backupGlobalsBlacklist is deprecated and will be removed in PHPUnit 10. Please use PHPUnit\Framework\TestCase::$backupGlobalsExcludeList instead.'); + /** @phpstan-ignore method.dynamicName */ + $emitter->{$method}( + $test, + $methodInvoked, + Event\Code\ThrowableBuilder::from($t), + ); - foreach ($this->backupGlobalsBlacklist as $globalVariable) { - $excludeList->addGlobalVariable($globalVariable); + break; } } - if (!defined('PHPUNIT_TESTSUITE')) { - $excludeList->addClassNamePrefix('PHPUnit'); - $excludeList->addClassNamePrefix('SebastianBergmann\CodeCoverage'); - $excludeList->addClassNamePrefix('SebastianBergmann\FileIterator'); - $excludeList->addClassNamePrefix('SebastianBergmann\Invoker'); - $excludeList->addClassNamePrefix('SebastianBergmann\Template'); - $excludeList->addClassNamePrefix('SebastianBergmann\Timer'); - $excludeList->addClassNamePrefix('Doctrine\Instantiator'); - $excludeList->addClassNamePrefix('Prophecy'); - $excludeList->addStaticAttribute(ComparatorFactory::class, 'instance'); - - foreach ($this->backupStaticAttributesExcludeList as $class => $attributes) { - foreach ($attributes as $attribute) { - $excludeList->addStaticAttribute($class, $attribute); - } - } - - if (!empty($this->backupStaticAttributesBlacklist)) { - $this->addWarning('PHPUnit\Framework\TestCase::$backupStaticAttributesBlacklist is deprecated and will be removed in PHPUnit 10. Please use PHPUnit\Framework\TestCase::$backupStaticAttributesExcludeList instead.'); + if ($methodsInvoked !== []) { + /** @phpstan-ignore method.dynamicName */ + $emitter->{$finishedMethod}( + $test, + ...$methodsInvoked + ); + } - foreach ($this->backupStaticAttributesBlacklist as $class => $attributes) { - foreach ($attributes as $attribute) { - $excludeList->addStaticAttribute($class, $attribute); - } - } - } + if (isset($t)) { + throw $t; } + } - return new Snapshot( - $excludeList, - $backupGlobals, - (bool) $this->backupStaticAttributes, - false, - false, - false, - false, - false, - false, - false, - ); + /** + * @param non-empty-string $methodName + */ + private function methodDoesNotExistOrIsDeclaredInTestCase(string $methodName): bool + { + $reflector = new ReflectionObject($this); + + return !$reflector->hasMethod($methodName) || + $reflector->getMethod($methodName)->getDeclaringClass()->getName() === self::class; } /** - * @throws InvalidArgumentException - * @throws RiskyTestError + * @throws ExpectationFailedException */ - private function compareGlobalStateSnapshots(Snapshot $before, Snapshot $after): void + private function verifyExceptionExpectations(\Exception|Throwable $exception): void { - $backupGlobals = $this->backupGlobals === null || $this->backupGlobals; + if ($this->expectedException !== null) { + $this->assertThat( + $exception, + new ExceptionConstraint( + $this->expectedException, + ), + ); + } - if ($backupGlobals) { - $this->compareGlobalStateSnapshotPart( - $before->globalVariables(), - $after->globalVariables(), - "--- Global variables before the test\n+++ Global variables after the test\n", + if ($this->expectedExceptionMessage !== null) { + $this->assertThat( + $exception->getMessage(), + new ExceptionMessageIsOrContains( + $this->expectedExceptionMessage, + ), ); + } - $this->compareGlobalStateSnapshotPart( - $before->superGlobalVariables(), - $after->superGlobalVariables(), - "--- Super-global variables before the test\n+++ Super-global variables after the test\n", + if ($this->expectedExceptionMessageRegExp !== null) { + $this->assertThat( + $exception->getMessage(), + new ExceptionMessageMatchesRegularExpression( + $this->expectedExceptionMessageRegExp, + ), ); } - if ($this->backupStaticAttributes) { - $this->compareGlobalStateSnapshotPart( - $before->staticAttributes(), - $after->staticAttributes(), - "--- Static attributes before the test\n+++ Static attributes after the test\n", + if ($this->expectedExceptionCode !== null) { + $this->assertThat( + $exception->getCode(), + new ExceptionCode( + $this->expectedExceptionCode, + ), ); } } /** - * @throws RiskyTestError + * @throws AssertionFailedError */ - private function compareGlobalStateSnapshotPart(array $before, array $after, string $header): void + private function expectedExceptionWasNotRaised(): void { - if ($before != $after) { - $differ = new Differ($header); - $exporter = new Exporter; + if ($this->expectedException !== null) { + $this->assertThat( + null, + new ExceptionConstraint($this->expectedException), + ); + } elseif ($this->expectedExceptionMessage !== null) { + $this->numberOfAssertionsPerformed++; + + throw new AssertionFailedError( + sprintf( + 'Failed asserting that exception with message "%s" is thrown', + $this->expectedExceptionMessage, + ), + ); + } elseif ($this->expectedExceptionMessageRegExp !== null) { + $this->numberOfAssertionsPerformed++; - $diff = $differ->diff( - $exporter->export($before), - $exporter->export($after), + throw new AssertionFailedError( + sprintf( + 'Failed asserting that exception with message matching "%s" is thrown', + $this->expectedExceptionMessageRegExp, + ), ); + } elseif ($this->expectedExceptionCode !== null) { + $this->numberOfAssertionsPerformed++; - throw new RiskyTestError( - $diff, + throw new AssertionFailedError( + sprintf( + 'Failed asserting that exception with code "%s" is thrown', + $this->expectedExceptionCode, + ), ); } } - private function getProphet(): Prophet + private function isRegisteredFailure(Throwable $t): bool { - if ($this->prophet === null) { - $this->prophet = new Prophet; + foreach (array_keys($this->failureTypes) as $failureType) { + if ($t instanceof $failureType) { + return true; + } } - return $this->prophet; + return false; } /** - * @throws \SebastianBergmann\ObjectEnumerator\InvalidArgumentException + * @internal This method is not covered by the backward compatibility promise for PHPUnit */ - private function shouldInvocationMockerBeReset(MockObject $mock): bool + private function hasExpectationOnOutput(): bool { - $enumerator = new Enumerator; - - foreach ($enumerator->enumerate($this->dependencyInput) as $object) { - if ($mock === $object) { - return false; - } - } - - if (!is_array($this->testResult) && !is_object($this->testResult)) { - return true; - } + return is_string($this->outputExpectedString) || is_string($this->outputExpectedRegex); + } - return !in_array($mock, $enumerator->enumerate($this->testResult), true); + private function requirementsNotSatisfied(): bool + { + return (new Requirements)->requirementsNotSatisfiedFor(static::class, $this->methodName) !== []; } /** - * @throws \SebastianBergmann\ObjectEnumerator\InvalidArgumentException - * @throws \SebastianBergmann\ObjectReflector\InvalidArgumentException - * @throws InvalidArgumentException + * @see https://github.com/sebastianbergmann/phpunit/issues/6095 */ - private function registerMockObjectsFromTestArguments(array $testArguments, array &$visited = []): void + private function handleExceptionFromInvokedCountMockObjectRule(Throwable $t): void { - if ($this->registerMockObjectsFromTestArgumentsRecursively) { - foreach ((new Enumerator)->enumerate($testArguments) as $object) { - if ($object instanceof MockObject) { - $this->registerMockObject($object); - } - } - } else { - foreach ($testArguments as $testArgument) { - if ($testArgument instanceof MockObject) { - $testArgument = Cloner::clone($testArgument); - - $this->registerMockObject($testArgument); - } elseif (is_array($testArgument) && !in_array($testArgument, $visited, true)) { - $visited[] = $testArgument; - - $this->registerMockObjectsFromTestArguments( - $testArgument, - $visited, - ); - } - } + if (!$t instanceof ExpectationFailedException) { + return; } - } - private function setDoesNotPerformAssertionsFromAnnotation(): void - { - $annotations = TestUtil::parseTestMethodAnnotations( - static::class, - $this->name, - ); + $trace = $t->getTrace(); - if (isset($annotations['method']['doesNotPerformAssertions'])) { - $this->doesNotPerformAssertions = true; + if (isset($trace[0]['class']) && $trace[0]['class'] === InvokedCount::class) { + $this->numberOfAssertionsPerformed++; } } - private function unregisterCustomComparators(): void + private function startErrorLogCapture(): void { - $factory = ComparatorFactory::getInstance(); - - foreach ($this->customComparators as $comparator) { - $factory->unregister($comparator); + if (ini_get('display_errors') === '0') { + ShutdownHandler::setMessage( + 'Fatal error: Premature end of PHPUnit\'s PHP process. Use display_errors=On to see the error message.', + ); } - $this->customComparators = []; - } + $errorLogCapture = tmpfile(); - private function cleanupIniSettings(): void - { - foreach ($this->iniSettings as $varName => $oldValue) { - ini_set($varName, $oldValue); + if ($errorLogCapture === false) { + return; } - $this->iniSettings = []; - } + $capturePath = stream_get_meta_data($errorLogCapture)['uri']; - private function cleanupLocaleSettings(): void - { - foreach ($this->locale as $category => $locale) { - setlocale($category, $locale); + if (!@is_writable($capturePath)) { + return; } - $this->locale = []; + $this->errorLogCapture = $errorLogCapture; + $this->previousErrorLogTarget = ini_set('error_log', $capturePath); } /** - * @throws Exception + * @throws ErrorLogNotWritableException */ - private function checkExceptionExpectations(Throwable $throwable): bool + private function verifyErrorLogExpectation(): void { - $result = false; + if ($this->errorLogCapture === false) { + if ($this->expectErrorLog) { + throw new ErrorLogNotWritableException; + } - if ($this->expectedException !== null || $this->expectedExceptionCode !== null || $this->expectedExceptionMessage !== null || $this->expectedExceptionMessageRegExp !== null) { - $result = true; + return; } - if ($throwable instanceof Exception) { - $result = false; - } + $errorLogOutput = stream_get_contents($this->errorLogCapture); - if (is_string($this->expectedException)) { - try { - $reflector = new ReflectionClass($this->expectedException); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - $e->getCode(), - $e, - ); - } - // @codeCoverageIgnoreEnd + if ($this->expectErrorLog) { + $this->assertNotEmpty($errorLogOutput, 'error_log() was not called'); - if ($this->expectedException === 'PHPUnit\Framework\Exception' || - $this->expectedException === '\PHPUnit\Framework\Exception' || - $reflector->isSubclassOf(Exception::class)) { - $result = true; - } + return; } - return $result; - } + if ($errorLogOutput === false) { + return; + } - private function runInSeparateProcess(): bool - { - return ($this->runTestInSeparateProcess || $this->runClassInSeparateProcess) && - !$this->inIsolation && !$this instanceof PhptTestCase; + print $this->stripDateFromErrorLog($errorLogOutput); } - private function isCallableTestMethod(string $dependency): bool + private function handleErrorLogError(): void { - [$className, $methodName] = explode('::', $dependency); - - if (!class_exists($className)) { - return false; + if ($this->errorLogCapture === false) { + return; } - try { - $class = new ReflectionClass($className); - } catch (ReflectionException $e) { - return false; + if ($this->expectErrorLog) { + return; } - if (!$class->isSubclassOf(__CLASS__)) { - return false; + $errorLogOutput = stream_get_contents($this->errorLogCapture); + + if ($errorLogOutput !== false) { + print $this->stripDateFromErrorLog($errorLogOutput); } + } - if (!$class->hasMethod($methodName)) { - return false; + private function stopErrorLogCapture(): void + { + if ($this->errorLogCapture === false) { + return; } - try { - $method = $class->getMethod($methodName); - } catch (ReflectionException $e) { - return false; + ShutdownHandler::resetMessage(); + + fclose($this->errorLogCapture); + + $this->errorLogCapture = false; + + if ($this->previousErrorLogTarget === false) { + return; } - return TestUtil::isTestMethod($method); + ini_set('error_log', $this->previousErrorLogTarget); + + $this->previousErrorLogTarget = false; } /** - * @psalm-template RealInstanceType of object + * Creates a test stub for the specified interface or class. + * + * @template RealInstanceType of object * - * @psalm-param class-string $originalClassName + * @param class-string $type + * + * @throws InvalidArgumentException + * @throws MockObjectException + * @throws NoPreviousThrowableException * - * @psalm-return MockObject&RealInstanceType + * @return RealInstanceType&Stub */ - private function createMockObject(string $originalClassName): MockObject + final protected static function createStub(string $type): Stub { - return $this->getMockBuilder($originalClassName) - ->disableOriginalConstructor() - ->disableOriginalClone() - ->disableArgumentCloning() - ->disallowMockingUnknownTypes() - ->getMock(); + $stub = (new MockGenerator)->testDouble( + $type, + false, + callOriginalConstructor: false, + callOriginalClone: false, + returnValueGeneration: self::generateReturnValuesForTestDoubles(), + ); + + Event\Facade::emitter()->testCreatedStub($type); + + assert($stub instanceof $type); + assert($stub instanceof Stub); + + return $stub; + } + + /** + * @param list $interfaces + * + * @throws MockObjectException + */ + final protected static function createStubForIntersectionOfInterfaces(array $interfaces): Stub + { + $stub = (new MockGenerator)->testDoubleForInterfaceIntersection( + $interfaces, + false, + returnValueGeneration: self::generateReturnValuesForTestDoubles(), + ); + + Event\Facade::emitter()->testCreatedStubForIntersectionOfInterfaces($interfaces); + + return $stub; + } + + /** + * Creates (and configures) a test stub for the specified interface or class. + * + * @template RealInstanceType of object + * + * @param class-string $type + * @param array $configuration + * + * @throws InvalidArgumentException + * @throws MockObjectException + * @throws NoPreviousThrowableException + * + * @return RealInstanceType&Stub + */ + final protected static function createConfiguredStub(string $type, array $configuration): Stub + { + $o = self::createStub($type); + + foreach ($configuration as $method => $return) { + $o->method($method)->willReturn($return); + } + + return $o; + } + + private static function generateReturnValuesForTestDoubles(): bool + { + return MetadataRegistry::parser()->forClass(static::class)->isDisableReturnValueGenerationForTestDoubles()->isEmpty(); } } diff --git a/app/vendor/phpunit/phpunit/src/Framework/TestFailure.php b/app/vendor/phpunit/phpunit/src/Framework/TestFailure.php deleted file mode 100644 index f49dfc355..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/TestFailure.php +++ /dev/null @@ -1,155 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -use function get_class; -use function sprintf; -use function trim; -use PHPUnit\Framework\Error\Error; -use Throwable; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class TestFailure -{ - /** - * @var null|Test - */ - private $failedTest; - - /** - * @var Throwable - */ - private $thrownException; - - /** - * @var string - */ - private $testName; - - /** - * Returns a description for an exception. - */ - public static function exceptionToString(Throwable $e): string - { - if ($e instanceof SelfDescribing) { - $buffer = $e->toString(); - - if ($e instanceof ExpectationFailedException && $e->getComparisonFailure()) { - $buffer .= $e->getComparisonFailure()->getDiff(); - } - - if ($e instanceof PHPTAssertionFailedError) { - $buffer .= $e->getDiff(); - } - - if (!empty($buffer)) { - $buffer = trim($buffer) . "\n"; - } - - return $buffer; - } - - if ($e instanceof Error) { - return $e->getMessage() . "\n"; - } - - if ($e instanceof ExceptionWrapper) { - return $e->getClassName() . ': ' . $e->getMessage() . "\n"; - } - - return get_class($e) . ': ' . $e->getMessage() . "\n"; - } - - /** - * Constructs a TestFailure with the given test and exception. - */ - public function __construct(Test $failedTest, Throwable $t) - { - if ($failedTest instanceof SelfDescribing) { - $this->testName = $failedTest->toString(); - } else { - $this->testName = get_class($failedTest); - } - - if (!$failedTest instanceof TestCase || !$failedTest->isInIsolation()) { - $this->failedTest = $failedTest; - } - - $this->thrownException = $t; - } - - /** - * Returns a short description of the failure. - */ - public function toString(): string - { - return sprintf( - '%s: %s', - $this->testName, - $this->thrownException->getMessage(), - ); - } - - /** - * Returns a description for the thrown exception. - */ - public function getExceptionAsString(): string - { - return self::exceptionToString($this->thrownException); - } - - /** - * Returns the name of the failing test (including data set, if any). - */ - public function getTestName(): string - { - return $this->testName; - } - - /** - * Returns the failing test. - * - * Note: The test object is not set when the test is executed in process - * isolation. - * - * @see Exception - */ - public function failedTest(): ?Test - { - return $this->failedTest; - } - - /** - * Gets the thrown exception. - */ - public function thrownException(): Throwable - { - return $this->thrownException; - } - - /** - * Returns the exception's message. - */ - public function exceptionMessage(): string - { - return $this->thrownException()->getMessage(); - } - - /** - * Returns true if the thrown exception - * is of type AssertionFailedError. - */ - public function isFailure(): bool - { - return $this->thrownException() instanceof AssertionFailedError; - } -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/TestListener.php b/app/vendor/phpunit/phpunit/src/Framework/TestListener.php deleted file mode 100644 index eade600f2..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/TestListener.php +++ /dev/null @@ -1,45 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -use Throwable; - -/** - * This interface, as well as the associated mechanism for extending PHPUnit, - * will be removed in PHPUnit 10. There is no alternative available in this - * version of PHPUnit. - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @deprecated - * @see https://github.com/sebastianbergmann/phpunit/issues/4676 - */ -interface TestListener -{ - public function addError(Test $test, Throwable $t, float $time): void; - - public function addWarning(Test $test, Warning $e, float $time): void; - - public function addFailure(Test $test, AssertionFailedError $e, float $time): void; - - public function addIncompleteTest(Test $test, Throwable $t, float $time): void; - - public function addRiskyTest(Test $test, Throwable $t, float $time): void; - - public function addSkippedTest(Test $test, Throwable $t, float $time): void; - - public function startTestSuite(TestSuite $suite): void; - - public function endTestSuite(TestSuite $suite): void; - - public function startTest(Test $test): void; - - public function endTest(Test $test, float $time): void; -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/TestListenerDefaultImplementation.php b/app/vendor/phpunit/phpunit/src/Framework/TestListenerDefaultImplementation.php deleted file mode 100644 index 5731d98d5..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/TestListenerDefaultImplementation.php +++ /dev/null @@ -1,60 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -use Throwable; - -/** - * @deprecated The `TestListener` interface is deprecated - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - */ -trait TestListenerDefaultImplementation -{ - public function addError(Test $test, Throwable $t, float $time): void - { - } - - public function addWarning(Test $test, Warning $e, float $time): void - { - } - - public function addFailure(Test $test, AssertionFailedError $e, float $time): void - { - } - - public function addIncompleteTest(Test $test, Throwable $t, float $time): void - { - } - - public function addRiskyTest(Test $test, Throwable $t, float $time): void - { - } - - public function addSkippedTest(Test $test, Throwable $t, float $time): void - { - } - - public function startTestSuite(TestSuite $suite): void - { - } - - public function endTestSuite(TestSuite $suite): void - { - } - - public function startTest(Test $test): void - { - } - - public function endTest(Test $test, float $time): void - { - } -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/TestResult.php b/app/vendor/phpunit/phpunit/src/Framework/TestResult.php deleted file mode 100644 index 29d9535f0..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/TestResult.php +++ /dev/null @@ -1,1324 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -use const PHP_EOL; -use function class_exists; -use function count; -use function extension_loaded; -use function function_exists; -use function get_class; -use function sprintf; -use function xdebug_get_monitored_functions; -use function xdebug_is_debugger_active; -use function xdebug_start_function_monitor; -use function xdebug_stop_function_monitor; -use AssertionError; -use Countable; -use Error; -use PHPUnit\Util\ErrorHandler; -use PHPUnit\Util\ExcludeList; -use PHPUnit\Util\Printer; -use PHPUnit\Util\Test as TestUtil; -use ReflectionClass; -use ReflectionException; -use SebastianBergmann\CodeCoverage\CodeCoverage; -use SebastianBergmann\CodeCoverage\Exception as OriginalCodeCoverageException; -use SebastianBergmann\CodeCoverage\UnintentionallyCoveredCodeException; -use SebastianBergmann\Invoker\Invoker; -use SebastianBergmann\Invoker\TimeoutException; -use SebastianBergmann\RecursionContext\InvalidArgumentException; -use SebastianBergmann\ResourceOperations\ResourceOperations; -use SebastianBergmann\Timer\Timer; -use Throwable; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class TestResult implements Countable -{ - /** - * @var array - */ - private $passed = []; - - /** - * @var array - */ - private $passedTestClasses = []; - - /** - * @var bool - */ - private $currentTestSuiteFailed = false; - - /** - * @var TestFailure[] - */ - private $errors = []; - - /** - * @var TestFailure[] - */ - private $failures = []; - - /** - * @var TestFailure[] - */ - private $warnings = []; - - /** - * @var TestFailure[] - */ - private $notImplemented = []; - - /** - * @var TestFailure[] - */ - private $risky = []; - - /** - * @var TestFailure[] - */ - private $skipped = []; - - /** - * @deprecated Use the `TestHook` interfaces instead - * - * @var TestListener[] - */ - private $listeners = []; - - /** - * @var int - */ - private $runTests = 0; - - /** - * @var float - */ - private $time = 0; - - /** - * Code Coverage information. - * - * @var CodeCoverage - */ - private $codeCoverage; - - /** - * @var bool - */ - private $convertDeprecationsToExceptions = false; - - /** - * @var bool - */ - private $convertErrorsToExceptions = true; - - /** - * @var bool - */ - private $convertNoticesToExceptions = true; - - /** - * @var bool - */ - private $convertWarningsToExceptions = true; - - /** - * @var bool - */ - private $stop = false; - - /** - * @var bool - */ - private $stopOnError = false; - - /** - * @var bool - */ - private $stopOnFailure = false; - - /** - * @var bool - */ - private $stopOnWarning = false; - - /** - * @var bool - */ - private $beStrictAboutTestsThatDoNotTestAnything = true; - - /** - * @var bool - */ - private $beStrictAboutOutputDuringTests = false; - - /** - * @var bool - */ - private $beStrictAboutTodoAnnotatedTests = false; - - /** - * @var bool - */ - private $beStrictAboutResourceUsageDuringSmallTests = false; - - /** - * @var bool - */ - private $enforceTimeLimit = false; - - /** - * @var bool - */ - private $forceCoversAnnotation = false; - - /** - * @var int - */ - private $timeoutForSmallTests = 1; - - /** - * @var int - */ - private $timeoutForMediumTests = 10; - - /** - * @var int - */ - private $timeoutForLargeTests = 60; - - /** - * @var bool - */ - private $stopOnRisky = false; - - /** - * @var bool - */ - private $stopOnIncomplete = false; - - /** - * @var bool - */ - private $stopOnSkipped = false; - - /** - * @var bool - */ - private $lastTestFailed = false; - - /** - * @var int - */ - private $defaultTimeLimit = 0; - - /** - * @var bool - */ - private $stopOnDefect = false; - - /** - * @var bool - */ - private $registerMockObjectsFromTestArgumentsRecursively = false; - - /** - * @deprecated Use the `TestHook` interfaces instead - * - * @codeCoverageIgnore - * - * Registers a TestListener. - */ - public function addListener(TestListener $listener): void - { - $this->listeners[] = $listener; - } - - /** - * @deprecated Use the `TestHook` interfaces instead - * - * @codeCoverageIgnore - * - * Unregisters a TestListener. - */ - public function removeListener(TestListener $listener): void - { - foreach ($this->listeners as $key => $_listener) { - if ($listener === $_listener) { - unset($this->listeners[$key]); - } - } - } - - /** - * @deprecated Use the `TestHook` interfaces instead - * - * @codeCoverageIgnore - * - * Flushes all flushable TestListeners. - */ - public function flushListeners(): void - { - foreach ($this->listeners as $listener) { - if ($listener instanceof Printer) { - $listener->flush(); - } - } - } - - /** - * Adds an error to the list of errors. - */ - public function addError(Test $test, Throwable $t, float $time): void - { - if ($t instanceof RiskyTestError) { - $this->recordRisky($test, $t); - - $notifyMethod = 'addRiskyTest'; - - if ($test instanceof TestCase) { - $test->markAsRisky(); - } - - if ($this->stopOnRisky || $this->stopOnDefect) { - $this->stop(); - } - } elseif ($t instanceof IncompleteTest) { - $this->recordNotImplemented($test, $t); - - $notifyMethod = 'addIncompleteTest'; - - if ($this->stopOnIncomplete) { - $this->stop(); - } - } elseif ($t instanceof SkippedTest) { - $this->recordSkipped($test, $t); - - $notifyMethod = 'addSkippedTest'; - - if ($this->stopOnSkipped) { - $this->stop(); - } - } else { - $this->recordError($test, $t); - - $notifyMethod = 'addError'; - - if ($this->stopOnError || $this->stopOnFailure) { - $this->stop(); - } - } - - // @see https://github.com/sebastianbergmann/phpunit/issues/1953 - if ($t instanceof Error) { - $t = new ExceptionWrapper($t); - } - - foreach ($this->listeners as $listener) { - $listener->{$notifyMethod}($test, $t, $time); - } - - $this->lastTestFailed = true; - $this->time += $time; - } - - /** - * Adds a warning to the list of warnings. - * The passed in exception caused the warning. - */ - public function addWarning(Test $test, Warning $e, float $time): void - { - if ($this->stopOnWarning || $this->stopOnDefect) { - $this->stop(); - } - - $this->recordWarning($test, $e); - - foreach ($this->listeners as $listener) { - $listener->addWarning($test, $e, $time); - } - - $this->time += $time; - } - - /** - * Adds a failure to the list of failures. - * The passed in exception caused the failure. - */ - public function addFailure(Test $test, AssertionFailedError $e, float $time): void - { - if ($e instanceof RiskyTestError || $e instanceof OutputError) { - $this->recordRisky($test, $e); - - $notifyMethod = 'addRiskyTest'; - - if ($test instanceof TestCase) { - $test->markAsRisky(); - } - - if ($this->stopOnRisky || $this->stopOnDefect) { - $this->stop(); - } - } elseif ($e instanceof IncompleteTest) { - $this->recordNotImplemented($test, $e); - - $notifyMethod = 'addIncompleteTest'; - - if ($this->stopOnIncomplete) { - $this->stop(); - } - } elseif ($e instanceof SkippedTest) { - $this->recordSkipped($test, $e); - - $notifyMethod = 'addSkippedTest'; - - if ($this->stopOnSkipped) { - $this->stop(); - } - } else { - $this->failures[] = new TestFailure($test, $e); - $notifyMethod = 'addFailure'; - - if ($this->stopOnFailure || $this->stopOnDefect) { - $this->stop(); - } - } - - foreach ($this->listeners as $listener) { - $listener->{$notifyMethod}($test, $e, $time); - } - - $this->lastTestFailed = true; - $this->time += $time; - } - - /** - * Informs the result that a test suite will be started. - */ - public function startTestSuite(TestSuite $suite): void - { - $this->currentTestSuiteFailed = false; - - foreach ($this->listeners as $listener) { - $listener->startTestSuite($suite); - } - } - - /** - * Informs the result that a test suite was completed. - */ - public function endTestSuite(TestSuite $suite): void - { - if (!$this->currentTestSuiteFailed) { - $this->passedTestClasses[] = $suite->getName(); - } - - foreach ($this->listeners as $listener) { - $listener->endTestSuite($suite); - } - } - - /** - * Informs the result that a test will be started. - */ - public function startTest(Test $test): void - { - $this->lastTestFailed = false; - $this->runTests += count($test); - - foreach ($this->listeners as $listener) { - $listener->startTest($test); - } - } - - /** - * Informs the result that a test was completed. - * - * @throws InvalidArgumentException - */ - public function endTest(Test $test, float $time): void - { - foreach ($this->listeners as $listener) { - $listener->endTest($test, $time); - } - - if (!$this->lastTestFailed && $test instanceof TestCase) { - $class = get_class($test); - $key = $class . '::' . $test->getName(); - - $this->passed[$key] = [ - 'result' => $test->getResult(), - 'size' => TestUtil::getSize( - $class, - $test->getName(false), - ), - ]; - - $this->time += $time; - } - - if ($this->lastTestFailed && $test instanceof TestCase) { - $this->currentTestSuiteFailed = true; - } - } - - /** - * Returns true if no risky test occurred. - */ - public function allHarmless(): bool - { - return $this->riskyCount() === 0; - } - - /** - * Gets the number of risky tests. - */ - public function riskyCount(): int - { - return count($this->risky); - } - - /** - * Returns true if no incomplete test occurred. - */ - public function allCompletelyImplemented(): bool - { - return $this->notImplementedCount() === 0; - } - - /** - * Gets the number of incomplete tests. - */ - public function notImplementedCount(): int - { - return count($this->notImplemented); - } - - /** - * Returns an array of TestFailure objects for the risky tests. - * - * @return TestFailure[] - */ - public function risky(): array - { - return $this->risky; - } - - /** - * Returns an array of TestFailure objects for the incomplete tests. - * - * @return TestFailure[] - */ - public function notImplemented(): array - { - return $this->notImplemented; - } - - /** - * Returns true if no test has been skipped. - */ - public function noneSkipped(): bool - { - return $this->skippedCount() === 0; - } - - /** - * Gets the number of skipped tests. - */ - public function skippedCount(): int - { - return count($this->skipped); - } - - /** - * Returns an array of TestFailure objects for the skipped tests. - * - * @return TestFailure[] - */ - public function skipped(): array - { - return $this->skipped; - } - - /** - * Gets the number of detected errors. - */ - public function errorCount(): int - { - return count($this->errors); - } - - /** - * Returns an array of TestFailure objects for the errors. - * - * @return TestFailure[] - */ - public function errors(): array - { - return $this->errors; - } - - /** - * Gets the number of detected failures. - */ - public function failureCount(): int - { - return count($this->failures); - } - - /** - * Returns an array of TestFailure objects for the failures. - * - * @return TestFailure[] - */ - public function failures(): array - { - return $this->failures; - } - - /** - * Gets the number of detected warnings. - */ - public function warningCount(): int - { - return count($this->warnings); - } - - /** - * Returns an array of TestFailure objects for the warnings. - * - * @return TestFailure[] - */ - public function warnings(): array - { - return $this->warnings; - } - - /** - * Returns the names of the tests that have passed. - */ - public function passed(): array - { - return $this->passed; - } - - /** - * Returns the names of the TestSuites that have passed. - * - * This enables @depends-annotations for TestClassName::class - */ - public function passedClasses(): array - { - return $this->passedTestClasses; - } - - /** - * Returns whether code coverage information should be collected. - */ - public function getCollectCodeCoverageInformation(): bool - { - return $this->codeCoverage !== null; - } - - /** - * Runs a TestCase. - * - * @throws \SebastianBergmann\CodeCoverage\InvalidArgumentException - * @throws CodeCoverageException - * @throws InvalidArgumentException - * @throws UnintentionallyCoveredCodeException - */ - public function run(Test $test): void - { - Assert::resetCount(); - - $size = TestUtil::UNKNOWN; - - if ($test instanceof TestCase) { - $test->setRegisterMockObjectsFromTestArgumentsRecursively( - $this->registerMockObjectsFromTestArgumentsRecursively, - ); - - $isAnyCoverageRequired = TestUtil::requiresCodeCoverageDataCollection($test); - $size = $test->getSize(); - } - - $error = false; - $failure = false; - $warning = false; - $incomplete = false; - $risky = false; - $skipped = false; - - $this->startTest($test); - - if ($this->convertDeprecationsToExceptions || $this->convertErrorsToExceptions || $this->convertNoticesToExceptions || $this->convertWarningsToExceptions) { - $errorHandler = new ErrorHandler( - $this->convertDeprecationsToExceptions, - $this->convertErrorsToExceptions, - $this->convertNoticesToExceptions, - $this->convertWarningsToExceptions, - ); - - $errorHandler->register(); - } - - $collectCodeCoverage = $this->codeCoverage !== null && - !$test instanceof ErrorTestCase && - !$test instanceof WarningTestCase && - $isAnyCoverageRequired; - - if ($collectCodeCoverage) { - $this->codeCoverage->start($test); - } - - $monitorFunctions = $this->beStrictAboutResourceUsageDuringSmallTests && - !$test instanceof ErrorTestCase && - !$test instanceof WarningTestCase && - $size === TestUtil::SMALL && - function_exists('xdebug_start_function_monitor'); - - if ($monitorFunctions) { - /* @noinspection ForgottenDebugOutputInspection */ - xdebug_start_function_monitor(ResourceOperations::getFunctions()); - } - - $timer = new Timer; - $timer->start(); - - try { - $invoker = new Invoker; - - if (!$test instanceof ErrorTestCase && - !$test instanceof WarningTestCase && - $this->shouldTimeLimitBeEnforced($size) && - $invoker->canInvokeWithTimeout()) { - switch ($size) { - case TestUtil::SMALL: - $_timeout = $this->timeoutForSmallTests; - - break; - - case TestUtil::MEDIUM: - $_timeout = $this->timeoutForMediumTests; - - break; - - case TestUtil::LARGE: - $_timeout = $this->timeoutForLargeTests; - - break; - - default: - $_timeout = $this->defaultTimeLimit; - } - - $invoker->invoke([$test, 'runBare'], [], $_timeout); - } else { - $test->runBare(); - } - } catch (TimeoutException $e) { - $this->addFailure( - $test, - new RiskyTestError( - $e->getMessage(), - ), - $_timeout, - ); - - $risky = true; - } catch (AssertionFailedError $e) { - $failure = true; - - if ($e instanceof RiskyTestError) { - $risky = true; - } elseif ($e instanceof IncompleteTestError) { - $incomplete = true; - } elseif ($e instanceof SkippedTestError) { - $skipped = true; - } - } catch (AssertionError $e) { - $test->addToAssertionCount(1); - - $failure = true; - $frame = $e->getTrace()[0]; - - $e = new AssertionFailedError( - sprintf( - '%s in %s:%s', - $e->getMessage(), - $frame['file'] ?? $e->getFile(), - $frame['line'] ?? $e->getLine(), - ), - 0, - $e, - ); - } catch (Warning $e) { - $warning = true; - } catch (Exception $e) { - $error = true; - } catch (Throwable $e) { - $e = new ExceptionWrapper($e); - $error = true; - } - - $time = $timer->stop()->asSeconds(); - - $test->addToAssertionCount(Assert::getCount()); - - if ($monitorFunctions) { - $excludeList = new ExcludeList; - - /** @noinspection ForgottenDebugOutputInspection */ - $functions = xdebug_get_monitored_functions(); - - /* @noinspection ForgottenDebugOutputInspection */ - xdebug_stop_function_monitor(); - - foreach ($functions as $function) { - if (!$excludeList->isExcluded($function['filename'])) { - $this->addFailure( - $test, - new RiskyTestError( - sprintf( - '%s() used in %s:%s', - $function['function'], - $function['filename'], - $function['lineno'], - ), - ), - $time, - ); - } - } - } - - if ($this->beStrictAboutTestsThatDoNotTestAnything && - !$test->doesNotPerformAssertions() && - $test->getNumAssertions() === 0) { - $risky = true; - } - - if ($this->forceCoversAnnotation && !$error && !$failure && !$warning && !$incomplete && !$skipped && !$risky) { - $annotations = TestUtil::parseTestMethodAnnotations( - get_class($test), - $test->getName(false), - ); - - if (!isset($annotations['class']['covers']) && - !isset($annotations['method']['covers']) && - !isset($annotations['class']['coversNothing']) && - !isset($annotations['method']['coversNothing'])) { - $this->addFailure( - $test, - new MissingCoversAnnotationException( - 'This test does not have a @covers annotation but is expected to have one', - ), - $time, - ); - - $risky = true; - } - } - - if ($collectCodeCoverage) { - $append = !$risky && !$incomplete && !$skipped; - $linesToBeCovered = []; - $linesToBeUsed = []; - - if ($append && $test instanceof TestCase) { - try { - $linesToBeCovered = TestUtil::getLinesToBeCovered( - get_class($test), - $test->getName(false), - ); - - $linesToBeUsed = TestUtil::getLinesToBeUsed( - get_class($test), - $test->getName(false), - ); - } catch (InvalidCoversTargetException $cce) { - $this->addWarning( - $test, - new Warning( - $cce->getMessage(), - ), - $time, - ); - } - } - - try { - $this->codeCoverage->stop( - $append, - $linesToBeCovered, - $linesToBeUsed, - ); - } catch (UnintentionallyCoveredCodeException $cce) { - $unintentionallyCoveredCodeError = new UnintentionallyCoveredCodeError( - 'This test executed code that is not listed as code to be covered or used:' . - PHP_EOL . $cce->getMessage(), - ); - } catch (OriginalCodeCoverageException $cce) { - $error = true; - - $e = $e ?? $cce; - } - } - - if (isset($errorHandler)) { - $errorHandler->unregister(); - - unset($errorHandler); - } - - if ($error) { - $this->addError($test, $e, $time); - } elseif ($failure) { - $this->addFailure($test, $e, $time); - } elseif ($warning) { - $this->addWarning($test, $e, $time); - } elseif (isset($unintentionallyCoveredCodeError)) { - $this->addFailure( - $test, - $unintentionallyCoveredCodeError, - $time, - ); - } elseif ($this->beStrictAboutTestsThatDoNotTestAnything && - !$test->doesNotPerformAssertions() && - $test->getNumAssertions() === 0) { - try { - $reflected = new ReflectionClass($test); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - $e->getCode(), - $e, - ); - } - // @codeCoverageIgnoreEnd - - $name = $test->getName(false); - - if ($name && $reflected->hasMethod($name)) { - try { - $reflected = $reflected->getMethod($name); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - $e->getCode(), - $e, - ); - } - // @codeCoverageIgnoreEnd - } - - $this->addFailure( - $test, - new RiskyTestError( - sprintf( - "This test did not perform any assertions\n\n%s:%d", - $reflected->getFileName(), - $reflected->getStartLine(), - ), - ), - $time, - ); - } elseif ($this->beStrictAboutTestsThatDoNotTestAnything && - $test->doesNotPerformAssertions() && - $test->getNumAssertions() > 0) { - $this->addFailure( - $test, - new RiskyTestError( - sprintf( - 'This test is annotated with "@doesNotPerformAssertions" but performed %d assertions', - $test->getNumAssertions(), - ), - ), - $time, - ); - } elseif ($this->beStrictAboutOutputDuringTests && $test->hasOutput()) { - $this->addFailure( - $test, - new OutputError( - sprintf( - 'Test code or tested code printed unexpected output: %s', - $test->getActualOutput(), - ), - ), - $time, - ); - } elseif ($this->beStrictAboutTodoAnnotatedTests && $test instanceof TestCase) { - $annotations = TestUtil::parseTestMethodAnnotations( - get_class($test), - $test->getName(false), - ); - - if (isset($annotations['method']['todo'])) { - $this->addFailure( - $test, - new RiskyTestError( - 'Test method is annotated with @todo', - ), - $time, - ); - } - } - - $this->endTest($test, $time); - } - - /** - * Gets the number of run tests. - */ - public function count(): int - { - return $this->runTests; - } - - /** - * Checks whether the test run should stop. - */ - public function shouldStop(): bool - { - return $this->stop; - } - - /** - * Marks that the test run should stop. - */ - public function stop(): void - { - $this->stop = true; - } - - /** - * Returns the code coverage object. - */ - public function getCodeCoverage(): ?CodeCoverage - { - return $this->codeCoverage; - } - - /** - * Sets the code coverage object. - */ - public function setCodeCoverage(CodeCoverage $codeCoverage): void - { - $this->codeCoverage = $codeCoverage; - } - - /** - * Enables or disables the deprecation-to-exception conversion. - */ - public function convertDeprecationsToExceptions(bool $flag): void - { - $this->convertDeprecationsToExceptions = $flag; - } - - /** - * Returns the deprecation-to-exception conversion setting. - */ - public function getConvertDeprecationsToExceptions(): bool - { - return $this->convertDeprecationsToExceptions; - } - - /** - * Enables or disables the error-to-exception conversion. - */ - public function convertErrorsToExceptions(bool $flag): void - { - $this->convertErrorsToExceptions = $flag; - } - - /** - * Returns the error-to-exception conversion setting. - */ - public function getConvertErrorsToExceptions(): bool - { - return $this->convertErrorsToExceptions; - } - - /** - * Enables or disables the notice-to-exception conversion. - */ - public function convertNoticesToExceptions(bool $flag): void - { - $this->convertNoticesToExceptions = $flag; - } - - /** - * Returns the notice-to-exception conversion setting. - */ - public function getConvertNoticesToExceptions(): bool - { - return $this->convertNoticesToExceptions; - } - - /** - * Enables or disables the warning-to-exception conversion. - */ - public function convertWarningsToExceptions(bool $flag): void - { - $this->convertWarningsToExceptions = $flag; - } - - /** - * Returns the warning-to-exception conversion setting. - */ - public function getConvertWarningsToExceptions(): bool - { - return $this->convertWarningsToExceptions; - } - - /** - * Enables or disables the stopping when an error occurs. - */ - public function stopOnError(bool $flag): void - { - $this->stopOnError = $flag; - } - - /** - * Enables or disables the stopping when a failure occurs. - */ - public function stopOnFailure(bool $flag): void - { - $this->stopOnFailure = $flag; - } - - /** - * Enables or disables the stopping when a warning occurs. - */ - public function stopOnWarning(bool $flag): void - { - $this->stopOnWarning = $flag; - } - - public function beStrictAboutTestsThatDoNotTestAnything(bool $flag): void - { - $this->beStrictAboutTestsThatDoNotTestAnything = $flag; - } - - public function isStrictAboutTestsThatDoNotTestAnything(): bool - { - return $this->beStrictAboutTestsThatDoNotTestAnything; - } - - public function beStrictAboutOutputDuringTests(bool $flag): void - { - $this->beStrictAboutOutputDuringTests = $flag; - } - - public function isStrictAboutOutputDuringTests(): bool - { - return $this->beStrictAboutOutputDuringTests; - } - - public function beStrictAboutResourceUsageDuringSmallTests(bool $flag): void - { - $this->beStrictAboutResourceUsageDuringSmallTests = $flag; - } - - public function isStrictAboutResourceUsageDuringSmallTests(): bool - { - return $this->beStrictAboutResourceUsageDuringSmallTests; - } - - public function enforceTimeLimit(bool $flag): void - { - $this->enforceTimeLimit = $flag; - } - - public function enforcesTimeLimit(): bool - { - return $this->enforceTimeLimit; - } - - public function beStrictAboutTodoAnnotatedTests(bool $flag): void - { - $this->beStrictAboutTodoAnnotatedTests = $flag; - } - - public function isStrictAboutTodoAnnotatedTests(): bool - { - return $this->beStrictAboutTodoAnnotatedTests; - } - - public function forceCoversAnnotation(): void - { - $this->forceCoversAnnotation = true; - } - - public function forcesCoversAnnotation(): bool - { - return $this->forceCoversAnnotation; - } - - /** - * Enables or disables the stopping for risky tests. - */ - public function stopOnRisky(bool $flag): void - { - $this->stopOnRisky = $flag; - } - - /** - * Enables or disables the stopping for incomplete tests. - */ - public function stopOnIncomplete(bool $flag): void - { - $this->stopOnIncomplete = $flag; - } - - /** - * Enables or disables the stopping for skipped tests. - */ - public function stopOnSkipped(bool $flag): void - { - $this->stopOnSkipped = $flag; - } - - /** - * Enables or disables the stopping for defects: error, failure, warning. - */ - public function stopOnDefect(bool $flag): void - { - $this->stopOnDefect = $flag; - } - - /** - * Returns the time spent running the tests. - */ - public function time(): float - { - return $this->time; - } - - /** - * Returns whether the entire test was successful or not. - */ - public function wasSuccessful(): bool - { - return $this->wasSuccessfulIgnoringWarnings() && empty($this->warnings); - } - - public function wasSuccessfulIgnoringWarnings(): bool - { - return empty($this->errors) && empty($this->failures); - } - - public function wasSuccessfulAndNoTestIsRiskyOrSkippedOrIncomplete(): bool - { - return $this->wasSuccessful() && $this->allHarmless() && $this->allCompletelyImplemented() && $this->noneSkipped(); - } - - /** - * Sets the default timeout for tests. - */ - public function setDefaultTimeLimit(int $timeout): void - { - $this->defaultTimeLimit = $timeout; - } - - /** - * Sets the timeout for small tests. - */ - public function setTimeoutForSmallTests(int $timeout): void - { - $this->timeoutForSmallTests = $timeout; - } - - /** - * Sets the timeout for medium tests. - */ - public function setTimeoutForMediumTests(int $timeout): void - { - $this->timeoutForMediumTests = $timeout; - } - - /** - * Sets the timeout for large tests. - */ - public function setTimeoutForLargeTests(int $timeout): void - { - $this->timeoutForLargeTests = $timeout; - } - - /** - * Returns the set timeout for large tests. - */ - public function getTimeoutForLargeTests(): int - { - return $this->timeoutForLargeTests; - } - - public function setRegisterMockObjectsFromTestArgumentsRecursively(bool $flag): void - { - $this->registerMockObjectsFromTestArgumentsRecursively = $flag; - } - - private function recordError(Test $test, Throwable $t): void - { - $this->errors[] = new TestFailure($test, $t); - } - - private function recordNotImplemented(Test $test, Throwable $t): void - { - $this->notImplemented[] = new TestFailure($test, $t); - } - - private function recordRisky(Test $test, Throwable $t): void - { - $this->risky[] = new TestFailure($test, $t); - } - - private function recordSkipped(Test $test, Throwable $t): void - { - $this->skipped[] = new TestFailure($test, $t); - } - - private function recordWarning(Test $test, Throwable $t): void - { - $this->warnings[] = new TestFailure($test, $t); - } - - private function shouldTimeLimitBeEnforced(int $size): bool - { - if (!$this->enforceTimeLimit) { - return false; - } - - if (!(($this->defaultTimeLimit || $size !== TestUtil::UNKNOWN))) { - return false; - } - - if (!extension_loaded('pcntl')) { - return false; - } - - if (!class_exists(Invoker::class)) { - return false; - } - - if (extension_loaded('xdebug') && xdebug_is_debugger_active()) { - return false; - } - - return true; - } -} diff --git a/app/vendor/phpunit/phpunit/src/Framework/TestRunner/ChildProcessResultProcessor.php b/app/vendor/phpunit/phpunit/src/Framework/TestRunner/ChildProcessResultProcessor.php new file mode 100644 index 000000000..af487c196 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/TestRunner/ChildProcessResultProcessor.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function assert; +use function trim; +use function unserialize; +use PHPUnit\Event\Code\TestMethodBuilder; +use PHPUnit\Event\Code\ThrowableBuilder; +use PHPUnit\Event\Emitter; +use PHPUnit\Event\Facade; +use PHPUnit\Runner\CodeCoverage; +use PHPUnit\TestRunner\TestResult\PassedTests; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ChildProcessResultProcessor +{ + private Facade $eventFacade; + private Emitter $emitter; + private PassedTests $passedTests; + private CodeCoverage $codeCoverage; + + public function __construct(Facade $eventFacade, Emitter $emitter, PassedTests $passedTests, CodeCoverage $codeCoverage) + { + $this->eventFacade = $eventFacade; + $this->emitter = $emitter; + $this->passedTests = $passedTests; + $this->codeCoverage = $codeCoverage; + } + + public function process(Test $test, string $serializedProcessResult, string $stderr): void + { + if ($stderr !== '') { + $exception = new Exception(trim($stderr)); + + assert($test instanceof TestCase); + + $this->emitter->testErrored( + TestMethodBuilder::fromTestCase($test), + ThrowableBuilder::from($exception), + ); + + return; + } + + $childResult = @unserialize($serializedProcessResult); + + if ($childResult === false) { + $this->emitter->childProcessErrored(); + + $exception = new AssertionFailedError('Test was run in child process and ended unexpectedly'); + + assert($test instanceof TestCase); + + $this->emitter->testErrored( + TestMethodBuilder::fromTestCase($test), + ThrowableBuilder::from($exception), + ); + + $this->emitter->testFinished( + TestMethodBuilder::fromTestCase($test), + 0, + ); + + return; + } + + $this->eventFacade->forward($childResult->events); + $this->passedTests->import($childResult->passedTests); + + assert($test instanceof TestCase); + + $test->setResult($childResult->testResult); + $test->addToAssertionCount($childResult->numAssertions); + + if (!$this->codeCoverage->isActive()) { + return; + } + + // @codeCoverageIgnoreStart + if (!$childResult->codeCoverage instanceof \SebastianBergmann\CodeCoverage\CodeCoverage) { + return; + } + + CodeCoverage::instance()->codeCoverage()->merge( + $childResult->codeCoverage, + ); + // @codeCoverageIgnoreEnd + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/TestRunner/IsolatedTestRunner.php b/app/vendor/phpunit/phpunit/src/Framework/TestRunner/IsolatedTestRunner.php new file mode 100644 index 000000000..0322a6046 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/TestRunner/IsolatedTestRunner.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 PHPUnit\Framework; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This interface is not covered by the backward compatibility promise for PHPUnit + */ +interface IsolatedTestRunner +{ + public function run(TestCase $test, bool $runEntireClass, bool $preserveGlobalState): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/TestRunner/IsolatedTestRunnerRegistry.php b/app/vendor/phpunit/phpunit/src/Framework/TestRunner/IsolatedTestRunnerRegistry.php new file mode 100644 index 000000000..058246543 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/TestRunner/IsolatedTestRunnerRegistry.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 PHPUnit\Framework; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class IsolatedTestRunnerRegistry +{ + private static ?IsolatedTestRunner $runner = null; + + public static function run(TestCase $test, bool $runEntireClass, bool $preserveGlobalState): void + { + if (self::$runner === null) { + self::$runner = new SeparateProcessTestRunner; + } + + self::$runner->run($test, $runEntireClass, $preserveGlobalState); + } + + public static function set(IsolatedTestRunner $runner): void + { + self::$runner = $runner; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/TestRunner/SeparateProcessTestRunner.php b/app/vendor/phpunit/phpunit/src/Framework/TestRunner/SeparateProcessTestRunner.php new file mode 100644 index 000000000..6a6e2adeb --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/TestRunner/SeparateProcessTestRunner.php @@ -0,0 +1,164 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use function assert; +use function defined; +use function get_include_path; +use function hrtime; +use function serialize; +use function sys_get_temp_dir; +use function tempnam; +use function unlink; +use function unserialize; +use function var_export; +use PHPUnit\Event\NoPreviousThrowableException; +use PHPUnit\Runner\CodeCoverage; +use PHPUnit\TextUI\Configuration\Registry as ConfigurationRegistry; +use PHPUnit\Util\GlobalState; +use PHPUnit\Util\PHP\Job; +use PHPUnit\Util\PHP\JobRunnerRegistry; +use ReflectionClass; +use SebastianBergmann\Template\InvalidArgumentException; +use SebastianBergmann\Template\Template; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class SeparateProcessTestRunner implements IsolatedTestRunner +{ + /** + * @throws \PHPUnit\Runner\Exception + * @throws \PHPUnit\Util\Exception + * @throws Exception + * @throws InvalidArgumentException + * @throws NoPreviousThrowableException + * @throws ProcessIsolationException + */ + public function run(TestCase $test, bool $runEntireClass, bool $preserveGlobalState): void + { + $class = new ReflectionClass($test); + + if ($runEntireClass) { + $template = new Template( + __DIR__ . '/templates/class.tpl', + ); + } else { + $template = new Template( + __DIR__ . '/templates/method.tpl', + ); + } + + $bootstrap = ''; + $constants = ''; + $globals = ''; + $includedFiles = ''; + $iniSettings = ''; + + if (ConfigurationRegistry::get()->hasBootstrap()) { + $bootstrap = ConfigurationRegistry::get()->bootstrap(); + } + + if ($preserveGlobalState) { + $constants = GlobalState::getConstantsAsString(); + $globals = GlobalState::getGlobalsAsString(); + $includedFiles = GlobalState::getIncludedFilesAsString(); + $iniSettings = GlobalState::getIniSettingsAsString(); + } + + $coverage = CodeCoverage::instance()->isActive() ? 'true' : 'false'; + + if (defined('PHPUNIT_COMPOSER_INSTALL')) { + $composerAutoload = var_export(PHPUNIT_COMPOSER_INSTALL, true); + } else { + $composerAutoload = '\'\''; + } + + if (defined('__PHPUNIT_PHAR__')) { + $phar = var_export(__PHPUNIT_PHAR__, true); + } else { + $phar = '\'\''; + } + + $data = var_export(serialize($test->providedData()), true); + $dataName = var_export($test->dataName(), true); + $dependencyInput = var_export(serialize($test->dependencyInput()), true); + $includePath = var_export(get_include_path(), true); + // must do these fixes because TestCaseMethod.tpl has unserialize('{data}') in it, and we can't break BC + // the lines above used to use addcslashes() rather than var_export(), which breaks null byte escape sequences + $data = "'." . $data . ".'"; + $dataName = "'.(" . $dataName . ").'"; + $dependencyInput = "'." . $dependencyInput . ".'"; + $includePath = "'." . $includePath . ".'"; + $offset = hrtime(); + $serializedConfiguration = $this->saveConfigurationForChildProcess(); + $processResultFile = tempnam(sys_get_temp_dir(), 'phpunit_'); + + $file = $class->getFileName(); + + assert($file !== false); + + $var = [ + 'bootstrap' => $bootstrap, + 'composerAutoload' => $composerAutoload, + 'phar' => $phar, + 'filename' => $file, + 'className' => $class->getName(), + 'collectCodeCoverageInformation' => $coverage, + 'data' => $data, + 'dataName' => $dataName, + 'dependencyInput' => $dependencyInput, + 'constants' => $constants, + 'globals' => $globals, + 'include_path' => $includePath, + 'included_files' => $includedFiles, + 'iniSettings' => $iniSettings, + 'name' => $test->name(), + 'offsetSeconds' => (string) $offset[0], + 'offsetNanoseconds' => (string) $offset[1], + 'serializedConfiguration' => $serializedConfiguration, + 'processResultFile' => $processResultFile, + ]; + + if (!$runEntireClass) { + $var['methodName'] = $test->name(); + } + + $template->setVar($var); + + $code = $template->render(); + + assert($code !== ''); + + JobRunnerRegistry::runTestJob(new Job($code), $processResultFile, $test); + + @unlink($serializedConfiguration); + } + + /** + * @throws ProcessIsolationException + */ + private function saveConfigurationForChildProcess(): string + { + $path = tempnam(sys_get_temp_dir(), 'phpunit_'); + + if ($path === false) { + throw new ProcessIsolationException; + } + + if (!ConfigurationRegistry::saveTo($path)) { + throw new ProcessIsolationException; + } + + return $path; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/TestRunner/TestRunner.php b/app/vendor/phpunit/phpunit/src/Framework/TestRunner/TestRunner.php new file mode 100644 index 000000000..e6cf03f6b --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/TestRunner/TestRunner.php @@ -0,0 +1,395 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework; + +use const PHP_EOL; +use function array_diff_assoc; +use function array_intersect; +use function array_unique; +use function assert; +use function extension_loaded; +use function sprintf; +use function xdebug_is_debugger_active; +use AssertionError; +use PHPUnit\Event\Facade; +use PHPUnit\Metadata\Api\CodeCoverage as CodeCoverageMetadataApi; +use PHPUnit\Metadata\Parser\Registry as MetadataRegistry; +use PHPUnit\Runner\CodeCoverage; +use PHPUnit\Runner\ErrorHandler; +use PHPUnit\Runner\Exception; +use PHPUnit\TextUI\Configuration\Configuration; +use PHPUnit\TextUI\Configuration\Registry as ConfigurationRegistry; +use SebastianBergmann\CodeCoverage\Exception as CodeCoverageException; +use SebastianBergmann\CodeCoverage\InvalidArgumentException; +use SebastianBergmann\CodeCoverage\Test\Target\TargetCollection; +use SebastianBergmann\CodeCoverage\UnintentionallyCoveredCodeException; +use SebastianBergmann\Invoker\Invoker; +use SebastianBergmann\Invoker\TimeoutException; +use Throwable; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class TestRunner +{ + private ?bool $timeLimitCanBeEnforced = null; + private readonly Configuration $configuration; + + public function __construct() + { + $this->configuration = ConfigurationRegistry::get(); + } + + /** + * @throws Exception + * @throws InvalidArgumentException + * @throws UnintentionallyCoveredCodeException + */ + public function run(TestCase $test): void + { + Assert::resetCount(); + + $codeCoverageMetadataApi = new CodeCoverageMetadataApi; + + $coversTargets = $codeCoverageMetadataApi->coversTargets( + $test::class, + $test->name(), + ); + + $usesTargets = $codeCoverageMetadataApi->usesTargets( + $test::class, + $test->name(), + ); + + $shouldCodeCoverageBeCollected = $codeCoverageMetadataApi->shouldCodeCoverageBeCollectedFor($test); + + $this->performSanityChecks($test, $coversTargets, $usesTargets, $shouldCodeCoverageBeCollected); + + $error = false; + $failure = false; + $incomplete = false; + $risky = false; + $skipped = false; + + if ($this->shouldErrorHandlerBeUsed($test)) { + ErrorHandler::instance()->enable($test); + } + + $collectCodeCoverage = CodeCoverage::instance()->isActive() && + $shouldCodeCoverageBeCollected; + + if ($collectCodeCoverage) { + CodeCoverage::instance()->start($test); + } + + try { + if ($this->canTimeLimitBeEnforced() && + $this->shouldTimeLimitBeEnforced($test)) { + $risky = $this->runTestWithTimeout($test); + } else { + $test->runBare(); + } + } catch (AssertionFailedError $e) { + $failure = true; + + if ($e instanceof IncompleteTestError) { + $incomplete = true; + } elseif ($e instanceof SkippedTest) { + $skipped = true; + } + } catch (AssertionError $e) { + $test->addToAssertionCount(1); + + $failure = true; + $frame = $e->getTrace()[0]; + + assert(isset($frame['file'])); + assert(isset($frame['line'])); + + $e = new AssertionFailedError( + sprintf( + '%s in %s:%s', + $e->getMessage(), + $frame['file'], + $frame['line'], + ), + ); + } catch (Throwable $e) { + $error = true; + } + + $test->addToAssertionCount(Assert::getCount()); + + if ($this->configuration->reportUselessTests() && + !$test->doesNotPerformAssertions() && + $test->numberOfAssertionsPerformed() === 0) { + $risky = true; + } + + if (!$error && !$failure && !$incomplete && !$skipped && !$risky && + $this->configuration->requireCoverageMetadata() && + !$this->hasCoverageMetadata($test::class, $test->name())) { + Facade::emitter()->testConsideredRisky( + $test->valueObjectForEvents(), + 'This test does not define a code coverage target but is expected to do so', + ); + + $risky = true; + } + + if ($collectCodeCoverage) { + $append = !$risky && !$incomplete && !$skipped; + + if (!$append) { + $coversTargets = false; + $usesTargets = null; + } + + try { + CodeCoverage::instance()->stop( + $append, + $coversTargets, + $usesTargets, + ); + } catch (UnintentionallyCoveredCodeException $cce) { + Facade::emitter()->testConsideredRisky( + $test->valueObjectForEvents(), + 'This test executed code that is not listed as code to be covered or used:' . + PHP_EOL . + $cce->getMessage(), + ); + } catch (CodeCoverageException $cce) { + $error = true; + + $e = $e ?? $cce; + } + } + + ErrorHandler::instance()->disable(); + + if (!$error && + !$incomplete && + !$skipped && + $this->configuration->reportUselessTests() && + !$test->doesNotPerformAssertions() && + $test->numberOfAssertionsPerformed() === 0) { + Facade::emitter()->testConsideredRisky( + $test->valueObjectForEvents(), + 'This test did not perform any assertions', + ); + } + + if ($test->doesNotPerformAssertions() && + $test->numberOfAssertionsPerformed() > 0) { + Facade::emitter()->testConsideredRisky( + $test->valueObjectForEvents(), + sprintf( + 'This test is not expected to perform assertions but performed %d assertion%s', + $test->numberOfAssertionsPerformed(), + $test->numberOfAssertionsPerformed() > 1 ? 's' : '', + ), + ); + } + + if ($test->hasUnexpectedOutput()) { + Facade::emitter()->testPrintedUnexpectedOutput($test->output()); + } + + if ($this->configuration->disallowTestOutput() && $test->hasUnexpectedOutput()) { + Facade::emitter()->testConsideredRisky( + $test->valueObjectForEvents(), + sprintf( + 'Test code or tested code printed unexpected output: %s', + $test->output(), + ), + ); + } + + if ($test->wasPrepared()) { + Facade::emitter()->testFinished( + $test->valueObjectForEvents(), + $test->numberOfAssertionsPerformed(), + ); + } + } + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + private function hasCoverageMetadata(string $className, string $methodName): bool + { + foreach (MetadataRegistry::parser()->forClassAndMethod($className, $methodName) as $metadata) { + if ($metadata->isCoversNamespace()) { + return true; + } + + if ($metadata->isCoversTrait()) { + return true; + } + + if ($metadata->isCoversClass()) { + return true; + } + + if ($metadata->isCoversClassesThatExtendClass()) { + return true; + } + + if ($metadata->isCoversClassesThatImplementInterface()) { + return true; + } + + if ($metadata->isCoversMethod()) { + return true; + } + + if ($metadata->isCoversFunction()) { + return true; + } + + if ($metadata->isCoversNothing()) { + return true; + } + } + + return false; + } + + private function canTimeLimitBeEnforced(): bool + { + if ($this->timeLimitCanBeEnforced !== null) { + return $this->timeLimitCanBeEnforced; + } + + $this->timeLimitCanBeEnforced = (new Invoker)->canInvokeWithTimeout(); + + return $this->timeLimitCanBeEnforced; + } + + private function shouldTimeLimitBeEnforced(TestCase $test): bool + { + if (!$this->configuration->enforceTimeLimit()) { + return false; + } + + if (!(($this->configuration->defaultTimeLimit() > 0 || $test->size()->isKnown()))) { + return false; + } + + if (extension_loaded('xdebug') && xdebug_is_debugger_active()) { + return false; + } + + return true; + } + + /** + * @throws Throwable + */ + private function runTestWithTimeout(TestCase $test): bool + { + $_timeout = $this->configuration->defaultTimeLimit(); + $testSize = $test->size(); + + if ($testSize->isSmall()) { + $_timeout = $this->configuration->timeoutForSmallTests(); + } elseif ($testSize->isMedium()) { + $_timeout = $this->configuration->timeoutForMediumTests(); + } elseif ($testSize->isLarge()) { + $_timeout = $this->configuration->timeoutForLargeTests(); + } + + try { + (new Invoker)->invoke([$test, 'runBare'], [], $_timeout); + } catch (TimeoutException) { + Facade::emitter()->testConsideredRisky( + $test->valueObjectForEvents(), + sprintf( + 'This test was aborted after %d second%s', + $_timeout, + $_timeout !== 1 ? 's' : '', + ), + ); + + return true; + } + + return false; + } + + private function shouldErrorHandlerBeUsed(TestCase $test): bool + { + if (MetadataRegistry::parser()->forMethod($test::class, $test->name())->isWithoutErrorHandler()->isNotEmpty()) { + return false; + } + + return true; + } + + private function performSanityChecks(TestCase $test, TargetCollection $coversTargets, TargetCollection $usesTargets, bool $shouldCodeCoverageBeCollected): void + { + if (!$shouldCodeCoverageBeCollected) { + if ($coversTargets->isNotEmpty() || $usesTargets->isNotEmpty()) { + Facade::emitter()->testTriggeredPhpunitWarning( + $test->valueObjectForEvents(), + '#[Covers*] and #[Uses*] attributes do not have an effect when the #[CoversNothing] attribute is used', + ); + } + } + + $coversAsString = []; + $usesAsString = []; + + foreach ($coversTargets as $coversTarget) { + $coversAsString[] = $coversTarget->description(); + } + + foreach ($usesTargets as $usesTarget) { + $usesAsString[] = $usesTarget->description(); + } + + $coversDuplicates = array_unique(array_diff_assoc($coversAsString, array_unique($coversAsString))); + $usesDuplicates = array_unique(array_diff_assoc($usesAsString, array_unique($usesAsString))); + $coversAndUses = array_intersect($coversAsString, $usesAsString); + + foreach ($coversDuplicates as $target) { + Facade::emitter()->testTriggeredPhpunitWarning( + $test->valueObjectForEvents(), + sprintf( + '%s is targeted multiple times by the same "Covers" attribute', + $target, + ), + ); + } + + foreach ($usesDuplicates as $target) { + Facade::emitter()->testTriggeredPhpunitWarning( + $test->valueObjectForEvents(), + sprintf( + '%s is targeted multiple times by the same "Uses" attribute', + $target, + ), + ); + } + + foreach ($coversAndUses as $target) { + Facade::emitter()->testTriggeredPhpunitWarning( + $test->valueObjectForEvents(), + sprintf( + '%s is targeted by both "Covers" and "Uses" attributes', + $target, + ), + ); + } + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/TestRunner/templates/class.tpl b/app/vendor/phpunit/phpunit/src/Framework/TestRunner/templates/class.tpl new file mode 100644 index 000000000..5edd7ee29 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/TestRunner/templates/class.tpl @@ -0,0 +1,137 @@ +initForIsolation( + PHPUnit\Event\Telemetry\HRTime::fromSecondsAndNanoseconds( + {offsetSeconds}, + {offsetNanoseconds} + ), + ); + + require_once '{filename}'; + + $configuration = ConfigurationRegistry::get(); + + if ({collectCodeCoverageInformation}) { + CodeCoverage::instance()->init($configuration, CodeCoverageFilterRegistry::instance(), true); + } + + $deprecationTriggers = [ + 'functions' => [], + 'methods' => [], + ]; + + foreach ($configuration->source()->deprecationTriggers()['functions'] as $function) { + $deprecationTriggers['functions'][] = $function; + } + + foreach ($configuration->source()->deprecationTriggers()['methods'] as $method) { + [$className, $methodName] = explode('::', $method); + + $deprecationTriggers['methods'][] = [ + 'className' => $className, + 'methodName' => $methodName, + ]; + } + + ErrorHandler::instance()->useDeprecationTriggers($deprecationTriggers); + + $test = new {className}('{name}'); + + $test->setData('{dataName}', unserialize('{data}')); + $test->setDependencyInput(unserialize('{dependencyInput}')); + $test->setInIsolation(true); + + ob_end_clean(); + + $test->run(); + + $output = ''; + + if (!$test->expectsOutput()) { + $output = $test->output(); + } + + ini_set('xdebug.scream', '0'); + + // Not every STDOUT target stream is rewindable + @rewind(STDOUT); + + if ($stdout = @stream_get_contents(STDOUT)) { + $output = $stdout . $output; + $streamMetaData = stream_get_meta_data(STDOUT); + + if (!empty($streamMetaData['stream_type']) && 'STDIO' === $streamMetaData['stream_type']) { + @ftruncate(STDOUT, 0); + @rewind(STDOUT); + } + } + + file_put_contents( + '{processResultFile}', + serialize( + (object)[ + 'testResult' => $test->result(), + 'codeCoverage' => {collectCodeCoverageInformation} ? CodeCoverage::instance()->codeCoverage() : null, + 'numAssertions' => $test->numberOfAssertionsPerformed(), + 'output' => $output, + 'events' => $dispatcher->flush(), + 'passedTests' => PassedTests::instance() + ] + ) + ); +} + +function __phpunit_error_handler($errno, $errstr, $errfile, $errline) +{ + return true; +} + +set_error_handler('__phpunit_error_handler'); + +{constants} +{included_files} +{globals} + +restore_error_handler(); + +ConfigurationRegistry::loadFrom('{serializedConfiguration}'); +(new PhpHandler)->handle(ConfigurationRegistry::get()->php()); + +if ('{bootstrap}' !== '') { + require_once '{bootstrap}'; +} + +__phpunit_run_isolated_test(); diff --git a/app/vendor/phpunit/phpunit/src/Framework/TestRunner/templates/method.tpl b/app/vendor/phpunit/phpunit/src/Framework/TestRunner/templates/method.tpl new file mode 100644 index 000000000..8d4f38439 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/TestRunner/templates/method.tpl @@ -0,0 +1,137 @@ +initForIsolation( + PHPUnit\Event\Telemetry\HRTime::fromSecondsAndNanoseconds( + {offsetSeconds}, + {offsetNanoseconds} + ), + ); + + require_once '{filename}'; + + $configuration = ConfigurationRegistry::get(); + + if ({collectCodeCoverageInformation}) { + CodeCoverage::instance()->init($configuration, CodeCoverageFilterRegistry::instance(), true); + } + + $deprecationTriggers = [ + 'functions' => [], + 'methods' => [], + ]; + + foreach ($configuration->source()->deprecationTriggers()['functions'] as $function) { + $deprecationTriggers['functions'][] = $function; + } + + foreach ($configuration->source()->deprecationTriggers()['methods'] as $method) { + [$className, $methodName] = explode('::', $method); + + $deprecationTriggers['methods'][] = [ + 'className' => $className, + 'methodName' => $methodName, + ]; + } + + ErrorHandler::instance()->useDeprecationTriggers($deprecationTriggers); + + $test = new {className}('{methodName}'); + + $test->setData('{dataName}', unserialize('{data}')); + $test->setDependencyInput(unserialize('{dependencyInput}')); + $test->setInIsolation(true); + + ob_end_clean(); + + $test->run(); + + $output = ''; + + if (!$test->expectsOutput()) { + $output = $test->output(); + } + + ini_set('xdebug.scream', '0'); + + // Not every STDOUT target stream is rewindable + @rewind(STDOUT); + + if ($stdout = @stream_get_contents(STDOUT)) { + $output = $stdout . $output; + $streamMetaData = stream_get_meta_data(STDOUT); + + if (!empty($streamMetaData['stream_type']) && 'STDIO' === $streamMetaData['stream_type']) { + @ftruncate(STDOUT, 0); + @rewind(STDOUT); + } + } + + file_put_contents( + '{processResultFile}', + serialize( + (object)[ + 'testResult' => $test->result(), + 'codeCoverage' => {collectCodeCoverageInformation} ? CodeCoverage::instance()->codeCoverage() : null, + 'numAssertions' => $test->numberOfAssertionsPerformed(), + 'output' => $output, + 'events' => $dispatcher->flush(), + 'passedTests' => PassedTests::instance() + ] + ) + ); +} + +function __phpunit_error_handler($errno, $errstr, $errfile, $errline) +{ + return true; +} + +set_error_handler('__phpunit_error_handler'); + +{constants} +{included_files} +{globals} + +restore_error_handler(); + +ConfigurationRegistry::loadFrom('{serializedConfiguration}'); +(new PhpHandler)->handle(ConfigurationRegistry::get()->php()); + +if ('{bootstrap}' !== '') { + require_once '{bootstrap}'; +} + +__phpunit_run_isolated_test(); diff --git a/app/vendor/phpunit/phpunit/src/Framework/TestSize/Known.php b/app/vendor/phpunit/phpunit/src/Framework/TestSize/Known.php new file mode 100644 index 000000000..a61a05a00 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/TestSize/Known.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 PHPUnit\Framework\TestSize; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +abstract readonly class Known extends TestSize +{ + public function isKnown(): true + { + return true; + } + + abstract public function isGreaterThan(self $other): bool; +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/TestSize/Large.php b/app/vendor/phpunit/phpunit/src/Framework/TestSize/Large.php new file mode 100644 index 000000000..71a254ed4 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/TestSize/Large.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\TestSize; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class Large extends Known +{ + public function isLarge(): true + { + return true; + } + + public function isGreaterThan(TestSize $other): bool + { + return !$other->isLarge(); + } + + public function asString(): string + { + return 'large'; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/TestSize/Medium.php b/app/vendor/phpunit/phpunit/src/Framework/TestSize/Medium.php new file mode 100644 index 000000000..beb574463 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/TestSize/Medium.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\TestSize; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class Medium extends Known +{ + public function isMedium(): true + { + return true; + } + + public function isGreaterThan(TestSize $other): bool + { + return $other->isSmall(); + } + + public function asString(): string + { + return 'medium'; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/TestSize/Small.php b/app/vendor/phpunit/phpunit/src/Framework/TestSize/Small.php new file mode 100644 index 000000000..76bdec643 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/TestSize/Small.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\TestSize; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class Small extends Known +{ + public function isSmall(): true + { + return true; + } + + public function isGreaterThan(TestSize $other): bool + { + return false; + } + + public function asString(): string + { + return 'small'; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/TestSize/TestSize.php b/app/vendor/phpunit/phpunit/src/Framework/TestSize/TestSize.php new file mode 100644 index 000000000..c341d8b93 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/TestSize/TestSize.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\TestSize; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +abstract readonly class TestSize +{ + public static function unknown(): self + { + return new Unknown; + } + + public static function small(): self + { + return new Small; + } + + public static function medium(): self + { + return new Medium; + } + + public static function large(): self + { + return new Large; + } + + /** + * @phpstan-assert-if-true Known $this + */ + public function isKnown(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true Unknown $this + */ + public function isUnknown(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true Small $this + */ + public function isSmall(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true Medium $this + */ + public function isMedium(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true Large $this + */ + public function isLarge(): bool + { + return false; + } + + abstract public function asString(): string; +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/TestSize/Unknown.php b/app/vendor/phpunit/phpunit/src/Framework/TestSize/Unknown.php new file mode 100644 index 000000000..daf1e7d35 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/TestSize/Unknown.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\TestSize; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class Unknown extends TestSize +{ + public function isUnknown(): true + { + return true; + } + + public function asString(): string + { + return 'unknown'; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/TestStatus/Deprecation.php b/app/vendor/phpunit/phpunit/src/Framework/TestStatus/Deprecation.php new file mode 100644 index 000000000..0bc4957ca --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/TestStatus/Deprecation.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\TestStatus; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Deprecation extends Known +{ + public function isDeprecation(): true + { + return true; + } + + public function asInt(): int + { + return 4; + } + + public function asString(): string + { + return 'deprecation'; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/TestStatus/Error.php b/app/vendor/phpunit/phpunit/src/Framework/TestStatus/Error.php new file mode 100644 index 000000000..35e368e81 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/TestStatus/Error.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\TestStatus; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Error extends Known +{ + public function isError(): true + { + return true; + } + + public function asInt(): int + { + return 8; + } + + public function asString(): string + { + return 'error'; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/TestStatus/Failure.php b/app/vendor/phpunit/phpunit/src/Framework/TestStatus/Failure.php new file mode 100644 index 000000000..ec5996c90 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/TestStatus/Failure.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\TestStatus; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Failure extends Known +{ + public function isFailure(): true + { + return true; + } + + public function asInt(): int + { + return 7; + } + + public function asString(): string + { + return 'failure'; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/TestStatus/Incomplete.php b/app/vendor/phpunit/phpunit/src/Framework/TestStatus/Incomplete.php new file mode 100644 index 000000000..92f86fba8 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/TestStatus/Incomplete.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\TestStatus; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Incomplete extends Known +{ + public function isIncomplete(): true + { + return true; + } + + public function asInt(): int + { + return 2; + } + + public function asString(): string + { + return 'incomplete'; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/TestStatus/Known.php b/app/vendor/phpunit/phpunit/src/Framework/TestStatus/Known.php new file mode 100644 index 000000000..4d9e5e8b4 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/TestStatus/Known.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\TestStatus; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +abstract readonly class Known extends TestStatus +{ + public function isKnown(): true + { + return true; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/TestStatus/Notice.php b/app/vendor/phpunit/phpunit/src/Framework/TestStatus/Notice.php new file mode 100644 index 000000000..be9bd4c03 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/TestStatus/Notice.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\TestStatus; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Notice extends Known +{ + public function isNotice(): true + { + return true; + } + + public function asInt(): int + { + return 3; + } + + public function asString(): string + { + return 'notice'; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/TestStatus/Risky.php b/app/vendor/phpunit/phpunit/src/Framework/TestStatus/Risky.php new file mode 100644 index 000000000..63bde2972 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/TestStatus/Risky.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\TestStatus; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Risky extends Known +{ + public function isRisky(): true + { + return true; + } + + public function asInt(): int + { + return 5; + } + + public function asString(): string + { + return 'risky'; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/TestStatus/Skipped.php b/app/vendor/phpunit/phpunit/src/Framework/TestStatus/Skipped.php new file mode 100644 index 000000000..9539e9e0d --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/TestStatus/Skipped.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\TestStatus; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Skipped extends Known +{ + public function isSkipped(): true + { + return true; + } + + public function asInt(): int + { + return 1; + } + + public function asString(): string + { + return 'skipped'; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/TestStatus/Success.php b/app/vendor/phpunit/phpunit/src/Framework/TestStatus/Success.php new file mode 100644 index 000000000..789150516 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/TestStatus/Success.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\TestStatus; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Success extends Known +{ + public function isSuccess(): true + { + return true; + } + + public function asInt(): int + { + return 0; + } + + public function asString(): string + { + return 'success'; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/TestStatus/TestStatus.php b/app/vendor/phpunit/phpunit/src/Framework/TestStatus/TestStatus.php new file mode 100644 index 000000000..81870826b --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/TestStatus/TestStatus.php @@ -0,0 +1,195 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\TestStatus; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +abstract readonly class TestStatus +{ + private string $message; + + public static function from(int $status): self + { + return match ($status) { + 0 => self::success(), + 1 => self::skipped(), + 2 => self::incomplete(), + 3 => self::notice(), + 4 => self::deprecation(), + 5 => self::risky(), + 6 => self::warning(), + 7 => self::failure(), + 8 => self::error(), + default => self::unknown(), + }; + } + + public static function unknown(): self + { + return new Unknown; + } + + public static function success(): self + { + return new Success; + } + + public static function skipped(string $message = ''): self + { + return new Skipped($message); + } + + public static function incomplete(string $message = ''): self + { + return new Incomplete($message); + } + + public static function notice(string $message = ''): self + { + return new Notice($message); + } + + public static function deprecation(string $message = ''): self + { + return new Deprecation($message); + } + + public static function failure(string $message = ''): self + { + return new Failure($message); + } + + public static function error(string $message = ''): self + { + return new Error($message); + } + + public static function warning(string $message = ''): self + { + return new Warning($message); + } + + public static function risky(string $message = ''): self + { + return new Risky($message); + } + + private function __construct(string $message = '') + { + $this->message = $message; + } + + /** + * @phpstan-assert-if-true Known $this + */ + public function isKnown(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true Unknown $this + */ + public function isUnknown(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true Success $this + */ + public function isSuccess(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true Skipped $this + */ + public function isSkipped(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true Incomplete $this + */ + public function isIncomplete(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true Notice $this + */ + public function isNotice(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true Deprecation $this + */ + public function isDeprecation(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true Failure $this + */ + public function isFailure(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true Error $this + */ + public function isError(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true Warning $this + */ + public function isWarning(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true Risky $this + */ + public function isRisky(): bool + { + return false; + } + + public function message(): string + { + return $this->message; + } + + public function isMoreImportantThan(self $other): bool + { + return $this->asInt() > $other->asInt(); + } + + abstract public function asInt(): int; + + abstract public function asString(): string; +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/TestStatus/Unknown.php b/app/vendor/phpunit/phpunit/src/Framework/TestStatus/Unknown.php new file mode 100644 index 000000000..a5c1309b5 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/TestStatus/Unknown.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\TestStatus; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Unknown extends TestStatus +{ + public function isUnknown(): true + { + return true; + } + + public function asInt(): int + { + return -1; + } + + public function asString(): string + { + return 'unknown'; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/TestStatus/Warning.php b/app/vendor/phpunit/phpunit/src/Framework/TestStatus/Warning.php new file mode 100644 index 000000000..c091262b8 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Framework/TestStatus/Warning.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Framework\TestStatus; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Warning extends Known +{ + public function isWarning(): true + { + return true; + } + + public function asInt(): int + { + return 6; + } + + public function asString(): string + { + return 'warning'; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Framework/TestSuite.php b/app/vendor/phpunit/phpunit/src/Framework/TestSuite.php index 3fa0ba525..49c3370d4 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/TestSuite.php +++ b/app/vendor/phpunit/phpunit/src/Framework/TestSuite.php @@ -10,291 +10,175 @@ namespace PHPUnit\Framework; use const PHP_EOL; -use function array_keys; -use function array_map; use function array_merge; -use function array_slice; -use function array_unique; -use function basename; +use function array_pop; +use function array_reverse; +use function assert; use function call_user_func; use function class_exists; use function count; -use function dirname; -use function get_declared_classes; use function implode; -use function is_bool; use function is_callable; use function is_file; -use function is_object; -use function is_string; -use function method_exists; -use function preg_match; -use function preg_quote; +use function is_subclass_of; use function sprintf; -use function strpos; -use function substr; +use function str_ends_with; +use function str_starts_with; +use function trim; use Iterator; use IteratorAggregate; -use PHPUnit\Runner\BaseTestRunner; +use PHPUnit\Event; +use PHPUnit\Event\Code\TestMethod; +use PHPUnit\Event\NoPreviousThrowableException; +use PHPUnit\Metadata\Api\Dependencies; +use PHPUnit\Metadata\Api\Groups; +use PHPUnit\Metadata\Api\HookMethods; +use PHPUnit\Metadata\Api\Requirements; +use PHPUnit\Metadata\MetadataCollection; +use PHPUnit\Runner\Exception as RunnerException; use PHPUnit\Runner\Filter\Factory; -use PHPUnit\Runner\PhptTestCase; -use PHPUnit\Util\FileLoader; +use PHPUnit\Runner\Phpt\TestCase as PhptTestCase; +use PHPUnit\Runner\TestSuiteLoader; +use PHPUnit\TestRunner\TestResult\Facade as TestResultFacade; +use PHPUnit\Util\Filter; use PHPUnit\Util\Reflection; use PHPUnit\Util\Test as TestUtil; use ReflectionClass; -use ReflectionException; use ReflectionMethod; +use SebastianBergmann\CodeCoverage\InvalidArgumentException; use SebastianBergmann\CodeCoverage\UnintentionallyCoveredCodeException; use Throwable; /** * @template-implements IteratorAggregate * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ -class TestSuite implements IteratorAggregate, Reorderable, SelfDescribing, Test +class TestSuite implements IteratorAggregate, Reorderable, Test { /** - * Enable or disable the backup and restoration of the $GLOBALS array. - * - * @var bool - */ - protected $backupGlobals; - - /** - * Enable or disable the backup and restoration of static attributes. - * - * @var bool + * @var non-empty-string */ - protected $backupStaticAttributes; + private string $name; /** - * @var bool + * @var array> */ - protected $runTestInSeparateProcess = false; + private array $groups = []; /** - * The name of the test suite. - * - * @var string + * @var ?list */ - protected $name = ''; + private ?array $requiredTests = null; /** - * The test groups of the test suite. - * - * @psalm-var array> + * @var list */ - protected $groups = []; + private array $tests = []; /** - * The tests in the test suite. - * - * @var Test[] + * @var ?list */ - protected $tests = []; + private ?array $providedTests = null; + private ?Factory $iteratorFilter = null; + private bool $wasRun = false; /** - * The number of tests in the test suite. - * - * @var int + * @param non-empty-string $name */ - protected $numTests = -1; - - /** - * @var bool - */ - protected $testCase = false; - - /** - * @var string[] - */ - protected $foundClasses = []; - - /** - * @var null|list - */ - protected $providedTests; - - /** - * @var null|list - */ - protected $requiredTests; - - /** - * @var bool - */ - private $beStrictAboutChangesToGlobalState; - - /** - * @var Factory - */ - private $iteratorFilter; - - /** - * @var int - */ - private $declaredClassesPointer; - - /** - * @psalm-var array - */ - private $warnings = []; + public static function empty(string $name): static + { + return new static($name); + } /** - * Constructs a new TestSuite. - * - * - PHPUnit\Framework\TestSuite() constructs an empty TestSuite. - * - * - PHPUnit\Framework\TestSuite(ReflectionClass) constructs a - * TestSuite from the given class. - * - * - PHPUnit\Framework\TestSuite(ReflectionClass, String) - * constructs a TestSuite from the given class with the given - * name. - * - * - PHPUnit\Framework\TestSuite(String) either constructs a - * TestSuite from the given class (if the passed string is the - * name of an existing class) or constructs an empty TestSuite - * with the given name. - * - * @param ReflectionClass|string $theClass - * - * @throws Exception + * @param ReflectionClass $class + * @param list $groups */ - public function __construct($theClass = '', string $name = '') + public static function fromClassReflector(ReflectionClass $class, array $groups = []): static { - if (!is_string($theClass) && !$theClass instanceof ReflectionClass) { - throw InvalidArgumentException::create( - 1, - 'ReflectionClass object or string', - ); - } + $testSuite = new static($class->getName()); - $this->declaredClassesPointer = count(get_declared_classes()); - - if (!$theClass instanceof ReflectionClass) { - if (class_exists($theClass, true)) { - if ($name === '') { - $name = $theClass; - } - - try { - $theClass = new ReflectionClass($theClass); - } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - $e->getCode(), - $e, - ); - } - // @codeCoverageIgnoreEnd - } else { - $this->setName($theClass); - - return; + foreach (Reflection::publicMethodsDeclaredDirectlyInTestClass($class) as $method) { + if (!TestUtil::isTestMethod($method)) { + continue; } - } - - if (!$theClass->isSubclassOf(TestCase::class)) { - $this->setName((string) $theClass); - return; - } - - if ($name !== '') { - $this->setName($name); - } else { - $this->setName($theClass->getName()); - } - - $constructor = $theClass->getConstructor(); - - if ($constructor !== null && - !$constructor->isPublic()) { - $this->addTest( - new WarningTestCase( + if (TestUtil::isHookMethod($method)) { + Event\Facade::emitter()->testRunnerTriggeredPhpunitWarning( sprintf( - 'Class "%s" has no public constructor.', - $theClass->getName(), + 'Method %s::%s() cannot be used both as a hook method and as a test method', + $class->getName(), + $method->getName(), ), - ), - ); - - return; - } + ); - foreach ((new Reflection)->publicMethodsInTestClass($theClass) as $method) { - if (!TestUtil::isTestMethod($method)) { continue; } - $this->addTestMethod($theClass, $method); + $testSuite->addTestMethod($class, $method, $groups); } - if (empty($this->tests)) { - $this->addTest( - new WarningTestCase( - sprintf( - 'No tests found in class "%s".', - $theClass->getName(), - ), + if ($testSuite->isEmpty()) { + Event\Facade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + 'No tests found in class "%s".', + $class->getName(), ), ); } - $this->testCase = true; + return $testSuite; } /** - * Returns a string representation of the test suite. + * @param non-empty-string $name */ - public function toString(): string + final private function __construct(string $name) { - return $this->getName(); + $this->name = $name; } /** * Adds a test to the suite. * - * @param array $groups + * @param list $groups */ - public function addTest(Test $test, $groups = []): void + public function addTest(Test $test, array $groups = []): void { - try { - $class = new ReflectionClass($test); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - $e->getCode(), - $e, - ); - } - // @codeCoverageIgnoreEnd - - if (!$class->isAbstract()) { + if ($test instanceof self) { $this->tests[] = $test; + $this->clearCaches(); - if ($test instanceof self && empty($groups)) { - $groups = $test->getGroups(); - } + return; + } - if ($this->containsOnlyVirtualGroups($groups)) { - $groups[] = 'default'; - } + assert($test instanceof TestCase || $test instanceof PhptTestCase); - foreach ($groups as $group) { - if (!isset($this->groups[$group])) { - $this->groups[$group] = [$test]; - } else { - $this->groups[$group][] = $test; - } - } + $this->tests[] = $test; - if ($test instanceof TestCase) { - $test->setGroups($groups); + $this->clearCaches(); + + if ($this->containsOnlyVirtualGroups($groups)) { + $groups[] = 'default'; + } + + if ($test instanceof TestCase) { + $id = $test->valueObjectForEvents()->id(); + + $test->setGroups($groups); + } else { + $id = $test->valueObjectForEvents()->id(); + } + + foreach ($groups as $group) { + if (!isset($this->groups[$group])) { + $this->groups[$group] = [$id]; + } else { + $this->groups[$group][] = $id; } } } @@ -302,73 +186,33 @@ public function addTest(Test $test, $groups = []): void /** * Adds the tests from the given class to the suite. * - * @psalm-param object|class-string $testClass + * @param ReflectionClass $testClass + * @param list $groups * * @throws Exception */ - public function addTestSuite($testClass): void + public function addTestSuite(ReflectionClass $testClass, array $groups = []): void { - if (!(is_object($testClass) || (is_string($testClass) && class_exists($testClass)))) { - throw InvalidArgumentException::create( - 1, - 'class name or object', + if ($testClass->isAbstract()) { + throw new Exception( + sprintf( + 'Class %s is abstract', + $testClass->getName(), + ), ); } - if (!is_object($testClass)) { - try { - $testClass = new ReflectionClass($testClass); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - $e->getCode(), - $e, - ); - } - // @codeCoverageIgnoreEnd - } - - if ($testClass instanceof self) { - $this->addTest($testClass); - } elseif ($testClass instanceof ReflectionClass) { - $suiteMethod = false; - - if (!$testClass->isAbstract() && $testClass->hasMethod(BaseTestRunner::SUITE_METHODNAME)) { - try { - $method = $testClass->getMethod( - BaseTestRunner::SUITE_METHODNAME, - ); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - $e->getCode(), - $e, - ); - } - // @codeCoverageIgnoreEnd - - if ($method->isStatic()) { - $this->addTest( - $method->invoke(null, $testClass->getName()), - ); - - $suiteMethod = true; - } - } - - if (!$suiteMethod && !$testClass->isAbstract() && $testClass->isSubclassOf(TestCase::class)) { - $this->addTest(new self($testClass)); - } - } else { - throw new Exception; + if (!$testClass->isSubclassOf(TestCase::class)) { + throw new Exception( + sprintf( + 'Class %s is not a subclass of %s', + $testClass->getName(), + TestCase::class, + ), + ); } - } - public function addWarning(string $warning): void - { - $this->warnings[] = $warning; + $this->addTest(self::fromClassReflector($testClass, $groups), $groups); } /** @@ -379,156 +223,33 @@ public function addWarning(string $warning): void * added, a PHPUnit\Framework\WarningTestCase will be created instead, * leaving the current test run untouched. * + * @param list $groups + * * @throws Exception */ - public function addTestFile(string $filename): void + public function addTestFile(string $filename, array $groups = []): void { - if (is_file($filename) && substr($filename, -5) === '.phpt') { - $this->addTest(new PhptTestCase($filename)); - - $this->declaredClassesPointer = count(get_declared_classes()); - - return; - } - - $numTests = count($this->tests); - - // The given file may contain further stub classes in addition to the - // test class itself. Figure out the actual test class. - $filename = FileLoader::checkAndLoad($filename); - $newClasses = array_slice(get_declared_classes(), $this->declaredClassesPointer); - - // The diff is empty in case a parent class (with test methods) is added - // AFTER a child class that inherited from it. To account for that case, - // accumulate all discovered classes, so the parent class may be found in - // a later invocation. - if (!empty($newClasses)) { - // On the assumption that test classes are defined first in files, - // process discovered classes in approximate LIFO order, so as to - // avoid unnecessary reflection. - $this->foundClasses = array_merge($newClasses, $this->foundClasses); - $this->declaredClassesPointer = count(get_declared_classes()); - } - - // The test class's name must match the filename, either in full, or as - // a PEAR/PSR-0 prefixed short name ('NameSpace_ShortName'), or as a - // PSR-1 local short name ('NameSpace\ShortName'). The comparison must be - // anchored to prevent false-positive matches (e.g., 'OtherShortName'). - $shortName = basename($filename, '.php'); - $shortNameRegEx = '/(?:^|_|\\\\)' . preg_quote($shortName, '/') . '$/'; - - foreach ($this->foundClasses as $i => $className) { - if (preg_match($shortNameRegEx, $className)) { - try { - $class = new ReflectionClass($className); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - $e->getCode(), - $e, - ); - } - // @codeCoverageIgnoreEnd - - if ($class->getFileName() == $filename) { - $newClasses = [$className]; - unset($this->foundClasses[$i]); - - break; - } - } - } - - foreach ($newClasses as $className) { - try { - $class = new ReflectionClass($className); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - $e->getCode(), - $e, - ); - } - // @codeCoverageIgnoreEnd - - if (dirname($class->getFileName()) === __DIR__) { - continue; - } - - if ($class->isAbstract() && $class->isSubclassOf(TestCase::class)) { - $this->addWarning( - sprintf( - 'Abstract test case classes with "Test" suffix are deprecated (%s)', - $class->getName(), - ), + try { + if (str_ends_with($filename, '.phpt') && is_file($filename)) { + $this->addTest(new PhptTestCase($filename)); + } else { + $this->addTestSuite( + (new TestSuiteLoader)->load($filename), + $groups, ); } - - if (!$class->isAbstract()) { - if ($class->hasMethod(BaseTestRunner::SUITE_METHODNAME)) { - try { - $method = $class->getMethod( - BaseTestRunner::SUITE_METHODNAME, - ); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - $e->getCode(), - $e, - ); - } - // @codeCoverageIgnoreEnd - - if ($method->isStatic()) { - $this->addTest($method->invoke(null, $className)); - } - } elseif ($class->implementsInterface(Test::class)) { - // Do we have modern namespacing ('Foo\Bar\WhizBangTest') or old-school namespacing ('Foo_Bar_WhizBangTest')? - $isPsr0 = (!$class->inNamespace()) && (strpos($class->getName(), '_') !== false); - $expectedClassName = $isPsr0 ? $className : $shortName; - - if (($pos = strpos($expectedClassName, '.')) !== false) { - $expectedClassName = substr( - $expectedClassName, - 0, - $pos, - ); - } - - if ($class->getShortName() !== $expectedClassName) { - $this->addWarning( - sprintf( - "Test case class not matching filename is deprecated\n in %s\n Class name was '%s', expected '%s'", - $filename, - $class->getShortName(), - $expectedClassName, - ), - ); - } - - $this->addTestSuite($class); - } - } - } - - if (count($this->tests) > ++$numTests) { - $this->addWarning( - sprintf( - "Multiple test case classes per file is deprecated\n in %s", - $filename, - ), + } catch (RunnerException $e) { + Event\Facade::emitter()->testRunnerTriggeredPhpunitWarning( + $e->getMessage(), ); } - - $this->numTests = -1; } /** * Wrapper for addTestFile() that adds multiple test files. * + * @param iterable $fileNames + * * @throws Exception */ public function addTestFiles(iterable $fileNames): void @@ -540,190 +261,128 @@ public function addTestFiles(iterable $fileNames): void /** * Counts the number of test cases that will be run by this test. - * - * @todo refactor usage of numTests in DefaultResultPrinter */ public function count(): int { - $this->numTests = 0; + $numTests = 0; foreach ($this as $test) { - $this->numTests += count($test); + $numTests += count($test); } - return $this->numTests; + return $numTests; } - /** - * Returns the name of the suite. - */ - public function getName(): string + public function isEmpty(): bool { - return $this->name; + foreach ($this as $test) { + if (count($test) !== 0) { + return false; + } + } + + return true; } /** - * Returns the test groups of the suite. - * - * @psalm-return list + * @return non-empty-string */ - public function getGroups(): array + public function name(): string { - return array_map( - static function ($key): string - { - return (string) $key; - }, - array_keys($this->groups), - ); + return $this->name; } - public function getGroupDetails(): array + /** + * @return array> + */ + public function groups(): array { return $this->groups; } /** - * Set tests groups of the test case. + * @return list */ - public function setGroupDetails(array $groups): void + public function collect(): array { - $this->groups = $groups; + $tests = []; + + foreach ($this as $test) { + if ($test instanceof self) { + $tests = array_merge($tests, $test->collect()); + + continue; + } + + assert($test instanceof TestCase || $test instanceof PhptTestCase); + + $tests[] = $test; + } + + return $tests; } /** - * Runs the tests and collects their result in a TestResult. - * - * @throws \SebastianBergmann\CodeCoverage\InvalidArgumentException - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws CodeCoverageException + * @throws Event\RuntimeException + * @throws Exception + * @throws InvalidArgumentException + * @throws NoPreviousThrowableException * @throws UnintentionallyCoveredCodeException - * @throws Warning */ - public function run(?TestResult $result = null): TestResult + public function run(): void { - if ($result === null) { - $result = $this->createResult(); - } - - if (count($this) === 0) { - return $result; + if ($this->wasRun) { + // @codeCoverageIgnoreStart + throw new Exception('The tests aggregated by this TestSuite were already run'); + // @codeCoverageIgnoreEnd } - /** @psalm-var class-string $className */ - $className = $this->name; - $hookMethods = TestUtil::getHookMethods($className); + $this->wasRun = true; - $result->startTestSuite($this); - - $test = null; - - if ($this->testCase && class_exists($this->name, false)) { - try { - foreach ($hookMethods['beforeClass'] as $beforeClassMethod) { - if (method_exists($this->name, $beforeClassMethod)) { - if ($missingRequirements = TestUtil::getMissingRequirements($this->name, $beforeClassMethod)) { - $this->markTestSuiteSkipped(implode(PHP_EOL, $missingRequirements)); - } - - call_user_func([$this->name, $beforeClassMethod]); - } - } - } catch (SkippedTestError|SkippedTestSuiteError $error) { - foreach ($this->tests() as $test) { - $result->startTest($test); - $result->addFailure($test, $error, 0); - $result->endTest($test, 0); - } - - $result->endTestSuite($this); + if ($this->isEmpty()) { + return; + } - return $result; - } catch (Throwable $t) { - $errorAdded = false; + $emitter = Event\Facade::emitter(); + $testSuiteValueObjectForEvents = Event\TestSuite\TestSuiteBuilder::from($this); - foreach ($this->tests() as $test) { - if ($result->shouldStop()) { - break; - } + $emitter->testSuiteStarted($testSuiteValueObjectForEvents); - $result->startTest($test); + if (!$this->invokeMethodsBeforeFirstTest($emitter, $testSuiteValueObjectForEvents)) { + return; + } - if (!$errorAdded) { - $result->addError($test, $t, 0); + /** @var list $tests */ + $tests = []; - $errorAdded = true; - } else { - $result->addFailure( - $test, - new SkippedTestError('Test skipped because of an error in hook method'), - 0, - ); - } + foreach ($this as $test) { + $tests[] = $test; + } - $result->endTest($test, 0); - } + $tests = array_reverse($tests); - $result->endTestSuite($this); + $this->tests = []; + $this->groups = []; - return $result; - } - } + while (($test = array_pop($tests)) !== null) { + if (TestResultFacade::shouldStop()) { + $emitter->testRunnerExecutionAborted(); - foreach ($this as $test) { - if ($result->shouldStop()) { break; } - if ($test instanceof TestCase || $test instanceof self) { - $test->setBeStrictAboutChangesToGlobalState($this->beStrictAboutChangesToGlobalState); - $test->setBackupGlobals($this->backupGlobals); - $test->setBackupStaticAttributes($this->backupStaticAttributes); - $test->setRunTestInSeparateProcess($this->runTestInSeparateProcess); - } - - $test->run($result); + $test->run(); } - if ($this->testCase && class_exists($this->name, false)) { - foreach ($hookMethods['afterClass'] as $afterClassMethod) { - if (method_exists($this->name, $afterClassMethod)) { - try { - call_user_func([$this->name, $afterClassMethod]); - } catch (Throwable $t) { - $message = "Exception in {$this->name}::{$afterClassMethod}" . PHP_EOL . $t->getMessage(); - $error = new SyntheticError($message, 0, $t->getFile(), $t->getLine(), $t->getTrace()); - - $placeholderTest = clone $test; - $placeholderTest->setName($afterClassMethod); - - $result->startTest($placeholderTest); - $result->addFailure($placeholderTest, $error, 0); - $result->endTest($placeholderTest, 0); - } - } - } - } - - $result->endTestSuite($this); - - return $result; - } - - public function setRunTestInSeparateProcess(bool $runTestInSeparateProcess): void - { - $this->runTestInSeparateProcess = $runTestInSeparateProcess; - } + $this->invokeMethodsAfterLastTest($emitter); - public function setName(string $name): void - { - $this->name = $name; + $emitter->testSuiteFinished($testSuiteValueObjectForEvents); } /** * Returns the tests as an enumeration. * - * @return Test[] + * @return list */ public function tests(): array { @@ -733,7 +392,7 @@ public function tests(): array /** * Set tests of the test suite. * - * @param Test[] $tests + * @param list $tests */ public function setTests(array $tests): void { @@ -743,47 +402,13 @@ public function setTests(array $tests): void /** * Mark the test suite as skipped. * - * @param string $message - * * @throws SkippedTestSuiteError - * - * @psalm-return never-return */ - public function markTestSuiteSkipped($message = ''): void + public function markTestSuiteSkipped(string $message = ''): never { throw new SkippedTestSuiteError($message); } - /** - * @param bool $beStrictAboutChangesToGlobalState - */ - public function setBeStrictAboutChangesToGlobalState($beStrictAboutChangesToGlobalState): void - { - if (null === $this->beStrictAboutChangesToGlobalState && is_bool($beStrictAboutChangesToGlobalState)) { - $this->beStrictAboutChangesToGlobalState = $beStrictAboutChangesToGlobalState; - } - } - - /** - * @param bool $backupGlobals - */ - public function setBackupGlobals($backupGlobals): void - { - if (null === $this->backupGlobals && is_bool($backupGlobals)) { - $this->backupGlobals = $backupGlobals; - } - } - - /** - * @param bool $backupStaticAttributes - */ - public function setBackupStaticAttributes($backupStaticAttributes): void - { - if (null === $this->backupStaticAttributes && is_bool($backupStaticAttributes)) { - $this->backupStaticAttributes = $backupStaticAttributes; - } - } - /** * Returns an iterator for this test suite. */ @@ -809,14 +434,6 @@ public function injectFilter(Factory $filter): void } } - /** - * @psalm-return array - */ - public function warnings(): array - { - return array_unique($this->warnings); - } - /** * @return list */ @@ -830,11 +447,12 @@ public function provides(): array } foreach ($this->tests as $test) { - if (!($test instanceof Reorderable)) { + if (!$test instanceof Reorderable) { // @codeCoverageIgnoreStart continue; // @codeCoverageIgnoreEnd } + $this->providedTests = ExecutionOrderDependency::mergeUnique($this->providedTests, $test->provides()); } } @@ -851,11 +469,12 @@ public function requires(): array $this->requiredTests = []; foreach ($this->tests as $test) { - if (!($test instanceof Reorderable)) { + if (!$test instanceof Reorderable) { // @codeCoverageIgnoreStart continue; // @codeCoverageIgnoreEnd } + $this->requiredTests = ExecutionOrderDependency::mergeUnique( ExecutionOrderDependency::filterInvalid($this->requiredTests), $test->requires(), @@ -870,53 +489,270 @@ public function requires(): array public function sortId(): string { - return $this->getName() . '::class'; + return $this->name() . '::class'; } /** - * Creates a default TestResult object. + * @phpstan-assert-if-true class-string $this->name */ - protected function createResult(): TestResult + public function isForTestClass(): bool { - return new TestResult; + return class_exists($this->name, false) && is_subclass_of($this->name, TestCase::class); } /** + * @param ReflectionClass $class + * @param list $groups + * * @throws Exception */ - protected function addTestMethod(ReflectionClass $class, ReflectionMethod $method): void + protected function addTestMethod(ReflectionClass $class, ReflectionMethod $method, array $groups): void { + $className = $class->getName(); $methodName = $method->getName(); - $test = (new TestBuilder)->build($class, $methodName); + try { + $test = (new TestBuilder)->build($class, $methodName, $groups); + } catch (InvalidDataProviderException $e) { + if ($e->getProviderLabel() === null) { + $message = sprintf( + "The data provider specified for %s::%s is invalid\n%s", + $className, + $methodName, + $this->exceptionToString($e), + ); + } else { + $message = sprintf( + "The data provider %s specified for %s::%s is invalid\n%s", + $e->getProviderLabel(), + $className, + $methodName, + $this->exceptionToString($e), + ); + } + + Event\Facade::emitter()->testTriggeredPhpunitError( + new TestMethod( + $className, + $methodName, + $class->getFileName(), + $method->getStartLine(), + Event\Code\TestDoxBuilder::fromClassNameAndMethodName( + $className, + $methodName, + ), + MetadataCollection::fromArray([]), + Event\TestData\TestDataCollection::fromArray([]), + ), + $message, + ); + + return; + } if ($test instanceof TestCase || $test instanceof DataProviderTestSuite) { $test->setDependencies( - TestUtil::getDependencies($class->getName(), $methodName), + Dependencies::dependencies($class->getName(), $methodName), ); } $this->addTest( $test, - TestUtil::getGroups($class->getName(), $methodName), + array_merge( + $groups, + (new Groups)->groups($class->getName(), $methodName), + ), ); } private function clearCaches(): void { - $this->numTests = -1; $this->providedTests = null; $this->requiredTests = null; } + /** + * @param list $groups + */ private function containsOnlyVirtualGroups(array $groups): bool { foreach ($groups as $group) { - if (strpos($group, '__phpunit_') !== 0) { + if (!str_starts_with($group, '__phpunit_')) { return false; } } return true; } + + private function methodDoesNotExistOrIsDeclaredInTestCase(string $methodName): bool + { + $reflector = new ReflectionClass($this->name); + + return !$reflector->hasMethod($methodName) || + $reflector->getMethod($methodName)->getDeclaringClass()->getName() === TestCase::class; + } + + /** + * @throws Exception + */ + private function exceptionToString(InvalidDataProviderException $e): string + { + $message = $e->getMessage(); + + if (trim($message) === '') { + $message = ''; + } + + return sprintf( + "%s\n%s", + $message, + Filter::stackTraceFromThrowableAsString($e), + ); + } + + /** + * @throws Exception + * @throws NoPreviousThrowableException + */ + private function invokeMethodsBeforeFirstTest(Event\Emitter $emitter, Event\TestSuite\TestSuite $testSuiteValueObjectForEvents): bool + { + if (!$this->isForTestClass()) { + return true; + } + + $methods = (new HookMethods)->hookMethods($this->name)['beforeClass']->methodNamesSortedByPriority(); + $calledMethods = []; + $emitCalledEvent = true; + $result = true; + + foreach ($methods as $method) { + if ($this->methodDoesNotExistOrIsDeclaredInTestCase($method)) { + continue; + } + + $calledMethod = new Event\Code\ClassMethod( + $this->name, + $method, + ); + + try { + $missingRequirements = (new Requirements)->requirementsNotSatisfiedFor($this->name, $method); + + if ($missingRequirements !== []) { + $emitCalledEvent = false; + + $this->markTestSuiteSkipped(implode(PHP_EOL, $missingRequirements)); + } + + call_user_func([$this->name, $method]); + } catch (Throwable $t) { + } + + if ($emitCalledEvent) { + $emitter->beforeFirstTestMethodCalled( + $this->name, + $calledMethod, + ); + + $calledMethods[] = $calledMethod; + } + + if (isset($t) && $t instanceof SkippedTest) { + $emitter->testSuiteSkipped( + $testSuiteValueObjectForEvents, + $t->getMessage(), + ); + + return false; + } + + if (isset($t)) { + if ($t instanceof AssertionFailedError) { + $emitter->beforeFirstTestMethodFailed( + $this->name, + $calledMethod, + Event\Code\ThrowableBuilder::from($t), + ); + } else { + $emitter->beforeFirstTestMethodErrored( + $this->name, + $calledMethod, + Event\Code\ThrowableBuilder::from($t), + ); + } + + $result = false; + } + } + + if ($calledMethods !== []) { + $emitter->beforeFirstTestMethodFinished( + $this->name, + ...$calledMethods, + ); + } + + if (!$result) { + $emitter->testSuiteFinished($testSuiteValueObjectForEvents); + } + + return $result; + } + + private function invokeMethodsAfterLastTest(Event\Emitter $emitter): void + { + if (!$this->isForTestClass()) { + return; + } + + $methods = (new HookMethods)->hookMethods($this->name)['afterClass']->methodNamesSortedByPriority(); + $calledMethods = []; + + foreach ($methods as $method) { + if ($this->methodDoesNotExistOrIsDeclaredInTestCase($method)) { + continue; + } + + $calledMethod = new Event\Code\ClassMethod( + $this->name, + $method, + ); + + try { + call_user_func([$this->name, $method]); + } catch (Throwable $t) { + } + + $emitter->afterLastTestMethodCalled( + $this->name, + $calledMethod, + ); + + $calledMethods[] = $calledMethod; + + if (isset($t)) { + if ($t instanceof AssertionFailedError) { + $emitter->afterLastTestMethodFailed( + $this->name, + $calledMethod, + Event\Code\ThrowableBuilder::from($t), + ); + } else { + $emitter->afterLastTestMethodErrored( + $this->name, + $calledMethod, + Event\Code\ThrowableBuilder::from($t), + ); + } + } + } + + if ($calledMethods !== []) { + $emitter->afterLastTestMethodFinished( + $this->name, + ...$calledMethods, + ); + } + } } diff --git a/app/vendor/phpunit/phpunit/src/Framework/TestSuiteIterator.php b/app/vendor/phpunit/phpunit/src/Framework/TestSuiteIterator.php index 27c9d8b4c..3cbf43116 100644 --- a/app/vendor/phpunit/phpunit/src/Framework/TestSuiteIterator.php +++ b/app/vendor/phpunit/phpunit/src/Framework/TestSuiteIterator.php @@ -16,19 +16,18 @@ /** * @template-implements RecursiveIterator * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class TestSuiteIterator implements RecursiveIterator { - /** - * @var int - */ - private $position = 0; + private int $position = 0; /** - * @var Test[] + * @var list */ - private $tests; + private readonly array $tests; public function __construct(TestSuite $testSuite) { diff --git a/app/vendor/phpunit/phpunit/src/Framework/WarningTestCase.php b/app/vendor/phpunit/phpunit/src/Framework/WarningTestCase.php deleted file mode 100644 index d27c6b577..000000000 --- a/app/vendor/phpunit/phpunit/src/Framework/WarningTestCase.php +++ /dev/null @@ -1,66 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Framework; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class WarningTestCase extends TestCase -{ - /** - * @var ?bool - */ - protected $backupGlobals = false; - - /** - * @var ?bool - */ - protected $backupStaticAttributes = false; - - /** - * @var ?bool - */ - protected $runTestInSeparateProcess = false; - - /** - * @var string - */ - private $message; - - public function __construct(string $message = '') - { - $this->message = $message; - - parent::__construct('Warning'); - } - - public function getMessage(): string - { - return $this->message; - } - - /** - * Returns a string representation of the test case. - */ - public function toString(): string - { - return 'Warning'; - } - - /** - * @throws Exception - * - * @psalm-return never-return - */ - protected function runTest(): void - { - throw new Warning($this->message); - } -} diff --git a/app/vendor/phpunit/phpunit/src/Logging/EventLogger.php b/app/vendor/phpunit/phpunit/src/Logging/EventLogger.php new file mode 100644 index 000000000..738f30377 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/EventLogger.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging; + +use const FILE_APPEND; +use const LOCK_EX; +use const PHP_EOL; +use const PHP_OS_FAMILY; +use function file_put_contents; +use function implode; +use function preg_split; +use function str_repeat; +use function strlen; +use PHPUnit\Event\Event; +use PHPUnit\Event\Tracer\Tracer; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class EventLogger implements Tracer +{ + private string $path; + private bool $includeTelemetryInfo; + + public function __construct(string $path, bool $includeTelemetryInfo) + { + $this->path = $path; + $this->includeTelemetryInfo = $includeTelemetryInfo; + } + + public function trace(Event $event): void + { + $telemetryInfo = $this->telemetryInfo($event); + $indentation = PHP_EOL . str_repeat(' ', strlen($telemetryInfo)); + $flags = FILE_APPEND; + + if (!(PHP_OS_FAMILY === 'Windows' || PHP_OS_FAMILY === 'Darwin') || + $this->path !== 'php://stdout') { + $flags |= LOCK_EX; + } + + $lines = preg_split('/\r\n|\r|\n/', $event->asString()); + + if ($lines === false) { + $lines = []; + } + + file_put_contents( + $this->path, + $telemetryInfo . implode($indentation, $lines) . PHP_EOL, + $flags, + ); + } + + private function telemetryInfo(Event $event): string + { + if (!$this->includeTelemetryInfo) { + return ''; + } + + return $event->telemetryInfo()->asString() . ' '; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/JUnit/JunitXmlLogger.php b/app/vendor/phpunit/phpunit/src/Logging/JUnit/JunitXmlLogger.php new file mode 100644 index 000000000..14a71b769 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/JUnit/JunitXmlLogger.php @@ -0,0 +1,462 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\JUnit; + +use const PHP_EOL; +use function assert; +use function basename; +use function is_int; +use function sprintf; +use function str_replace; +use function trim; +use DOMDocument; +use DOMElement; +use PHPUnit\Event\Code\Test; +use PHPUnit\Event\Code\TestMethod; +use PHPUnit\Event\Facade; +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Telemetry\HRTime; +use PHPUnit\Event\Telemetry\Info; +use PHPUnit\Event\Test\Errored; +use PHPUnit\Event\Test\Failed; +use PHPUnit\Event\Test\Finished; +use PHPUnit\Event\Test\MarkedIncomplete; +use PHPUnit\Event\Test\PreparationStarted; +use PHPUnit\Event\Test\Prepared; +use PHPUnit\Event\Test\PrintedUnexpectedOutput; +use PHPUnit\Event\Test\Skipped; +use PHPUnit\Event\TestSuite\Started; +use PHPUnit\TextUI\Output\Printer; +use PHPUnit\Util\Xml; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class JunitXmlLogger +{ + private readonly Printer $printer; + private DOMDocument $document; + private DOMElement $root; + + /** + * @var array + */ + private array $testSuites = []; + + /** + * @var array + */ + private array $testSuiteTests = [0]; + + /** + * @var array + */ + private array $testSuiteAssertions = [0]; + + /** + * @var array + */ + private array $testSuiteErrors = [0]; + + /** + * @var array + */ + private array $testSuiteFailures = [0]; + + /** + * @var array + */ + private array $testSuiteSkipped = [0]; + + /** + * @var array + */ + private array $testSuiteTimes = [0.0]; + private int $testSuiteLevel = 0; + private ?DOMElement $currentTestCase = null; + private ?HRTime $time = null; + private bool $prepared = false; + private bool $preparationFailed = false; + private ?string $unexpectedOutput = null; + + public function __construct(Printer $printer, Facade $facade) + { + $this->printer = $printer; + + $this->registerSubscribers($facade); + $this->createDocument(); + } + + public function flush(): void + { + $xml = $this->document->saveXML(); + + if ($xml === false) { + $xml = ''; + } + + $this->printer->print($xml); + $this->printer->flush(); + } + + public function testSuiteStarted(Started $event): void + { + $testSuite = $this->document->createElement('testsuite'); + $testSuite->setAttribute('name', $event->testSuite()->name()); + + if ($event->testSuite()->isForTestClass()) { + $testSuite->setAttribute('file', $event->testSuite()->file()); + } + + if ($this->testSuiteLevel > 0) { + $this->testSuites[$this->testSuiteLevel]->appendChild($testSuite); + } else { + $this->root->appendChild($testSuite); + } + + $this->testSuiteLevel++; + $this->testSuites[$this->testSuiteLevel] = $testSuite; + $this->testSuiteTests[$this->testSuiteLevel] = 0; + $this->testSuiteAssertions[$this->testSuiteLevel] = 0; + $this->testSuiteErrors[$this->testSuiteLevel] = 0; + $this->testSuiteFailures[$this->testSuiteLevel] = 0; + $this->testSuiteSkipped[$this->testSuiteLevel] = 0; + $this->testSuiteTimes[$this->testSuiteLevel] = 0.0; + } + + public function testSuiteFinished(): void + { + $this->testSuites[$this->testSuiteLevel]->setAttribute( + 'tests', + (string) $this->testSuiteTests[$this->testSuiteLevel], + ); + + $this->testSuites[$this->testSuiteLevel]->setAttribute( + 'assertions', + (string) $this->testSuiteAssertions[$this->testSuiteLevel], + ); + + $this->testSuites[$this->testSuiteLevel]->setAttribute( + 'errors', + (string) $this->testSuiteErrors[$this->testSuiteLevel], + ); + + $this->testSuites[$this->testSuiteLevel]->setAttribute( + 'failures', + (string) $this->testSuiteFailures[$this->testSuiteLevel], + ); + + $this->testSuites[$this->testSuiteLevel]->setAttribute( + 'skipped', + (string) $this->testSuiteSkipped[$this->testSuiteLevel], + ); + + $this->testSuites[$this->testSuiteLevel]->setAttribute( + 'time', + sprintf('%F', $this->testSuiteTimes[$this->testSuiteLevel]), + ); + + if ($this->testSuiteLevel > 1) { + $this->testSuiteTests[$this->testSuiteLevel - 1] += $this->testSuiteTests[$this->testSuiteLevel]; + $this->testSuiteAssertions[$this->testSuiteLevel - 1] += $this->testSuiteAssertions[$this->testSuiteLevel]; + $this->testSuiteErrors[$this->testSuiteLevel - 1] += $this->testSuiteErrors[$this->testSuiteLevel]; + $this->testSuiteFailures[$this->testSuiteLevel - 1] += $this->testSuiteFailures[$this->testSuiteLevel]; + $this->testSuiteSkipped[$this->testSuiteLevel - 1] += $this->testSuiteSkipped[$this->testSuiteLevel]; + $this->testSuiteTimes[$this->testSuiteLevel - 1] += $this->testSuiteTimes[$this->testSuiteLevel]; + } + + $this->testSuiteLevel--; + } + + /** + * @throws InvalidArgumentException + */ + public function testPreparationStarted(PreparationStarted $event): void + { + $this->createTestCase($event); + + $this->preparationFailed = false; + } + + public function testPreparationErrored(): void + { + $this->preparationFailed = true; + } + + public function testPreparationFailed(): void + { + $this->preparationFailed = true; + } + + public function testPrepared(): void + { + $this->prepared = true; + } + + public function testPrintedUnexpectedOutput(PrintedUnexpectedOutput $event): void + { + $this->unexpectedOutput = $event->output(); + } + + /** + * @throws InvalidArgumentException + */ + public function testFinished(Finished $event): void + { + if (!$this->prepared || $this->preparationFailed) { + return; + } + + $this->handleFinish($event->telemetryInfo(), $event->numberOfAssertionsPerformed()); + } + + /** + * @throws InvalidArgumentException + */ + public function testMarkedIncomplete(MarkedIncomplete $event): void + { + $this->handleIncompleteOrSkipped($event); + } + + /** + * @throws InvalidArgumentException + */ + public function testSkipped(Skipped $event): void + { + $this->handleIncompleteOrSkipped($event); + } + + /** + * @throws InvalidArgumentException + */ + public function testErrored(Errored $event): void + { + $this->handleFault($event, 'error'); + + $this->testSuiteErrors[$this->testSuiteLevel]++; + } + + /** + * @throws InvalidArgumentException + */ + public function testFailed(Failed $event): void + { + $this->handleFault($event, 'failure'); + + $this->testSuiteFailures[$this->testSuiteLevel]++; + } + + /** + * @throws InvalidArgumentException + */ + private function handleFinish(Info $telemetryInfo, int $numberOfAssertionsPerformed): void + { + assert($this->currentTestCase !== null); + assert($this->time !== null); + + $time = $telemetryInfo->time()->duration($this->time)->asFloat(); + + $this->testSuiteAssertions[$this->testSuiteLevel] += $numberOfAssertionsPerformed; + + $this->currentTestCase->setAttribute( + 'assertions', + (string) $numberOfAssertionsPerformed, + ); + + $this->currentTestCase->setAttribute( + 'time', + sprintf('%F', $time), + ); + + if ($this->unexpectedOutput !== null) { + $systemOut = $this->document->createElement( + 'system-out', + Xml::prepareString($this->unexpectedOutput), + ); + + $this->currentTestCase->appendChild($systemOut); + } + + $this->testSuites[$this->testSuiteLevel]->appendChild( + $this->currentTestCase, + ); + + $this->testSuiteTests[$this->testSuiteLevel]++; + $this->testSuiteTimes[$this->testSuiteLevel] += $time; + + $this->currentTestCase = null; + $this->time = null; + $this->preparationFailed = false; + $this->prepared = false; + $this->unexpectedOutput = null; + } + + private function registerSubscribers(Facade $facade): void + { + $facade->registerSubscribers( + new TestSuiteStartedSubscriber($this), + new TestSuiteFinishedSubscriber($this), + new TestPreparationStartedSubscriber($this), + new TestPreparationErroredSubscriber($this), + new TestPreparationFailedSubscriber($this), + new TestPreparedSubscriber($this), + new TestPrintedUnexpectedOutputSubscriber($this), + new TestFinishedSubscriber($this), + new TestErroredSubscriber($this), + new TestFailedSubscriber($this), + new TestMarkedIncompleteSubscriber($this), + new TestSkippedSubscriber($this), + new TestRunnerExecutionFinishedSubscriber($this), + ); + } + + private function createDocument(): void + { + $this->document = new DOMDocument('1.0', 'UTF-8'); + $this->document->formatOutput = true; + + $this->root = $this->document->createElement('testsuites'); + $this->document->appendChild($this->root); + } + + /** + * @throws InvalidArgumentException + */ + private function handleFault(Errored|Failed $event, string $type): void + { + if (!$this->prepared) { + $this->createTestCase($event); + } + + assert($this->currentTestCase !== null); + + $buffer = $this->testAsString($event->test()); + + $throwable = $event->throwable(); + $buffer .= trim( + $throwable->description() . PHP_EOL . + $throwable->stackTrace(), + ); + + $fault = $this->document->createElement( + $type, + Xml::prepareString($buffer), + ); + + $fault->setAttribute('type', $throwable->className()); + + $this->currentTestCase->appendChild($fault); + + if (!$this->prepared) { + $this->handleFinish($event->telemetryInfo(), 0); + } + } + + /** + * @throws InvalidArgumentException + */ + private function handleIncompleteOrSkipped(MarkedIncomplete|Skipped $event): void + { + if (!$this->prepared) { + $this->createTestCase($event); + } + + assert($this->currentTestCase !== null); + + $skipped = $this->document->createElement('skipped'); + + $this->currentTestCase->appendChild($skipped); + + $this->testSuiteSkipped[$this->testSuiteLevel]++; + + if (!$this->prepared) { + $this->handleFinish($event->telemetryInfo(), 0); + } + } + + /** + * @throws InvalidArgumentException + */ + private function testAsString(Test $test): string + { + if ($test->isPhpt()) { + return basename($test->file()); + } + + assert($test instanceof TestMethod); + + return sprintf( + '%s::%s%s', + $test->className(), + $this->name($test), + PHP_EOL, + ); + } + + /** + * @throws InvalidArgumentException + */ + private function name(Test $test): string + { + if ($test->isPhpt()) { + return basename($test->file()); + } + + assert($test instanceof TestMethod); + + if (!$test->testData()->hasDataFromDataProvider()) { + return $test->methodName(); + } + + $dataSetName = $test->testData()->dataFromDataProvider()->dataSetName(); + + if (is_int($dataSetName)) { + return sprintf( + '%s with data set #%d', + $test->methodName(), + $dataSetName, + ); + } + + return sprintf( + '%s with data set "%s"', + $test->methodName(), + $dataSetName, + ); + } + + /** + * @throws InvalidArgumentException + * + * @phpstan-assert !null $this->currentTestCase + */ + private function createTestCase(Errored|Failed|MarkedIncomplete|PreparationStarted|Prepared|Skipped $event): void + { + $testCase = $this->document->createElement('testcase'); + + $test = $event->test(); + + $testCase->setAttribute('name', $this->name($test)); + $testCase->setAttribute('file', $test->file()); + + if ($test->isTestMethod()) { + assert($test instanceof TestMethod); + + $testCase->setAttribute('line', (string) $test->line()); + $testCase->setAttribute('class', $test->className()); + $testCase->setAttribute('classname', str_replace('\\', '.', $test->className())); + } + + $this->currentTestCase = $testCase; + $this->time = $event->telemetryInfo()->time(); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/JUnit/Subscriber/Subscriber.php b/app/vendor/phpunit/phpunit/src/Logging/JUnit/Subscriber/Subscriber.php new file mode 100644 index 000000000..b81f30da4 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/JUnit/Subscriber/Subscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\JUnit; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +abstract readonly class Subscriber +{ + private JunitXmlLogger $logger; + + public function __construct(JunitXmlLogger $logger) + { + $this->logger = $logger; + } + + protected function logger(): JunitXmlLogger + { + return $this->logger; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/JUnit/Subscriber/TestErroredSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/JUnit/Subscriber/TestErroredSubscriber.php new file mode 100644 index 000000000..114b1c84d --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/JUnit/Subscriber/TestErroredSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\JUnit; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\Errored; +use PHPUnit\Event\Test\ErroredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestErroredSubscriber extends Subscriber implements ErroredSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(Errored $event): void + { + $this->logger()->testErrored($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/JUnit/Subscriber/TestFailedSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/JUnit/Subscriber/TestFailedSubscriber.php new file mode 100644 index 000000000..e80507843 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/JUnit/Subscriber/TestFailedSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\JUnit; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\Failed; +use PHPUnit\Event\Test\FailedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestFailedSubscriber extends Subscriber implements FailedSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(Failed $event): void + { + $this->logger()->testFailed($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/JUnit/Subscriber/TestFinishedSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/JUnit/Subscriber/TestFinishedSubscriber.php new file mode 100644 index 000000000..55aed8c66 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/JUnit/Subscriber/TestFinishedSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\JUnit; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\Finished; +use PHPUnit\Event\Test\FinishedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestFinishedSubscriber extends Subscriber implements FinishedSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(Finished $event): void + { + $this->logger()->testFinished($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/JUnit/Subscriber/TestMarkedIncompleteSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/JUnit/Subscriber/TestMarkedIncompleteSubscriber.php new file mode 100644 index 000000000..8732af9dd --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/JUnit/Subscriber/TestMarkedIncompleteSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\JUnit; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\MarkedIncomplete; +use PHPUnit\Event\Test\MarkedIncompleteSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestMarkedIncompleteSubscriber extends Subscriber implements MarkedIncompleteSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(MarkedIncomplete $event): void + { + $this->logger()->testMarkedIncomplete($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/JUnit/Subscriber/TestPreparationErroredSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/JUnit/Subscriber/TestPreparationErroredSubscriber.php new file mode 100644 index 000000000..c6fb388f8 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/JUnit/Subscriber/TestPreparationErroredSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\JUnit; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\PreparationErrored; +use PHPUnit\Event\Test\PreparationErroredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestPreparationErroredSubscriber extends Subscriber implements PreparationErroredSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(PreparationErrored $event): void + { + $this->logger()->testPreparationErrored(); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/JUnit/Subscriber/TestPreparationFailedSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/JUnit/Subscriber/TestPreparationFailedSubscriber.php new file mode 100644 index 000000000..456466a18 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/JUnit/Subscriber/TestPreparationFailedSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\JUnit; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\PreparationFailed; +use PHPUnit\Event\Test\PreparationFailedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestPreparationFailedSubscriber extends Subscriber implements PreparationFailedSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(PreparationFailed $event): void + { + $this->logger()->testPreparationFailed(); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/JUnit/Subscriber/TestPreparationStartedSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/JUnit/Subscriber/TestPreparationStartedSubscriber.php new file mode 100644 index 000000000..8d24d6554 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/JUnit/Subscriber/TestPreparationStartedSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\JUnit; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\PreparationStarted; +use PHPUnit\Event\Test\PreparationStartedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestPreparationStartedSubscriber extends Subscriber implements PreparationStartedSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(PreparationStarted $event): void + { + $this->logger()->testPreparationStarted($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/JUnit/Subscriber/TestPreparedSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/JUnit/Subscriber/TestPreparedSubscriber.php new file mode 100644 index 000000000..2a80b8af5 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/JUnit/Subscriber/TestPreparedSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\JUnit; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\Prepared; +use PHPUnit\Event\Test\PreparedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestPreparedSubscriber extends Subscriber implements PreparedSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(Prepared $event): void + { + $this->logger()->testPrepared(); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/JUnit/Subscriber/TestPrintedUnexpectedOutputSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/JUnit/Subscriber/TestPrintedUnexpectedOutputSubscriber.php new file mode 100644 index 000000000..186bf1502 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/JUnit/Subscriber/TestPrintedUnexpectedOutputSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\JUnit; + +use PHPUnit\Event\Test\PrintedUnexpectedOutput; +use PHPUnit\Event\Test\PrintedUnexpectedOutputSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestPrintedUnexpectedOutputSubscriber extends Subscriber implements PrintedUnexpectedOutputSubscriber +{ + public function notify(PrintedUnexpectedOutput $event): void + { + $this->logger()->testPrintedUnexpectedOutput($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/JUnit/Subscriber/TestRunnerExecutionFinishedSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/JUnit/Subscriber/TestRunnerExecutionFinishedSubscriber.php new file mode 100644 index 000000000..00617621a --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/JUnit/Subscriber/TestRunnerExecutionFinishedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\JUnit; + +use PHPUnit\Event\TestRunner\ExecutionFinished; +use PHPUnit\Event\TestRunner\ExecutionFinishedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestRunnerExecutionFinishedSubscriber extends Subscriber implements ExecutionFinishedSubscriber +{ + public function notify(ExecutionFinished $event): void + { + $this->logger()->flush(); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/JUnit/Subscriber/TestSkippedSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/JUnit/Subscriber/TestSkippedSubscriber.php new file mode 100644 index 000000000..c6ee84ac6 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/JUnit/Subscriber/TestSkippedSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\JUnit; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\Skipped; +use PHPUnit\Event\Test\SkippedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestSkippedSubscriber extends Subscriber implements SkippedSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(Skipped $event): void + { + $this->logger()->testSkipped($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/JUnit/Subscriber/TestSuiteFinishedSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/JUnit/Subscriber/TestSuiteFinishedSubscriber.php new file mode 100644 index 000000000..476917707 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/JUnit/Subscriber/TestSuiteFinishedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\JUnit; + +use PHPUnit\Event\TestSuite\Finished; +use PHPUnit\Event\TestSuite\FinishedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestSuiteFinishedSubscriber extends Subscriber implements FinishedSubscriber +{ + public function notify(Finished $event): void + { + $this->logger()->testSuiteFinished(); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/JUnit/Subscriber/TestSuiteStartedSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/JUnit/Subscriber/TestSuiteStartedSubscriber.php new file mode 100644 index 000000000..30e350d34 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/JUnit/Subscriber/TestSuiteStartedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\JUnit; + +use PHPUnit\Event\TestSuite\Started; +use PHPUnit\Event\TestSuite\StartedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestSuiteStartedSubscriber extends Subscriber implements StartedSubscriber +{ + public function notify(Started $event): void + { + $this->logger()->testSuiteStarted($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Exception/CannotOpenUriForWritingException.php b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Exception/CannotOpenUriForWritingException.php new file mode 100644 index 000000000..a8ad7eba5 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Exception/CannotOpenUriForWritingException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\OpenTestReporting; + +use RuntimeException; + +final class CannotOpenUriForWritingException extends RuntimeException implements Exception +{ +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Exception/Exception.php b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Exception/Exception.php new file mode 100644 index 000000000..30766ae9f --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Exception/Exception.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\OpenTestReporting; + +interface Exception extends \PHPUnit\Exception +{ +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/InfrastructureInformationProvider.php b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/InfrastructureInformationProvider.php new file mode 100644 index 000000000..e0e12cbb8 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/InfrastructureInformationProvider.php @@ -0,0 +1,188 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\OpenTestReporting; + +use const DIRECTORY_SEPARATOR; +use const PHP_OS_FAMILY; +use function assert; +use function explode; +use function fclose; +use function function_exists; +use function getenv; +use function gethostname; +use function is_array; +use function is_resource; +use function php_uname; +use function posix_geteuid; +use function posix_getpwuid; +use function preg_split; +use function proc_close; +use function proc_open; +use function str_contains; +use function str_replace; +use function str_starts_with; +use function stream_get_contents; +use function trim; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class InfrastructureInformationProvider +{ + /** + * @return non-empty-string + */ + public function operatingSystem(): string + { + return php_uname(); + } + + /** + * @return non-empty-string + */ + public function hostName(): string + { + $candidate = gethostname(); + + if ($candidate === false) { + return 'unknown'; + } + + $candidate = trim($candidate); + + if ($candidate === '') { + return 'unknown'; + } + + return $candidate; + } + + /** + * @return non-empty-string + */ + public function userName(): string + { + if (function_exists('posix_getpwuid') && function_exists('posix_geteuid')) { + $candidate = trim(posix_getpwuid(posix_geteuid())['name']); + } elseif (PHP_OS_FAMILY === 'Windows') { + $candidate = trim((string) getenv('USERNAME')); + } + + if (!isset($candidate) || $candidate === '') { + return 'unknown'; + } + + return $candidate; + } + + /** + * @return array{originUrl: non-empty-string, branch: non-empty-string, commit: non-empty-string, clean: bool, status: string}|false + */ + public function gitInformation(): array|false + { + $buffer = $this->executeGitCommand('remote show -n'); + + if ($buffer === false) { + return false; + } + + if (!str_contains($buffer, 'origin')) { + return false; + } + + $buffer = $this->executeGitCommand('remote show -n origin'); + + if ($buffer === false) { + return false; + } + + $lines = preg_split("/\r\n|\n|\r/", $buffer); + + if (!isset($lines[1]) || !str_starts_with($lines[1], ' Fetch URL: ')) { + return false; + } + + $originUrl = trim(str_replace(' Fetch URL: ', '', $lines[1])); + + if (str_contains($originUrl, '@')) { + $originUrl = explode('@', $originUrl)[1]; + } + + $branch = $this->executeGitCommand('symbolic-ref --short HEAD'); + + if ($branch === false) { + return false; + } + + $commit = $this->executeGitCommand('rev-parse HEAD'); + + if ($commit === false) { + return false; + } + + $status = $this->executeGitCommand('status --porcelain'); + + if ($status === false) { + return false; + } + + return [ + 'originUrl' => $originUrl, + 'branch' => $branch, + 'commit' => $commit, + 'clean' => $status === '', + 'status' => $status, + ]; + } + + /** + * @return false|non-empty-string + */ + private function executeGitCommand(string $command): false|string + { + $command = 'git ' . $command; + + if (DIRECTORY_SEPARATOR === '/') { + $command = 'LC_ALL=en_US.UTF-8 ' . $command; + } + + $process = @proc_open( + $command, + [ + 1 => ['pipe', 'w'], + 2 => ['pipe', 'w'], + ], + $pipes, + ); + + if (!is_resource($process)) { + return false; + } + + assert(is_array($pipes)); + assert(isset($pipes[1]) && is_resource($pipes[1])); + assert(isset($pipes[2]) && is_resource($pipes[2])); + + $result = trim((string) stream_get_contents($pipes[1])); + + fclose($pipes[1]); + fclose($pipes[2]); + + $returnCode = proc_close($process); + + if ($returnCode !== 0) { + return false; + } + + return $result; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/OtrXmlLogger.php b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/OtrXmlLogger.php new file mode 100644 index 000000000..fe45f7409 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/OtrXmlLogger.php @@ -0,0 +1,472 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\OpenTestReporting; + +use const PHP_VERSION; +use const ZEND_THREAD_SAFE; +use function array_pop; +use function assert; +use function count; +use function error_get_last; +use function str_replace; +use DateTimeImmutable; +use DateTimeZone; +use PHPUnit\Event\Code\Test; +use PHPUnit\Event\Code\TestMethod; +use PHPUnit\Event\Code\Throwable; +use PHPUnit\Event\Facade; +use PHPUnit\Event\Test\AfterLastTestMethodErrored; +use PHPUnit\Event\Test\AfterLastTestMethodFailed; +use PHPUnit\Event\Test\BeforeFirstTestMethodErrored; +use PHPUnit\Event\Test\BeforeFirstTestMethodFailed; +use PHPUnit\Event\Test\Errored; +use PHPUnit\Event\Test\Failed; +use PHPUnit\Event\Test\MarkedIncomplete; +use PHPUnit\Event\Test\PreparationErrored; +use PHPUnit\Event\Test\PreparationFailed; +use PHPUnit\Event\Test\Prepared as TestStarted; +use PHPUnit\Event\Test\Skipped; +use PHPUnit\Event\TestSuite\Skipped as TestSuiteSkipped; +use PHPUnit\Event\TestSuite\Started as TestSuiteStarted; +use PHPUnit\Event\TestSuite\TestSuiteForTestClass; +use XMLWriter; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class OtrXmlLogger +{ + private readonly XMLWriter $writer; + + /** + * @var non-negative-int + */ + private int $idSequence = 0; + + /** + * @var ?positive-int + */ + private ?int $parentId = null; + + /** + * @var list + */ + private array $parentIdStack = []; + + /** + * @var ?positive-int + */ + private ?int $testId = null; + private ?Throwable $parentErrored = null; + private ?Throwable $parentFailed = null; + private bool $alreadyFinished = false; + private bool $includeGitInformation; + + /** + * @param non-empty-string $uri + * + * @throws CannotOpenUriForWritingException + */ + public function __construct(Facade $facade, string $uri, bool $includeGitInformation) + { + $this->writer = new XMLWriter; + + if (@$this->writer->openUri($uri) === false) { + throw new CannotOpenUriForWritingException( + str_replace('XMLWriter::openUri(): ', '', error_get_last()['message']), + ); + } + + $this->writer->setIndent(true); + + $this->registerSubscribers($facade); + + $this->includeGitInformation = $includeGitInformation; + } + + public function testRunnerStarted(): void + { + $infrastructure = new InfrastructureInformationProvider; + $gitInformation = false; + + if ($this->includeGitInformation) { + $gitInformation = $infrastructure->gitInformation(); + } + + $this->writer->startDocument(); + + $this->writer->startElement('e:events'); + $this->writer->writeAttribute('xmlns', 'https://schemas.opentest4j.org/reporting/core/0.2.0'); + $this->writer->writeAttribute('xmlns:e', 'https://schemas.opentest4j.org/reporting/events/0.2.0'); + + if ($gitInformation !== false) { + $this->writer->writeAttribute('xmlns:git', 'https://schemas.opentest4j.org/reporting/git/0.2.0'); + } + + $this->writer->writeAttribute('xmlns:php', 'https://schema.phpunit.de/otr/php/0.0.1'); + $this->writer->writeAttribute('xmlns:phpunit', 'https://schema.phpunit.de/otr/phpunit/0.0.1'); + + $this->writer->startElement('infrastructure'); + $this->writer->writeElement('hostName', $infrastructure->hostName()); + $this->writer->writeElement('userName', $infrastructure->userName()); + $this->writer->writeElement('operatingSystem', $infrastructure->operatingSystem()); + + $this->writer->writeElement('php:phpVersion', PHP_VERSION); + $this->writer->writeElement('php:threadModel', ZEND_THREAD_SAFE ? 'ZTS' : 'NTS'); + + if ($gitInformation !== false) { + $this->writer->startElement('git:repository'); + $this->writer->writeAttribute('originUrl', $gitInformation['originUrl']); + $this->writer->endElement(); + + $this->writer->writeElement('git:branch', $gitInformation['branch']); + $this->writer->writeElement('git:commit', $gitInformation['commit']); + + $this->writer->startElement('git:status'); + $this->writer->writeAttribute('clean', $gitInformation['clean'] === true ? 'true' : 'false'); + $this->writer->writeCdata($gitInformation['status']); + $this->writer->endElement(); + } + + $this->writer->endElement(); + + $this->writer->flush(); + } + + public function testRunnerFinished(): void + { + $this->writer->endDocument(); + + $this->writer->flush(); + } + + public function testSuiteStarted(TestSuiteStarted $event): void + { + $id = $this->nextId(); + + $this->writer->startElement('e:started'); + $this->writer->writeAttribute('id', (string) $id); + + if ($this->parentId !== null) { + $this->writer->writeAttribute('parentId', (string) $this->parentId); + } + + $testSuite = $event->testSuite(); + + $this->writer->writeAttribute('name', $testSuite->name()); + $this->writer->writeAttribute('time', $this->timestamp()); + + if ($testSuite->isForTestClass()) { + assert($testSuite instanceof TestSuiteForTestClass); + + $this->writer->startElement('sources'); + + $this->writer->startElement('fileSource'); + $this->writer->writeAttribute('path', $testSuite->file()); + $this->writer->startElement('filePosition'); + $this->writer->writeAttribute('line', (string) $testSuite->line()); + $this->writer->endElement(); + $this->writer->endElement(); + + $this->writer->startElement('phpunit:classSource'); + $this->writer->writeAttribute('className', $testSuite->className()); + $this->writer->endElement(); + + $this->writer->endElement(); + } + + $this->writer->endElement(); + + $this->writer->flush(); + + $this->parentId = $id; + $this->parentIdStack[] = $id; + } + + public function testSuiteSkipped(TestSuiteSkipped $event): void + { + $this->writer->startElement('e:finished'); + $this->writer->writeAttribute('id', (string) $this->parentId); + $this->writer->writeAttribute('time', $this->timestamp()); + + $this->writer->startElement('result'); + $this->writer->writeAttribute('status', 'SKIPPED'); + $this->writer->writeElement('reason', $event->message()); + $this->writer->endElement(); + + $this->writer->endElement(); + + $this->writer->flush(); + + $this->reduceTestSuiteLevel(); + } + + public function testSuiteFinished(): void + { + $this->writer->startElement('e:finished'); + $this->writer->writeAttribute('id', (string) $this->parentId); + $this->writer->writeAttribute('time', $this->timestamp()); + + if ($this->parentErrored !== null) { + $this->writer->startElement('result'); + $this->writer->writeAttribute('status', 'ERRORED'); + $this->writer->writeElement('reason', $this->parentErrored->message()); + $this->writeThrowable($this->parentErrored, false); + $this->writer->endElement(); + } elseif ($this->parentFailed !== null) { + $this->writer->startElement('result'); + $this->writer->writeAttribute('status', 'FAILED'); + $this->writer->writeElement('reason', $this->parentFailed->message()); + $this->writeThrowable($this->parentFailed, true); + $this->writer->endElement(); + } + + $this->writer->endElement(); + + $this->writer->flush(); + + $this->parentErrored = null; + $this->parentFailed = null; + + $this->reduceTestSuiteLevel(); + } + + public function testPrepared(PreparationErrored|PreparationFailed|TestStarted $event): void + { + $this->testId = $this->nextId(); + + $this->writeTestStarted( + $event->test(), + $this->testId, + $this->parentId, + ); + } + + public function testFinished(): void + { + if (!$this->alreadyFinished) { + $this->writer->startElement('e:finished'); + $this->writer->writeAttribute('id', (string) $this->testId); + $this->writer->writeAttribute('time', $this->timestamp()); + $this->writer->startElement('result'); + $this->writer->writeAttribute('status', Status::Successful->value); + $this->writer->endElement(); + $this->writer->endElement(); + } + + $this->alreadyFinished = false; + $this->testId = null; + } + + public function testFailed(Failed $event): void + { + $this->writer->startElement('e:finished'); + $this->writer->writeAttribute('id', (string) $this->testId); + $this->writer->writeAttribute('time', $this->timestamp()); + $this->writer->startElement('result'); + $this->writer->writeAttribute('status', Status::Failed->value); + + $this->writer->writeElement('reason', $event->throwable()->message()); + $this->writeThrowable($event->throwable(), true); + + $this->writer->endElement(); + $this->writer->endElement(); + + $this->writer->flush(); + + $this->alreadyFinished = true; + } + + public function testErrored(Errored $event): void + { + $this->writer->startElement('e:finished'); + $this->writer->writeAttribute('id', (string) $this->testId); + $this->writer->writeAttribute('time', $this->timestamp()); + $this->writer->startElement('result'); + $this->writer->writeAttribute('status', Status::Errored->value); + + $this->writer->writeElement('reason', $event->throwable()->message()); + $this->writeThrowable($event->throwable(), false); + + $this->writer->endElement(); + $this->writer->endElement(); + + $this->writer->flush(); + + $this->alreadyFinished = true; + } + + public function testSkipped(Skipped $event): void + { + if ($this->testId === null) { + $this->testId = $this->nextId(); + + $this->writeTestStarted( + $event->test(), + $this->testId, + $this->parentId, + ); + } + + $this->writer->startElement('e:finished'); + $this->writer->writeAttribute('id', (string) $this->testId); + $this->writer->writeAttribute('time', $this->timestamp()); + $this->writer->startElement('result'); + $this->writer->writeAttribute('status', Status::Skipped->value); + + $this->writer->writeElement('reason', $event->message()); + + $this->writer->endElement(); + $this->writer->endElement(); + + $this->writer->flush(); + + $this->alreadyFinished = true; + } + + public function markTestIncomplete(MarkedIncomplete $event): void + { + $this->writer->startElement('e:finished'); + $this->writer->writeAttribute('id', (string) $this->testId); + $this->writer->writeAttribute('time', $this->timestamp()); + $this->writer->startElement('result'); + $this->writer->writeAttribute('status', Status::Aborted->value); + + $this->writer->writeElement('reason', $event->throwable()->message()); + $this->writeThrowable($event->throwable(), false); + + $this->writer->endElement(); + $this->writer->endElement(); + + $this->writer->flush(); + + $this->alreadyFinished = true; + } + + public function parentErrored(AfterLastTestMethodErrored|BeforeFirstTestMethodErrored $event): void + { + $this->parentErrored = $event->throwable(); + } + + public function parentFailed(AfterLastTestMethodFailed|BeforeFirstTestMethodFailed $event): void + { + $this->parentFailed = $event->throwable(); + } + + private function registerSubscribers(Facade $facade): void + { + $facade->registerSubscribers( + new TestRunnerStartedSubscriber($this), + new TestSuiteStartedSubscriber($this), + new TestSuiteSkippedSubscriber($this), + new BeforeFirstTestMethodErroredSubscriber($this), + new BeforeFirstTestMethodFailedSubscriber($this), + new AfterLastTestMethodErroredSubscriber($this), + new AfterLastTestMethodFailedSubscriber($this), + new TestPreparationErroredSubscriber($this), + new TestPreparationFailedSubscriber($this), + new TestPreparedSubscriber($this), + new TestAbortedSubscriber($this), + new TestErroredSubscriber($this), + new TestFailedSubscriber($this), + new TestSkippedSubscriber($this), + new TestFinishedSubscriber($this), + new TestSuiteFinishedSubscriber($this), + new TestRunnerFinishedSubscriber($this), + ); + } + + /** + * @param positive-int $id + * @param ?positive-int $parentId + */ + private function writeTestStarted(Test $test, int $id, ?int $parentId): void + { + $this->writer->startElement('e:started'); + $this->writer->writeAttribute('id', (string) $id); + + if ($parentId !== null) { + $this->writer->writeAttribute('parentId', (string) $parentId); + } + + $this->writer->writeAttribute('name', $test->name()); + $this->writer->writeAttribute('time', $this->timestamp()); + + $this->writer->startElement('sources'); + + $this->writer->startElement('fileSource'); + $this->writer->writeAttribute('path', $test->file()); + + if ($test->isTestMethod()) { + assert($test instanceof TestMethod); + + $this->writer->startElement('filePosition'); + $this->writer->writeAttribute('line', (string) $test->line()); + $this->writer->endElement(); + } + + $this->writer->endElement(); + + if ($test->isTestMethod()) { + assert($test instanceof TestMethod); + + $this->writer->startElement('phpunit:methodSource'); + $this->writer->writeAttribute('className', $test->className()); + $this->writer->writeAttribute('methodName', $test->methodName()); + $this->writer->endElement(); + } + + $this->writer->endElement(); + + $this->writer->endElement(); + + $this->writer->flush(); + } + + private function writeThrowable(Throwable $throwable, bool $assertionError): void + { + $this->writer->startElement('phpunit:throwable'); + $this->writer->writeAttribute('type', $throwable->className()); + $this->writer->writeAttribute('assertionError', $assertionError ? 'true' : 'false'); + $this->writer->writeCdata($throwable->asString()); + $this->writer->endElement(); + } + + /** + * @return non-empty-string + */ + private function timestamp(): string + { + return (new DateTimeImmutable('now', new DateTimeZone('UTC')))->format('Y-m-d\TH:i:s.u\Z'); + } + + /** + * @return positive-int + */ + private function nextId(): int + { + return ++$this->idSequence; + } + + private function reduceTestSuiteLevel(): void + { + array_pop($this->parentIdStack); + + if ($this->parentIdStack !== []) { + $this->parentId = $this->parentIdStack[count($this->parentIdStack) - 1]; + + return; + } + + $this->parentId = null; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Status.php b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Status.php new file mode 100644 index 000000000..4ff399b2c --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Status.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 PHPUnit\Logging\OpenTestReporting; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This enumeration is not covered by the backward compatibility promise for PHPUnit + */ +enum Status: string +{ + case Aborted = 'ABORTED'; + case Errored = 'ERRORED'; + case Failed = 'FAILED'; + case Skipped = 'SKIPPED'; + case Successful = 'SUCCESSFUL'; +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Subscriber/AfterLastTestMethodErroredSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Subscriber/AfterLastTestMethodErroredSubscriber.php new file mode 100644 index 000000000..5b31f11cf --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Subscriber/AfterLastTestMethodErroredSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\OpenTestReporting; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\AfterLastTestMethodErrored; +use PHPUnit\Event\Test\AfterLastTestMethodErroredSubscriber as AfterLastTestMethodErroredSubscriberInterface; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class AfterLastTestMethodErroredSubscriber extends Subscriber implements AfterLastTestMethodErroredSubscriberInterface +{ + /** + * @throws InvalidArgumentException + */ + public function notify(AfterLastTestMethodErrored $event): void + { + $this->logger()->parentErrored($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Subscriber/AfterLastTestMethodFailedSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Subscriber/AfterLastTestMethodFailedSubscriber.php new file mode 100644 index 000000000..340d26d73 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Subscriber/AfterLastTestMethodFailedSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\OpenTestReporting; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\AfterLastTestMethodFailed; +use PHPUnit\Event\Test\AfterLastTestMethodFailedSubscriber as AfterLastTestMethodFailedSubscriberInterface; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class AfterLastTestMethodFailedSubscriber extends Subscriber implements AfterLastTestMethodFailedSubscriberInterface +{ + /** + * @throws InvalidArgumentException + */ + public function notify(AfterLastTestMethodFailed $event): void + { + $this->logger()->parentFailed($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Subscriber/BeforeFirstTestMethodErroredSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Subscriber/BeforeFirstTestMethodErroredSubscriber.php new file mode 100644 index 000000000..3fb8c1efc --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Subscriber/BeforeFirstTestMethodErroredSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\OpenTestReporting; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\BeforeFirstTestMethodErrored; +use PHPUnit\Event\Test\BeforeFirstTestMethodErroredSubscriber as BeforeFirstTestMethodErroredSubscriberInterface; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class BeforeFirstTestMethodErroredSubscriber extends Subscriber implements BeforeFirstTestMethodErroredSubscriberInterface +{ + /** + * @throws InvalidArgumentException + */ + public function notify(BeforeFirstTestMethodErrored $event): void + { + $this->logger()->parentErrored($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Subscriber/BeforeFirstTestMethodFailedSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Subscriber/BeforeFirstTestMethodFailedSubscriber.php new file mode 100644 index 000000000..fad858cfe --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Subscriber/BeforeFirstTestMethodFailedSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\OpenTestReporting; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\BeforeFirstTestMethodFailed; +use PHPUnit\Event\Test\BeforeFirstTestMethodFailedSubscriber as BeforeFirstTestMethodFailedSubscriberInterface; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class BeforeFirstTestMethodFailedSubscriber extends Subscriber implements BeforeFirstTestMethodFailedSubscriberInterface +{ + /** + * @throws InvalidArgumentException + */ + public function notify(BeforeFirstTestMethodFailed $event): void + { + $this->logger()->parentFailed($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Subscriber/Subscriber.php b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Subscriber/Subscriber.php new file mode 100644 index 000000000..84b71de67 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Subscriber/Subscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\OpenTestReporting; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +abstract readonly class Subscriber +{ + private OtrXmlLogger $logger; + + public function __construct(OtrXmlLogger $logger) + { + $this->logger = $logger; + } + + protected function logger(): OtrXmlLogger + { + return $this->logger; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Subscriber/TestAbortedSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Subscriber/TestAbortedSubscriber.php new file mode 100644 index 000000000..31376214a --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Subscriber/TestAbortedSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\OpenTestReporting; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\MarkedIncomplete; +use PHPUnit\Event\Test\MarkedIncompleteSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestAbortedSubscriber extends Subscriber implements MarkedIncompleteSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(MarkedIncomplete $event): void + { + $this->logger()->markTestIncomplete($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Subscriber/TestErroredSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Subscriber/TestErroredSubscriber.php new file mode 100644 index 000000000..1a8a40be9 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Subscriber/TestErroredSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\OpenTestReporting; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\Errored; +use PHPUnit\Event\Test\ErroredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestErroredSubscriber extends Subscriber implements ErroredSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(Errored $event): void + { + $this->logger()->testErrored($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Subscriber/TestFailedSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Subscriber/TestFailedSubscriber.php new file mode 100644 index 000000000..0b0caf971 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Subscriber/TestFailedSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\OpenTestReporting; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\Failed; +use PHPUnit\Event\Test\FailedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestFailedSubscriber extends Subscriber implements FailedSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(Failed $event): void + { + $this->logger()->testFailed($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Subscriber/TestFinishedSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Subscriber/TestFinishedSubscriber.php new file mode 100644 index 000000000..d05de8b5a --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Subscriber/TestFinishedSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\OpenTestReporting; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\Finished; +use PHPUnit\Event\Test\FinishedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestFinishedSubscriber extends Subscriber implements FinishedSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(Finished $event): void + { + $this->logger()->testFinished(); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Subscriber/TestPreparationErroredSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Subscriber/TestPreparationErroredSubscriber.php new file mode 100644 index 000000000..6fb725628 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Subscriber/TestPreparationErroredSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\OpenTestReporting; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\PreparationErrored; +use PHPUnit\Event\Test\PreparationErroredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestPreparationErroredSubscriber extends Subscriber implements PreparationErroredSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(PreparationErrored $event): void + { + $this->logger()->testPrepared($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Subscriber/TestPreparationFailedSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Subscriber/TestPreparationFailedSubscriber.php new file mode 100644 index 000000000..f0167508a --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Subscriber/TestPreparationFailedSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\OpenTestReporting; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\PreparationFailed; +use PHPUnit\Event\Test\PreparationFailedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestPreparationFailedSubscriber extends Subscriber implements PreparationFailedSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(PreparationFailed $event): void + { + $this->logger()->testPrepared($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Subscriber/TestPreparedSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Subscriber/TestPreparedSubscriber.php new file mode 100644 index 000000000..9138f142b --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Subscriber/TestPreparedSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\OpenTestReporting; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\Prepared; +use PHPUnit\Event\Test\PreparedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestPreparedSubscriber extends Subscriber implements PreparedSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(Prepared $event): void + { + $this->logger()->testPrepared($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Subscriber/TestRunnerFinishedSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Subscriber/TestRunnerFinishedSubscriber.php new file mode 100644 index 000000000..d690ba567 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Subscriber/TestRunnerFinishedSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\OpenTestReporting; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\TestRunner\ExecutionFinished; +use PHPUnit\Event\TestRunner\ExecutionFinishedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestRunnerFinishedSubscriber extends Subscriber implements ExecutionFinishedSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(ExecutionFinished $event): void + { + $this->logger()->testRunnerFinished(); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Subscriber/TestRunnerStartedSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Subscriber/TestRunnerStartedSubscriber.php new file mode 100644 index 000000000..6aba01c96 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Subscriber/TestRunnerStartedSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\OpenTestReporting; + +use PHPUnit\Event\Application\Started; +use PHPUnit\Event\Application\StartedSubscriber; +use PHPUnit\Event\InvalidArgumentException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestRunnerStartedSubscriber extends Subscriber implements StartedSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(Started $event): void + { + $this->logger()->testRunnerStarted(); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Subscriber/TestSkippedSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Subscriber/TestSkippedSubscriber.php new file mode 100644 index 000000000..32e67ca8c --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Subscriber/TestSkippedSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\OpenTestReporting; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\Skipped; +use PHPUnit\Event\Test\SkippedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestSkippedSubscriber extends Subscriber implements SkippedSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(Skipped $event): void + { + $this->logger()->testSkipped($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Subscriber/TestSuiteFinishedSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Subscriber/TestSuiteFinishedSubscriber.php new file mode 100644 index 000000000..16ebf626c --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Subscriber/TestSuiteFinishedSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\OpenTestReporting; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\TestSuite\Finished; +use PHPUnit\Event\TestSuite\FinishedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestSuiteFinishedSubscriber extends Subscriber implements FinishedSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(Finished $event): void + { + $this->logger()->testSuiteFinished(); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Subscriber/TestSuiteSkippedSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Subscriber/TestSuiteSkippedSubscriber.php new file mode 100644 index 000000000..58691b613 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Subscriber/TestSuiteSkippedSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\OpenTestReporting; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\TestSuite\Skipped; +use PHPUnit\Event\TestSuite\SkippedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestSuiteSkippedSubscriber extends Subscriber implements SkippedSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(Skipped $event): void + { + $this->logger()->testSuiteSkipped($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Subscriber/TestSuiteStartedSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Subscriber/TestSuiteStartedSubscriber.php new file mode 100644 index 000000000..786780125 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/Subscriber/TestSuiteStartedSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\OpenTestReporting; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\TestSuite\Started; +use PHPUnit\Event\TestSuite\StartedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestSuiteStartedSubscriber extends Subscriber implements StartedSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(Started $event): void + { + $this->logger()->testSuiteStarted($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/schema/core-0.2.0.xsd b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/schema/core-0.2.0.xsd new file mode 100644 index 000000000..170e20cae --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/schema/core-0.2.0.xsd @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The media type of the file content, e.g. 'text/plain' or 'application/json', + see https://www.iana.org/assignments/media-types/media-types.xhtml. + For text files, the charset should be specified in the 'charset' attribute, + e.g. 'text/plain; charset=utf-8'. + + + + + + + + + + + + + + Typically 'stdout' or 'stderr' but may also be used for attaching other log output + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/schema/events-0.2.0.xsd b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/schema/events-0.2.0.xsd new file mode 100644 index 000000000..e5c37ccc3 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/schema/events-0.2.0.xsd @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/schema/git-0.2.0.xsd b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/schema/git-0.2.0.xsd new file mode 100644 index 000000000..1a1dd6d6a --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/schema/git-0.2.0.xsd @@ -0,0 +1,69 @@ + + + + + + + + + the URL of the 'origin' remote of the Git repository + + + + + + + + + + + + + + + + + + + + the branch the HEAD commit is pointing to, if any + + + + + + + + + + + + the commit hash + + + + + + + + + + + + the output of `git status --porcelain`, potentially empty + + + + + whether the working directory clean contains no changes or untracked files + + + + + + + + + diff --git a/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/schema/otr.xsd b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/schema/otr.xsd new file mode 100644 index 000000000..982dac4de --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/schema/otr.xsd @@ -0,0 +1,7 @@ + + + + + + + diff --git a/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/schema/php.xsd b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/schema/php.xsd new file mode 100644 index 000000000..f880345d1 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/schema/php.xsd @@ -0,0 +1,15 @@ + + + + + + + + + + + + + diff --git a/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/schema/phpunit.xsd b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/schema/phpunit.xsd new file mode 100644 index 000000000..0a0c00eda --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/OpenTestReporting/schema/phpunit.xsd @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/vendor/phpunit/phpunit/src/Logging/TeamCity/Subscriber/Subscriber.php b/app/vendor/phpunit/phpunit/src/Logging/TeamCity/Subscriber/Subscriber.php new file mode 100644 index 000000000..b1ad46d83 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/TeamCity/Subscriber/Subscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TeamCity; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +abstract readonly class Subscriber +{ + private TeamCityLogger $logger; + + public function __construct(TeamCityLogger $logger) + { + $this->logger = $logger; + } + + protected function logger(): TeamCityLogger + { + return $this->logger; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/TeamCity/Subscriber/TestConsideredRiskySubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/TeamCity/Subscriber/TestConsideredRiskySubscriber.php new file mode 100644 index 000000000..9482ccb22 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/TeamCity/Subscriber/TestConsideredRiskySubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TeamCity; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\ConsideredRisky; +use PHPUnit\Event\Test\ConsideredRiskySubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestConsideredRiskySubscriber extends Subscriber implements ConsideredRiskySubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(ConsideredRisky $event): void + { + $this->logger()->testConsideredRisky($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/TeamCity/Subscriber/TestErroredSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/TeamCity/Subscriber/TestErroredSubscriber.php new file mode 100644 index 000000000..4ce8d0cb7 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/TeamCity/Subscriber/TestErroredSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TeamCity; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\Errored; +use PHPUnit\Event\Test\ErroredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestErroredSubscriber extends Subscriber implements ErroredSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(Errored $event): void + { + $this->logger()->testErrored($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/TeamCity/Subscriber/TestFailedSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/TeamCity/Subscriber/TestFailedSubscriber.php new file mode 100644 index 000000000..8d8caa630 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/TeamCity/Subscriber/TestFailedSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TeamCity; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\Failed; +use PHPUnit\Event\Test\FailedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestFailedSubscriber extends Subscriber implements FailedSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(Failed $event): void + { + $this->logger()->testFailed($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/TeamCity/Subscriber/TestFinishedSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/TeamCity/Subscriber/TestFinishedSubscriber.php new file mode 100644 index 000000000..6b4bef3dc --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/TeamCity/Subscriber/TestFinishedSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TeamCity; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\Finished; +use PHPUnit\Event\Test\FinishedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestFinishedSubscriber extends Subscriber implements FinishedSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(Finished $event): void + { + $this->logger()->testFinished($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/TeamCity/Subscriber/TestMarkedIncompleteSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/TeamCity/Subscriber/TestMarkedIncompleteSubscriber.php new file mode 100644 index 000000000..b38d54c44 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/TeamCity/Subscriber/TestMarkedIncompleteSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TeamCity; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\MarkedIncomplete; +use PHPUnit\Event\Test\MarkedIncompleteSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestMarkedIncompleteSubscriber extends Subscriber implements MarkedIncompleteSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(MarkedIncomplete $event): void + { + $this->logger()->testMarkedIncomplete($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/TeamCity/Subscriber/TestPreparedSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/TeamCity/Subscriber/TestPreparedSubscriber.php new file mode 100644 index 000000000..85476a059 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/TeamCity/Subscriber/TestPreparedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TeamCity; + +use PHPUnit\Event\Test\Prepared; +use PHPUnit\Event\Test\PreparedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestPreparedSubscriber extends Subscriber implements PreparedSubscriber +{ + public function notify(Prepared $event): void + { + $this->logger()->testPrepared($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/TeamCity/Subscriber/TestRunnerExecutionFinishedSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/TeamCity/Subscriber/TestRunnerExecutionFinishedSubscriber.php new file mode 100644 index 000000000..824ea4244 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/TeamCity/Subscriber/TestRunnerExecutionFinishedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TeamCity; + +use PHPUnit\Event\TestRunner\ExecutionFinished; +use PHPUnit\Event\TestRunner\ExecutionFinishedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestRunnerExecutionFinishedSubscriber extends Subscriber implements ExecutionFinishedSubscriber +{ + public function notify(ExecutionFinished $event): void + { + $this->logger()->flush(); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/TeamCity/Subscriber/TestSkippedSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/TeamCity/Subscriber/TestSkippedSubscriber.php new file mode 100644 index 000000000..0f55795fc --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/TeamCity/Subscriber/TestSkippedSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TeamCity; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\Skipped; +use PHPUnit\Event\Test\SkippedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestSkippedSubscriber extends Subscriber implements SkippedSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(Skipped $event): void + { + $this->logger()->testSkipped($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/TeamCity/Subscriber/TestSuiteBeforeFirstTestMethodErroredSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/TeamCity/Subscriber/TestSuiteBeforeFirstTestMethodErroredSubscriber.php new file mode 100644 index 000000000..25459197b --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/TeamCity/Subscriber/TestSuiteBeforeFirstTestMethodErroredSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TeamCity; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\BeforeFirstTestMethodErrored; +use PHPUnit\Event\Test\BeforeFirstTestMethodErroredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestSuiteBeforeFirstTestMethodErroredSubscriber extends Subscriber implements BeforeFirstTestMethodErroredSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(BeforeFirstTestMethodErrored $event): void + { + $this->logger()->beforeFirstTestMethodErrored($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/TeamCity/Subscriber/TestSuiteFinishedSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/TeamCity/Subscriber/TestSuiteFinishedSubscriber.php new file mode 100644 index 000000000..71889d8ce --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/TeamCity/Subscriber/TestSuiteFinishedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TeamCity; + +use PHPUnit\Event\TestSuite\Finished; +use PHPUnit\Event\TestSuite\FinishedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestSuiteFinishedSubscriber extends Subscriber implements FinishedSubscriber +{ + public function notify(Finished $event): void + { + $this->logger()->testSuiteFinished($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/TeamCity/Subscriber/TestSuiteSkippedSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/TeamCity/Subscriber/TestSuiteSkippedSubscriber.php new file mode 100644 index 000000000..2d6a3f3d2 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/TeamCity/Subscriber/TestSuiteSkippedSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TeamCity; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\TestSuite\Skipped; +use PHPUnit\Event\TestSuite\SkippedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestSuiteSkippedSubscriber extends Subscriber implements SkippedSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(Skipped $event): void + { + $this->logger()->testSuiteSkipped($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/TeamCity/Subscriber/TestSuiteStartedSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/TeamCity/Subscriber/TestSuiteStartedSubscriber.php new file mode 100644 index 000000000..7caba60b6 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/TeamCity/Subscriber/TestSuiteStartedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TeamCity; + +use PHPUnit\Event\TestSuite\Started; +use PHPUnit\Event\TestSuite\StartedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestSuiteStartedSubscriber extends Subscriber implements StartedSubscriber +{ + public function notify(Started $event): void + { + $this->logger()->testSuiteStarted($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/TeamCity/TeamCityLogger.php b/app/vendor/phpunit/phpunit/src/Logging/TeamCity/TeamCityLogger.php new file mode 100644 index 000000000..9a56fc383 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/TeamCity/TeamCityLogger.php @@ -0,0 +1,418 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TeamCity; + +use function assert; +use function getmypid; +use function ini_get; +use function is_a; +use function round; +use function sprintf; +use function str_replace; +use function stripos; +use PHPUnit\Event\Code\TestMethod; +use PHPUnit\Event\Code\Throwable; +use PHPUnit\Event\Event; +use PHPUnit\Event\Facade; +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Telemetry\HRTime; +use PHPUnit\Event\Test\BeforeFirstTestMethodErrored; +use PHPUnit\Event\Test\ConsideredRisky; +use PHPUnit\Event\Test\Errored; +use PHPUnit\Event\Test\Failed; +use PHPUnit\Event\Test\Finished; +use PHPUnit\Event\Test\MarkedIncomplete; +use PHPUnit\Event\Test\Prepared; +use PHPUnit\Event\Test\Skipped; +use PHPUnit\Event\TestSuite\Finished as TestSuiteFinished; +use PHPUnit\Event\TestSuite\Skipped as TestSuiteSkipped; +use PHPUnit\Event\TestSuite\Started as TestSuiteStarted; +use PHPUnit\Event\TestSuite\TestSuiteForTestClass; +use PHPUnit\Event\TestSuite\TestSuiteForTestMethodWithDataProvider; +use PHPUnit\Framework\Exception as FrameworkException; +use PHPUnit\TextUI\Output\Printer; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class TeamCityLogger +{ + private readonly Printer $printer; + private bool $isSummaryTestCountPrinted = false; + private ?HRTime $time = null; + private ?int $flowId = null; + + public function __construct(Printer $printer, Facade $facade) + { + $this->printer = $printer; + + $this->registerSubscribers($facade); + $this->setFlowId(); + } + + public function testSuiteStarted(TestSuiteStarted $event): void + { + $testSuite = $event->testSuite(); + + if (!$this->isSummaryTestCountPrinted) { + $this->isSummaryTestCountPrinted = true; + + $this->writeMessage( + 'testCount', + ['count' => $testSuite->count()], + ); + } + + $parameters = ['name' => $testSuite->name()]; + + if ($testSuite->isForTestClass()) { + assert($testSuite instanceof TestSuiteForTestClass); + + $parameters['locationHint'] = sprintf( + 'php_qn://%s::\\%s', + $testSuite->file(), + $testSuite->name(), + ); + } elseif ($testSuite->isForTestMethodWithDataProvider()) { + assert($testSuite instanceof TestSuiteForTestMethodWithDataProvider); + + $parameters['locationHint'] = sprintf( + 'php_qn://%s::\\%s', + $testSuite->file(), + $testSuite->name(), + ); + + $parameters['name'] = $testSuite->methodName(); + } + + $this->writeMessage('testSuiteStarted', $parameters); + } + + public function testSuiteFinished(TestSuiteFinished $event): void + { + $testSuite = $event->testSuite(); + + $parameters = ['name' => $testSuite->name()]; + + if ($testSuite->isForTestMethodWithDataProvider()) { + assert($testSuite instanceof TestSuiteForTestMethodWithDataProvider); + + $parameters['name'] = $testSuite->methodName(); + } + + $this->writeMessage('testSuiteFinished', $parameters); + } + + public function testPrepared(Prepared $event): void + { + $test = $event->test(); + + $parameters = [ + 'name' => $test->name(), + ]; + + if ($test->isTestMethod()) { + assert($test instanceof TestMethod); + + $parameters['locationHint'] = sprintf( + 'php_qn://%s::\\%s::%s', + $test->file(), + $test->className(), + $test->name(), + ); + } + + $this->writeMessage('testStarted', $parameters); + + $this->time = $event->telemetryInfo()->time(); + } + + /** + * @throws InvalidArgumentException + */ + public function testMarkedIncomplete(MarkedIncomplete $event): void + { + if ($this->time === null) { + // @codeCoverageIgnoreStart + $this->time = $event->telemetryInfo()->time(); + // @codeCoverageIgnoreEnd + } + + $this->writeMessage( + 'testIgnored', + [ + 'name' => $event->test()->name(), + 'message' => $event->throwable()->message(), + 'details' => $this->details($event->throwable()), + 'duration' => $this->duration($event), + ], + ); + } + + /** + * @throws InvalidArgumentException + */ + public function testSkipped(Skipped $event): void + { + if ($this->time === null) { + $this->time = $event->telemetryInfo()->time(); + } + + $parameters = [ + 'name' => $event->test()->name(), + 'message' => $event->message(), + ]; + + $parameters['duration'] = $this->duration($event); + + $this->writeMessage('testIgnored', $parameters); + } + + /** + * @throws InvalidArgumentException + */ + public function testSuiteSkipped(TestSuiteSkipped $event): void + { + if ($this->time === null) { + $this->time = $event->telemetryInfo()->time(); + } + + $parameters = [ + 'name' => $event->testSuite()->name(), + 'message' => $event->message(), + ]; + + $parameters['duration'] = $this->duration($event); + + $this->writeMessage('testIgnored', $parameters); + $this->writeMessage('testSuiteFinished', $parameters); + } + + /** + * @throws InvalidArgumentException + */ + public function beforeFirstTestMethodErrored(BeforeFirstTestMethodErrored $event): void + { + if ($this->time === null) { + $this->time = $event->telemetryInfo()->time(); + } + + $parameters = [ + 'name' => $event->testClassName(), + 'message' => $this->message($event->throwable()), + 'details' => $this->details($event->throwable()), + 'duration' => $this->duration($event), + ]; + + $this->writeMessage('testFailed', $parameters); + $this->writeMessage('testSuiteFinished', $parameters); + } + + /** + * @throws InvalidArgumentException + */ + public function testErrored(Errored $event): void + { + if ($this->time === null) { + $this->time = $event->telemetryInfo()->time(); + } + + $this->writeMessage( + 'testFailed', + [ + 'name' => $event->test()->name(), + 'message' => $this->message($event->throwable()), + 'details' => $this->details($event->throwable()), + 'duration' => $this->duration($event), + ], + ); + } + + /** + * @throws InvalidArgumentException + */ + public function testFailed(Failed $event): void + { + if ($this->time === null) { + // @codeCoverageIgnoreStart + $this->time = $event->telemetryInfo()->time(); + // @codeCoverageIgnoreEnd + } + + $parameters = [ + 'name' => $event->test()->name(), + 'message' => $this->message($event->throwable()), + 'details' => $this->details($event->throwable()), + 'duration' => $this->duration($event), + ]; + + if ($event->hasComparisonFailure()) { + $parameters['type'] = 'comparisonFailure'; + $parameters['actual'] = $event->comparisonFailure()->actual(); + $parameters['expected'] = $event->comparisonFailure()->expected(); + } + + $this->writeMessage('testFailed', $parameters); + } + + /** + * @throws InvalidArgumentException + */ + public function testConsideredRisky(ConsideredRisky $event): void + { + if ($this->time === null) { + // @codeCoverageIgnoreStart + $this->time = $event->telemetryInfo()->time(); + // @codeCoverageIgnoreEnd + } + + $this->writeMessage( + 'testFailed', + [ + 'name' => $event->test()->name(), + 'message' => $event->message(), + 'details' => '', + 'duration' => $this->duration($event), + ], + ); + } + + /** + * @throws InvalidArgumentException + */ + public function testFinished(Finished $event): void + { + $this->writeMessage( + 'testFinished', + [ + 'name' => $event->test()->name(), + 'duration' => $this->duration($event), + ], + ); + + $this->time = null; + } + + public function flush(): void + { + $this->printer->flush(); + } + + private function registerSubscribers(Facade $facade): void + { + $facade->registerSubscribers( + new TestSuiteStartedSubscriber($this), + new TestSuiteFinishedSubscriber($this), + new TestPreparedSubscriber($this), + new TestFinishedSubscriber($this), + new TestErroredSubscriber($this), + new TestFailedSubscriber($this), + new TestMarkedIncompleteSubscriber($this), + new TestSkippedSubscriber($this), + new TestSuiteSkippedSubscriber($this), + new TestConsideredRiskySubscriber($this), + new TestRunnerExecutionFinishedSubscriber($this), + new TestSuiteBeforeFirstTestMethodErroredSubscriber($this), + ); + } + + private function setFlowId(): void + { + if (stripos(ini_get('disable_functions'), 'getmypid') === false) { + $this->flowId = getmypid(); + } + } + + /** + * @param array $parameters + */ + private function writeMessage(string $eventName, array $parameters = []): void + { + $this->printer->print( + sprintf( + '##teamcity[%s', + $eventName, + ), + ); + + if ($this->flowId !== null) { + $parameters['flowId'] = $this->flowId; + } + + foreach ($parameters as $key => $value) { + $this->printer->print( + sprintf( + " %s='%s'", + $key, + $this->escape((string) $value), + ), + ); + } + + $this->printer->print("]\n"); + } + + /** + * @throws InvalidArgumentException + */ + private function duration(Event $event): int + { + if ($this->time === null) { + // @codeCoverageIgnoreStart + return 0; + // @codeCoverageIgnoreEnd + } + + return (int) round($event->telemetryInfo()->time()->duration($this->time)->asFloat() * 1000); + } + + private function escape(string $string): string + { + return str_replace( + ['|', "'", "\n", "\r", ']', '['], + ['||', "|'", '|n', '|r', '|]', '|['], + $string, + ); + } + + private function message(Throwable $throwable): string + { + if (is_a($throwable->className(), FrameworkException::class, true)) { + return $throwable->message(); + } + + $buffer = $throwable->className(); + + if ($throwable->message() !== '') { + $buffer .= ': ' . $throwable->message(); + } + + return $buffer; + } + + private function details(Throwable $throwable): string + { + $buffer = $throwable->stackTrace(); + + while ($throwable->hasPrevious()) { + $throwable = $throwable->previous(); + + $buffer .= sprintf( + "\nCaused by\n%s\n%s", + $throwable->description(), + $throwable->stackTrace(), + ); + } + + return $buffer; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/TestDox/HtmlRenderer.php b/app/vendor/phpunit/phpunit/src/Logging/TestDox/HtmlRenderer.php new file mode 100644 index 000000000..3d74d5936 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/TestDox/HtmlRenderer.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 PHPUnit\Logging\TestDox; + +use function sprintf; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class HtmlRenderer +{ + private const string PAGE_HEADER = <<<'EOT' + + + + + Test Documentation + + + +EOT; + private const string CLASS_HEADER = <<<'EOT' + +

%s

+
    + +EOT; + private const string CLASS_FOOTER = <<<'EOT' +
+EOT; + private const string PAGE_FOOTER = <<<'EOT' + + + +EOT; + + /** + * @param array $tests + */ + public function render(array $tests): string + { + $buffer = self::PAGE_HEADER; + + foreach ($tests as $prettifiedClassName => $_tests) { + $buffer .= sprintf( + self::CLASS_HEADER, + $prettifiedClassName, + ); + + foreach ($this->reduce($_tests) as $prettifiedMethodName => $outcome) { + $buffer .= sprintf( + "
  • %s
  • \n", + $outcome, + $prettifiedMethodName, + ); + } + + $buffer .= self::CLASS_FOOTER; + } + + return $buffer . self::PAGE_FOOTER; + } + + /** + * @return array + */ + private function reduce(TestResultCollection $tests): array + { + $result = []; + + foreach ($tests as $test) { + $prettifiedMethodName = $test->test()->testDox()->prettifiedMethodName(); + + if (!isset($result[$prettifiedMethodName])) { + $result[$prettifiedMethodName] = $test->status()->isSuccess() ? 'success' : 'defect'; + + continue; + } + + if ($test->status()->isSuccess()) { + continue; + } + + $result[$prettifiedMethodName] = 'defect'; + } + + return $result; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/TestDox/NamePrettifier.php b/app/vendor/phpunit/phpunit/src/Logging/TestDox/NamePrettifier.php new file mode 100644 index 000000000..99d266c6c --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/TestDox/NamePrettifier.php @@ -0,0 +1,441 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TestDox; + +use const PHP_EOL; +use function array_key_exists; +use function array_keys; +use function array_map; +use function array_pop; +use function array_values; +use function assert; +use function class_exists; +use function explode; +use function gettype; +use function implode; +use function is_bool; +use function is_float; +use function is_int; +use function is_object; +use function is_scalar; +use function method_exists; +use function preg_quote; +use function preg_replace; +use function rtrim; +use function sprintf; +use function str_contains; +use function str_ends_with; +use function str_replace; +use function str_starts_with; +use function strlen; +use function strtolower; +use function strtoupper; +use function substr; +use function trim; +use PHPUnit\Event\Code\TestMethodBuilder; +use PHPUnit\Event\Facade as EventFacade; +use PHPUnit\Framework\TestCase; +use PHPUnit\Metadata\Parser\Registry as MetadataRegistry; +use PHPUnit\Metadata\TestDox; +use PHPUnit\Metadata\TestDoxFormatter; +use PHPUnit\Util\Color; +use PHPUnit\Util\Exporter; +use PHPUnit\Util\Filter; +use ReflectionEnum; +use ReflectionMethod; +use ReflectionObject; +use Throwable; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class NamePrettifier +{ + /** + * @var array + */ + private array $strings = []; + + /** + * @var array + */ + private array $prettifiedTestCases = []; + + /** + * @var array + */ + private array $erroredFormatters = []; + + /** + * @param class-string $className + */ + public function prettifyTestClassName(string $className): string + { + if (class_exists($className)) { + $classLevelTestDox = MetadataRegistry::parser()->forClass($className)->isTestDox(); + + if ($classLevelTestDox->isNotEmpty()) { + $classLevelTestDox = $classLevelTestDox->asArray()[0]; + + assert($classLevelTestDox instanceof TestDox); + + return $classLevelTestDox->text(); + } + } + + $parts = explode('\\', $className); + $className = array_pop($parts); + + if (str_ends_with($className, 'Test')) { + $className = substr($className, 0, strlen($className) - strlen('Test')); + } + + if (str_starts_with($className, 'Tests')) { + $className = substr($className, strlen('Tests')); + } elseif (str_starts_with($className, 'Test')) { + $className = substr($className, strlen('Test')); + } + + if ($className === '') { + $className = 'UnnamedTests'; + } + + if ($parts !== []) { + $parts[] = $className; + $fullyQualifiedName = implode('\\', $parts); + } else { + $fullyQualifiedName = $className; + } + + $result = preg_replace('/(?<=[[:lower:]])(?=[[:upper:]])/u', ' ', $className); + + if ($fullyQualifiedName !== $className) { + return $result . ' (' . $fullyQualifiedName . ')'; + } + + return $result; + } + + // NOTE: this method is on a hot path and very performance sensitive. change with care. + public function prettifyTestMethodName(string $name): string + { + if ($name === '') { + return ''; + } + + $string = rtrim($name, '0123456789'); + + if (array_key_exists($string, $this->strings)) { + $name = $string; + } elseif ($string === $name) { + $this->strings[$string] = 1; + } + + if (str_starts_with($name, 'test_')) { + $name = substr($name, 5); + } elseif (str_starts_with($name, 'test')) { + $name = substr($name, 4); + } + + if ($name === '') { + return ''; + } + + $name[0] = strtoupper($name[0]); + + $noUnderscore = str_replace('_', ' ', $name); + + if ($noUnderscore !== $name) { + return trim($noUnderscore); + } + + $wasNumeric = false; + + $buffer = ''; + + $len = strlen($name); + + for ($i = 0; $i < $len; $i++) { + if ($i > 0 && $name[$i] >= 'A' && $name[$i] <= 'Z') { + $buffer .= ' ' . strtolower($name[$i]); + } else { + $isNumeric = $name[$i] >= '0' && $name[$i] <= '9'; + + if (!$wasNumeric && $isNumeric) { + $buffer .= ' '; + $wasNumeric = true; + } + + if ($wasNumeric && !$isNumeric) { + $wasNumeric = false; + } + + $buffer .= $name[$i]; + } + } + + return trim($buffer); + } + + public function prettifyTestCase(TestCase $test, bool $colorize): string + { + $key = $test::class . '#' . $test->name(); + + if ($test->usesDataProvider()) { + $key .= '#' . $test->dataName(); + } + + if ($colorize) { + $key .= '#colorize'; + } + + if (isset($this->prettifiedTestCases[$key])) { + return $this->prettifiedTestCases[$key]; + } + + $metadataCollection = MetadataRegistry::parser()->forMethod($test::class, $test->name()); + $testDox = $metadataCollection->isTestDox()->isMethodLevel(); + $callback = $metadataCollection->isTestDoxFormatter(); + $isCustomized = false; + + if ($testDox->isNotEmpty()) { + $testDox = $testDox->asArray()[0]; + + assert($testDox instanceof TestDox); + + [$result, $isCustomized] = $this->processTestDox($test, $testDox, $colorize); + } elseif ($callback->isNotEmpty()) { + $callback = $callback->asArray()[0]; + + assert($callback instanceof TestDoxFormatter); + + [$result, $isCustomized] = $this->processTestDoxFormatter($test, $callback); + } else { + $result = $this->prettifyTestMethodName($test->name()); + } + + if (!$isCustomized && $test->usesDataProvider()) { + $result .= $this->prettifyDataSet($test, $colorize); + } + + $this->prettifiedTestCases[$key] = $result; + + return $result; + } + + public function prettifyDataSet(TestCase $test, bool $colorize): string + { + if (!$colorize) { + return $test->dataSetAsString(); + } + + if (is_int($test->dataName())) { + return Color::dim(' with data set ') . Color::colorize('fg-cyan', (string) $test->dataName()); + } + + return Color::dim(' with ') . Color::colorize('fg-cyan', Color::visualizeWhitespace($test->dataName())); + } + + /** + * @return array + */ + private function mapTestMethodParameterNamesToProvidedDataValues(TestCase $test, bool $colorize): array + { + assert(method_exists($test, $test->name())); + + /** @noinspection PhpUnhandledExceptionInspection */ + $reflector = new ReflectionMethod($test::class, $test->name()); + + $providedData = []; + $providedDataValues = array_values($test->providedData()); + $i = 0; + + $providedData['$_dataName'] = $test->dataName(); + + foreach ($reflector->getParameters() as $parameter) { + if (!array_key_exists($i, $providedDataValues) && $parameter->isDefaultValueAvailable()) { + $providedDataValues[$i] = $parameter->getDefaultValue(); + } + + $value = $providedDataValues[$i++] ?? null; + + if (is_object($value)) { + $value = $this->objectToString($value); + } + + if (!is_scalar($value)) { + $value = gettype($value); + + if ($value === 'NULL') { + $value = 'null'; + } + } + + if (is_bool($value) || is_int($value) || is_float($value)) { + $value = Exporter::export($value); + } + + if ($value === '') { + if ($colorize) { + $value = Color::colorize('dim,underlined', 'empty'); + } else { + $value = "''"; + } + } + + $providedData['$' . $parameter->getName()] = str_replace('$', '\\$', $value); + } + + if ($colorize) { + $providedData = array_map( + static fn (mixed $value) => Color::colorize('fg-cyan', Color::visualizeWhitespace((string) $value, true)), + $providedData, + ); + } + + return $providedData; + } + + /** + * @return non-empty-string + */ + private function objectToString(object $value): string + { + $reflector = new ReflectionObject($value); + + if ($reflector->isEnum()) { + $enumReflector = new ReflectionEnum($value); + + if ($enumReflector->isBacked()) { + return (string) $value->value; + } + + return $value->name; + } + + if ($reflector->hasMethod('__toString')) { + return $value->__toString(); + } + + return $value::class; + } + + /** + * @return array{0: string, 1: bool} + */ + private function processTestDox(TestCase $test, TestDox $testDox, bool $colorize): array + { + $placeholdersUsed = false; + + $result = $testDox->text(); + + if (str_contains($result, '$')) { + $annotation = $result; + $providedData = $this->mapTestMethodParameterNamesToProvidedDataValues($test, $colorize); + + $variables = array_map( + static fn (string $variable): string => sprintf( + '/%s(?=\b)/', + preg_quote($variable, '/'), + ), + array_keys($providedData), + ); + + $result = preg_replace($variables, $providedData, $annotation); + + $placeholdersUsed = true; + } + + return [$result, $placeholdersUsed]; + } + + /** + * @return array{0: string, 1: bool} + */ + private function processTestDoxFormatter(TestCase $test, TestDoxFormatter $formatter): array + { + $className = $formatter->className(); + $methodName = $formatter->methodName(); + $formatterIdentifier = $className . '::' . $methodName; + + if (isset($this->erroredFormatters[$formatterIdentifier])) { + return [$this->prettifyTestMethodName($test->name()), false]; + } + + if (!method_exists($className, $methodName)) { + EventFacade::emitter()->testTriggeredPhpunitError( + TestMethodBuilder::fromTestCase($test, false), + sprintf( + 'Method %s::%s() cannot be used as a TestDox formatter because it does not exist', + $className, + $methodName, + ), + ); + + $this->erroredFormatters[$formatterIdentifier] = true; + + return [$this->prettifyTestMethodName($test->name()), false]; + } + + $reflector = new ReflectionMethod($className, $methodName); + + if (!$reflector->isPublic()) { + EventFacade::emitter()->testTriggeredPhpunitError( + TestMethodBuilder::fromTestCase($test, false), + sprintf( + 'Method %s::%s() cannot be used as a TestDox formatter because it is not public', + $className, + $methodName, + ), + ); + + $this->erroredFormatters[$formatterIdentifier] = true; + + return [$this->prettifyTestMethodName($test->name()), false]; + } + + if (!$reflector->isStatic()) { + EventFacade::emitter()->testTriggeredPhpunitError( + TestMethodBuilder::fromTestCase($test, false), + sprintf( + 'Method %s::%s() cannot be used as a TestDox formatter because it is not static', + $className, + $methodName, + ), + ); + + $this->erroredFormatters[$formatterIdentifier] = true; + + return [$this->prettifyTestMethodName($test->name()), false]; + } + + try { + return [$reflector->invokeArgs(null, array_values($test->providedData())), true]; + } catch (Throwable $t) { + EventFacade::emitter()->testTriggeredPhpunitError( + TestMethodBuilder::fromTestCase($test, false), + sprintf( + 'TestDox formatter %s::%s() triggered an error: %s%s%s', + $className, + $methodName, + $t->getMessage(), + PHP_EOL, + Filter::stackTraceFromThrowableAsString($t), + ), + ); + + $this->erroredFormatters[$formatterIdentifier] = true; + + return [$this->prettifyTestMethodName($test->name()), false]; + } + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/TestDox/PlainTextRenderer.php b/app/vendor/phpunit/phpunit/src/Logging/TestDox/PlainTextRenderer.php new file mode 100644 index 000000000..db591ca9f --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/TestDox/PlainTextRenderer.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TestDox; + +use function sprintf; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PlainTextRenderer +{ + /** + * @param array $tests + */ + public function render(array $tests): string + { + $buffer = ''; + + foreach ($tests as $prettifiedClassName => $_tests) { + $buffer .= $prettifiedClassName . "\n"; + + foreach ($this->reduce($_tests) as $prettifiedMethodName => $outcome) { + $buffer .= sprintf( + ' [%s] %s' . "\n", + $outcome, + $prettifiedMethodName, + ); + } + + $buffer .= "\n"; + } + + return $buffer; + } + + /** + * @return array + */ + private function reduce(TestResultCollection $tests): array + { + $result = []; + + foreach ($tests as $test) { + $prettifiedMethodName = $test->test()->testDox()->prettifiedMethodName(); + + $success = true; + + if ($test->status()->isError() || + $test->status()->isFailure() || + $test->status()->isIncomplete() || + $test->status()->isSkipped()) { + $success = false; + } + + if (!isset($result[$prettifiedMethodName])) { + $result[$prettifiedMethodName] = $success ? 'x' : ' '; + + continue; + } + + if ($success) { + continue; + } + + $result[$prettifiedMethodName] = ' '; + } + + return $result; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/Subscriber/Subscriber.php b/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/Subscriber/Subscriber.php new file mode 100644 index 000000000..41fc465a1 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/Subscriber/Subscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TestDox; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +abstract readonly class Subscriber +{ + private TestResultCollector $collector; + + public function __construct(TestResultCollector $collector) + { + $this->collector = $collector; + } + + protected function collector(): TestResultCollector + { + return $this->collector; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/Subscriber/TestConsideredRiskySubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/Subscriber/TestConsideredRiskySubscriber.php new file mode 100644 index 000000000..150a48635 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/Subscriber/TestConsideredRiskySubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TestDox; + +use PHPUnit\Event\Test\ConsideredRisky; +use PHPUnit\Event\Test\ConsideredRiskySubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestConsideredRiskySubscriber extends Subscriber implements ConsideredRiskySubscriber +{ + public function notify(ConsideredRisky $event): void + { + $this->collector()->testConsideredRisky($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/Subscriber/TestErroredSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/Subscriber/TestErroredSubscriber.php new file mode 100644 index 000000000..b210ffa3c --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/Subscriber/TestErroredSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TestDox; + +use PHPUnit\Event\Test\Errored; +use PHPUnit\Event\Test\ErroredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestErroredSubscriber extends Subscriber implements ErroredSubscriber +{ + public function notify(Errored $event): void + { + $this->collector()->testErrored($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/Subscriber/TestFailedSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/Subscriber/TestFailedSubscriber.php new file mode 100644 index 000000000..b776227c3 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/Subscriber/TestFailedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TestDox; + +use PHPUnit\Event\Test\Failed; +use PHPUnit\Event\Test\FailedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestFailedSubscriber extends Subscriber implements FailedSubscriber +{ + public function notify(Failed $event): void + { + $this->collector()->testFailed($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/Subscriber/TestFinishedSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/Subscriber/TestFinishedSubscriber.php new file mode 100644 index 000000000..14ddea33e --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/Subscriber/TestFinishedSubscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TestDox; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\Finished; +use PHPUnit\Event\Test\FinishedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestFinishedSubscriber extends Subscriber implements FinishedSubscriber +{ + /** + * @throws InvalidArgumentException + */ + public function notify(Finished $event): void + { + $this->collector()->testFinished($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/Subscriber/TestMarkedIncompleteSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/Subscriber/TestMarkedIncompleteSubscriber.php new file mode 100644 index 000000000..7e21545cb --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/Subscriber/TestMarkedIncompleteSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TestDox; + +use PHPUnit\Event\Test\MarkedIncomplete; +use PHPUnit\Event\Test\MarkedIncompleteSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestMarkedIncompleteSubscriber extends Subscriber implements MarkedIncompleteSubscriber +{ + public function notify(MarkedIncomplete $event): void + { + $this->collector()->testMarkedIncomplete($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/Subscriber/TestPassedSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/Subscriber/TestPassedSubscriber.php new file mode 100644 index 000000000..1eb1a5777 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/Subscriber/TestPassedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TestDox; + +use PHPUnit\Event\Test\Passed; +use PHPUnit\Event\Test\PassedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestPassedSubscriber extends Subscriber implements PassedSubscriber +{ + public function notify(Passed $event): void + { + $this->collector()->testPassed($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/Subscriber/TestPreparedSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/Subscriber/TestPreparedSubscriber.php new file mode 100644 index 000000000..cdaddb09a --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/Subscriber/TestPreparedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TestDox; + +use PHPUnit\Event\Test\Prepared; +use PHPUnit\Event\Test\PreparedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestPreparedSubscriber extends Subscriber implements PreparedSubscriber +{ + public function notify(Prepared $event): void + { + $this->collector()->testPrepared($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/Subscriber/TestSkippedSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/Subscriber/TestSkippedSubscriber.php new file mode 100644 index 000000000..76d7e3bb0 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/Subscriber/TestSkippedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TestDox; + +use PHPUnit\Event\Test\Skipped; +use PHPUnit\Event\Test\SkippedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestSkippedSubscriber extends Subscriber implements SkippedSubscriber +{ + public function notify(Skipped $event): void + { + $this->collector()->testSkipped($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/Subscriber/TestTriggeredDeprecationSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/Subscriber/TestTriggeredDeprecationSubscriber.php new file mode 100644 index 000000000..8e0802966 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/Subscriber/TestTriggeredDeprecationSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TestDox; + +use PHPUnit\Event\Test\DeprecationTriggered; +use PHPUnit\Event\Test\DeprecationTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredDeprecationSubscriber extends Subscriber implements DeprecationTriggeredSubscriber +{ + public function notify(DeprecationTriggered $event): void + { + $this->collector()->testTriggeredDeprecation($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/Subscriber/TestTriggeredNoticeSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/Subscriber/TestTriggeredNoticeSubscriber.php new file mode 100644 index 000000000..18eff3a9d --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/Subscriber/TestTriggeredNoticeSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TestDox; + +use PHPUnit\Event\Test\NoticeTriggered; +use PHPUnit\Event\Test\NoticeTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredNoticeSubscriber extends Subscriber implements NoticeTriggeredSubscriber +{ + public function notify(NoticeTriggered $event): void + { + $this->collector()->testTriggeredNotice($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/Subscriber/TestTriggeredPhpDeprecationSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/Subscriber/TestTriggeredPhpDeprecationSubscriber.php new file mode 100644 index 000000000..082bb3c33 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/Subscriber/TestTriggeredPhpDeprecationSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TestDox; + +use PHPUnit\Event\Test\PhpDeprecationTriggered; +use PHPUnit\Event\Test\PhpDeprecationTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredPhpDeprecationSubscriber extends Subscriber implements PhpDeprecationTriggeredSubscriber +{ + public function notify(PhpDeprecationTriggered $event): void + { + $this->collector()->testTriggeredPhpDeprecation($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/Subscriber/TestTriggeredPhpNoticeSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/Subscriber/TestTriggeredPhpNoticeSubscriber.php new file mode 100644 index 000000000..b743b64a3 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/Subscriber/TestTriggeredPhpNoticeSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TestDox; + +use PHPUnit\Event\Test\PhpNoticeTriggered; +use PHPUnit\Event\Test\PhpNoticeTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredPhpNoticeSubscriber extends Subscriber implements PhpNoticeTriggeredSubscriber +{ + public function notify(PhpNoticeTriggered $event): void + { + $this->collector()->testTriggeredPhpNotice($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/Subscriber/TestTriggeredPhpWarningSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/Subscriber/TestTriggeredPhpWarningSubscriber.php new file mode 100644 index 000000000..4e9c6ac1a --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/Subscriber/TestTriggeredPhpWarningSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TestDox; + +use PHPUnit\Event\Test\PhpWarningTriggered; +use PHPUnit\Event\Test\PhpWarningTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredPhpWarningSubscriber extends Subscriber implements PhpWarningTriggeredSubscriber +{ + public function notify(PhpWarningTriggered $event): void + { + $this->collector()->testTriggeredPhpWarning($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/Subscriber/TestTriggeredPhpunitDeprecationSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/Subscriber/TestTriggeredPhpunitDeprecationSubscriber.php new file mode 100644 index 000000000..4423ff98c --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/Subscriber/TestTriggeredPhpunitDeprecationSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TestDox; + +use PHPUnit\Event\Test\PhpunitDeprecationTriggered; +use PHPUnit\Event\Test\PhpunitDeprecationTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredPhpunitDeprecationSubscriber extends Subscriber implements PhpunitDeprecationTriggeredSubscriber +{ + public function notify(PhpunitDeprecationTriggered $event): void + { + $this->collector()->testTriggeredPhpunitDeprecation($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/Subscriber/TestTriggeredPhpunitErrorSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/Subscriber/TestTriggeredPhpunitErrorSubscriber.php new file mode 100644 index 000000000..e4e90f18d --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/Subscriber/TestTriggeredPhpunitErrorSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TestDox; + +use PHPUnit\Event\Test\PhpunitErrorTriggered; +use PHPUnit\Event\Test\PhpunitErrorTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredPhpunitErrorSubscriber extends Subscriber implements PhpunitErrorTriggeredSubscriber +{ + public function notify(PhpunitErrorTriggered $event): void + { + $this->collector()->testTriggeredPhpunitError($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/Subscriber/TestTriggeredPhpunitWarningSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/Subscriber/TestTriggeredPhpunitWarningSubscriber.php new file mode 100644 index 000000000..72cb8af22 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/Subscriber/TestTriggeredPhpunitWarningSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TestDox; + +use PHPUnit\Event\Test\PhpunitWarningTriggered; +use PHPUnit\Event\Test\PhpunitWarningTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredPhpunitWarningSubscriber extends Subscriber implements PhpunitWarningTriggeredSubscriber +{ + public function notify(PhpunitWarningTriggered $event): void + { + $this->collector()->testTriggeredPhpunitWarning($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/Subscriber/TestTriggeredWarningSubscriber.php b/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/Subscriber/TestTriggeredWarningSubscriber.php new file mode 100644 index 000000000..d44f4005c --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/Subscriber/TestTriggeredWarningSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TestDox; + +use PHPUnit\Event\Test\WarningTriggered; +use PHPUnit\Event\Test\WarningTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredWarningSubscriber extends Subscriber implements WarningTriggeredSubscriber +{ + public function notify(WarningTriggered $event): void + { + $this->collector()->testTriggeredWarning($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/TestResult.php b/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/TestResult.php new file mode 100644 index 000000000..2648a0dbc --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/TestResult.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 PHPUnit\Logging\TestDox; + +use PHPUnit\Event\Code\TestMethod; +use PHPUnit\Event\Code\Throwable; +use PHPUnit\Framework\TestStatus\TestStatus; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestResult +{ + private TestMethod $test; + private TestStatus $status; + private ?Throwable $throwable; + + public function __construct(TestMethod $test, TestStatus $status, ?Throwable $throwable) + { + $this->test = $test; + $this->status = $status; + $this->throwable = $throwable; + } + + public function test(): TestMethod + { + return $this->test; + } + + public function status(): TestStatus + { + return $this->status; + } + + /** + * @phpstan-assert-if-true !null $this->throwable + */ + public function hasThrowable(): bool + { + return $this->throwable !== null; + } + + public function throwable(): ?Throwable + { + return $this->throwable; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/TestResultCollection.php b/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/TestResultCollection.php new file mode 100644 index 000000000..5c4a4d644 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/TestResultCollection.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 PHPUnit\Logging\TestDox; + +use IteratorAggregate; + +/** + * @template-implements IteratorAggregate + * + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestResultCollection implements IteratorAggregate +{ + /** + * @var list + */ + private array $testResults; + + /** + * @param list $testResults + */ + public static function fromArray(array $testResults): self + { + return new self(...$testResults); + } + + private function __construct(TestResult ...$testResults) + { + $this->testResults = $testResults; + } + + /** + * @return list + */ + public function asArray(): array + { + return $this->testResults; + } + + public function getIterator(): TestResultCollectionIterator + { + return new TestResultCollectionIterator($this); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/TestResultCollectionIterator.php b/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/TestResultCollectionIterator.php new file mode 100644 index 000000000..cf8ae7ac8 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/TestResultCollectionIterator.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TestDox; + +use function count; +use Iterator; + +/** + * @template-implements Iterator + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class TestResultCollectionIterator implements Iterator +{ + /** + * @var list + */ + private readonly array $testResults; + private int $position = 0; + + public function __construct(TestResultCollection $testResults) + { + $this->testResults = $testResults->asArray(); + } + + public function rewind(): void + { + $this->position = 0; + } + + public function valid(): bool + { + return $this->position < count($this->testResults); + } + + public function key(): int + { + return $this->position; + } + + public function current(): TestResult + { + return $this->testResults[$this->position]; + } + + public function next(): void + { + $this->position++; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/TestResultCollector.php b/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/TestResultCollector.php new file mode 100644 index 000000000..bdf03480f --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Logging/TestDox/TestResult/TestResultCollector.php @@ -0,0 +1,383 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Logging\TestDox; + +use function array_merge; +use function assert; +use function is_subclass_of; +use function ksort; +use function uksort; +use function usort; +use PHPUnit\Event\Code\TestMethod; +use PHPUnit\Event\Code\Throwable; +use PHPUnit\Event\Facade; +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\ConsideredRisky; +use PHPUnit\Event\Test\DeprecationTriggered; +use PHPUnit\Event\Test\Errored; +use PHPUnit\Event\Test\Failed; +use PHPUnit\Event\Test\Finished; +use PHPUnit\Event\Test\MarkedIncomplete; +use PHPUnit\Event\Test\NoticeTriggered; +use PHPUnit\Event\Test\Passed; +use PHPUnit\Event\Test\PhpDeprecationTriggered; +use PHPUnit\Event\Test\PhpNoticeTriggered; +use PHPUnit\Event\Test\PhpunitDeprecationTriggered; +use PHPUnit\Event\Test\PhpunitErrorTriggered; +use PHPUnit\Event\Test\PhpunitWarningTriggered; +use PHPUnit\Event\Test\PhpWarningTriggered; +use PHPUnit\Event\Test\Prepared; +use PHPUnit\Event\Test\Skipped; +use PHPUnit\Event\Test\WarningTriggered; +use PHPUnit\Framework\TestStatus\TestStatus; +use PHPUnit\Logging\TestDox\TestResult as TestDoxTestMethod; +use PHPUnit\TestRunner\IssueFilter; +use ReflectionMethod; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class TestResultCollector +{ + private readonly IssueFilter $issueFilter; + + /** + * @var array> + */ + private array $tests = []; + private ?TestStatus $status = null; + private ?Throwable $throwable = null; + private bool $prepared = false; + + public function __construct(Facade $facade, IssueFilter $issueFilter) + { + $this->issueFilter = $issueFilter; + + $this->registerSubscribers($facade); + } + + /** + * @return array + */ + public function testMethodsGroupedByClass(): array + { + $result = []; + + foreach ($this->tests as $prettifiedClassName => $tests) { + $testsByDeclaringClass = []; + + foreach ($tests as $test) { + $declaringClassName = (new ReflectionMethod($test->test()->className(), $test->test()->methodName()))->getDeclaringClass()->getName(); + + if (!isset($testsByDeclaringClass[$declaringClassName])) { + $testsByDeclaringClass[$declaringClassName] = []; + } + + $testsByDeclaringClass[$declaringClassName][] = $test; + } + + foreach ($testsByDeclaringClass as $declaringClassName) { + usort( + $declaringClassName, + static function (TestDoxTestMethod $a, TestDoxTestMethod $b): int + { + return $a->test()->line() <=> $b->test()->line(); + }, + ); + } + + uksort( + $testsByDeclaringClass, + /** + * @param class-string $a + * @param class-string $b + */ + static function (string $a, string $b): int + { + if (is_subclass_of($b, $a)) { + return -1; + } + + if (is_subclass_of($a, $b)) { + return 1; + } + + return 0; + }, + ); + + $tests = []; + + foreach ($testsByDeclaringClass as $_tests) { + $tests = array_merge($tests, $_tests); + } + + $result[$prettifiedClassName] = TestResultCollection::fromArray($tests); + } + + ksort($result); + + return $result; + } + + public function testPrepared(Prepared $event): void + { + if (!$event->test()->isTestMethod()) { + return; + } + + $this->status = TestStatus::unknown(); + $this->throwable = null; + $this->prepared = true; + } + + public function testErrored(Errored $event): void + { + if (!$event->test()->isTestMethod()) { + return; + } + + $this->status = TestStatus::error($event->throwable()->message()); + $this->throwable = $event->throwable(); + + if (!$this->prepared) { + $test = $event->test(); + + assert($test instanceof TestMethod); + + $this->process($test); + } + } + + public function testFailed(Failed $event): void + { + if (!$event->test()->isTestMethod()) { + return; + } + + $this->status = TestStatus::failure($event->throwable()->message()); + $this->throwable = $event->throwable(); + } + + public function testPassed(Passed $event): void + { + if (!$event->test()->isTestMethod()) { + return; + } + + $this->updateTestStatus(TestStatus::success()); + } + + public function testSkipped(Skipped $event): void + { + if (!$event->test()->isTestMethod()) { + return; + } + + $this->updateTestStatus(TestStatus::skipped($event->message())); + } + + public function testMarkedIncomplete(MarkedIncomplete $event): void + { + if (!$event->test()->isTestMethod()) { + return; + } + + $this->updateTestStatus(TestStatus::incomplete($event->throwable()->message())); + + $this->throwable = $event->throwable(); + } + + public function testConsideredRisky(ConsideredRisky $event): void + { + if (!$event->test()->isTestMethod()) { + return; + } + + $this->updateTestStatus(TestStatus::risky()); + } + + public function testTriggeredDeprecation(DeprecationTriggered $event): void + { + if (!$this->issueFilter->shouldBeProcessed($event, true)) { + return; + } + + if ($event->ignoredByBaseline()) { + return; + } + + $this->updateTestStatus(TestStatus::deprecation()); + } + + public function testTriggeredNotice(NoticeTriggered $event): void + { + if (!$this->issueFilter->shouldBeProcessed($event, true)) { + return; + } + + if ($event->ignoredByBaseline()) { + return; + } + + $this->updateTestStatus(TestStatus::notice()); + } + + public function testTriggeredWarning(WarningTriggered $event): void + { + if (!$this->issueFilter->shouldBeProcessed($event, true)) { + return; + } + + if ($event->ignoredByBaseline()) { + return; + } + + $this->updateTestStatus(TestStatus::warning()); + } + + public function testTriggeredPhpDeprecation(PhpDeprecationTriggered $event): void + { + if (!$this->issueFilter->shouldBeProcessed($event, true)) { + return; + } + + if ($event->ignoredByBaseline()) { + return; + } + + $this->updateTestStatus(TestStatus::deprecation()); + } + + public function testTriggeredPhpNotice(PhpNoticeTriggered $event): void + { + if (!$this->issueFilter->shouldBeProcessed($event, true)) { + return; + } + + if ($event->ignoredByBaseline()) { + return; + } + + $this->updateTestStatus(TestStatus::notice()); + } + + public function testTriggeredPhpWarning(PhpWarningTriggered $event): void + { + if (!$this->issueFilter->shouldBeProcessed($event, true)) { + return; + } + + if ($event->ignoredByBaseline()) { + return; + } + + $this->updateTestStatus(TestStatus::warning()); + } + + public function testTriggeredPhpunitDeprecation(PhpunitDeprecationTriggered $event): void + { + if (!$event->test()->isTestMethod()) { + return; + } + + $this->updateTestStatus(TestStatus::deprecation()); + } + + public function testTriggeredPhpunitError(PhpunitErrorTriggered $event): void + { + if (!$event->test()->isTestMethod()) { + return; + } + + $this->updateTestStatus(TestStatus::error()); + } + + public function testTriggeredPhpunitWarning(PhpunitWarningTriggered $event): void + { + if (!$event->test()->isTestMethod()) { + return; + } + + if ($event->ignoredByTest()) { + return; + } + + $this->updateTestStatus(TestStatus::warning()); + } + + /** + * @throws InvalidArgumentException + */ + public function testFinished(Finished $event): void + { + if (!$event->test()->isTestMethod()) { + return; + } + + $test = $event->test(); + + assert($test instanceof TestMethod); + + $this->process($test); + + $this->status = null; + $this->throwable = null; + $this->prepared = false; + } + + private function registerSubscribers(Facade $facade): void + { + $facade->registerSubscribers( + new TestConsideredRiskySubscriber($this), + new TestErroredSubscriber($this), + new TestFailedSubscriber($this), + new TestFinishedSubscriber($this), + new TestMarkedIncompleteSubscriber($this), + new TestPassedSubscriber($this), + new TestPreparedSubscriber($this), + new TestSkippedSubscriber($this), + new TestTriggeredDeprecationSubscriber($this), + new TestTriggeredNoticeSubscriber($this), + new TestTriggeredPhpDeprecationSubscriber($this), + new TestTriggeredPhpNoticeSubscriber($this), + new TestTriggeredPhpunitDeprecationSubscriber($this), + new TestTriggeredPhpunitErrorSubscriber($this), + new TestTriggeredPhpunitWarningSubscriber($this), + new TestTriggeredPhpWarningSubscriber($this), + new TestTriggeredWarningSubscriber($this), + ); + } + + private function updateTestStatus(TestStatus $status): void + { + if ($this->status !== null && + $this->status->isMoreImportantThan($status)) { + return; + } + + $this->status = $status; + } + + private function process(TestMethod $test): void + { + if (!isset($this->tests[$test->testDox()->prettifiedClassName()])) { + $this->tests[$test->testDox()->prettifiedClassName()] = []; + } + + $this->tests[$test->testDox()->prettifiedClassName()][] = new TestDoxTestMethod( + $test, + $this->status, + $this->throwable, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/After.php b/app/vendor/phpunit/phpunit/src/Metadata/After.php new file mode 100644 index 000000000..144136390 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/After.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 PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class After extends Metadata +{ + private int $priority; + + /** + * @param int<0, 1> $level + */ + protected function __construct(int $level, int $priority) + { + parent::__construct($level); + + $this->priority = $priority; + } + + public function isAfter(): true + { + return true; + } + + public function priority(): int + { + return $this->priority; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/AfterClass.php b/app/vendor/phpunit/phpunit/src/Metadata/AfterClass.php new file mode 100644 index 000000000..7dcb96fd7 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/AfterClass.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 PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class AfterClass extends Metadata +{ + private int $priority; + + /** + * @param int<0, 1> $level + */ + protected function __construct(int $level, int $priority) + { + parent::__construct($level); + + $this->priority = $priority; + } + + public function isAfterClass(): true + { + return true; + } + + public function priority(): int + { + return $this->priority; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/Api/CodeCoverage.php b/app/vendor/phpunit/phpunit/src/Metadata/Api/CodeCoverage.php new file mode 100644 index 000000000..f6d0a9cec --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/Api/CodeCoverage.php @@ -0,0 +1,171 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata\Api; + +use function assert; +use PHPUnit\Event\Facade as EventFacade; +use PHPUnit\Framework\TestCase; +use PHPUnit\Metadata\CoversClass; +use PHPUnit\Metadata\CoversClassesThatExtendClass; +use PHPUnit\Metadata\CoversClassesThatImplementInterface; +use PHPUnit\Metadata\CoversFunction; +use PHPUnit\Metadata\CoversMethod; +use PHPUnit\Metadata\CoversNamespace; +use PHPUnit\Metadata\CoversTrait; +use PHPUnit\Metadata\Parser\Registry; +use PHPUnit\Metadata\UsesClass; +use PHPUnit\Metadata\UsesClassesThatExtendClass; +use PHPUnit\Metadata\UsesClassesThatImplementInterface; +use PHPUnit\Metadata\UsesFunction; +use PHPUnit\Metadata\UsesMethod; +use PHPUnit\Metadata\UsesNamespace; +use PHPUnit\Metadata\UsesTrait; +use SebastianBergmann\CodeCoverage\Test\Target\Target; +use SebastianBergmann\CodeCoverage\Test\Target\TargetCollection; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class CodeCoverage +{ + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public function coversTargets(string $className, string $methodName): TargetCollection + { + $targets = []; + + foreach (Registry::parser()->forClassAndMethod($className, $methodName) as $metadata) { + if ($metadata->isCoversNamespace()) { + assert($metadata instanceof CoversNamespace); + + $targets[] = Target::forNamespace($metadata->namespace()); + } + + if ($metadata->isCoversClass()) { + assert($metadata instanceof CoversClass); + + $targets[] = Target::forClass($metadata->className()); + } + + if ($metadata->isCoversClassesThatExtendClass()) { + assert($metadata instanceof CoversClassesThatExtendClass); + + $targets[] = Target::forClassesThatExtendClass($metadata->className()); + } + + if ($metadata->isCoversClassesThatImplementInterface()) { + assert($metadata instanceof CoversClassesThatImplementInterface); + + $targets[] = Target::forClassesThatImplementInterface($metadata->interfaceName()); + } + + if ($metadata->isCoversMethod()) { + assert($metadata instanceof CoversMethod); + + $targets[] = Target::forMethod($metadata->className(), $metadata->methodName()); + } + + if ($metadata->isCoversFunction()) { + assert($metadata instanceof CoversFunction); + + $targets[] = Target::forFunction($metadata->functionName()); + } + + if ($metadata->isCoversTrait()) { + assert($metadata instanceof CoversTrait); + + $targets[] = Target::forTrait($metadata->traitName()); + } + } + + return TargetCollection::fromArray($targets); + } + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public function usesTargets(string $className, string $methodName): TargetCollection + { + $targets = []; + + foreach (Registry::parser()->forClassAndMethod($className, $methodName) as $metadata) { + if ($metadata->isUsesNamespace()) { + assert($metadata instanceof UsesNamespace); + + $targets[] = Target::forNamespace($metadata->namespace()); + } + + if ($metadata->isUsesClass()) { + assert($metadata instanceof UsesClass); + + $targets[] = Target::forClass($metadata->className()); + } + + if ($metadata->isUsesClassesThatExtendClass()) { + assert($metadata instanceof UsesClassesThatExtendClass); + + $targets[] = Target::forClassesThatExtendClass($metadata->className()); + } + + if ($metadata->isUsesClassesThatImplementInterface()) { + assert($metadata instanceof UsesClassesThatImplementInterface); + + $targets[] = Target::forClassesThatImplementInterface($metadata->interfaceName()); + } + + if ($metadata->isUsesMethod()) { + assert($metadata instanceof UsesMethod); + + $targets[] = Target::forMethod($metadata->className(), $metadata->methodName()); + } + + if ($metadata->isUsesFunction()) { + assert($metadata instanceof UsesFunction); + + $targets[] = Target::forFunction($metadata->functionName()); + } + + if ($metadata->isUsesTrait()) { + assert($metadata instanceof UsesTrait); + + $targets[] = Target::forTrait($metadata->traitName()); + } + } + + return TargetCollection::fromArray($targets); + } + + public function shouldCodeCoverageBeCollectedFor(TestCase $test): bool + { + $className = $test::class; + $methodName = $test->name(); + $parser = Registry::parser(); + + if ($parser->forMethod($className, $methodName)->isCoversNothing()->isNotEmpty()) { + EventFacade::emitter()->testTriggeredPhpunitDeprecation( + $test->valueObjectForEvents(), + 'Using #[CoversNothing] on a test method is deprecated, support for this will be removed in PHPUnit 13', + ); + + return false; + } + + if ($parser->forClass($className)->isCoversNothing()->isNotEmpty()) { + return false; + } + + return true; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/Api/DataProvider.php b/app/vendor/phpunit/phpunit/src/Metadata/Api/DataProvider.php new file mode 100644 index 000000000..a8d6d3c80 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/Api/DataProvider.php @@ -0,0 +1,313 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata\Api; + +use function array_key_exists; +use function assert; +use function count; +use function get_debug_type; +use function is_a; +use function is_array; +use function is_int; +use function is_string; +use function sprintf; +use function str_starts_with; +use PHPUnit\Event; +use PHPUnit\Event\Code\TestMethod; +use PHPUnit\Framework\InvalidDataProviderException; +use PHPUnit\Framework\TestCase; +use PHPUnit\Metadata\DataProvider as DataProviderMetadata; +use PHPUnit\Metadata\MetadataCollection; +use PHPUnit\Metadata\Parser\Registry as MetadataRegistry; +use PHPUnit\Metadata\TestWith; +use ReflectionMethod; +use Throwable; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class DataProvider +{ + /** + * @param class-string $className + * @param non-empty-string $methodName + * + * @throws InvalidDataProviderException + * + * @return ?array + */ + public function providedData(string $className, string $methodName): ?array + { + $metadataCollection = MetadataRegistry::parser()->forMethod($className, $methodName); + $dataProvider = $metadataCollection->isDataProvider(); + $testWith = $metadataCollection->isTestWith(); + + if ($dataProvider->isEmpty() && $testWith->isEmpty()) { + return null; + } + + if ($dataProvider->isNotEmpty()) { + if ($testWith->isNotEmpty()) { + $method = new ReflectionMethod($className, $methodName); + + Event\Facade::emitter()->testTriggeredPhpunitWarning( + new TestMethod( + $className, + $methodName, + $method->getFileName(), + $method->getStartLine(), + Event\Code\TestDoxBuilder::fromClassNameAndMethodName( + $className, + $methodName, + ), + MetadataCollection::fromArray([]), + Event\TestData\TestDataCollection::fromArray([]), + ), + 'Mixing #[DataProvider*] and #[TestWith*] attributes is not supported, only the data provided by #[DataProvider*] will be used', + ); + } + $data = $this->dataProvidedByMethods($className, $methodName, $dataProvider); + } else { + $data = $this->dataProvidedByMetadata($testWith); + } + + if ($data === []) { + throw new InvalidDataProviderException( + 'Empty data set provided by data provider', + ); + } + + $method = new ReflectionMethod($className, $methodName); + $testMethodNumberOfParameters = $method->getNumberOfParameters(); + $testMethodIsNonVariadic = !$method->isVariadic(); + + foreach ($data as $key => $providedData) { + $value = $providedData->value(); + + if (!is_array($value)) { + throw new InvalidDataProviderException( + sprintf( + 'Data set %s provided by %s is invalid, expected array but got %s', + $this->formatKey($key), + $providedData->label(), + get_debug_type($value), + ), + ); + } + + if ($testMethodIsNonVariadic && $testMethodNumberOfParameters < count($value)) { + Event\Facade::emitter()->testTriggeredPhpunitWarning( + new TestMethod( + $className, + $methodName, + $method->getFileName(), + $method->getStartLine(), + Event\Code\TestDoxBuilder::fromClassNameAndMethodName( + $className, + $methodName, + ), + MetadataCollection::fromArray([]), + Event\TestData\TestDataCollection::fromArray([]), + ), + sprintf( + 'Data set %s provided by %s has more arguments (%d) than the test method accepts (%d)', + $this->formatKey($key), + $providedData->label(), + count($value), + $testMethodNumberOfParameters, + ), + ); + } + } + + return $data; + } + + /** + * @param class-string $className + * @param non-empty-string $methodName + * + * @throws InvalidDataProviderException + * + * @return array + */ + private function dataProvidedByMethods(string $className, string $methodName, MetadataCollection $dataProvider): array + { + $testMethod = new Event\Code\ClassMethod($className, $methodName); + $methodsCalled = []; + + /** + * @var array $result + */ + $result = []; + + foreach ($dataProvider as $_dataProvider) { + assert($_dataProvider instanceof DataProviderMetadata); + + if (is_a($_dataProvider->className(), TestCase::class, true) && + str_starts_with($_dataProvider->methodName(), 'test')) { + Event\Facade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + 'The name of the data provider method %s::%s() used by test method %s::%s() begins with "test", therefore PHPUnit also treats it as a test method', + $_dataProvider->className(), + $_dataProvider->methodName(), + $className, + $methodName, + ), + ); + } + + $providerLabel = $_dataProvider->className() . '::' . $_dataProvider->methodName(); + $dataProviderMethod = new Event\Code\ClassMethod($_dataProvider->className(), $_dataProvider->methodName()); + + Event\Facade::emitter()->dataProviderMethodCalled( + $testMethod, + $dataProviderMethod, + ); + + $methodsCalled[] = $dataProviderMethod; + + try { + $method = new ReflectionMethod($_dataProvider->className(), $_dataProvider->methodName()); + + if (!$method->isPublic()) { + throw new InvalidDataProviderException( + sprintf( + 'Data Provider method %s::%s() is not public', + $_dataProvider->className(), + $_dataProvider->methodName(), + ), + ); + } + + if (!$method->isStatic()) { + throw new InvalidDataProviderException( + sprintf( + 'Data Provider method %s::%s() is not static', + $_dataProvider->className(), + $_dataProvider->methodName(), + ), + ); + } + + if ($method->getNumberOfParameters() > 0) { + throw new InvalidDataProviderException( + sprintf( + 'Data Provider method %s::%s() expects an argument', + $_dataProvider->className(), + $_dataProvider->methodName(), + ), + ); + } + + $className = $_dataProvider->className(); + $methodName = $_dataProvider->methodName(); + + /** @phpstan-ignore staticMethod.dynamicName */ + $data = $className::$methodName(); + } catch (Throwable $e) { + Event\Facade::emitter()->dataProviderMethodFinished( + $testMethod, + ...$methodsCalled, + ); + + throw InvalidDataProviderException::forException($e, $providerLabel); + } + + foreach ($data as $key => $value) { + if (is_int($key)) { + $result[] = new ProvidedData($providerLabel, $value); + } elseif (is_string($key)) { + if (array_key_exists($key, $result)) { + Event\Facade::emitter()->dataProviderMethodFinished( + $testMethod, + ...$methodsCalled, + ); + + throw new InvalidDataProviderException( + sprintf( + 'The key "%s" has already been defined by provider %s', + $key, + $result[$key]->label(), + ), + ); + } + + $result[$key] = new ProvidedData($providerLabel, $value); + } else { + Event\Facade::emitter()->dataProviderMethodFinished( + $testMethod, + ...$methodsCalled, + ); + + throw new InvalidDataProviderException( + sprintf( + 'The key must be an integer or a string, %s given', + get_debug_type($key), + ), + ); + } + } + } + + Event\Facade::emitter()->dataProviderMethodFinished( + $testMethod, + ...$methodsCalled, + ); + + return $result; + } + + /** + * @return array + */ + private function dataProvidedByMetadata(MetadataCollection $testWith): array + { + $result = []; + + foreach ($testWith as $i => $_testWith) { + assert($_testWith instanceof TestWith); + + $providerLabel = sprintf('TestWith#%s attribute', $i); + + if ($_testWith->hasName()) { + $key = $_testWith->name(); + + if (array_key_exists($key, $result)) { + throw new InvalidDataProviderException( + sprintf( + 'The key "%s" has already been defined by %s', + $key, + $result[$key]->label(), + ), + ); + } + + $result[$key] = new ProvidedData($providerLabel, $_testWith->data()); + } else { + $result[] = new ProvidedData($providerLabel, $_testWith->data()); + } + } + + return $result; + } + + /** + * @param int|non-empty-string $key + * + * @return non-empty-string + */ + private function formatKey(int|string $key): string + { + return is_int($key) ? '#' . $key : '"' . $key . '"'; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/Api/Dependencies.php b/app/vendor/phpunit/phpunit/src/Metadata/Api/Dependencies.php new file mode 100644 index 000000000..3ba35568f --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/Api/Dependencies.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 PHPUnit\Metadata\Api; + +use function assert; +use PHPUnit\Framework\ExecutionOrderDependency; +use PHPUnit\Metadata\DependsOnClass; +use PHPUnit\Metadata\DependsOnMethod; +use PHPUnit\Metadata\Parser\Registry; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Dependencies +{ + /** + * @param class-string $className + * @param non-empty-string $methodName + * + * @return list + */ + public static function dependencies(string $className, string $methodName): array + { + $dependencies = []; + + foreach (Registry::parser()->forClassAndMethod($className, $methodName)->isDepends() as $metadata) { + if ($metadata->isDependsOnClass()) { + assert($metadata instanceof DependsOnClass); + + $dependencies[] = ExecutionOrderDependency::forClass($metadata); + + continue; + } + + assert($metadata instanceof DependsOnMethod); + + if ($metadata->methodName() === '') { + $dependencies[] = ExecutionOrderDependency::invalid(); + + continue; + } + + $dependencies[] = ExecutionOrderDependency::forMethod($metadata); + } + + return $dependencies; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/Api/Groups.php b/app/vendor/phpunit/phpunit/src/Metadata/Api/Groups.php new file mode 100644 index 000000000..aa2cbf43f --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/Api/Groups.php @@ -0,0 +1,135 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata\Api; + +use function array_flip; +use function array_key_exists; +use function array_unique; +use function assert; +use function strtolower; +use function trim; +use PHPUnit\Framework\TestSize\TestSize; +use PHPUnit\Metadata\CoversClass; +use PHPUnit\Metadata\CoversFunction; +use PHPUnit\Metadata\Group; +use PHPUnit\Metadata\Parser\Registry; +use PHPUnit\Metadata\RequiresPhpExtension; +use PHPUnit\Metadata\UsesClass; +use PHPUnit\Metadata\UsesFunction; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Groups +{ + /** + * @var array> + */ + private static array $groupCache = []; + + /** + * @param class-string $className + * @param non-empty-string $methodName + * + * @return list + */ + public function groups(string $className, string $methodName, bool $includeVirtual = true): array + { + $key = $className . '::' . $methodName . '::' . $includeVirtual; + + if (array_key_exists($key, self::$groupCache)) { + return self::$groupCache[$key]; + } + + $groups = []; + + foreach (Registry::parser()->forClassAndMethod($className, $methodName)->isGroup() as $group) { + assert($group instanceof Group); + + $groups[] = $group->groupName(); + } + + if (!$includeVirtual) { + return self::$groupCache[$key] = array_unique($groups); + } + + foreach (Registry::parser()->forClassAndMethod($className, $methodName) as $metadata) { + if ($metadata->isCoversClass()) { + assert($metadata instanceof CoversClass); + + $groups[] = '__phpunit_covers_' . $this->canonicalizeName($metadata->className()); + + continue; + } + + if ($metadata->isCoversFunction()) { + assert($metadata instanceof CoversFunction); + + $groups[] = '__phpunit_covers_' . $this->canonicalizeName($metadata->functionName()); + + continue; + } + + if ($metadata->isUsesClass()) { + assert($metadata instanceof UsesClass); + + $groups[] = '__phpunit_uses_' . $this->canonicalizeName($metadata->className()); + + continue; + } + + if ($metadata->isUsesFunction()) { + assert($metadata instanceof UsesFunction); + + $groups[] = '__phpunit_uses_' . $this->canonicalizeName($metadata->functionName()); + + continue; + } + + if ($metadata->isRequiresPhpExtension()) { + assert($metadata instanceof RequiresPhpExtension); + + $groups[] = '__phpunit_requires_php_extension' . $this->canonicalizeName($metadata->extension()); + } + } + + return self::$groupCache[$key] = array_unique($groups); + } + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public function size(string $className, string $methodName): TestSize + { + $groups = array_flip($this->groups($className, $methodName)); + + if (isset($groups['large'])) { + return TestSize::large(); + } + + if (isset($groups['medium'])) { + return TestSize::medium(); + } + + if (isset($groups['small'])) { + return TestSize::small(); + } + + return TestSize::unknown(); + } + + private function canonicalizeName(string $name): string + { + return strtolower(trim($name, '\\')); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/Api/HookMethods.php b/app/vendor/phpunit/phpunit/src/Metadata/Api/HookMethods.php new file mode 100644 index 000000000..94d429704 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/Api/HookMethods.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 PHPUnit\Metadata\Api; + +use function assert; +use function class_exists; +use PHPUnit\Framework\TestCase; +use PHPUnit\Metadata\After; +use PHPUnit\Metadata\AfterClass; +use PHPUnit\Metadata\Before; +use PHPUnit\Metadata\BeforeClass; +use PHPUnit\Metadata\Parser\Registry; +use PHPUnit\Metadata\PostCondition; +use PHPUnit\Metadata\PreCondition; +use PHPUnit\Runner\HookMethod; +use PHPUnit\Runner\HookMethodCollection; +use PHPUnit\Util\Reflection; +use ReflectionClass; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class HookMethods +{ + /** + * @var array + */ + private static array $hookMethods = []; + + /** + * @param class-string $className + * + * @return array{beforeClass: HookMethodCollection, before: HookMethodCollection, preCondition: HookMethodCollection, postCondition: HookMethodCollection, after: HookMethodCollection, afterClass: HookMethodCollection} + */ + public function hookMethods(string $className): array + { + if (!class_exists($className)) { + return self::emptyHookMethodsArray(); + } + + if (isset(self::$hookMethods[$className])) { + return self::$hookMethods[$className]; + } + + self::$hookMethods[$className] = self::emptyHookMethodsArray(); + + foreach (Reflection::methodsDeclaredDirectlyInTestClass(new ReflectionClass($className)) as $method) { + $methodName = $method->getName(); + $metadata = Registry::parser()->forMethod($className, $methodName); + + if ($method->isStatic()) { + if ($metadata->isBeforeClass()->isNotEmpty()) { + $beforeClass = $metadata->isBeforeClass()->asArray()[0]; + assert($beforeClass instanceof BeforeClass); + + self::$hookMethods[$className]['beforeClass']->add( + new HookMethod($methodName, $beforeClass->priority()), + ); + } + + if ($metadata->isAfterClass()->isNotEmpty()) { + $afterClass = $metadata->isAfterClass()->asArray()[0]; + assert($afterClass instanceof AfterClass); + + self::$hookMethods[$className]['afterClass']->add( + new HookMethod($methodName, $afterClass->priority()), + ); + } + } + + if ($metadata->isBefore()->isNotEmpty()) { + $before = $metadata->isBefore()->asArray()[0]; + assert($before instanceof Before); + + self::$hookMethods[$className]['before']->add( + new HookMethod($methodName, $before->priority()), + ); + } + + if ($metadata->isPreCondition()->isNotEmpty()) { + $preCondition = $metadata->isPreCondition()->asArray()[0]; + assert($preCondition instanceof PreCondition); + + self::$hookMethods[$className]['preCondition']->add( + new HookMethod($methodName, $preCondition->priority()), + ); + } + + if ($metadata->isPostCondition()->isNotEmpty()) { + $postCondition = $metadata->isPostCondition()->asArray()[0]; + assert($postCondition instanceof PostCondition); + + self::$hookMethods[$className]['postCondition']->add( + new HookMethod($methodName, $postCondition->priority()), + ); + } + + if ($metadata->isAfter()->isNotEmpty()) { + $after = $metadata->isAfter()->asArray()[0]; + assert($after instanceof After); + + self::$hookMethods[$className]['after']->add( + new HookMethod($methodName, $after->priority()), + ); + } + } + + return self::$hookMethods[$className]; + } + + /** + * @return array{beforeClass: HookMethodCollection, before: HookMethodCollection, preCondition: HookMethodCollection, postCondition: HookMethodCollection, after: HookMethodCollection, afterClass: HookMethodCollection} + */ + private function emptyHookMethodsArray(): array + { + return [ + 'beforeClass' => HookMethodCollection::defaultBeforeClass(), + 'before' => HookMethodCollection::defaultBefore(), + 'preCondition' => HookMethodCollection::defaultPreCondition(), + 'postCondition' => HookMethodCollection::defaultPostCondition(), + 'after' => HookMethodCollection::defaultAfter(), + 'afterClass' => HookMethodCollection::defaultAfterClass(), + ]; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/Api/ProvidedData.php b/app/vendor/phpunit/phpunit/src/Metadata/Api/ProvidedData.php new file mode 100644 index 000000000..7d21766bc --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/Api/ProvidedData.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 PHPUnit\Metadata\Api; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ProvidedData +{ + /** + * @var non-empty-string + */ + private string $label; + private mixed $value; + + /** + * @param non-empty-string $label + */ + public function __construct(string $label, mixed $value) + { + $this->label = $label; + $this->value = $value; + } + + /** + * @return non-empty-string + */ + public function label(): string + { + return $this->label; + } + + public function value(): mixed + { + return $this->value; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/Api/Requirements.php b/app/vendor/phpunit/phpunit/src/Metadata/Api/Requirements.php new file mode 100644 index 000000000..4a6abe76f --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/Api/Requirements.php @@ -0,0 +1,200 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata\Api; + +use const PHP_OS; +use const PHP_OS_FAMILY; +use const PHP_VERSION; +use function addcslashes; +use function array_column; +use function array_key_exists; +use function assert; +use function extension_loaded; +use function function_exists; +use function in_array; +use function ini_get; +use function method_exists; +use function phpversion; +use function preg_match; +use function sprintf; +use PHPUnit\Metadata\Parser\Registry; +use PHPUnit\Metadata\RequiresEnvironmentVariable; +use PHPUnit\Metadata\RequiresFunction; +use PHPUnit\Metadata\RequiresMethod; +use PHPUnit\Metadata\RequiresOperatingSystem; +use PHPUnit\Metadata\RequiresOperatingSystemFamily; +use PHPUnit\Metadata\RequiresPhp; +use PHPUnit\Metadata\RequiresPhpExtension; +use PHPUnit\Metadata\RequiresPhpunit; +use PHPUnit\Metadata\RequiresPhpunitExtension; +use PHPUnit\Metadata\RequiresSetting; +use PHPUnit\Runner\Version; +use PHPUnit\TextUI\Configuration\Registry as ConfigurationRegistry; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Requirements +{ + /** + * @param class-string $className + * @param non-empty-string $methodName + * + * @return list + */ + public function requirementsNotSatisfiedFor(string $className, string $methodName): array + { + $notSatisfied = []; + + foreach (Registry::parser()->forClassAndMethod($className, $methodName) as $metadata) { + if ($metadata->isRequiresPhp()) { + assert($metadata instanceof RequiresPhp); + + if (!$metadata->versionRequirement()->isSatisfiedBy(PHP_VERSION)) { + $notSatisfied[] = sprintf( + 'PHP %s is required.', + $metadata->versionRequirement()->asString(), + ); + } + } + + if ($metadata->isRequiresPhpExtension()) { + assert($metadata instanceof RequiresPhpExtension); + + $extensionVersion = phpversion($metadata->extension()); + + if ($extensionVersion === false) { + $extensionVersion = ''; + } + + if (!extension_loaded($metadata->extension()) || + ($metadata->hasVersionRequirement() && + !$metadata->versionRequirement()->isSatisfiedBy($extensionVersion))) { + $notSatisfied[] = sprintf( + 'PHP extension %s%s is required.', + $metadata->extension(), + $metadata->hasVersionRequirement() ? (' ' . $metadata->versionRequirement()->asString()) : '', + ); + } + } + + if ($metadata->isRequiresPhpunit()) { + assert($metadata instanceof RequiresPhpunit); + + if (!$metadata->versionRequirement()->isSatisfiedBy(Version::id())) { + $notSatisfied[] = sprintf( + 'PHPUnit %s is required.', + $metadata->versionRequirement()->asString(), + ); + } + } + + if ($metadata->isRequiresPhpunitExtension()) { + assert($metadata instanceof RequiresPhpunitExtension); + + $configuration = ConfigurationRegistry::get(); + + $extensionBootstrappers = array_column($configuration->extensionBootstrappers(), 'className'); + + if ($configuration->noExtensions() || !in_array($metadata->extensionClass(), $extensionBootstrappers, true)) { + $notSatisfied[] = sprintf( + 'PHPUnit extension "%s" is required.', + $metadata->extensionClass(), + ); + } + } + + if ($metadata->isRequiresEnvironmentVariable()) { + assert($metadata instanceof RequiresEnvironmentVariable); + + if (!array_key_exists($metadata->environmentVariableName(), $_ENV) || + $metadata->value() === null && $_ENV[$metadata->environmentVariableName()] === '') { + $notSatisfied[] = sprintf('Environment variable "%s" is required.', $metadata->environmentVariableName()); + + continue; + } + + if ($metadata->value() !== null && $_ENV[$metadata->environmentVariableName()] !== $metadata->value()) { + $notSatisfied[] = sprintf( + 'Environment variable "%s" is required to be "%s".', + $metadata->environmentVariableName(), + $metadata->value(), + ); + } + } + + if ($metadata->isRequiresOperatingSystemFamily()) { + assert($metadata instanceof RequiresOperatingSystemFamily); + + if ($metadata->operatingSystemFamily() !== PHP_OS_FAMILY) { + $notSatisfied[] = sprintf( + 'Operating system %s is required.', + $metadata->operatingSystemFamily(), + ); + } + } + + if ($metadata->isRequiresOperatingSystem()) { + assert($metadata instanceof RequiresOperatingSystem); + + $pattern = sprintf( + '/%s/i', + addcslashes($metadata->operatingSystem(), '/'), + ); + + if (preg_match($pattern, PHP_OS) === 0) { + $notSatisfied[] = sprintf( + 'Operating system %s is required.', + $metadata->operatingSystem(), + ); + } + } + + if ($metadata->isRequiresFunction()) { + assert($metadata instanceof RequiresFunction); + + if (!function_exists($metadata->functionName())) { + $notSatisfied[] = sprintf( + 'Function %s() is required.', + $metadata->functionName(), + ); + } + } + + if ($metadata->isRequiresMethod()) { + assert($metadata instanceof RequiresMethod); + + if (!method_exists($metadata->className(), $metadata->methodName())) { + $notSatisfied[] = sprintf( + 'Method %s::%s() is required.', + $metadata->className(), + $metadata->methodName(), + ); + } + } + + if ($metadata->isRequiresSetting()) { + assert($metadata instanceof RequiresSetting); + + if (ini_get($metadata->setting()) !== $metadata->value()) { + $notSatisfied[] = sprintf( + 'Setting "%s" is required to be "%s".', + $metadata->setting(), + $metadata->value(), + ); + } + } + } + + return $notSatisfied; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/BackupGlobals.php b/app/vendor/phpunit/phpunit/src/Metadata/BackupGlobals.php new file mode 100644 index 000000000..5d9478443 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/BackupGlobals.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 PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class BackupGlobals extends Metadata +{ + private bool $enabled; + + /** + * @param int<0, 1> $level + */ + protected function __construct(int $level, bool $enabled) + { + parent::__construct($level); + + $this->enabled = $enabled; + } + + public function isBackupGlobals(): true + { + return true; + } + + public function enabled(): bool + { + return $this->enabled; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/BackupStaticProperties.php b/app/vendor/phpunit/phpunit/src/Metadata/BackupStaticProperties.php new file mode 100644 index 000000000..a07698eeb --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/BackupStaticProperties.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 PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class BackupStaticProperties extends Metadata +{ + private bool $enabled; + + /** + * @param int<0, 1> $level + */ + protected function __construct(int $level, bool $enabled) + { + parent::__construct($level); + + $this->enabled = $enabled; + } + + public function isBackupStaticProperties(): true + { + return true; + } + + public function enabled(): bool + { + return $this->enabled; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/Before.php b/app/vendor/phpunit/phpunit/src/Metadata/Before.php new file mode 100644 index 000000000..08372f76c --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/Before.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 PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Before extends Metadata +{ + private int $priority; + + /** + * @param int<0, 1> $level + */ + protected function __construct(int $level, int $priority) + { + parent::__construct($level); + + $this->priority = $priority; + } + + public function isBefore(): true + { + return true; + } + + public function priority(): int + { + return $this->priority; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/BeforeClass.php b/app/vendor/phpunit/phpunit/src/Metadata/BeforeClass.php new file mode 100644 index 000000000..c56463207 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/BeforeClass.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 PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class BeforeClass extends Metadata +{ + private int $priority; + + /** + * @param int<0, 1> $level + */ + protected function __construct(int $level, int $priority) + { + parent::__construct($level); + + $this->priority = $priority; + } + + public function isBeforeClass(): true + { + return true; + } + + public function priority(): int + { + return $this->priority; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/CoversClass.php b/app/vendor/phpunit/phpunit/src/Metadata/CoversClass.php new file mode 100644 index 000000000..e573952b6 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/CoversClass.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 PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class CoversClass extends Metadata +{ + /** + * @var class-string + */ + private string $className; + + /** + * @param int<0, 1> $level + * @param class-string $className + */ + protected function __construct(int $level, string $className) + { + parent::__construct($level); + + $this->className = $className; + } + + public function isCoversClass(): true + { + return true; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/CoversClassesThatExtendClass.php b/app/vendor/phpunit/phpunit/src/Metadata/CoversClassesThatExtendClass.php new file mode 100644 index 000000000..7feb40d70 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/CoversClassesThatExtendClass.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 PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class CoversClassesThatExtendClass extends Metadata +{ + /** + * @var class-string + */ + private string $className; + + /** + * @param int<0, 1> $level + * @param class-string $className + */ + protected function __construct(int $level, string $className) + { + parent::__construct($level); + + $this->className = $className; + } + + public function isCoversClassesThatExtendClass(): true + { + return true; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/CoversClassesThatImplementInterface.php b/app/vendor/phpunit/phpunit/src/Metadata/CoversClassesThatImplementInterface.php new file mode 100644 index 000000000..e980801ec --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/CoversClassesThatImplementInterface.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 PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class CoversClassesThatImplementInterface extends Metadata +{ + /** + * @var class-string + */ + private string $interfaceName; + + /** + * @param int<0, 1> $level + * @param class-string $interfaceName + */ + protected function __construct(int $level, string $interfaceName) + { + parent::__construct($level); + + $this->interfaceName = $interfaceName; + } + + public function isCoversClassesThatImplementInterface(): true + { + return true; + } + + /** + * @return class-string + */ + public function interfaceName(): string + { + return $this->interfaceName; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/CoversFunction.php b/app/vendor/phpunit/phpunit/src/Metadata/CoversFunction.php new file mode 100644 index 000000000..4e953601f --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/CoversFunction.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 PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class CoversFunction extends Metadata +{ + /** + * @var non-empty-string + */ + private string $functionName; + + /** + * @param int<0, 1> $level + * @param non-empty-string $functionName + */ + protected function __construct(int $level, string $functionName) + { + parent::__construct($level); + + $this->functionName = $functionName; + } + + public function isCoversFunction(): true + { + return true; + } + + /** + * @return non-empty-string + */ + public function functionName(): string + { + return $this->functionName; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/CoversMethod.php b/app/vendor/phpunit/phpunit/src/Metadata/CoversMethod.php new file mode 100644 index 000000000..73092ff78 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/CoversMethod.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class CoversMethod extends Metadata +{ + /** + * @var class-string + */ + private string $className; + + /** + * @var non-empty-string + */ + private string $methodName; + + /** + * @param int<0, 1> $level + * @param class-string $className + * @param non-empty-string $methodName + */ + protected function __construct(int $level, string $className, string $methodName) + { + parent::__construct($level); + + $this->className = $className; + $this->methodName = $methodName; + } + + public function isCoversMethod(): true + { + return true; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + /** + * @return non-empty-string + */ + public function methodName(): string + { + return $this->methodName; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/CoversNamespace.php b/app/vendor/phpunit/phpunit/src/Metadata/CoversNamespace.php new file mode 100644 index 000000000..ef7452a66 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/CoversNamespace.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 PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class CoversNamespace extends Metadata +{ + /** + * @var non-empty-string + */ + private string $namespace; + + /** + * @param int<0, 1> $level + * @param non-empty-string $namespace + */ + protected function __construct(int $level, string $namespace) + { + parent::__construct($level); + + $this->namespace = $namespace; + } + + public function isCoversNamespace(): true + { + return true; + } + + /** + * @return class-string + */ + public function namespace(): string + { + return $this->namespace; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/CoversNothing.php b/app/vendor/phpunit/phpunit/src/Metadata/CoversNothing.php new file mode 100644 index 000000000..c81e72765 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/CoversNothing.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 PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class CoversNothing extends Metadata +{ + public function isCoversNothing(): true + { + return true; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/CoversTrait.php b/app/vendor/phpunit/phpunit/src/Metadata/CoversTrait.php new file mode 100644 index 000000000..2cad9dfb4 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/CoversTrait.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 PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class CoversTrait extends Metadata +{ + /** + * @var trait-string + */ + private string $traitName; + + /** + * @param 0|1 $level + * @param trait-string $traitName + */ + protected function __construct(int $level, string $traitName) + { + parent::__construct($level); + + $this->traitName = $traitName; + } + + public function isCoversTrait(): true + { + return true; + } + + /** + * @return trait-string + */ + public function traitName(): string + { + return $this->traitName; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/DataProvider.php b/app/vendor/phpunit/phpunit/src/Metadata/DataProvider.php new file mode 100644 index 000000000..57b821e48 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/DataProvider.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class DataProvider extends Metadata +{ + /** + * @var class-string + */ + private string $className; + + /** + * @var non-empty-string + */ + private string $methodName; + + /** + * @param int<0, 1> $level + * @param class-string $className + * @param non-empty-string $methodName + */ + protected function __construct(int $level, string $className, string $methodName) + { + parent::__construct($level); + + $this->className = $className; + $this->methodName = $methodName; + } + + public function isDataProvider(): true + { + return true; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + /** + * @return non-empty-string + */ + public function methodName(): string + { + return $this->methodName; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/DependsOnClass.php b/app/vendor/phpunit/phpunit/src/Metadata/DependsOnClass.php new file mode 100644 index 000000000..fbc40fd14 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/DependsOnClass.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 PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class DependsOnClass extends Metadata +{ + /** + * @var class-string + */ + private string $className; + private bool $deepClone; + private bool $shallowClone; + + /** + * @param int<0, 1> $level + * @param class-string $className + */ + protected function __construct(int $level, string $className, bool $deepClone, bool $shallowClone) + { + parent::__construct($level); + + $this->className = $className; + $this->deepClone = $deepClone; + $this->shallowClone = $shallowClone; + } + + public function isDependsOnClass(): true + { + return true; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + public function deepClone(): bool + { + return $this->deepClone; + } + + public function shallowClone(): bool + { + return $this->shallowClone; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/DependsOnMethod.php b/app/vendor/phpunit/phpunit/src/Metadata/DependsOnMethod.php new file mode 100644 index 000000000..82056a996 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/DependsOnMethod.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 PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class DependsOnMethod extends Metadata +{ + /** + * @var class-string + */ + private string $className; + + /** + * @var non-empty-string + */ + private string $methodName; + private bool $deepClone; + private bool $shallowClone; + + /** + * @param int<0, 1> $level + * @param class-string $className + * @param non-empty-string $methodName + */ + protected function __construct(int $level, string $className, string $methodName, bool $deepClone, bool $shallowClone) + { + parent::__construct($level); + + $this->className = $className; + $this->methodName = $methodName; + $this->deepClone = $deepClone; + $this->shallowClone = $shallowClone; + } + + public function isDependsOnMethod(): true + { + return true; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + /** + * @return non-empty-string + */ + public function methodName(): string + { + return $this->methodName; + } + + public function deepClone(): bool + { + return $this->deepClone; + } + + public function shallowClone(): bool + { + return $this->shallowClone; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/DisableReturnValueGenerationForTestDoubles.php b/app/vendor/phpunit/phpunit/src/Metadata/DisableReturnValueGenerationForTestDoubles.php new file mode 100644 index 000000000..59cf34e28 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/DisableReturnValueGenerationForTestDoubles.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 PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class DisableReturnValueGenerationForTestDoubles extends Metadata +{ + public function isDisableReturnValueGenerationForTestDoubles(): true + { + return true; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/DoesNotPerformAssertions.php b/app/vendor/phpunit/phpunit/src/Metadata/DoesNotPerformAssertions.php new file mode 100644 index 000000000..e2925c857 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/DoesNotPerformAssertions.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 PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class DoesNotPerformAssertions extends Metadata +{ + public function isDoesNotPerformAssertions(): true + { + return true; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/Exception/Exception.php b/app/vendor/phpunit/phpunit/src/Metadata/Exception/Exception.php new file mode 100644 index 000000000..5d562f1a2 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/Exception/Exception.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface Exception extends \PHPUnit\Exception +{ +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/Exception/InvalidAttributeException.php b/app/vendor/phpunit/phpunit/src/Metadata/Exception/InvalidAttributeException.php new file mode 100644 index 000000000..9158de328 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/Exception/InvalidAttributeException.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +use const PHP_EOL; +use function sprintf; +use PHPUnit\Exception; +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class InvalidAttributeException extends RuntimeException implements Exception +{ + /** + * @param non-empty-string $attributeName + * @param non-empty-string $target + * @param non-empty-string $file + * @param positive-int $line + * @param non-empty-string $message + */ + public function __construct(string $attributeName, string $target, string $file, int $line, string $message) + { + parent::__construct( + sprintf( + 'Invalid attribute %s for %s in %s:%d%s%s', + $attributeName, + $target, + $file, + $line, + PHP_EOL, + $message, + ), + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/Exception/InvalidVersionRequirementException.php b/app/vendor/phpunit/phpunit/src/Metadata/Exception/InvalidVersionRequirementException.php new file mode 100644 index 000000000..359f723c1 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/Exception/InvalidVersionRequirementException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class InvalidVersionRequirementException extends RuntimeException implements Exception +{ +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/Exception/NoVersionRequirementException.php b/app/vendor/phpunit/phpunit/src/Metadata/Exception/NoVersionRequirementException.php new file mode 100644 index 000000000..299652cf0 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/Exception/NoVersionRequirementException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class NoVersionRequirementException extends RuntimeException implements Exception +{ +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/ExcludeGlobalVariableFromBackup.php b/app/vendor/phpunit/phpunit/src/Metadata/ExcludeGlobalVariableFromBackup.php new file mode 100644 index 000000000..4f646dbde --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/ExcludeGlobalVariableFromBackup.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 PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ExcludeGlobalVariableFromBackup extends Metadata +{ + /** + * @var non-empty-string + */ + private string $globalVariableName; + + /** + * @param int<0, 1> $level + * @param non-empty-string $globalVariableName + */ + protected function __construct(int $level, string $globalVariableName) + { + parent::__construct($level); + + $this->globalVariableName = $globalVariableName; + } + + public function isExcludeGlobalVariableFromBackup(): true + { + return true; + } + + /** + * @return non-empty-string + */ + public function globalVariableName(): string + { + return $this->globalVariableName; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/ExcludeStaticPropertyFromBackup.php b/app/vendor/phpunit/phpunit/src/Metadata/ExcludeStaticPropertyFromBackup.php new file mode 100644 index 000000000..21725e939 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/ExcludeStaticPropertyFromBackup.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ExcludeStaticPropertyFromBackup extends Metadata +{ + /** + * @var class-string + */ + private string $className; + + /** + * @var non-empty-string + */ + private string $propertyName; + + /** + * @param int<0, 1> $level + * @param class-string $className + * @param non-empty-string $propertyName + */ + protected function __construct(int $level, string $className, string $propertyName) + { + parent::__construct($level); + + $this->className = $className; + $this->propertyName = $propertyName; + } + + public function isExcludeStaticPropertyFromBackup(): true + { + return true; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + /** + * @return non-empty-string + */ + public function propertyName(): string + { + return $this->propertyName; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/Group.php b/app/vendor/phpunit/phpunit/src/Metadata/Group.php new file mode 100644 index 000000000..2ee2784f2 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/Group.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 PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Group extends Metadata +{ + /** + * @var non-empty-string + */ + private string $groupName; + + /** + * @param int<0, 1> $level + * @param non-empty-string $groupName + */ + protected function __construct(int $level, string $groupName) + { + parent::__construct($level); + + $this->groupName = $groupName; + } + + public function isGroup(): true + { + return true; + } + + /** + * @return non-empty-string + */ + public function groupName(): string + { + return $this->groupName; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/IgnoreDeprecations.php b/app/vendor/phpunit/phpunit/src/Metadata/IgnoreDeprecations.php new file mode 100644 index 000000000..e1f85a8d8 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/IgnoreDeprecations.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 PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class IgnoreDeprecations extends Metadata +{ + public function isIgnoreDeprecations(): true + { + return true; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/IgnorePhpunitDeprecations.php b/app/vendor/phpunit/phpunit/src/Metadata/IgnorePhpunitDeprecations.php new file mode 100644 index 000000000..5abcfa485 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/IgnorePhpunitDeprecations.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class IgnorePhpunitDeprecations extends Metadata +{ + public function isIgnorePhpunitDeprecations(): true + { + return true; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/IgnorePhpunitWarnings.php b/app/vendor/phpunit/phpunit/src/Metadata/IgnorePhpunitWarnings.php new file mode 100644 index 000000000..bf50c98e7 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/IgnorePhpunitWarnings.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +use function preg_match; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class IgnorePhpunitWarnings extends Metadata +{ + /** @var null|non-empty-string */ + private ?string $messagePattern; + + /** + * @param int<0, 1> $level + * @param null|non-empty-string $messagePattern + */ + protected function __construct(int $level, null|string $messagePattern) + { + parent::__construct($level); + + $this->messagePattern = $messagePattern; + } + + public function isIgnorePhpunitWarnings(): true + { + return true; + } + + public function shouldIgnore(string $message): bool + { + return $this->messagePattern === null || + (bool) preg_match('{' . $this->messagePattern . '}', $message); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/Metadata.php b/app/vendor/phpunit/phpunit/src/Metadata/Metadata.php new file mode 100644 index 000000000..72851d865 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/Metadata.php @@ -0,0 +1,1004 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +use PHPUnit\Metadata\Version\Requirement; +use PHPUnit\Runner\Extension\Extension; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +abstract readonly class Metadata +{ + private const int CLASS_LEVEL = 0; + private const int METHOD_LEVEL = 1; + + /** + * @var int<0, 1> + */ + private int $level; + + public static function after(int $priority): After + { + return new After(self::METHOD_LEVEL, $priority); + } + + public static function afterClass(int $priority): AfterClass + { + return new AfterClass(self::METHOD_LEVEL, $priority); + } + + public static function backupGlobalsOnClass(bool $enabled): BackupGlobals + { + return new BackupGlobals(self::CLASS_LEVEL, $enabled); + } + + public static function backupGlobalsOnMethod(bool $enabled): BackupGlobals + { + return new BackupGlobals(self::METHOD_LEVEL, $enabled); + } + + public static function backupStaticPropertiesOnClass(bool $enabled): BackupStaticProperties + { + return new BackupStaticProperties(self::CLASS_LEVEL, $enabled); + } + + public static function backupStaticPropertiesOnMethod(bool $enabled): BackupStaticProperties + { + return new BackupStaticProperties(self::METHOD_LEVEL, $enabled); + } + + public static function before(int $priority): Before + { + return new Before(self::METHOD_LEVEL, $priority); + } + + public static function beforeClass(int $priority): BeforeClass + { + return new BeforeClass(self::METHOD_LEVEL, $priority); + } + + /** + * @param non-empty-string $namespace + */ + public static function coversNamespace(string $namespace): CoversNamespace + { + return new CoversNamespace(self::CLASS_LEVEL, $namespace); + } + + /** + * @param class-string $className + */ + public static function coversClass(string $className): CoversClass + { + return new CoversClass(self::CLASS_LEVEL, $className); + } + + /** + * @param class-string $className + */ + public static function coversClassesThatExtendClass(string $className): CoversClassesThatExtendClass + { + return new CoversClassesThatExtendClass(self::CLASS_LEVEL, $className); + } + + /** + * @param class-string $interfaceName + */ + public static function coversClassesThatImplementInterface(string $interfaceName): CoversClassesThatImplementInterface + { + return new CoversClassesThatImplementInterface(self::CLASS_LEVEL, $interfaceName); + } + + /** + * @param trait-string $traitName + */ + public static function coversTrait(string $traitName): CoversTrait + { + return new CoversTrait(self::CLASS_LEVEL, $traitName); + } + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public static function coversMethod(string $className, string $methodName): CoversMethod + { + return new CoversMethod(self::CLASS_LEVEL, $className, $methodName); + } + + /** + * @param non-empty-string $functionName + */ + public static function coversFunction(string $functionName): CoversFunction + { + return new CoversFunction(self::CLASS_LEVEL, $functionName); + } + + public static function coversNothingOnClass(): CoversNothing + { + return new CoversNothing(self::CLASS_LEVEL); + } + + public static function coversNothingOnMethod(): CoversNothing + { + return new CoversNothing(self::METHOD_LEVEL); + } + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public static function dataProvider(string $className, string $methodName): DataProvider + { + return new DataProvider(self::METHOD_LEVEL, $className, $methodName); + } + + /** + * @param class-string $className + */ + public static function dependsOnClass(string $className, bool $deepClone, bool $shallowClone): DependsOnClass + { + return new DependsOnClass(self::METHOD_LEVEL, $className, $deepClone, $shallowClone); + } + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public static function dependsOnMethod(string $className, string $methodName, bool $deepClone, bool $shallowClone): DependsOnMethod + { + return new DependsOnMethod(self::METHOD_LEVEL, $className, $methodName, $deepClone, $shallowClone); + } + + public static function disableReturnValueGenerationForTestDoubles(): DisableReturnValueGenerationForTestDoubles + { + return new DisableReturnValueGenerationForTestDoubles(self::CLASS_LEVEL); + } + + public static function doesNotPerformAssertionsOnClass(): DoesNotPerformAssertions + { + return new DoesNotPerformAssertions(self::CLASS_LEVEL); + } + + public static function doesNotPerformAssertionsOnMethod(): DoesNotPerformAssertions + { + return new DoesNotPerformAssertions(self::METHOD_LEVEL); + } + + /** + * @param non-empty-string $globalVariableName + */ + public static function excludeGlobalVariableFromBackupOnClass(string $globalVariableName): ExcludeGlobalVariableFromBackup + { + return new ExcludeGlobalVariableFromBackup(self::CLASS_LEVEL, $globalVariableName); + } + + /** + * @param non-empty-string $globalVariableName + */ + public static function excludeGlobalVariableFromBackupOnMethod(string $globalVariableName): ExcludeGlobalVariableFromBackup + { + return new ExcludeGlobalVariableFromBackup(self::METHOD_LEVEL, $globalVariableName); + } + + /** + * @param class-string $className + * @param non-empty-string $propertyName + */ + public static function excludeStaticPropertyFromBackupOnClass(string $className, string $propertyName): ExcludeStaticPropertyFromBackup + { + return new ExcludeStaticPropertyFromBackup(self::CLASS_LEVEL, $className, $propertyName); + } + + /** + * @param class-string $className + * @param non-empty-string $propertyName + */ + public static function excludeStaticPropertyFromBackupOnMethod(string $className, string $propertyName): ExcludeStaticPropertyFromBackup + { + return new ExcludeStaticPropertyFromBackup(self::METHOD_LEVEL, $className, $propertyName); + } + + /** + * @param non-empty-string $groupName + */ + public static function groupOnClass(string $groupName): Group + { + return new Group(self::CLASS_LEVEL, $groupName); + } + + /** + * @param non-empty-string $groupName + */ + public static function groupOnMethod(string $groupName): Group + { + return new Group(self::METHOD_LEVEL, $groupName); + } + + public static function ignoreDeprecationsOnClass(): IgnoreDeprecations + { + return new IgnoreDeprecations(self::CLASS_LEVEL); + } + + public static function ignoreDeprecationsOnMethod(): IgnoreDeprecations + { + return new IgnoreDeprecations(self::METHOD_LEVEL); + } + + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public static function ignorePhpunitDeprecationsOnClass(): IgnorePhpunitDeprecations + { + return new IgnorePhpunitDeprecations(self::CLASS_LEVEL); + } + + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public static function ignorePhpunitDeprecationsOnMethod(): IgnorePhpunitDeprecations + { + return new IgnorePhpunitDeprecations(self::METHOD_LEVEL); + } + + public static function postCondition(int $priority): PostCondition + { + return new PostCondition(self::METHOD_LEVEL, $priority); + } + + public static function preCondition(int $priority): PreCondition + { + return new PreCondition(self::METHOD_LEVEL, $priority); + } + + public static function preserveGlobalStateOnClass(bool $enabled): PreserveGlobalState + { + return new PreserveGlobalState(self::CLASS_LEVEL, $enabled); + } + + public static function preserveGlobalStateOnMethod(bool $enabled): PreserveGlobalState + { + return new PreserveGlobalState(self::METHOD_LEVEL, $enabled); + } + + /** + * @param non-empty-string $functionName + */ + public static function requiresFunctionOnClass(string $functionName): RequiresFunction + { + return new RequiresFunction(self::CLASS_LEVEL, $functionName); + } + + /** + * @param non-empty-string $functionName + */ + public static function requiresFunctionOnMethod(string $functionName): RequiresFunction + { + return new RequiresFunction(self::METHOD_LEVEL, $functionName); + } + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public static function requiresMethodOnClass(string $className, string $methodName): RequiresMethod + { + return new RequiresMethod(self::CLASS_LEVEL, $className, $methodName); + } + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public static function requiresMethodOnMethod(string $className, string $methodName): RequiresMethod + { + return new RequiresMethod(self::METHOD_LEVEL, $className, $methodName); + } + + /** + * @param non-empty-string $operatingSystem + */ + public static function requiresOperatingSystemOnClass(string $operatingSystem): RequiresOperatingSystem + { + return new RequiresOperatingSystem(self::CLASS_LEVEL, $operatingSystem); + } + + /** + * @param non-empty-string $operatingSystem + */ + public static function requiresOperatingSystemOnMethod(string $operatingSystem): RequiresOperatingSystem + { + return new RequiresOperatingSystem(self::METHOD_LEVEL, $operatingSystem); + } + + /** + * @param non-empty-string $operatingSystemFamily + */ + public static function requiresOperatingSystemFamilyOnClass(string $operatingSystemFamily): RequiresOperatingSystemFamily + { + return new RequiresOperatingSystemFamily(self::CLASS_LEVEL, $operatingSystemFamily); + } + + /** + * @param non-empty-string $operatingSystemFamily + */ + public static function requiresOperatingSystemFamilyOnMethod(string $operatingSystemFamily): RequiresOperatingSystemFamily + { + return new RequiresOperatingSystemFamily(self::METHOD_LEVEL, $operatingSystemFamily); + } + + public static function requiresPhpOnClass(Requirement $versionRequirement): RequiresPhp + { + return new RequiresPhp(self::CLASS_LEVEL, $versionRequirement); + } + + public static function requiresPhpOnMethod(Requirement $versionRequirement): RequiresPhp + { + return new RequiresPhp(self::METHOD_LEVEL, $versionRequirement); + } + + /** + * @param non-empty-string $extension + */ + public static function requiresPhpExtensionOnClass(string $extension, ?Requirement $versionRequirement): RequiresPhpExtension + { + return new RequiresPhpExtension(self::CLASS_LEVEL, $extension, $versionRequirement); + } + + /** + * @param non-empty-string $extension + */ + public static function requiresPhpExtensionOnMethod(string $extension, ?Requirement $versionRequirement): RequiresPhpExtension + { + return new RequiresPhpExtension(self::METHOD_LEVEL, $extension, $versionRequirement); + } + + public static function requiresPhpunitOnClass(Requirement $versionRequirement): RequiresPhpunit + { + return new RequiresPhpunit(self::CLASS_LEVEL, $versionRequirement); + } + + public static function requiresPhpunitOnMethod(Requirement $versionRequirement): RequiresPhpunit + { + return new RequiresPhpunit(self::METHOD_LEVEL, $versionRequirement); + } + + /** + * @param class-string $extensionClass + */ + public static function requiresPhpunitExtensionOnClass(string $extensionClass): RequiresPhpunitExtension + { + return new RequiresPhpunitExtension(self::CLASS_LEVEL, $extensionClass); + } + + /** + * @param class-string $extensionClass + */ + public static function requiresPhpunitExtensionOnMethod(string $extensionClass): RequiresPhpunitExtension + { + return new RequiresPhpunitExtension(self::METHOD_LEVEL, $extensionClass); + } + + public static function requiresEnvironmentVariableOnClass(string $environmentVariableName, null|string $value): RequiresEnvironmentVariable + { + return new RequiresEnvironmentVariable(self::CLASS_LEVEL, $environmentVariableName, $value); + } + + public static function requiresEnvironmentVariableOnMethod(string $environmentVariableName, null|string $value): RequiresEnvironmentVariable + { + return new RequiresEnvironmentVariable(self::METHOD_LEVEL, $environmentVariableName, $value); + } + + public static function withEnvironmentVariableOnClass(string $environmentVariableName, null|string $value): WithEnvironmentVariable + { + return new WithEnvironmentVariable(self::CLASS_LEVEL, $environmentVariableName, $value); + } + + public static function withEnvironmentVariableOnMethod(string $environmentVariableName, null|string $value): WithEnvironmentVariable + { + return new WithEnvironmentVariable(self::METHOD_LEVEL, $environmentVariableName, $value); + } + + /** + * @param non-empty-string $setting + * @param non-empty-string $value + */ + public static function requiresSettingOnClass(string $setting, string $value): RequiresSetting + { + return new RequiresSetting(self::CLASS_LEVEL, $setting, $value); + } + + /** + * @param non-empty-string $setting + * @param non-empty-string $value + */ + public static function requiresSettingOnMethod(string $setting, string $value): RequiresSetting + { + return new RequiresSetting(self::METHOD_LEVEL, $setting, $value); + } + + public static function runClassInSeparateProcess(): RunClassInSeparateProcess + { + return new RunClassInSeparateProcess(self::CLASS_LEVEL); + } + + public static function runTestsInSeparateProcesses(): RunTestsInSeparateProcesses + { + return new RunTestsInSeparateProcesses(self::CLASS_LEVEL); + } + + public static function runInSeparateProcess(): RunInSeparateProcess + { + return new RunInSeparateProcess(self::METHOD_LEVEL); + } + + public static function test(): Test + { + return new Test(self::METHOD_LEVEL); + } + + /** + * @param non-empty-string $text + */ + public static function testDoxOnClass(string $text): TestDox + { + return new TestDox(self::CLASS_LEVEL, $text); + } + + /** + * @param non-empty-string $text + */ + public static function testDoxOnMethod(string $text): TestDox + { + return new TestDox(self::METHOD_LEVEL, $text); + } + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public static function testDoxFormatter(string $className, string $methodName): TestDoxFormatter + { + return new TestDoxFormatter(self::METHOD_LEVEL, $className, $methodName); + } + + /** + * @param ?non-empty-string $name + */ + public static function testWith(mixed $data, ?string $name = null): TestWith + { + return new TestWith(self::METHOD_LEVEL, $data, $name); + } + + /** + * @param non-empty-string $namespace + */ + public static function usesNamespace(string $namespace): UsesNamespace + { + return new UsesNamespace(self::CLASS_LEVEL, $namespace); + } + + /** + * @param class-string $className + */ + public static function usesClass(string $className): UsesClass + { + return new UsesClass(self::CLASS_LEVEL, $className); + } + + /** + * @param class-string $className + */ + public static function usesClassesThatExtendClass(string $className): UsesClassesThatExtendClass + { + return new UsesClassesThatExtendClass(self::CLASS_LEVEL, $className); + } + + /** + * @param class-string $interfaceName + */ + public static function usesClassesThatImplementInterface(string $interfaceName): UsesClassesThatImplementInterface + { + return new UsesClassesThatImplementInterface(self::CLASS_LEVEL, $interfaceName); + } + + /** + * @param trait-string $traitName + */ + public static function usesTrait(string $traitName): UsesTrait + { + return new UsesTrait(self::CLASS_LEVEL, $traitName); + } + + /** + * @param non-empty-string $functionName + */ + public static function usesFunction(string $functionName): UsesFunction + { + return new UsesFunction(self::CLASS_LEVEL, $functionName); + } + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public static function usesMethod(string $className, string $methodName): UsesMethod + { + return new UsesMethod(self::CLASS_LEVEL, $className, $methodName); + } + + public static function withoutErrorHandler(): WithoutErrorHandler + { + return new WithoutErrorHandler(self::METHOD_LEVEL); + } + + /** + * @param null|non-empty-string $messagePattern + */ + public static function ignorePhpunitWarnings(?string $messagePattern): IgnorePhpunitWarnings + { + return new IgnorePhpunitWarnings(self::METHOD_LEVEL, $messagePattern); + } + + /** + * @param int<0, 1> $level + */ + protected function __construct(int $level) + { + $this->level = $level; + } + + public function isClassLevel(): bool + { + return $this->level === self::CLASS_LEVEL; + } + + public function isMethodLevel(): bool + { + return $this->level === self::METHOD_LEVEL; + } + + /** + * @phpstan-assert-if-true After $this + */ + public function isAfter(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true AfterClass $this + */ + public function isAfterClass(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true BackupGlobals $this + */ + public function isBackupGlobals(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true BackupStaticProperties $this + */ + public function isBackupStaticProperties(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true BeforeClass $this + */ + public function isBeforeClass(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true Before $this + */ + public function isBefore(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true CoversNamespace $this + */ + public function isCoversNamespace(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true CoversClass $this + */ + public function isCoversClass(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true CoversClassesThatExtendClass $this + */ + public function isCoversClassesThatExtendClass(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true CoversClassesThatImplementInterface $this + */ + public function isCoversClassesThatImplementInterface(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true CoversTrait $this + */ + public function isCoversTrait(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true CoversFunction $this + */ + public function isCoversFunction(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true CoversMethod $this + */ + public function isCoversMethod(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true CoversNothing $this + */ + public function isCoversNothing(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true DataProvider $this + */ + public function isDataProvider(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true DependsOnClass $this + */ + public function isDependsOnClass(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true DependsOnMethod $this + */ + public function isDependsOnMethod(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true DisableReturnValueGenerationForTestDoubles $this + */ + public function isDisableReturnValueGenerationForTestDoubles(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true DoesNotPerformAssertions $this + */ + public function isDoesNotPerformAssertions(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true ExcludeGlobalVariableFromBackup $this + */ + public function isExcludeGlobalVariableFromBackup(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true ExcludeStaticPropertyFromBackup $this + */ + public function isExcludeStaticPropertyFromBackup(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true Group $this + */ + public function isGroup(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true IgnoreDeprecations $this + */ + public function isIgnoreDeprecations(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true IgnorePhpunitDeprecations $this + * + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function isIgnorePhpunitDeprecations(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true RunClassInSeparateProcess $this + */ + public function isRunClassInSeparateProcess(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true RunInSeparateProcess $this + */ + public function isRunInSeparateProcess(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true RunTestsInSeparateProcesses $this + */ + public function isRunTestsInSeparateProcesses(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true Test $this + */ + public function isTest(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true PreCondition $this + */ + public function isPreCondition(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true PostCondition $this + */ + public function isPostCondition(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true PreserveGlobalState $this + */ + public function isPreserveGlobalState(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true RequiresMethod $this + */ + public function isRequiresMethod(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true RequiresFunction $this + */ + public function isRequiresFunction(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true RequiresOperatingSystem $this + */ + public function isRequiresOperatingSystem(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true RequiresOperatingSystemFamily $this + */ + public function isRequiresOperatingSystemFamily(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true RequiresPhp $this + */ + public function isRequiresPhp(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true RequiresPhpExtension $this + */ + public function isRequiresPhpExtension(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true RequiresPhpunit $this + */ + public function isRequiresPhpunit(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true RequiresPhpunitExtension $this + */ + public function isRequiresPhpunitExtension(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true RequiresEnvironmentVariable $this + */ + public function isRequiresEnvironmentVariable(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true WithEnvironmentVariable $this + */ + public function isWithEnvironmentVariable(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true RequiresSetting $this + */ + public function isRequiresSetting(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true TestDox $this + */ + public function isTestDox(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true TestDoxFormatter $this + */ + public function isTestDoxFormatter(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true TestWith $this + */ + public function isTestWith(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true UsesNamespace $this + */ + public function isUsesNamespace(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true UsesClass $this + */ + public function isUsesClass(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true UsesClassesThatExtendClass $this + */ + public function isUsesClassesThatExtendClass(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true UsesClassesThatImplementInterface $this + */ + public function isUsesClassesThatImplementInterface(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true UsesTrait $this + */ + public function isUsesTrait(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true UsesFunction $this + */ + public function isUsesFunction(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true UsesMethod $this + */ + public function isUsesMethod(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true WithoutErrorHandler $this + */ + public function isWithoutErrorHandler(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true IgnorePhpunitWarnings $this + */ + public function isIgnorePhpunitWarnings(): bool + { + return false; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/MetadataCollection.php b/app/vendor/phpunit/phpunit/src/Metadata/MetadataCollection.php new file mode 100644 index 000000000..621e7b86d --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/MetadataCollection.php @@ -0,0 +1,663 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +use function array_filter; +use function array_merge; +use function count; +use Countable; +use IteratorAggregate; + +/** + * @template-implements IteratorAggregate + * + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class MetadataCollection implements Countable, IteratorAggregate +{ + /** + * @var list + */ + private array $metadata; + + /** + * @param list $metadata + */ + public static function fromArray(array $metadata): self + { + return new self(...$metadata); + } + + private function __construct(Metadata ...$metadata) + { + $this->metadata = $metadata; + } + + /** + * @return list + */ + public function asArray(): array + { + return $this->metadata; + } + + public function count(): int + { + return count($this->metadata); + } + + /** + * @phpstan-assert-if-true 0 $this->count() + * @phpstan-assert-if-true array{} $this->asArray() + */ + public function isEmpty(): bool + { + return $this->count() === 0; + } + + /** + * @phpstan-assert-if-true positive-int $this->count() + * @phpstan-assert-if-true non-empty-list $this->asArray() + */ + public function isNotEmpty(): bool + { + return $this->count() > 0; + } + + public function getIterator(): MetadataCollectionIterator + { + return new MetadataCollectionIterator($this); + } + + public function mergeWith(self $other): self + { + return new self( + ...array_merge( + $this->asArray(), + $other->asArray(), + ), + ); + } + + public function isClassLevel(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isClassLevel(), + ), + ); + } + + public function isMethodLevel(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isMethodLevel(), + ), + ); + } + + public function isAfter(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isAfter(), + ), + ); + } + + public function isAfterClass(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isAfterClass(), + ), + ); + } + + public function isBackupGlobals(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isBackupGlobals(), + ), + ); + } + + public function isBackupStaticProperties(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isBackupStaticProperties(), + ), + ); + } + + public function isBeforeClass(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isBeforeClass(), + ), + ); + } + + public function isBefore(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isBefore(), + ), + ); + } + + public function isCoversNamespace(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isCoversNamespace(), + ), + ); + } + + public function isCoversClass(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isCoversClass(), + ), + ); + } + + public function isCoversClassesThatExtendClass(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isCoversClassesThatExtendClass(), + ), + ); + } + + public function isCoversClassesThatImplementInterface(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isCoversClassesThatImplementInterface(), + ), + ); + } + + public function isCoversTrait(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isCoversTrait(), + ), + ); + } + + public function isCoversFunction(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isCoversFunction(), + ), + ); + } + + public function isCoversMethod(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isCoversMethod(), + ), + ); + } + + public function isExcludeGlobalVariableFromBackup(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isExcludeGlobalVariableFromBackup(), + ), + ); + } + + public function isExcludeStaticPropertyFromBackup(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isExcludeStaticPropertyFromBackup(), + ), + ); + } + + public function isCoversNothing(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isCoversNothing(), + ), + ); + } + + public function isDataProvider(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isDataProvider(), + ), + ); + } + + public function isDepends(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isDependsOnClass() || $metadata->isDependsOnMethod(), + ), + ); + } + + public function isDependsOnClass(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isDependsOnClass(), + ), + ); + } + + public function isDependsOnMethod(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isDependsOnMethod(), + ), + ); + } + + public function isDisableReturnValueGenerationForTestDoubles(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isDisableReturnValueGenerationForTestDoubles(), + ), + ); + } + + public function isDoesNotPerformAssertions(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isDoesNotPerformAssertions(), + ), + ); + } + + public function isGroup(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isGroup(), + ), + ); + } + + public function isIgnoreDeprecations(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isIgnoreDeprecations(), + ), + ); + } + + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function isIgnorePhpunitDeprecations(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isIgnorePhpunitDeprecations(), + ), + ); + } + + public function isIgnorePhpunitWarnings(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isIgnorePhpunitWarnings(), + ), + ); + } + + public function isRunClassInSeparateProcess(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isRunClassInSeparateProcess(), + ), + ); + } + + public function isRunInSeparateProcess(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isRunInSeparateProcess(), + ), + ); + } + + public function isRunTestsInSeparateProcesses(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isRunTestsInSeparateProcesses(), + ), + ); + } + + public function isTest(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isTest(), + ), + ); + } + + public function isPreCondition(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isPreCondition(), + ), + ); + } + + public function isPostCondition(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isPostCondition(), + ), + ); + } + + public function isPreserveGlobalState(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isPreserveGlobalState(), + ), + ); + } + + public function isRequiresMethod(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isRequiresMethod(), + ), + ); + } + + public function isRequiresFunction(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isRequiresFunction(), + ), + ); + } + + public function isRequiresOperatingSystem(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isRequiresOperatingSystem(), + ), + ); + } + + public function isRequiresOperatingSystemFamily(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isRequiresOperatingSystemFamily(), + ), + ); + } + + public function isRequiresPhp(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isRequiresPhp(), + ), + ); + } + + public function isRequiresPhpExtension(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isRequiresPhpExtension(), + ), + ); + } + + public function isRequiresPhpunit(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isRequiresPhpunit(), + ), + ); + } + + public function isRequiresPhpunitExtension(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isRequiresPhpunitExtension(), + ), + ); + } + + public function isRequiresEnvironmentVariable(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isRequiresEnvironmentVariable(), + ), + ); + } + + public function isWithEnvironmentVariable(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isWithEnvironmentVariable(), + ), + ); + } + + public function isRequiresSetting(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isRequiresSetting(), + ), + ); + } + + public function isTestDox(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isTestDox(), + ), + ); + } + + public function isTestDoxFormatter(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isTestDoxFormatter(), + ), + ); + } + + public function isTestWith(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isTestWith(), + ), + ); + } + + public function isUsesNamespace(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isUsesNamespace(), + ), + ); + } + + public function isUsesClass(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isUsesClass(), + ), + ); + } + + public function isUsesClassesThatExtendClass(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isUsesClassesThatExtendClass(), + ), + ); + } + + public function isUsesClassesThatImplementInterface(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isUsesClassesThatImplementInterface(), + ), + ); + } + + public function isUsesTrait(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isUsesTrait(), + ), + ); + } + + public function isUsesFunction(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isUsesFunction(), + ), + ); + } + + public function isUsesMethod(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isUsesMethod(), + ), + ); + } + + public function isWithoutErrorHandler(): self + { + return new self( + ...array_filter( + $this->metadata, + static fn (Metadata $metadata): bool => $metadata->isWithoutErrorHandler(), + ), + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/MetadataCollectionIterator.php b/app/vendor/phpunit/phpunit/src/Metadata/MetadataCollectionIterator.php new file mode 100644 index 000000000..bfe398999 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/MetadataCollectionIterator.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 PHPUnit\Metadata; + +use function count; +use Iterator; + +/** + * @template-implements Iterator + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class MetadataCollectionIterator implements Iterator +{ + /** + * @var list + */ + private readonly array $metadata; + private int $position = 0; + + public function __construct(MetadataCollection $metadata) + { + $this->metadata = $metadata->asArray(); + } + + public function rewind(): void + { + $this->position = 0; + } + + public function valid(): bool + { + return $this->position < count($this->metadata); + } + + public function key(): int + { + return $this->position; + } + + public function current(): Metadata + { + return $this->metadata[$this->position]; + } + + public function next(): void + { + $this->position++; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/Parser/AttributeParser.php b/app/vendor/phpunit/phpunit/src/Metadata/Parser/AttributeParser.php new file mode 100644 index 000000000..d95cab77f --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/Parser/AttributeParser.php @@ -0,0 +1,967 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata\Parser; + +use const JSON_THROW_ON_ERROR; +use function assert; +use function class_exists; +use function json_decode; +use function method_exists; +use function sprintf; +use function str_starts_with; +use function strtolower; +use function trim; +use Error; +use PHPUnit\Event\Facade as EventFacade; +use PHPUnit\Framework\Attributes\After; +use PHPUnit\Framework\Attributes\AfterClass; +use PHPUnit\Framework\Attributes\BackupGlobals; +use PHPUnit\Framework\Attributes\BackupStaticProperties; +use PHPUnit\Framework\Attributes\Before; +use PHPUnit\Framework\Attributes\BeforeClass; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\CoversClassesThatExtendClass; +use PHPUnit\Framework\Attributes\CoversClassesThatImplementInterface; +use PHPUnit\Framework\Attributes\CoversFunction; +use PHPUnit\Framework\Attributes\CoversMethod; +use PHPUnit\Framework\Attributes\CoversNamespace; +use PHPUnit\Framework\Attributes\CoversNothing; +use PHPUnit\Framework\Attributes\CoversTrait; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\DataProviderExternal; +use PHPUnit\Framework\Attributes\Depends; +use PHPUnit\Framework\Attributes\DependsExternal; +use PHPUnit\Framework\Attributes\DependsExternalUsingDeepClone; +use PHPUnit\Framework\Attributes\DependsExternalUsingShallowClone; +use PHPUnit\Framework\Attributes\DependsOnClass; +use PHPUnit\Framework\Attributes\DependsOnClassUsingDeepClone; +use PHPUnit\Framework\Attributes\DependsOnClassUsingShallowClone; +use PHPUnit\Framework\Attributes\DependsUsingDeepClone; +use PHPUnit\Framework\Attributes\DependsUsingShallowClone; +use PHPUnit\Framework\Attributes\DisableReturnValueGenerationForTestDoubles; +use PHPUnit\Framework\Attributes\DoesNotPerformAssertions; +use PHPUnit\Framework\Attributes\ExcludeGlobalVariableFromBackup; +use PHPUnit\Framework\Attributes\ExcludeStaticPropertyFromBackup; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\IgnoreDeprecations; +use PHPUnit\Framework\Attributes\IgnorePhpunitDeprecations; +use PHPUnit\Framework\Attributes\IgnorePhpunitWarnings; +use PHPUnit\Framework\Attributes\Large; +use PHPUnit\Framework\Attributes\Medium; +use PHPUnit\Framework\Attributes\PostCondition; +use PHPUnit\Framework\Attributes\PreCondition; +use PHPUnit\Framework\Attributes\PreserveGlobalState; +use PHPUnit\Framework\Attributes\RequiresEnvironmentVariable; +use PHPUnit\Framework\Attributes\RequiresFunction; +use PHPUnit\Framework\Attributes\RequiresMethod; +use PHPUnit\Framework\Attributes\RequiresOperatingSystem; +use PHPUnit\Framework\Attributes\RequiresOperatingSystemFamily; +use PHPUnit\Framework\Attributes\RequiresPhp; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; +use PHPUnit\Framework\Attributes\RequiresPhpunit; +use PHPUnit\Framework\Attributes\RequiresPhpunitExtension; +use PHPUnit\Framework\Attributes\RequiresSetting; +use PHPUnit\Framework\Attributes\RunClassInSeparateProcess; +use PHPUnit\Framework\Attributes\RunInSeparateProcess; +use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\Attributes\TestDoxFormatter; +use PHPUnit\Framework\Attributes\TestDoxFormatterExternal; +use PHPUnit\Framework\Attributes\TestWith; +use PHPUnit\Framework\Attributes\TestWithJson; +use PHPUnit\Framework\Attributes\Ticket; +use PHPUnit\Framework\Attributes\UsesClass; +use PHPUnit\Framework\Attributes\UsesClassesThatExtendClass; +use PHPUnit\Framework\Attributes\UsesClassesThatImplementInterface; +use PHPUnit\Framework\Attributes\UsesFunction; +use PHPUnit\Framework\Attributes\UsesMethod; +use PHPUnit\Framework\Attributes\UsesNamespace; +use PHPUnit\Framework\Attributes\UsesTrait; +use PHPUnit\Framework\Attributes\WithEnvironmentVariable; +use PHPUnit\Framework\Attributes\WithoutErrorHandler; +use PHPUnit\Metadata\InvalidAttributeException; +use PHPUnit\Metadata\Metadata; +use PHPUnit\Metadata\MetadataCollection; +use PHPUnit\Metadata\Version\ConstraintRequirement; +use ReflectionClass; +use ReflectionMethod; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class AttributeParser implements Parser +{ + /** + * @param class-string $className + */ + public function forClass(string $className): MetadataCollection + { + assert(class_exists($className)); + + $reflector = new ReflectionClass($className); + $result = []; + + $small = false; + $medium = false; + $large = false; + + foreach ($reflector->getAttributes() as $attribute) { + if (!str_starts_with($attribute->getName(), 'PHPUnit\\Framework\\Attributes\\')) { + continue; + } + + if (!class_exists($attribute->getName())) { + continue; + } + + try { + $attributeInstance = $attribute->newInstance(); + } catch (Error $e) { + throw new InvalidAttributeException( + $attribute->getName(), + 'class ' . $className, + $reflector->getFileName(), + $reflector->getStartLine(), + $e->getMessage(), + ); + } + + switch ($attribute->getName()) { + case BackupGlobals::class: + assert($attributeInstance instanceof BackupGlobals); + + $result[] = Metadata::backupGlobalsOnClass($attributeInstance->enabled()); + + break; + + case BackupStaticProperties::class: + assert($attributeInstance instanceof BackupStaticProperties); + + $result[] = Metadata::backupStaticPropertiesOnClass($attributeInstance->enabled()); + + break; + + case CoversNamespace::class: + assert($attributeInstance instanceof CoversNamespace); + + $result[] = Metadata::coversNamespace($attributeInstance->namespace()); + + break; + + case CoversClass::class: + assert($attributeInstance instanceof CoversClass); + + $result[] = Metadata::coversClass($attributeInstance->className()); + + break; + + case CoversClassesThatExtendClass::class: + assert($attributeInstance instanceof CoversClassesThatExtendClass); + + $result[] = Metadata::coversClassesThatExtendClass($attributeInstance->className()); + + break; + + case CoversClassesThatImplementInterface::class: + assert($attributeInstance instanceof CoversClassesThatImplementInterface); + + $result[] = Metadata::coversClassesThatImplementInterface($attributeInstance->interfaceName()); + + break; + + case CoversTrait::class: + assert($attributeInstance instanceof CoversTrait); + + $result[] = Metadata::coversTrait($attributeInstance->traitName()); + + break; + + case CoversFunction::class: + assert($attributeInstance instanceof CoversFunction); + + $result[] = Metadata::coversFunction($attributeInstance->functionName()); + + break; + + case CoversMethod::class: + assert($attributeInstance instanceof CoversMethod); + + $result[] = Metadata::coversMethod( + $attributeInstance->className(), + $attributeInstance->methodName(), + ); + + break; + + case CoversNothing::class: + $result[] = Metadata::coversNothingOnClass(); + + break; + + case DisableReturnValueGenerationForTestDoubles::class: + $result[] = Metadata::disableReturnValueGenerationForTestDoubles(); + + break; + + case DoesNotPerformAssertions::class: + $result[] = Metadata::doesNotPerformAssertionsOnClass(); + + break; + + case ExcludeGlobalVariableFromBackup::class: + assert($attributeInstance instanceof ExcludeGlobalVariableFromBackup); + + $result[] = Metadata::excludeGlobalVariableFromBackupOnClass($attributeInstance->globalVariableName()); + + break; + + case ExcludeStaticPropertyFromBackup::class: + assert($attributeInstance instanceof ExcludeStaticPropertyFromBackup); + + $result[] = Metadata::excludeStaticPropertyFromBackupOnClass( + $attributeInstance->className(), + $attributeInstance->propertyName(), + ); + + break; + + case Group::class: + assert($attributeInstance instanceof Group); + + if (!$this->isSizeGroup($attributeInstance->name(), $className)) { + $result[] = Metadata::groupOnClass($attributeInstance->name()); + } + + break; + + case Small::class: + if (!$medium && !$large) { + $result[] = Metadata::groupOnClass('small'); + + $small = true; + } else { + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + '#[Small] cannot be combined with #[Medium] or #[Large] for %s', + $this->testAsString($className), + ), + ); + } + + break; + + case Medium::class: + if (!$small && !$large) { + $result[] = Metadata::groupOnClass('medium'); + + $medium = true; + } else { + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + '#[Medium] cannot be combined with #[Small] or #[Large] for %s', + $this->testAsString($className), + ), + ); + } + + break; + + case Large::class: + if (!$small && !$medium) { + $result[] = Metadata::groupOnClass('large'); + + $large = true; + } else { + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + '#[Large] cannot be combined with #[Small] or #[Medium] for %s', + $this->testAsString($className), + ), + ); + } + + break; + + case IgnoreDeprecations::class: + assert($attributeInstance instanceof IgnoreDeprecations); + + $result[] = Metadata::ignoreDeprecationsOnClass(); + + break; + + case IgnorePhpunitDeprecations::class: + assert($attributeInstance instanceof IgnorePhpunitDeprecations); + + $result[] = Metadata::ignorePhpunitDeprecationsOnClass(); + + break; + + case PreserveGlobalState::class: + assert($attributeInstance instanceof PreserveGlobalState); + + $result[] = Metadata::preserveGlobalStateOnClass($attributeInstance->enabled()); + + break; + + case RequiresMethod::class: + assert($attributeInstance instanceof RequiresMethod); + + $result[] = Metadata::requiresMethodOnClass( + $attributeInstance->className(), + $attributeInstance->methodName(), + ); + + break; + + case RequiresFunction::class: + assert($attributeInstance instanceof RequiresFunction); + + $result[] = Metadata::requiresFunctionOnClass($attributeInstance->functionName()); + + break; + + case RequiresOperatingSystem::class: + assert($attributeInstance instanceof RequiresOperatingSystem); + + $result[] = Metadata::requiresOperatingSystemOnClass($attributeInstance->regularExpression()); + + break; + + case RequiresOperatingSystemFamily::class: + assert($attributeInstance instanceof RequiresOperatingSystemFamily); + + $result[] = Metadata::requiresOperatingSystemFamilyOnClass($attributeInstance->operatingSystemFamily()); + + break; + + case RequiresPhp::class: + assert($attributeInstance instanceof RequiresPhp); + + $result[] = Metadata::requiresPhpOnClass( + ConstraintRequirement::from( + $attributeInstance->versionRequirement(), + ), + ); + + break; + + case RequiresPhpExtension::class: + assert($attributeInstance instanceof RequiresPhpExtension); + + $versionConstraint = null; + $versionRequirement = $attributeInstance->versionRequirement(); + + if ($versionRequirement !== null) { + $versionConstraint = ConstraintRequirement::from($versionRequirement); + } + + $result[] = Metadata::requiresPhpExtensionOnClass( + $attributeInstance->extension(), + $versionConstraint, + ); + + break; + + case RequiresPhpunit::class: + assert($attributeInstance instanceof RequiresPhpunit); + + $result[] = Metadata::requiresPhpunitOnClass( + ConstraintRequirement::from( + $attributeInstance->versionRequirement(), + ), + ); + + break; + + case RequiresPhpunitExtension::class: + assert($attributeInstance instanceof RequiresPhpunitExtension); + + $result[] = Metadata::requiresPhpunitExtensionOnClass( + $attributeInstance->extensionClass(), + ); + + break; + + case RequiresEnvironmentVariable::class: + assert($attributeInstance instanceof RequiresEnvironmentVariable); + + $result[] = Metadata::requiresEnvironmentVariableOnClass( + $attributeInstance->environmentVariableName(), + $attributeInstance->value(), + ); + + break; + + case WithEnvironmentVariable::class: + assert($attributeInstance instanceof WithEnvironmentVariable); + + $result[] = Metadata::withEnvironmentVariableOnClass( + $attributeInstance->environmentVariableName(), + $attributeInstance->value(), + ); + + break; + + case RequiresSetting::class: + assert($attributeInstance instanceof RequiresSetting); + + $result[] = Metadata::requiresSettingOnClass( + $attributeInstance->setting(), + $attributeInstance->value(), + ); + + break; + + case RunClassInSeparateProcess::class: + $result[] = Metadata::runClassInSeparateProcess(); + + break; + + case RunTestsInSeparateProcesses::class: + $result[] = Metadata::runTestsInSeparateProcesses(); + + break; + + case TestDox::class: + assert($attributeInstance instanceof TestDox); + + $result[] = Metadata::testDoxOnClass($attributeInstance->text()); + + break; + + case Ticket::class: + assert($attributeInstance instanceof Ticket); + + $result[] = Metadata::groupOnClass($attributeInstance->text()); + + break; + + case UsesNamespace::class: + assert($attributeInstance instanceof UsesNamespace); + + $result[] = Metadata::usesNamespace($attributeInstance->namespace()); + + break; + + case UsesClass::class: + assert($attributeInstance instanceof UsesClass); + + $result[] = Metadata::usesClass($attributeInstance->className()); + + break; + + case UsesClassesThatExtendClass::class: + assert($attributeInstance instanceof UsesClassesThatExtendClass); + + $result[] = Metadata::usesClassesThatExtendClass($attributeInstance->className()); + + break; + + case UsesClassesThatImplementInterface::class: + assert($attributeInstance instanceof UsesClassesThatImplementInterface); + + $result[] = Metadata::usesClassesThatImplementInterface($attributeInstance->interfaceName()); + + break; + + case UsesTrait::class: + assert($attributeInstance instanceof UsesTrait); + + $result[] = Metadata::usesTrait($attributeInstance->traitName()); + + break; + + case UsesFunction::class: + assert($attributeInstance instanceof UsesFunction); + + $result[] = Metadata::usesFunction($attributeInstance->functionName()); + + break; + + case UsesMethod::class: + assert($attributeInstance instanceof UsesMethod); + + $result[] = Metadata::usesMethod( + $attributeInstance->className(), + $attributeInstance->methodName(), + ); + + break; + } + } + + return MetadataCollection::fromArray($result); + } + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public function forMethod(string $className, string $methodName): MetadataCollection + { + assert(class_exists($className)); + assert(method_exists($className, $methodName)); + + $reflector = new ReflectionMethod($className, $methodName); + $result = []; + + foreach ($reflector->getAttributes() as $attribute) { + if (!str_starts_with($attribute->getName(), 'PHPUnit\\Framework\\Attributes\\')) { + continue; + } + + if (!class_exists($attribute->getName())) { + continue; + } + + try { + $attributeInstance = $attribute->newInstance(); + } catch (Error $e) { + throw new InvalidAttributeException( + $attribute->getName(), + 'method ' . $className . '::' . $methodName . '()', + $reflector->getFileName(), + $reflector->getStartLine(), + $e->getMessage(), + ); + } + + switch ($attribute->getName()) { + case After::class: + assert($attributeInstance instanceof After); + + $result[] = Metadata::after($attributeInstance->priority()); + + break; + + case AfterClass::class: + assert($attributeInstance instanceof AfterClass); + + $result[] = Metadata::afterClass($attributeInstance->priority()); + + break; + + case BackupGlobals::class: + assert($attributeInstance instanceof BackupGlobals); + + $result[] = Metadata::backupGlobalsOnMethod($attributeInstance->enabled()); + + break; + + case BackupStaticProperties::class: + assert($attributeInstance instanceof BackupStaticProperties); + + $result[] = Metadata::backupStaticPropertiesOnMethod($attributeInstance->enabled()); + + break; + + case Before::class: + assert($attributeInstance instanceof Before); + + $result[] = Metadata::before($attributeInstance->priority()); + + break; + + case BeforeClass::class: + assert($attributeInstance instanceof BeforeClass); + + $result[] = Metadata::beforeClass($attributeInstance->priority()); + + break; + + case CoversNothing::class: + $result[] = Metadata::coversNothingOnMethod(); + + break; + + case DataProvider::class: + assert($attributeInstance instanceof DataProvider); + + $result[] = Metadata::dataProvider($className, $attributeInstance->methodName()); + + break; + + case DataProviderExternal::class: + assert($attributeInstance instanceof DataProviderExternal); + + $result[] = Metadata::dataProvider($attributeInstance->className(), $attributeInstance->methodName()); + + break; + + case Depends::class: + assert($attributeInstance instanceof Depends); + + $result[] = Metadata::dependsOnMethod($className, $attributeInstance->methodName(), false, false); + + break; + + case DependsUsingDeepClone::class: + assert($attributeInstance instanceof DependsUsingDeepClone); + + $result[] = Metadata::dependsOnMethod($className, $attributeInstance->methodName(), true, false); + + break; + + case DependsUsingShallowClone::class: + assert($attributeInstance instanceof DependsUsingShallowClone); + + $result[] = Metadata::dependsOnMethod($className, $attributeInstance->methodName(), false, true); + + break; + + case DependsExternal::class: + assert($attributeInstance instanceof DependsExternal); + + $result[] = Metadata::dependsOnMethod($attributeInstance->className(), $attributeInstance->methodName(), false, false); + + break; + + case DependsExternalUsingDeepClone::class: + assert($attributeInstance instanceof DependsExternalUsingDeepClone); + + $result[] = Metadata::dependsOnMethod($attributeInstance->className(), $attributeInstance->methodName(), true, false); + + break; + + case DependsExternalUsingShallowClone::class: + assert($attributeInstance instanceof DependsExternalUsingShallowClone); + + $result[] = Metadata::dependsOnMethod($attributeInstance->className(), $attributeInstance->methodName(), false, true); + + break; + + case DependsOnClass::class: + assert($attributeInstance instanceof DependsOnClass); + + $result[] = Metadata::dependsOnClass($attributeInstance->className(), false, false); + + break; + + case DependsOnClassUsingDeepClone::class: + assert($attributeInstance instanceof DependsOnClassUsingDeepClone); + + $result[] = Metadata::dependsOnClass($attributeInstance->className(), true, false); + + break; + + case DependsOnClassUsingShallowClone::class: + assert($attributeInstance instanceof DependsOnClassUsingShallowClone); + + $result[] = Metadata::dependsOnClass($attributeInstance->className(), false, true); + + break; + + case DoesNotPerformAssertions::class: + assert($attributeInstance instanceof DoesNotPerformAssertions); + + $result[] = Metadata::doesNotPerformAssertionsOnMethod(); + + break; + + case ExcludeGlobalVariableFromBackup::class: + assert($attributeInstance instanceof ExcludeGlobalVariableFromBackup); + + $result[] = Metadata::excludeGlobalVariableFromBackupOnMethod($attributeInstance->globalVariableName()); + + break; + + case ExcludeStaticPropertyFromBackup::class: + assert($attributeInstance instanceof ExcludeStaticPropertyFromBackup); + + $result[] = Metadata::excludeStaticPropertyFromBackupOnMethod( + $attributeInstance->className(), + $attributeInstance->propertyName(), + ); + + break; + + case Group::class: + assert($attributeInstance instanceof Group); + + if (!$this->isSizeGroup($attributeInstance->name(), $className, $methodName)) { + $result[] = Metadata::groupOnMethod($attributeInstance->name()); + } + + break; + + case IgnoreDeprecations::class: + assert($attributeInstance instanceof IgnoreDeprecations); + + $result[] = Metadata::ignoreDeprecationsOnMethod(); + + break; + + case IgnorePhpunitDeprecations::class: + assert($attributeInstance instanceof IgnorePhpunitDeprecations); + + $result[] = Metadata::ignorePhpunitDeprecationsOnMethod(); + + break; + + case PostCondition::class: + assert($attributeInstance instanceof PostCondition); + + $result[] = Metadata::postCondition($attributeInstance->priority()); + + break; + + case PreCondition::class: + assert($attributeInstance instanceof PreCondition); + + $result[] = Metadata::preCondition($attributeInstance->priority()); + + break; + + case PreserveGlobalState::class: + assert($attributeInstance instanceof PreserveGlobalState); + + $result[] = Metadata::preserveGlobalStateOnMethod($attributeInstance->enabled()); + + break; + + case RequiresMethod::class: + assert($attributeInstance instanceof RequiresMethod); + + $result[] = Metadata::requiresMethodOnMethod( + $attributeInstance->className(), + $attributeInstance->methodName(), + ); + + break; + + case RequiresFunction::class: + assert($attributeInstance instanceof RequiresFunction); + + $result[] = Metadata::requiresFunctionOnMethod($attributeInstance->functionName()); + + break; + + case RequiresOperatingSystem::class: + assert($attributeInstance instanceof RequiresOperatingSystem); + + $result[] = Metadata::requiresOperatingSystemOnMethod($attributeInstance->regularExpression()); + + break; + + case RequiresOperatingSystemFamily::class: + assert($attributeInstance instanceof RequiresOperatingSystemFamily); + + $result[] = Metadata::requiresOperatingSystemFamilyOnMethod($attributeInstance->operatingSystemFamily()); + + break; + + case RequiresPhp::class: + assert($attributeInstance instanceof RequiresPhp); + + $result[] = Metadata::requiresPhpOnMethod( + ConstraintRequirement::from( + $attributeInstance->versionRequirement(), + ), + ); + + break; + + case RequiresPhpExtension::class: + assert($attributeInstance instanceof RequiresPhpExtension); + + $versionConstraint = null; + $versionRequirement = $attributeInstance->versionRequirement(); + + if ($versionRequirement !== null) { + $versionConstraint = ConstraintRequirement::from($versionRequirement); + } + + $result[] = Metadata::requiresPhpExtensionOnMethod( + $attributeInstance->extension(), + $versionConstraint, + ); + + break; + + case RequiresPhpunit::class: + assert($attributeInstance instanceof RequiresPhpunit); + + $result[] = Metadata::requiresPhpunitOnMethod( + ConstraintRequirement::from( + $attributeInstance->versionRequirement(), + ), + ); + + break; + + case RequiresPhpunitExtension::class: + assert($attributeInstance instanceof RequiresPhpunitExtension); + + $result[] = Metadata::requiresPhpunitExtensionOnMethod( + $attributeInstance->extensionClass(), + ); + + break; + + case RequiresEnvironmentVariable::class: + assert($attributeInstance instanceof RequiresEnvironmentVariable); + + $result[] = Metadata::requiresEnvironmentVariableOnMethod( + $attributeInstance->environmentVariableName(), + $attributeInstance->value(), + ); + + break; + + case WithEnvironmentVariable::class: + assert($attributeInstance instanceof WithEnvironmentVariable); + + $result[] = Metadata::withEnvironmentVariableOnMethod( + $attributeInstance->environmentVariableName(), + $attributeInstance->value(), + ); + + break; + + case RequiresSetting::class: + assert($attributeInstance instanceof RequiresSetting); + + $result[] = Metadata::requiresSettingOnMethod( + $attributeInstance->setting(), + $attributeInstance->value(), + ); + + break; + + case RunInSeparateProcess::class: + $result[] = Metadata::runInSeparateProcess(); + + break; + + case Test::class: + $result[] = Metadata::test(); + + break; + + case TestDox::class: + assert($attributeInstance instanceof TestDox); + + $result[] = Metadata::testDoxOnMethod($attributeInstance->text()); + + break; + + case TestDoxFormatter::class: + assert($attributeInstance instanceof TestDoxFormatter); + + $result[] = Metadata::testDoxFormatter($className, $attributeInstance->methodName()); + + break; + + case TestDoxFormatterExternal::class: + assert($attributeInstance instanceof TestDoxFormatterExternal); + + $result[] = Metadata::testDoxFormatter($attributeInstance->className(), $attributeInstance->methodName()); + + break; + + case TestWith::class: + assert($attributeInstance instanceof TestWith); + + $result[] = Metadata::testWith($attributeInstance->data(), $attributeInstance->name()); + + break; + + case TestWithJson::class: + assert($attributeInstance instanceof TestWithJson); + + $result[] = Metadata::testWith( + json_decode($attributeInstance->json(), true, 512, JSON_THROW_ON_ERROR), + $attributeInstance->name(), + ); + + break; + + case Ticket::class: + assert($attributeInstance instanceof Ticket); + + $result[] = Metadata::groupOnMethod($attributeInstance->text()); + + break; + + case WithoutErrorHandler::class: + assert($attributeInstance instanceof WithoutErrorHandler); + + $result[] = Metadata::withoutErrorHandler(); + + break; + + case IgnorePhpunitWarnings::class: + assert($attributeInstance instanceof IgnorePhpunitWarnings); + + $result[] = Metadata::ignorePhpunitWarnings($attributeInstance->messagePattern()); + + break; + } + } + + return MetadataCollection::fromArray($result); + } + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public function forClassAndMethod(string $className, string $methodName): MetadataCollection + { + return $this->forClass($className)->mergeWith( + $this->forMethod($className, $methodName), + ); + } + + /** + * @param non-empty-string $groupName + * @param class-string $testClassName + * @param ?non-empty-string $testMethodName + */ + private function isSizeGroup(string $groupName, string $testClassName, ?string $testMethodName = null): bool + { + $_groupName = strtolower(trim($groupName)); + + if ($_groupName !== 'small' && $_groupName !== 'medium' && $_groupName !== 'large') { + return false; + } + + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + 'Group name "%s" is not allowed for %s', + $_groupName, + $this->testAsString($testClassName, $testMethodName), + ), + ); + + return true; + } + + /** + * @param class-string $testClassName + * @param ?non-empty-string $testMethodName + * + * @return non-empty-string + */ + private function testAsString(string $testClassName, ?string $testMethodName = null): string + { + return sprintf( + '%s %s%s%s', + $testMethodName !== null ? 'method' : 'class', + $testClassName, + $testMethodName !== null ? '::' : '', + $testMethodName !== null ? $testMethodName : '', + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/Parser/CachingParser.php b/app/vendor/phpunit/phpunit/src/Metadata/Parser/CachingParser.php new file mode 100644 index 000000000..7c274c15e --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/Parser/CachingParser.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 PHPUnit\Metadata\Parser; + +use function assert; +use function class_exists; +use function method_exists; +use PHPUnit\Metadata\MetadataCollection; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class CachingParser implements Parser +{ + private readonly Parser $reader; + + /** + * @var array + */ + private array $classCache = []; + + /** + * @var array + */ + private array $methodCache = []; + + /** + * @var array + */ + private array $classAndMethodCache = []; + + public function __construct(Parser $reader) + { + $this->reader = $reader; + } + + /** + * @param class-string $className + */ + public function forClass(string $className): MetadataCollection + { + assert(class_exists($className)); + + if (isset($this->classCache[$className])) { + return $this->classCache[$className]; + } + + $this->classCache[$className] = $this->reader->forClass($className); + + return $this->classCache[$className]; + } + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public function forMethod(string $className, string $methodName): MetadataCollection + { + assert(class_exists($className)); + assert(method_exists($className, $methodName)); + + $key = $className . '::' . $methodName; + + if (isset($this->methodCache[$key])) { + return $this->methodCache[$key]; + } + + $this->methodCache[$key] = $this->reader->forMethod($className, $methodName); + + return $this->methodCache[$key]; + } + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public function forClassAndMethod(string $className, string $methodName): MetadataCollection + { + $key = $className . '::' . $methodName; + + if (isset($this->classAndMethodCache[$key])) { + return $this->classAndMethodCache[$key]; + } + + $this->classAndMethodCache[$key] = $this->forClass($className)->mergeWith( + $this->forMethod($className, $methodName), + ); + + return $this->classAndMethodCache[$key]; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/Parser/Parser.php b/app/vendor/phpunit/phpunit/src/Metadata/Parser/Parser.php new file mode 100644 index 000000000..edc2d87f4 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/Parser/Parser.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 PHPUnit\Metadata\Parser; + +use PHPUnit\Metadata\MetadataCollection; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +interface Parser +{ + /** + * @param class-string $className + */ + public function forClass(string $className): MetadataCollection; + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public function forMethod(string $className, string $methodName): MetadataCollection; + + /** + * @param class-string $className + * @param non-empty-string $methodName + */ + public function forClassAndMethod(string $className, string $methodName): MetadataCollection; +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/Parser/Registry.php b/app/vendor/phpunit/phpunit/src/Metadata/Parser/Registry.php new file mode 100644 index 000000000..0b30782ec --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/Parser/Registry.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata\Parser; + +/** + * Attribute information is static within a single PHP process. + * It is therefore okay to use a Singleton registry here. + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Registry +{ + private static ?Parser $instance = null; + + public static function parser(): Parser + { + return self::$instance ?? self::$instance = self::build(); + } + + private static function build(): Parser + { + return new CachingParser(new AttributeParser); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/PostCondition.php b/app/vendor/phpunit/phpunit/src/Metadata/PostCondition.php new file mode 100644 index 000000000..d52ae0628 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/PostCondition.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 PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PostCondition extends Metadata +{ + private int $priority; + + /** + * @param int<0, 1> $level + */ + protected function __construct(int $level, int $priority) + { + parent::__construct($level); + + $this->priority = $priority; + } + + public function isPostCondition(): true + { + return true; + } + + public function priority(): int + { + return $this->priority; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/PreCondition.php b/app/vendor/phpunit/phpunit/src/Metadata/PreCondition.php new file mode 100644 index 000000000..9243122bc --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/PreCondition.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 PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PreCondition extends Metadata +{ + private int $priority; + + /** + * @param int<0, 1> $level + */ + protected function __construct(int $level, int $priority) + { + parent::__construct($level); + + $this->priority = $priority; + } + + public function isPreCondition(): true + { + return true; + } + + public function priority(): int + { + return $this->priority; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/PreserveGlobalState.php b/app/vendor/phpunit/phpunit/src/Metadata/PreserveGlobalState.php new file mode 100644 index 000000000..f88811915 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/PreserveGlobalState.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 PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class PreserveGlobalState extends Metadata +{ + private bool $enabled; + + /** + * @param int<0, 1> $level + */ + protected function __construct(int $level, bool $enabled) + { + parent::__construct($level); + + $this->enabled = $enabled; + } + + public function isPreserveGlobalState(): true + { + return true; + } + + public function enabled(): bool + { + return $this->enabled; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/RequiresEnvironmentVariable.php b/app/vendor/phpunit/phpunit/src/Metadata/RequiresEnvironmentVariable.php new file mode 100644 index 000000000..0888c296d --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/RequiresEnvironmentVariable.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RequiresEnvironmentVariable extends Metadata +{ + private string $environmentVariableName; + private null|string $value; + + public function __construct(int $level, string $environmentVariableName, null|string $value) + { + parent::__construct($level); + + $this->environmentVariableName = $environmentVariableName; + $this->value = $value; + } + + public function isRequiresEnvironmentVariable(): true + { + return true; + } + + public function environmentVariableName(): string + { + return $this->environmentVariableName; + } + + public function value(): null|string + { + return $this->value; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/RequiresFunction.php b/app/vendor/phpunit/phpunit/src/Metadata/RequiresFunction.php new file mode 100644 index 000000000..f66c5180c --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/RequiresFunction.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 PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RequiresFunction extends Metadata +{ + /** + * @var non-empty-string + */ + private string $functionName; + + /** + * @param int<0, 1> $level + * @param non-empty-string $functionName + */ + protected function __construct(int $level, string $functionName) + { + parent::__construct($level); + + $this->functionName = $functionName; + } + + public function isRequiresFunction(): true + { + return true; + } + + /** + * @return non-empty-string + */ + public function functionName(): string + { + return $this->functionName; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/RequiresMethod.php b/app/vendor/phpunit/phpunit/src/Metadata/RequiresMethod.php new file mode 100644 index 000000000..2ad7fbb9a --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/RequiresMethod.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RequiresMethod extends Metadata +{ + /** + * @var class-string + */ + private string $className; + + /** + * @var non-empty-string + */ + private string $methodName; + + /** + * @param int<0, 1> $level + * @param class-string $className + * @param non-empty-string $methodName + */ + protected function __construct(int $level, string $className, string $methodName) + { + parent::__construct($level); + + $this->className = $className; + $this->methodName = $methodName; + } + + public function isRequiresMethod(): true + { + return true; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + /** + * @return non-empty-string + */ + public function methodName(): string + { + return $this->methodName; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/RequiresOperatingSystem.php b/app/vendor/phpunit/phpunit/src/Metadata/RequiresOperatingSystem.php new file mode 100644 index 000000000..8719d2961 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/RequiresOperatingSystem.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 PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RequiresOperatingSystem extends Metadata +{ + /** + * @var non-empty-string + */ + private string $operatingSystem; + + /** + * @param int<0, 1> $level + * @param non-empty-string $operatingSystem + */ + public function __construct(int $level, string $operatingSystem) + { + parent::__construct($level); + + $this->operatingSystem = $operatingSystem; + } + + public function isRequiresOperatingSystem(): true + { + return true; + } + + /** + * @return non-empty-string + */ + public function operatingSystem(): string + { + return $this->operatingSystem; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/RequiresOperatingSystemFamily.php b/app/vendor/phpunit/phpunit/src/Metadata/RequiresOperatingSystemFamily.php new file mode 100644 index 000000000..4481dcf8c --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/RequiresOperatingSystemFamily.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 PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RequiresOperatingSystemFamily extends Metadata +{ + /** + * @var non-empty-string + */ + private string $operatingSystemFamily; + + /** + * @param int<0, 1> $level + * @param non-empty-string $operatingSystemFamily + */ + protected function __construct(int $level, string $operatingSystemFamily) + { + parent::__construct($level); + + $this->operatingSystemFamily = $operatingSystemFamily; + } + + public function isRequiresOperatingSystemFamily(): true + { + return true; + } + + /** + * @return non-empty-string + */ + public function operatingSystemFamily(): string + { + return $this->operatingSystemFamily; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/RequiresPhp.php b/app/vendor/phpunit/phpunit/src/Metadata/RequiresPhp.php new file mode 100644 index 000000000..c64a39628 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/RequiresPhp.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 PHPUnit\Metadata; + +use PHPUnit\Metadata\Version\Requirement; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RequiresPhp extends Metadata +{ + private Requirement $versionRequirement; + + /** + * @param int<0, 1> $level + */ + protected function __construct(int $level, Requirement $versionRequirement) + { + parent::__construct($level); + + $this->versionRequirement = $versionRequirement; + } + + public function isRequiresPhp(): true + { + return true; + } + + public function versionRequirement(): Requirement + { + return $this->versionRequirement; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/RequiresPhpExtension.php b/app/vendor/phpunit/phpunit/src/Metadata/RequiresPhpExtension.php new file mode 100644 index 000000000..5c547ea9e --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/RequiresPhpExtension.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 PHPUnit\Metadata; + +use PHPUnit\Metadata\Version\Requirement; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RequiresPhpExtension extends Metadata +{ + /** + * @var non-empty-string + */ + private string $extension; + private ?Requirement $versionRequirement; + + /** + * @param int<0, 1> $level + * @param non-empty-string $extension + */ + protected function __construct(int $level, string $extension, ?Requirement $versionRequirement) + { + parent::__construct($level); + + $this->extension = $extension; + $this->versionRequirement = $versionRequirement; + } + + public function isRequiresPhpExtension(): true + { + return true; + } + + /** + * @return non-empty-string + */ + public function extension(): string + { + return $this->extension; + } + + /** + * @phpstan-assert-if-true !null $this->versionRequirement + */ + public function hasVersionRequirement(): bool + { + return $this->versionRequirement !== null; + } + + /** + * @throws NoVersionRequirementException + */ + public function versionRequirement(): Requirement + { + if ($this->versionRequirement === null) { + throw new NoVersionRequirementException; + } + + return $this->versionRequirement; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/RequiresPhpunit.php b/app/vendor/phpunit/phpunit/src/Metadata/RequiresPhpunit.php new file mode 100644 index 000000000..dc8ae80e2 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/RequiresPhpunit.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 PHPUnit\Metadata; + +use PHPUnit\Metadata\Version\Requirement; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RequiresPhpunit extends Metadata +{ + private Requirement $versionRequirement; + + /** + * @param int<0, 1> $level + */ + protected function __construct(int $level, Requirement $versionRequirement) + { + parent::__construct($level); + + $this->versionRequirement = $versionRequirement; + } + + public function isRequiresPhpunit(): true + { + return true; + } + + public function versionRequirement(): Requirement + { + return $this->versionRequirement; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/RequiresPhpunitExtension.php b/app/vendor/phpunit/phpunit/src/Metadata/RequiresPhpunitExtension.php new file mode 100644 index 000000000..726c3b961 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/RequiresPhpunitExtension.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 PHPUnit\Metadata; + +use PHPUnit\Runner\Extension\Extension; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RequiresPhpunitExtension extends Metadata +{ + /** + * @var class-string + */ + private string $extensionClass; + + /** + * @param class-string $extensionClass + */ + public function __construct(int $level, string $extensionClass) + { + parent::__construct($level); + + $this->extensionClass = $extensionClass; + } + + public function isRequiresPhpunitExtension(): true + { + return true; + } + + /** + * @return class-string + */ + public function extensionClass(): string + { + return $this->extensionClass; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/RequiresSetting.php b/app/vendor/phpunit/phpunit/src/Metadata/RequiresSetting.php new file mode 100644 index 000000000..0d0b0230a --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/RequiresSetting.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RequiresSetting extends Metadata +{ + /** + * @var non-empty-string + */ + private string $setting; + + /** + * @var non-empty-string + */ + private string $value; + + /** + * @param int<0, 1> $level + * @param non-empty-string $setting + * @param non-empty-string $value + */ + protected function __construct(int $level, string $setting, string $value) + { + parent::__construct($level); + + $this->setting = $setting; + $this->value = $value; + } + + public function isRequiresSetting(): true + { + return true; + } + + /** + * @return non-empty-string + */ + public function setting(): string + { + return $this->setting; + } + + /** + * @return non-empty-string + */ + public function value(): string + { + return $this->value; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/RunClassInSeparateProcess.php b/app/vendor/phpunit/phpunit/src/Metadata/RunClassInSeparateProcess.php new file mode 100644 index 000000000..6f7927cec --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/RunClassInSeparateProcess.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 PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RunClassInSeparateProcess extends Metadata +{ + public function isRunClassInSeparateProcess(): true + { + return true; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/RunInSeparateProcess.php b/app/vendor/phpunit/phpunit/src/Metadata/RunInSeparateProcess.php new file mode 100644 index 000000000..dc755219c --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/RunInSeparateProcess.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 PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RunInSeparateProcess extends Metadata +{ + public function isRunInSeparateProcess(): true + { + return true; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/RunTestsInSeparateProcesses.php b/app/vendor/phpunit/phpunit/src/Metadata/RunTestsInSeparateProcesses.php new file mode 100644 index 000000000..3a544a792 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/RunTestsInSeparateProcesses.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 PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RunTestsInSeparateProcesses extends Metadata +{ + public function isRunTestsInSeparateProcesses(): true + { + return true; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/Test.php b/app/vendor/phpunit/phpunit/src/Metadata/Test.php new file mode 100644 index 000000000..04007610a --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/Test.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 PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Test extends Metadata +{ + public function isTest(): true + { + return true; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/TestDox.php b/app/vendor/phpunit/phpunit/src/Metadata/TestDox.php new file mode 100644 index 000000000..6e2944e5f --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/TestDox.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 PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestDox extends Metadata +{ + /** + * @var non-empty-string + */ + private string $text; + + /** + * @param int<0, 1> $level + * @param non-empty-string $text + */ + protected function __construct(int $level, string $text) + { + parent::__construct($level); + + $this->text = $text; + } + + public function isTestDox(): true + { + return true; + } + + /** + * @return non-empty-string + */ + public function text(): string + { + return $this->text; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/TestDoxFormatter.php b/app/vendor/phpunit/phpunit/src/Metadata/TestDoxFormatter.php new file mode 100644 index 000000000..a7f055b87 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/TestDoxFormatter.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestDoxFormatter extends Metadata +{ + /** + * @var class-string + */ + private string $className; + + /** + * @var non-empty-string + */ + private string $methodName; + + /** + * @param int<0, 1> $level + * @param class-string $className + * @param non-empty-string $methodName + */ + protected function __construct(int $level, string $className, string $methodName) + { + parent::__construct($level); + + $this->className = $className; + $this->methodName = $methodName; + } + + public function isTestDoxFormatter(): true + { + return true; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + /** + * @return non-empty-string + */ + public function methodName(): string + { + return $this->methodName; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/TestWith.php b/app/vendor/phpunit/phpunit/src/Metadata/TestWith.php new file mode 100644 index 000000000..d704d863f --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/TestWith.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 PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestWith extends Metadata +{ + private mixed $data; + + /** + * @var ?non-empty-string + */ + private ?string $name; + + /** + * @param int<0, 1> $level + * @param ?non-empty-string $name + */ + protected function __construct(int $level, mixed $data, ?string $name = null) + { + parent::__construct($level); + + $this->data = $data; + $this->name = $name; + } + + public function isTestWith(): true + { + return true; + } + + public function data(): mixed + { + return $this->data; + } + + /** + * @phpstan-assert-if-true !null $this->name + */ + public function hasName(): bool + { + return $this->name !== null; + } + + /** + * @return ?non-empty-string + */ + public function name(): ?string + { + return $this->name; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/UsesClass.php b/app/vendor/phpunit/phpunit/src/Metadata/UsesClass.php new file mode 100644 index 000000000..edbd03ac5 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/UsesClass.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 PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class UsesClass extends Metadata +{ + /** + * @var class-string + */ + private string $className; + + /** + * @param int<0, 1> $level + * @param class-string $className + */ + protected function __construct(int $level, string $className) + { + parent::__construct($level); + + $this->className = $className; + } + + public function isUsesClass(): true + { + return true; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/UsesClassesThatExtendClass.php b/app/vendor/phpunit/phpunit/src/Metadata/UsesClassesThatExtendClass.php new file mode 100644 index 000000000..baddfeb6b --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/UsesClassesThatExtendClass.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 PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class UsesClassesThatExtendClass extends Metadata +{ + /** + * @var class-string + */ + private string $className; + + /** + * @param int<0, 1> $level + * @param class-string $className + */ + protected function __construct(int $level, string $className) + { + parent::__construct($level); + + $this->className = $className; + } + + public function isUsesClassesThatExtendClass(): true + { + return true; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/UsesClassesThatImplementInterface.php b/app/vendor/phpunit/phpunit/src/Metadata/UsesClassesThatImplementInterface.php new file mode 100644 index 000000000..5cdc6f629 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/UsesClassesThatImplementInterface.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 PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class UsesClassesThatImplementInterface extends Metadata +{ + /** + * @var class-string + */ + private string $interfaceName; + + /** + * @param int<0, 1> $level + * @param class-string $interfaceName + */ + protected function __construct(int $level, string $interfaceName) + { + parent::__construct($level); + + $this->interfaceName = $interfaceName; + } + + public function isUsesClassesThatImplementInterface(): true + { + return true; + } + + /** + * @return class-string + */ + public function interfaceName(): string + { + return $this->interfaceName; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/UsesFunction.php b/app/vendor/phpunit/phpunit/src/Metadata/UsesFunction.php new file mode 100644 index 000000000..ec6cafee8 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/UsesFunction.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 PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class UsesFunction extends Metadata +{ + /** + * @var non-empty-string + */ + private string $functionName; + + /** + * @param int<0, 1> $level + * @param non-empty-string $functionName + */ + public function __construct(int $level, string $functionName) + { + parent::__construct($level); + + $this->functionName = $functionName; + } + + public function isUsesFunction(): true + { + return true; + } + + /** + * @return non-empty-string + */ + public function functionName(): string + { + return $this->functionName; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/UsesMethod.php b/app/vendor/phpunit/phpunit/src/Metadata/UsesMethod.php new file mode 100644 index 000000000..6fc78f8f2 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/UsesMethod.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class UsesMethod extends Metadata +{ + /** + * @var class-string + */ + private string $className; + + /** + * @var non-empty-string + */ + private string $methodName; + + /** + * @param int<0, 1> $level + * @param class-string $className + * @param non-empty-string $methodName + */ + protected function __construct(int $level, string $className, string $methodName) + { + parent::__construct($level); + + $this->className = $className; + $this->methodName = $methodName; + } + + public function isUsesMethod(): true + { + return true; + } + + /** + * @return class-string + */ + public function className(): string + { + return $this->className; + } + + /** + * @return non-empty-string + */ + public function methodName(): string + { + return $this->methodName; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/UsesNamespace.php b/app/vendor/phpunit/phpunit/src/Metadata/UsesNamespace.php new file mode 100644 index 000000000..f5e5d01ec --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/UsesNamespace.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 PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class UsesNamespace extends Metadata +{ + /** + * @var non-empty-string + */ + private string $namespace; + + /** + * @param int<0, 1> $level + * @param non-empty-string $namespace + */ + protected function __construct(int $level, string $namespace) + { + parent::__construct($level); + + $this->namespace = $namespace; + } + + public function isUsesNamespace(): true + { + return true; + } + + /** + * @return class-string + */ + public function namespace(): string + { + return $this->namespace; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/UsesTrait.php b/app/vendor/phpunit/phpunit/src/Metadata/UsesTrait.php new file mode 100644 index 000000000..19490f26c --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/UsesTrait.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 PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class UsesTrait extends Metadata +{ + /** + * @var trait-string + */ + private string $traitName; + + /** + * @param 0|1 $level + * @param trait-string $traitName + */ + protected function __construct(int $level, string $traitName) + { + parent::__construct($level); + + $this->traitName = $traitName; + } + + public function isUsesTrait(): true + { + return true; + } + + /** + * @return trait-string + */ + public function traitName(): string + { + return $this->traitName; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/Version/ComparisonRequirement.php b/app/vendor/phpunit/phpunit/src/Metadata/Version/ComparisonRequirement.php new file mode 100644 index 000000000..b3b6a1fca --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/Version/ComparisonRequirement.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 PHPUnit\Metadata\Version; + +use function version_compare; +use PHPUnit\Util\VersionComparisonOperator; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ComparisonRequirement extends Requirement +{ + private string $version; + private VersionComparisonOperator $operator; + + public function __construct(string $version, VersionComparisonOperator $operator) + { + $this->version = $version; + $this->operator = $operator; + } + + public function isSatisfiedBy(string $version): bool + { + return version_compare($version, $this->version, $this->operator->asString()); + } + + public function asString(): string + { + return $this->operator->asString() . ' ' . $this->version; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/Version/ConstraintRequirement.php b/app/vendor/phpunit/phpunit/src/Metadata/Version/ConstraintRequirement.php new file mode 100644 index 000000000..107544503 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/Version/ConstraintRequirement.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Metadata\Version; + +use function preg_replace; +use PharIo\Version\Version; +use PharIo\Version\VersionConstraint; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ConstraintRequirement extends Requirement +{ + private VersionConstraint $constraint; + + public function __construct(VersionConstraint $constraint) + { + $this->constraint = $constraint; + } + + public function isSatisfiedBy(string $version): bool + { + return $this->constraint->complies( + new Version($this->sanitize($version)), + ); + } + + public function asString(): string + { + return $this->constraint->asString(); + } + + private function sanitize(string $version): string + { + return preg_replace( + '/^(\d+\.\d+(?:.\d+)?).*$/', + '$1', + $version, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/Version/Requirement.php b/app/vendor/phpunit/phpunit/src/Metadata/Version/Requirement.php new file mode 100644 index 000000000..01f98f731 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/Version/Requirement.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 PHPUnit\Metadata\Version; + +use function preg_match; +use PharIo\Version\UnsupportedVersionConstraintException; +use PharIo\Version\VersionConstraintParser; +use PHPUnit\Metadata\InvalidVersionRequirementException; +use PHPUnit\Util\InvalidVersionOperatorException; +use PHPUnit\Util\VersionComparisonOperator; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +abstract readonly class Requirement +{ + private const string VERSION_COMPARISON = "/(?P!=|<|<=|<>|=|==|>|>=)?\s*(?P[\d\.-]+(dev|(RC|alpha|beta)[\d\.])?)[ \t]*\r?$/m"; + + /** + * @throws InvalidVersionOperatorException + * @throws InvalidVersionRequirementException + */ + public static function from(string $versionRequirement): self + { + try { + return new ConstraintRequirement( + (new VersionConstraintParser)->parse( + $versionRequirement, + ), + ); + } catch (UnsupportedVersionConstraintException) { + if (preg_match(self::VERSION_COMPARISON, $versionRequirement, $matches) > 0) { + return new ComparisonRequirement( + $matches['version'], + new VersionComparisonOperator( + $matches['operator'] !== '' ? $matches['operator'] : '>=', + ), + ); + } + } + + throw new InvalidVersionRequirementException; + } + + abstract public function isSatisfiedBy(string $version): bool; + + abstract public function asString(): string; +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/WithEnvironmentVariable.php b/app/vendor/phpunit/phpunit/src/Metadata/WithEnvironmentVariable.php new file mode 100644 index 000000000..cc4d0fe7d --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/WithEnvironmentVariable.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 PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class WithEnvironmentVariable extends Metadata +{ + /** + * @var non-empty-string + */ + private string $environmentVariableName; + private null|string $value; + + /** + * @param non-empty-string $environmentVariableName + */ + public function __construct(int $level, string $environmentVariableName, null|string $value) + { + parent::__construct($level); + + $this->environmentVariableName = $environmentVariableName; + $this->value = $value; + } + + public function isWithEnvironmentVariable(): true + { + return true; + } + + /** + * @return non-empty-string + */ + public function environmentVariableName(): string + { + return $this->environmentVariableName; + } + + public function value(): null|string + { + return $this->value; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Metadata/WithoutErrorHandler.php b/app/vendor/phpunit/phpunit/src/Metadata/WithoutErrorHandler.php new file mode 100644 index 000000000..a8bf00147 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Metadata/WithoutErrorHandler.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 PHPUnit\Metadata; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class WithoutErrorHandler extends Metadata +{ + public function isWithoutErrorHandler(): true + { + return true; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/BackedUpEnvironmentVariable.php b/app/vendor/phpunit/phpunit/src/Runner/BackedUpEnvironmentVariable.php new file mode 100644 index 000000000..a62ebe95c --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/BackedUpEnvironmentVariable.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 PHPUnit\Runner; + +use function getenv; +use function putenv; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class BackedUpEnvironmentVariable +{ + private const string FROM_GETENV = 'getenv'; + private const string FROM_SUPERGLOBAL = 'superglobal'; + + /** + * @var self::FROM_GETENV|self::FROM_SUPERGLOBAL + */ + private string $from; + + /** + * @var non-empty-string + */ + private string $name; + private null|string $value; + + /** + * @param non-empty-string $name + * + * @return array{0: self, 1: self} + */ + public static function create(string $name): array + { + $getenv = getenv($name); + + if ($getenv === false) { + $getenv = null; + } + + return [ + new self(self::FROM_SUPERGLOBAL, $name, $_ENV[$name] ?? null), + new self(self::FROM_GETENV, $name, $getenv), + ]; + } + + /** + * @param self::FROM_GETENV|self::FROM_SUPERGLOBAL $from + * @param non-empty-string $name + */ + private function __construct(string $from, string $name, null|string $value) + { + $this->from = $from; + $this->name = $name; + $this->value = $value; + } + + public function restore(): void + { + if ($this->from === self::FROM_GETENV) { + $this->restoreGetEnv(); + } else { + $this->restoreSuperGlobal(); + } + } + + private function restoreGetEnv(): void + { + if ($this->value === null) { + putenv($this->name); + } else { + putenv("{$this->name}={$this->value}"); + } + } + + private function restoreSuperGlobal(): void + { + if ($this->value === null) { + unset($_ENV[$this->name]); + } else { + $_ENV[$this->name] = $this->value; + } + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/BaseTestRunner.php b/app/vendor/phpunit/phpunit/src/Runner/BaseTestRunner.php deleted file mode 100644 index bbef329fc..000000000 --- a/app/vendor/phpunit/phpunit/src/Runner/BaseTestRunner.php +++ /dev/null @@ -1,161 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner; - -use function is_dir; -use function is_file; -use function substr; -use PHPUnit\Framework\Exception; -use PHPUnit\Framework\TestSuite; -use ReflectionClass; -use ReflectionException; -use SebastianBergmann\FileIterator\Facade as FileIteratorFacade; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -abstract class BaseTestRunner -{ - /** - * @var int - */ - public const STATUS_UNKNOWN = -1; - - /** - * @var int - */ - public const STATUS_PASSED = 0; - - /** - * @var int - */ - public const STATUS_SKIPPED = 1; - - /** - * @var int - */ - public const STATUS_INCOMPLETE = 2; - - /** - * @var int - */ - public const STATUS_FAILURE = 3; - - /** - * @var int - */ - public const STATUS_ERROR = 4; - - /** - * @var int - */ - public const STATUS_RISKY = 5; - - /** - * @var int - */ - public const STATUS_WARNING = 6; - - /** - * @var string - */ - public const SUITE_METHODNAME = 'suite'; - - /** - * Returns the loader to be used. - */ - public function getLoader(): TestSuiteLoader - { - return new StandardTestSuiteLoader; - } - - /** - * Returns the Test corresponding to the given suite. - * This is a template method, subclasses override - * the runFailed() and clearStatus() methods. - * - * @param string|string[] $suffixes - * - * @throws Exception - */ - public function getTest(string $suiteClassFile, $suffixes = ''): ?TestSuite - { - if (is_dir($suiteClassFile)) { - /** @var string[] $files */ - $files = (new FileIteratorFacade)->getFilesAsArray( - $suiteClassFile, - $suffixes, - ); - - $suite = new TestSuite($suiteClassFile); - $suite->addTestFiles($files); - - return $suite; - } - - if (is_file($suiteClassFile) && substr($suiteClassFile, -5, 5) === '.phpt') { - $suite = new TestSuite; - $suite->addTestFile($suiteClassFile); - - return $suite; - } - - try { - $testClass = $this->loadSuiteClass( - $suiteClassFile, - ); - } catch (\PHPUnit\Exception $e) { - $this->runFailed($e->getMessage()); - - return null; - } - - try { - $suiteMethod = $testClass->getMethod(self::SUITE_METHODNAME); - - if (!$suiteMethod->isStatic()) { - $this->runFailed( - 'suite() method must be static.', - ); - - return null; - } - - $test = $suiteMethod->invoke(null, $testClass->getName()); - } catch (ReflectionException $e) { - $test = new TestSuite($testClass); - } - - $this->clearStatus(); - - return $test; - } - - /** - * Returns the loaded ReflectionClass for a suite name. - */ - protected function loadSuiteClass(string $suiteClassFile): ReflectionClass - { - return $this->getLoader()->load($suiteClassFile); - } - - /** - * Clears the status message. - */ - protected function clearStatus(): void - { - } - - /** - * Override to define how to handle a failed loading of - * a test suite. - */ - abstract protected function runFailed(string $message): void; -} diff --git a/app/vendor/phpunit/phpunit/src/Runner/Baseline/Baseline.php b/app/vendor/phpunit/phpunit/src/Runner/Baseline/Baseline.php new file mode 100644 index 000000000..cffc3791d --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/Baseline/Baseline.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 PHPUnit\Runner\Baseline; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Baseline +{ + public const int VERSION = 1; + + /** + * @var array>> + */ + private array $issues = []; + + public function add(Issue $issue): void + { + if (!isset($this->issues[$issue->file()])) { + $this->issues[$issue->file()] = []; + } + + if (!isset($this->issues[$issue->file()][$issue->line()])) { + $this->issues[$issue->file()][$issue->line()] = []; + } + + $this->issues[$issue->file()][$issue->line()][] = $issue; + } + + public function has(Issue $issue): bool + { + if (!isset($this->issues[$issue->file()][$issue->line()])) { + return false; + } + + foreach ($this->issues[$issue->file()][$issue->line()] as $_issue) { + if ($_issue->equals($issue)) { + return true; + } + } + + return false; + } + + /** + * @return array>> + */ + public function groupedByFileAndLine(): array + { + return $this->issues; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/Baseline/Exception/CannotLoadBaselineException.php b/app/vendor/phpunit/phpunit/src/Runner/Baseline/Exception/CannotLoadBaselineException.php new file mode 100644 index 000000000..c55901365 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/Baseline/Exception/CannotLoadBaselineException.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Baseline; + +use PHPUnit\Runner\Exception; +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class CannotLoadBaselineException extends RuntimeException implements Exception +{ +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/Baseline/Exception/CannotWriteBaselineException.php b/app/vendor/phpunit/phpunit/src/Runner/Baseline/Exception/CannotWriteBaselineException.php new file mode 100644 index 000000000..914a2c34d --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/Baseline/Exception/CannotWriteBaselineException.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Baseline; + +use PHPUnit\Runner\Exception; +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class CannotWriteBaselineException extends RuntimeException implements Exception +{ +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/Baseline/Exception/FileDoesNotHaveLineException.php b/app/vendor/phpunit/phpunit/src/Runner/Baseline/Exception/FileDoesNotHaveLineException.php new file mode 100644 index 000000000..20c6ca030 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/Baseline/Exception/FileDoesNotHaveLineException.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Baseline; + +use function sprintf; +use PHPUnit\Runner\Exception; +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class FileDoesNotHaveLineException extends RuntimeException implements Exception +{ + public function __construct(string $file, int $line) + { + parent::__construct( + sprintf( + 'File "%s" does not have line %d', + $file, + $line, + ), + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/Baseline/Generator.php b/app/vendor/phpunit/phpunit/src/Runner/Baseline/Generator.php new file mode 100644 index 000000000..c5ce074de --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/Baseline/Generator.php @@ -0,0 +1,114 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Baseline; + +use PHPUnit\Event\Facade; +use PHPUnit\Event\Test\DeprecationTriggered; +use PHPUnit\Event\Test\NoticeTriggered; +use PHPUnit\Event\Test\PhpDeprecationTriggered; +use PHPUnit\Event\Test\PhpNoticeTriggered; +use PHPUnit\Event\Test\PhpWarningTriggered; +use PHPUnit\Event\Test\WarningTriggered; +use PHPUnit\Runner\FileDoesNotExistException; +use PHPUnit\TextUI\Configuration\Source; +use PHPUnit\TextUI\Configuration\SourceFilter; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Generator +{ + private Baseline $baseline; + private Source $source; + + public function __construct(Facade $facade, Source $source) + { + $facade->registerSubscribers( + new TestTriggeredDeprecationSubscriber($this), + new TestTriggeredNoticeSubscriber($this), + new TestTriggeredPhpDeprecationSubscriber($this), + new TestTriggeredPhpNoticeSubscriber($this), + new TestTriggeredPhpWarningSubscriber($this), + new TestTriggeredWarningSubscriber($this), + ); + + $this->baseline = new Baseline; + $this->source = $source; + } + + public function baseline(): Baseline + { + return $this->baseline; + } + + /** + * @throws FileDoesNotExistException + * @throws FileDoesNotHaveLineException + */ + public function testTriggeredIssue(DeprecationTriggered|NoticeTriggered|PhpDeprecationTriggered|PhpNoticeTriggered|PhpWarningTriggered|WarningTriggered $event): void + { + if ($event->wasSuppressed() && !$this->isSuppressionIgnored($event)) { + return; + } + + if ($this->restrict($event) && !SourceFilter::instance()->includes($event->file())) { + return; + } + + $this->baseline->add( + Issue::from( + $event->file(), + $event->line(), + null, + $event->message(), + ), + ); + } + + private function restrict(DeprecationTriggered|NoticeTriggered|PhpDeprecationTriggered|PhpNoticeTriggered|PhpWarningTriggered|WarningTriggered $event): bool + { + if ($event instanceof WarningTriggered || $event instanceof PhpWarningTriggered) { + return $this->source->restrictWarnings(); + } + + if ($event instanceof NoticeTriggered || $event instanceof PhpNoticeTriggered) { + return $this->source->restrictNotices(); + } + + return false; + } + + private function isSuppressionIgnored(DeprecationTriggered|NoticeTriggered|PhpDeprecationTriggered|PhpNoticeTriggered|PhpWarningTriggered|WarningTriggered $event): bool + { + if ($event instanceof WarningTriggered) { + return $this->source->ignoreSuppressionOfWarnings(); + } + + if ($event instanceof PhpWarningTriggered) { + return $this->source->ignoreSuppressionOfPhpWarnings(); + } + + if ($event instanceof PhpNoticeTriggered) { + return $this->source->ignoreSuppressionOfPhpNotices(); + } + + if ($event instanceof NoticeTriggered) { + return $this->source->ignoreSuppressionOfNotices(); + } + + if ($event instanceof PhpDeprecationTriggered) { + return $this->source->ignoreSuppressionOfPhpDeprecations(); + } + + return $this->source->ignoreSuppressionOfDeprecations(); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/Baseline/Issue.php b/app/vendor/phpunit/phpunit/src/Runner/Baseline/Issue.php new file mode 100644 index 000000000..869ea26a4 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/Baseline/Issue.php @@ -0,0 +1,147 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Baseline; + +use const FILE_IGNORE_NEW_LINES; +use function assert; +use function file; +use function is_file; +use function sha1; +use PHPUnit\Runner\FileDoesNotExistException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Issue +{ + /** + * @var non-empty-string + */ + private string $file; + + /** + * @var positive-int + */ + private int $line; + + /** + * @var non-empty-string + */ + private string $hash; + + /** + * @var non-empty-string + */ + private string $description; + + /** + * @param non-empty-string $file + * @param positive-int $line + * @param ?non-empty-string $hash + * @param non-empty-string $description + * + * @throws FileDoesNotExistException + * @throws FileDoesNotHaveLineException + */ + public static function from(string $file, int $line, ?string $hash, string $description): self + { + if ($hash === null) { + $hash = self::calculateHash($file, $line); + } + + return new self($file, $line, $hash, $description); + } + + /** + * @param non-empty-string $file + * @param positive-int $line + * @param non-empty-string $hash + * @param non-empty-string $description + */ + private function __construct(string $file, int $line, string $hash, string $description) + { + $this->file = $file; + $this->line = $line; + $this->hash = $hash; + $this->description = $description; + } + + /** + * @return non-empty-string + */ + public function file(): string + { + return $this->file; + } + + /** + * @return positive-int + */ + public function line(): int + { + return $this->line; + } + + /** + * @return non-empty-string + */ + public function hash(): string + { + return $this->hash; + } + + /** + * @return non-empty-string + */ + public function description(): string + { + return $this->description; + } + + public function equals(self $other): bool + { + return $this->file() === $other->file() && + $this->line() === $other->line() && + $this->hash() === $other->hash() && + $this->description() === $other->description(); + } + + /** + * @param non-empty-string $file + * @param positive-int $line + * + * @throws FileDoesNotExistException + * @throws FileDoesNotHaveLineException + * + * @return non-empty-string + */ + private static function calculateHash(string $file, int $line): string + { + $lines = @file($file, FILE_IGNORE_NEW_LINES); + + if ($lines === false && !is_file($file)) { + throw new FileDoesNotExistException($file); + } + + $key = $line - 1; + + if (!isset($lines[$key])) { + throw new FileDoesNotHaveLineException($file, $line); + } + + $hash = sha1($lines[$key]); + + assert($hash !== ''); + + return $hash; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/Baseline/Reader.php b/app/vendor/phpunit/phpunit/src/Runner/Baseline/Reader.php new file mode 100644 index 000000000..ce3a194ff --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/Baseline/Reader.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Baseline; + +use const DIRECTORY_SEPARATOR; +use function assert; +use function dirname; +use function is_file; +use function realpath; +use function sprintf; +use function str_replace; +use function trim; +use DOMElement; +use DOMXPath; +use PHPUnit\Util\Xml\Loader as XmlLoader; +use PHPUnit\Util\Xml\XmlException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Reader +{ + /** + * @param non-empty-string $baselineFile + * + * @throws CannotLoadBaselineException + */ + public function read(string $baselineFile): Baseline + { + if (!is_file($baselineFile)) { + throw new CannotLoadBaselineException( + sprintf( + 'Cannot read baseline %s, file does not exist', + $baselineFile, + ), + ); + } + + try { + $document = (new XmlLoader)->loadFile($baselineFile); + } catch (XmlException $e) { + throw new CannotLoadBaselineException( + sprintf( + 'Cannot read baseline %s: %s', + $baselineFile, + trim($e->getMessage()), + ), + ); + } + + $version = (int) $document->documentElement->getAttribute('version'); + + if ($version !== Baseline::VERSION) { + throw new CannotLoadBaselineException( + sprintf( + 'Cannot read baseline %s, version %d is not supported', + $baselineFile, + $version, + ), + ); + } + + $baseline = new Baseline; + $baselineDirectory = dirname(realpath($baselineFile)); + $xpath = new DOMXPath($document); + + foreach ($xpath->query('file') as $fileElement) { + assert($fileElement instanceof DOMElement); + + $file = $baselineDirectory . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $fileElement->getAttribute('path')); + + foreach ($xpath->query('line', $fileElement) as $lineElement) { + assert($lineElement instanceof DOMElement); + + $line = (int) $lineElement->getAttribute('number'); + $hash = $lineElement->getAttribute('hash'); + + foreach ($xpath->query('issue', $lineElement) as $issueElement) { + assert($issueElement instanceof DOMElement); + + $description = $issueElement->textContent; + + assert($line > 0); + assert($hash !== ''); + assert($description !== ''); + + $baseline->add(Issue::from($file, $line, $hash, $description)); + } + } + } + + return $baseline; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/Baseline/RelativePathCalculator.php b/app/vendor/phpunit/phpunit/src/Runner/Baseline/RelativePathCalculator.php new file mode 100644 index 000000000..a05e6bbea --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/Baseline/RelativePathCalculator.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Baseline; + +use function array_fill; +use function array_merge; +use function array_slice; +use function assert; +use function count; +use function explode; +use function implode; +use function str_replace; +use function strpos; +use function substr; +use function trim; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @see Copied from https://github.com/phpstan/phpstan-src/blob/1.10.33/src/File/ParentDirectoryRelativePathHelper.php + */ +final readonly class RelativePathCalculator +{ + /** + * @var non-empty-string + */ + private string $baselineDirectory; + + /** + * @param non-empty-string $baselineDirectory + */ + public function __construct(string $baselineDirectory) + { + $this->baselineDirectory = $baselineDirectory; + } + + /** + * @param non-empty-string $filename + * + * @return non-empty-string + */ + public function calculate(string $filename): string + { + $result = implode('/', $this->parts($filename)); + + assert($result !== ''); + + return $result; + } + + /** + * @param non-empty-string $filename + * + * @return list + */ + public function parts(string $filename): array + { + $schemePosition = strpos($filename, '://'); + + if ($schemePosition !== false) { + $filename = substr($filename, $schemePosition + 3); + + assert($filename !== ''); + } + + $parentParts = explode('/', trim(str_replace('\\', '/', $this->baselineDirectory), '/')); + $parentPartsCount = count($parentParts); + $filenameParts = explode('/', trim(str_replace('\\', '/', $filename), '/')); + $filenamePartsCount = count($filenameParts); + + $i = 0; + + for (; $i < $filenamePartsCount; $i++) { + if ($parentPartsCount < $i + 1) { + break; + } + + $parentPath = implode('/', array_slice($parentParts, 0, $i + 1)); + $filenamePath = implode('/', array_slice($filenameParts, 0, $i + 1)); + + if ($parentPath !== $filenamePath) { + break; + } + } + + if ($i === 0) { + return [$filename]; + } + + $dotsCount = $parentPartsCount - $i; + + assert($dotsCount >= 0); + + return array_merge(array_fill(0, $dotsCount, '..'), array_slice($filenameParts, $i)); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/Baseline/Subscriber/Subscriber.php b/app/vendor/phpunit/phpunit/src/Runner/Baseline/Subscriber/Subscriber.php new file mode 100644 index 000000000..59ca634b1 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/Baseline/Subscriber/Subscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Baseline; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +abstract readonly class Subscriber +{ + private Generator $generator; + + public function __construct(Generator $generator) + { + $this->generator = $generator; + } + + protected function generator(): Generator + { + return $this->generator; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/Baseline/Subscriber/TestTriggeredDeprecationSubscriber.php b/app/vendor/phpunit/phpunit/src/Runner/Baseline/Subscriber/TestTriggeredDeprecationSubscriber.php new file mode 100644 index 000000000..72e261106 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/Baseline/Subscriber/TestTriggeredDeprecationSubscriber.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 PHPUnit\Runner\Baseline; + +use PHPUnit\Event\Test\DeprecationTriggered; +use PHPUnit\Event\Test\DeprecationTriggeredSubscriber; +use PHPUnit\Runner\FileDoesNotExistException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredDeprecationSubscriber extends Subscriber implements DeprecationTriggeredSubscriber +{ + /** + * @throws FileDoesNotExistException + * @throws FileDoesNotHaveLineException + */ + public function notify(DeprecationTriggered $event): void + { + $this->generator()->testTriggeredIssue($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/Baseline/Subscriber/TestTriggeredNoticeSubscriber.php b/app/vendor/phpunit/phpunit/src/Runner/Baseline/Subscriber/TestTriggeredNoticeSubscriber.php new file mode 100644 index 000000000..288d0eff0 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/Baseline/Subscriber/TestTriggeredNoticeSubscriber.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 PHPUnit\Runner\Baseline; + +use PHPUnit\Event\Test\NoticeTriggered; +use PHPUnit\Event\Test\NoticeTriggeredSubscriber; +use PHPUnit\Runner\FileDoesNotExistException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredNoticeSubscriber extends Subscriber implements NoticeTriggeredSubscriber +{ + /** + * @throws FileDoesNotExistException + * @throws FileDoesNotHaveLineException + */ + public function notify(NoticeTriggered $event): void + { + $this->generator()->testTriggeredIssue($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/Baseline/Subscriber/TestTriggeredPhpDeprecationSubscriber.php b/app/vendor/phpunit/phpunit/src/Runner/Baseline/Subscriber/TestTriggeredPhpDeprecationSubscriber.php new file mode 100644 index 000000000..f72095ac1 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/Baseline/Subscriber/TestTriggeredPhpDeprecationSubscriber.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 PHPUnit\Runner\Baseline; + +use PHPUnit\Event\Test\PhpDeprecationTriggered; +use PHPUnit\Event\Test\PhpDeprecationTriggeredSubscriber; +use PHPUnit\Runner\FileDoesNotExistException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredPhpDeprecationSubscriber extends Subscriber implements PhpDeprecationTriggeredSubscriber +{ + /** + * @throws FileDoesNotExistException + * @throws FileDoesNotHaveLineException + */ + public function notify(PhpDeprecationTriggered $event): void + { + $this->generator()->testTriggeredIssue($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/Baseline/Subscriber/TestTriggeredPhpNoticeSubscriber.php b/app/vendor/phpunit/phpunit/src/Runner/Baseline/Subscriber/TestTriggeredPhpNoticeSubscriber.php new file mode 100644 index 000000000..9707a46b3 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/Baseline/Subscriber/TestTriggeredPhpNoticeSubscriber.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 PHPUnit\Runner\Baseline; + +use PHPUnit\Event\Test\PhpNoticeTriggered; +use PHPUnit\Event\Test\PhpNoticeTriggeredSubscriber; +use PHPUnit\Runner\FileDoesNotExistException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredPhpNoticeSubscriber extends Subscriber implements PhpNoticeTriggeredSubscriber +{ + /** + * @throws FileDoesNotExistException + * @throws FileDoesNotHaveLineException + */ + public function notify(PhpNoticeTriggered $event): void + { + $this->generator()->testTriggeredIssue($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/Baseline/Subscriber/TestTriggeredPhpWarningSubscriber.php b/app/vendor/phpunit/phpunit/src/Runner/Baseline/Subscriber/TestTriggeredPhpWarningSubscriber.php new file mode 100644 index 000000000..22af95db4 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/Baseline/Subscriber/TestTriggeredPhpWarningSubscriber.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 PHPUnit\Runner\Baseline; + +use PHPUnit\Event\Test\PhpWarningTriggered; +use PHPUnit\Event\Test\PhpWarningTriggeredSubscriber; +use PHPUnit\Runner\FileDoesNotExistException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredPhpWarningSubscriber extends Subscriber implements PhpWarningTriggeredSubscriber +{ + /** + * @throws FileDoesNotExistException + * @throws FileDoesNotHaveLineException + */ + public function notify(PhpWarningTriggered $event): void + { + $this->generator()->testTriggeredIssue($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/Baseline/Subscriber/TestTriggeredWarningSubscriber.php b/app/vendor/phpunit/phpunit/src/Runner/Baseline/Subscriber/TestTriggeredWarningSubscriber.php new file mode 100644 index 000000000..fd5e0db6f --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/Baseline/Subscriber/TestTriggeredWarningSubscriber.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 PHPUnit\Runner\Baseline; + +use PHPUnit\Event\Test\WarningTriggered; +use PHPUnit\Event\Test\WarningTriggeredSubscriber; +use PHPUnit\Runner\FileDoesNotExistException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredWarningSubscriber extends Subscriber implements WarningTriggeredSubscriber +{ + /** + * @throws FileDoesNotExistException + * @throws FileDoesNotHaveLineException + */ + public function notify(WarningTriggered $event): void + { + $this->generator()->testTriggeredIssue($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/Baseline/Writer.php b/app/vendor/phpunit/phpunit/src/Runner/Baseline/Writer.php new file mode 100644 index 000000000..7d7d7645b --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/Baseline/Writer.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 PHPUnit\Runner\Baseline; + +use function dirname; +use function file_put_contents; +use function is_dir; +use function realpath; +use function sprintf; +use XMLWriter; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Writer +{ + /** + * @param non-empty-string $baselineFile + * + * @throws CannotWriteBaselineException + */ + public function write(string $baselineFile, Baseline $baseline): void + { + $normalizedBaselineFile = realpath(dirname($baselineFile)); + + if ($normalizedBaselineFile === false || !is_dir($normalizedBaselineFile)) { + throw new CannotWriteBaselineException(sprintf('Cannot write baseline to "%s".', $baselineFile)); + } + + $pathCalculator = new RelativePathCalculator($normalizedBaselineFile); + + $writer = new XMLWriter; + + $writer->openMemory(); + $writer->setIndent(true); + $writer->startDocument(); + + $writer->startElement('files'); + $writer->writeAttribute('version', (string) Baseline::VERSION); + + foreach ($baseline->groupedByFileAndLine() as $file => $lines) { + $writer->startElement('file'); + $writer->writeAttribute('path', $pathCalculator->calculate($file)); + + foreach ($lines as $line => $issues) { + $writer->startElement('line'); + $writer->writeAttribute('number', (string) $line); + $writer->writeAttribute('hash', $issues[0]->hash()); + + foreach ($issues as $issue) { + $writer->startElement('issue'); + $writer->writeCdata($issue->description()); + $writer->endElement(); + } + + $writer->endElement(); + } + + $writer->endElement(); + } + + $writer->endElement(); + + file_put_contents($baselineFile, $writer->outputMemory()); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/CodeCoverage.php b/app/vendor/phpunit/phpunit/src/Runner/CodeCoverage.php new file mode 100644 index 000000000..e84a5fc5f --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/CodeCoverage.php @@ -0,0 +1,488 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner; + +use function assert; +use function file_put_contents; +use function sprintf; +use function sys_get_temp_dir; +use PHPUnit\Event\Facade as EventFacade; +use PHPUnit\Framework\TestCase; +use PHPUnit\TextUI\Configuration\CodeCoverageFilterRegistry; +use PHPUnit\TextUI\Configuration\Configuration; +use PHPUnit\TextUI\Output\Printer; +use PHPUnit\Util\Filesystem; +use SebastianBergmann\CodeCoverage\Driver\Driver; +use SebastianBergmann\CodeCoverage\Driver\Selector; +use SebastianBergmann\CodeCoverage\Exception as CodeCoverageException; +use SebastianBergmann\CodeCoverage\Filter; +use SebastianBergmann\CodeCoverage\Report\Clover as CloverReport; +use SebastianBergmann\CodeCoverage\Report\Cobertura as CoberturaReport; +use SebastianBergmann\CodeCoverage\Report\Crap4j as Crap4jReport; +use SebastianBergmann\CodeCoverage\Report\Html\Colors; +use SebastianBergmann\CodeCoverage\Report\Html\CustomCssFile; +use SebastianBergmann\CodeCoverage\Report\Html\Facade as HtmlReport; +use SebastianBergmann\CodeCoverage\Report\OpenClover as OpenCloverReport; +use SebastianBergmann\CodeCoverage\Report\PHP as PhpReport; +use SebastianBergmann\CodeCoverage\Report\Text as TextReport; +use SebastianBergmann\CodeCoverage\Report\Thresholds; +use SebastianBergmann\CodeCoverage\Report\Xml\Facade as XmlReport; +use SebastianBergmann\CodeCoverage\StaticAnalysis\CacheWarmer; +use SebastianBergmann\CodeCoverage\Test\Target\TargetCollection; +use SebastianBergmann\CodeCoverage\Test\Target\ValidationFailure; +use SebastianBergmann\CodeCoverage\Test\TestSize\TestSize; +use SebastianBergmann\CodeCoverage\Test\TestStatus\TestStatus; +use SebastianBergmann\Comparator\Comparator; +use SebastianBergmann\Timer\NoActiveTimerException; +use SebastianBergmann\Timer\Timer; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @codeCoverageIgnore + */ +final class CodeCoverage +{ + private static ?self $instance = null; + private ?\SebastianBergmann\CodeCoverage\CodeCoverage $codeCoverage = null; + + /** + * @phpstan-ignore property.internalClass + */ + private ?Driver $driver = null; + private bool $collecting = false; + private ?TestCase $test = null; + private ?Timer $timer = null; + + public static function instance(): self + { + if (self::$instance === null) { + self::$instance = new self; + } + + return self::$instance; + } + + public function init(Configuration $configuration, CodeCoverageFilterRegistry $codeCoverageFilterRegistry, bool $extensionRequiresCodeCoverageCollection): CodeCoverageInitializationStatus + { + $codeCoverageFilterRegistry->init($configuration); + + if (!$configuration->hasCoverageReport() && !$extensionRequiresCodeCoverageCollection) { + return CodeCoverageInitializationStatus::NOT_REQUESTED; + } + + $this->activate($codeCoverageFilterRegistry->get(), $configuration->pathCoverage()); + + if (!$this->isActive()) { + return CodeCoverageInitializationStatus::FAILED; + } + + if ($configuration->hasCoverageCacheDirectory()) { + $coverageCacheDirectory = $configuration->coverageCacheDirectory(); + } else { + $candidate = sys_get_temp_dir() . '/phpunit-code-coverage-cache'; + + if (Filesystem::createDirectory($candidate)) { + $coverageCacheDirectory = $candidate; + } + } + + if (isset($coverageCacheDirectory)) { + $this->codeCoverage()->cacheStaticAnalysis($coverageCacheDirectory); + } + + $this->codeCoverage()->excludeSubclassesOfThisClassFromUnintentionallyCoveredCodeCheck(Comparator::class); + + if ($configuration->strictCoverage()) { + $this->codeCoverage()->enableCheckForUnintentionallyCoveredCode(); + } + + if ($configuration->ignoreDeprecatedCodeUnitsFromCodeCoverage()) { + $this->codeCoverage()->ignoreDeprecatedCode(); + } else { + $this->codeCoverage()->doNotIgnoreDeprecatedCode(); + } + + if ($configuration->disableCodeCoverageIgnore()) { + $this->codeCoverage()->disableAnnotationsForIgnoringCode(); + } else { + $this->codeCoverage()->enableAnnotationsForIgnoringCode(); + } + + if ($configuration->includeUncoveredFiles()) { + $this->codeCoverage()->includeUncoveredFiles(); + } else { + $this->codeCoverage()->excludeUncoveredFiles(); + } + + if ($codeCoverageFilterRegistry->get()->isEmpty()) { + if (!$codeCoverageFilterRegistry->configured()) { + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning( + 'No filter is configured, code coverage will not be processed', + ); + } else { + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning( + 'Incorrect filter configuration, code coverage will not be processed', + ); + } + + $this->deactivate(); + } + + if (isset($coverageCacheDirectory) && $configuration->includeUncoveredFiles()) { + EventFacade::emitter()->testRunnerStartedStaticAnalysisForCodeCoverage(); + + /** @phpstan-ignore new.internalClass,method.internalClass */ + $statistics = (new CacheWarmer)->warmCache( + $coverageCacheDirectory, + !$configuration->disableCodeCoverageIgnore(), + $configuration->ignoreDeprecatedCodeUnitsFromCodeCoverage(), + $codeCoverageFilterRegistry->get(), + ); + + EventFacade::emitter()->testRunnerFinishedStaticAnalysisForCodeCoverage( + $statistics['cacheHits'], + $statistics['cacheMisses'], + ); + } + + return CodeCoverageInitializationStatus::SUCCEEDED; + } + + /** + * @phpstan-assert-if-true !null $this->instance + */ + public function isActive(): bool + { + return $this->codeCoverage !== null; + } + + public function codeCoverage(): \SebastianBergmann\CodeCoverage\CodeCoverage + { + return $this->codeCoverage; + } + + /** + * @return non-empty-string + */ + public function driverNameAndVersion(): string + { + return $this->driver->nameAndVersion(); + } + + public function start(TestCase $test): void + { + if ($this->collecting) { + return; + } + + $size = TestSize::unknown(); + + if ($test->size()->isSmall()) { + $size = TestSize::small(); + } elseif ($test->size()->isMedium()) { + $size = TestSize::medium(); + } elseif ($test->size()->isLarge()) { + $size = TestSize::large(); + } + + $this->test = $test; + + $this->codeCoverage->start( + $test->valueObjectForEvents()->id(), + $size, + ); + + $this->collecting = true; + } + + public function stop(bool $append, null|false|TargetCollection $covers = null, ?TargetCollection $uses = null): void + { + if (!$this->collecting) { + return; + } + + $status = TestStatus::unknown(); + + if ($this->test !== null) { + if ($this->test->status()->isSuccess()) { + $status = TestStatus::success(); + } else { + $status = TestStatus::failure(); + } + } + + if ($covers instanceof TargetCollection) { + $result = $this->codeCoverage->validate($covers); + + if ($result->isFailure()) { + assert($result instanceof ValidationFailure); + + EventFacade::emitter()->testTriggeredPhpunitWarning( + $this->test->valueObjectForEvents(), + $result->message(), + ); + + $append = false; + } + } + + if ($uses instanceof TargetCollection) { + $result = $this->codeCoverage->validate($uses); + + if ($result->isFailure()) { + assert($result instanceof ValidationFailure); + + EventFacade::emitter()->testTriggeredPhpunitWarning( + $this->test->valueObjectForEvents(), + $result->message(), + ); + + $append = false; + } + } + + $this->codeCoverage->stop($append, $status, $covers, $uses); + + $this->test = null; + $this->collecting = false; + } + + public function deactivate(): void + { + $this->driver = null; + $this->codeCoverage = null; + $this->test = null; + } + + public function generateReports(Printer $printer, Configuration $configuration): void + { + if (!$this->isActive()) { + return; + } + + if ($configuration->hasCoveragePhp()) { + $this->codeCoverageGenerationStart($printer, 'PHP'); + + try { + $writer = new PhpReport; + $writer->process($this->codeCoverage(), $configuration->coveragePhp()); + + $this->codeCoverageGenerationSucceeded($printer); + + unset($writer); + } catch (CodeCoverageException $e) { + $this->codeCoverageGenerationFailed($printer, $e); + } + } + + if ($configuration->hasCoverageClover()) { + $this->codeCoverageGenerationStart($printer, 'Clover XML'); + + try { + $writer = new CloverReport; + $writer->process($this->codeCoverage(), $configuration->coverageClover(), 'Clover Coverage'); + + $this->codeCoverageGenerationSucceeded($printer); + + unset($writer); + } catch (CodeCoverageException $e) { + $this->codeCoverageGenerationFailed($printer, $e); + } + } + + if ($configuration->hasCoverageOpenClover()) { + $this->codeCoverageGenerationStart($printer, 'OpenClover XML'); + + try { + $writer = new OpenCloverReport; + $writer->process($this->codeCoverage(), $configuration->coverageOpenClover(), 'OpenClover Coverage'); + + $this->codeCoverageGenerationSucceeded($printer); + + unset($writer); + } catch (CodeCoverageException $e) { + $this->codeCoverageGenerationFailed($printer, $e); + } + } + + if ($configuration->hasCoverageCobertura()) { + $this->codeCoverageGenerationStart($printer, 'Cobertura XML'); + + try { + $writer = new CoberturaReport; + $writer->process($this->codeCoverage(), $configuration->coverageCobertura()); + + $this->codeCoverageGenerationSucceeded($printer); + + unset($writer); + } catch (CodeCoverageException $e) { + $this->codeCoverageGenerationFailed($printer, $e); + } + } + + if ($configuration->hasCoverageCrap4j()) { + $this->codeCoverageGenerationStart($printer, 'Crap4J XML'); + + try { + $writer = new Crap4jReport($configuration->coverageCrap4jThreshold()); + $writer->process($this->codeCoverage(), $configuration->coverageCrap4j()); + + $this->codeCoverageGenerationSucceeded($printer); + + unset($writer); + } catch (CodeCoverageException $e) { + $this->codeCoverageGenerationFailed($printer, $e); + } + } + + if ($configuration->hasCoverageHtml()) { + $this->codeCoverageGenerationStart($printer, 'HTML'); + + try { + $customCssFile = CustomCssFile::default(); + + if ($configuration->hasCoverageHtmlCustomCssFile()) { + $customCssFile = CustomCssFile::from($configuration->coverageHtmlCustomCssFile()); + } + + $writer = new HtmlReport( + sprintf( + ' and PHPUnit %s', + Version::id(), + ), + Colors::from( + $configuration->coverageHtmlColorSuccessLow(), + $configuration->coverageHtmlColorSuccessMedium(), + $configuration->coverageHtmlColorSuccessHigh(), + $configuration->coverageHtmlColorWarning(), + $configuration->coverageHtmlColorDanger(), + ), + Thresholds::from( + $configuration->coverageHtmlLowUpperBound(), + $configuration->coverageHtmlHighLowerBound(), + ), + $customCssFile, + ); + + $writer->process($this->codeCoverage(), $configuration->coverageHtml()); + + $this->codeCoverageGenerationSucceeded($printer); + + unset($writer); + } catch (CodeCoverageException $e) { + $this->codeCoverageGenerationFailed($printer, $e); + } + } + + if ($configuration->hasCoverageText()) { + $processor = new TextReport( + Thresholds::default(), + $configuration->coverageTextShowUncoveredFiles(), + $configuration->coverageTextShowOnlySummary(), + ); + + $textReport = $processor->process($this->codeCoverage(), $configuration->colors()); + + if ($configuration->coverageText() === 'php://stdout') { + if (!$configuration->noOutput() && !$configuration->debug()) { + $printer->print($textReport); + } + } else { + file_put_contents($configuration->coverageText(), $textReport); + } + } + + if ($configuration->hasCoverageXml()) { + $this->codeCoverageGenerationStart($printer, 'PHPUnit XML'); + + try { + $writer = new XmlReport(Version::id()); + $writer->process($this->codeCoverage(), $configuration->coverageXml()); + + $this->codeCoverageGenerationSucceeded($printer); + + unset($writer); + } catch (CodeCoverageException $e) { + $this->codeCoverageGenerationFailed($printer, $e); + } + } + } + + private function activate(Filter $filter, bool $pathCoverage): void + { + try { + if ($pathCoverage) { + $this->driver = (new Selector)->forLineAndPathCoverage($filter); + } else { + $this->driver = (new Selector)->forLineCoverage($filter); + } + + $this->codeCoverage = new \SebastianBergmann\CodeCoverage\CodeCoverage( + $this->driver, + $filter, + ); + } catch (CodeCoverageException $e) { + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning( + $e->getMessage(), + ); + } + } + + private function codeCoverageGenerationStart(Printer $printer, string $format): void + { + $printer->print( + sprintf( + "\nGenerating code coverage report in %s format ... ", + $format, + ), + ); + + $this->timer()->start(); + } + + /** + * @throws NoActiveTimerException + */ + private function codeCoverageGenerationSucceeded(Printer $printer): void + { + $printer->print( + sprintf( + "done [%s]\n", + $this->timer()->stop()->asString(), + ), + ); + } + + /** + * @throws NoActiveTimerException + */ + private function codeCoverageGenerationFailed(Printer $printer, CodeCoverageException $e): void + { + $printer->print( + sprintf( + "failed [%s]\n%s\n", + $this->timer()->stop()->asString(), + $e->getMessage(), + ), + ); + } + + private function timer(): Timer + { + if ($this->timer === null) { + $this->timer = new Timer; + } + + return $this->timer; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/CodeCoverageInitializationStatus.php b/app/vendor/phpunit/phpunit/src/Runner/CodeCoverageInitializationStatus.php new file mode 100644 index 000000000..ce895f6e6 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/CodeCoverageInitializationStatus.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This enumeration is not covered by the backward compatibility promise for PHPUnit + */ +enum CodeCoverageInitializationStatus +{ + case NOT_REQUESTED; + case SUCCEEDED; + case FAILED; +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/DefaultTestResultCache.php b/app/vendor/phpunit/phpunit/src/Runner/DefaultTestResultCache.php deleted file mode 100644 index f9d8a90d1..000000000 --- a/app/vendor/phpunit/phpunit/src/Runner/DefaultTestResultCache.php +++ /dev/null @@ -1,158 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner; - -use const DIRECTORY_SEPARATOR; -use const LOCK_EX; -use function assert; -use function dirname; -use function file_get_contents; -use function file_put_contents; -use function in_array; -use function is_array; -use function is_dir; -use function is_file; -use function json_decode; -use function json_encode; -use function sprintf; -use PHPUnit\Util\Filesystem; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class DefaultTestResultCache implements TestResultCache -{ - /** - * @var int - */ - private const VERSION = 1; - - /** - * @psalm-var list - */ - private const ALLOWED_TEST_STATUSES = [ - BaseTestRunner::STATUS_SKIPPED, - BaseTestRunner::STATUS_INCOMPLETE, - BaseTestRunner::STATUS_FAILURE, - BaseTestRunner::STATUS_ERROR, - BaseTestRunner::STATUS_RISKY, - BaseTestRunner::STATUS_WARNING, - ]; - - /** - * @var string - */ - private const DEFAULT_RESULT_CACHE_FILENAME = '.phpunit.result.cache'; - - /** - * @var string - */ - private $cacheFilename; - - /** - * @psalm-var array - */ - private $defects = []; - - /** - * @psalm-var array - */ - private $times = []; - - public function __construct(?string $filepath = null) - { - if ($filepath !== null && is_dir($filepath)) { - $filepath .= DIRECTORY_SEPARATOR . self::DEFAULT_RESULT_CACHE_FILENAME; - } - - $this->cacheFilename = $filepath ?? $_ENV['PHPUNIT_RESULT_CACHE'] ?? self::DEFAULT_RESULT_CACHE_FILENAME; - } - - public function setState(string $testName, int $state): void - { - if (!in_array($state, self::ALLOWED_TEST_STATUSES, true)) { - return; - } - - $this->defects[$testName] = $state; - } - - public function getState(string $testName): int - { - return $this->defects[$testName] ?? BaseTestRunner::STATUS_UNKNOWN; - } - - public function setTime(string $testName, float $time): void - { - $this->times[$testName] = $time; - } - - public function getTime(string $testName): float - { - return $this->times[$testName] ?? 0.0; - } - - public function load(): void - { - if (!is_file($this->cacheFilename)) { - return; - } - - $data = json_decode( - file_get_contents($this->cacheFilename), - true, - ); - - if ($data === null) { - return; - } - - if (!isset($data['version'])) { - return; - } - - if ($data['version'] !== self::VERSION) { - return; - } - - assert(isset($data['defects']) && is_array($data['defects'])); - assert(isset($data['times']) && is_array($data['times'])); - - $this->defects = $data['defects']; - $this->times = $data['times']; - } - - /** - * @throws Exception - */ - public function persist(): void - { - if (!Filesystem::createDirectory(dirname($this->cacheFilename))) { - throw new Exception( - sprintf( - 'Cannot create directory "%s" for result cache file', - $this->cacheFilename, - ), - ); - } - - file_put_contents( - $this->cacheFilename, - json_encode( - [ - 'version' => self::VERSION, - 'defects' => $this->defects, - 'times' => $this->times, - ], - ), - LOCK_EX, - ); - } -} diff --git a/app/vendor/phpunit/phpunit/src/Runner/DeprecationCollector/Collector.php b/app/vendor/phpunit/phpunit/src/Runner/DeprecationCollector/Collector.php new file mode 100644 index 000000000..575bc2dd3 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/DeprecationCollector/Collector.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 PHPUnit\Runner\DeprecationCollector; + +use PHPUnit\Event\Facade; +use PHPUnit\Event\Test\DeprecationTriggered; +use PHPUnit\TestRunner\IssueFilter; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Collector +{ + private readonly IssueFilter $issueFilter; + + /** + * @var list + */ + private array $deprecations = []; + + /** + * @var list + */ + private array $filteredDeprecations = []; + + public function __construct(Facade $facade, IssueFilter $issueFilter) + { + $facade->registerSubscribers( + new TestPreparedSubscriber($this), + new TestTriggeredDeprecationSubscriber($this), + ); + + $this->issueFilter = $issueFilter; + } + + /** + * @return list + */ + public function deprecations(): array + { + return $this->deprecations; + } + + /** + * @return list + */ + public function filteredDeprecations(): array + { + return $this->filteredDeprecations; + } + + public function testPrepared(): void + { + $this->deprecations = []; + } + + public function testTriggeredDeprecation(DeprecationTriggered $event): void + { + $this->deprecations[] = $event->message(); + + if (!$this->issueFilter->shouldBeProcessed($event)) { + return; + } + + $this->filteredDeprecations[] = $event->message(); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/DeprecationCollector/Facade.php b/app/vendor/phpunit/phpunit/src/Runner/DeprecationCollector/Facade.php new file mode 100644 index 000000000..a08fdc64c --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/DeprecationCollector/Facade.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 PHPUnit\Runner\DeprecationCollector; + +use PHPUnit\Event\EventFacadeIsSealedException; +use PHPUnit\Event\Facade as EventFacade; +use PHPUnit\Event\UnknownSubscriberTypeException; +use PHPUnit\TestRunner\IssueFilter; +use PHPUnit\TextUI\Configuration\Registry as ConfigurationRegistry; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Facade +{ + private static null|Collector|InIsolationCollector $collector = null; + private static bool $inIsolation = false; + + public static function init(): void + { + self::collector(); + } + + public static function initForIsolation(): void + { + self::collector(); + + self::$inIsolation = true; + } + + /** + * @return list + */ + public static function deprecations(): array + { + return self::collector()->deprecations(); + } + + /** + * @return list + */ + public static function filteredDeprecations(): array + { + return self::collector()->filteredDeprecations(); + } + + /** + * @throws EventFacadeIsSealedException + * @throws UnknownSubscriberTypeException + */ + public static function collector(): Collector|InIsolationCollector + { + if (self::$collector !== null) { + return self::$collector; + } + + $issueFilter = new IssueFilter( + ConfigurationRegistry::get()->source(), + ); + + if (self::$inIsolation) { + self::$collector = new InIsolationCollector( + $issueFilter, + ); + + return self::$collector; + } + + self::$collector = new Collector( + EventFacade::instance(), + $issueFilter, + ); + + return self::$collector; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/DeprecationCollector/InIsolationCollector.php b/app/vendor/phpunit/phpunit/src/Runner/DeprecationCollector/InIsolationCollector.php new file mode 100644 index 000000000..312876221 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/DeprecationCollector/InIsolationCollector.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 PHPUnit\Runner\DeprecationCollector; + +use PHPUnit\Event\Test\DeprecationTriggered; +use PHPUnit\TestRunner\IssueFilter; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class InIsolationCollector +{ + private readonly IssueFilter $issueFilter; + + /** + * @var list + */ + private array $deprecations = []; + + /** + * @var list + */ + private array $filteredDeprecations = []; + + public function __construct(IssueFilter $issueFilter) + { + $this->issueFilter = $issueFilter; + } + + /** + * @return list + */ + public function deprecations(): array + { + return $this->deprecations; + } + + /** + * @return list + */ + public function filteredDeprecations(): array + { + return $this->filteredDeprecations; + } + + public function testTriggeredDeprecation(DeprecationTriggered $event): void + { + $this->deprecations[] = $event->message(); + + if (!$this->issueFilter->shouldBeProcessed($event)) { + return; + } + + $this->filteredDeprecations[] = $event->message(); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/DeprecationCollector/Subscriber/Subscriber.php b/app/vendor/phpunit/phpunit/src/Runner/DeprecationCollector/Subscriber/Subscriber.php new file mode 100644 index 000000000..65af6ab70 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/DeprecationCollector/Subscriber/Subscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\DeprecationCollector; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +abstract class Subscriber +{ + private readonly Collector|InIsolationCollector $collector; + + public function __construct(Collector|InIsolationCollector $collector) + { + $this->collector = $collector; + } + + protected function collector(): Collector|InIsolationCollector + { + return $this->collector; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/DeprecationCollector/Subscriber/TestPreparedSubscriber.php b/app/vendor/phpunit/phpunit/src/Runner/DeprecationCollector/Subscriber/TestPreparedSubscriber.php new file mode 100644 index 000000000..0e78c31a4 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/DeprecationCollector/Subscriber/TestPreparedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\DeprecationCollector; + +use PHPUnit\Event\Test\Prepared; +use PHPUnit\Event\Test\PreparedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class TestPreparedSubscriber extends Subscriber implements PreparedSubscriber +{ + public function notify(Prepared $event): void + { + $this->collector()->testPrepared(); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/DeprecationCollector/Subscriber/TestTriggeredDeprecationSubscriber.php b/app/vendor/phpunit/phpunit/src/Runner/DeprecationCollector/Subscriber/TestTriggeredDeprecationSubscriber.php new file mode 100644 index 000000000..a01f1b61e --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/DeprecationCollector/Subscriber/TestTriggeredDeprecationSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\DeprecationCollector; + +use PHPUnit\Event\Test\DeprecationTriggered; +use PHPUnit\Event\Test\DeprecationTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class TestTriggeredDeprecationSubscriber extends Subscriber implements DeprecationTriggeredSubscriber +{ + public function notify(DeprecationTriggered $event): void + { + $this->collector()->testTriggeredDeprecation($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/ErrorHandler.php b/app/vendor/phpunit/phpunit/src/Runner/ErrorHandler.php new file mode 100644 index 000000000..b1d0468bd --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/ErrorHandler.php @@ -0,0 +1,491 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner; + +use const DEBUG_BACKTRACE_IGNORE_ARGS; +use const E_COMPILE_ERROR; +use const E_COMPILE_WARNING; +use const E_CORE_ERROR; +use const E_CORE_WARNING; +use const E_DEPRECATED; +use const E_ERROR; +use const E_NOTICE; +use const E_PARSE; +use const E_RECOVERABLE_ERROR; +use const E_USER_DEPRECATED; +use const E_USER_ERROR; +use const E_USER_NOTICE; +use const E_USER_WARNING; +use const E_WARNING; +use function array_keys; +use function array_values; +use function debug_backtrace; +use function defined; +use function error_reporting; +use function restore_error_handler; +use function set_error_handler; +use function sprintf; +use PHPUnit\Event; +use PHPUnit\Event\Code\IssueTrigger\IssueTrigger; +use PHPUnit\Event\Code\NoTestCaseObjectOnCallStackException; +use PHPUnit\Event\Code\TestMethod; +use PHPUnit\Framework\TestCase; +use PHPUnit\Runner\Baseline\Baseline; +use PHPUnit\Runner\Baseline\Issue; +use PHPUnit\TextUI\Configuration\Registry; +use PHPUnit\TextUI\Configuration\Source; +use PHPUnit\TextUI\Configuration\SourceFilter; +use PHPUnit\Util\ExcludeList; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ErrorHandler +{ + private const int UNHANDLEABLE_LEVELS = E_ERROR | E_PARSE | E_CORE_ERROR | E_CORE_WARNING | E_COMPILE_ERROR | E_COMPILE_WARNING; + private const int INSUPPRESSIBLE_LEVELS = E_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR; + private static ?self $instance = null; + private ?Baseline $baseline = null; + private bool $enabled = false; + private ?int $originalErrorReportingLevel = null; + private readonly Source $source; + + /** + * @var list + */ + private array $globalDeprecations = []; + + /** + * @var array> + */ + private array $testCaseContextDeprecations = []; + private ?string $testCaseContext = null; + + /** + * @var ?array{functions: list, methods: list} + */ + private ?array $deprecationTriggers = null; + + public static function instance(): self + { + return self::$instance ?? self::$instance = new self(Registry::get()->source()); + } + + private function __construct(Source $source) + { + $this->source = $source; + } + + /** + * @throws NoTestCaseObjectOnCallStackException + */ + public function __invoke(int $errorNumber, string $errorString, string $errorFile, int $errorLine): bool + { + $suppressed = (error_reporting() & ~self::INSUPPRESSIBLE_LEVELS) === 0; + + if ($suppressed && (new ExcludeList)->isExcluded($errorFile)) { + return false; + } + + /** + * E_STRICT is deprecated since PHP 8.4. + * + * @see https://github.com/sebastianbergmann/phpunit/issues/5956 + */ + if (defined('E_STRICT') && $errorNumber === 2048) { + $errorNumber = E_NOTICE; + } + + $test = Event\Code\TestMethodBuilder::fromCallStack(); + + if ($errorNumber === E_USER_DEPRECATED) { + $deprecationFrame = $this->guessDeprecationFrame(); + $errorFile = $deprecationFrame['file'] ?? $errorFile; + $errorLine = $deprecationFrame['line'] ?? $errorLine; + } + + $ignoredByBaseline = $this->ignoredByBaseline($errorFile, $errorLine, $errorString); + $ignoredByTest = $test->metadata()->isIgnoreDeprecations()->isNotEmpty(); + + switch ($errorNumber) { + case E_NOTICE: + Event\Facade::emitter()->testTriggeredPhpNotice( + $test, + $errorString, + $errorFile, + $errorLine, + $suppressed, + $ignoredByBaseline, + ); + + break; + + case E_USER_NOTICE: + Event\Facade::emitter()->testTriggeredNotice( + $test, + $errorString, + $errorFile, + $errorLine, + $suppressed, + $ignoredByBaseline, + ); + + break; + + case E_WARNING: + Event\Facade::emitter()->testTriggeredPhpWarning( + $test, + $errorString, + $errorFile, + $errorLine, + $suppressed, + $ignoredByBaseline, + ); + + break; + + case E_USER_WARNING: + Event\Facade::emitter()->testTriggeredWarning( + $test, + $errorString, + $errorFile, + $errorLine, + $suppressed, + $ignoredByBaseline, + ); + + break; + + case E_DEPRECATED: + Event\Facade::emitter()->testTriggeredPhpDeprecation( + $test, + $errorString, + $errorFile, + $errorLine, + $suppressed, + $ignoredByBaseline, + $ignoredByTest, + $this->trigger($test, false), + ); + + break; + + case E_USER_DEPRECATED: + Event\Facade::emitter()->testTriggeredDeprecation( + $test, + $errorString, + $errorFile, + $errorLine, + $suppressed, + $ignoredByBaseline, + $ignoredByTest, + $this->trigger($test, true), + $this->stackTrace(), + ); + + break; + + case E_USER_ERROR: + Event\Facade::emitter()->testTriggeredError( + $test, + $errorString, + $errorFile, + $errorLine, + $suppressed, + ); + + throw new ErrorException('E_USER_ERROR was triggered'); + + default: + return false; + } + + return false; + } + + public function deprecationHandler(int $errorNumber, string $errorString, string $errorFile, int $errorLine): bool + { + if ($this->testCaseContext !== null) { + $this->testCaseContextDeprecations[$this->testCaseContext][] = [$errorNumber, $errorString, $errorFile, $errorLine]; + } else { + $this->globalDeprecations[] = [$errorNumber, $errorString, $errorFile, $errorLine]; + } + + return true; + } + + public function registerDeprecationHandler(): void + { + set_error_handler([self::$instance, 'deprecationHandler'], E_USER_DEPRECATED); + } + + public function restoreDeprecationHandler(): void + { + restore_error_handler(); + } + + public function enable(TestCase $test): void + { + if ($this->enabled) { + return; + } + + $oldErrorHandler = set_error_handler($this); + + if ($oldErrorHandler !== null) { + restore_error_handler(); + + return; + } + + $this->enabled = true; + $this->originalErrorReportingLevel = error_reporting(); + + $this->triggerGlobalDeprecations($test); + + error_reporting($this->originalErrorReportingLevel & self::UNHANDLEABLE_LEVELS); + } + + public function disable(): void + { + if (!$this->enabled) { + return; + } + + restore_error_handler(); + + error_reporting(error_reporting() | $this->originalErrorReportingLevel); + + $this->enabled = false; + $this->originalErrorReportingLevel = null; + } + + public function useBaseline(Baseline $baseline): void + { + $this->baseline = $baseline; + } + + /** + * @param array{functions: list, methods: list} $deprecationTriggers + */ + public function useDeprecationTriggers(array $deprecationTriggers): void + { + $this->deprecationTriggers = $deprecationTriggers; + } + + public function enterTestCaseContext(string $className, string $methodName): void + { + $this->testCaseContext = $this->testCaseContext($className, $methodName); + } + + public function leaveTestCaseContext(): void + { + $this->testCaseContext = null; + } + + /** + * @param non-empty-string $file + * @param positive-int $line + * @param non-empty-string $description + */ + private function ignoredByBaseline(string $file, int $line, string $description): bool + { + if ($this->baseline === null) { + return false; + } + + return $this->baseline->has(Issue::from($file, $line, null, $description)); + } + + private function trigger(TestMethod $test, bool $filterTrigger): IssueTrigger + { + if (!$this->source->notEmpty()) { + return IssueTrigger::unknown(); + } + + $trace = $this->filteredStackTrace($filterTrigger); + + $triggeredInFirstPartyCode = false; + $triggerCalledFromFirstPartyCode = false; + + if (isset($trace[0]['file'])) { + if ($trace[0]['file'] === $test->file()) { + return IssueTrigger::test(); + } + + if (SourceFilter::instance()->includes($trace[0]['file'])) { + $triggeredInFirstPartyCode = true; + } + } + + if (isset($trace[1]['file']) && + ($trace[1]['file'] === $test->file() || + SourceFilter::instance()->includes($trace[1]['file']))) { + $triggerCalledFromFirstPartyCode = true; + } + + if ($triggerCalledFromFirstPartyCode) { + if ($triggeredInFirstPartyCode) { + return IssueTrigger::self(); + } + + return IssueTrigger::direct(); + } + + return IssueTrigger::indirect(); + } + + /** + * @return list + */ + private function filteredStackTrace(bool $filterDeprecationTriggers): array + { + $trace = $this->errorStackTrace(); + + if ($this->deprecationTriggers === null || !$filterDeprecationTriggers) { + return array_values($trace); + } + + foreach (array_keys($trace) as $frame) { + foreach ($this->deprecationTriggers['functions'] as $function) { + if ($this->frameIsFunction($trace[$frame], $function)) { + unset($trace[$frame]); + + continue 2; + } + } + + foreach ($this->deprecationTriggers['methods'] as $method) { + if ($this->frameIsMethod($trace[$frame], $method)) { + unset($trace[$frame]); + + continue 2; + } + } + } + + return array_values($trace); + } + + /** + * @return ?array{file: non-empty-string, line: positive-int} + */ + private function guessDeprecationFrame(): ?array + { + if ($this->deprecationTriggers === null) { + return null; + } + + $trace = $this->errorStackTrace(); + + foreach ($trace as $frame) { + foreach ($this->deprecationTriggers['functions'] as $function) { + if ($this->frameIsFunction($frame, $function)) { + return $frame; + } + } + + foreach ($this->deprecationTriggers['methods'] as $method) { + if ($this->frameIsMethod($frame, $method)) { + return $frame; + } + } + } + + return null; + } + + /** + * @return list + */ + private function errorStackTrace(): array + { + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); + + $i = 0; + + do { + unset($trace[$i]); + } while (self::class === ($trace[++$i]['class'] ?? null)); + + return array_values($trace); + } + + /** + * @param array{class? : class-string, function?: non-empty-string} $frame + * @param non-empty-string $function + */ + private function frameIsFunction(array $frame, string $function): bool + { + return !isset($frame['class']) && isset($frame['function']) && $frame['function'] === $function; + } + + /** + * @param array{class? : class-string, function?: non-empty-string} $frame + * @param array{className: class-string, methodName: non-empty-string} $method + */ + private function frameIsMethod(array $frame, array $method): bool + { + return isset($frame['class']) && + $frame['class'] === $method['className'] && + isset($frame['function']) && + $frame['function'] === $method['methodName']; + } + + /** + * @return non-empty-string + */ + private function stackTrace(): string + { + $buffer = ''; + $excludeList = new ExcludeList(true); + + foreach ($this->errorStackTrace() as $frame) { + /** + * @see https://github.com/sebastianbergmann/phpunit/issues/6043 + */ + if (!isset($frame['file'])) { + continue; + } + + if ($excludeList->isExcluded($frame['file'])) { + continue; + } + + $buffer .= sprintf( + "%s:%s\n", + $frame['file'], + $frame['line'] ?? '?', + ); + } + + return $buffer; + } + + private function triggerGlobalDeprecations(TestCase $test): void + { + foreach ($this->globalDeprecations ?? [] as $d) { + $this->__invoke(...$d); + } + + $testCaseContext = $this->testCaseContext($test::class, $test->name()); + + foreach ($this->testCaseContextDeprecations[$testCaseContext] ?? [] as $d) { + $this->__invoke(...$d); + } + } + + private function testCaseContext(string $className, string $methodName): string + { + return "{$className}::{$methodName}"; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/Exception.php b/app/vendor/phpunit/phpunit/src/Runner/Exception.php deleted file mode 100644 index adcd11558..000000000 --- a/app/vendor/phpunit/phpunit/src/Runner/Exception.php +++ /dev/null @@ -1,19 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner; - -use RuntimeException; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class Exception extends RuntimeException implements \PHPUnit\Exception -{ -} diff --git a/app/vendor/phpunit/phpunit/src/Runner/Exception/ClassCannotBeFoundException.php b/app/vendor/phpunit/phpunit/src/Runner/Exception/ClassCannotBeFoundException.php new file mode 100644 index 000000000..701cbb5b1 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/Exception/ClassCannotBeFoundException.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 PHPUnit\Runner; + +use function sprintf; +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ClassCannotBeFoundException extends RuntimeException implements Exception +{ + public function __construct(string $className, string $file) + { + parent::__construct( + sprintf( + 'Class %s cannot be found in %s', + $className, + $file, + ), + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/Exception/ClassDoesNotExtendTestCaseException.php b/app/vendor/phpunit/phpunit/src/Runner/Exception/ClassDoesNotExtendTestCaseException.php new file mode 100644 index 000000000..c9d5474e3 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/Exception/ClassDoesNotExtendTestCaseException.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 PHPUnit\Runner; + +use function sprintf; +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ClassDoesNotExtendTestCaseException extends RuntimeException implements Exception +{ + public function __construct(string $className, string $file) + { + parent::__construct( + sprintf( + 'Class %s declared in %s does not extend PHPUnit\Framework\TestCase', + $className, + $file, + ), + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/Exception/ClassIsAbstractException.php b/app/vendor/phpunit/phpunit/src/Runner/Exception/ClassIsAbstractException.php new file mode 100644 index 000000000..bf9475899 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/Exception/ClassIsAbstractException.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 PHPUnit\Runner; + +use function sprintf; +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ClassIsAbstractException extends RuntimeException implements Exception +{ + public function __construct(string $className, string $file) + { + parent::__construct( + sprintf( + 'Class %s declared in %s is abstract', + $className, + $file, + ), + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/Exception/DirectoryDoesNotExistException.php b/app/vendor/phpunit/phpunit/src/Runner/Exception/DirectoryDoesNotExistException.php new file mode 100644 index 000000000..626c42256 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/Exception/DirectoryDoesNotExistException.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 PHPUnit\Runner; + +use function sprintf; +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class DirectoryDoesNotExistException extends RuntimeException implements Exception +{ + public function __construct(string $directory) + { + parent::__construct( + sprintf( + 'Directory "%s" does not exist and could not be created', + $directory, + ), + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/Exception/ErrorException.php b/app/vendor/phpunit/phpunit/src/Runner/Exception/ErrorException.php new file mode 100644 index 000000000..954684e9f --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/Exception/ErrorException.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 PHPUnit\Runner; + +use Error; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ErrorException extends Error implements Exception +{ +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/Exception/Exception.php b/app/vendor/phpunit/phpunit/src/Runner/Exception/Exception.php new file mode 100644 index 000000000..ea0cf4244 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/Exception/Exception.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +interface Exception extends \PHPUnit\Exception +{ +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/Exception/FileDoesNotExistException.php b/app/vendor/phpunit/phpunit/src/Runner/Exception/FileDoesNotExistException.php new file mode 100644 index 000000000..5b84c785d --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/Exception/FileDoesNotExistException.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 PHPUnit\Runner; + +use function sprintf; +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class FileDoesNotExistException extends RuntimeException implements Exception +{ + public function __construct(string $file) + { + parent::__construct( + sprintf( + 'File "%s" does not exist', + $file, + ), + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/Exception/InvalidOrderException.php b/app/vendor/phpunit/phpunit/src/Runner/Exception/InvalidOrderException.php new file mode 100644 index 000000000..016ec85e4 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/Exception/InvalidOrderException.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 PHPUnit\Runner; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class InvalidOrderException extends RuntimeException implements Exception +{ +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/Exception/ParameterDoesNotExistException.php b/app/vendor/phpunit/phpunit/src/Runner/Exception/ParameterDoesNotExistException.php new file mode 100644 index 000000000..5d7a09675 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/Exception/ParameterDoesNotExistException.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 PHPUnit\Runner; + +use function sprintf; +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ParameterDoesNotExistException extends RuntimeException implements Exception +{ + public function __construct(string $name) + { + parent::__construct( + sprintf( + 'Parameter "%s" does not exist', + $name, + ), + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/Extension/Extension.php b/app/vendor/phpunit/phpunit/src/Runner/Extension/Extension.php new file mode 100644 index 000000000..35610bc3d --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/Extension/Extension.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 PHPUnit\Runner\Extension; + +use PHPUnit\TextUI\Configuration\Configuration; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +interface Extension +{ + public function bootstrap(Configuration $configuration, Facade $facade, ParameterCollection $parameters): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/Extension/ExtensionBootstrapper.php b/app/vendor/phpunit/phpunit/src/Runner/Extension/ExtensionBootstrapper.php new file mode 100644 index 000000000..b9c6c9fa2 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/Extension/ExtensionBootstrapper.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 PHPUnit\Runner\Extension; + +use const PHP_EOL; +use function assert; +use function class_exists; +use function class_implements; +use function in_array; +use function sprintf; +use PHPUnit\Event\Facade as EventFacade; +use PHPUnit\TextUI\Configuration\Configuration; +use ReflectionClass; +use Throwable; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ExtensionBootstrapper +{ + private Configuration $configuration; + private Facade $facade; + + public function __construct(Configuration $configuration, Facade $facade) + { + $this->configuration = $configuration; + $this->facade = $facade; + } + + /** + * @param non-empty-string $className + * @param array $parameters + */ + public function bootstrap(string $className, array $parameters): void + { + if (!class_exists($className)) { + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + 'Cannot bootstrap extension because class %s does not exist', + $className, + ), + ); + + return; + } + + if (!in_array(Extension::class, class_implements($className), true)) { + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + 'Cannot bootstrap extension because class %s does not implement interface %s', + $className, + Extension::class, + ), + ); + + return; + } + + try { + $instance = (new ReflectionClass($className))->newInstance(); + + assert($instance instanceof Extension); + + $instance->bootstrap( + $this->configuration, + $this->facade, + ParameterCollection::fromArray($parameters), + ); + } catch (Throwable $t) { + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + 'Bootstrapping of extension %s failed: %s%s%s', + $className, + $t->getMessage(), + PHP_EOL, + $t->getTraceAsString(), + ), + ); + + return; + } + + EventFacade::emitter()->testRunnerBootstrappedExtension( + $className, + $parameters, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/Extension/ExtensionHandler.php b/app/vendor/phpunit/phpunit/src/Runner/Extension/ExtensionHandler.php deleted file mode 100644 index c57e70e72..000000000 --- a/app/vendor/phpunit/phpunit/src/Runner/Extension/ExtensionHandler.php +++ /dev/null @@ -1,118 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner\Extension; - -use function class_exists; -use function sprintf; -use PHPUnit\Framework\TestListener; -use PHPUnit\Runner\Exception; -use PHPUnit\Runner\Hook; -use PHPUnit\TextUI\TestRunner; -use PHPUnit\TextUI\XmlConfiguration\Extension; -use ReflectionClass; -use ReflectionException; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class ExtensionHandler -{ - /** - * @throws Exception - */ - public function registerExtension(Extension $extensionConfiguration, TestRunner $runner): void - { - $extension = $this->createInstance($extensionConfiguration); - - if (!$extension instanceof Hook) { - throw new Exception( - sprintf( - 'Class "%s" does not implement a PHPUnit\Runner\Hook interface', - $extensionConfiguration->className(), - ), - ); - } - - $runner->addExtension($extension); - } - - /** - * @throws Exception - * - * @deprecated - */ - public function createTestListenerInstance(Extension $listenerConfiguration): TestListener - { - $listener = $this->createInstance($listenerConfiguration); - - if (!$listener instanceof TestListener) { - throw new Exception( - sprintf( - 'Class "%s" does not implement the PHPUnit\Framework\TestListener interface', - $listenerConfiguration->className(), - ), - ); - } - - return $listener; - } - - /** - * @throws Exception - */ - private function createInstance(Extension $extensionConfiguration): object - { - $this->ensureClassExists($extensionConfiguration); - - try { - $reflector = new ReflectionClass($extensionConfiguration->className()); - } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - $e->getCode(), - $e, - ); - } - - if (!$extensionConfiguration->hasArguments()) { - return $reflector->newInstance(); - } - - return $reflector->newInstanceArgs($extensionConfiguration->arguments()); - } - - /** - * @throws Exception - */ - private function ensureClassExists(Extension $extensionConfiguration): void - { - if (class_exists($extensionConfiguration->className(), false)) { - return; - } - - if ($extensionConfiguration->hasSourceFile()) { - /** - * @noinspection PhpIncludeInspection - * - * @psalm-suppress UnresolvableInclude - */ - require_once $extensionConfiguration->sourceFile(); - } - - if (!class_exists($extensionConfiguration->className())) { - throw new Exception( - sprintf( - 'Class "%s" does not exist', - $extensionConfiguration->className(), - ), - ); - } - } -} diff --git a/app/vendor/phpunit/phpunit/src/Runner/Extension/Facade.php b/app/vendor/phpunit/phpunit/src/Runner/Extension/Facade.php new file mode 100644 index 000000000..910f4e5fe --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/Extension/Facade.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Extension; + +use PHPUnit\Event\EventFacadeIsSealedException; +use PHPUnit\Event\Facade as EventFacade; +use PHPUnit\Event\Subscriber; +use PHPUnit\Event\Tracer\Tracer; +use PHPUnit\Event\UnknownSubscriberTypeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class Facade +{ + private bool $replacesOutput = false; + private bool $replacesProgressOutput = false; + private bool $replacesResultOutput = false; + private bool $requiresCodeCoverageCollection = false; + + /** + * @throws EventFacadeIsSealedException + * @throws UnknownSubscriberTypeException + */ + public function registerSubscribers(Subscriber ...$subscribers): void + { + EventFacade::instance()->registerSubscribers(...$subscribers); + } + + /** + * @throws EventFacadeIsSealedException + * @throws UnknownSubscriberTypeException + */ + public function registerSubscriber(Subscriber $subscriber): void + { + EventFacade::instance()->registerSubscriber($subscriber); + } + + /** + * @throws EventFacadeIsSealedException + */ + public function registerTracer(Tracer $tracer): void + { + EventFacade::instance()->registerTracer($tracer); + } + + public function replaceOutput(): void + { + $this->replacesOutput = true; + } + + public function replacesOutput(): bool + { + return $this->replacesOutput; + } + + public function replaceProgressOutput(): void + { + $this->replacesProgressOutput = true; + } + + public function replacesProgressOutput(): bool + { + return $this->replacesOutput || $this->replacesProgressOutput; + } + + public function replaceResultOutput(): void + { + $this->replacesResultOutput = true; + } + + public function replacesResultOutput(): bool + { + return $this->replacesOutput || $this->replacesResultOutput; + } + + public function requireCodeCoverageCollection(): void + { + $this->requiresCodeCoverageCollection = true; + } + + public function requiresCodeCoverageCollection(): bool + { + return $this->requiresCodeCoverageCollection; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/Extension/ParameterCollection.php b/app/vendor/phpunit/phpunit/src/Runner/Extension/ParameterCollection.php new file mode 100644 index 000000000..fef1c9b1a --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/Extension/ParameterCollection.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Extension; + +use function array_key_exists; +use PHPUnit\Runner\ParameterDoesNotExistException; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ParameterCollection +{ + /** + * @var array + */ + private array $parameters; + + /** + * @param array $parameters + */ + public static function fromArray(array $parameters): self + { + return new self($parameters); + } + + /** + * @param array $parameters + */ + private function __construct(array $parameters) + { + $this->parameters = $parameters; + } + + public function has(string $name): bool + { + return array_key_exists($name, $this->parameters); + } + + /** + * @throws ParameterDoesNotExistException + */ + public function get(string $name): string + { + if (!$this->has($name)) { + throw new ParameterDoesNotExistException($name); + } + + return $this->parameters[$name]; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/Extension/PharLoader.php b/app/vendor/phpunit/phpunit/src/Runner/Extension/PharLoader.php index c65b19482..7f3c4b449 100644 --- a/app/vendor/phpunit/phpunit/src/Runner/Extension/PharLoader.php +++ b/app/vendor/phpunit/phpunit/src/Runner/Extension/PharLoader.php @@ -11,32 +11,56 @@ use function count; use function explode; +use function extension_loaded; use function implode; use function is_file; -use function strpos; +use function sprintf; +use function str_contains; use PharIo\Manifest\ApplicationName; use PharIo\Manifest\Exception as ManifestException; use PharIo\Manifest\ManifestLoader; use PharIo\Version\Version as PharIoVersion; +use PHPUnit\Event; use PHPUnit\Runner\Version; use SebastianBergmann\FileIterator\Facade as FileIteratorFacade; +use Throwable; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ -final class PharLoader +final readonly class PharLoader { /** - * @psalm-return array{loadedExtensions: list, notLoadedExtensions: list} + * @param non-empty-string $directory + * + * @return list */ public function loadPharExtensionsInDirectory(string $directory): array { + $pharExtensionLoaded = extension_loaded('phar'); $loadedExtensions = []; - $notLoadedExtensions = []; foreach ((new FileIteratorFacade)->getFilesAsArray($directory, '.phar') as $file) { + if (!$pharExtensionLoaded) { + Event\Facade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + 'Cannot load extension from %s because the PHAR extension is not available', + $file, + ), + ); + + continue; + } + if (!is_file('phar://' . $file . '/manifest.xml')) { - $notLoadedExtensions[] = $file . ' is not an extension for PHPUnit'; + Event\Facade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + '%s is not an extension for PHPUnit', + $file, + ), + ); continue; } @@ -47,43 +71,70 @@ public function loadPharExtensionsInDirectory(string $directory): array $manifest = ManifestLoader::fromFile('phar://' . $file . '/manifest.xml'); if (!$manifest->isExtensionFor($applicationName)) { - $notLoadedExtensions[] = $file . ' is not an extension for PHPUnit'; + Event\Facade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + '%s is not an extension for PHPUnit', + $file, + ), + ); continue; } if (!$manifest->isExtensionFor($applicationName, $version)) { - $notLoadedExtensions[] = $file . ' is not compatible with this version of PHPUnit'; + Event\Facade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + '%s is not compatible with PHPUnit %s', + $file, + Version::series(), + ), + ); continue; } } catch (ManifestException $e) { - $notLoadedExtensions[] = $file . ': ' . $e->getMessage(); + Event\Facade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + 'Cannot load extension from %s: %s', + $file, + $e->getMessage(), + ), + ); continue; } - /** - * @noinspection PhpIncludeInspection - * - * @psalm-suppress UnresolvableInclude - */ - require $file; + try { + @require $file; + } catch (Throwable $t) { + Event\Facade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + 'Cannot load extension from %s: %s', + $file, + $t->getMessage(), + ), + ); + + continue; + } $loadedExtensions[] = $manifest->getName()->asString() . ' ' . $manifest->getVersion()->getVersionString(); + + Event\Facade::emitter()->testRunnerLoadedExtensionFromPhar( + $file, + $manifest->getName()->asString(), + $manifest->getVersion()->getVersionString(), + ); } - return [ - 'loadedExtensions' => $loadedExtensions, - 'notLoadedExtensions' => $notLoadedExtensions, - ]; + return $loadedExtensions; } private function phpunitVersion(): string { $version = Version::id(); - if (strpos($version, '-') === false) { + if (!str_contains($version, '-')) { return $version; } diff --git a/app/vendor/phpunit/phpunit/src/Runner/Filter/ExcludeGroupFilterIterator.php b/app/vendor/phpunit/phpunit/src/Runner/Filter/ExcludeGroupFilterIterator.php index 4b26e5716..45296b2d1 100644 --- a/app/vendor/phpunit/phpunit/src/Runner/Filter/ExcludeGroupFilterIterator.php +++ b/app/vendor/phpunit/phpunit/src/Runner/Filter/ExcludeGroupFilterIterator.php @@ -12,12 +12,18 @@ use function in_array; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class ExcludeGroupFilterIterator extends GroupFilterIterator { - protected function doAccept(string $hash): bool + /** + * @param non-empty-string $id + * @param list $groupTests + */ + protected function doAccept(string $id, array $groupTests): bool { - return !in_array($hash, $this->groupTests, true); + return !in_array($id, $groupTests, true); } } diff --git a/app/vendor/phpunit/phpunit/src/Runner/Filter/ExcludeNameFilterIterator.php b/app/vendor/phpunit/phpunit/src/Runner/Filter/ExcludeNameFilterIterator.php new file mode 100644 index 000000000..ff8459312 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/Filter/ExcludeNameFilterIterator.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 PHPUnit\Runner\Filter; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ExcludeNameFilterIterator extends NameFilterIterator +{ + protected function doAccept(bool $result): bool + { + return !$result; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/Filter/Factory.php b/app/vendor/phpunit/phpunit/src/Runner/Filter/Factory.php index 4880470ea..0335e25b5 100644 --- a/app/vendor/phpunit/phpunit/src/Runner/Filter/Factory.php +++ b/app/vendor/phpunit/phpunit/src/Runner/Filter/Factory.php @@ -10,48 +10,91 @@ namespace PHPUnit\Runner\Filter; use function assert; -use function sprintf; use FilterIterator; use Iterator; +use PHPUnit\Framework\Test; use PHPUnit\Framework\TestSuite; -use PHPUnit\Runner\Exception; -use RecursiveFilterIterator; -use ReflectionClass; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class Factory { /** - * @psalm-var array + * @var list>>, argument: list|non-empty-string}> */ - private $filters = []; + private array $filters = []; /** - * @param array|string $args - * - * @throws Exception + * @param list $testIds */ - public function addFilter(ReflectionClass $filter, $args): void + public function addTestIdFilter(array $testIds): void { - if (!$filter->isSubclassOf(RecursiveFilterIterator::class)) { - throw new Exception( - sprintf( - 'Class "%s" does not extend RecursiveFilterIterator', - $filter->name, - ), - ); - } + $this->filters[] = [ + 'className' => TestIdFilterIterator::class, + 'argument' => $testIds, + ]; + } + + /** + * @param list $groups + */ + public function addIncludeGroupFilter(array $groups): void + { + $this->filters[] = [ + 'className' => IncludeGroupFilterIterator::class, + 'argument' => $groups, + ]; + } - $this->filters[] = [$filter, $args]; + /** + * @param list $groups + */ + public function addExcludeGroupFilter(array $groups): void + { + $this->filters[] = [ + 'className' => ExcludeGroupFilterIterator::class, + 'argument' => $groups, + ]; } + /** + * @param non-empty-string $name + */ + public function addIncludeNameFilter(string $name): void + { + $this->filters[] = [ + 'className' => IncludeNameFilterIterator::class, + 'argument' => $name, + ]; + } + + /** + * @param non-empty-string $name + */ + public function addExcludeNameFilter(string $name): void + { + $this->filters[] = [ + 'className' => ExcludeNameFilterIterator::class, + 'argument' => $name, + ]; + } + + /** + * @param Iterator $iterator + * + * @return FilterIterator> + */ public function factory(Iterator $iterator, TestSuite $suite): FilterIterator { foreach ($this->filters as $filter) { - [$class, $args] = $filter; - $iterator = $class->newInstance($iterator, $args, $suite); + $iterator = new $filter['className']( + $iterator, + $filter['argument'], + $suite, + ); } assert($iterator instanceof FilterIterator); diff --git a/app/vendor/phpunit/phpunit/src/Runner/Filter/GroupFilterIterator.php b/app/vendor/phpunit/phpunit/src/Runner/Filter/GroupFilterIterator.php index b203c1960..da45211d7 100644 --- a/app/vendor/phpunit/phpunit/src/Runner/Filter/GroupFilterIterator.php +++ b/app/vendor/phpunit/phpunit/src/Runner/Filter/GroupFilterIterator.php @@ -9,38 +9,47 @@ */ namespace PHPUnit\Runner\Filter; -use function array_map; use function array_merge; +use function array_push; use function in_array; -use function spl_object_hash; +use PHPUnit\Framework\Test; +use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestSuite; +use PHPUnit\Runner\Phpt\TestCase as PhptTestCase; use RecursiveFilterIterator; use RecursiveIterator; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ abstract class GroupFilterIterator extends RecursiveFilterIterator { /** - * @var string[] + * @var list */ - protected $groupTests = []; + private readonly array $groupTests; + /** + * @param RecursiveIterator $iterator + * @param list $groups + */ public function __construct(RecursiveIterator $iterator, array $groups, TestSuite $suite) { parent::__construct($iterator); - foreach ($suite->getGroupDetails() as $group => $tests) { - if (in_array((string) $group, $groups, true)) { - $testHashes = array_map( - 'spl_object_hash', - $tests, - ); + $groupTests = []; - $this->groupTests = array_merge($this->groupTests, $testHashes); + foreach ($suite->groups() as $group => $tests) { + if (in_array($group, $groups, true)) { + $groupTests = array_merge($groupTests, $tests); + + array_push($groupTests, ...$groupTests); } } + + $this->groupTests = $groupTests; } public function accept(): bool @@ -51,8 +60,16 @@ public function accept(): bool return true; } - return $this->doAccept(spl_object_hash($test)); + if ($test instanceof TestCase || $test instanceof PhptTestCase) { + return $this->doAccept($test->valueObjectForEvents()->id(), $this->groupTests); + } + + return true; } - abstract protected function doAccept(string $hash); + /** + * @param non-empty-string $id + * @param list $groupTests + */ + abstract protected function doAccept(string $id, array $groupTests): bool; } diff --git a/app/vendor/phpunit/phpunit/src/Runner/Filter/IncludeGroupFilterIterator.php b/app/vendor/phpunit/phpunit/src/Runner/Filter/IncludeGroupFilterIterator.php index 0346c6013..afdaefda9 100644 --- a/app/vendor/phpunit/phpunit/src/Runner/Filter/IncludeGroupFilterIterator.php +++ b/app/vendor/phpunit/phpunit/src/Runner/Filter/IncludeGroupFilterIterator.php @@ -12,12 +12,18 @@ use function in_array; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class IncludeGroupFilterIterator extends GroupFilterIterator { - protected function doAccept(string $hash): bool + /** + * @param non-empty-string $id + * @param list $groupTests + */ + protected function doAccept(string $id, array $groupTests): bool { - return in_array($hash, $this->groupTests, true); + return in_array($id, $groupTests, true); } } diff --git a/app/vendor/phpunit/phpunit/src/Runner/Filter/IncludeNameFilterIterator.php b/app/vendor/phpunit/phpunit/src/Runner/Filter/IncludeNameFilterIterator.php new file mode 100644 index 000000000..9bca65eb2 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/Filter/IncludeNameFilterIterator.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 PHPUnit\Runner\Filter; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class IncludeNameFilterIterator extends NameFilterIterator +{ + protected function doAccept(bool $result): bool + { + return $result; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/Filter/NameFilterIterator.php b/app/vendor/phpunit/phpunit/src/Runner/Filter/NameFilterIterator.php index 80ab5817d..f28bf0b43 100644 --- a/app/vendor/phpunit/phpunit/src/Runner/Filter/NameFilterIterator.php +++ b/app/vendor/phpunit/phpunit/src/Runner/Filter/NameFilterIterator.php @@ -10,53 +10,44 @@ namespace PHPUnit\Runner\Filter; use function end; -use function implode; use function preg_match; use function sprintf; -use function str_replace; -use Exception; -use PHPUnit\Framework\ErrorTestCase; +use function substr; +use PHPUnit\Framework\Test; use PHPUnit\Framework\TestSuite; -use PHPUnit\Framework\WarningTestCase; -use PHPUnit\Util\RegularExpression; -use PHPUnit\Util\Test; +use PHPUnit\Runner\Phpt\TestCase as PhptTestCase; use RecursiveFilterIterator; use RecursiveIterator; -use SebastianBergmann\RecursionContext\InvalidArgumentException; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ -final class NameFilterIterator extends RecursiveFilterIterator +abstract class NameFilterIterator extends RecursiveFilterIterator { /** - * @var string - */ - private $filter; - - /** - * @var int - */ - private $filterMin; - - /** - * @var int + * @var non-empty-string */ - private $filterMax; + private readonly string $regularExpression; + private readonly ?int $dataSetMinimum; + private readonly ?int $dataSetMaximum; /** - * @throws Exception + * @param RecursiveIterator $iterator + * @param non-empty-string $filter */ public function __construct(RecursiveIterator $iterator, string $filter) { parent::__construct($iterator); - $this->setFilter($filter); + $preparedFilter = $this->prepareFilter($filter); + + $this->regularExpression = $preparedFilter['regularExpression']; + $this->dataSetMinimum = $preparedFilter['dataSetMinimum']; + $this->dataSetMaximum = $preparedFilter['dataSetMaximum']; } - /** - * @throws InvalidArgumentException - */ public function accept(): bool { $test = $this->getInnerIterator()->current(); @@ -65,32 +56,35 @@ public function accept(): bool return true; } - $tmp = Test::describe($test); - - if ($test instanceof ErrorTestCase || $test instanceof WarningTestCase) { - $name = $test->getMessage(); - } elseif ($tmp[0] !== '') { - $name = implode('::', $tmp); - } else { - $name = $tmp[1]; + if ($test instanceof PhptTestCase) { + return false; } - $accepted = @preg_match($this->filter, $name, $matches); + $name = $test::class . '::' . $test->nameWithDataSet(); - if ($accepted && isset($this->filterMax)) { + $accepted = @preg_match($this->regularExpression, $name, $matches) === 1; + + if ($accepted && isset($this->dataSetMaximum)) { $set = end($matches); - $accepted = $set >= $this->filterMin && $set <= $this->filterMax; + $accepted = $set >= $this->dataSetMinimum && $set <= $this->dataSetMaximum; } - return (bool) $accepted; + return $this->doAccept($accepted); } + abstract protected function doAccept(bool $result): bool; + /** - * @throws Exception + * @param non-empty-string $filter + * + * @return array{regularExpression: non-empty-string, dataSetMinimum: ?int, dataSetMaximum: ?int} */ - private function setFilter(string $filter): void + private function prepareFilter(string $filter): array { - if (RegularExpression::safeMatch($filter, '') === false) { + $dataSetMinimum = null; + $dataSetMaximum = null; + + if (preg_match('/[a-zA-Z0-9]/', substr($filter, 0, 1)) === 1 || @preg_match($filter, '') === false) { // Handles: // * testAssertEqualsSucceeds#4 // * testAssertEqualsSucceeds#4-8 @@ -101,8 +95,8 @@ private function setFilter(string $filter): void $matches[1], ); - $this->filterMin = (int) $matches[2]; - $this->filterMax = (int) $matches[3]; + $dataSetMinimum = (int) $matches[2]; + $dataSetMaximum = (int) $matches[3]; } else { $filter = sprintf( '%s.*with data set #%s$', @@ -115,24 +109,23 @@ private function setFilter(string $filter): void // * testDetermineJsonError@JSON.* elseif (preg_match('/^(.*?)@(.+)$/', $filter, $matches)) { $filter = sprintf( - '%s.*with data set "%s"$', + '%s.*with data set "@%s"$', $matches[1], $matches[2], ); } - // Escape delimiters in regular expression. Do NOT use preg_quote, - // to keep magic characters. + // Do NOT use preg_quote, to keep magic characters. $filter = sprintf( - '/%s/i', - str_replace( - '/', - '\\/', - $filter, - ), + '{%s}i', + $filter, ); } - $this->filter = $filter; + return [ + 'regularExpression' => $filter, + 'dataSetMinimum' => $dataSetMinimum, + 'dataSetMaximum' => $dataSetMaximum, + ]; } } diff --git a/app/vendor/phpunit/phpunit/src/Runner/Filter/TestIdFilterIterator.php b/app/vendor/phpunit/phpunit/src/Runner/Filter/TestIdFilterIterator.php new file mode 100644 index 000000000..1180de4e7 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/Filter/TestIdFilterIterator.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Filter; + +use function in_array; +use PHPUnit\Event\TestData\NoDataSetFromDataProviderException; +use PHPUnit\Framework\Test; +use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\TestSuite; +use PHPUnit\Runner\Phpt\TestCase as PhptTestCase; +use RecursiveFilterIterator; +use RecursiveIterator; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class TestIdFilterIterator extends RecursiveFilterIterator +{ + /** + * @var non-empty-list + */ + private readonly array $testIds; + + /** + * @param RecursiveIterator $iterator + * @param non-empty-list $testIds + */ + public function __construct(RecursiveIterator $iterator, array $testIds) + { + parent::__construct($iterator); + + $this->testIds = $testIds; + } + + public function accept(): bool + { + $test = $this->getInnerIterator()->current(); + + if ($test instanceof TestSuite) { + return true; + } + + if (!$test instanceof TestCase && !$test instanceof PhptTestCase) { + return false; + } + + try { + return in_array($test->valueObjectForEvents()->id(), $this->testIds, true); + } catch (NoDataSetFromDataProviderException) { + return false; + } + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/GarbageCollection/GarbageCollectionHandler.php b/app/vendor/phpunit/phpunit/src/Runner/GarbageCollection/GarbageCollectionHandler.php new file mode 100644 index 000000000..c6cd2dd3b --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/GarbageCollection/GarbageCollectionHandler.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\GarbageCollection; + +use function gc_collect_cycles; +use function gc_disable; +use function gc_enable; +use PHPUnit\Event\Facade; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class GarbageCollectionHandler +{ + private readonly Facade $facade; + private readonly int $threshold; + private int $tests = 0; + + public function __construct(Facade $facade, int $threshold) + { + $this->facade = $facade; + $this->threshold = $threshold; + + $this->registerSubscribers(); + } + + public function executionStarted(): void + { + gc_disable(); + + $this->facade->emitter()->testRunnerDisabledGarbageCollection(); + + gc_collect_cycles(); + + $this->facade->emitter()->testRunnerTriggeredGarbageCollection(); + } + + public function executionFinished(): void + { + gc_collect_cycles(); + + $this->facade->emitter()->testRunnerTriggeredGarbageCollection(); + + gc_enable(); + + $this->facade->emitter()->testRunnerEnabledGarbageCollection(); + } + + public function testFinished(): void + { + $this->tests++; + + if ($this->tests === $this->threshold) { + gc_collect_cycles(); + + $this->facade->emitter()->testRunnerTriggeredGarbageCollection(); + + $this->tests = 0; + } + } + + private function registerSubscribers(): void + { + $this->facade->registerSubscribers( + new ExecutionStartedSubscriber($this), + new ExecutionFinishedSubscriber($this), + new TestFinishedSubscriber($this), + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/GarbageCollection/Subscriber/ExecutionFinishedSubscriber.php b/app/vendor/phpunit/phpunit/src/Runner/GarbageCollection/Subscriber/ExecutionFinishedSubscriber.php new file mode 100644 index 000000000..4ff8e1135 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/GarbageCollection/Subscriber/ExecutionFinishedSubscriber.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 PHPUnit\Runner\GarbageCollection; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\TestRunner\ExecutionFinished; +use PHPUnit\Event\TestRunner\ExecutionFinishedSubscriber as TestRunnerExecutionFinishedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ExecutionFinishedSubscriber extends Subscriber implements TestRunnerExecutionFinishedSubscriber +{ + /** + * @throws \PHPUnit\Framework\InvalidArgumentException + * @throws InvalidArgumentException + */ + public function notify(ExecutionFinished $event): void + { + $this->handler()->executionFinished(); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/GarbageCollection/Subscriber/ExecutionStartedSubscriber.php b/app/vendor/phpunit/phpunit/src/Runner/GarbageCollection/Subscriber/ExecutionStartedSubscriber.php new file mode 100644 index 000000000..1b99b8b72 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/GarbageCollection/Subscriber/ExecutionStartedSubscriber.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 PHPUnit\Runner\GarbageCollection; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\TestRunner\ExecutionStarted; +use PHPUnit\Event\TestRunner\ExecutionStartedSubscriber as TestRunnerExecutionStartedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ExecutionStartedSubscriber extends Subscriber implements TestRunnerExecutionStartedSubscriber +{ + /** + * @throws \PHPUnit\Framework\InvalidArgumentException + * @throws InvalidArgumentException + */ + public function notify(ExecutionStarted $event): void + { + $this->handler()->executionStarted(); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/GarbageCollection/Subscriber/Subscriber.php b/app/vendor/phpunit/phpunit/src/Runner/GarbageCollection/Subscriber/Subscriber.php new file mode 100644 index 000000000..3c9abce8d --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/GarbageCollection/Subscriber/Subscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\GarbageCollection; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +abstract readonly class Subscriber +{ + private GarbageCollectionHandler $handler; + + public function __construct(GarbageCollectionHandler $handler) + { + $this->handler = $handler; + } + + protected function handler(): GarbageCollectionHandler + { + return $this->handler; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/GarbageCollection/Subscriber/TestFinishedSubscriber.php b/app/vendor/phpunit/phpunit/src/Runner/GarbageCollection/Subscriber/TestFinishedSubscriber.php new file mode 100644 index 000000000..4ffae389f --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/GarbageCollection/Subscriber/TestFinishedSubscriber.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 PHPUnit\Runner\GarbageCollection; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\Finished; +use PHPUnit\Event\Test\FinishedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestFinishedSubscriber extends Subscriber implements FinishedSubscriber +{ + /** + * @throws \PHPUnit\Framework\InvalidArgumentException + * @throws InvalidArgumentException + */ + public function notify(Finished $event): void + { + $this->handler()->testFinished(); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/Hook/AfterIncompleteTestHook.php b/app/vendor/phpunit/phpunit/src/Runner/Hook/AfterIncompleteTestHook.php deleted file mode 100644 index 432be9a93..000000000 --- a/app/vendor/phpunit/phpunit/src/Runner/Hook/AfterIncompleteTestHook.php +++ /dev/null @@ -1,24 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner; - -/** - * This interface, as well as the associated mechanism for extending PHPUnit, - * will be removed in PHPUnit 10. There is no alternative available in this - * version of PHPUnit. - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see https://github.com/sebastianbergmann/phpunit/issues/4676 - */ -interface AfterIncompleteTestHook extends TestHook -{ - public function executeAfterIncompleteTest(string $test, string $message, float $time): void; -} diff --git a/app/vendor/phpunit/phpunit/src/Runner/Hook/AfterLastTestHook.php b/app/vendor/phpunit/phpunit/src/Runner/Hook/AfterLastTestHook.php deleted file mode 100644 index eb789f264..000000000 --- a/app/vendor/phpunit/phpunit/src/Runner/Hook/AfterLastTestHook.php +++ /dev/null @@ -1,24 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner; - -/** - * This interface, as well as the associated mechanism for extending PHPUnit, - * will be removed in PHPUnit 10. There is no alternative available in this - * version of PHPUnit. - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see https://github.com/sebastianbergmann/phpunit/issues/4676 - */ -interface AfterLastTestHook extends Hook -{ - public function executeAfterLastTest(): void; -} diff --git a/app/vendor/phpunit/phpunit/src/Runner/Hook/AfterRiskyTestHook.php b/app/vendor/phpunit/phpunit/src/Runner/Hook/AfterRiskyTestHook.php deleted file mode 100644 index 31cc91abf..000000000 --- a/app/vendor/phpunit/phpunit/src/Runner/Hook/AfterRiskyTestHook.php +++ /dev/null @@ -1,24 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner; - -/** - * This interface, as well as the associated mechanism for extending PHPUnit, - * will be removed in PHPUnit 10. There is no alternative available in this - * version of PHPUnit. - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see https://github.com/sebastianbergmann/phpunit/issues/4676 - */ -interface AfterRiskyTestHook extends TestHook -{ - public function executeAfterRiskyTest(string $test, string $message, float $time): void; -} diff --git a/app/vendor/phpunit/phpunit/src/Runner/Hook/AfterSkippedTestHook.php b/app/vendor/phpunit/phpunit/src/Runner/Hook/AfterSkippedTestHook.php deleted file mode 100644 index 76980b3fc..000000000 --- a/app/vendor/phpunit/phpunit/src/Runner/Hook/AfterSkippedTestHook.php +++ /dev/null @@ -1,24 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner; - -/** - * This interface, as well as the associated mechanism for extending PHPUnit, - * will be removed in PHPUnit 10. There is no alternative available in this - * version of PHPUnit. - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see https://github.com/sebastianbergmann/phpunit/issues/4676 - */ -interface AfterSkippedTestHook extends TestHook -{ - public function executeAfterSkippedTest(string $test, string $message, float $time): void; -} diff --git a/app/vendor/phpunit/phpunit/src/Runner/Hook/AfterSuccessfulTestHook.php b/app/vendor/phpunit/phpunit/src/Runner/Hook/AfterSuccessfulTestHook.php deleted file mode 100644 index d0a10dd15..000000000 --- a/app/vendor/phpunit/phpunit/src/Runner/Hook/AfterSuccessfulTestHook.php +++ /dev/null @@ -1,24 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner; - -/** - * This interface, as well as the associated mechanism for extending PHPUnit, - * will be removed in PHPUnit 10. There is no alternative available in this - * version of PHPUnit. - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see https://github.com/sebastianbergmann/phpunit/issues/4676 - */ -interface AfterSuccessfulTestHook extends TestHook -{ - public function executeAfterSuccessfulTest(string $test, float $time): void; -} diff --git a/app/vendor/phpunit/phpunit/src/Runner/Hook/AfterTestErrorHook.php b/app/vendor/phpunit/phpunit/src/Runner/Hook/AfterTestErrorHook.php deleted file mode 100644 index 12ecebd32..000000000 --- a/app/vendor/phpunit/phpunit/src/Runner/Hook/AfterTestErrorHook.php +++ /dev/null @@ -1,24 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner; - -/** - * This interface, as well as the associated mechanism for extending PHPUnit, - * will be removed in PHPUnit 10. There is no alternative available in this - * version of PHPUnit. - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see https://github.com/sebastianbergmann/phpunit/issues/4676 - */ -interface AfterTestErrorHook extends TestHook -{ - public function executeAfterTestError(string $test, string $message, float $time): void; -} diff --git a/app/vendor/phpunit/phpunit/src/Runner/Hook/AfterTestFailureHook.php b/app/vendor/phpunit/phpunit/src/Runner/Hook/AfterTestFailureHook.php deleted file mode 100644 index 94b2f3004..000000000 --- a/app/vendor/phpunit/phpunit/src/Runner/Hook/AfterTestFailureHook.php +++ /dev/null @@ -1,24 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner; - -/** - * This interface, as well as the associated mechanism for extending PHPUnit, - * will be removed in PHPUnit 10. There is no alternative available in this - * version of PHPUnit. - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see https://github.com/sebastianbergmann/phpunit/issues/4676 - */ -interface AfterTestFailureHook extends TestHook -{ - public function executeAfterTestFailure(string $test, string $message, float $time): void; -} diff --git a/app/vendor/phpunit/phpunit/src/Runner/Hook/AfterTestHook.php b/app/vendor/phpunit/phpunit/src/Runner/Hook/AfterTestHook.php deleted file mode 100644 index 3d5bcaa93..000000000 --- a/app/vendor/phpunit/phpunit/src/Runner/Hook/AfterTestHook.php +++ /dev/null @@ -1,30 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner; - -/** - * This interface, as well as the associated mechanism for extending PHPUnit, - * will be removed in PHPUnit 10. There is no alternative available in this - * version of PHPUnit. - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see https://github.com/sebastianbergmann/phpunit/issues/4676 - */ -interface AfterTestHook extends TestHook -{ - /** - * This hook will fire after any test, regardless of the result. - * - * For more fine grained control, have a look at the other hooks - * that extend PHPUnit\Runner\Hook. - */ - public function executeAfterTest(string $test, float $time): void; -} diff --git a/app/vendor/phpunit/phpunit/src/Runner/Hook/AfterTestWarningHook.php b/app/vendor/phpunit/phpunit/src/Runner/Hook/AfterTestWarningHook.php deleted file mode 100644 index 860fcceeb..000000000 --- a/app/vendor/phpunit/phpunit/src/Runner/Hook/AfterTestWarningHook.php +++ /dev/null @@ -1,24 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner; - -/** - * This interface, as well as the associated mechanism for extending PHPUnit, - * will be removed in PHPUnit 10. There is no alternative available in this - * version of PHPUnit. - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see https://github.com/sebastianbergmann/phpunit/issues/4676 - */ -interface AfterTestWarningHook extends TestHook -{ - public function executeAfterTestWarning(string $test, string $message, float $time): void; -} diff --git a/app/vendor/phpunit/phpunit/src/Runner/Hook/BeforeFirstTestHook.php b/app/vendor/phpunit/phpunit/src/Runner/Hook/BeforeFirstTestHook.php deleted file mode 100644 index feeb90fba..000000000 --- a/app/vendor/phpunit/phpunit/src/Runner/Hook/BeforeFirstTestHook.php +++ /dev/null @@ -1,24 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner; - -/** - * This interface, as well as the associated mechanism for extending PHPUnit, - * will be removed in PHPUnit 10. There is no alternative available in this - * version of PHPUnit. - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see https://github.com/sebastianbergmann/phpunit/issues/4676 - */ -interface BeforeFirstTestHook extends Hook -{ - public function executeBeforeFirstTest(): void; -} diff --git a/app/vendor/phpunit/phpunit/src/Runner/Hook/BeforeTestHook.php b/app/vendor/phpunit/phpunit/src/Runner/Hook/BeforeTestHook.php deleted file mode 100644 index b7e0827d0..000000000 --- a/app/vendor/phpunit/phpunit/src/Runner/Hook/BeforeTestHook.php +++ /dev/null @@ -1,24 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner; - -/** - * This interface, as well as the associated mechanism for extending PHPUnit, - * will be removed in PHPUnit 10. There is no alternative available in this - * version of PHPUnit. - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see https://github.com/sebastianbergmann/phpunit/issues/4676 - */ -interface BeforeTestHook extends TestHook -{ - public function executeBeforeTest(string $test): void; -} diff --git a/app/vendor/phpunit/phpunit/src/Runner/Hook/Hook.php b/app/vendor/phpunit/phpunit/src/Runner/Hook/Hook.php deleted file mode 100644 index a08dc72b0..000000000 --- a/app/vendor/phpunit/phpunit/src/Runner/Hook/Hook.php +++ /dev/null @@ -1,23 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner; - -/** - * This interface, as well as the associated mechanism for extending PHPUnit, - * will be removed in PHPUnit 10. There is no alternative available in this - * version of PHPUnit. - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see https://github.com/sebastianbergmann/phpunit/issues/4676 - */ -interface Hook -{ -} diff --git a/app/vendor/phpunit/phpunit/src/Runner/Hook/TestHook.php b/app/vendor/phpunit/phpunit/src/Runner/Hook/TestHook.php deleted file mode 100644 index 31e880e2c..000000000 --- a/app/vendor/phpunit/phpunit/src/Runner/Hook/TestHook.php +++ /dev/null @@ -1,23 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner; - -/** - * This interface, as well as the associated mechanism for extending PHPUnit, - * will be removed in PHPUnit 10. There is no alternative available in this - * version of PHPUnit. - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - * - * @see https://github.com/sebastianbergmann/phpunit/issues/4676 - */ -interface TestHook extends Hook -{ -} diff --git a/app/vendor/phpunit/phpunit/src/Runner/Hook/TestListenerAdapter.php b/app/vendor/phpunit/phpunit/src/Runner/Hook/TestListenerAdapter.php deleted file mode 100644 index 60fbfba31..000000000 --- a/app/vendor/phpunit/phpunit/src/Runner/Hook/TestListenerAdapter.php +++ /dev/null @@ -1,141 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner; - -use PHPUnit\Framework\AssertionFailedError; -use PHPUnit\Framework\Test; -use PHPUnit\Framework\TestListener; -use PHPUnit\Framework\TestSuite; -use PHPUnit\Framework\Warning; -use PHPUnit\Util\Test as TestUtil; -use Throwable; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class TestListenerAdapter implements TestListener -{ - /** - * @var TestHook[] - */ - private $hooks = []; - - /** - * @var bool - */ - private $lastTestWasNotSuccessful; - - public function add(TestHook $hook): void - { - $this->hooks[] = $hook; - } - - public function startTest(Test $test): void - { - foreach ($this->hooks as $hook) { - if ($hook instanceof BeforeTestHook) { - $hook->executeBeforeTest(TestUtil::describeAsString($test)); - } - } - - $this->lastTestWasNotSuccessful = false; - } - - public function addError(Test $test, Throwable $t, float $time): void - { - foreach ($this->hooks as $hook) { - if ($hook instanceof AfterTestErrorHook) { - $hook->executeAfterTestError(TestUtil::describeAsString($test), $t->getMessage(), $time); - } - } - - $this->lastTestWasNotSuccessful = true; - } - - public function addWarning(Test $test, Warning $e, float $time): void - { - foreach ($this->hooks as $hook) { - if ($hook instanceof AfterTestWarningHook) { - $hook->executeAfterTestWarning(TestUtil::describeAsString($test), $e->getMessage(), $time); - } - } - - $this->lastTestWasNotSuccessful = true; - } - - public function addFailure(Test $test, AssertionFailedError $e, float $time): void - { - foreach ($this->hooks as $hook) { - if ($hook instanceof AfterTestFailureHook) { - $hook->executeAfterTestFailure(TestUtil::describeAsString($test), $e->getMessage(), $time); - } - } - - $this->lastTestWasNotSuccessful = true; - } - - public function addIncompleteTest(Test $test, Throwable $t, float $time): void - { - foreach ($this->hooks as $hook) { - if ($hook instanceof AfterIncompleteTestHook) { - $hook->executeAfterIncompleteTest(TestUtil::describeAsString($test), $t->getMessage(), $time); - } - } - - $this->lastTestWasNotSuccessful = true; - } - - public function addRiskyTest(Test $test, Throwable $t, float $time): void - { - foreach ($this->hooks as $hook) { - if ($hook instanceof AfterRiskyTestHook) { - $hook->executeAfterRiskyTest(TestUtil::describeAsString($test), $t->getMessage(), $time); - } - } - - $this->lastTestWasNotSuccessful = true; - } - - public function addSkippedTest(Test $test, Throwable $t, float $time): void - { - foreach ($this->hooks as $hook) { - if ($hook instanceof AfterSkippedTestHook) { - $hook->executeAfterSkippedTest(TestUtil::describeAsString($test), $t->getMessage(), $time); - } - } - - $this->lastTestWasNotSuccessful = true; - } - - public function endTest(Test $test, float $time): void - { - if (!$this->lastTestWasNotSuccessful) { - foreach ($this->hooks as $hook) { - if ($hook instanceof AfterSuccessfulTestHook) { - $hook->executeAfterSuccessfulTest(TestUtil::describeAsString($test), $time); - } - } - } - - foreach ($this->hooks as $hook) { - if ($hook instanceof AfterTestHook) { - $hook->executeAfterTest(TestUtil::describeAsString($test), $time); - } - } - } - - public function startTestSuite(TestSuite $suite): void - { - } - - public function endTestSuite(TestSuite $suite): void - { - } -} diff --git a/app/vendor/phpunit/phpunit/src/Runner/HookMethod/HookMethod.php b/app/vendor/phpunit/phpunit/src/Runner/HookMethod/HookMethod.php new file mode 100644 index 000000000..2442f75ec --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/HookMethod/HookMethod.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 PHPUnit\Runner; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class HookMethod +{ + /** + * @var non-empty-string + */ + private string $methodName; + private int $priority; + + /** + * @param non-empty-string $methodName + */ + public function __construct(string $methodName, int $priority) + { + $this->methodName = $methodName; + $this->priority = $priority; + } + + /** + * @return non-empty-string + */ + public function methodName(): string + { + return $this->methodName; + } + + public function priority(): int + { + return $this->priority; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/HookMethod/HookMethodCollection.php b/app/vendor/phpunit/phpunit/src/Runner/HookMethod/HookMethodCollection.php new file mode 100644 index 000000000..a3593fd5f --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/HookMethod/HookMethodCollection.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 PHPUnit\Runner; + +use function array_map; +use function usort; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class HookMethodCollection +{ + private readonly bool $shouldPrepend; + + /** + * @var non-empty-list + */ + private array $hookMethods; + + public static function defaultBeforeClass(): self + { + return new self(new HookMethod('setUpBeforeClass', 0), true); + } + + public static function defaultBefore(): self + { + return new self(new HookMethod('setUp', 0), true); + } + + public static function defaultPreCondition(): self + { + return new self(new HookMethod('assertPreConditions', 0), true); + } + + public static function defaultPostCondition(): self + { + return new self(new HookMethod('assertPostConditions', 0), false); + } + + public static function defaultAfter(): self + { + return new self(new HookMethod('tearDown', 0), false); + } + + public static function defaultAfterClass(): self + { + return new self(new HookMethod('tearDownAfterClass', 0), false); + } + + private function __construct(HookMethod $default, bool $shouldPrepend) + { + $this->hookMethods = [$default]; + $this->shouldPrepend = $shouldPrepend; + } + + public function add(HookMethod $hookMethod): self + { + if ($this->shouldPrepend) { + $this->hookMethods = [$hookMethod, ...$this->hookMethods]; + } else { + $this->hookMethods[] = $hookMethod; + } + + return $this; + } + + /** + * @return list + */ + public function methodNamesSortedByPriority(): array + { + $hookMethods = $this->hookMethods; + + usort($hookMethods, static fn (HookMethod $a, HookMethod $b) => $b->priority() <=> $a->priority()); + + return array_map( + static fn (HookMethod $hookMethod) => $hookMethod->methodName(), + $hookMethods, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/IssueFilter.php b/app/vendor/phpunit/phpunit/src/Runner/IssueFilter.php new file mode 100644 index 000000000..445c15b7f --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/IssueFilter.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 PHPUnit\TestRunner; + +use PHPUnit\Event\Test\DeprecationTriggered; +use PHPUnit\Event\Test\ErrorTriggered; +use PHPUnit\Event\Test\NoticeTriggered; +use PHPUnit\Event\Test\PhpDeprecationTriggered; +use PHPUnit\Event\Test\PhpNoticeTriggered; +use PHPUnit\Event\Test\PhpWarningTriggered; +use PHPUnit\Event\Test\WarningTriggered; +use PHPUnit\TextUI\Configuration\Source; +use PHPUnit\TextUI\Configuration\SourceFilter; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class IssueFilter +{ + private Source $source; + + public function __construct(Source $source) + { + $this->source = $source; + } + + public function shouldBeProcessed(DeprecationTriggered|ErrorTriggered|NoticeTriggered|PhpDeprecationTriggered|PhpNoticeTriggered|PhpWarningTriggered|WarningTriggered $event, bool $onlyTestMethods = false): bool + { + if ($onlyTestMethods && !$event->test()->isTestMethod()) { + return false; + } + + if ($event instanceof DeprecationTriggered || $event instanceof PhpDeprecationTriggered) { + if ($event->ignoredByTest()) { + return false; + } + + if ($this->source->ignoreSelfDeprecations() && + ($event->trigger()->isTest() || $event->trigger()->isSelf())) { + return false; + } + + if ($this->source->ignoreDirectDeprecations() && $event->trigger()->isDirect()) { + return false; + } + + if ($this->source->ignoreIndirectDeprecations() && $event->trigger()->isIndirect()) { + return false; + } + + if (!$this->source->ignoreSuppressionOfDeprecations() && $event->wasSuppressed()) { + return false; + } + } + + if ($event instanceof NoticeTriggered) { + if (!$this->source->ignoreSuppressionOfNotices() && $event->wasSuppressed()) { + return false; + } + + if ($this->source->restrictNotices() && !SourceFilter::instance()->includes($event->file())) { + return false; + } + } + + if ($event instanceof PhpNoticeTriggered) { + if (!$this->source->ignoreSuppressionOfPhpNotices() && $event->wasSuppressed()) { + return false; + } + + if ($this->source->restrictNotices() && !SourceFilter::instance()->includes($event->file())) { + return false; + } + } + + if ($event instanceof WarningTriggered) { + if (!$this->source->ignoreSuppressionOfWarnings() && $event->wasSuppressed()) { + return false; + } + + if ($this->source->restrictWarnings() && !SourceFilter::instance()->includes($event->file())) { + return false; + } + } + + if ($event instanceof PhpWarningTriggered) { + if (!$this->source->ignoreSuppressionOfPhpWarnings() && $event->wasSuppressed()) { + return false; + } + + if ($this->source->restrictWarnings() && !SourceFilter::instance()->includes($event->file())) { + return false; + } + } + + if ($event instanceof ErrorTriggered) { + if (!$this->source->ignoreSuppressionOfErrors() && $event->wasSuppressed()) { + return false; + } + } + + return true; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/NullTestResultCache.php b/app/vendor/phpunit/phpunit/src/Runner/NullTestResultCache.php deleted file mode 100644 index 2aa86534a..000000000 --- a/app/vendor/phpunit/phpunit/src/Runner/NullTestResultCache.php +++ /dev/null @@ -1,42 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class NullTestResultCache implements TestResultCache -{ - public function setState(string $testName, int $state): void - { - } - - public function getState(string $testName): int - { - return BaseTestRunner::STATUS_UNKNOWN; - } - - public function setTime(string $testName, float $time): void - { - } - - public function getTime(string $testName): float - { - return 0; - } - - public function load(): void - { - } - - public function persist(): void - { - } -} diff --git a/app/vendor/phpunit/phpunit/src/Runner/Phpt/Exception/InvalidPhptFileException.php b/app/vendor/phpunit/phpunit/src/Runner/Phpt/Exception/InvalidPhptFileException.php new file mode 100644 index 000000000..07a2953ec --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/Phpt/Exception/InvalidPhptFileException.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Phpt; + +use PHPUnit\Runner\Exception as RunnerException; +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class InvalidPhptFileException extends RuntimeException implements RunnerException +{ +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/Phpt/Exception/PhptExternalFileCannotBeLoadedException.php b/app/vendor/phpunit/phpunit/src/Runner/Phpt/Exception/PhptExternalFileCannotBeLoadedException.php new file mode 100644 index 000000000..bdee62622 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/Phpt/Exception/PhptExternalFileCannotBeLoadedException.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Phpt; + +use function sprintf; +use PHPUnit\Runner\Exception as RunnerException; +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class PhptExternalFileCannotBeLoadedException extends RuntimeException implements RunnerException +{ + public function __construct(string $section, string $file) + { + parent::__construct( + sprintf( + 'Could not load --%s-- %s for PHPT file', + $section . '_EXTERNAL', + $file, + ), + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/Phpt/Exception/UnsupportedPhptSectionException.php b/app/vendor/phpunit/phpunit/src/Runner/Phpt/Exception/UnsupportedPhptSectionException.php new file mode 100644 index 000000000..9079f9967 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/Phpt/Exception/UnsupportedPhptSectionException.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 PHPUnit\Runner\Phpt; + +use function sprintf; +use PHPUnit\Runner\Exception as RunnerException; +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class UnsupportedPhptSectionException extends RuntimeException implements RunnerException +{ + public function __construct(string $section) + { + parent::__construct( + sprintf( + 'PHPUnit does not support PHPT --%s-- sections', + $section, + ), + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/Phpt/Parser.php b/app/vendor/phpunit/phpunit/src/Runner/Phpt/Parser.php new file mode 100644 index 000000000..72582d733 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/Phpt/Parser.php @@ -0,0 +1,213 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Phpt; + +use const DIRECTORY_SEPARATOR; +use function assert; +use function dirname; +use function explode; +use function file; +use function file_get_contents; +use function is_file; +use function is_readable; +use function is_string; +use function preg_match; +use function rtrim; +use function str_contains; +use function trim; +use PHPUnit\Runner\Exception; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @see https://qa.php.net/phpt_details.php + */ +final readonly class Parser +{ + /** + * @param non-empty-string $phptFile + * + * @throws Exception + * + * @return array + */ + public function parse(string $phptFile): array + { + $sections = []; + $section = ''; + + $unsupportedSections = [ + 'CGI', + 'COOKIE', + 'DEFLATE_POST', + 'EXPECTHEADERS', + 'EXTENSIONS', + 'GET', + 'GZIP_POST', + 'HEADERS', + 'PHPDBG', + 'POST', + 'POST_RAW', + 'PUT', + 'REDIRECTTEST', + 'REQUEST', + ]; + + $lineNr = 0; + + foreach (file($phptFile) as $line) { + $lineNr++; + + if (preg_match('/^--([_A-Z]+)--/', $line, $result)) { + $section = $result[1]; + $sections[$section] = ''; + $sections[$section . '_offset'] = $lineNr; + + continue; + } + + if ($section === '') { + throw new InvalidPhptFileException; + } + + $sections[$section] .= $line; + } + + if (isset($sections['FILEEOF'])) { + $sections['FILE'] = rtrim($sections['FILEEOF'], "\r\n"); + + unset($sections['FILEEOF']); + } + + $this->parseExternal($phptFile, $sections); + $this->validate($sections); + + foreach ($unsupportedSections as $unsupportedSection) { + if (isset($sections[$unsupportedSection])) { + throw new UnsupportedPhptSectionException($unsupportedSection); + } + } + + return $sections; + } + + /** + * @return array + */ + public function parseEnvSection(string $content): array + { + $env = []; + + foreach (explode("\n", trim($content)) as $e) { + $e = explode('=', trim($e), 2); + + if ($e[0] !== '' && isset($e[1])) { + $env[$e[0]] = $e[1]; + } + } + + return $env; + } + + /** + * @param array|string $content + * @param array|non-empty-string> $ini + * + * @return array|non-empty-string> + */ + public function parseIniSection(array|string $content, array $ini = []): array + { + if (is_string($content)) { + $content = explode("\n", trim($content)); + } + + foreach ($content as $setting) { + if (!str_contains($setting, '=')) { + continue; + } + + $setting = explode('=', $setting, 2); + $name = trim($setting[0]); + $value = trim($setting[1]); + + if ($name === 'extension' || $name === 'zend_extension') { + if (!isset($ini[$name])) { + $ini[$name] = []; + } + + $ini[$name][] = $value; + + continue; + } + + $ini[$name] = $value; + } + + return $ini; + } + + /** + * @param non-empty-string $phptFile + * @param array $sections + * + * @throws Exception + */ + private function parseExternal(string $phptFile, array &$sections): void + { + $allowSections = [ + 'FILE', + 'EXPECT', + 'EXPECTF', + 'EXPECTREGEX', + ]; + + $testDirectory = dirname($phptFile) . DIRECTORY_SEPARATOR; + + foreach ($allowSections as $section) { + if (isset($sections[$section . '_EXTERNAL'])) { + $externalFilename = trim($sections[$section . '_EXTERNAL']); + + if (!is_file($testDirectory . $externalFilename) || + !is_readable($testDirectory . $externalFilename)) { + throw new PhptExternalFileCannotBeLoadedException( + $section, + $testDirectory . $externalFilename, + ); + } + + $contents = file_get_contents($testDirectory . $externalFilename); + + assert($contents !== false && $contents !== ''); + + $sections[$section] = $contents; + } + } + } + + /** + * @param array $sections + * + * @throws InvalidPhptFileException + */ + private function validate(array $sections): void + { + if (!isset($sections['FILE'])) { + throw new InvalidPhptFileException; + } + + if (!isset($sections['EXPECT']) && + !isset($sections['EXPECTF']) && + !isset($sections['EXPECTREGEX'])) { + throw new InvalidPhptFileException; + } + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/Phpt/Renderer.php b/app/vendor/phpunit/phpunit/src/Runner/Phpt/Renderer.php new file mode 100644 index 000000000..0fe1de925 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/Phpt/Renderer.php @@ -0,0 +1,110 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Phpt; + +use function assert; +use function defined; +use function dirname; +use function file_put_contents; +use function str_replace; +use function var_export; +use PHPUnit\TextUI\Configuration\Registry as ConfigurationRegistry; +use SebastianBergmann\Template\InvalidArgumentException; +use SebastianBergmann\Template\Template; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @see https://qa.php.net/phpt_details.php + */ +final readonly class Renderer +{ + /** + * @param non-empty-string $phptFile + * @param non-empty-string $code + * + * @return non-empty-string + */ + public function render(string $phptFile, string $code): string + { + return str_replace( + [ + '__DIR__', + '__FILE__', + ], + [ + "'" . dirname($phptFile) . "'", + "'" . $phptFile . "'", + ], + $code, + ); + } + + /** + * @param non-empty-string $job + * @param array{coverage: non-empty-string, job: non-empty-string} $files + * + * @param-out non-empty-string $job + * + * @throws InvalidArgumentException + */ + public function renderForCoverage(string &$job, bool $pathCoverage, ?string $codeCoverageCacheDirectory, array $files): void + { + $template = new Template( + __DIR__ . '/templates/phpt.tpl', + ); + + $composerAutoload = '\'\''; + + if (defined('PHPUNIT_COMPOSER_INSTALL')) { + $composerAutoload = var_export(PHPUNIT_COMPOSER_INSTALL, true); + } + + $phar = '\'\''; + + if (defined('__PHPUNIT_PHAR__')) { + $phar = var_export(__PHPUNIT_PHAR__, true); + } + + if ($codeCoverageCacheDirectory === null) { + $codeCoverageCacheDirectory = 'null'; + } else { + $codeCoverageCacheDirectory = "'" . $codeCoverageCacheDirectory . "'"; + } + + $bootstrap = ''; + + if (ConfigurationRegistry::get()->hasBootstrap()) { + $bootstrap = ConfigurationRegistry::get()->bootstrap(); + } + + $template->setVar( + [ + 'bootstrap' => $bootstrap, + 'composerAutoload' => $composerAutoload, + 'phar' => $phar, + 'job' => $files['job'], + 'coverageFile' => $files['coverage'], + 'driverMethod' => $pathCoverage ? 'forLineAndPathCoverage' : 'forLineCoverage', + 'codeCoverageCacheDirectory' => $codeCoverageCacheDirectory, + ], + ); + + file_put_contents($files['job'], $job); + + $rendered = $template->render(); + + assert($rendered !== ''); + + $job = $rendered; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/Phpt/TestCase.php b/app/vendor/phpunit/phpunit/src/Runner/Phpt/TestCase.php new file mode 100644 index 000000000..ab75397b2 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/Phpt/TestCase.php @@ -0,0 +1,709 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\Phpt; + +use const DEBUG_BACKTRACE_IGNORE_ARGS; +use const DIRECTORY_SEPARATOR; +use function array_merge; +use function basename; +use function debug_backtrace; +use function dirname; +use function explode; +use function extension_loaded; +use function file_get_contents; +use function is_array; +use function is_file; +use function ltrim; +use function ob_get_clean; +use function ob_start; +use function preg_match; +use function preg_replace; +use function preg_split; +use function realpath; +use function sprintf; +use function str_contains; +use function str_starts_with; +use function strncasecmp; +use function substr; +use function trim; +use function unlink; +use function unserialize; +use PHPUnit\Event\Code\Phpt; +use PHPUnit\Event\Code\ThrowableBuilder; +use PHPUnit\Event\Facade as EventFacade; +use PHPUnit\Event\NoPreviousThrowableException; +use PHPUnit\Framework\Assert; +use PHPUnit\Framework\AssertionFailedError; +use PHPUnit\Framework\ExecutionOrderDependency; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\IncompleteTestError; +use PHPUnit\Framework\PhptAssertionFailedError; +use PHPUnit\Framework\Reorderable; +use PHPUnit\Framework\SelfDescribing; +use PHPUnit\Framework\Test; +use PHPUnit\Runner\CodeCoverage; +use PHPUnit\Runner\Exception; +use PHPUnit\Util\PHP\Job; +use PHPUnit\Util\PHP\JobRunnerRegistry; +use SebastianBergmann\CodeCoverage\Data\RawCodeCoverageData; +use SebastianBergmann\CodeCoverage\InvalidArgumentException; +use SebastianBergmann\CodeCoverage\ReflectionException; +use SebastianBergmann\CodeCoverage\Test\TestSize\TestSize; +use SebastianBergmann\CodeCoverage\Test\TestStatus\TestStatus; +use SebastianBergmann\CodeCoverage\TestIdMissingException; +use SebastianBergmann\CodeCoverage\UnintentionallyCoveredCodeException; +use staabm\SideEffectsDetector\SideEffect; +use staabm\SideEffectsDetector\SideEffectsDetector; +use Throwable; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @see https://qa.php.net/phpt_details.php + */ +final readonly class TestCase implements Reorderable, SelfDescribing, Test +{ + /** + * @var non-empty-string + */ + private string $filename; + + /** + * @param non-empty-string $filename + */ + public function __construct(string $filename) + { + $this->filename = $filename; + } + + public function count(): int + { + return 1; + } + + /** + * @throws \PHPUnit\Framework\Exception + * @throws \SebastianBergmann\Template\InvalidArgumentException + * @throws Exception + * @throws InvalidArgumentException + * @throws NoPreviousThrowableException + * @throws ReflectionException + * @throws TestIdMissingException + * @throws UnintentionallyCoveredCodeException + */ + public function run(): void + { + $emitter = EventFacade::emitter(); + $parser = new Parser; + + $emitter->testPreparationStarted( + $this->valueObjectForEvents(), + ); + + try { + $sections = $parser->parse($this->filename); + } catch (Exception $e) { + $emitter->testPrepared($this->valueObjectForEvents()); + $emitter->testErrored($this->valueObjectForEvents(), ThrowableBuilder::from($e)); + $emitter->testFinished($this->valueObjectForEvents(), 0); + + return; + } + + $code = (new Renderer)->render($this->filename, $sections['FILE']); + $xfail = false; + $environmentVariables = []; + $phpSettings = $parser->parseIniSection($this->settings(CodeCoverage::instance()->isActive())); + $input = null; + $arguments = []; + + $emitter->testPrepared($this->valueObjectForEvents()); + + if (isset($sections['INI'])) { + $phpSettings = $parser->parseIniSection($sections['INI'], $phpSettings); + } + + if (isset($sections['ENV'])) { + $environmentVariables = $parser->parseEnvSection($sections['ENV']); + } + + if ($this->shouldTestBeSkipped($sections, $phpSettings)) { + return; + } + + if (isset($sections['XFAIL'])) { + $xfail = trim($sections['XFAIL']); + } + + if (isset($sections['STDIN'])) { + $input = $sections['STDIN']; + } + + if (isset($sections['ARGS'])) { + $arguments = explode(' ', $sections['ARGS']); + } + + if (CodeCoverage::instance()->isActive()) { + $codeCoverageCacheDirectory = null; + + if (CodeCoverage::instance()->codeCoverage()->cachesStaticAnalysis()) { + $codeCoverageCacheDirectory = CodeCoverage::instance()->codeCoverage()->cacheDirectory(); + } + + (new Renderer)->renderForCoverage( + $code, + CodeCoverage::instance()->codeCoverage()->collectsBranchAndPathCoverage(), + $codeCoverageCacheDirectory, + $this->coverageFiles(), + ); + } + + $jobResult = JobRunnerRegistry::run( + new Job( + $code, + $this->stringifyIni($phpSettings), + $environmentVariables, + $arguments, + $input, + true, + ), + ); + + EventFacade::emitter()->childProcessFinished($jobResult->stdout(), $jobResult->stderr()); + + $output = $jobResult->stdout(); + + if (CodeCoverage::instance()->isActive()) { + $coverage = $this->cleanupForCoverage(); + + CodeCoverage::instance()->codeCoverage()->start($this->filename, TestSize::large()); + + CodeCoverage::instance()->codeCoverage()->append( + $coverage, + $this->filename, + true, + TestStatus::unknown(), + ); + } + + $passed = true; + + try { + $this->assertPhptExpectation($sections, $output); + } catch (AssertionFailedError $e) { + $failure = $e; + + if ($xfail !== false) { + $failure = new IncompleteTestError($xfail, 0, $e); + } elseif ($e instanceof ExpectationFailedException) { + $comparisonFailure = $e->getComparisonFailure(); + + if ($comparisonFailure !== null) { + $diff = $comparisonFailure->getDiff(); + } else { + $diff = $e->getMessage(); + } + + $hint = $this->locationHintFromDiff($diff, $sections); + $trace = array_merge($hint, debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS)); + $failure = new PhptAssertionFailedError( + $e->getMessage(), + 0, + (string) $trace[0]['file'], + (int) $trace[0]['line'], + $trace, + $comparisonFailure !== null ? $diff : '', + ); + } + + if ($failure instanceof IncompleteTestError) { + $emitter->testMarkedAsIncomplete($this->valueObjectForEvents(), ThrowableBuilder::from($failure)); + } else { + $emitter->testFailed($this->valueObjectForEvents(), ThrowableBuilder::from($failure), null); + } + + $passed = false; + } catch (Throwable $t) { + $emitter->testErrored($this->valueObjectForEvents(), ThrowableBuilder::from($t)); + + $passed = false; + } + + if ($passed) { + $emitter->testPassed($this->valueObjectForEvents()); + } + + $this->runClean($sections, CodeCoverage::instance()->isActive()); + + $emitter->testFinished($this->valueObjectForEvents(), 1); + } + + /** + * Returns the name of the test case. + */ + public function getName(): string + { + return $this->toString(); + } + + /** + * Returns a string representation of the test case. + */ + public function toString(): string + { + return $this->filename; + } + + public function sortId(): string + { + return $this->filename; + } + + /** + * @return list + */ + public function provides(): array + { + return []; + } + + /** + * @return list + */ + public function requires(): array + { + return []; + } + + /** + * @internal This method is not covered by the backward compatibility promise for PHPUnit + */ + public function valueObjectForEvents(): Phpt + { + return new Phpt($this->filename); + } + + /** + * @param array $sections + * + * @throws Exception + * @throws ExpectationFailedException + */ + private function assertPhptExpectation(array $sections, string $output): void + { + $assertions = [ + 'EXPECT' => 'assertEquals', + 'EXPECTF' => 'assertStringMatchesFormat', + 'EXPECTREGEX' => 'assertMatchesRegularExpression', + ]; + + $actual = preg_replace('/\r\n/', "\n", trim($output)); + + foreach ($assertions as $sectionName => $sectionAssertion) { + if (isset($sections[$sectionName])) { + $sectionContent = preg_replace('/\r\n/', "\n", trim($sections[$sectionName])); + $expected = $sectionName === 'EXPECTREGEX' ? "/{$sectionContent}/" : $sectionContent; + + /** @phpstan-ignore staticMethod.dynamicName */ + Assert::$sectionAssertion($expected, $actual); + + return; + } + } + + throw new InvalidPhptFileException; + } + + /** + * @param array $sections + * @param array|non-empty-string> $settings + */ + private function shouldTestBeSkipped(array $sections, array $settings): bool + { + if (!isset($sections['SKIPIF'])) { + return false; + } + + $skipIfCode = (new Renderer)->render($this->filename, $sections['SKIPIF']); + + if ($this->shouldRunInSubprocess($sections, $skipIfCode)) { + $jobResult = JobRunnerRegistry::run( + new Job( + $skipIfCode, + $this->stringifyIni($settings), + ), + ); + + $output = $jobResult->stdout(); + + EventFacade::emitter()->childProcessFinished($output, $jobResult->stderr()); + } else { + $output = $this->runCodeInLocalSandbox($skipIfCode); + } + + $this->triggerRunnerWarningOnPhpErrors('SKIPIF', $output); + + if (strncasecmp('skip', ltrim($output), 4) === 0) { + $message = ''; + + if (preg_match('/^\s*skip\s*(.+)\s*/i', $output, $skipMatch)) { + $message = substr($skipMatch[1], 2); + } + + EventFacade::emitter()->testSkipped( + $this->valueObjectForEvents(), + $message, + ); + + EventFacade::emitter()->testFinished($this->valueObjectForEvents(), 0); + + return true; + } + + return false; + } + + /** + * @param array $sections + */ + private function shouldRunInSubprocess(array $sections, string $cleanCode): bool + { + if (isset($sections['INI'])) { + // to get per-test INI settings, we need a dedicated subprocess + return true; + } + + $detector = new SideEffectsDetector; + $sideEffects = $detector->getSideEffects($cleanCode); + + if ($sideEffects === []) { + // no side-effects + return false; + } + + foreach ($sideEffects as $sideEffect) { + if ($sideEffect === SideEffect::STANDARD_OUTPUT) { + // stdout is fine, we will catch it using output-buffering + continue; + } + + if ($sideEffect === SideEffect::INPUT_OUTPUT) { + // IO is fine, as it doesn't pollute the main process + continue; + } + + return true; + } + + return false; + } + + private function runCodeInLocalSandbox(string $code): string + { + $code = preg_replace('/^<\?(?:php)?|\?>\s*+$/', '', $code); + $code = preg_replace('/declare\S?\([^)]+\)\S?;/', '', $code); + + // wrap in immediately invoked function to isolate local-side-effects of $code from our own process + $code = '(function() {' . $code . '})();'; + ob_start(); + @eval($code); + + return ob_get_clean(); + } + + /** + * @param array $sections + */ + private function runClean(array $sections, bool $collectCoverage): void + { + if (!isset($sections['CLEAN'])) { + return; + } + + $cleanCode = (new Renderer)->render($this->filename, $sections['CLEAN']); + + if ($this->shouldRunInSubprocess($sections, $cleanCode)) { + $jobResult = JobRunnerRegistry::run( + new Job( + $cleanCode, + $this->settings($collectCoverage), + ), + ); + + $output = $jobResult->stdout(); + + EventFacade::emitter()->childProcessFinished($jobResult->stdout(), $jobResult->stderr()); + } else { + $output = $this->runCodeInLocalSandbox($cleanCode); + } + + $this->triggerRunnerWarningOnPhpErrors('CLEAN', $output); + } + + /** + * @phpstan-ignore return.internalClass + */ + private function cleanupForCoverage(): RawCodeCoverageData + { + /** + * @phpstan-ignore staticMethod.internalClass + */ + $coverage = RawCodeCoverageData::fromXdebugWithoutPathCoverage([]); + $files = $this->coverageFiles(); + + $buffer = false; + + if (is_file($files['coverage'])) { + $buffer = @file_get_contents($files['coverage']); + } + + if ($buffer !== false) { + $coverage = @unserialize($buffer); + + if ($coverage === false) { + /** + * @phpstan-ignore staticMethod.internalClass + */ + $coverage = RawCodeCoverageData::fromXdebugWithoutPathCoverage([]); + } + } + + foreach ($files as $file) { + @unlink($file); + } + + return $coverage; + } + + /** + * @return array{coverage: non-empty-string, job: non-empty-string} + */ + private function coverageFiles(): array + { + $baseDir = dirname(realpath($this->filename)) . DIRECTORY_SEPARATOR; + $basename = basename($this->filename, 'phpt'); + + return [ + 'coverage' => $baseDir . $basename . 'coverage', + 'job' => $baseDir . $basename . 'php', + ]; + } + + /** + * @param array|non-empty-string> $ini + * + * @return list + */ + private function stringifyIni(array $ini): array + { + $settings = []; + + foreach ($ini as $key => $value) { + if (is_array($value)) { + foreach ($value as $val) { + $settings[] = $key . '=' . $val; + } + + continue; + } + + $settings[] = $key . '=' . $value; + } + + return $settings; + } + + /** + * @param array $sections + * + * @return non-empty-list + */ + private function locationHintFromDiff(string $message, array $sections): array + { + $needle = ''; + $previousLine = ''; + $block = 'message'; + + foreach (preg_split('/\r\n|\r|\n/', $message) as $line) { + $line = trim($line); + + if ($block === 'message' && $line === '--- Expected') { + $block = 'expected'; + } + + if ($block === 'expected' && $line === '@@ @@') { + $block = 'diff'; + } + + if ($block === 'diff') { + if (str_starts_with($line, '+')) { + $needle = $this->cleanDiffLine($previousLine); + + break; + } + + if (str_starts_with($line, '-')) { + $needle = $this->cleanDiffLine($line); + + break; + } + } + + if ($line !== '') { + $previousLine = $line; + } + } + + return $this->locationHint($needle, $sections); + } + + private function cleanDiffLine(string $line): string + { + if (preg_match('/^[\-+]([\'\"]?)(.*)\1$/', $line, $matches)) { + $line = $matches[2]; + } + + return $line; + } + + /** + * @param array $sections + * + * @return non-empty-list + */ + private function locationHint(string $needle, array $sections): array + { + $needle = trim($needle); + + if ($needle === '') { + return [[ + 'file' => realpath($this->filename), + 'line' => 1, + ]]; + } + + $search = [ + // 'FILE', + 'EXPECT', + 'EXPECTF', + 'EXPECTREGEX', + ]; + + foreach ($search as $section) { + if (!isset($sections[$section])) { + continue; + } + + if (isset($sections[$section . '_EXTERNAL'])) { + $externalFile = trim($sections[$section . '_EXTERNAL']); + + return [ + [ + 'file' => realpath(dirname($this->filename) . DIRECTORY_SEPARATOR . $externalFile), + 'line' => 1, + ], + [ + 'file' => realpath($this->filename), + 'line' => ($sections[$section . '_EXTERNAL_offset'] ?? 0) + 1, + ], + ]; + } + + $sectionOffset = $sections[$section . '_offset'] ?? 0; + $offset = $sectionOffset + 1; + + foreach (preg_split('/\r\n|\r|\n/', $sections[$section]) as $line) { + if (str_contains($line, $needle)) { + return [ + [ + 'file' => realpath($this->filename), + 'line' => $offset, + ], + ]; + } + + $offset++; + } + } + + return [ + [ + 'file' => realpath($this->filename), + 'line' => 1, + ], + ]; + } + + /** + * @return list + */ + private function settings(bool $collectCoverage): array + { + $settings = [ + 'allow_url_fopen=1', + 'auto_append_file=', + 'auto_prepend_file=', + 'disable_functions=', + 'display_errors=1', + 'docref_ext=.html', + 'docref_root=', + 'error_append_string=', + 'error_prepend_string=', + 'error_reporting=-1', + 'html_errors=0', + 'log_errors=0', + 'open_basedir=', + 'output_buffering=Off', + 'output_handler=', + 'report_zend_debug=0', + ]; + + if (extension_loaded('pcov')) { + if ($collectCoverage) { + $settings[] = 'pcov.enabled=1'; + } else { + $settings[] = 'pcov.enabled=0'; + } + } + + if (extension_loaded('xdebug')) { + if ($collectCoverage) { + $settings[] = 'xdebug.mode=coverage'; + } + } + + return $settings; + } + + private function triggerRunnerWarningOnPhpErrors(string $section, string $output): void + { + if (str_contains($output, 'Parse error:')) { + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + '%s section triggered a parse error: %s', + $section, + $output, + ), + ); + } + + if (str_contains($output, 'Fatal error:')) { + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + '%s section triggered a fatal error: %s', + $section, + $output, + ), + ); + } + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/Phpt/templates/phpt.tpl b/app/vendor/phpunit/phpunit/src/Runner/Phpt/templates/phpt.tpl new file mode 100644 index 000000000..c42518886 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/Phpt/templates/phpt.tpl @@ -0,0 +1,56 @@ +{driverMethod}($filter), + $filter + ); + + if ({codeCoverageCacheDirectory}) { + $coverage->cacheStaticAnalysis({codeCoverageCacheDirectory}); + } + + $coverage->start(__FILE__); +} + +register_shutdown_function( + function() use ($coverage) { + $output = null; + + if ($coverage) { + $output = $coverage->stop(); + } + + file_put_contents('{coverageFile}', serialize($output)); + } +); + +ob_end_clean(); + +require '{job}'; diff --git a/app/vendor/phpunit/phpunit/src/Runner/PhptTestCase.php b/app/vendor/phpunit/phpunit/src/Runner/PhptTestCase.php deleted file mode 100644 index b345dfc4f..000000000 --- a/app/vendor/phpunit/phpunit/src/Runner/PhptTestCase.php +++ /dev/null @@ -1,866 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner; - -use const DEBUG_BACKTRACE_IGNORE_ARGS; -use const DIRECTORY_SEPARATOR; -use function array_merge; -use function basename; -use function debug_backtrace; -use function defined; -use function dirname; -use function explode; -use function extension_loaded; -use function file; -use function file_get_contents; -use function file_put_contents; -use function is_array; -use function is_file; -use function is_readable; -use function is_string; -use function ltrim; -use function phpversion; -use function preg_match; -use function preg_replace; -use function preg_split; -use function realpath; -use function rtrim; -use function sprintf; -use function str_replace; -use function strncasecmp; -use function strpos; -use function substr; -use function trim; -use function unlink; -use function unserialize; -use function var_export; -use function version_compare; -use PHPUnit\Framework\Assert; -use PHPUnit\Framework\AssertionFailedError; -use PHPUnit\Framework\ExecutionOrderDependency; -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Framework\IncompleteTestError; -use PHPUnit\Framework\PHPTAssertionFailedError; -use PHPUnit\Framework\Reorderable; -use PHPUnit\Framework\SelfDescribing; -use PHPUnit\Framework\SkippedTestError; -use PHPUnit\Framework\SyntheticSkippedError; -use PHPUnit\Framework\Test; -use PHPUnit\Framework\TestResult; -use PHPUnit\Util\PHP\AbstractPhpProcess; -use SebastianBergmann\CodeCoverage\InvalidArgumentException; -use SebastianBergmann\CodeCoverage\RawCodeCoverageData; -use SebastianBergmann\CodeCoverage\UnintentionallyCoveredCodeException; -use SebastianBergmann\Template\Template; -use SebastianBergmann\Timer\Timer; -use Throwable; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class PhptTestCase implements Reorderable, SelfDescribing, Test -{ - /** - * @var string - */ - private $filename; - - /** - * @var AbstractPhpProcess - */ - private $phpUtil; - - /** - * @var string - */ - private $output = ''; - - /** - * Constructs a test case with the given filename. - * - * @throws Exception - */ - public function __construct(string $filename, ?AbstractPhpProcess $phpUtil = null) - { - if (!is_file($filename)) { - throw new Exception( - sprintf( - 'File "%s" does not exist.', - $filename, - ), - ); - } - - $this->filename = $filename; - $this->phpUtil = $phpUtil ?: AbstractPhpProcess::factory(); - } - - /** - * Counts the number of test cases executed by run(TestResult result). - */ - public function count(): int - { - return 1; - } - - /** - * Runs a test and collects its result in a TestResult instance. - * - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * @throws InvalidArgumentException - * @throws UnintentionallyCoveredCodeException - */ - public function run(?TestResult $result = null): TestResult - { - if ($result === null) { - $result = new TestResult; - } - - try { - $sections = $this->parse(); - } catch (Exception $e) { - $result->startTest($this); - $result->addFailure($this, new SkippedTestError($e->getMessage()), 0); - $result->endTest($this, 0); - - return $result; - } - - $code = $this->render($sections['FILE']); - $xfail = false; - $settings = $this->parseIniSection($this->settings($result->getCollectCodeCoverageInformation())); - - $result->startTest($this); - - if (isset($sections['INI'])) { - $settings = $this->parseIniSection($sections['INI'], $settings); - } - - if (isset($sections['ENV'])) { - $env = $this->parseEnvSection($sections['ENV']); - $this->phpUtil->setEnv($env); - } - - $this->phpUtil->setUseStderrRedirection(true); - - if ($result->enforcesTimeLimit()) { - $this->phpUtil->setTimeout($result->getTimeoutForLargeTests()); - } - - $skip = $this->runSkip($sections, $result, $settings); - - if ($skip) { - return $result; - } - - if (isset($sections['XFAIL'])) { - $xfail = trim($sections['XFAIL']); - } - - if (isset($sections['STDIN'])) { - $this->phpUtil->setStdin($sections['STDIN']); - } - - if (isset($sections['ARGS'])) { - $this->phpUtil->setArgs($sections['ARGS']); - } - - if ($result->getCollectCodeCoverageInformation()) { - $codeCoverageCacheDirectory = null; - $pathCoverage = false; - - $codeCoverage = $result->getCodeCoverage(); - - if ($codeCoverage) { - if ($codeCoverage->cachesStaticAnalysis()) { - $codeCoverageCacheDirectory = $codeCoverage->cacheDirectory(); - } - - $pathCoverage = $codeCoverage->collectsBranchAndPathCoverage(); - } - - $this->renderForCoverage($code, $pathCoverage, $codeCoverageCacheDirectory); - } - - $timer = new Timer; - $timer->start(); - - $jobResult = $this->phpUtil->runJob($code, $this->stringifyIni($settings)); - $time = $timer->stop()->asSeconds(); - $this->output = $jobResult['stdout'] ?? ''; - - if (isset($codeCoverage) && ($coverage = $this->cleanupForCoverage())) { - $codeCoverage->append($coverage, $this, true, [], []); - } - - try { - $this->assertPhptExpectation($sections, $this->output); - } catch (AssertionFailedError $e) { - $failure = $e; - - if ($xfail !== false) { - $failure = new IncompleteTestError($xfail, 0, $e); - } elseif ($e instanceof ExpectationFailedException) { - $comparisonFailure = $e->getComparisonFailure(); - - if ($comparisonFailure) { - $diff = $comparisonFailure->getDiff(); - } else { - $diff = $e->getMessage(); - } - - $hint = $this->getLocationHintFromDiff($diff, $sections); - $trace = array_merge($hint, debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS)); - $failure = new PHPTAssertionFailedError( - $e->getMessage(), - 0, - $trace[0]['file'], - $trace[0]['line'], - $trace, - $comparisonFailure ? $diff : '', - ); - } - - $result->addFailure($this, $failure, $time); - } catch (Throwable $t) { - $result->addError($this, $t, $time); - } - - if ($xfail !== false && $result->allCompletelyImplemented()) { - $result->addFailure($this, new IncompleteTestError('XFAIL section but test passes'), $time); - } - - $this->runClean($sections, $result->getCollectCodeCoverageInformation()); - - $result->endTest($this, $time); - - return $result; - } - - /** - * Returns the name of the test case. - */ - public function getName(): string - { - return $this->toString(); - } - - /** - * Returns a string representation of the test case. - */ - public function toString(): string - { - return $this->filename; - } - - public function usesDataProvider(): bool - { - return false; - } - - public function getNumAssertions(): int - { - return 1; - } - - public function getActualOutput(): string - { - return $this->output; - } - - public function hasOutput(): bool - { - return !empty($this->output); - } - - public function sortId(): string - { - return $this->filename; - } - - /** - * @return list - */ - public function provides(): array - { - return []; - } - - /** - * @return list - */ - public function requires(): array - { - return []; - } - - /** - * Parse --INI-- section key value pairs and return as array. - * - * @param array|string $content - */ - private function parseIniSection($content, array $ini = []): array - { - if (is_string($content)) { - $content = explode("\n", trim($content)); - } - - foreach ($content as $setting) { - if (strpos($setting, '=') === false) { - continue; - } - - $setting = explode('=', $setting, 2); - $name = trim($setting[0]); - $value = trim($setting[1]); - - if ($name === 'extension' || $name === 'zend_extension') { - if (!isset($ini[$name])) { - $ini[$name] = []; - } - - $ini[$name][] = $value; - - continue; - } - - $ini[$name] = $value; - } - - return $ini; - } - - private function parseEnvSection(string $content): array - { - $env = []; - - foreach (explode("\n", trim($content)) as $e) { - $e = explode('=', trim($e), 2); - - if ($e[0] !== '' && isset($e[1])) { - $env[$e[0]] = $e[1]; - } - } - - return $env; - } - - /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - * @throws Exception - * @throws ExpectationFailedException - */ - private function assertPhptExpectation(array $sections, string $output): void - { - $assertions = [ - 'EXPECT' => 'assertEquals', - 'EXPECTF' => 'assertStringMatchesFormat', - 'EXPECTREGEX' => 'assertMatchesRegularExpression', - ]; - - $actual = preg_replace('/\r\n/', "\n", trim($output)); - - foreach ($assertions as $sectionName => $sectionAssertion) { - if (isset($sections[$sectionName])) { - $sectionContent = preg_replace('/\r\n/', "\n", trim($sections[$sectionName])); - $expected = $sectionName === 'EXPECTREGEX' ? "/{$sectionContent}/" : $sectionContent; - - if ($expected === '') { - throw new Exception('No PHPT expectation found'); - } - - Assert::$sectionAssertion($expected, $actual); - - return; - } - } - - throw new Exception('No PHPT assertion found'); - } - - /** - * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException - */ - private function runSkip(array &$sections, TestResult $result, array $settings): bool - { - if (!isset($sections['SKIPIF'])) { - return false; - } - - $skipif = $this->render($sections['SKIPIF']); - $jobResult = $this->phpUtil->runJob($skipif, $this->stringifyIni($settings)); - - if (!strncasecmp('skip', ltrim($jobResult['stdout']), 4)) { - $message = ''; - - if (preg_match('/^\s*skip\s*(.+)\s*/i', $jobResult['stdout'], $skipMatch)) { - $message = substr($skipMatch[1], 2); - } - - $hint = $this->getLocationHint($message, $sections, 'SKIPIF'); - $trace = array_merge($hint, debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS)); - $result->addFailure( - $this, - new SyntheticSkippedError($message, 0, $trace[0]['file'], $trace[0]['line'], $trace), - 0, - ); - $result->endTest($this, 0); - - return true; - } - - return false; - } - - private function runClean(array &$sections, bool $collectCoverage): void - { - $this->phpUtil->setStdin(''); - $this->phpUtil->setArgs(''); - - if (isset($sections['CLEAN'])) { - $cleanCode = $this->render($sections['CLEAN']); - - $this->phpUtil->runJob($cleanCode, $this->settings($collectCoverage)); - } - } - - /** - * @throws Exception - */ - private function parse(): array - { - $sections = []; - $section = ''; - - $unsupportedSections = [ - 'CGI', - 'COOKIE', - 'DEFLATE_POST', - 'EXPECTHEADERS', - 'EXTENSIONS', - 'GET', - 'GZIP_POST', - 'HEADERS', - 'PHPDBG', - 'POST', - 'POST_RAW', - 'PUT', - 'REDIRECTTEST', - 'REQUEST', - ]; - - $lineNr = 0; - - foreach (file($this->filename) as $line) { - $lineNr++; - - if (preg_match('/^--([_A-Z]+)--/', $line, $result)) { - $section = $result[1]; - $sections[$section] = ''; - $sections[$section . '_offset'] = $lineNr; - - continue; - } - - if (empty($section)) { - throw new Exception('Invalid PHPT file: empty section header'); - } - - $sections[$section] .= $line; - } - - if (isset($sections['FILEEOF'])) { - $sections['FILE'] = rtrim($sections['FILEEOF'], "\r\n"); - unset($sections['FILEEOF']); - } - - $this->parseExternal($sections); - - if (!$this->validate($sections)) { - throw new Exception('Invalid PHPT file'); - } - - foreach ($unsupportedSections as $section) { - if (isset($sections[$section])) { - throw new Exception( - "PHPUnit does not support PHPT {$section} sections", - ); - } - } - - return $sections; - } - - /** - * @throws Exception - */ - private function parseExternal(array &$sections): void - { - $allowSections = [ - 'FILE', - 'EXPECT', - 'EXPECTF', - 'EXPECTREGEX', - ]; - $testDirectory = dirname($this->filename) . DIRECTORY_SEPARATOR; - - foreach ($allowSections as $section) { - if (isset($sections[$section . '_EXTERNAL'])) { - $externalFilename = trim($sections[$section . '_EXTERNAL']); - - if (!is_file($testDirectory . $externalFilename) || - !is_readable($testDirectory . $externalFilename)) { - throw new Exception( - sprintf( - 'Could not load --%s-- %s for PHPT file', - $section . '_EXTERNAL', - $testDirectory . $externalFilename, - ), - ); - } - - $sections[$section] = file_get_contents($testDirectory . $externalFilename); - } - } - } - - private function validate(array &$sections): bool - { - $requiredSections = [ - 'FILE', - [ - 'EXPECT', - 'EXPECTF', - 'EXPECTREGEX', - ], - ]; - - foreach ($requiredSections as $section) { - if (is_array($section)) { - $foundSection = false; - - foreach ($section as $anySection) { - if (isset($sections[$anySection])) { - $foundSection = true; - - break; - } - } - - if (!$foundSection) { - return false; - } - - continue; - } - - if (!isset($sections[$section])) { - return false; - } - } - - return true; - } - - private function render(string $code): string - { - return str_replace( - [ - '__DIR__', - '__FILE__', - ], - [ - "'" . dirname($this->filename) . "'", - "'" . $this->filename . "'", - ], - $code, - ); - } - - private function getCoverageFiles(): array - { - $baseDir = dirname(realpath($this->filename)) . DIRECTORY_SEPARATOR; - $basename = basename($this->filename, 'phpt'); - - return [ - 'coverage' => $baseDir . $basename . 'coverage', - 'job' => $baseDir . $basename . 'php', - ]; - } - - private function renderForCoverage(string &$job, bool $pathCoverage, ?string $codeCoverageCacheDirectory): void - { - $files = $this->getCoverageFiles(); - - $template = new Template( - __DIR__ . '/../Util/PHP/Template/PhptTestCase.tpl', - ); - - $composerAutoload = '\'\''; - - if (defined('PHPUNIT_COMPOSER_INSTALL')) { - $composerAutoload = var_export(PHPUNIT_COMPOSER_INSTALL, true); - } - - $phar = '\'\''; - - if (defined('__PHPUNIT_PHAR__')) { - $phar = var_export(__PHPUNIT_PHAR__, true); - } - - $globals = ''; - - if (!empty($GLOBALS['__PHPUNIT_BOOTSTRAP'])) { - $globals = '$GLOBALS[\'__PHPUNIT_BOOTSTRAP\'] = ' . var_export( - $GLOBALS['__PHPUNIT_BOOTSTRAP'], - true, - ) . ";\n"; - } - - if ($codeCoverageCacheDirectory === null) { - $codeCoverageCacheDirectory = 'null'; - } else { - $codeCoverageCacheDirectory = "'" . $codeCoverageCacheDirectory . "'"; - } - - $template->setVar( - [ - 'composerAutoload' => $composerAutoload, - 'phar' => $phar, - 'globals' => $globals, - 'job' => $files['job'], - 'coverageFile' => $files['coverage'], - 'driverMethod' => $pathCoverage ? 'forLineAndPathCoverage' : 'forLineCoverage', - 'codeCoverageCacheDirectory' => $codeCoverageCacheDirectory, - ], - ); - - file_put_contents($files['job'], $job); - - $job = $template->render(); - } - - private function cleanupForCoverage(): RawCodeCoverageData - { - $coverage = RawCodeCoverageData::fromXdebugWithoutPathCoverage([]); - $files = $this->getCoverageFiles(); - - if (is_file($files['coverage'])) { - $buffer = @file_get_contents($files['coverage']); - - if ($buffer !== false) { - $coverage = @unserialize($buffer); - - if ($coverage === false) { - $coverage = RawCodeCoverageData::fromXdebugWithoutPathCoverage([]); - } - } - } - - foreach ($files as $file) { - @unlink($file); - } - - return $coverage; - } - - private function stringifyIni(array $ini): array - { - $settings = []; - - foreach ($ini as $key => $value) { - if (is_array($value)) { - foreach ($value as $val) { - $settings[] = $key . '=' . $val; - } - - continue; - } - - $settings[] = $key . '=' . $value; - } - - return $settings; - } - - private function getLocationHintFromDiff(string $message, array $sections): array - { - $needle = ''; - $previousLine = ''; - $block = 'message'; - - foreach (preg_split('/\r\n|\r|\n/', $message) as $line) { - $line = trim($line); - - if ($block === 'message' && $line === '--- Expected') { - $block = 'expected'; - } - - if ($block === 'expected' && $line === '@@ @@') { - $block = 'diff'; - } - - if ($block === 'diff') { - if (strpos($line, '+') === 0) { - $needle = $this->getCleanDiffLine($previousLine); - - break; - } - - if (strpos($line, '-') === 0) { - $needle = $this->getCleanDiffLine($line); - - break; - } - } - - if (!empty($line)) { - $previousLine = $line; - } - } - - return $this->getLocationHint($needle, $sections); - } - - private function getCleanDiffLine(string $line): string - { - if (preg_match('/^[\-+]([\'\"]?)(.*)\1$/', $line, $matches)) { - $line = $matches[2]; - } - - return $line; - } - - private function getLocationHint(string $needle, array $sections, ?string $sectionName = null): array - { - $needle = trim($needle); - - if (empty($needle)) { - return [[ - 'file' => realpath($this->filename), - 'line' => 1, - ]]; - } - - if ($sectionName) { - $search = [$sectionName]; - } else { - $search = [ - // 'FILE', - 'EXPECT', - 'EXPECTF', - 'EXPECTREGEX', - ]; - } - - $sectionOffset = null; - - foreach ($search as $section) { - if (!isset($sections[$section])) { - continue; - } - - if (isset($sections[$section . '_EXTERNAL'])) { - $externalFile = trim($sections[$section . '_EXTERNAL']); - - return [ - [ - 'file' => realpath(dirname($this->filename) . DIRECTORY_SEPARATOR . $externalFile), - 'line' => 1, - ], - [ - 'file' => realpath($this->filename), - 'line' => ($sections[$section . '_EXTERNAL_offset'] ?? 0) + 1, - ], - ]; - } - - $sectionOffset = $sections[$section . '_offset'] ?? 0; - $offset = $sectionOffset + 1; - - foreach (preg_split('/\r\n|\r|\n/', $sections[$section]) as $line) { - if (strpos($line, $needle) !== false) { - return [[ - 'file' => realpath($this->filename), - 'line' => $offset, - ]]; - } - $offset++; - } - } - - if ($sectionName) { - // String not found in specified section, show user the start of the named section - return [[ - 'file' => realpath($this->filename), - 'line' => $sectionOffset, - ]]; - } - - // No section specified, show user start of code - return [[ - 'file' => realpath($this->filename), - 'line' => 1, - ]]; - } - - /** - * @psalm-return list - */ - private function settings(bool $collectCoverage): array - { - $settings = [ - 'allow_url_fopen=1', - 'auto_append_file=', - 'auto_prepend_file=', - 'disable_functions=', - 'display_errors=1', - 'docref_ext=.html', - 'docref_root=', - 'error_append_string=', - 'error_prepend_string=', - 'error_reporting=-1', - 'html_errors=0', - 'log_errors=0', - 'open_basedir=', - 'output_buffering=Off', - 'output_handler=', - 'report_memleaks=0', - 'report_zend_debug=0', - ]; - - if (extension_loaded('pcov')) { - if ($collectCoverage) { - $settings[] = 'pcov.enabled=1'; - } else { - $settings[] = 'pcov.enabled=0'; - } - } - - if (extension_loaded('xdebug')) { - if (version_compare(phpversion('xdebug'), '3', '>=')) { - if ($collectCoverage) { - $settings[] = 'xdebug.mode=coverage'; - } else { - $settings[] = 'xdebug.mode=off'; - } - } else { - $settings[] = 'xdebug.default_enable=0'; - - if ($collectCoverage) { - $settings[] = 'xdebug.coverage_enable=1'; - } - } - } - - return $settings; - } -} diff --git a/app/vendor/phpunit/phpunit/src/Runner/ResultCache/DefaultResultCache.php b/app/vendor/phpunit/phpunit/src/Runner/ResultCache/DefaultResultCache.php new file mode 100644 index 000000000..0d700a9a0 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/ResultCache/DefaultResultCache.php @@ -0,0 +1,159 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\ResultCache; + +use const DIRECTORY_SEPARATOR; +use const LOCK_EX; +use function array_keys; +use function assert; +use function dirname; +use function file_get_contents; +use function file_put_contents; +use function is_array; +use function is_dir; +use function is_file; +use function json_decode; +use function json_encode; +use PHPUnit\Framework\TestStatus\TestStatus; +use PHPUnit\Runner\DirectoryDoesNotExistException; +use PHPUnit\Runner\Exception; +use PHPUnit\Util\Filesystem; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class DefaultResultCache implements ResultCache +{ + private const int VERSION = 2; + private const string DEFAULT_RESULT_CACHE_FILENAME = '.phpunit.result.cache'; + private readonly string $cacheFilename; + + /** + * @var array + */ + private array $defects = []; + + /** + * @var array + */ + private array $times = []; + + public function __construct(?string $filepath = null) + { + if ($filepath !== null && is_dir($filepath)) { + $filepath .= DIRECTORY_SEPARATOR . self::DEFAULT_RESULT_CACHE_FILENAME; + } + + $this->cacheFilename = $filepath ?? $_ENV['PHPUNIT_RESULT_CACHE'] ?? self::DEFAULT_RESULT_CACHE_FILENAME; + } + + public function setStatus(ResultCacheId $id, TestStatus $status): void + { + if ($status->isSuccess()) { + return; + } + + $this->defects[$id->asString()] = $status; + } + + public function status(ResultCacheId $id): TestStatus + { + return $this->defects[$id->asString()] ?? TestStatus::unknown(); + } + + public function setTime(ResultCacheId $id, float $time): void + { + $this->times[$id->asString()] = $time; + } + + public function time(ResultCacheId $id): float + { + return $this->times[$id->asString()] ?? 0.0; + } + + public function mergeWith(self $other): void + { + foreach ($other->defects as $id => $defect) { + $this->defects[$id] = $defect; + } + + foreach ($other->times as $id => $time) { + $this->times[$id] = $time; + } + } + + public function load(): void + { + if (!is_file($this->cacheFilename)) { + return; + } + + $contents = file_get_contents($this->cacheFilename); + + if ($contents === false) { + return; + } + + $data = json_decode( + $contents, + true, + ); + + if ($data === null) { + return; + } + + if (!isset($data['version'])) { + return; + } + + if ($data['version'] !== self::VERSION) { + return; + } + + assert(isset($data['defects']) && is_array($data['defects'])); + assert(isset($data['times']) && is_array($data['times'])); + + foreach (array_keys($data['defects']) as $test) { + $data['defects'][$test] = TestStatus::from($data['defects'][$test]); + } + + $this->defects = $data['defects']; + $this->times = $data['times']; + } + + /** + * @throws Exception + */ + public function persist(): void + { + if (!Filesystem::createDirectory(dirname($this->cacheFilename))) { + throw new DirectoryDoesNotExistException(dirname($this->cacheFilename)); + } + + $data = [ + 'version' => self::VERSION, + 'defects' => [], + 'times' => $this->times, + ]; + + foreach ($this->defects as $test => $status) { + $data['defects'][$test] = $status->asInt(); + } + + file_put_contents( + $this->cacheFilename, + json_encode($data), + LOCK_EX, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/ResultCache/NullResultCache.php b/app/vendor/phpunit/phpunit/src/Runner/ResultCache/NullResultCache.php new file mode 100644 index 000000000..46417d40a --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/ResultCache/NullResultCache.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 PHPUnit\Runner\ResultCache; + +use PHPUnit\Framework\TestStatus\TestStatus; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class NullResultCache implements ResultCache +{ + public function setStatus(ResultCacheId $id, TestStatus $status): void + { + } + + public function status(ResultCacheId $id): TestStatus + { + return TestStatus::unknown(); + } + + public function setTime(ResultCacheId $id, float $time): void + { + } + + public function time(ResultCacheId $id): float + { + return 0; + } + + public function load(): void + { + } + + public function persist(): void + { + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/ResultCache/ResultCache.php b/app/vendor/phpunit/phpunit/src/Runner/ResultCache/ResultCache.php new file mode 100644 index 000000000..a3d62f50b --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/ResultCache/ResultCache.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 PHPUnit\Runner\ResultCache; + +use PHPUnit\Framework\TestStatus\TestStatus; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +interface ResultCache +{ + public function setStatus(ResultCacheId $id, TestStatus $status): void; + + public function status(ResultCacheId $id): TestStatus; + + public function setTime(ResultCacheId $id, float $time): void; + + public function time(ResultCacheId $id): float; + + public function load(): void; + + public function persist(): void; +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/ResultCache/ResultCacheHandler.php b/app/vendor/phpunit/phpunit/src/Runner/ResultCache/ResultCacheHandler.php new file mode 100644 index 000000000..b0b45c6d5 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/ResultCache/ResultCacheHandler.php @@ -0,0 +1,147 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\ResultCache; + +use function round; +use PHPUnit\Event\Event; +use PHPUnit\Event\Facade; +use PHPUnit\Event\Telemetry\HRTime; +use PHPUnit\Event\Test\ConsideredRisky; +use PHPUnit\Event\Test\Errored; +use PHPUnit\Event\Test\Failed; +use PHPUnit\Event\Test\Finished; +use PHPUnit\Event\Test\MarkedIncomplete; +use PHPUnit\Event\Test\Prepared; +use PHPUnit\Event\Test\Skipped; +use PHPUnit\Framework\InvalidArgumentException; +use PHPUnit\Framework\TestStatus\TestStatus; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ResultCacheHandler +{ + private readonly ResultCache $cache; + private ?HRTime $time = null; + private int $testSuite = 0; + + public function __construct(ResultCache $cache, Facade $facade) + { + $this->cache = $cache; + + $this->registerSubscribers($facade); + } + + public function testSuiteStarted(): void + { + $this->testSuite++; + } + + public function testSuiteFinished(): void + { + $this->testSuite--; + + if ($this->testSuite === 0) { + $this->cache->persist(); + } + } + + public function testPrepared(Prepared $event): void + { + $this->time = $event->telemetryInfo()->time(); + } + + public function testMarkedIncomplete(MarkedIncomplete $event): void + { + $this->cache->setStatus( + ResultCacheId::fromTest($event->test()), + TestStatus::incomplete($event->throwable()->message()), + ); + } + + public function testConsideredRisky(ConsideredRisky $event): void + { + $this->cache->setStatus( + ResultCacheId::fromTest($event->test()), + TestStatus::risky($event->message()), + ); + } + + public function testErrored(Errored $event): void + { + $this->cache->setStatus( + ResultCacheId::fromTest($event->test()), + TestStatus::error($event->throwable()->message()), + ); + } + + public function testFailed(Failed $event): void + { + $this->cache->setStatus( + ResultCacheId::fromTest($event->test()), + TestStatus::failure($event->throwable()->message()), + ); + } + + /** + * @throws \PHPUnit\Event\InvalidArgumentException + * @throws InvalidArgumentException + */ + public function testSkipped(Skipped $event): void + { + $this->cache->setStatus( + ResultCacheId::fromTest($event->test()), + TestStatus::skipped($event->message()), + ); + + $this->cache->setTime(ResultCacheId::fromTest($event->test()), $this->duration($event)); + } + + /** + * @throws \PHPUnit\Event\InvalidArgumentException + * @throws InvalidArgumentException + */ + public function testFinished(Finished $event): void + { + $this->cache->setTime(ResultCacheId::fromTest($event->test()), $this->duration($event)); + + $this->time = null; + } + + /** + * @throws \PHPUnit\Event\InvalidArgumentException + * @throws InvalidArgumentException + */ + private function duration(Event $event): float + { + if ($this->time === null) { + return 0.0; + } + + return round($event->telemetryInfo()->time()->duration($this->time)->asFloat(), 3); + } + + private function registerSubscribers(Facade $facade): void + { + $facade->registerSubscribers( + new TestSuiteStartedSubscriber($this), + new TestSuiteFinishedSubscriber($this), + new TestPreparedSubscriber($this), + new TestMarkedIncompleteSubscriber($this), + new TestConsideredRiskySubscriber($this), + new TestErroredSubscriber($this), + new TestFailedSubscriber($this), + new TestSkippedSubscriber($this), + new TestFinishedSubscriber($this), + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/ResultCache/ResultCacheId.php b/app/vendor/phpunit/phpunit/src/Runner/ResultCache/ResultCacheId.php new file mode 100644 index 000000000..35a84f287 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/ResultCache/ResultCacheId.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 PHPUnit\Runner\ResultCache; + +use PHPUnit\Event\Code\Test; +use PHPUnit\Event\Code\TestMethod; +use PHPUnit\Framework\Reorderable; +use PHPUnit\Framework\TestCase; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ResultCacheId +{ + public static function fromTest(Test $test): self + { + if ($test instanceof TestMethod) { + return new self($test->className() . '::' . $test->name()); + } + + return new self($test->id()); + } + + public static function fromReorderable(Reorderable $reorderable): self + { + return new self($reorderable->sortId()); + } + + /** + * For use in PHPUnit tests only! + * + * @param class-string $class + */ + public static function fromTestClassAndMethodName(string $class, string $methodName): self + { + return new self($class . '::' . $methodName); + } + + private function __construct( + private string $id, + ) { + } + + public function asString(): string + { + return $this->id; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/ResultCache/Subscriber/Subscriber.php b/app/vendor/phpunit/phpunit/src/Runner/ResultCache/Subscriber/Subscriber.php new file mode 100644 index 000000000..d64dd9f4a --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/ResultCache/Subscriber/Subscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\ResultCache; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +abstract readonly class Subscriber +{ + private ResultCacheHandler $handler; + + public function __construct(ResultCacheHandler $handler) + { + $this->handler = $handler; + } + + protected function handler(): ResultCacheHandler + { + return $this->handler; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/ResultCache/Subscriber/TestConsideredRiskySubscriber.php b/app/vendor/phpunit/phpunit/src/Runner/ResultCache/Subscriber/TestConsideredRiskySubscriber.php new file mode 100644 index 000000000..b2d934013 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/ResultCache/Subscriber/TestConsideredRiskySubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\ResultCache; + +use PHPUnit\Event\Test\ConsideredRisky; +use PHPUnit\Event\Test\ConsideredRiskySubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestConsideredRiskySubscriber extends Subscriber implements ConsideredRiskySubscriber +{ + public function notify(ConsideredRisky $event): void + { + $this->handler()->testConsideredRisky($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/ResultCache/Subscriber/TestErroredSubscriber.php b/app/vendor/phpunit/phpunit/src/Runner/ResultCache/Subscriber/TestErroredSubscriber.php new file mode 100644 index 000000000..ff34e0d8f --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/ResultCache/Subscriber/TestErroredSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\ResultCache; + +use PHPUnit\Event\Test\Errored; +use PHPUnit\Event\Test\ErroredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestErroredSubscriber extends Subscriber implements ErroredSubscriber +{ + public function notify(Errored $event): void + { + $this->handler()->testErrored($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/ResultCache/Subscriber/TestFailedSubscriber.php b/app/vendor/phpunit/phpunit/src/Runner/ResultCache/Subscriber/TestFailedSubscriber.php new file mode 100644 index 000000000..082fa51bd --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/ResultCache/Subscriber/TestFailedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\ResultCache; + +use PHPUnit\Event\Test\Failed; +use PHPUnit\Event\Test\FailedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestFailedSubscriber extends Subscriber implements FailedSubscriber +{ + public function notify(Failed $event): void + { + $this->handler()->testFailed($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/ResultCache/Subscriber/TestFinishedSubscriber.php b/app/vendor/phpunit/phpunit/src/Runner/ResultCache/Subscriber/TestFinishedSubscriber.php new file mode 100644 index 000000000..65f75fcb6 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/ResultCache/Subscriber/TestFinishedSubscriber.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 PHPUnit\Runner\ResultCache; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\Finished; +use PHPUnit\Event\Test\FinishedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestFinishedSubscriber extends Subscriber implements FinishedSubscriber +{ + /** + * @throws \PHPUnit\Framework\InvalidArgumentException + * @throws InvalidArgumentException + */ + public function notify(Finished $event): void + { + $this->handler()->testFinished($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/ResultCache/Subscriber/TestMarkedIncompleteSubscriber.php b/app/vendor/phpunit/phpunit/src/Runner/ResultCache/Subscriber/TestMarkedIncompleteSubscriber.php new file mode 100644 index 000000000..d9c65cf8c --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/ResultCache/Subscriber/TestMarkedIncompleteSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\ResultCache; + +use PHPUnit\Event\Test\MarkedIncomplete; +use PHPUnit\Event\Test\MarkedIncompleteSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestMarkedIncompleteSubscriber extends Subscriber implements MarkedIncompleteSubscriber +{ + public function notify(MarkedIncomplete $event): void + { + $this->handler()->testMarkedIncomplete($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/ResultCache/Subscriber/TestPreparedSubscriber.php b/app/vendor/phpunit/phpunit/src/Runner/ResultCache/Subscriber/TestPreparedSubscriber.php new file mode 100644 index 000000000..a92b82777 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/ResultCache/Subscriber/TestPreparedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\ResultCache; + +use PHPUnit\Event\Test\Prepared; +use PHPUnit\Event\Test\PreparedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestPreparedSubscriber extends Subscriber implements PreparedSubscriber +{ + public function notify(Prepared $event): void + { + $this->handler()->testPrepared($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/ResultCache/Subscriber/TestSkippedSubscriber.php b/app/vendor/phpunit/phpunit/src/Runner/ResultCache/Subscriber/TestSkippedSubscriber.php new file mode 100644 index 000000000..0e493bdc2 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/ResultCache/Subscriber/TestSkippedSubscriber.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 PHPUnit\Runner\ResultCache; + +use PHPUnit\Event\InvalidArgumentException; +use PHPUnit\Event\Test\Skipped; +use PHPUnit\Event\Test\SkippedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestSkippedSubscriber extends Subscriber implements SkippedSubscriber +{ + /** + * @throws \PHPUnit\Framework\InvalidArgumentException + * @throws InvalidArgumentException + */ + public function notify(Skipped $event): void + { + $this->handler()->testSkipped($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/ResultCache/Subscriber/TestSuiteFinishedSubscriber.php b/app/vendor/phpunit/phpunit/src/Runner/ResultCache/Subscriber/TestSuiteFinishedSubscriber.php new file mode 100644 index 000000000..1ef0cc3fb --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/ResultCache/Subscriber/TestSuiteFinishedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\ResultCache; + +use PHPUnit\Event\TestSuite\Finished; +use PHPUnit\Event\TestSuite\FinishedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestSuiteFinishedSubscriber extends Subscriber implements FinishedSubscriber +{ + public function notify(Finished $event): void + { + $this->handler()->testSuiteFinished(); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/ResultCache/Subscriber/TestSuiteStartedSubscriber.php b/app/vendor/phpunit/phpunit/src/Runner/ResultCache/Subscriber/TestSuiteStartedSubscriber.php new file mode 100644 index 000000000..cddedf511 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/ResultCache/Subscriber/TestSuiteStartedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Runner\ResultCache; + +use PHPUnit\Event\TestSuite\Started; +use PHPUnit\Event\TestSuite\StartedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestSuiteStartedSubscriber extends Subscriber implements StartedSubscriber +{ + public function notify(Started $event): void + { + $this->handler()->testSuiteStarted(); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/ResultCacheExtension.php b/app/vendor/phpunit/phpunit/src/Runner/ResultCacheExtension.php deleted file mode 100644 index 31d7610e2..000000000 --- a/app/vendor/phpunit/phpunit/src/Runner/ResultCacheExtension.php +++ /dev/null @@ -1,110 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner; - -use function preg_match; -use function round; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class ResultCacheExtension implements AfterIncompleteTestHook, AfterLastTestHook, AfterRiskyTestHook, AfterSkippedTestHook, AfterSuccessfulTestHook, AfterTestErrorHook, AfterTestFailureHook, AfterTestWarningHook -{ - /** - * @var TestResultCache - */ - private $cache; - - public function __construct(TestResultCache $cache) - { - $this->cache = $cache; - } - - public function flush(): void - { - $this->cache->persist(); - } - - public function executeAfterSuccessfulTest(string $test, float $time): void - { - $testName = $this->getTestName($test); - - $this->cache->setTime($testName, round($time, 3)); - } - - public function executeAfterIncompleteTest(string $test, string $message, float $time): void - { - $testName = $this->getTestName($test); - - $this->cache->setTime($testName, round($time, 3)); - $this->cache->setState($testName, BaseTestRunner::STATUS_INCOMPLETE); - } - - public function executeAfterRiskyTest(string $test, string $message, float $time): void - { - $testName = $this->getTestName($test); - - $this->cache->setTime($testName, round($time, 3)); - $this->cache->setState($testName, BaseTestRunner::STATUS_RISKY); - } - - public function executeAfterSkippedTest(string $test, string $message, float $time): void - { - $testName = $this->getTestName($test); - - $this->cache->setTime($testName, round($time, 3)); - $this->cache->setState($testName, BaseTestRunner::STATUS_SKIPPED); - } - - public function executeAfterTestError(string $test, string $message, float $time): void - { - $testName = $this->getTestName($test); - - $this->cache->setTime($testName, round($time, 3)); - $this->cache->setState($testName, BaseTestRunner::STATUS_ERROR); - } - - public function executeAfterTestFailure(string $test, string $message, float $time): void - { - $testName = $this->getTestName($test); - - $this->cache->setTime($testName, round($time, 3)); - $this->cache->setState($testName, BaseTestRunner::STATUS_FAILURE); - } - - public function executeAfterTestWarning(string $test, string $message, float $time): void - { - $testName = $this->getTestName($test); - - $this->cache->setTime($testName, round($time, 3)); - $this->cache->setState($testName, BaseTestRunner::STATUS_WARNING); - } - - public function executeAfterLastTest(): void - { - $this->flush(); - } - - /** - * @param string $test A long description format of the current test - * - * @return string The test name without TestSuiteClassName:: and @dataprovider details - */ - private function getTestName(string $test): string - { - $matches = []; - - if (preg_match('/^(?\S+::\S+)(?:(? with data set (?:#\d+|"[^"]+"))\s\()?/', $test, $matches)) { - $test = $matches['name'] . ($matches['dataname'] ?? ''); - } - - return $test; - } -} diff --git a/app/vendor/phpunit/phpunit/src/Runner/ShutdownHandler.php b/app/vendor/phpunit/phpunit/src/Runner/ShutdownHandler.php new file mode 100644 index 000000000..29d82d9df --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/ShutdownHandler.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 PHPUnit\Runner; + +use function register_shutdown_function; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ShutdownHandler +{ + private static bool $registered = false; + private static string $message = ''; + + public static function setMessage(string $message): void + { + self::register(); + + self::$message = $message; + } + + public static function resetMessage(): void + { + self::$message = ''; + } + + private static function register(): void + { + if (self::$registered) { + return; + } + + self::$registered = true; + + register_shutdown_function( + static function (): void + { + print self::$message; + }, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/StandardTestSuiteLoader.php b/app/vendor/phpunit/phpunit/src/Runner/StandardTestSuiteLoader.php deleted file mode 100644 index f957e81ac..000000000 --- a/app/vendor/phpunit/phpunit/src/Runner/StandardTestSuiteLoader.php +++ /dev/null @@ -1,152 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner; - -use function array_diff; -use function array_values; -use function basename; -use function class_exists; -use function get_declared_classes; -use function sprintf; -use function stripos; -use function strlen; -use function substr; -use PHPUnit\Framework\TestCase; -use PHPUnit\Util\FileLoader; -use ReflectionClass; -use ReflectionException; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @deprecated see https://github.com/sebastianbergmann/phpunit/issues/4039 - */ -final class StandardTestSuiteLoader implements TestSuiteLoader -{ - /** - * @throws Exception - */ - public function load(string $suiteClassFile): ReflectionClass - { - $suiteClassName = basename($suiteClassFile, '.php'); - $loadedClasses = get_declared_classes(); - - if (!class_exists($suiteClassName, false)) { - /* @noinspection UnusedFunctionResultInspection */ - FileLoader::checkAndLoad($suiteClassFile); - - $loadedClasses = array_values( - array_diff(get_declared_classes(), $loadedClasses), - ); - - if (empty($loadedClasses)) { - throw new Exception( - sprintf( - 'Class %s could not be found in %s', - $suiteClassName, - $suiteClassFile, - ), - ); - } - } - - if (!class_exists($suiteClassName, false)) { - $offset = 0 - strlen($suiteClassName); - - foreach ($loadedClasses as $loadedClass) { - // @see https://github.com/sebastianbergmann/phpunit/issues/5020 - if (stripos(substr($loadedClass, $offset - 1), '\\' . $suiteClassName) === 0 || - stripos(substr($loadedClass, $offset - 1), '_' . $suiteClassName) === 0) { - $suiteClassName = $loadedClass; - - break; - } - } - } - - if (!class_exists($suiteClassName, false)) { - throw new Exception( - sprintf( - 'Class %s could not be found in %s', - $suiteClassName, - $suiteClassFile, - ), - ); - } - - try { - $class = new ReflectionClass($suiteClassName); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - $e->getCode(), - $e, - ); - } - // @codeCoverageIgnoreEnd - - if ($class->isSubclassOf(TestCase::class)) { - if ($class->isAbstract()) { - throw new Exception( - sprintf( - 'Class %s declared in %s is abstract', - $suiteClassName, - $suiteClassFile, - ), - ); - } - - return $class; - } - - if ($class->hasMethod('suite')) { - try { - $method = $class->getMethod('suite'); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception( - sprintf( - 'Method %s::suite() declared in %s is abstract', - $suiteClassName, - $suiteClassFile, - ), - ); - } - - if (!$method->isPublic()) { - throw new Exception( - sprintf( - 'Method %s::suite() declared in %s is not public', - $suiteClassName, - $suiteClassFile, - ), - ); - } - - if (!$method->isStatic()) { - throw new Exception( - sprintf( - 'Method %s::suite() declared in %s is not static', - $suiteClassName, - $suiteClassFile, - ), - ); - } - } - - return $class; - } - - public function reload(ReflectionClass $aClass): ReflectionClass - { - return $aClass; - } -} diff --git a/app/vendor/phpunit/phpunit/src/Runner/TestResult/Collector.php b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Collector.php new file mode 100644 index 000000000..5615a0dad --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Collector.php @@ -0,0 +1,681 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use function array_values; +use function assert; +use function count; +use function implode; +use PHPUnit\Event\Code\TestMethod; +use PHPUnit\Event\Facade; +use PHPUnit\Event\Test\AfterLastTestMethodErrored; +use PHPUnit\Event\Test\AfterLastTestMethodFailed; +use PHPUnit\Event\Test\BeforeFirstTestMethodErrored; +use PHPUnit\Event\Test\BeforeFirstTestMethodFailed; +use PHPUnit\Event\Test\ConsideredRisky; +use PHPUnit\Event\Test\DeprecationTriggered; +use PHPUnit\Event\Test\Errored; +use PHPUnit\Event\Test\ErrorTriggered; +use PHPUnit\Event\Test\Failed; +use PHPUnit\Event\Test\Finished; +use PHPUnit\Event\Test\MarkedIncomplete; +use PHPUnit\Event\Test\NoticeTriggered; +use PHPUnit\Event\Test\PhpDeprecationTriggered; +use PHPUnit\Event\Test\PhpNoticeTriggered; +use PHPUnit\Event\Test\PhpunitDeprecationTriggered; +use PHPUnit\Event\Test\PhpunitErrorTriggered; +use PHPUnit\Event\Test\PhpunitNoticeTriggered; +use PHPUnit\Event\Test\PhpunitWarningTriggered; +use PHPUnit\Event\Test\PhpWarningTriggered; +use PHPUnit\Event\Test\Skipped as TestSkipped; +use PHPUnit\Event\Test\WarningTriggered; +use PHPUnit\Event\TestRunner\ChildProcessErrored; +use PHPUnit\Event\TestRunner\DeprecationTriggered as TestRunnerDeprecationTriggered; +use PHPUnit\Event\TestRunner\ExecutionStarted; +use PHPUnit\Event\TestRunner\NoticeTriggered as TestRunnerNoticeTriggered; +use PHPUnit\Event\TestRunner\WarningTriggered as TestRunnerWarningTriggered; +use PHPUnit\Event\TestSuite\Finished as TestSuiteFinished; +use PHPUnit\Event\TestSuite\Skipped as TestSuiteSkipped; +use PHPUnit\Event\TestSuite\Started as TestSuiteStarted; +use PHPUnit\Event\TestSuite\TestSuiteForTestClass; +use PHPUnit\Event\TestSuite\TestSuiteForTestMethodWithDataProvider; +use PHPUnit\TestRunner\IssueFilter; +use PHPUnit\TestRunner\TestResult\Issues\Issue; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Collector +{ + private readonly IssueFilter $issueFilter; + private int $numberOfTests = 0; + private int $numberOfTestsRun = 0; + private int $numberOfAssertions = 0; + private bool $prepared = false; + private bool $childProcessErrored = false; + + /** + * @var non-negative-int + */ + private int $numberOfIssuesIgnoredByBaseline = 0; + + /** + * @var list + */ + private array $testErroredEvents = []; + + /** + * @var list + */ + private array $testFailedEvents = []; + + /** + * @var list + */ + private array $testMarkedIncompleteEvents = []; + + /** + * @var list + */ + private array $testSuiteSkippedEvents = []; + + /** + * @var list + */ + private array $testSkippedEvents = []; + + /** + * @var array> + */ + private array $testConsideredRiskyEvents = []; + + /** + * @var array> + */ + private array $testTriggeredPhpunitDeprecationEvents = []; + + /** + * @var array> + */ + private array $testTriggeredPhpunitErrorEvents = []; + + /** + * @var array> + */ + private array $testTriggeredPhpunitNoticeEvents = []; + + /** + * @var array> + */ + private array $testTriggeredPhpunitWarningEvents = []; + + /** + * @var list + */ + private array $testRunnerTriggeredDeprecationEvents = []; + + /** + * @var list + */ + private array $testRunnerTriggeredNoticeEvents = []; + + /** + * @var list + */ + private array $testRunnerTriggeredWarningEvents = []; + + /** + * @var array + */ + private array $errors = []; + + /** + * @var array + */ + private array $deprecations = []; + + /** + * @var array + */ + private array $notices = []; + + /** + * @var array + */ + private array $warnings = []; + + /** + * @var array + */ + private array $phpDeprecations = []; + + /** + * @var array + */ + private array $phpNotices = []; + + /** + * @var array + */ + private array $phpWarnings = []; + + public function __construct(Facade $facade, IssueFilter $issueFilter) + { + $facade->registerSubscribers( + new ExecutionStartedSubscriber($this), + new TestSuiteSkippedSubscriber($this), + new TestSuiteStartedSubscriber($this), + new TestSuiteFinishedSubscriber($this), + new TestPreparedSubscriber($this), + new TestFinishedSubscriber($this), + new BeforeTestClassMethodErroredSubscriber($this), + new BeforeTestClassMethodFailedSubscriber($this), + new AfterTestClassMethodErroredSubscriber($this), + new AfterTestClassMethodFailedSubscriber($this), + new TestErroredSubscriber($this), + new TestFailedSubscriber($this), + new TestMarkedIncompleteSubscriber($this), + new TestSkippedSubscriber($this), + new TestConsideredRiskySubscriber($this), + new TestTriggeredDeprecationSubscriber($this), + new TestTriggeredErrorSubscriber($this), + new TestTriggeredNoticeSubscriber($this), + new TestTriggeredPhpDeprecationSubscriber($this), + new TestTriggeredPhpNoticeSubscriber($this), + new TestTriggeredPhpunitDeprecationSubscriber($this), + new TestTriggeredPhpunitErrorSubscriber($this), + new TestTriggeredPhpunitNoticeSubscriber($this), + new TestTriggeredPhpunitWarningSubscriber($this), + new TestTriggeredPhpWarningSubscriber($this), + new TestTriggeredWarningSubscriber($this), + new TestRunnerTriggeredDeprecationSubscriber($this), + new TestRunnerTriggeredNoticeSubscriber($this), + new TestRunnerTriggeredWarningSubscriber($this), + new ChildProcessErroredSubscriber($this), + ); + + $this->issueFilter = $issueFilter; + } + + public function result(): TestResult + { + return new TestResult( + $this->numberOfTests, + $this->numberOfTestsRun, + $this->numberOfAssertions, + $this->testErroredEvents, + $this->testFailedEvents, + $this->testConsideredRiskyEvents, + $this->testSuiteSkippedEvents, + $this->testSkippedEvents, + $this->testMarkedIncompleteEvents, + $this->testTriggeredPhpunitDeprecationEvents, + $this->testTriggeredPhpunitErrorEvents, + $this->testTriggeredPhpunitNoticeEvents, + $this->testTriggeredPhpunitWarningEvents, + $this->testRunnerTriggeredDeprecationEvents, + $this->testRunnerTriggeredNoticeEvents, + $this->testRunnerTriggeredWarningEvents, + array_values($this->errors), + array_values($this->deprecations), + array_values($this->notices), + array_values($this->warnings), + array_values($this->phpDeprecations), + array_values($this->phpNotices), + array_values($this->phpWarnings), + $this->numberOfIssuesIgnoredByBaseline, + ); + } + + public function executionStarted(ExecutionStarted $event): void + { + $this->numberOfTests = $event->testSuite()->count(); + } + + public function testSuiteSkipped(TestSuiteSkipped $event): void + { + $testSuite = $event->testSuite(); + + if (!$testSuite->isForTestClass()) { + return; + } + + $this->testSuiteSkippedEvents[] = $event; + } + + public function testSuiteStarted(TestSuiteStarted $event): void + { + $testSuite = $event->testSuite(); + + if (!$testSuite->isForTestClass()) { + return; + } + } + + public function testSuiteFinished(TestSuiteFinished $event): void + { + $testSuite = $event->testSuite(); + + if ($testSuite->isWithName()) { + return; + } + + if ($testSuite->isForTestMethodWithDataProvider()) { + assert($testSuite instanceof TestSuiteForTestMethodWithDataProvider); + assert(count($testSuite->tests()->asArray()) > 0); + + $test = $testSuite->tests()->asArray()[0]; + + assert($test instanceof TestMethod); + + foreach ($this->testFailedEvents as $testFailedEvent) { + if ($testFailedEvent->test()->isTestMethod() && $testFailedEvent->test()->methodName() === $test->methodName()) { + return; + } + } + + PassedTests::instance()->testMethodPassed($test, null); + + return; + } + + assert($testSuite instanceof TestSuiteForTestClass); + + PassedTests::instance()->testClassPassed($testSuite->className()); + } + + public function testPrepared(): void + { + $this->prepared = true; + } + + public function testFinished(Finished $event): void + { + $this->numberOfAssertions += $event->numberOfAssertionsPerformed(); + + $this->numberOfTestsRun++; + + $this->prepared = false; + $this->childProcessErrored = false; + } + + public function beforeTestClassMethodErrored(BeforeFirstTestMethodErrored $event): void + { + $this->testErroredEvents[] = $event; + + $this->numberOfTestsRun++; + } + + public function beforeTestClassMethodFailed(BeforeFirstTestMethodFailed $event): void + { + $this->testFailedEvents[] = $event; + + $this->numberOfTestsRun++; + } + + public function afterTestClassMethodErrored(AfterLastTestMethodErrored $event): void + { + $this->testErroredEvents[] = $event; + } + + public function afterTestClassMethodFailed(AfterLastTestMethodFailed $event): void + { + $this->testFailedEvents[] = $event; + } + + public function testErrored(Errored $event): void + { + $this->testErroredEvents[] = $event; + + if ($this->childProcessErrored) { + return; + } + + if (!$this->prepared) { + $this->numberOfTestsRun++; + } + } + + public function testFailed(Failed $event): void + { + $this->testFailedEvents[] = $event; + } + + public function testMarkedIncomplete(MarkedIncomplete $event): void + { + $this->testMarkedIncompleteEvents[] = $event; + } + + public function testSkipped(TestSkipped $event): void + { + $this->testSkippedEvents[] = $event; + + if (!$this->prepared) { + $this->numberOfTestsRun++; + } + } + + public function testConsideredRisky(ConsideredRisky $event): void + { + if (!isset($this->testConsideredRiskyEvents[$event->test()->id()])) { + $this->testConsideredRiskyEvents[$event->test()->id()] = []; + } + + $this->testConsideredRiskyEvents[$event->test()->id()][] = $event; + } + + public function testTriggeredDeprecation(DeprecationTriggered $event): void + { + if (!$this->issueFilter->shouldBeProcessed($event)) { + return; + } + + if ($event->ignoredByBaseline()) { + $this->numberOfIssuesIgnoredByBaseline++; + + return; + } + + $id = $this->issueId($event); + + if (!isset($this->deprecations[$id])) { + $this->deprecations[$id] = Issue::from( + $event->file(), + $event->line(), + $event->message(), + $event->test(), + $event->stackTrace(), + ); + + return; + } + + $this->deprecations[$id]->triggeredBy($event->test()); + } + + public function testTriggeredPhpDeprecation(PhpDeprecationTriggered $event): void + { + if (!$this->issueFilter->shouldBeProcessed($event)) { + return; + } + + if ($event->ignoredByBaseline()) { + $this->numberOfIssuesIgnoredByBaseline++; + + return; + } + + $id = $this->issueId($event); + + if (!isset($this->phpDeprecations[$id])) { + $this->phpDeprecations[$id] = Issue::from( + $event->file(), + $event->line(), + $event->message(), + $event->test(), + ); + + return; + } + + $this->phpDeprecations[$id]->triggeredBy($event->test()); + } + + public function testTriggeredPhpunitDeprecation(PhpunitDeprecationTriggered $event): void + { + if (!isset($this->testTriggeredPhpunitDeprecationEvents[$event->test()->id()])) { + $this->testTriggeredPhpunitDeprecationEvents[$event->test()->id()] = []; + } + + $this->testTriggeredPhpunitDeprecationEvents[$event->test()->id()][] = $event; + } + + public function testTriggeredPhpunitNotice(PhpunitNoticeTriggered $event): void + { + if (!isset($this->testTriggeredPhpunitNoticeEvents[$event->test()->id()])) { + $this->testTriggeredPhpunitNoticeEvents[$event->test()->id()] = []; + } + + $this->testTriggeredPhpunitNoticeEvents[$event->test()->id()][] = $event; + } + + public function testTriggeredError(ErrorTriggered $event): void + { + if (!$this->issueFilter->shouldBeProcessed($event)) { + return; + } + + $id = $this->issueId($event); + + if (!isset($this->errors[$id])) { + $this->errors[$id] = Issue::from( + $event->file(), + $event->line(), + $event->message(), + $event->test(), + ); + + return; + } + + $this->errors[$id]->triggeredBy($event->test()); + } + + public function testTriggeredNotice(NoticeTriggered $event): void + { + if (!$this->issueFilter->shouldBeProcessed($event)) { + return; + } + + if ($event->ignoredByBaseline()) { + $this->numberOfIssuesIgnoredByBaseline++; + + return; + } + + $id = $this->issueId($event); + + if (!isset($this->notices[$id])) { + $this->notices[$id] = Issue::from( + $event->file(), + $event->line(), + $event->message(), + $event->test(), + ); + + return; + } + + $this->notices[$id]->triggeredBy($event->test()); + } + + public function testTriggeredPhpNotice(PhpNoticeTriggered $event): void + { + if (!$this->issueFilter->shouldBeProcessed($event)) { + return; + } + + if ($event->ignoredByBaseline()) { + $this->numberOfIssuesIgnoredByBaseline++; + + return; + } + + $id = $this->issueId($event); + + if (!isset($this->phpNotices[$id])) { + $this->phpNotices[$id] = Issue::from( + $event->file(), + $event->line(), + $event->message(), + $event->test(), + ); + + return; + } + + $this->phpNotices[$id]->triggeredBy($event->test()); + } + + public function testTriggeredWarning(WarningTriggered $event): void + { + if (!$this->issueFilter->shouldBeProcessed($event)) { + return; + } + + if ($event->ignoredByBaseline()) { + $this->numberOfIssuesIgnoredByBaseline++; + + return; + } + + $id = $this->issueId($event); + + if (!isset($this->warnings[$id])) { + $this->warnings[$id] = Issue::from( + $event->file(), + $event->line(), + $event->message(), + $event->test(), + ); + + return; + } + + $this->warnings[$id]->triggeredBy($event->test()); + } + + public function testTriggeredPhpWarning(PhpWarningTriggered $event): void + { + if (!$this->issueFilter->shouldBeProcessed($event)) { + return; + } + + if ($event->ignoredByBaseline()) { + $this->numberOfIssuesIgnoredByBaseline++; + + return; + } + + $id = $this->issueId($event); + + if (!isset($this->phpWarnings[$id])) { + $this->phpWarnings[$id] = Issue::from( + $event->file(), + $event->line(), + $event->message(), + $event->test(), + ); + + return; + } + + $this->phpWarnings[$id]->triggeredBy($event->test()); + } + + public function testTriggeredPhpunitError(PhpunitErrorTriggered $event): void + { + if (!isset($this->testTriggeredPhpunitErrorEvents[$event->test()->id()])) { + $this->testTriggeredPhpunitErrorEvents[$event->test()->id()] = []; + } + + $this->testTriggeredPhpunitErrorEvents[$event->test()->id()][] = $event; + } + + public function testTriggeredPhpunitWarning(PhpunitWarningTriggered $event): void + { + if ($event->ignoredByTest()) { + return; + } + + if (!isset($this->testTriggeredPhpunitWarningEvents[$event->test()->id()])) { + $this->testTriggeredPhpunitWarningEvents[$event->test()->id()] = []; + } + + $this->testTriggeredPhpunitWarningEvents[$event->test()->id()][] = $event; + } + + public function testRunnerTriggeredDeprecation(TestRunnerDeprecationTriggered $event): void + { + $this->testRunnerTriggeredDeprecationEvents[] = $event; + } + + public function testRunnerTriggeredNotice(TestRunnerNoticeTriggered $event): void + { + $this->testRunnerTriggeredNoticeEvents[] = $event; + } + + public function testRunnerTriggeredWarning(TestRunnerWarningTriggered $event): void + { + $this->testRunnerTriggeredWarningEvents[] = $event; + } + + public function childProcessErrored(ChildProcessErrored $event): void + { + $this->childProcessErrored = true; + } + + public function hasErroredTests(): bool + { + return $this->testErroredEvents !== []; + } + + public function hasFailedTests(): bool + { + return $this->testFailedEvents !== []; + } + + public function hasRiskyTests(): bool + { + return $this->testConsideredRiskyEvents !== []; + } + + public function hasSkippedTests(): bool + { + return $this->testSkippedEvents !== []; + } + + public function hasIncompleteTests(): bool + { + return $this->testMarkedIncompleteEvents !== []; + } + + public function hasDeprecations(): bool + { + return $this->deprecations !== [] || + $this->phpDeprecations !== [] || + $this->testTriggeredPhpunitDeprecationEvents !== [] || + $this->testRunnerTriggeredDeprecationEvents !== []; + } + + public function hasNotices(): bool + { + return $this->notices !== [] || + $this->phpNotices !== []; + } + + public function hasWarnings(): bool + { + return $this->warnings !== [] || + $this->phpWarnings !== [] || + $this->testTriggeredPhpunitWarningEvents !== [] || + $this->testRunnerTriggeredWarningEvents !== []; + } + + /** + * @return non-empty-string + */ + private function issueId(DeprecationTriggered|ErrorTriggered|NoticeTriggered|PhpDeprecationTriggered|PhpNoticeTriggered|PhpWarningTriggered|WarningTriggered $event): string + { + return implode(':', [$event->file(), $event->line(), $event->message()]); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/TestResult/Facade.php b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Facade.php new file mode 100644 index 000000000..4862d6860 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Facade.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use function str_contains; +use PHPUnit\Event\Facade as EventFacade; +use PHPUnit\Runner\DeprecationCollector\Facade as DeprecationCollectorFacade; +use PHPUnit\TestRunner\IssueFilter; +use PHPUnit\TextUI\Configuration\Configuration; +use PHPUnit\TextUI\Configuration\Registry as ConfigurationRegistry; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Facade +{ + private static ?Collector $collector = null; + + public static function init(): void + { + self::collector(); + } + + public static function result(): TestResult + { + return self::collector()->result(); + } + + public static function shouldStop(): bool + { + $configuration = ConfigurationRegistry::get(); + $collector = self::collector(); + + if (($configuration->stopOnDefect() || $configuration->stopOnError()) && $collector->hasErroredTests()) { + return true; + } + + if (($configuration->stopOnDefect() || $configuration->stopOnFailure()) && $collector->hasFailedTests()) { + return true; + } + + if (($configuration->stopOnDefect() || $configuration->stopOnWarning()) && $collector->hasWarnings()) { + return true; + } + + if (($configuration->stopOnDefect() || $configuration->stopOnRisky()) && $collector->hasRiskyTests()) { + return true; + } + + if (self::stopOnDeprecation($configuration)) { + return true; + } + + if ($configuration->stopOnNotice() && $collector->hasNotices()) { + return true; + } + + if ($configuration->stopOnIncomplete() && $collector->hasIncompleteTests()) { + return true; + } + + if ($configuration->stopOnSkipped() && $collector->hasSkippedTests()) { + return true; + } + + return false; + } + + private static function collector(): Collector + { + if (self::$collector === null) { + $configuration = ConfigurationRegistry::get(); + + self::$collector = new Collector( + EventFacade::instance(), + new IssueFilter($configuration->source()), + ); + } + + return self::$collector; + } + + private static function stopOnDeprecation(Configuration $configuration): bool + { + if (!$configuration->stopOnDeprecation()) { + return false; + } + + $deprecations = DeprecationCollectorFacade::filteredDeprecations(); + + if (!$configuration->hasSpecificDeprecationToStopOn()) { + return $deprecations !== []; + } + + foreach ($deprecations as $deprecation) { + if (str_contains($deprecation, $configuration->specificDeprecationToStopOn())) { + return true; + } + } + + return false; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/TestResult/Issue.php b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Issue.php new file mode 100644 index 000000000..12ade5c3f --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Issue.php @@ -0,0 +1,145 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult\Issues; + +use function array_keys; +use function count; +use PHPUnit\Event\Code\Test; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Issue +{ + /** + * @var non-empty-string + */ + private readonly string $file; + + /** + * @var positive-int + */ + private readonly int $line; + + /** + * @var non-empty-string + */ + private readonly string $description; + + /** + * @var non-empty-array + */ + private array $triggeringTests; + + /** + * @var ?non-empty-string + */ + private ?string $stackTrace; + + /** + * @param non-empty-string $file + * @param positive-int $line + * @param non-empty-string $description + */ + public static function from(string $file, int $line, string $description, Test $triggeringTest, ?string $stackTrace = null): self + { + return new self($file, $line, $description, $triggeringTest, $stackTrace); + } + + /** + * @param non-empty-string $file + * @param positive-int $line + * @param non-empty-string $description + */ + private function __construct(string $file, int $line, string $description, Test $triggeringTest, ?string $stackTrace) + { + $this->file = $file; + $this->line = $line; + $this->description = $description; + $this->stackTrace = $stackTrace; + + $this->triggeringTests = [ + $triggeringTest->id() => [ + 'test' => $triggeringTest, + 'count' => 1, + ], + ]; + } + + public function triggeredBy(Test $test): void + { + if (isset($this->triggeringTests[$test->id()])) { + $this->triggeringTests[$test->id()]['count']++; + + return; + } + + $this->triggeringTests[$test->id()] = [ + 'test' => $test, + 'count' => 1, + ]; + } + + /** + * @return non-empty-string + */ + public function file(): string + { + return $this->file; + } + + /** + * @return positive-int + */ + public function line(): int + { + return $this->line; + } + + /** + * @return non-empty-string + */ + public function description(): string + { + return $this->description; + } + + /** + * @return non-empty-array + */ + public function triggeringTests(): array + { + return $this->triggeringTests; + } + + /** + * @phpstan-assert-if-true !null $this->stackTrace + */ + public function hasStackTrace(): bool + { + return $this->stackTrace !== null; + } + + /** + * @return ?non-empty-string + */ + public function stackTrace(): ?string + { + return $this->stackTrace; + } + + public function triggeredInTest(): bool + { + return count($this->triggeringTests) === 1 && + $this->file === $this->triggeringTests[array_keys($this->triggeringTests)[0]]['test']->file(); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/TestResult/PassedTests.php b/app/vendor/phpunit/phpunit/src/Runner/TestResult/PassedTests.php new file mode 100644 index 000000000..4fad1b5ec --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/TestResult/PassedTests.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 PHPUnit\TestRunner\TestResult; + +use function array_merge; +use function assert; +use function explode; +use function in_array; +use PHPUnit\Event\Code\TestMethod; +use PHPUnit\Framework\TestSize\Known; +use PHPUnit\Framework\TestSize\TestSize; +use PHPUnit\Metadata\Api\Groups; +use ReflectionMethod; +use ReflectionNamedType; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class PassedTests +{ + private static ?self $instance = null; + + /** + * @var list + */ + private array $passedTestClasses = []; + + /** + * @var array + */ + private array $passedTestMethods = []; + + public static function instance(): self + { + if (self::$instance !== null) { + return self::$instance; + } + + self::$instance = new self; + + return self::$instance; + } + + /** + * @param class-string $className + */ + public function testClassPassed(string $className): void + { + $this->passedTestClasses[] = $className; + } + + public function testMethodPassed(TestMethod $test, mixed $returnValue): void + { + $size = (new Groups)->size( + $test->className(), + $test->methodName(), + ); + + $this->passedTestMethods[$test->className() . '::' . $test->methodName()] = [ + 'returnValue' => $returnValue, + 'size' => $size, + ]; + } + + public function import(self $other): void + { + $this->passedTestClasses = array_merge( + $this->passedTestClasses, + $other->passedTestClasses, + ); + + $this->passedTestMethods = array_merge( + $this->passedTestMethods, + $other->passedTestMethods, + ); + } + + /** + * @param class-string $className + */ + public function hasTestClassPassed(string $className): bool + { + return in_array($className, $this->passedTestClasses, true); + } + + public function hasTestMethodPassed(string $method): bool + { + return isset($this->passedTestMethods[$method]); + } + + public function isGreaterThan(string $method, TestSize $other): bool + { + if ($other->isUnknown()) { + return false; + } + + assert($other instanceof Known); + + $size = $this->passedTestMethods[$method]['size']; + + if ($size->isUnknown()) { + return false; + } + + assert($size instanceof Known); + + return $size->isGreaterThan($other); + } + + public function hasReturnValue(string $method): bool + { + $returnType = (new ReflectionMethod(...explode('::', $method)))->getReturnType(); + + return !$returnType instanceof ReflectionNamedType || !in_array($returnType->getName(), ['never', 'void'], true); + } + + public function returnValue(string $method): mixed + { + if (isset($this->passedTestMethods[$method])) { + return $this->passedTestMethods[$method]['returnValue']; + } + + return null; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/AfterTestClassMethodErroredSubscriber.php b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/AfterTestClassMethodErroredSubscriber.php new file mode 100644 index 000000000..eb94433bd --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/AfterTestClassMethodErroredSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\Test\AfterLastTestMethodErrored; +use PHPUnit\Event\Test\AfterLastTestMethodErroredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class AfterTestClassMethodErroredSubscriber extends Subscriber implements AfterLastTestMethodErroredSubscriber +{ + public function notify(AfterLastTestMethodErrored $event): void + { + $this->collector()->afterTestClassMethodErrored($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/AfterTestClassMethodFailedSubscriber.php b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/AfterTestClassMethodFailedSubscriber.php new file mode 100644 index 000000000..e207ba1d0 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/AfterTestClassMethodFailedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\Test\AfterLastTestMethodFailed; +use PHPUnit\Event\Test\AfterLastTestMethodFailedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class AfterTestClassMethodFailedSubscriber extends Subscriber implements AfterLastTestMethodFailedSubscriber +{ + public function notify(AfterLastTestMethodFailed $event): void + { + $this->collector()->afterTestClassMethodFailed($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/BeforeTestClassMethodErroredSubscriber.php b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/BeforeTestClassMethodErroredSubscriber.php new file mode 100644 index 000000000..1929125e5 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/BeforeTestClassMethodErroredSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\Test\BeforeFirstTestMethodErrored; +use PHPUnit\Event\Test\BeforeFirstTestMethodErroredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class BeforeTestClassMethodErroredSubscriber extends Subscriber implements BeforeFirstTestMethodErroredSubscriber +{ + public function notify(BeforeFirstTestMethodErrored $event): void + { + $this->collector()->beforeTestClassMethodErrored($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/BeforeTestClassMethodFailedSubscriber.php b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/BeforeTestClassMethodFailedSubscriber.php new file mode 100644 index 000000000..0e69855a0 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/BeforeTestClassMethodFailedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\Test\BeforeFirstTestMethodFailed; +use PHPUnit\Event\Test\BeforeFirstTestMethodFailedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class BeforeTestClassMethodFailedSubscriber extends Subscriber implements BeforeFirstTestMethodFailedSubscriber +{ + public function notify(BeforeFirstTestMethodFailed $event): void + { + $this->collector()->beforeTestClassMethodFailed($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/ChildProcessErroredSubscriber.php b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/ChildProcessErroredSubscriber.php new file mode 100644 index 000000000..3d70d88ed --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/ChildProcessErroredSubscriber.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\TestRunner\ChildProcessErrored; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ChildProcessErroredSubscriber extends Subscriber implements \PHPUnit\Event\TestRunner\ChildProcessErroredSubscriber +{ + public function notify(ChildProcessErrored $event): void + { + $this->collector()->childProcessErrored($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/ExecutionStartedSubscriber.php b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/ExecutionStartedSubscriber.php new file mode 100644 index 000000000..b54ae9e72 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/ExecutionStartedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\TestRunner\ExecutionStarted; +use PHPUnit\Event\TestRunner\ExecutionStartedSubscriber as TestRunnerExecutionStartedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ExecutionStartedSubscriber extends Subscriber implements TestRunnerExecutionStartedSubscriber +{ + public function notify(ExecutionStarted $event): void + { + $this->collector()->executionStarted($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/Subscriber.php b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/Subscriber.php new file mode 100644 index 000000000..36be4941b --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/Subscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +abstract readonly class Subscriber +{ + private Collector $collector; + + public function __construct(Collector $collector) + { + $this->collector = $collector; + } + + protected function collector(): Collector + { + return $this->collector; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestConsideredRiskySubscriber.php b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestConsideredRiskySubscriber.php new file mode 100644 index 000000000..8584fddcc --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestConsideredRiskySubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\Test\ConsideredRisky; +use PHPUnit\Event\Test\ConsideredRiskySubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestConsideredRiskySubscriber extends Subscriber implements ConsideredRiskySubscriber +{ + public function notify(ConsideredRisky $event): void + { + $this->collector()->testConsideredRisky($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestErroredSubscriber.php b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestErroredSubscriber.php new file mode 100644 index 000000000..a97c21a68 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestErroredSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\Test\Errored; +use PHPUnit\Event\Test\ErroredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestErroredSubscriber extends Subscriber implements ErroredSubscriber +{ + public function notify(Errored $event): void + { + $this->collector()->testErrored($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestFailedSubscriber.php b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestFailedSubscriber.php new file mode 100644 index 000000000..118b304cd --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestFailedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\Test\Failed; +use PHPUnit\Event\Test\FailedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestFailedSubscriber extends Subscriber implements FailedSubscriber +{ + public function notify(Failed $event): void + { + $this->collector()->testFailed($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestFinishedSubscriber.php b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestFinishedSubscriber.php new file mode 100644 index 000000000..37fe67d0f --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestFinishedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\Test\Finished; +use PHPUnit\Event\Test\FinishedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestFinishedSubscriber extends Subscriber implements FinishedSubscriber +{ + public function notify(Finished $event): void + { + $this->collector()->testFinished($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestMarkedIncompleteSubscriber.php b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestMarkedIncompleteSubscriber.php new file mode 100644 index 000000000..c9d13ab56 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestMarkedIncompleteSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\Test\MarkedIncomplete; +use PHPUnit\Event\Test\MarkedIncompleteSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestMarkedIncompleteSubscriber extends Subscriber implements MarkedIncompleteSubscriber +{ + public function notify(MarkedIncomplete $event): void + { + $this->collector()->testMarkedIncomplete($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestPreparedSubscriber.php b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestPreparedSubscriber.php new file mode 100644 index 000000000..6dd05ca75 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestPreparedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\Test\Prepared; +use PHPUnit\Event\Test\PreparedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestPreparedSubscriber extends Subscriber implements PreparedSubscriber +{ + public function notify(Prepared $event): void + { + $this->collector()->testPrepared(); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestRunnerTriggeredDeprecationSubscriber.php b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestRunnerTriggeredDeprecationSubscriber.php new file mode 100644 index 000000000..36b3ea039 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestRunnerTriggeredDeprecationSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\TestRunner\DeprecationTriggered; +use PHPUnit\Event\TestRunner\DeprecationTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestRunnerTriggeredDeprecationSubscriber extends Subscriber implements DeprecationTriggeredSubscriber +{ + public function notify(DeprecationTriggered $event): void + { + $this->collector()->testRunnerTriggeredDeprecation($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestRunnerTriggeredNoticeSubscriber.php b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestRunnerTriggeredNoticeSubscriber.php new file mode 100644 index 000000000..8fd9bc4b8 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestRunnerTriggeredNoticeSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\TestRunner\NoticeTriggered; +use PHPUnit\Event\TestRunner\NoticeTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestRunnerTriggeredNoticeSubscriber extends Subscriber implements NoticeTriggeredSubscriber +{ + public function notify(NoticeTriggered $event): void + { + $this->collector()->testRunnerTriggeredNotice($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestRunnerTriggeredWarningSubscriber.php b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestRunnerTriggeredWarningSubscriber.php new file mode 100644 index 000000000..cc01d5db8 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestRunnerTriggeredWarningSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\TestRunner\WarningTriggered; +use PHPUnit\Event\TestRunner\WarningTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestRunnerTriggeredWarningSubscriber extends Subscriber implements WarningTriggeredSubscriber +{ + public function notify(WarningTriggered $event): void + { + $this->collector()->testRunnerTriggeredWarning($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestSkippedSubscriber.php b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestSkippedSubscriber.php new file mode 100644 index 000000000..152db85bf --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestSkippedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\Test\Skipped; +use PHPUnit\Event\Test\SkippedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestSkippedSubscriber extends Subscriber implements SkippedSubscriber +{ + public function notify(Skipped $event): void + { + $this->collector()->testSkipped($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestSuiteFinishedSubscriber.php b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestSuiteFinishedSubscriber.php new file mode 100644 index 000000000..e5f2acac5 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestSuiteFinishedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\TestSuite\Finished; +use PHPUnit\Event\TestSuite\FinishedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestSuiteFinishedSubscriber extends Subscriber implements FinishedSubscriber +{ + public function notify(Finished $event): void + { + $this->collector()->testSuiteFinished($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestSuiteSkippedSubscriber.php b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestSuiteSkippedSubscriber.php new file mode 100644 index 000000000..0c7cd7abb --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestSuiteSkippedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\TestSuite\Skipped; +use PHPUnit\Event\TestSuite\SkippedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestSuiteSkippedSubscriber extends Subscriber implements SkippedSubscriber +{ + public function notify(Skipped $event): void + { + $this->collector()->testSuiteSkipped($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestSuiteStartedSubscriber.php b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestSuiteStartedSubscriber.php new file mode 100644 index 000000000..d3cb3bffd --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestSuiteStartedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\TestSuite\Started; +use PHPUnit\Event\TestSuite\StartedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestSuiteStartedSubscriber extends Subscriber implements StartedSubscriber +{ + public function notify(Started $event): void + { + $this->collector()->testSuiteStarted($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestTriggeredDeprecationSubscriber.php b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestTriggeredDeprecationSubscriber.php new file mode 100644 index 000000000..81e93eb34 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestTriggeredDeprecationSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\Test\DeprecationTriggered; +use PHPUnit\Event\Test\DeprecationTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredDeprecationSubscriber extends Subscriber implements DeprecationTriggeredSubscriber +{ + public function notify(DeprecationTriggered $event): void + { + $this->collector()->testTriggeredDeprecation($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestTriggeredErrorSubscriber.php b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestTriggeredErrorSubscriber.php new file mode 100644 index 000000000..0aef461db --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestTriggeredErrorSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\Test\ErrorTriggered; +use PHPUnit\Event\Test\ErrorTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredErrorSubscriber extends Subscriber implements ErrorTriggeredSubscriber +{ + public function notify(ErrorTriggered $event): void + { + $this->collector()->testTriggeredError($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestTriggeredNoticeSubscriber.php b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestTriggeredNoticeSubscriber.php new file mode 100644 index 000000000..67b73c06a --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestTriggeredNoticeSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\Test\NoticeTriggered; +use PHPUnit\Event\Test\NoticeTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredNoticeSubscriber extends Subscriber implements NoticeTriggeredSubscriber +{ + public function notify(NoticeTriggered $event): void + { + $this->collector()->testTriggeredNotice($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestTriggeredPhpDeprecationSubscriber.php b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestTriggeredPhpDeprecationSubscriber.php new file mode 100644 index 000000000..5cd17e3f9 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestTriggeredPhpDeprecationSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\Test\PhpDeprecationTriggered; +use PHPUnit\Event\Test\PhpDeprecationTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredPhpDeprecationSubscriber extends Subscriber implements PhpDeprecationTriggeredSubscriber +{ + public function notify(PhpDeprecationTriggered $event): void + { + $this->collector()->testTriggeredPhpDeprecation($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestTriggeredPhpNoticeSubscriber.php b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestTriggeredPhpNoticeSubscriber.php new file mode 100644 index 000000000..9af0d3206 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestTriggeredPhpNoticeSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\Test\PhpNoticeTriggered; +use PHPUnit\Event\Test\PhpNoticeTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredPhpNoticeSubscriber extends Subscriber implements PhpNoticeTriggeredSubscriber +{ + public function notify(PhpNoticeTriggered $event): void + { + $this->collector()->testTriggeredPhpNotice($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestTriggeredPhpWarningSubscriber.php b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestTriggeredPhpWarningSubscriber.php new file mode 100644 index 000000000..18eaf4f17 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestTriggeredPhpWarningSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\Test\PhpWarningTriggered; +use PHPUnit\Event\Test\PhpWarningTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredPhpWarningSubscriber extends Subscriber implements PhpWarningTriggeredSubscriber +{ + public function notify(PhpWarningTriggered $event): void + { + $this->collector()->testTriggeredPhpWarning($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestTriggeredPhpunitDeprecationSubscriber.php b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestTriggeredPhpunitDeprecationSubscriber.php new file mode 100644 index 000000000..3475f11aa --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestTriggeredPhpunitDeprecationSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\Test\PhpunitDeprecationTriggered; +use PHPUnit\Event\Test\PhpunitDeprecationTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredPhpunitDeprecationSubscriber extends Subscriber implements PhpunitDeprecationTriggeredSubscriber +{ + public function notify(PhpunitDeprecationTriggered $event): void + { + $this->collector()->testTriggeredPhpunitDeprecation($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestTriggeredPhpunitErrorSubscriber.php b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestTriggeredPhpunitErrorSubscriber.php new file mode 100644 index 000000000..0ceba9caa --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestTriggeredPhpunitErrorSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\Test\PhpunitErrorTriggered; +use PHPUnit\Event\Test\PhpunitErrorTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredPhpunitErrorSubscriber extends Subscriber implements PhpunitErrorTriggeredSubscriber +{ + public function notify(PhpunitErrorTriggered $event): void + { + $this->collector()->testTriggeredPhpunitError($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestTriggeredPhpunitNoticeSubscriber.php b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestTriggeredPhpunitNoticeSubscriber.php new file mode 100644 index 000000000..f7ebfd146 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestTriggeredPhpunitNoticeSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\Test\PhpunitNoticeTriggered; +use PHPUnit\Event\Test\PhpunitNoticeTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredPhpunitNoticeSubscriber extends Subscriber implements PhpunitNoticeTriggeredSubscriber +{ + public function notify(PhpunitNoticeTriggered $event): void + { + $this->collector()->testTriggeredPhpunitNotice($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestTriggeredPhpunitWarningSubscriber.php b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestTriggeredPhpunitWarningSubscriber.php new file mode 100644 index 000000000..376c4b60e --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestTriggeredPhpunitWarningSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\Test\PhpunitWarningTriggered; +use PHPUnit\Event\Test\PhpunitWarningTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredPhpunitWarningSubscriber extends Subscriber implements PhpunitWarningTriggeredSubscriber +{ + public function notify(PhpunitWarningTriggered $event): void + { + $this->collector()->testTriggeredPhpunitWarning($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestTriggeredWarningSubscriber.php b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestTriggeredWarningSubscriber.php new file mode 100644 index 000000000..d5fe3ed5c --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/TestResult/Subscriber/TestTriggeredWarningSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use PHPUnit\Event\Test\WarningTriggered; +use PHPUnit\Event\Test\WarningTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredWarningSubscriber extends Subscriber implements WarningTriggeredSubscriber +{ + public function notify(WarningTriggered $event): void + { + $this->collector()->testTriggeredWarning($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/TestResult/TestResult.php b/app/vendor/phpunit/phpunit/src/Runner/TestResult/TestResult.php new file mode 100644 index 000000000..f491718f7 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Runner/TestResult/TestResult.php @@ -0,0 +1,647 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TestRunner\TestResult; + +use function count; +use PHPUnit\Event\Test\AfterLastTestMethodErrored; +use PHPUnit\Event\Test\AfterLastTestMethodFailed; +use PHPUnit\Event\Test\BeforeFirstTestMethodErrored; +use PHPUnit\Event\Test\BeforeFirstTestMethodFailed; +use PHPUnit\Event\Test\ConsideredRisky; +use PHPUnit\Event\Test\Errored; +use PHPUnit\Event\Test\Failed; +use PHPUnit\Event\Test\MarkedIncomplete; +use PHPUnit\Event\Test\PhpunitDeprecationTriggered; +use PHPUnit\Event\Test\PhpunitErrorTriggered; +use PHPUnit\Event\Test\PhpunitNoticeTriggered; +use PHPUnit\Event\Test\PhpunitWarningTriggered; +use PHPUnit\Event\Test\Skipped as TestSkipped; +use PHPUnit\Event\TestRunner\DeprecationTriggered as TestRunnerDeprecationTriggered; +use PHPUnit\Event\TestRunner\NoticeTriggered as TestRunnerNoticeTriggered; +use PHPUnit\Event\TestRunner\WarningTriggered as TestRunnerWarningTriggered; +use PHPUnit\Event\TestSuite\Skipped as TestSuiteSkipped; +use PHPUnit\TestRunner\TestResult\Issues\Issue; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestResult +{ + private int $numberOfTests; + private int $numberOfTestsRun; + private int $numberOfAssertions; + + /** + * @var list + */ + private array $testErroredEvents; + + /** + * @var list + */ + private array $testFailedEvents; + + /** + * @var list + */ + private array $testMarkedIncompleteEvents; + + /** + * @var list + */ + private array $testSuiteSkippedEvents; + + /** + * @var list + */ + private array $testSkippedEvents; + + /** + * @var array> + */ + private array $testConsideredRiskyEvents; + + /** + * @var array> + */ + private array $testTriggeredPhpunitDeprecationEvents; + + /** + * @var array> + */ + private array $testTriggeredPhpunitErrorEvents; + + /** + * @var array> + */ + private array $testTriggeredPhpunitNoticeEvents; + + /** + * @var array> + */ + private array $testTriggeredPhpunitWarningEvents; + + /** + * @var list + */ + private array $testRunnerTriggeredDeprecationEvents; + + /** + * @var list + */ + private array $testRunnerTriggeredNoticeEvents; + + /** + * @var list + */ + private array $testRunnerTriggeredWarningEvents; + + /** + * @var list + */ + private array $errors; + + /** + * @var list + */ + private array $deprecations; + + /** + * @var list + */ + private array $notices; + + /** + * @var list + */ + private array $warnings; + + /** + * @var list + */ + private array $phpDeprecations; + + /** + * @var list + */ + private array $phpNotices; + + /** + * @var list + */ + private array $phpWarnings; + + /** + * @var non-negative-int + */ + private int $numberOfIssuesIgnoredByBaseline; + + /** + * @param list $testErroredEvents + * @param list $testFailedEvents + * @param array> $testConsideredRiskyEvents + * @param list $testSuiteSkippedEvents + * @param list $testSkippedEvents + * @param list $testMarkedIncompleteEvents + * @param array> $testTriggeredPhpunitDeprecationEvents + * @param array> $testTriggeredPhpunitErrorEvents + * @param array> $testTriggeredPhpunitNoticeEvents + * @param array> $testTriggeredPhpunitWarningEvents + * @param list $testRunnerTriggeredDeprecationEvents + * @param list $testRunnerTriggeredNoticeEvents + * @param list $testRunnerTriggeredWarningEvents + * @param list $errors + * @param list $deprecations + * @param list $notices + * @param list $warnings + * @param list $phpDeprecations + * @param list $phpNotices + * @param list $phpWarnings + * @param non-negative-int $numberOfIssuesIgnoredByBaseline + */ + public function __construct(int $numberOfTests, int $numberOfTestsRun, int $numberOfAssertions, array $testErroredEvents, array $testFailedEvents, array $testConsideredRiskyEvents, array $testSuiteSkippedEvents, array $testSkippedEvents, array $testMarkedIncompleteEvents, array $testTriggeredPhpunitDeprecationEvents, array $testTriggeredPhpunitErrorEvents, array $testTriggeredPhpunitNoticeEvents, array $testTriggeredPhpunitWarningEvents, array $testRunnerTriggeredDeprecationEvents, array $testRunnerTriggeredNoticeEvents, array $testRunnerTriggeredWarningEvents, array $errors, array $deprecations, array $notices, array $warnings, array $phpDeprecations, array $phpNotices, array $phpWarnings, int $numberOfIssuesIgnoredByBaseline) + { + $this->numberOfTests = $numberOfTests; + $this->numberOfTestsRun = $numberOfTestsRun; + $this->numberOfAssertions = $numberOfAssertions; + $this->testErroredEvents = $testErroredEvents; + $this->testFailedEvents = $testFailedEvents; + $this->testConsideredRiskyEvents = $testConsideredRiskyEvents; + $this->testSuiteSkippedEvents = $testSuiteSkippedEvents; + $this->testSkippedEvents = $testSkippedEvents; + $this->testMarkedIncompleteEvents = $testMarkedIncompleteEvents; + $this->testTriggeredPhpunitDeprecationEvents = $testTriggeredPhpunitDeprecationEvents; + $this->testTriggeredPhpunitErrorEvents = $testTriggeredPhpunitErrorEvents; + $this->testTriggeredPhpunitNoticeEvents = $testTriggeredPhpunitNoticeEvents; + $this->testTriggeredPhpunitWarningEvents = $testTriggeredPhpunitWarningEvents; + $this->testRunnerTriggeredDeprecationEvents = $testRunnerTriggeredDeprecationEvents; + $this->testRunnerTriggeredNoticeEvents = $testRunnerTriggeredNoticeEvents; + $this->testRunnerTriggeredWarningEvents = $testRunnerTriggeredWarningEvents; + $this->errors = $errors; + $this->deprecations = $deprecations; + $this->notices = $notices; + $this->warnings = $warnings; + $this->phpDeprecations = $phpDeprecations; + $this->phpNotices = $phpNotices; + $this->phpWarnings = $phpWarnings; + $this->numberOfIssuesIgnoredByBaseline = $numberOfIssuesIgnoredByBaseline; + } + + public function numberOfTestsRun(): int + { + return $this->numberOfTestsRun; + } + + public function numberOfAssertions(): int + { + return $this->numberOfAssertions; + } + + /** + * @return list + */ + public function testErroredEvents(): array + { + return $this->testErroredEvents; + } + + public function numberOfTestErroredEvents(): int + { + return count($this->testErroredEvents); + } + + public function hasTestErroredEvents(): bool + { + return $this->numberOfTestErroredEvents() > 0; + } + + /** + * @return list + */ + public function testFailedEvents(): array + { + return $this->testFailedEvents; + } + + public function numberOfTestFailedEvents(): int + { + return count($this->testFailedEvents); + } + + public function hasTestFailedEvents(): bool + { + return $this->numberOfTestFailedEvents() > 0; + } + + /** + * @return array> + */ + public function testConsideredRiskyEvents(): array + { + return $this->testConsideredRiskyEvents; + } + + public function numberOfTestsWithTestConsideredRiskyEvents(): int + { + return count($this->testConsideredRiskyEvents); + } + + public function hasTestConsideredRiskyEvents(): bool + { + return $this->numberOfTestsWithTestConsideredRiskyEvents() > 0; + } + + /** + * @return list + */ + public function testSuiteSkippedEvents(): array + { + return $this->testSuiteSkippedEvents; + } + + public function numberOfTestSuiteSkippedEvents(): int + { + return count($this->testSuiteSkippedEvents); + } + + public function hasTestSuiteSkippedEvents(): bool + { + return $this->numberOfTestSuiteSkippedEvents() > 0; + } + + /** + * @return list + */ + public function testSkippedEvents(): array + { + return $this->testSkippedEvents; + } + + public function numberOfTestSkippedEvents(): int + { + return count($this->testSkippedEvents); + } + + public function hasTestSkippedEvents(): bool + { + return $this->numberOfTestSkippedEvents() > 0; + } + + /** + * @return list + */ + public function testMarkedIncompleteEvents(): array + { + return $this->testMarkedIncompleteEvents; + } + + public function numberOfTestMarkedIncompleteEvents(): int + { + return count($this->testMarkedIncompleteEvents); + } + + public function hasTestMarkedIncompleteEvents(): bool + { + return $this->numberOfTestMarkedIncompleteEvents() > 0; + } + + /** + * @return array> + */ + public function testTriggeredPhpunitDeprecationEvents(): array + { + return $this->testTriggeredPhpunitDeprecationEvents; + } + + public function numberOfTestsWithTestTriggeredPhpunitDeprecationEvents(): int + { + return count($this->testTriggeredPhpunitDeprecationEvents); + } + + public function hasTestTriggeredPhpunitDeprecationEvents(): bool + { + return $this->numberOfTestsWithTestTriggeredPhpunitDeprecationEvents() > 0; + } + + /** + * @return array> + */ + public function testTriggeredPhpunitErrorEvents(): array + { + return $this->testTriggeredPhpunitErrorEvents; + } + + public function numberOfTestsWithTestTriggeredPhpunitErrorEvents(): int + { + return count($this->testTriggeredPhpunitErrorEvents); + } + + public function hasTestTriggeredPhpunitErrorEvents(): bool + { + return $this->numberOfTestsWithTestTriggeredPhpunitErrorEvents() > 0; + } + + /** + * @return array> + */ + public function testTriggeredPhpunitNoticeEvents(): array + { + return $this->testTriggeredPhpunitNoticeEvents; + } + + public function numberOfTestsWithTestTriggeredPhpunitNoticeEvents(): int + { + return count($this->testTriggeredPhpunitNoticeEvents); + } + + public function hasTestTriggeredPhpunitNoticeEvents(): bool + { + return $this->numberOfTestsWithTestTriggeredPhpunitNoticeEvents() > 0; + } + + /** + * @return array> + */ + public function testTriggeredPhpunitWarningEvents(): array + { + return $this->testTriggeredPhpunitWarningEvents; + } + + public function numberOfTestsWithTestTriggeredPhpunitWarningEvents(): int + { + return count($this->testTriggeredPhpunitWarningEvents); + } + + public function hasTestTriggeredPhpunitWarningEvents(): bool + { + return $this->numberOfTestsWithTestTriggeredPhpunitWarningEvents() > 0; + } + + /** + * @return list + */ + public function testRunnerTriggeredDeprecationEvents(): array + { + return $this->testRunnerTriggeredDeprecationEvents; + } + + public function numberOfTestRunnerTriggeredDeprecationEvents(): int + { + return count($this->testRunnerTriggeredDeprecationEvents); + } + + public function hasTestRunnerTriggeredDeprecationEvents(): bool + { + return $this->numberOfTestRunnerTriggeredDeprecationEvents() > 0; + } + + /** + * @return list + */ + public function testRunnerTriggeredNoticeEvents(): array + { + return $this->testRunnerTriggeredNoticeEvents; + } + + public function numberOfTestRunnerTriggeredNoticeEvents(): int + { + return count($this->testRunnerTriggeredNoticeEvents); + } + + public function hasTestRunnerTriggeredNoticeEvents(): bool + { + return $this->numberOfTestRunnerTriggeredNoticeEvents() > 0; + } + + /** + * @return list + */ + public function testRunnerTriggeredWarningEvents(): array + { + return $this->testRunnerTriggeredWarningEvents; + } + + public function numberOfTestRunnerTriggeredWarningEvents(): int + { + return count($this->testRunnerTriggeredWarningEvents); + } + + public function hasTestRunnerTriggeredWarningEvents(): bool + { + return $this->numberOfTestRunnerTriggeredWarningEvents() > 0; + } + + public function wasSuccessful(): bool + { + return !$this->hasTestErroredEvents() && + !$this->hasTestFailedEvents() && + !$this->hasTestTriggeredPhpunitErrorEvents(); + } + + public function hasIssues(): bool + { + return $this->hasTestsWithIssues() || + $this->hasTestRunnerTriggeredWarningEvents(); + } + + public function hasTestsWithIssues(): bool + { + return $this->hasRiskyTests() || + $this->hasIncompleteTests() || + $this->hasDeprecations() || + $this->errors !== [] || + $this->hasNotices() || + $this->hasWarnings() || + $this->hasPhpunitNotices() || + $this->hasPhpunitWarnings(); + } + + /** + * @return list + */ + public function errors(): array + { + return $this->errors; + } + + /** + * @return list + */ + public function deprecations(): array + { + return $this->deprecations; + } + + /** + * @return list + */ + public function notices(): array + { + return $this->notices; + } + + /** + * @return list + */ + public function warnings(): array + { + return $this->warnings; + } + + /** + * @return list + */ + public function phpDeprecations(): array + { + return $this->phpDeprecations; + } + + /** + * @return list + */ + public function phpNotices(): array + { + return $this->phpNotices; + } + + /** + * @return list + */ + public function phpWarnings(): array + { + return $this->phpWarnings; + } + + public function hasTests(): bool + { + return $this->numberOfTests > 0; + } + + public function hasErrors(): bool + { + return $this->numberOfErrors() > 0; + } + + public function numberOfErrors(): int + { + return $this->numberOfTestErroredEvents() + + count($this->errors) + + $this->numberOfTestsWithTestTriggeredPhpunitErrorEvents(); + } + + public function hasDeprecations(): bool + { + return $this->numberOfDeprecations() > 0; + } + + public function hasPhpOrUserDeprecations(): bool + { + return $this->numberOfPhpOrUserDeprecations() > 0; + } + + public function numberOfPhpOrUserDeprecations(): int + { + return count($this->deprecations) + + count($this->phpDeprecations); + } + + public function hasPhpunitDeprecations(): bool + { + return $this->numberOfPhpunitDeprecations() > 0; + } + + public function numberOfPhpunitDeprecations(): int + { + return count($this->testTriggeredPhpunitDeprecationEvents) + + count($this->testRunnerTriggeredDeprecationEvents); + } + + public function hasPhpunitWarnings(): bool + { + return $this->numberOfPhpunitWarnings() > 0; + } + + public function numberOfPhpunitWarnings(): int + { + return count($this->testTriggeredPhpunitWarningEvents) + + count($this->testRunnerTriggeredWarningEvents); + } + + public function numberOfDeprecations(): int + { + return count($this->deprecations) + + count($this->phpDeprecations) + + count($this->testTriggeredPhpunitDeprecationEvents) + + count($this->testRunnerTriggeredDeprecationEvents); + } + + public function hasNotices(): bool + { + return $this->numberOfNotices() > 0; + } + + public function numberOfNotices(): int + { + return count($this->notices) + + count($this->phpNotices); + } + + public function hasWarnings(): bool + { + return $this->numberOfWarnings() > 0; + } + + public function numberOfWarnings(): int + { + return count($this->warnings) + + count($this->phpWarnings); + } + + public function hasIncompleteTests(): bool + { + return $this->testMarkedIncompleteEvents !== []; + } + + public function hasRiskyTests(): bool + { + return $this->testConsideredRiskyEvents !== []; + } + + public function hasSkippedTests(): bool + { + return $this->testSkippedEvents !== []; + } + + public function hasIssuesIgnoredByBaseline(): bool + { + return $this->numberOfIssuesIgnoredByBaseline > 0; + } + + /** + * @return non-negative-int + */ + public function numberOfIssuesIgnoredByBaseline(): int + { + return $this->numberOfIssuesIgnoredByBaseline; + } + + public function hasPhpunitNotices(): bool + { + return $this->numberOfPhpunitNotices() > 0; + } + + public function numberOfPhpunitNotices(): int + { + return $this->numberOfTestsWithTestTriggeredPhpunitNoticeEvents() + + $this->numberOfTestRunnerTriggeredNoticeEvents(); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Runner/TestResultCache.php b/app/vendor/phpunit/phpunit/src/Runner/TestResultCache.php deleted file mode 100644 index 69e628289..000000000 --- a/app/vendor/phpunit/phpunit/src/Runner/TestResultCache.php +++ /dev/null @@ -1,28 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Runner; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -interface TestResultCache -{ - public function setState(string $testName, int $state): void; - - public function getState(string $testName): int; - - public function setTime(string $testName, float $time): void; - - public function getTime(string $testName): float; - - public function load(): void; - - public function persist(): void; -} diff --git a/app/vendor/phpunit/phpunit/src/Runner/TestSuiteLoader.php b/app/vendor/phpunit/phpunit/src/Runner/TestSuiteLoader.php index c9d8e01b1..3d01964de 100644 --- a/app/vendor/phpunit/phpunit/src/Runner/TestSuiteLoader.php +++ b/app/vendor/phpunit/phpunit/src/Runner/TestSuiteLoader.php @@ -9,16 +9,134 @@ */ namespace PHPUnit\Runner; +use function array_diff; +use function basename; +use function get_declared_classes; +use function realpath; +use function str_ends_with; +use function strpos; +use function strtolower; +use function substr; +use PHPUnit\Framework\TestCase; use ReflectionClass; /** - * @deprecated see https://github.com/sebastianbergmann/phpunit/issues/4039 - * * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit */ -interface TestSuiteLoader +final class TestSuiteLoader { - public function load(string $suiteClassFile): ReflectionClass; + /** + * @var list + */ + private static array $declaredClasses = []; + + /** + * @var array> + */ + private static array $fileToClassesMap = []; + + /** + * @throws Exception + * + * @return ReflectionClass + */ + public function load(string $suiteClassFile): ReflectionClass + { + $suiteClassFile = realpath($suiteClassFile); + $suiteClassName = $this->classNameFromFileName($suiteClassFile); + $loadedClasses = $this->loadSuiteClassFile($suiteClassFile); + + foreach ($loadedClasses as $className) { + /** @noinspection PhpUnhandledExceptionInspection */ + $class = new ReflectionClass($className); + + if ($class->isAnonymous()) { + continue; + } + + if ($class->getFileName() !== $suiteClassFile) { + continue; + } + + if (!$class->isSubclassOf(TestCase::class)) { + continue; + } + + if (!str_ends_with(strtolower($class->getShortName()), strtolower($suiteClassName))) { + continue; + } + + if (!$class->isAbstract()) { + return $class; + } + + $e = new ClassIsAbstractException($class->getName(), $suiteClassFile); + } + + if (isset($e)) { + throw $e; + } + + foreach ($loadedClasses as $className) { + if (str_ends_with(strtolower($className), strtolower($suiteClassName))) { + throw new ClassDoesNotExtendTestCaseException($className, $suiteClassFile); + } + } + + throw new ClassCannotBeFoundException($suiteClassName, $suiteClassFile); + } + + private function classNameFromFileName(string $suiteClassFile): string + { + $className = basename($suiteClassFile, '.php'); + $dotPos = strpos($className, '.'); + + if ($dotPos !== false) { + $className = substr($className, 0, $dotPos); + } + + return $className; + } + + /** + * @return array + */ + private function loadSuiteClassFile(string $suiteClassFile): array + { + if (isset(self::$fileToClassesMap[$suiteClassFile])) { + return self::$fileToClassesMap[$suiteClassFile]; + } + + if (self::$declaredClasses === []) { + self::$declaredClasses = get_declared_classes(); + } + + require_once $suiteClassFile; + + $loadedClasses = array_diff( + get_declared_classes(), + self::$declaredClasses, + ); + + foreach ($loadedClasses as $loadedClass) { + /** @noinspection PhpUnhandledExceptionInspection */ + $class = new ReflectionClass($loadedClass); + + if (!isset(self::$fileToClassesMap[$class->getFileName()])) { + self::$fileToClassesMap[$class->getFileName()] = []; + } + + self::$fileToClassesMap[$class->getFileName()][] = $class->getName(); + } + + self::$declaredClasses = get_declared_classes(); + + if ($loadedClasses === []) { + return self::$declaredClasses; + } - public function reload(ReflectionClass $aClass): ReflectionClass; + return $loadedClasses; + } } diff --git a/app/vendor/phpunit/phpunit/src/Runner/TestSuiteSorter.php b/app/vendor/phpunit/phpunit/src/Runner/TestSuiteSorter.php index a48a7db61..b20ae2501 100644 --- a/app/vendor/phpunit/phpunit/src/Runner/TestSuiteSorter.php +++ b/app/vendor/phpunit/phpunit/src/Runner/TestSuiteSorter.php @@ -23,94 +23,57 @@ use PHPUnit\Framework\Test; use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestSuite; -use PHPUnit\Util\Test as TestUtil; -use SebastianBergmann\RecursionContext\InvalidArgumentException; +use PHPUnit\Runner\ResultCache\NullResultCache; +use PHPUnit\Runner\ResultCache\ResultCache; +use PHPUnit\Runner\ResultCache\ResultCacheId; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class TestSuiteSorter { - /** - * @var int - */ - public const ORDER_DEFAULT = 0; - - /** - * @var int - */ - public const ORDER_RANDOMIZED = 1; - - /** - * @var int - */ - public const ORDER_REVERSED = 2; - - /** - * @var int - */ - public const ORDER_DEFECTS_FIRST = 3; + public const int ORDER_DEFAULT = 0; + public const int ORDER_RANDOMIZED = 1; + public const int ORDER_REVERSED = 2; + public const int ORDER_DEFECTS_FIRST = 3; + public const int ORDER_DURATION = 4; + public const int ORDER_SIZE = 5; /** - * @var int + * @var non-empty-array */ - public const ORDER_DURATION = 4; - - /** - * Order tests by @size annotation 'small', 'medium', 'large'. - * - * @var int - */ - public const ORDER_SIZE = 5; - - /** - * List of sorting weights for all test result codes. A higher number gives higher priority. - */ - private const DEFECT_SORT_WEIGHT = [ - BaseTestRunner::STATUS_ERROR => 6, - BaseTestRunner::STATUS_FAILURE => 5, - BaseTestRunner::STATUS_WARNING => 4, - BaseTestRunner::STATUS_INCOMPLETE => 3, - BaseTestRunner::STATUS_RISKY => 2, - BaseTestRunner::STATUS_SKIPPED => 1, - BaseTestRunner::STATUS_UNKNOWN => 0, - ]; - - private const SIZE_SORT_WEIGHT = [ - TestUtil::SMALL => 1, - TestUtil::MEDIUM => 2, - TestUtil::LARGE => 3, - TestUtil::UNKNOWN => 4, + private const array SIZE_SORT_WEIGHT = [ + 'small' => 1, + 'medium' => 2, + 'large' => 3, + 'unknown' => 4, ]; /** * @var array Associative array of (string => DEFECT_SORT_WEIGHT) elements */ - private $defectSortOrder = []; - - /** - * @var TestResultCache - */ - private $cache; + private array $defectSortOrder = []; + private readonly ResultCache $cache; /** * @var array A list of normalized names of tests before reordering */ - private $originalExecutionOrder = []; + private array $originalExecutionOrder = []; /** * @var array A list of normalized names of tests affected by reordering */ - private $executionOrder = []; + private array $executionOrder = []; - public function __construct(?TestResultCache $cache = null) + public function __construct(?ResultCache $cache = null) { - $this->cache = $cache ?? new NullTestResultCache; + $this->cache = $cache ?? new NullResultCache; } /** * @throws Exception - * @throws InvalidArgumentException */ public function reorderTestsInSuite(Test $suite, int $order, bool $resolveDependencies, int $orderDefects, bool $isRootTestSuite = true): void { @@ -123,9 +86,7 @@ public function reorderTestsInSuite(Test $suite, int $order, bool $resolveDepend ]; if (!in_array($order, $allowedOrders, true)) { - throw new Exception( - '$order must be one of TestSuiteSorter::ORDER_[DEFAULT|REVERSED|RANDOMIZED|DURATION|SIZE]', - ); + throw new InvalidOrderException; } $allowedOrderDefects = [ @@ -134,9 +95,7 @@ public function reorderTestsInSuite(Test $suite, int $order, bool $resolveDepend ]; if (!in_array($orderDefects, $allowedOrderDefects, true)) { - throw new Exception( - '$orderDefects must be one of TestSuiteSorter::ORDER_DEFAULT, TestSuiteSorter::ORDER_DEFECTS_FIRST', - ); + throw new InvalidOrderException; } if ($isRootTestSuite) { @@ -160,11 +119,17 @@ public function reorderTestsInSuite(Test $suite, int $order, bool $resolveDepend } } + /** + * @return array + */ public function getOriginalExecutionOrder(): array { return $this->originalExecutionOrder; } + /** + * @return array + */ public function getExecutionOrder(): array { return $this->executionOrder; @@ -172,7 +137,7 @@ public function getExecutionOrder(): array private function sort(TestSuite $suite, int $order, bool $resolveDependencies, int $orderDefects): void { - if (empty($suite->tests())) { + if ($suite->tests() === []) { return; } @@ -180,27 +145,25 @@ private function sort(TestSuite $suite, int $order, bool $resolveDependencies, i $suite->setTests($this->reverse($suite->tests())); } elseif ($order === self::ORDER_RANDOMIZED) { $suite->setTests($this->randomize($suite->tests())); - } elseif ($order === self::ORDER_DURATION && $this->cache !== null) { + } elseif ($order === self::ORDER_DURATION) { $suite->setTests($this->sortByDuration($suite->tests())); } elseif ($order === self::ORDER_SIZE) { $suite->setTests($this->sortBySize($suite->tests())); } - if ($orderDefects === self::ORDER_DEFECTS_FIRST && $this->cache !== null) { + if ($orderDefects === self::ORDER_DEFECTS_FIRST) { $suite->setTests($this->sortDefectsFirst($suite->tests())); } if ($resolveDependencies && !($suite instanceof DataProviderTestSuite)) { - /** @var TestCase[] $tests */ $tests = $suite->tests(); + /** @noinspection PhpParamsInspection */ + /** @phpstan-ignore argument.type */ $suite->setTests($this->resolveDependencies($tests)); } } - /** - * @throws InvalidArgumentException - */ private function addSuiteToDefectSortOrder(TestSuite $suite): void { $max = 0; @@ -210,20 +173,32 @@ private function addSuiteToDefectSortOrder(TestSuite $suite): void continue; } - if (!isset($this->defectSortOrder[$test->sortId()])) { - $this->defectSortOrder[$test->sortId()] = self::DEFECT_SORT_WEIGHT[$this->cache->getState($test->sortId())]; - $max = max($max, $this->defectSortOrder[$test->sortId()]); + $sortId = $test->sortId(); + + if (!isset($this->defectSortOrder[$sortId])) { + $this->defectSortOrder[$sortId] = $this->cache->status(ResultCacheId::fromReorderable($test))->asInt(); + $max = max($max, $this->defectSortOrder[$sortId]); } } $this->defectSortOrder[$suite->sortId()] = $max; } + /** + * @param list $tests + * + * @return list + */ private function reverse(array $tests): array { return array_reverse($tests); } + /** + * @param list $tests + * + * @return list + */ private function randomize(array $tests): array { shuffle($tests); @@ -231,49 +206,46 @@ private function randomize(array $tests): array return $tests; } + /** + * @param list $tests + * + * @return list + */ private function sortDefectsFirst(array $tests): array { usort( $tests, - /** - * @throws InvalidArgumentException - */ - function ($left, $right) - { - return $this->cmpDefectPriorityAndTime($left, $right); - }, + fn (Test $left, Test $right) => $this->cmpDefectPriorityAndTime($left, $right), ); return $tests; } + /** + * @param list $tests + * + * @return list + */ private function sortByDuration(array $tests): array { usort( $tests, - /** - * @throws InvalidArgumentException - */ - function ($left, $right) - { - return $this->cmpDuration($left, $right); - }, + fn (Test $left, Test $right) => $this->cmpDuration($left, $right), ); return $tests; } + /** + * @param list $tests + * + * @return list + */ private function sortBySize(array $tests): array { usort( $tests, - /** - * @throws InvalidArgumentException - */ - function ($left, $right) - { - return $this->cmpSize($left, $right); - }, + fn (Test $left, Test $right) => $this->cmpSize($left, $right), ); return $tests; @@ -285,8 +257,6 @@ function ($left, $right) * 1. sort tests by defect weight defined in self::DEFECT_SORT_WEIGHT * 2. when tests are equally defective, sort the fastest to the front * 3. do not reorder successful tests - * - * @throws InvalidArgumentException */ private function cmpDefectPriorityAndTime(Test $a, Test $b): int { @@ -297,12 +267,12 @@ private function cmpDefectPriorityAndTime(Test $a, Test $b): int $priorityA = $this->defectSortOrder[$a->sortId()] ?? 0; $priorityB = $this->defectSortOrder[$b->sortId()] ?? 0; - if ($priorityB <=> $priorityA) { + if (($priorityB <=> $priorityA) > 0) { // Sort defect weight descending return $priorityB <=> $priorityA; } - if ($priorityA || $priorityB) { + if ($priorityA > 0 || $priorityB > 0) { return $this->cmpDuration($a, $b); } @@ -312,8 +282,6 @@ private function cmpDefectPriorityAndTime(Test $a, Test $b): int /** * Compares test duration for sorting tests by duration ascending. - * - * @throws InvalidArgumentException */ private function cmpDuration(Test $a, Test $b): int { @@ -321,7 +289,7 @@ private function cmpDuration(Test $a, Test $b): int return 0; } - return $this->cache->getTime($a->sortId()) <=> $this->cache->getTime($b->sortId()); + return $this->cache->time(ResultCacheId::fromReorderable($a)) <=> $this->cache->time(ResultCacheId::fromReorderable($b)); } /** @@ -330,11 +298,11 @@ private function cmpDuration(Test $a, Test $b): int private function cmpSize(Test $a, Test $b): int { $sizeA = ($a instanceof TestCase || $a instanceof DataProviderTestSuite) - ? $a->getSize() - : TestUtil::UNKNOWN; + ? $a->size()->asString() + : 'unknown'; $sizeB = ($b instanceof TestCase || $b instanceof DataProviderTestSuite) - ? $b->getSize() - : TestUtil::UNKNOWN; + ? $b->size()->asString() + : 'unknown'; return self::SIZE_SORT_WEIGHT[$sizeA] <=> self::SIZE_SORT_WEIGHT[$sizeB]; } @@ -350,9 +318,9 @@ private function cmpSize(Test $a, Test $b): int * 3. If the test has dependencies but none left to do: mark done, start again from the top * 4. When we reach the end add any leftover tests to the end. These will be marked 'skipped' during execution. * - * @param array $tests + * @param array $tests * - * @return array + * @return array */ private function resolveDependencies(array $tests): array { @@ -368,13 +336,13 @@ private function resolveDependencies(array $tests): array } else { $i++; } - } while (!empty($tests) && ($i < count($tests))); + } while ($tests !== [] && ($i < count($tests))); return array_merge($newTestOrder, $tests); } /** - * @throws InvalidArgumentException + * @return array */ private function calculateTestExecutionOrder(Test $suite): array { diff --git a/app/vendor/phpunit/phpunit/src/Runner/Version.php b/app/vendor/phpunit/phpunit/src/Runner/Version.php index 9b9959295..da09dd09f 100644 --- a/app/vendor/phpunit/phpunit/src/Runner/Version.php +++ b/app/vendor/phpunit/phpunit/src/Runner/Version.php @@ -14,7 +14,7 @@ use function dirname; use function explode; use function implode; -use function strpos; +use function str_contains; use SebastianBergmann\Version as VersionId; /** @@ -22,20 +22,11 @@ */ final class Version { - /** - * @var string - */ - private static $pharVersion = ''; - - /** - * @var string - */ - private static $version = ''; + private static string $pharVersion = ''; + private static string $version = ''; /** - * Returns the current version of PHPUnit. - * - * @psalm-return non-empty-string + * @return non-empty-string */ public static function id(): string { @@ -44,21 +35,19 @@ public static function id(): string } if (self::$version === '') { - self::$version = (new VersionId('9.6.23', dirname(__DIR__, 2)))->getVersion(); - - assert(!empty(self::$version)); + self::$version = (new VersionId('12.3.7', dirname(__DIR__, 2)))->asString(); } return self::$version; } /** - * @psalm-return non-empty-string + * @return non-empty-string */ public static function series(): string { - if (strpos(self::id(), '-')) { - $version = explode('-', self::id())[0]; + if (str_contains(self::id(), '-')) { + $version = explode('-', self::id(), 2)[0]; } else { $version = self::id(); } @@ -67,7 +56,18 @@ public static function series(): string } /** - * @psalm-return non-empty-string + * @return positive-int + */ + public static function majorVersionNumber(): int + { + $majorVersion = (int) explode('.', self::series())[0]; + assert($majorVersion > 0); + + return $majorVersion; + } + + /** + * @return non-empty-string */ public static function getVersionString(): string { diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Application.php b/app/vendor/phpunit/phpunit/src/TextUI/Application.php new file mode 100644 index 000000000..e430df7c7 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Application.php @@ -0,0 +1,862 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI; + +use const PHP_EOL; +use const PHP_VERSION; +use function assert; +use function class_exists; +use function defined; +use function dirname; +use function explode; +use function function_exists; +use function is_file; +use function method_exists; +use function printf; +use function realpath; +use function sprintf; +use function str_contains; +use function str_starts_with; +use function trim; +use function unlink; +use PHPUnit\Event\EventFacadeIsSealedException; +use PHPUnit\Event\Facade as EventFacade; +use PHPUnit\Event\UnknownSubscriberTypeException; +use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\TestSuite; +use PHPUnit\Logging\EventLogger; +use PHPUnit\Logging\JUnit\JunitXmlLogger; +use PHPUnit\Logging\OpenTestReporting\CannotOpenUriForWritingException; +use PHPUnit\Logging\OpenTestReporting\OtrXmlLogger; +use PHPUnit\Logging\TeamCity\TeamCityLogger; +use PHPUnit\Logging\TestDox\HtmlRenderer as TestDoxHtmlRenderer; +use PHPUnit\Logging\TestDox\PlainTextRenderer as TestDoxTextRenderer; +use PHPUnit\Logging\TestDox\TestResultCollector as TestDoxResultCollector; +use PHPUnit\Runner\Baseline\CannotLoadBaselineException; +use PHPUnit\Runner\Baseline\Generator as BaselineGenerator; +use PHPUnit\Runner\Baseline\Reader; +use PHPUnit\Runner\Baseline\Writer; +use PHPUnit\Runner\CodeCoverage; +use PHPUnit\Runner\CodeCoverageInitializationStatus; +use PHPUnit\Runner\DeprecationCollector\Facade as DeprecationCollector; +use PHPUnit\Runner\DirectoryDoesNotExistException; +use PHPUnit\Runner\ErrorHandler; +use PHPUnit\Runner\Extension\ExtensionBootstrapper; +use PHPUnit\Runner\Extension\Facade as ExtensionFacade; +use PHPUnit\Runner\Extension\PharLoader; +use PHPUnit\Runner\GarbageCollection\GarbageCollectionHandler; +use PHPUnit\Runner\Phpt\TestCase as PhptTestCase; +use PHPUnit\Runner\ResultCache\DefaultResultCache; +use PHPUnit\Runner\ResultCache\NullResultCache; +use PHPUnit\Runner\ResultCache\ResultCache; +use PHPUnit\Runner\ResultCache\ResultCacheHandler; +use PHPUnit\Runner\TestSuiteSorter; +use PHPUnit\Runner\Version; +use PHPUnit\TestRunner\IssueFilter; +use PHPUnit\TestRunner\TestResult\Facade as TestResultFacade; +use PHPUnit\TextUI\CliArguments\Builder; +use PHPUnit\TextUI\CliArguments\Configuration as CliConfiguration; +use PHPUnit\TextUI\CliArguments\Exception as ArgumentsException; +use PHPUnit\TextUI\CliArguments\XmlConfigurationFileFinder; +use PHPUnit\TextUI\Command\AtLeastVersionCommand; +use PHPUnit\TextUI\Command\CheckPhpConfigurationCommand; +use PHPUnit\TextUI\Command\GenerateConfigurationCommand; +use PHPUnit\TextUI\Command\ListGroupsCommand; +use PHPUnit\TextUI\Command\ListTestFilesCommand; +use PHPUnit\TextUI\Command\ListTestsAsTextCommand; +use PHPUnit\TextUI\Command\ListTestsAsXmlCommand; +use PHPUnit\TextUI\Command\ListTestSuitesCommand; +use PHPUnit\TextUI\Command\MigrateConfigurationCommand; +use PHPUnit\TextUI\Command\Result; +use PHPUnit\TextUI\Command\ShowHelpCommand; +use PHPUnit\TextUI\Command\ShowVersionCommand; +use PHPUnit\TextUI\Command\VersionCheckCommand; +use PHPUnit\TextUI\Command\WarmCodeCoverageCacheCommand; +use PHPUnit\TextUI\Configuration\BootstrapLoader; +use PHPUnit\TextUI\Configuration\BootstrapScriptDoesNotExistException; +use PHPUnit\TextUI\Configuration\BootstrapScriptException; +use PHPUnit\TextUI\Configuration\CodeCoverageFilterRegistry; +use PHPUnit\TextUI\Configuration\Configuration; +use PHPUnit\TextUI\Configuration\PhpHandler; +use PHPUnit\TextUI\Configuration\Registry; +use PHPUnit\TextUI\Configuration\TestSuiteBuilder; +use PHPUnit\TextUI\Output\DefaultPrinter; +use PHPUnit\TextUI\Output\Facade as OutputFacade; +use PHPUnit\TextUI\Output\Printer; +use PHPUnit\TextUI\XmlConfiguration\Configuration as XmlConfiguration; +use PHPUnit\TextUI\XmlConfiguration\DefaultConfiguration; +use PHPUnit\TextUI\XmlConfiguration\Loader; +use PHPUnit\Util\Http\PhpDownloader; +use SebastianBergmann\Timer\Timer; +use Throwable; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Application +{ + /** + * @param list $argv + */ + public function run(array $argv): int + { + $this->preload(); + + try { + EventFacade::emitter()->applicationStarted(); + + $cliConfiguration = $this->buildCliConfiguration($argv); + $pathToXmlConfigurationFile = (new XmlConfigurationFileFinder)->find($cliConfiguration); + + $this->executeCommandsThatOnlyRequireCliConfiguration($cliConfiguration, $pathToXmlConfigurationFile); + + $xmlConfiguration = $this->loadXmlConfiguration($pathToXmlConfigurationFile); + + $configuration = Registry::init( + $cliConfiguration, + $xmlConfiguration, + ); + + (new PhpHandler)->handle($configuration->php()); + + try { + (new BootstrapLoader)->handle($configuration); + } catch (BootstrapScriptDoesNotExistException|BootstrapScriptException $e) { + $this->exitWithErrorMessage($e->getMessage()); + } + + $this->executeCommandsThatDoNotRequireTheTestSuite($configuration, $cliConfiguration); + + $pharExtensions = null; + $extensionRequiresCodeCoverageCollection = false; + $extensionReplacesOutput = false; + $extensionReplacesProgressOutput = false; + $extensionReplacesResultOutput = false; + + if (!$configuration->noExtensions()) { + if ($configuration->hasPharExtensionDirectory()) { + $pharExtensions = (new PharLoader)->loadPharExtensionsInDirectory( + $configuration->pharExtensionDirectory(), + ); + } + + $bootstrappedExtensions = $this->bootstrapExtensions($configuration); + $extensionRequiresCodeCoverageCollection = $bootstrappedExtensions['requiresCodeCoverageCollection']; + $extensionReplacesOutput = $bootstrappedExtensions['replacesOutput']; + $extensionReplacesProgressOutput = $bootstrappedExtensions['replacesProgressOutput']; + $extensionReplacesResultOutput = $bootstrappedExtensions['replacesResultOutput']; + } + + $printer = OutputFacade::init( + $configuration, + $extensionReplacesProgressOutput, + $extensionReplacesResultOutput, + ); + + if ($configuration->debug()) { + EventFacade::instance()->registerTracer( + new EventLogger( + 'php://stdout', + $configuration->withTelemetry(), + ), + ); + } + + TestResultFacade::init(); + DeprecationCollector::init(); + + $this->registerLogfileWriters($configuration); + + $testDoxResultCollector = $this->testDoxResultCollector($configuration); + + $resultCache = $this->initializeTestResultCache($configuration); + + if ($configuration->controlGarbageCollector()) { + new GarbageCollectionHandler( + EventFacade::instance(), + $configuration->numberOfTestsBeforeGarbageCollection(), + ); + } + + $baselineGenerator = $this->configureBaseline($configuration); + + EventFacade::instance()->seal(); + + ErrorHandler::instance()->registerDeprecationHandler(); + + $testSuite = $this->buildTestSuite($configuration); + + ErrorHandler::instance()->restoreDeprecationHandler(); + + $this->executeCommandsThatRequireTheTestSuite($configuration, $cliConfiguration, $testSuite); + + if ($testSuite->isEmpty() && !$configuration->hasCliArguments() && $configuration->testSuite()->isEmpty()) { + $this->execute(new ShowHelpCommand(Result::FAILURE)); + } + + $coverageInitializationStatus = CodeCoverage::instance()->init( + $configuration, + CodeCoverageFilterRegistry::instance(), + $extensionRequiresCodeCoverageCollection, + ); + + if (!$configuration->debug() && !$extensionReplacesOutput) { + $this->writeRuntimeInformation($printer, $configuration); + $this->writePharExtensionInformation($printer, $pharExtensions); + $this->writeRandomSeedInformation($printer, $configuration); + + $printer->print(PHP_EOL); + } + + $this->configureDeprecationTriggers($configuration); + + $timer = new Timer; + $timer->start(); + + if ($coverageInitializationStatus === CodeCoverageInitializationStatus::NOT_REQUESTED || + $coverageInitializationStatus === CodeCoverageInitializationStatus::SUCCEEDED) { + $runner = new TestRunner; + + $runner->run( + $configuration, + $resultCache, + $testSuite, + ); + } + + $duration = $timer->stop(); + + $testDoxResult = null; + + if (isset($testDoxResultCollector)) { + $testDoxResult = $testDoxResultCollector->testMethodsGroupedByClass(); + } + + if ($testDoxResult !== null && + $configuration->hasLogfileTestdoxHtml()) { + try { + OutputFacade::printerFor($configuration->logfileTestdoxHtml())->print( + (new TestDoxHtmlRenderer)->render($testDoxResult), + ); + } catch (DirectoryDoesNotExistException|InvalidSocketException $e) { + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + 'Cannot log test results in TestDox HTML format to "%s": %s', + $configuration->logfileTestdoxHtml(), + $e->getMessage(), + ), + ); + } + } + + if ($testDoxResult !== null && + $configuration->hasLogfileTestdoxText()) { + try { + OutputFacade::printerFor($configuration->logfileTestdoxText())->print( + (new TestDoxTextRenderer)->render($testDoxResult), + ); + } catch (DirectoryDoesNotExistException|InvalidSocketException $e) { + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + 'Cannot log test results in TestDox plain text format to "%s": %s', + $configuration->logfileTestdoxText(), + $e->getMessage(), + ), + ); + } + } + + $result = TestResultFacade::result(); + + if (!$extensionReplacesResultOutput && !$configuration->debug()) { + OutputFacade::printResult( + $result, + $testDoxResult, + $duration, + $configuration->hasSpecificDeprecationToStopOn(), + ); + } + + CodeCoverage::instance()->generateReports($printer, $configuration); + + if (isset($baselineGenerator)) { + (new Writer)->write( + $configuration->generateBaseline(), + $baselineGenerator->baseline(), + ); + + $printer->print( + sprintf( + PHP_EOL . 'Baseline written to %s.' . PHP_EOL, + realpath($configuration->generateBaseline()), + ), + ); + } + + $shellExitCode = (new ShellExitCodeCalculator)->calculate( + $configuration, + $result, + ); + + EventFacade::emitter()->applicationFinished($shellExitCode); + + return $shellExitCode; + // @codeCoverageIgnoreStart + } catch (Throwable $t) { + $this->exitWithCrashMessage($t); + } + // @codeCoverageIgnoreEnd + } + + private function execute(Command\Command $command, bool $requiresResultCollectedFromEvents = false): never + { + $errored = false; + + if ($requiresResultCollectedFromEvents) { + try { + TestResultFacade::init(); + EventFacade::instance()->seal(); + + $resultCollectedFromEvents = TestResultFacade::result(); + + $errored = $resultCollectedFromEvents->hasTestTriggeredPhpunitErrorEvents(); + } catch (EventFacadeIsSealedException|UnknownSubscriberTypeException) { + } + } + + print Version::getVersionString() . PHP_EOL . PHP_EOL; + + if (!$errored) { + $result = $command->execute(); + + print $result->output(); + + exit($result->shellExitCode()); + } + + assert(isset($resultCollectedFromEvents)); + + print 'There were errors:' . PHP_EOL; + + foreach ($resultCollectedFromEvents->testTriggeredPhpunitErrorEvents() as $events) { + foreach ($events as $event) { + print PHP_EOL . trim($event->message()) . PHP_EOL; + } + } + + exit(Result::EXCEPTION); + } + + /** + * @param list $argv + */ + private function buildCliConfiguration(array $argv): CliConfiguration + { + try { + $cliConfiguration = (new Builder)->fromParameters($argv); + } catch (ArgumentsException $e) { + $this->exitWithErrorMessage($e->getMessage()); + } + + return $cliConfiguration; + } + + private function loadXmlConfiguration(false|string $configurationFile): XmlConfiguration + { + if ($configurationFile === false) { + return DefaultConfiguration::create(); + } + + try { + return (new Loader)->load($configurationFile); + } catch (Throwable $e) { + $this->exitWithErrorMessage($e->getMessage()); + } + } + + private function buildTestSuite(Configuration $configuration): TestSuite + { + try { + return (new TestSuiteBuilder)->build($configuration); + } catch (Exception $e) { + $this->exitWithErrorMessage($e->getMessage()); + } + } + + /** + * @return array{requiresCodeCoverageCollection: bool, replacesOutput: bool, replacesProgressOutput: bool, replacesResultOutput: bool} + */ + private function bootstrapExtensions(Configuration $configuration): array + { + $facade = new ExtensionFacade; + + $extensionBootstrapper = new ExtensionBootstrapper( + $configuration, + $facade, + ); + + foreach ($configuration->extensionBootstrappers() as $bootstrapper) { + $extensionBootstrapper->bootstrap( + $bootstrapper['className'], + $bootstrapper['parameters'], + ); + } + + return [ + 'requiresCodeCoverageCollection' => $facade->requiresCodeCoverageCollection(), + 'replacesOutput' => $facade->replacesOutput(), + 'replacesProgressOutput' => $facade->replacesProgressOutput(), + 'replacesResultOutput' => $facade->replacesResultOutput(), + ]; + } + + private function executeCommandsThatOnlyRequireCliConfiguration(CliConfiguration $cliConfiguration, false|string $configurationFile): void + { + if ($cliConfiguration->generateConfiguration()) { + $this->execute(new GenerateConfigurationCommand); + } + + if ($cliConfiguration->migrateConfiguration()) { + if ($configurationFile === false) { + $this->exitWithErrorMessage('No configuration file found to migrate'); + } + + $this->execute(new MigrateConfigurationCommand(realpath($configurationFile))); + } + + if ($cliConfiguration->hasAtLeastVersion()) { + $this->execute(new AtLeastVersionCommand($cliConfiguration->atLeastVersion())); + } + + if ($cliConfiguration->version()) { + $this->execute(new ShowVersionCommand); + } + + if ($cliConfiguration->checkPhpConfiguration()) { + $this->execute(new CheckPhpConfigurationCommand); + } + + if ($cliConfiguration->checkVersion()) { + $this->execute(new VersionCheckCommand(new PhpDownloader, Version::majorVersionNumber(), Version::id())); + } + + if ($cliConfiguration->help()) { + $this->execute(new ShowHelpCommand(Result::SUCCESS)); + } + } + + private function executeCommandsThatDoNotRequireTheTestSuite(Configuration $configuration, CliConfiguration $cliConfiguration): void + { + if ($cliConfiguration->warmCoverageCache()) { + $this->execute(new WarmCodeCoverageCacheCommand($configuration, CodeCoverageFilterRegistry::instance())); + } + } + + private function executeCommandsThatRequireTheTestSuite(Configuration $configuration, CliConfiguration $cliConfiguration, TestSuite $testSuite): void + { + if ($cliConfiguration->listSuites()) { + $this->execute(new ListTestSuitesCommand($testSuite)); + } + + if ($cliConfiguration->listGroups()) { + $this->execute( + new ListGroupsCommand( + $this->filteredTests( + $configuration, + $testSuite, + ), + ), + true, + ); + } + + if ($cliConfiguration->listTests()) { + $this->execute( + new ListTestsAsTextCommand( + $this->filteredTests( + $configuration, + $testSuite, + ), + ), + true, + ); + } + + if ($cliConfiguration->hasListTestsXml()) { + $this->execute( + new ListTestsAsXmlCommand( + $this->filteredTests( + $configuration, + $testSuite, + ), + $cliConfiguration->listTestsXml(), + ), + true, + ); + } + + if ($cliConfiguration->listTestFiles()) { + $this->execute( + new ListTestFilesCommand( + $this->filteredTests( + $configuration, + $testSuite, + ), + ), + true, + ); + } + } + + private function writeRuntimeInformation(Printer $printer, Configuration $configuration): void + { + $printer->print(Version::getVersionString() . PHP_EOL . PHP_EOL); + + $runtime = 'PHP ' . PHP_VERSION; + + if (CodeCoverage::instance()->isActive()) { + $runtime .= ' with ' . CodeCoverage::instance()->driverNameAndVersion(); + } + + $this->writeMessage($printer, 'Runtime', $runtime); + + if ($configuration->hasConfigurationFile()) { + $this->writeMessage( + $printer, + 'Configuration', + $configuration->configurationFile(), + ); + } + } + + /** + * @param ?list $pharExtensions + */ + private function writePharExtensionInformation(Printer $printer, ?array $pharExtensions): void + { + if ($pharExtensions === null) { + return; + } + + foreach ($pharExtensions as $extension) { + $this->writeMessage( + $printer, + 'Extension', + $extension, + ); + } + } + + private function writeMessage(Printer $printer, string $type, string $message): void + { + $printer->print( + sprintf( + "%-15s%s\n", + $type . ':', + $message, + ), + ); + } + + private function writeRandomSeedInformation(Printer $printer, Configuration $configuration): void + { + if ($configuration->executionOrder() === TestSuiteSorter::ORDER_RANDOMIZED) { + $this->writeMessage( + $printer, + 'Random Seed', + (string) $configuration->randomOrderSeed(), + ); + } + } + + private function registerLogfileWriters(Configuration $configuration): void + { + if ($configuration->hasLogEventsText()) { + if (is_file($configuration->logEventsText())) { + unlink($configuration->logEventsText()); + } + + EventFacade::instance()->registerTracer( + new EventLogger( + $configuration->logEventsText(), + false, + ), + ); + } + + if ($configuration->hasLogEventsVerboseText()) { + if (is_file($configuration->logEventsVerboseText())) { + unlink($configuration->logEventsVerboseText()); + } + + EventFacade::instance()->registerTracer( + new EventLogger( + $configuration->logEventsVerboseText(), + true, + ), + ); + } + + if ($configuration->hasLogfileJunit()) { + try { + new JunitXmlLogger( + OutputFacade::printerFor($configuration->logfileJunit()), + EventFacade::instance(), + ); + } catch (DirectoryDoesNotExistException|InvalidSocketException $e) { + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + 'Cannot log test results in JUnit XML format to "%s": %s', + $configuration->logfileJunit(), + $e->getMessage(), + ), + ); + } + } + + if ($configuration->hasLogfileOtr()) { + try { + new OtrXmlLogger( + EventFacade::instance(), + $configuration->logfileOtr(), + $configuration->includeGitInformationInOtrLogfile(), + ); + } catch (CannotOpenUriForWritingException $e) { + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + 'Cannot log test results in Open Test Reporting XML format to "%s": %s', + $configuration->logfileOtr(), + $e->getMessage(), + ), + ); + } + } + + if ($configuration->hasLogfileTeamcity()) { + try { + new TeamCityLogger( + DefaultPrinter::from( + $configuration->logfileTeamcity(), + ), + EventFacade::instance(), + ); + } catch (DirectoryDoesNotExistException|InvalidSocketException $e) { + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + 'Cannot log test results in TeamCity format to "%s": %s', + $configuration->logfileTeamcity(), + $e->getMessage(), + ), + ); + } + } + } + + private function testDoxResultCollector(Configuration $configuration): ?TestDoxResultCollector + { + if ($configuration->hasLogfileTestdoxHtml() || + $configuration->hasLogfileTestdoxText() || + $configuration->outputIsTestDox()) { + return new TestDoxResultCollector( + EventFacade::instance(), + new IssueFilter($configuration->source()), + ); + } + + return null; + } + + private function initializeTestResultCache(Configuration $configuration): ResultCache + { + if ($configuration->cacheResult()) { + $cache = new DefaultResultCache($configuration->testResultCacheFile()); + + new ResultCacheHandler($cache, EventFacade::instance()); + + return $cache; + } + + return new NullResultCache; + } + + private function configureBaseline(Configuration $configuration): ?BaselineGenerator + { + if ($configuration->hasGenerateBaseline()) { + return new BaselineGenerator( + EventFacade::instance(), + $configuration->source(), + ); + } + + if ($configuration->source()->useBaseline()) { + $baselineFile = $configuration->source()->baseline(); + $baseline = null; + + try { + $baseline = (new Reader)->read($baselineFile); + } catch (CannotLoadBaselineException $e) { + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning($e->getMessage()); + } + + if ($baseline !== null) { + ErrorHandler::instance()->useBaseline($baseline); + } + } + + return null; + } + + /** + * @codeCoverageIgnore + */ + private function exitWithCrashMessage(Throwable $t): never + { + $message = $t->getMessage(); + + if (trim($message) === '') { + $message = '(no message)'; + } + + printf( + '%s%sAn error occurred inside PHPUnit.%s%sMessage: %s', + PHP_EOL, + PHP_EOL, + PHP_EOL, + PHP_EOL, + $message, + ); + + $first = true; + + if ($t->getPrevious() !== null) { + $t = $t->getPrevious(); + } + + do { + printf( + '%s%s: %s:%d%s%s%s%s', + PHP_EOL, + $first ? 'Location' : 'Caused by', + $t->getFile(), + $t->getLine(), + PHP_EOL, + PHP_EOL, + $t->getTraceAsString(), + PHP_EOL, + ); + + $first = false; + } while ($t = $t->getPrevious()); + + exit(Result::CRASH); + } + + private function exitWithErrorMessage(string $message): never + { + print Version::getVersionString() . PHP_EOL . PHP_EOL . $message . PHP_EOL; + + exit(Result::EXCEPTION); + } + + /** + * @return list + */ + private function filteredTests(Configuration $configuration, TestSuite $suite): array + { + (new TestSuiteFilterProcessor)->process($configuration, $suite); + + return $suite->collect(); + } + + private function configureDeprecationTriggers(Configuration $configuration): void + { + $deprecationTriggers = [ + 'functions' => [], + 'methods' => [], + ]; + + foreach ($configuration->source()->deprecationTriggers()['functions'] as $function) { + if (!function_exists($function)) { + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + 'Function %s cannot be configured as a deprecation trigger because it is not declared', + $function, + ), + ); + + continue; + } + + $deprecationTriggers['functions'][] = $function; + } + + foreach ($configuration->source()->deprecationTriggers()['methods'] as $method) { + if (!str_contains($method, '::')) { + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + '%s cannot be configured as a deprecation trigger because it is not in ClassName::methodName format', + $method, + ), + ); + + continue; + } + + [$className, $methodName] = explode('::', $method); + + if (!class_exists($className) || !method_exists($className, $methodName)) { + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + 'Method %s::%s cannot be configured as a deprecation trigger because it is not declared', + $className, + $methodName, + ), + ); + + continue; + } + + $deprecationTriggers['methods'][] = [ + 'className' => $className, + 'methodName' => $methodName, + ]; + } + + ErrorHandler::instance()->useDeprecationTriggers($deprecationTriggers); + } + + private function preload(): void + { + if (!defined('PHPUNIT_COMPOSER_INSTALL')) { + return; + } + + $classMapFile = dirname(PHPUNIT_COMPOSER_INSTALL) . '/composer/autoload_classmap.php'; + + if (!is_file($classMapFile)) { + return; + } + + foreach (require $classMapFile as $codeUnitName => $sourceCodeFile) { + if (!str_starts_with($codeUnitName, 'PHPUnit\\') && + !str_starts_with($codeUnitName, 'SebastianBergmann\\')) { + continue; + } + + if (str_contains($sourceCodeFile, '/tests/')) { + continue; + } + + require_once $sourceCodeFile; + } + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/CliArguments/Builder.php b/app/vendor/phpunit/phpunit/src/TextUI/CliArguments/Builder.php deleted file mode 100644 index 51f0a5131..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/CliArguments/Builder.php +++ /dev/null @@ -1,886 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\CliArguments; - -use function array_map; -use function array_merge; -use function class_exists; -use function explode; -use function is_numeric; -use function str_replace; -use PHPUnit\Runner\TestSuiteSorter; -use PHPUnit\TextUI\DefaultResultPrinter; -use PHPUnit\TextUI\XmlConfiguration\Extension; -use PHPUnit\Util\Log\TeamCity; -use PHPUnit\Util\TestDox\CliTestDoxPrinter; -use SebastianBergmann\CliParser\Exception as CliParserException; -use SebastianBergmann\CliParser\Parser as CliParser; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class Builder -{ - private const LONG_OPTIONS = [ - 'atleast-version=', - 'prepend=', - 'bootstrap=', - 'cache-result', - 'do-not-cache-result', - 'cache-result-file=', - 'check-version', - 'colors==', - 'columns=', - 'configuration=', - 'coverage-cache=', - 'warm-coverage-cache', - 'coverage-filter=', - 'coverage-clover=', - 'coverage-cobertura=', - 'coverage-crap4j=', - 'coverage-html=', - 'coverage-php=', - 'coverage-text==', - 'coverage-xml=', - 'path-coverage', - 'debug', - 'disallow-test-output', - 'disallow-resource-usage', - 'disallow-todo-tests', - 'default-time-limit=', - 'enforce-time-limit', - 'exclude-group=', - 'extensions=', - 'filter=', - 'generate-configuration', - 'globals-backup', - 'group=', - 'covers=', - 'uses=', - 'help', - 'resolve-dependencies', - 'ignore-dependencies', - 'include-path=', - 'list-groups', - 'list-suites', - 'list-tests', - 'list-tests-xml=', - 'loader=', - 'log-junit=', - 'log-teamcity=', - 'migrate-configuration', - 'no-configuration', - 'no-coverage', - 'no-logging', - 'no-interaction', - 'no-extensions', - 'order-by=', - 'printer=', - 'process-isolation', - 'repeat=', - 'dont-report-useless-tests', - 'random-order', - 'random-order-seed=', - 'reverse-order', - 'reverse-list', - 'static-backup', - 'stderr', - 'stop-on-defect', - 'stop-on-error', - 'stop-on-failure', - 'stop-on-warning', - 'stop-on-incomplete', - 'stop-on-risky', - 'stop-on-skipped', - 'fail-on-empty-test-suite', - 'fail-on-incomplete', - 'fail-on-risky', - 'fail-on-skipped', - 'fail-on-warning', - 'strict-coverage', - 'disable-coverage-ignore', - 'strict-global-state', - 'teamcity', - 'testdox', - 'testdox-group=', - 'testdox-exclude-group=', - 'testdox-html=', - 'testdox-text=', - 'testdox-xml=', - 'test-suffix=', - 'testsuite=', - 'verbose', - 'version', - 'whitelist=', - 'dump-xdebug-filter=', - ]; - private const SHORT_OPTIONS = 'd:c:hv'; - - public function fromParameters(array $parameters, array $additionalLongOptions): Configuration - { - try { - $options = (new CliParser)->parse( - $parameters, - self::SHORT_OPTIONS, - array_merge(self::LONG_OPTIONS, $additionalLongOptions), - ); - } catch (CliParserException $e) { - throw new Exception( - $e->getMessage(), - $e->getCode(), - $e, - ); - } - - $argument = null; - $atLeastVersion = null; - $backupGlobals = null; - $backupStaticAttributes = null; - $beStrictAboutChangesToGlobalState = null; - $beStrictAboutResourceUsageDuringSmallTests = null; - $bootstrap = null; - $cacheResult = null; - $cacheResultFile = null; - $checkVersion = null; - $colors = null; - $columns = null; - $configuration = null; - $coverageCacheDirectory = null; - $warmCoverageCache = null; - $coverageFilter = null; - $coverageClover = null; - $coverageCobertura = null; - $coverageCrap4J = null; - $coverageHtml = null; - $coveragePhp = null; - $coverageText = null; - $coverageTextShowUncoveredFiles = null; - $coverageTextShowOnlySummary = null; - $coverageXml = null; - $pathCoverage = null; - $debug = null; - $defaultTimeLimit = null; - $disableCodeCoverageIgnore = null; - $disallowTestOutput = null; - $disallowTodoAnnotatedTests = null; - $enforceTimeLimit = null; - $excludeGroups = null; - $executionOrder = null; - $executionOrderDefects = null; - $extensions = []; - $unavailableExtensions = []; - $failOnEmptyTestSuite = null; - $failOnIncomplete = null; - $failOnRisky = null; - $failOnSkipped = null; - $failOnWarning = null; - $filter = null; - $generateConfiguration = null; - $migrateConfiguration = null; - $groups = null; - $testsCovering = null; - $testsUsing = null; - $help = null; - $includePath = null; - $iniSettings = []; - $junitLogfile = null; - $listGroups = null; - $listSuites = null; - $listTests = null; - $listTestsXml = null; - $loader = null; - $noCoverage = null; - $noExtensions = null; - $noInteraction = null; - $noLogging = null; - $printer = null; - $processIsolation = null; - $randomOrderSeed = null; - $repeat = null; - $reportUselessTests = null; - $resolveDependencies = null; - $reverseList = null; - $stderr = null; - $strictCoverage = null; - $stopOnDefect = null; - $stopOnError = null; - $stopOnFailure = null; - $stopOnIncomplete = null; - $stopOnRisky = null; - $stopOnSkipped = null; - $stopOnWarning = null; - $teamcityLogfile = null; - $testdoxExcludeGroups = null; - $testdoxGroups = null; - $testdoxHtmlFile = null; - $testdoxTextFile = null; - $testdoxXmlFile = null; - $testSuffixes = null; - $testSuite = null; - $unrecognizedOptions = []; - $unrecognizedOrderBy = null; - $useDefaultConfiguration = null; - $verbose = null; - $version = null; - $xdebugFilterFile = null; - - if (isset($options[1][0])) { - $argument = $options[1][0]; - } - - foreach ($options[0] as $option) { - switch ($option[0]) { - case '--colors': - $colors = $option[1] ?: DefaultResultPrinter::COLOR_AUTO; - - break; - - case '--bootstrap': - $bootstrap = $option[1]; - - break; - - case '--cache-result': - $cacheResult = true; - - break; - - case '--do-not-cache-result': - $cacheResult = false; - - break; - - case '--cache-result-file': - $cacheResultFile = $option[1]; - - break; - - case '--columns': - if (is_numeric($option[1])) { - $columns = (int) $option[1]; - } elseif ($option[1] === 'max') { - $columns = 'max'; - } - - break; - - case 'c': - case '--configuration': - $configuration = $option[1]; - - break; - - case '--coverage-cache': - $coverageCacheDirectory = $option[1]; - - break; - - case '--warm-coverage-cache': - $warmCoverageCache = true; - - break; - - case '--coverage-clover': - $coverageClover = $option[1]; - - break; - - case '--coverage-cobertura': - $coverageCobertura = $option[1]; - - break; - - case '--coverage-crap4j': - $coverageCrap4J = $option[1]; - - break; - - case '--coverage-html': - $coverageHtml = $option[1]; - - break; - - case '--coverage-php': - $coveragePhp = $option[1]; - - break; - - case '--coverage-text': - if ($option[1] === null) { - $option[1] = 'php://stdout'; - } - - $coverageText = $option[1]; - $coverageTextShowUncoveredFiles = false; - $coverageTextShowOnlySummary = false; - - break; - - case '--coverage-xml': - $coverageXml = $option[1]; - - break; - - case '--path-coverage': - $pathCoverage = true; - - break; - - case 'd': - $tmp = explode('=', $option[1]); - - if (isset($tmp[0])) { - if (isset($tmp[1])) { - $iniSettings[$tmp[0]] = $tmp[1]; - } else { - $iniSettings[$tmp[0]] = '1'; - } - } - - break; - - case '--debug': - $debug = true; - - break; - - case 'h': - case '--help': - $help = true; - - break; - - case '--filter': - $filter = $option[1]; - - break; - - case '--testsuite': - $testSuite = $option[1]; - - break; - - case '--generate-configuration': - $generateConfiguration = true; - - break; - - case '--migrate-configuration': - $migrateConfiguration = true; - - break; - - case '--group': - $groups = explode(',', $option[1]); - - break; - - case '--exclude-group': - $excludeGroups = explode(',', $option[1]); - - break; - - case '--covers': - $testsCovering = array_map('strtolower', explode(',', $option[1])); - - break; - - case '--uses': - $testsUsing = array_map('strtolower', explode(',', $option[1])); - - break; - - case '--test-suffix': - $testSuffixes = explode(',', $option[1]); - - break; - - case '--include-path': - $includePath = $option[1]; - - break; - - case '--list-groups': - $listGroups = true; - - break; - - case '--list-suites': - $listSuites = true; - - break; - - case '--list-tests': - $listTests = true; - - break; - - case '--list-tests-xml': - $listTestsXml = $option[1]; - - break; - - case '--printer': - $printer = $option[1]; - - break; - - case '--loader': - $loader = $option[1]; - - break; - - case '--log-junit': - $junitLogfile = $option[1]; - - break; - - case '--log-teamcity': - $teamcityLogfile = $option[1]; - - break; - - case '--order-by': - foreach (explode(',', $option[1]) as $order) { - switch ($order) { - case 'default': - $executionOrder = TestSuiteSorter::ORDER_DEFAULT; - $executionOrderDefects = TestSuiteSorter::ORDER_DEFAULT; - $resolveDependencies = true; - - break; - - case 'defects': - $executionOrderDefects = TestSuiteSorter::ORDER_DEFECTS_FIRST; - - break; - - case 'depends': - $resolveDependencies = true; - - break; - - case 'duration': - $executionOrder = TestSuiteSorter::ORDER_DURATION; - - break; - - case 'no-depends': - $resolveDependencies = false; - - break; - - case 'random': - $executionOrder = TestSuiteSorter::ORDER_RANDOMIZED; - - break; - - case 'reverse': - $executionOrder = TestSuiteSorter::ORDER_REVERSED; - - break; - - case 'size': - $executionOrder = TestSuiteSorter::ORDER_SIZE; - - break; - - default: - $unrecognizedOrderBy = $order; - } - } - - break; - - case '--process-isolation': - $processIsolation = true; - - break; - - case '--repeat': - $repeat = (int) $option[1]; - - break; - - case '--stderr': - $stderr = true; - - break; - - case '--stop-on-defect': - $stopOnDefect = true; - - break; - - case '--stop-on-error': - $stopOnError = true; - - break; - - case '--stop-on-failure': - $stopOnFailure = true; - - break; - - case '--stop-on-warning': - $stopOnWarning = true; - - break; - - case '--stop-on-incomplete': - $stopOnIncomplete = true; - - break; - - case '--stop-on-risky': - $stopOnRisky = true; - - break; - - case '--stop-on-skipped': - $stopOnSkipped = true; - - break; - - case '--fail-on-empty-test-suite': - $failOnEmptyTestSuite = true; - - break; - - case '--fail-on-incomplete': - $failOnIncomplete = true; - - break; - - case '--fail-on-risky': - $failOnRisky = true; - - break; - - case '--fail-on-skipped': - $failOnSkipped = true; - - break; - - case '--fail-on-warning': - $failOnWarning = true; - - break; - - case '--teamcity': - $printer = TeamCity::class; - - break; - - case '--testdox': - $printer = CliTestDoxPrinter::class; - - break; - - case '--testdox-group': - $testdoxGroups = explode(',', $option[1]); - - break; - - case '--testdox-exclude-group': - $testdoxExcludeGroups = explode(',', $option[1]); - - break; - - case '--testdox-html': - $testdoxHtmlFile = $option[1]; - - break; - - case '--testdox-text': - $testdoxTextFile = $option[1]; - - break; - - case '--testdox-xml': - $testdoxXmlFile = $option[1]; - - break; - - case '--no-configuration': - $useDefaultConfiguration = false; - - break; - - case '--extensions': - foreach (explode(',', $option[1]) as $extensionClass) { - if (!class_exists($extensionClass)) { - $unavailableExtensions[] = $extensionClass; - - continue; - } - - $extensions[] = new Extension($extensionClass, '', []); - } - - break; - - case '--no-extensions': - $noExtensions = true; - - break; - - case '--no-coverage': - $noCoverage = true; - - break; - - case '--no-logging': - $noLogging = true; - - break; - - case '--no-interaction': - $noInteraction = true; - - break; - - case '--globals-backup': - $backupGlobals = true; - - break; - - case '--static-backup': - $backupStaticAttributes = true; - - break; - - case 'v': - case '--verbose': - $verbose = true; - - break; - - case '--atleast-version': - $atLeastVersion = $option[1]; - - break; - - case '--version': - $version = true; - - break; - - case '--dont-report-useless-tests': - $reportUselessTests = false; - - break; - - case '--strict-coverage': - $strictCoverage = true; - - break; - - case '--disable-coverage-ignore': - $disableCodeCoverageIgnore = true; - - break; - - case '--strict-global-state': - $beStrictAboutChangesToGlobalState = true; - - break; - - case '--disallow-test-output': - $disallowTestOutput = true; - - break; - - case '--disallow-resource-usage': - $beStrictAboutResourceUsageDuringSmallTests = true; - - break; - - case '--default-time-limit': - $defaultTimeLimit = (int) $option[1]; - - break; - - case '--enforce-time-limit': - $enforceTimeLimit = true; - - break; - - case '--disallow-todo-tests': - $disallowTodoAnnotatedTests = true; - - break; - - case '--reverse-list': - $reverseList = true; - - break; - - case '--check-version': - $checkVersion = true; - - break; - - case '--coverage-filter': - case '--whitelist': - if ($coverageFilter === null) { - $coverageFilter = []; - } - - $coverageFilter[] = $option[1]; - - break; - - case '--random-order': - $executionOrder = TestSuiteSorter::ORDER_RANDOMIZED; - - break; - - case '--random-order-seed': - $randomOrderSeed = (int) $option[1]; - - break; - - case '--resolve-dependencies': - $resolveDependencies = true; - - break; - - case '--ignore-dependencies': - $resolveDependencies = false; - - break; - - case '--reverse-order': - $executionOrder = TestSuiteSorter::ORDER_REVERSED; - - break; - - case '--dump-xdebug-filter': - $xdebugFilterFile = $option[1]; - - break; - - default: - $unrecognizedOptions[str_replace('--', '', $option[0])] = $option[1]; - } - } - - if (empty($extensions)) { - $extensions = null; - } - - if (empty($unavailableExtensions)) { - $unavailableExtensions = null; - } - - if (empty($iniSettings)) { - $iniSettings = null; - } - - if (empty($coverageFilter)) { - $coverageFilter = null; - } - - return new Configuration( - $argument, - $atLeastVersion, - $backupGlobals, - $backupStaticAttributes, - $beStrictAboutChangesToGlobalState, - $beStrictAboutResourceUsageDuringSmallTests, - $bootstrap, - $cacheResult, - $cacheResultFile, - $checkVersion, - $colors, - $columns, - $configuration, - $coverageClover, - $coverageCobertura, - $coverageCrap4J, - $coverageHtml, - $coveragePhp, - $coverageText, - $coverageTextShowUncoveredFiles, - $coverageTextShowOnlySummary, - $coverageXml, - $pathCoverage, - $coverageCacheDirectory, - $warmCoverageCache, - $debug, - $defaultTimeLimit, - $disableCodeCoverageIgnore, - $disallowTestOutput, - $disallowTodoAnnotatedTests, - $enforceTimeLimit, - $excludeGroups, - $executionOrder, - $executionOrderDefects, - $extensions, - $unavailableExtensions, - $failOnEmptyTestSuite, - $failOnIncomplete, - $failOnRisky, - $failOnSkipped, - $failOnWarning, - $filter, - $generateConfiguration, - $migrateConfiguration, - $groups, - $testsCovering, - $testsUsing, - $help, - $includePath, - $iniSettings, - $junitLogfile, - $listGroups, - $listSuites, - $listTests, - $listTestsXml, - $loader, - $noCoverage, - $noExtensions, - $noInteraction, - $noLogging, - $printer, - $processIsolation, - $randomOrderSeed, - $repeat, - $reportUselessTests, - $resolveDependencies, - $reverseList, - $stderr, - $strictCoverage, - $stopOnDefect, - $stopOnError, - $stopOnFailure, - $stopOnIncomplete, - $stopOnRisky, - $stopOnSkipped, - $stopOnWarning, - $teamcityLogfile, - $testdoxExcludeGroups, - $testdoxGroups, - $testdoxHtmlFile, - $testdoxTextFile, - $testdoxXmlFile, - $testSuffixes, - $testSuite, - $unrecognizedOptions, - $unrecognizedOrderBy, - $useDefaultConfiguration, - $verbose, - $version, - $coverageFilter, - $xdebugFilterFile, - ); - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/CliArguments/Configuration.php b/app/vendor/phpunit/phpunit/src/TextUI/CliArguments/Configuration.php deleted file mode 100644 index 51bf5cb83..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/CliArguments/Configuration.php +++ /dev/null @@ -1,2108 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\CliArguments; - -use PHPUnit\TextUI\XmlConfiguration\Extension; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @psalm-immutable - */ -final class Configuration -{ - /** - * @var ?string - */ - private $argument; - - /** - * @var ?string - */ - private $atLeastVersion; - - /** - * @var ?bool - */ - private $backupGlobals; - - /** - * @var ?bool - */ - private $backupStaticAttributes; - - /** - * @var ?bool - */ - private $beStrictAboutChangesToGlobalState; - - /** - * @var ?bool - */ - private $beStrictAboutResourceUsageDuringSmallTests; - - /** - * @var ?string - */ - private $bootstrap; - - /** - * @var ?bool - */ - private $cacheResult; - - /** - * @var ?string - */ - private $cacheResultFile; - - /** - * @var ?bool - */ - private $checkVersion; - - /** - * @var ?string - */ - private $colors; - - /** - * @var null|int|string - */ - private $columns; - - /** - * @var ?string - */ - private $configuration; - - /** - * @var null|string[] - */ - private $coverageFilter; - - /** - * @var ?string - */ - private $coverageClover; - - /** - * @var ?string - */ - private $coverageCobertura; - - /** - * @var ?string - */ - private $coverageCrap4J; - - /** - * @var ?string - */ - private $coverageHtml; - - /** - * @var ?string - */ - private $coveragePhp; - - /** - * @var ?string - */ - private $coverageText; - - /** - * @var ?bool - */ - private $coverageTextShowUncoveredFiles; - - /** - * @var ?bool - */ - private $coverageTextShowOnlySummary; - - /** - * @var ?string - */ - private $coverageXml; - - /** - * @var ?bool - */ - private $pathCoverage; - - /** - * @var ?string - */ - private $coverageCacheDirectory; - - /** - * @var ?bool - */ - private $warmCoverageCache; - - /** - * @var ?bool - */ - private $debug; - - /** - * @var ?int - */ - private $defaultTimeLimit; - - /** - * @var ?bool - */ - private $disableCodeCoverageIgnore; - - /** - * @var ?bool - */ - private $disallowTestOutput; - - /** - * @var ?bool - */ - private $disallowTodoAnnotatedTests; - - /** - * @var ?bool - */ - private $enforceTimeLimit; - - /** - * @var null|string[] - */ - private $excludeGroups; - - /** - * @var ?int - */ - private $executionOrder; - - /** - * @var ?int - */ - private $executionOrderDefects; - - /** - * @var null|Extension[] - */ - private $extensions; - - /** - * @var null|string[] - */ - private $unavailableExtensions; - - /** - * @var ?bool - */ - private $failOnEmptyTestSuite; - - /** - * @var ?bool - */ - private $failOnIncomplete; - - /** - * @var ?bool - */ - private $failOnRisky; - - /** - * @var ?bool - */ - private $failOnSkipped; - - /** - * @var ?bool - */ - private $failOnWarning; - - /** - * @var ?string - */ - private $filter; - - /** - * @var ?bool - */ - private $generateConfiguration; - - /** - * @var ?bool - */ - private $migrateConfiguration; - - /** - * @var null|string[] - */ - private $groups; - - /** - * @var null|string[] - */ - private $testsCovering; - - /** - * @var null|string[] - */ - private $testsUsing; - - /** - * @var ?bool - */ - private $help; - - /** - * @var ?string - */ - private $includePath; - - /** - * @var null|string[] - */ - private $iniSettings; - - /** - * @var ?string - */ - private $junitLogfile; - - /** - * @var ?bool - */ - private $listGroups; - - /** - * @var ?bool - */ - private $listSuites; - - /** - * @var ?bool - */ - private $listTests; - - /** - * @var ?string - */ - private $listTestsXml; - - /** - * @var ?string - */ - private $loader; - - /** - * @var ?bool - */ - private $noCoverage; - - /** - * @var ?bool - */ - private $noExtensions; - - /** - * @var ?bool - */ - private $noInteraction; - - /** - * @var ?bool - */ - private $noLogging; - - /** - * @var ?string - */ - private $printer; - - /** - * @var ?bool - */ - private $processIsolation; - - /** - * @var ?int - */ - private $randomOrderSeed; - - /** - * @var ?int - */ - private $repeat; - - /** - * @var ?bool - */ - private $reportUselessTests; - - /** - * @var ?bool - */ - private $resolveDependencies; - - /** - * @var ?bool - */ - private $reverseList; - - /** - * @var ?bool - */ - private $stderr; - - /** - * @var ?bool - */ - private $strictCoverage; - - /** - * @var ?bool - */ - private $stopOnDefect; - - /** - * @var ?bool - */ - private $stopOnError; - - /** - * @var ?bool - */ - private $stopOnFailure; - - /** - * @var ?bool - */ - private $stopOnIncomplete; - - /** - * @var ?bool - */ - private $stopOnRisky; - - /** - * @var ?bool - */ - private $stopOnSkipped; - - /** - * @var ?bool - */ - private $stopOnWarning; - - /** - * @var ?string - */ - private $teamcityLogfile; - - /** - * @var null|string[] - */ - private $testdoxExcludeGroups; - - /** - * @var null|string[] - */ - private $testdoxGroups; - - /** - * @var ?string - */ - private $testdoxHtmlFile; - - /** - * @var ?string - */ - private $testdoxTextFile; - - /** - * @var ?string - */ - private $testdoxXmlFile; - - /** - * @var null|string[] - */ - private $testSuffixes; - - /** - * @var ?string - */ - private $testSuite; - - /** - * @var string[] - */ - private $unrecognizedOptions; - - /** - * @var ?string - */ - private $unrecognizedOrderBy; - - /** - * @var ?bool - */ - private $useDefaultConfiguration; - - /** - * @var ?bool - */ - private $verbose; - - /** - * @var ?bool - */ - private $version; - - /** - * @var ?string - */ - private $xdebugFilterFile; - - /** - * @param null|int|string $columns - */ - public function __construct(?string $argument, ?string $atLeastVersion, ?bool $backupGlobals, ?bool $backupStaticAttributes, ?bool $beStrictAboutChangesToGlobalState, ?bool $beStrictAboutResourceUsageDuringSmallTests, ?string $bootstrap, ?bool $cacheResult, ?string $cacheResultFile, ?bool $checkVersion, ?string $colors, $columns, ?string $configuration, ?string $coverageClover, ?string $coverageCobertura, ?string $coverageCrap4J, ?string $coverageHtml, ?string $coveragePhp, ?string $coverageText, ?bool $coverageTextShowUncoveredFiles, ?bool $coverageTextShowOnlySummary, ?string $coverageXml, ?bool $pathCoverage, ?string $coverageCacheDirectory, ?bool $warmCoverageCache, ?bool $debug, ?int $defaultTimeLimit, ?bool $disableCodeCoverageIgnore, ?bool $disallowTestOutput, ?bool $disallowTodoAnnotatedTests, ?bool $enforceTimeLimit, ?array $excludeGroups, ?int $executionOrder, ?int $executionOrderDefects, ?array $extensions, ?array $unavailableExtensions, ?bool $failOnEmptyTestSuite, ?bool $failOnIncomplete, ?bool $failOnRisky, ?bool $failOnSkipped, ?bool $failOnWarning, ?string $filter, ?bool $generateConfiguration, ?bool $migrateConfiguration, ?array $groups, ?array $testsCovering, ?array $testsUsing, ?bool $help, ?string $includePath, ?array $iniSettings, ?string $junitLogfile, ?bool $listGroups, ?bool $listSuites, ?bool $listTests, ?string $listTestsXml, ?string $loader, ?bool $noCoverage, ?bool $noExtensions, ?bool $noInteraction, ?bool $noLogging, ?string $printer, ?bool $processIsolation, ?int $randomOrderSeed, ?int $repeat, ?bool $reportUselessTests, ?bool $resolveDependencies, ?bool $reverseList, ?bool $stderr, ?bool $strictCoverage, ?bool $stopOnDefect, ?bool $stopOnError, ?bool $stopOnFailure, ?bool $stopOnIncomplete, ?bool $stopOnRisky, ?bool $stopOnSkipped, ?bool $stopOnWarning, ?string $teamcityLogfile, ?array $testdoxExcludeGroups, ?array $testdoxGroups, ?string $testdoxHtmlFile, ?string $testdoxTextFile, ?string $testdoxXmlFile, ?array $testSuffixes, ?string $testSuite, array $unrecognizedOptions, ?string $unrecognizedOrderBy, ?bool $useDefaultConfiguration, ?bool $verbose, ?bool $version, ?array $coverageFilter, ?string $xdebugFilterFile) - { - $this->argument = $argument; - $this->atLeastVersion = $atLeastVersion; - $this->backupGlobals = $backupGlobals; - $this->backupStaticAttributes = $backupStaticAttributes; - $this->beStrictAboutChangesToGlobalState = $beStrictAboutChangesToGlobalState; - $this->beStrictAboutResourceUsageDuringSmallTests = $beStrictAboutResourceUsageDuringSmallTests; - $this->bootstrap = $bootstrap; - $this->cacheResult = $cacheResult; - $this->cacheResultFile = $cacheResultFile; - $this->checkVersion = $checkVersion; - $this->colors = $colors; - $this->columns = $columns; - $this->configuration = $configuration; - $this->coverageFilter = $coverageFilter; - $this->coverageClover = $coverageClover; - $this->coverageCobertura = $coverageCobertura; - $this->coverageCrap4J = $coverageCrap4J; - $this->coverageHtml = $coverageHtml; - $this->coveragePhp = $coveragePhp; - $this->coverageText = $coverageText; - $this->coverageTextShowUncoveredFiles = $coverageTextShowUncoveredFiles; - $this->coverageTextShowOnlySummary = $coverageTextShowOnlySummary; - $this->coverageXml = $coverageXml; - $this->pathCoverage = $pathCoverage; - $this->coverageCacheDirectory = $coverageCacheDirectory; - $this->warmCoverageCache = $warmCoverageCache; - $this->debug = $debug; - $this->defaultTimeLimit = $defaultTimeLimit; - $this->disableCodeCoverageIgnore = $disableCodeCoverageIgnore; - $this->disallowTestOutput = $disallowTestOutput; - $this->disallowTodoAnnotatedTests = $disallowTodoAnnotatedTests; - $this->enforceTimeLimit = $enforceTimeLimit; - $this->excludeGroups = $excludeGroups; - $this->executionOrder = $executionOrder; - $this->executionOrderDefects = $executionOrderDefects; - $this->extensions = $extensions; - $this->unavailableExtensions = $unavailableExtensions; - $this->failOnEmptyTestSuite = $failOnEmptyTestSuite; - $this->failOnIncomplete = $failOnIncomplete; - $this->failOnRisky = $failOnRisky; - $this->failOnSkipped = $failOnSkipped; - $this->failOnWarning = $failOnWarning; - $this->filter = $filter; - $this->generateConfiguration = $generateConfiguration; - $this->migrateConfiguration = $migrateConfiguration; - $this->groups = $groups; - $this->testsCovering = $testsCovering; - $this->testsUsing = $testsUsing; - $this->help = $help; - $this->includePath = $includePath; - $this->iniSettings = $iniSettings; - $this->junitLogfile = $junitLogfile; - $this->listGroups = $listGroups; - $this->listSuites = $listSuites; - $this->listTests = $listTests; - $this->listTestsXml = $listTestsXml; - $this->loader = $loader; - $this->noCoverage = $noCoverage; - $this->noExtensions = $noExtensions; - $this->noInteraction = $noInteraction; - $this->noLogging = $noLogging; - $this->printer = $printer; - $this->processIsolation = $processIsolation; - $this->randomOrderSeed = $randomOrderSeed; - $this->repeat = $repeat; - $this->reportUselessTests = $reportUselessTests; - $this->resolveDependencies = $resolveDependencies; - $this->reverseList = $reverseList; - $this->stderr = $stderr; - $this->strictCoverage = $strictCoverage; - $this->stopOnDefect = $stopOnDefect; - $this->stopOnError = $stopOnError; - $this->stopOnFailure = $stopOnFailure; - $this->stopOnIncomplete = $stopOnIncomplete; - $this->stopOnRisky = $stopOnRisky; - $this->stopOnSkipped = $stopOnSkipped; - $this->stopOnWarning = $stopOnWarning; - $this->teamcityLogfile = $teamcityLogfile; - $this->testdoxExcludeGroups = $testdoxExcludeGroups; - $this->testdoxGroups = $testdoxGroups; - $this->testdoxHtmlFile = $testdoxHtmlFile; - $this->testdoxTextFile = $testdoxTextFile; - $this->testdoxXmlFile = $testdoxXmlFile; - $this->testSuffixes = $testSuffixes; - $this->testSuite = $testSuite; - $this->unrecognizedOptions = $unrecognizedOptions; - $this->unrecognizedOrderBy = $unrecognizedOrderBy; - $this->useDefaultConfiguration = $useDefaultConfiguration; - $this->verbose = $verbose; - $this->version = $version; - $this->xdebugFilterFile = $xdebugFilterFile; - } - - public function hasArgument(): bool - { - return $this->argument !== null; - } - - /** - * @throws Exception - */ - public function argument(): string - { - if ($this->argument === null) { - throw new Exception; - } - - return $this->argument; - } - - public function hasAtLeastVersion(): bool - { - return $this->atLeastVersion !== null; - } - - /** - * @throws Exception - */ - public function atLeastVersion(): string - { - if ($this->atLeastVersion === null) { - throw new Exception; - } - - return $this->atLeastVersion; - } - - public function hasBackupGlobals(): bool - { - return $this->backupGlobals !== null; - } - - /** - * @throws Exception - */ - public function backupGlobals(): bool - { - if ($this->backupGlobals === null) { - throw new Exception; - } - - return $this->backupGlobals; - } - - public function hasBackupStaticAttributes(): bool - { - return $this->backupStaticAttributes !== null; - } - - /** - * @throws Exception - */ - public function backupStaticAttributes(): bool - { - if ($this->backupStaticAttributes === null) { - throw new Exception; - } - - return $this->backupStaticAttributes; - } - - public function hasBeStrictAboutChangesToGlobalState(): bool - { - return $this->beStrictAboutChangesToGlobalState !== null; - } - - /** - * @throws Exception - */ - public function beStrictAboutChangesToGlobalState(): bool - { - if ($this->beStrictAboutChangesToGlobalState === null) { - throw new Exception; - } - - return $this->beStrictAboutChangesToGlobalState; - } - - public function hasBeStrictAboutResourceUsageDuringSmallTests(): bool - { - return $this->beStrictAboutResourceUsageDuringSmallTests !== null; - } - - /** - * @throws Exception - */ - public function beStrictAboutResourceUsageDuringSmallTests(): bool - { - if ($this->beStrictAboutResourceUsageDuringSmallTests === null) { - throw new Exception; - } - - return $this->beStrictAboutResourceUsageDuringSmallTests; - } - - public function hasBootstrap(): bool - { - return $this->bootstrap !== null; - } - - /** - * @throws Exception - */ - public function bootstrap(): string - { - if ($this->bootstrap === null) { - throw new Exception; - } - - return $this->bootstrap; - } - - public function hasCacheResult(): bool - { - return $this->cacheResult !== null; - } - - /** - * @throws Exception - */ - public function cacheResult(): bool - { - if ($this->cacheResult === null) { - throw new Exception; - } - - return $this->cacheResult; - } - - public function hasCacheResultFile(): bool - { - return $this->cacheResultFile !== null; - } - - /** - * @throws Exception - */ - public function cacheResultFile(): string - { - if ($this->cacheResultFile === null) { - throw new Exception; - } - - return $this->cacheResultFile; - } - - public function hasCheckVersion(): bool - { - return $this->checkVersion !== null; - } - - /** - * @throws Exception - */ - public function checkVersion(): bool - { - if ($this->checkVersion === null) { - throw new Exception; - } - - return $this->checkVersion; - } - - public function hasColors(): bool - { - return $this->colors !== null; - } - - /** - * @throws Exception - */ - public function colors(): string - { - if ($this->colors === null) { - throw new Exception; - } - - return $this->colors; - } - - public function hasColumns(): bool - { - return $this->columns !== null; - } - - /** - * @throws Exception - */ - public function columns() - { - if ($this->columns === null) { - throw new Exception; - } - - return $this->columns; - } - - public function hasConfiguration(): bool - { - return $this->configuration !== null; - } - - /** - * @throws Exception - */ - public function configuration(): string - { - if ($this->configuration === null) { - throw new Exception; - } - - return $this->configuration; - } - - public function hasCoverageFilter(): bool - { - return $this->coverageFilter !== null; - } - - /** - * @throws Exception - */ - public function coverageFilter(): array - { - if ($this->coverageFilter === null) { - throw new Exception; - } - - return $this->coverageFilter; - } - - public function hasCoverageClover(): bool - { - return $this->coverageClover !== null; - } - - /** - * @throws Exception - */ - public function coverageClover(): string - { - if ($this->coverageClover === null) { - throw new Exception; - } - - return $this->coverageClover; - } - - public function hasCoverageCobertura(): bool - { - return $this->coverageCobertura !== null; - } - - /** - * @throws Exception - */ - public function coverageCobertura(): string - { - if ($this->coverageCobertura === null) { - throw new Exception; - } - - return $this->coverageCobertura; - } - - public function hasCoverageCrap4J(): bool - { - return $this->coverageCrap4J !== null; - } - - /** - * @throws Exception - */ - public function coverageCrap4J(): string - { - if ($this->coverageCrap4J === null) { - throw new Exception; - } - - return $this->coverageCrap4J; - } - - public function hasCoverageHtml(): bool - { - return $this->coverageHtml !== null; - } - - /** - * @throws Exception - */ - public function coverageHtml(): string - { - if ($this->coverageHtml === null) { - throw new Exception; - } - - return $this->coverageHtml; - } - - public function hasCoveragePhp(): bool - { - return $this->coveragePhp !== null; - } - - /** - * @throws Exception - */ - public function coveragePhp(): string - { - if ($this->coveragePhp === null) { - throw new Exception; - } - - return $this->coveragePhp; - } - - public function hasCoverageText(): bool - { - return $this->coverageText !== null; - } - - /** - * @throws Exception - */ - public function coverageText(): string - { - if ($this->coverageText === null) { - throw new Exception; - } - - return $this->coverageText; - } - - public function hasCoverageTextShowUncoveredFiles(): bool - { - return $this->coverageTextShowUncoveredFiles !== null; - } - - /** - * @throws Exception - */ - public function coverageTextShowUncoveredFiles(): bool - { - if ($this->coverageTextShowUncoveredFiles === null) { - throw new Exception; - } - - return $this->coverageTextShowUncoveredFiles; - } - - public function hasCoverageTextShowOnlySummary(): bool - { - return $this->coverageTextShowOnlySummary !== null; - } - - /** - * @throws Exception - */ - public function coverageTextShowOnlySummary(): bool - { - if ($this->coverageTextShowOnlySummary === null) { - throw new Exception; - } - - return $this->coverageTextShowOnlySummary; - } - - public function hasCoverageXml(): bool - { - return $this->coverageXml !== null; - } - - /** - * @throws Exception - */ - public function coverageXml(): string - { - if ($this->coverageXml === null) { - throw new Exception; - } - - return $this->coverageXml; - } - - public function hasPathCoverage(): bool - { - return $this->pathCoverage !== null; - } - - /** - * @throws Exception - */ - public function pathCoverage(): bool - { - if ($this->pathCoverage === null) { - throw new Exception; - } - - return $this->pathCoverage; - } - - public function hasCoverageCacheDirectory(): bool - { - return $this->coverageCacheDirectory !== null; - } - - /** - * @throws Exception - */ - public function coverageCacheDirectory(): string - { - if ($this->coverageCacheDirectory === null) { - throw new Exception; - } - - return $this->coverageCacheDirectory; - } - - public function hasWarmCoverageCache(): bool - { - return $this->warmCoverageCache !== null; - } - - /** - * @throws Exception - */ - public function warmCoverageCache(): bool - { - if ($this->warmCoverageCache === null) { - throw new Exception; - } - - return $this->warmCoverageCache; - } - - public function hasDebug(): bool - { - return $this->debug !== null; - } - - /** - * @throws Exception - */ - public function debug(): bool - { - if ($this->debug === null) { - throw new Exception; - } - - return $this->debug; - } - - public function hasDefaultTimeLimit(): bool - { - return $this->defaultTimeLimit !== null; - } - - /** - * @throws Exception - */ - public function defaultTimeLimit(): int - { - if ($this->defaultTimeLimit === null) { - throw new Exception; - } - - return $this->defaultTimeLimit; - } - - public function hasDisableCodeCoverageIgnore(): bool - { - return $this->disableCodeCoverageIgnore !== null; - } - - /** - * @throws Exception - */ - public function disableCodeCoverageIgnore(): bool - { - if ($this->disableCodeCoverageIgnore === null) { - throw new Exception; - } - - return $this->disableCodeCoverageIgnore; - } - - public function hasDisallowTestOutput(): bool - { - return $this->disallowTestOutput !== null; - } - - /** - * @throws Exception - */ - public function disallowTestOutput(): bool - { - if ($this->disallowTestOutput === null) { - throw new Exception; - } - - return $this->disallowTestOutput; - } - - public function hasDisallowTodoAnnotatedTests(): bool - { - return $this->disallowTodoAnnotatedTests !== null; - } - - /** - * @throws Exception - */ - public function disallowTodoAnnotatedTests(): bool - { - if ($this->disallowTodoAnnotatedTests === null) { - throw new Exception; - } - - return $this->disallowTodoAnnotatedTests; - } - - public function hasEnforceTimeLimit(): bool - { - return $this->enforceTimeLimit !== null; - } - - /** - * @throws Exception - */ - public function enforceTimeLimit(): bool - { - if ($this->enforceTimeLimit === null) { - throw new Exception; - } - - return $this->enforceTimeLimit; - } - - public function hasExcludeGroups(): bool - { - return $this->excludeGroups !== null; - } - - /** - * @throws Exception - */ - public function excludeGroups(): array - { - if ($this->excludeGroups === null) { - throw new Exception; - } - - return $this->excludeGroups; - } - - public function hasExecutionOrder(): bool - { - return $this->executionOrder !== null; - } - - /** - * @throws Exception - */ - public function executionOrder(): int - { - if ($this->executionOrder === null) { - throw new Exception; - } - - return $this->executionOrder; - } - - public function hasExecutionOrderDefects(): bool - { - return $this->executionOrderDefects !== null; - } - - /** - * @throws Exception - */ - public function executionOrderDefects(): int - { - if ($this->executionOrderDefects === null) { - throw new Exception; - } - - return $this->executionOrderDefects; - } - - public function hasFailOnEmptyTestSuite(): bool - { - return $this->failOnEmptyTestSuite !== null; - } - - /** - * @throws Exception - */ - public function failOnEmptyTestSuite(): bool - { - if ($this->failOnEmptyTestSuite === null) { - throw new Exception; - } - - return $this->failOnEmptyTestSuite; - } - - public function hasFailOnIncomplete(): bool - { - return $this->failOnIncomplete !== null; - } - - /** - * @throws Exception - */ - public function failOnIncomplete(): bool - { - if ($this->failOnIncomplete === null) { - throw new Exception; - } - - return $this->failOnIncomplete; - } - - public function hasFailOnRisky(): bool - { - return $this->failOnRisky !== null; - } - - /** - * @throws Exception - */ - public function failOnRisky(): bool - { - if ($this->failOnRisky === null) { - throw new Exception; - } - - return $this->failOnRisky; - } - - public function hasFailOnSkipped(): bool - { - return $this->failOnSkipped !== null; - } - - /** - * @throws Exception - */ - public function failOnSkipped(): bool - { - if ($this->failOnSkipped === null) { - throw new Exception; - } - - return $this->failOnSkipped; - } - - public function hasFailOnWarning(): bool - { - return $this->failOnWarning !== null; - } - - /** - * @throws Exception - */ - public function failOnWarning(): bool - { - if ($this->failOnWarning === null) { - throw new Exception; - } - - return $this->failOnWarning; - } - - public function hasFilter(): bool - { - return $this->filter !== null; - } - - /** - * @throws Exception - */ - public function filter(): string - { - if ($this->filter === null) { - throw new Exception; - } - - return $this->filter; - } - - public function hasGenerateConfiguration(): bool - { - return $this->generateConfiguration !== null; - } - - /** - * @throws Exception - */ - public function generateConfiguration(): bool - { - if ($this->generateConfiguration === null) { - throw new Exception; - } - - return $this->generateConfiguration; - } - - public function hasMigrateConfiguration(): bool - { - return $this->migrateConfiguration !== null; - } - - /** - * @throws Exception - */ - public function migrateConfiguration(): bool - { - if ($this->migrateConfiguration === null) { - throw new Exception; - } - - return $this->migrateConfiguration; - } - - public function hasGroups(): bool - { - return $this->groups !== null; - } - - /** - * @throws Exception - */ - public function groups(): array - { - if ($this->groups === null) { - throw new Exception; - } - - return $this->groups; - } - - public function hasTestsCovering(): bool - { - return $this->testsCovering !== null; - } - - /** - * @throws Exception - */ - public function testsCovering(): array - { - if ($this->testsCovering === null) { - throw new Exception; - } - - return $this->testsCovering; - } - - public function hasTestsUsing(): bool - { - return $this->testsUsing !== null; - } - - /** - * @throws Exception - */ - public function testsUsing(): array - { - if ($this->testsUsing === null) { - throw new Exception; - } - - return $this->testsUsing; - } - - public function hasHelp(): bool - { - return $this->help !== null; - } - - /** - * @throws Exception - */ - public function help(): bool - { - if ($this->help === null) { - throw new Exception; - } - - return $this->help; - } - - public function hasIncludePath(): bool - { - return $this->includePath !== null; - } - - /** - * @throws Exception - */ - public function includePath(): string - { - if ($this->includePath === null) { - throw new Exception; - } - - return $this->includePath; - } - - public function hasIniSettings(): bool - { - return $this->iniSettings !== null; - } - - /** - * @throws Exception - */ - public function iniSettings(): array - { - if ($this->iniSettings === null) { - throw new Exception; - } - - return $this->iniSettings; - } - - public function hasJunitLogfile(): bool - { - return $this->junitLogfile !== null; - } - - /** - * @throws Exception - */ - public function junitLogfile(): string - { - if ($this->junitLogfile === null) { - throw new Exception; - } - - return $this->junitLogfile; - } - - public function hasListGroups(): bool - { - return $this->listGroups !== null; - } - - /** - * @throws Exception - */ - public function listGroups(): bool - { - if ($this->listGroups === null) { - throw new Exception; - } - - return $this->listGroups; - } - - public function hasListSuites(): bool - { - return $this->listSuites !== null; - } - - /** - * @throws Exception - */ - public function listSuites(): bool - { - if ($this->listSuites === null) { - throw new Exception; - } - - return $this->listSuites; - } - - public function hasListTests(): bool - { - return $this->listTests !== null; - } - - /** - * @throws Exception - */ - public function listTests(): bool - { - if ($this->listTests === null) { - throw new Exception; - } - - return $this->listTests; - } - - public function hasListTestsXml(): bool - { - return $this->listTestsXml !== null; - } - - /** - * @throws Exception - */ - public function listTestsXml(): string - { - if ($this->listTestsXml === null) { - throw new Exception; - } - - return $this->listTestsXml; - } - - public function hasLoader(): bool - { - return $this->loader !== null; - } - - /** - * @throws Exception - */ - public function loader(): string - { - if ($this->loader === null) { - throw new Exception; - } - - return $this->loader; - } - - public function hasNoCoverage(): bool - { - return $this->noCoverage !== null; - } - - /** - * @throws Exception - */ - public function noCoverage(): bool - { - if ($this->noCoverage === null) { - throw new Exception; - } - - return $this->noCoverage; - } - - public function hasNoExtensions(): bool - { - return $this->noExtensions !== null; - } - - /** - * @throws Exception - */ - public function noExtensions(): bool - { - if ($this->noExtensions === null) { - throw new Exception; - } - - return $this->noExtensions; - } - - public function hasExtensions(): bool - { - return $this->extensions !== null; - } - - /** - * @throws Exception - */ - public function extensions(): array - { - if ($this->extensions === null) { - throw new Exception; - } - - return $this->extensions; - } - - public function hasUnavailableExtensions(): bool - { - return $this->unavailableExtensions !== null; - } - - /** - * @throws Exception - */ - public function unavailableExtensions(): array - { - if ($this->unavailableExtensions === null) { - throw new Exception; - } - - return $this->unavailableExtensions; - } - - public function hasNoInteraction(): bool - { - return $this->noInteraction !== null; - } - - /** - * @throws Exception - */ - public function noInteraction(): bool - { - if ($this->noInteraction === null) { - throw new Exception; - } - - return $this->noInteraction; - } - - public function hasNoLogging(): bool - { - return $this->noLogging !== null; - } - - /** - * @throws Exception - */ - public function noLogging(): bool - { - if ($this->noLogging === null) { - throw new Exception; - } - - return $this->noLogging; - } - - public function hasPrinter(): bool - { - return $this->printer !== null; - } - - /** - * @throws Exception - */ - public function printer(): string - { - if ($this->printer === null) { - throw new Exception; - } - - return $this->printer; - } - - public function hasProcessIsolation(): bool - { - return $this->processIsolation !== null; - } - - /** - * @throws Exception - */ - public function processIsolation(): bool - { - if ($this->processIsolation === null) { - throw new Exception; - } - - return $this->processIsolation; - } - - public function hasRandomOrderSeed(): bool - { - return $this->randomOrderSeed !== null; - } - - /** - * @throws Exception - */ - public function randomOrderSeed(): int - { - if ($this->randomOrderSeed === null) { - throw new Exception; - } - - return $this->randomOrderSeed; - } - - public function hasRepeat(): bool - { - return $this->repeat !== null; - } - - /** - * @throws Exception - */ - public function repeat(): int - { - if ($this->repeat === null) { - throw new Exception; - } - - return $this->repeat; - } - - public function hasReportUselessTests(): bool - { - return $this->reportUselessTests !== null; - } - - /** - * @throws Exception - */ - public function reportUselessTests(): bool - { - if ($this->reportUselessTests === null) { - throw new Exception; - } - - return $this->reportUselessTests; - } - - public function hasResolveDependencies(): bool - { - return $this->resolveDependencies !== null; - } - - /** - * @throws Exception - */ - public function resolveDependencies(): bool - { - if ($this->resolveDependencies === null) { - throw new Exception; - } - - return $this->resolveDependencies; - } - - public function hasReverseList(): bool - { - return $this->reverseList !== null; - } - - /** - * @throws Exception - */ - public function reverseList(): bool - { - if ($this->reverseList === null) { - throw new Exception; - } - - return $this->reverseList; - } - - public function hasStderr(): bool - { - return $this->stderr !== null; - } - - /** - * @throws Exception - */ - public function stderr(): bool - { - if ($this->stderr === null) { - throw new Exception; - } - - return $this->stderr; - } - - public function hasStrictCoverage(): bool - { - return $this->strictCoverage !== null; - } - - /** - * @throws Exception - */ - public function strictCoverage(): bool - { - if ($this->strictCoverage === null) { - throw new Exception; - } - - return $this->strictCoverage; - } - - public function hasStopOnDefect(): bool - { - return $this->stopOnDefect !== null; - } - - /** - * @throws Exception - */ - public function stopOnDefect(): bool - { - if ($this->stopOnDefect === null) { - throw new Exception; - } - - return $this->stopOnDefect; - } - - public function hasStopOnError(): bool - { - return $this->stopOnError !== null; - } - - /** - * @throws Exception - */ - public function stopOnError(): bool - { - if ($this->stopOnError === null) { - throw new Exception; - } - - return $this->stopOnError; - } - - public function hasStopOnFailure(): bool - { - return $this->stopOnFailure !== null; - } - - /** - * @throws Exception - */ - public function stopOnFailure(): bool - { - if ($this->stopOnFailure === null) { - throw new Exception; - } - - return $this->stopOnFailure; - } - - public function hasStopOnIncomplete(): bool - { - return $this->stopOnIncomplete !== null; - } - - /** - * @throws Exception - */ - public function stopOnIncomplete(): bool - { - if ($this->stopOnIncomplete === null) { - throw new Exception; - } - - return $this->stopOnIncomplete; - } - - public function hasStopOnRisky(): bool - { - return $this->stopOnRisky !== null; - } - - /** - * @throws Exception - */ - public function stopOnRisky(): bool - { - if ($this->stopOnRisky === null) { - throw new Exception; - } - - return $this->stopOnRisky; - } - - public function hasStopOnSkipped(): bool - { - return $this->stopOnSkipped !== null; - } - - /** - * @throws Exception - */ - public function stopOnSkipped(): bool - { - if ($this->stopOnSkipped === null) { - throw new Exception; - } - - return $this->stopOnSkipped; - } - - public function hasStopOnWarning(): bool - { - return $this->stopOnWarning !== null; - } - - /** - * @throws Exception - */ - public function stopOnWarning(): bool - { - if ($this->stopOnWarning === null) { - throw new Exception; - } - - return $this->stopOnWarning; - } - - public function hasTeamcityLogfile(): bool - { - return $this->teamcityLogfile !== null; - } - - /** - * @throws Exception - */ - public function teamcityLogfile(): string - { - if ($this->teamcityLogfile === null) { - throw new Exception; - } - - return $this->teamcityLogfile; - } - - public function hasTestdoxExcludeGroups(): bool - { - return $this->testdoxExcludeGroups !== null; - } - - /** - * @throws Exception - */ - public function testdoxExcludeGroups(): array - { - if ($this->testdoxExcludeGroups === null) { - throw new Exception; - } - - return $this->testdoxExcludeGroups; - } - - public function hasTestdoxGroups(): bool - { - return $this->testdoxGroups !== null; - } - - /** - * @throws Exception - */ - public function testdoxGroups(): array - { - if ($this->testdoxGroups === null) { - throw new Exception; - } - - return $this->testdoxGroups; - } - - public function hasTestdoxHtmlFile(): bool - { - return $this->testdoxHtmlFile !== null; - } - - /** - * @throws Exception - */ - public function testdoxHtmlFile(): string - { - if ($this->testdoxHtmlFile === null) { - throw new Exception; - } - - return $this->testdoxHtmlFile; - } - - public function hasTestdoxTextFile(): bool - { - return $this->testdoxTextFile !== null; - } - - /** - * @throws Exception - */ - public function testdoxTextFile(): string - { - if ($this->testdoxTextFile === null) { - throw new Exception; - } - - return $this->testdoxTextFile; - } - - public function hasTestdoxXmlFile(): bool - { - return $this->testdoxXmlFile !== null; - } - - /** - * @throws Exception - */ - public function testdoxXmlFile(): string - { - if ($this->testdoxXmlFile === null) { - throw new Exception; - } - - return $this->testdoxXmlFile; - } - - public function hasTestSuffixes(): bool - { - return $this->testSuffixes !== null; - } - - /** - * @throws Exception - */ - public function testSuffixes(): array - { - if ($this->testSuffixes === null) { - throw new Exception; - } - - return $this->testSuffixes; - } - - public function hasTestSuite(): bool - { - return $this->testSuite !== null; - } - - /** - * @throws Exception - */ - public function testSuite(): string - { - if ($this->testSuite === null) { - throw new Exception; - } - - return $this->testSuite; - } - - public function unrecognizedOptions(): array - { - return $this->unrecognizedOptions; - } - - public function hasUnrecognizedOrderBy(): bool - { - return $this->unrecognizedOrderBy !== null; - } - - /** - * @throws Exception - */ - public function unrecognizedOrderBy(): string - { - if ($this->unrecognizedOrderBy === null) { - throw new Exception; - } - - return $this->unrecognizedOrderBy; - } - - public function hasUseDefaultConfiguration(): bool - { - return $this->useDefaultConfiguration !== null; - } - - /** - * @throws Exception - */ - public function useDefaultConfiguration(): bool - { - if ($this->useDefaultConfiguration === null) { - throw new Exception; - } - - return $this->useDefaultConfiguration; - } - - public function hasVerbose(): bool - { - return $this->verbose !== null; - } - - /** - * @throws Exception - */ - public function verbose(): bool - { - if ($this->verbose === null) { - throw new Exception; - } - - return $this->verbose; - } - - public function hasVersion(): bool - { - return $this->version !== null; - } - - /** - * @throws Exception - */ - public function version(): bool - { - if ($this->version === null) { - throw new Exception; - } - - return $this->version; - } - - public function hasXdebugFilterFile(): bool - { - return $this->xdebugFilterFile !== null; - } - - /** - * @throws Exception - */ - public function xdebugFilterFile(): string - { - if ($this->xdebugFilterFile === null) { - throw new Exception; - } - - return $this->xdebugFilterFile; - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/CliArguments/Exception.php b/app/vendor/phpunit/phpunit/src/TextUI/CliArguments/Exception.php deleted file mode 100644 index dd5536eaa..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/CliArguments/Exception.php +++ /dev/null @@ -1,19 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\CliArguments; - -use RuntimeException; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class Exception extends RuntimeException implements \PHPUnit\Exception -{ -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/CliArguments/Mapper.php b/app/vendor/phpunit/phpunit/src/TextUI/CliArguments/Mapper.php deleted file mode 100644 index 9ceb8ab47..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/CliArguments/Mapper.php +++ /dev/null @@ -1,365 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\CliArguments; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class Mapper -{ - /** - * @throws Exception - */ - public function mapToLegacyArray(Configuration $arguments): array - { - $result = [ - 'extensions' => [], - 'listGroups' => false, - 'listSuites' => false, - 'listTests' => false, - 'listTestsXml' => false, - 'loader' => null, - 'useDefaultConfiguration' => true, - 'loadedExtensions' => [], - 'unavailableExtensions' => [], - 'notLoadedExtensions' => [], - ]; - - if ($arguments->hasColors()) { - $result['colors'] = $arguments->colors(); - } - - if ($arguments->hasBootstrap()) { - $result['bootstrap'] = $arguments->bootstrap(); - } - - if ($arguments->hasCacheResult()) { - $result['cacheResult'] = $arguments->cacheResult(); - } - - if ($arguments->hasCacheResultFile()) { - $result['cacheResultFile'] = $arguments->cacheResultFile(); - } - - if ($arguments->hasColumns()) { - $result['columns'] = $arguments->columns(); - } - - if ($arguments->hasConfiguration()) { - $result['configuration'] = $arguments->configuration(); - } - - if ($arguments->hasCoverageCacheDirectory()) { - $result['coverageCacheDirectory'] = $arguments->coverageCacheDirectory(); - } - - if ($arguments->hasWarmCoverageCache()) { - $result['warmCoverageCache'] = $arguments->warmCoverageCache(); - } - - if ($arguments->hasCoverageClover()) { - $result['coverageClover'] = $arguments->coverageClover(); - } - - if ($arguments->hasCoverageCobertura()) { - $result['coverageCobertura'] = $arguments->coverageCobertura(); - } - - if ($arguments->hasCoverageCrap4J()) { - $result['coverageCrap4J'] = $arguments->coverageCrap4J(); - } - - if ($arguments->hasCoverageHtml()) { - $result['coverageHtml'] = $arguments->coverageHtml(); - } - - if ($arguments->hasCoveragePhp()) { - $result['coveragePHP'] = $arguments->coveragePhp(); - } - - if ($arguments->hasCoverageText()) { - $result['coverageText'] = $arguments->coverageText(); - } - - if ($arguments->hasCoverageTextShowUncoveredFiles()) { - $result['coverageTextShowUncoveredFiles'] = $arguments->hasCoverageTextShowUncoveredFiles(); - } - - if ($arguments->hasCoverageTextShowOnlySummary()) { - $result['coverageTextShowOnlySummary'] = $arguments->coverageTextShowOnlySummary(); - } - - if ($arguments->hasCoverageXml()) { - $result['coverageXml'] = $arguments->coverageXml(); - } - - if ($arguments->hasPathCoverage()) { - $result['pathCoverage'] = $arguments->pathCoverage(); - } - - if ($arguments->hasDebug()) { - $result['debug'] = $arguments->debug(); - } - - if ($arguments->hasHelp()) { - $result['help'] = $arguments->help(); - } - - if ($arguments->hasFilter()) { - $result['filter'] = $arguments->filter(); - } - - if ($arguments->hasTestSuite()) { - $result['testsuite'] = $arguments->testSuite(); - } - - if ($arguments->hasGroups()) { - $result['groups'] = $arguments->groups(); - } - - if ($arguments->hasExcludeGroups()) { - $result['excludeGroups'] = $arguments->excludeGroups(); - } - - if ($arguments->hasTestsCovering()) { - $result['testsCovering'] = $arguments->testsCovering(); - } - - if ($arguments->hasTestsUsing()) { - $result['testsUsing'] = $arguments->testsUsing(); - } - - if ($arguments->hasTestSuffixes()) { - $result['testSuffixes'] = $arguments->testSuffixes(); - } - - if ($arguments->hasIncludePath()) { - $result['includePath'] = $arguments->includePath(); - } - - if ($arguments->hasListGroups()) { - $result['listGroups'] = $arguments->listGroups(); - } - - if ($arguments->hasListSuites()) { - $result['listSuites'] = $arguments->listSuites(); - } - - if ($arguments->hasListTests()) { - $result['listTests'] = $arguments->listTests(); - } - - if ($arguments->hasListTestsXml()) { - $result['listTestsXml'] = $arguments->listTestsXml(); - } - - if ($arguments->hasPrinter()) { - $result['printer'] = $arguments->printer(); - } - - if ($arguments->hasLoader()) { - $result['loader'] = $arguments->loader(); - } - - if ($arguments->hasJunitLogfile()) { - $result['junitLogfile'] = $arguments->junitLogfile(); - } - - if ($arguments->hasTeamcityLogfile()) { - $result['teamcityLogfile'] = $arguments->teamcityLogfile(); - } - - if ($arguments->hasExecutionOrder()) { - $result['executionOrder'] = $arguments->executionOrder(); - } - - if ($arguments->hasExecutionOrderDefects()) { - $result['executionOrderDefects'] = $arguments->executionOrderDefects(); - } - - if ($arguments->hasExtensions()) { - $result['extensions'] = $arguments->extensions(); - } - - if ($arguments->hasUnavailableExtensions()) { - $result['unavailableExtensions'] = $arguments->unavailableExtensions(); - } - - if ($arguments->hasResolveDependencies()) { - $result['resolveDependencies'] = $arguments->resolveDependencies(); - } - - if ($arguments->hasProcessIsolation()) { - $result['processIsolation'] = $arguments->processIsolation(); - } - - if ($arguments->hasRepeat()) { - $result['repeat'] = $arguments->repeat(); - } - - if ($arguments->hasStderr()) { - $result['stderr'] = $arguments->stderr(); - } - - if ($arguments->hasStopOnDefect()) { - $result['stopOnDefect'] = $arguments->stopOnDefect(); - } - - if ($arguments->hasStopOnError()) { - $result['stopOnError'] = $arguments->stopOnError(); - } - - if ($arguments->hasStopOnFailure()) { - $result['stopOnFailure'] = $arguments->stopOnFailure(); - } - - if ($arguments->hasStopOnWarning()) { - $result['stopOnWarning'] = $arguments->stopOnWarning(); - } - - if ($arguments->hasStopOnIncomplete()) { - $result['stopOnIncomplete'] = $arguments->stopOnIncomplete(); - } - - if ($arguments->hasStopOnRisky()) { - $result['stopOnRisky'] = $arguments->stopOnRisky(); - } - - if ($arguments->hasStopOnSkipped()) { - $result['stopOnSkipped'] = $arguments->stopOnSkipped(); - } - - if ($arguments->hasFailOnEmptyTestSuite()) { - $result['failOnEmptyTestSuite'] = $arguments->failOnEmptyTestSuite(); - } - - if ($arguments->hasFailOnIncomplete()) { - $result['failOnIncomplete'] = $arguments->failOnIncomplete(); - } - - if ($arguments->hasFailOnRisky()) { - $result['failOnRisky'] = $arguments->failOnRisky(); - } - - if ($arguments->hasFailOnSkipped()) { - $result['failOnSkipped'] = $arguments->failOnSkipped(); - } - - if ($arguments->hasFailOnWarning()) { - $result['failOnWarning'] = $arguments->failOnWarning(); - } - - if ($arguments->hasTestdoxGroups()) { - $result['testdoxGroups'] = $arguments->testdoxGroups(); - } - - if ($arguments->hasTestdoxExcludeGroups()) { - $result['testdoxExcludeGroups'] = $arguments->testdoxExcludeGroups(); - } - - if ($arguments->hasTestdoxHtmlFile()) { - $result['testdoxHTMLFile'] = $arguments->testdoxHtmlFile(); - } - - if ($arguments->hasTestdoxTextFile()) { - $result['testdoxTextFile'] = $arguments->testdoxTextFile(); - } - - if ($arguments->hasTestdoxXmlFile()) { - $result['testdoxXMLFile'] = $arguments->testdoxXmlFile(); - } - - if ($arguments->hasUseDefaultConfiguration()) { - $result['useDefaultConfiguration'] = $arguments->useDefaultConfiguration(); - } - - if ($arguments->hasNoExtensions()) { - $result['noExtensions'] = $arguments->noExtensions(); - } - - if ($arguments->hasNoCoverage()) { - $result['noCoverage'] = $arguments->noCoverage(); - } - - if ($arguments->hasNoLogging()) { - $result['noLogging'] = $arguments->noLogging(); - } - - if ($arguments->hasNoInteraction()) { - $result['noInteraction'] = $arguments->noInteraction(); - } - - if ($arguments->hasBackupGlobals()) { - $result['backupGlobals'] = $arguments->backupGlobals(); - } - - if ($arguments->hasBackupStaticAttributes()) { - $result['backupStaticAttributes'] = $arguments->backupStaticAttributes(); - } - - if ($arguments->hasVerbose()) { - $result['verbose'] = $arguments->verbose(); - } - - if ($arguments->hasReportUselessTests()) { - $result['reportUselessTests'] = $arguments->reportUselessTests(); - } - - if ($arguments->hasStrictCoverage()) { - $result['strictCoverage'] = $arguments->strictCoverage(); - } - - if ($arguments->hasDisableCodeCoverageIgnore()) { - $result['disableCodeCoverageIgnore'] = $arguments->disableCodeCoverageIgnore(); - } - - if ($arguments->hasBeStrictAboutChangesToGlobalState()) { - $result['beStrictAboutChangesToGlobalState'] = $arguments->beStrictAboutChangesToGlobalState(); - } - - if ($arguments->hasDisallowTestOutput()) { - $result['disallowTestOutput'] = $arguments->disallowTestOutput(); - } - - if ($arguments->hasBeStrictAboutResourceUsageDuringSmallTests()) { - $result['beStrictAboutResourceUsageDuringSmallTests'] = $arguments->beStrictAboutResourceUsageDuringSmallTests(); - } - - if ($arguments->hasDefaultTimeLimit()) { - $result['defaultTimeLimit'] = $arguments->defaultTimeLimit(); - } - - if ($arguments->hasEnforceTimeLimit()) { - $result['enforceTimeLimit'] = $arguments->enforceTimeLimit(); - } - - if ($arguments->hasDisallowTodoAnnotatedTests()) { - $result['disallowTodoAnnotatedTests'] = $arguments->disallowTodoAnnotatedTests(); - } - - if ($arguments->hasReverseList()) { - $result['reverseList'] = $arguments->reverseList(); - } - - if ($arguments->hasCoverageFilter()) { - $result['coverageFilter'] = $arguments->coverageFilter(); - } - - if ($arguments->hasRandomOrderSeed()) { - $result['randomOrderSeed'] = $arguments->randomOrderSeed(); - } - - if ($arguments->hasXdebugFilterFile()) { - $result['xdebugFilterFile'] = $arguments->xdebugFilterFile(); - } - - return $result; - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Command.php b/app/vendor/phpunit/phpunit/src/TextUI/Command.php deleted file mode 100644 index 7b963f0a7..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/Command.php +++ /dev/null @@ -1,1042 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI; - -use const PATH_SEPARATOR; -use const PHP_EOL; -use const STDIN; -use function array_keys; -use function assert; -use function class_exists; -use function copy; -use function explode; -use function extension_loaded; -use function fgets; -use function file_get_contents; -use function file_put_contents; -use function get_class; -use function getcwd; -use function ini_get; -use function ini_set; -use function is_array; -use function is_callable; -use function is_dir; -use function is_file; -use function is_string; -use function printf; -use function realpath; -use function sort; -use function sprintf; -use function stream_resolve_include_path; -use function strpos; -use function trim; -use function version_compare; -use PHPUnit\Framework\TestSuite; -use PHPUnit\Runner\Extension\PharLoader; -use PHPUnit\Runner\StandardTestSuiteLoader; -use PHPUnit\Runner\TestSuiteLoader; -use PHPUnit\Runner\Version; -use PHPUnit\TextUI\CliArguments\Builder; -use PHPUnit\TextUI\CliArguments\Configuration; -use PHPUnit\TextUI\CliArguments\Exception as ArgumentsException; -use PHPUnit\TextUI\CliArguments\Mapper; -use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\FilterMapper; -use PHPUnit\TextUI\XmlConfiguration\Generator; -use PHPUnit\TextUI\XmlConfiguration\Loader; -use PHPUnit\TextUI\XmlConfiguration\Migrator; -use PHPUnit\TextUI\XmlConfiguration\PhpHandler; -use PHPUnit\Util\FileLoader; -use PHPUnit\Util\Filesystem; -use PHPUnit\Util\Printer; -use PHPUnit\Util\TextTestListRenderer; -use PHPUnit\Util\Xml\SchemaDetector; -use PHPUnit\Util\XmlTestListRenderer; -use ReflectionClass; -use SebastianBergmann\CodeCoverage\Filter; -use SebastianBergmann\CodeCoverage\StaticAnalysis\CacheWarmer; -use SebastianBergmann\RecursionContext\InvalidArgumentException; -use SebastianBergmann\Timer\Timer; -use Throwable; - -/** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - */ -class Command -{ - /** - * @var array - */ - protected $arguments = []; - - /** - * @var array - */ - protected $longOptions = []; - - /** - * @var bool - */ - private $versionStringPrinted = false; - - /** - * @psalm-var list - */ - private $warnings = []; - - /** - * @throws Exception - */ - public static function main(bool $exit = true): int - { - try { - return (new static)->run($_SERVER['argv'], $exit); - } catch (Throwable $t) { - throw new RuntimeException( - $t->getMessage(), - (int) $t->getCode(), - $t, - ); - } - } - - /** - * @throws Exception - */ - public function run(array $argv, bool $exit = true): int - { - $this->handleArguments($argv); - - $runner = $this->createRunner(); - - if ($this->arguments['test'] instanceof TestSuite) { - $suite = $this->arguments['test']; - } else { - $suite = $runner->getTest( - $this->arguments['test'], - $this->arguments['testSuffixes'], - ); - } - - if ($this->arguments['listGroups']) { - return $this->handleListGroups($suite, $exit); - } - - if ($this->arguments['listSuites']) { - return $this->handleListSuites($exit); - } - - if ($this->arguments['listTests']) { - return $this->handleListTests($suite, $exit); - } - - if ($this->arguments['listTestsXml']) { - return $this->handleListTestsXml($suite, $this->arguments['listTestsXml'], $exit); - } - - unset($this->arguments['test'], $this->arguments['testFile']); - - try { - $result = $runner->run($suite, $this->arguments, $this->warnings, $exit); - } catch (Throwable $t) { - print $t->getMessage() . PHP_EOL; - } - - $return = TestRunner::FAILURE_EXIT; - - if (isset($result) && $result->wasSuccessful()) { - $return = TestRunner::SUCCESS_EXIT; - } elseif (!isset($result) || $result->errorCount() > 0) { - $return = TestRunner::EXCEPTION_EXIT; - } - - if ($exit) { - exit($return); - } - - return $return; - } - - /** - * Create a TestRunner, override in subclasses. - */ - protected function createRunner(): TestRunner - { - return new TestRunner($this->arguments['loader']); - } - - /** - * Handles the command-line arguments. - * - * A child class of PHPUnit\TextUI\Command can hook into the argument - * parsing by adding the switch(es) to the $longOptions array and point to a - * callback method that handles the switch(es) in the child class like this - * - * - * longOptions['my-switch'] = 'myHandler'; - * // my-secondswitch will accept a value - note the equals sign - * $this->longOptions['my-secondswitch='] = 'myOtherHandler'; - * } - * - * // --my-switch -> myHandler() - * protected function myHandler() - * { - * } - * - * // --my-secondswitch foo -> myOtherHandler('foo') - * protected function myOtherHandler ($value) - * { - * } - * - * // You will also need this - the static keyword in the - * // PHPUnit\TextUI\Command will mean that it'll be - * // PHPUnit\TextUI\Command that gets instantiated, - * // not MyCommand - * public static function main($exit = true) - * { - * $command = new static; - * - * return $command->run($_SERVER['argv'], $exit); - * } - * - * } - * - * - * @throws Exception - */ - protected function handleArguments(array $argv): void - { - try { - $arguments = (new Builder)->fromParameters($argv, array_keys($this->longOptions)); - } catch (ArgumentsException $e) { - $this->exitWithErrorMessage($e->getMessage()); - } - - assert(isset($arguments) && $arguments instanceof Configuration); - - if ($arguments->hasGenerateConfiguration() && $arguments->generateConfiguration()) { - $this->generateConfiguration(); - } - - if ($arguments->hasAtLeastVersion()) { - if (version_compare(Version::id(), $arguments->atLeastVersion(), '>=')) { - exit(TestRunner::SUCCESS_EXIT); - } - - exit(TestRunner::FAILURE_EXIT); - } - - if ($arguments->hasVersion() && $arguments->version()) { - $this->printVersionString(); - - exit(TestRunner::SUCCESS_EXIT); - } - - if ($arguments->hasCheckVersion() && $arguments->checkVersion()) { - $this->handleVersionCheck(); - } - - if ($arguments->hasHelp()) { - $this->showHelp(); - - exit(TestRunner::SUCCESS_EXIT); - } - - if ($arguments->hasUnrecognizedOrderBy()) { - $this->exitWithErrorMessage( - sprintf( - 'unrecognized --order-by option: %s', - $arguments->unrecognizedOrderBy(), - ), - ); - } - - if ($arguments->hasIniSettings()) { - foreach ($arguments->iniSettings() as $name => $value) { - ini_set($name, $value); - } - } - - if ($arguments->hasIncludePath()) { - ini_set( - 'include_path', - $arguments->includePath() . PATH_SEPARATOR . ini_get('include_path'), - ); - } - - $this->arguments = (new Mapper)->mapToLegacyArray($arguments); - - $this->handleCustomOptions($arguments->unrecognizedOptions()); - $this->handleCustomTestSuite(); - - if (!isset($this->arguments['testSuffixes'])) { - $this->arguments['testSuffixes'] = ['Test.php', '.phpt']; - } - - if (!isset($this->arguments['test']) && $arguments->hasArgument()) { - $this->arguments['test'] = realpath($arguments->argument()); - - if ($this->arguments['test'] === false) { - $this->exitWithErrorMessage( - sprintf( - 'Cannot open file "%s".', - $arguments->argument(), - ), - ); - } - } - - if ($this->arguments['loader'] !== null) { - $this->arguments['loader'] = $this->handleLoader($this->arguments['loader']); - } - - if (isset($this->arguments['configuration'])) { - if (is_dir($this->arguments['configuration'])) { - $candidate = $this->configurationFileInDirectory($this->arguments['configuration']); - - if ($candidate !== null) { - $this->arguments['configuration'] = $candidate; - } - } - } elseif ($this->arguments['useDefaultConfiguration']) { - $candidate = $this->configurationFileInDirectory(getcwd()); - - if ($candidate !== null) { - $this->arguments['configuration'] = $candidate; - } - } - - if ($arguments->hasMigrateConfiguration() && $arguments->migrateConfiguration()) { - if (!isset($this->arguments['configuration'])) { - print 'No configuration file found to migrate.' . PHP_EOL; - - exit(TestRunner::EXCEPTION_EXIT); - } - - $this->migrateConfiguration(realpath($this->arguments['configuration'])); - } - - if (isset($this->arguments['configuration'])) { - try { - $this->arguments['configurationObject'] = (new Loader)->load($this->arguments['configuration']); - } catch (Throwable $e) { - print $e->getMessage() . PHP_EOL; - - exit(TestRunner::FAILURE_EXIT); - } - - $phpunitConfiguration = $this->arguments['configurationObject']->phpunit(); - - (new PhpHandler)->handle($this->arguments['configurationObject']->php()); - - if (isset($this->arguments['bootstrap'])) { - $this->handleBootstrap($this->arguments['bootstrap']); - } elseif ($phpunitConfiguration->hasBootstrap()) { - $this->handleBootstrap($phpunitConfiguration->bootstrap()); - } - - if (!isset($this->arguments['stderr'])) { - $this->arguments['stderr'] = $phpunitConfiguration->stderr(); - } - - if (!isset($this->arguments['noExtensions']) && $phpunitConfiguration->hasExtensionsDirectory() && extension_loaded('phar')) { - $result = (new PharLoader)->loadPharExtensionsInDirectory($phpunitConfiguration->extensionsDirectory()); - - $this->arguments['loadedExtensions'] = $result['loadedExtensions']; - $this->arguments['notLoadedExtensions'] = $result['notLoadedExtensions']; - - unset($result); - } - - if (!isset($this->arguments['columns'])) { - $this->arguments['columns'] = $phpunitConfiguration->columns(); - } - - if (!isset($this->arguments['printer']) && $phpunitConfiguration->hasPrinterClass()) { - $file = $phpunitConfiguration->hasPrinterFile() ? $phpunitConfiguration->printerFile() : ''; - - $this->arguments['printer'] = $this->handlePrinter( - $phpunitConfiguration->printerClass(), - $file, - ); - } - - if ($phpunitConfiguration->hasTestSuiteLoaderClass()) { - $file = $phpunitConfiguration->hasTestSuiteLoaderFile() ? $phpunitConfiguration->testSuiteLoaderFile() : ''; - - $this->arguments['loader'] = $this->handleLoader( - $phpunitConfiguration->testSuiteLoaderClass(), - $file, - ); - } - - if (!isset($this->arguments['testsuite']) && $phpunitConfiguration->hasDefaultTestSuite()) { - $this->arguments['testsuite'] = $phpunitConfiguration->defaultTestSuite(); - } - - if (!isset($this->arguments['test'])) { - try { - $this->arguments['test'] = (new TestSuiteMapper)->map( - $this->arguments['configurationObject']->testSuite(), - $this->arguments['testsuite'] ?? '', - ); - } catch (Exception $e) { - $this->printVersionString(); - - print $e->getMessage() . PHP_EOL; - - exit(TestRunner::EXCEPTION_EXIT); - } - } - } elseif (isset($this->arguments['bootstrap'])) { - $this->handleBootstrap($this->arguments['bootstrap']); - } - - if (isset($this->arguments['printer']) && is_string($this->arguments['printer'])) { - $this->arguments['printer'] = $this->handlePrinter($this->arguments['printer']); - } - - if (isset($this->arguments['configurationObject'], $this->arguments['warmCoverageCache'])) { - $this->handleWarmCoverageCache($this->arguments['configurationObject']); - } - - if (!isset($this->arguments['test'])) { - $this->showHelp(); - - exit(TestRunner::EXCEPTION_EXIT); - } - } - - /** - * Handles the loading of the PHPUnit\Runner\TestSuiteLoader implementation. - * - * @deprecated see https://github.com/sebastianbergmann/phpunit/issues/4039 - */ - protected function handleLoader(string $loaderClass, string $loaderFile = ''): ?TestSuiteLoader - { - $this->warnings[] = 'Using a custom test suite loader is deprecated'; - - if (!class_exists($loaderClass, false)) { - if ($loaderFile == '') { - $loaderFile = Filesystem::classNameToFilename( - $loaderClass, - ); - } - - $loaderFile = stream_resolve_include_path($loaderFile); - - if ($loaderFile) { - /** - * @noinspection PhpIncludeInspection - * - * @psalm-suppress UnresolvableInclude - */ - require $loaderFile; - } - } - - if (class_exists($loaderClass, false)) { - try { - $class = new ReflectionClass($loaderClass); - // @codeCoverageIgnoreStart - } catch (\ReflectionException $e) { - throw new ReflectionException( - $e->getMessage(), - $e->getCode(), - $e, - ); - } - // @codeCoverageIgnoreEnd - - if ($class->implementsInterface(TestSuiteLoader::class) && $class->isInstantiable()) { - $object = $class->newInstance(); - - assert($object instanceof TestSuiteLoader); - - return $object; - } - } - - if ($loaderClass == StandardTestSuiteLoader::class) { - return null; - } - - $this->exitWithErrorMessage( - sprintf( - 'Could not use "%s" as loader.', - $loaderClass, - ), - ); - - return null; - } - - /** - * Handles the loading of the PHPUnit\Util\Printer implementation. - * - * @return null|Printer|string - */ - protected function handlePrinter(string $printerClass, string $printerFile = '') - { - if (!class_exists($printerClass, false)) { - if ($printerFile === '') { - $printerFile = Filesystem::classNameToFilename( - $printerClass, - ); - } - - $printerFile = stream_resolve_include_path($printerFile); - - if ($printerFile) { - /** - * @noinspection PhpIncludeInspection - * - * @psalm-suppress UnresolvableInclude - */ - require $printerFile; - } - } - - if (!class_exists($printerClass)) { - $this->exitWithErrorMessage( - sprintf( - 'Could not use "%s" as printer: class does not exist', - $printerClass, - ), - ); - } - - try { - $class = new ReflectionClass($printerClass); - // @codeCoverageIgnoreStart - } catch (\ReflectionException $e) { - throw new ReflectionException( - $e->getMessage(), - $e->getCode(), - $e, - ); - // @codeCoverageIgnoreEnd - } - - if (!$class->implementsInterface(ResultPrinter::class)) { - $this->exitWithErrorMessage( - sprintf( - 'Could not use "%s" as printer: class does not implement %s', - $printerClass, - ResultPrinter::class, - ), - ); - } - - if (!$class->isInstantiable()) { - $this->exitWithErrorMessage( - sprintf( - 'Could not use "%s" as printer: class cannot be instantiated', - $printerClass, - ), - ); - } - - if ($class->isSubclassOf(ResultPrinter::class)) { - return $printerClass; - } - - $outputStream = isset($this->arguments['stderr']) ? 'php://stderr' : null; - - return $class->newInstance($outputStream); - } - - /** - * Loads a bootstrap file. - */ - protected function handleBootstrap(string $filename): void - { - try { - FileLoader::checkAndLoad($filename); - } catch (Throwable $t) { - if ($t instanceof \PHPUnit\Exception) { - $this->exitWithErrorMessage($t->getMessage()); - } - - $message = sprintf( - 'Error in bootstrap script: %s:%s%s%s%s', - get_class($t), - PHP_EOL, - $t->getMessage(), - PHP_EOL, - $t->getTraceAsString(), - ); - - while ($t = $t->getPrevious()) { - $message .= sprintf( - '%s%sPrevious error: %s:%s%s%s%s', - PHP_EOL, - PHP_EOL, - get_class($t), - PHP_EOL, - $t->getMessage(), - PHP_EOL, - $t->getTraceAsString(), - ); - } - - $this->exitWithErrorMessage($message); - } - } - - protected function handleVersionCheck(): void - { - $this->printVersionString(); - - $latestVersion = file_get_contents('https://phar.phpunit.de/latest-version-of/phpunit'); - $latestCompatibleVersion = file_get_contents('https://phar.phpunit.de/latest-version-of/phpunit-' . explode('.', Version::series())[0]); - - $notLatest = version_compare($latestVersion, Version::id(), '>'); - $notLatestCompatible = version_compare($latestCompatibleVersion, Version::id(), '>'); - - if ($notLatest || $notLatestCompatible) { - print 'You are not using the latest version of PHPUnit.' . PHP_EOL; - } else { - print 'You are using the latest version of PHPUnit.' . PHP_EOL; - } - - if ($notLatestCompatible) { - printf( - 'The latest version compatible with PHPUnit %s is PHPUnit %s.' . PHP_EOL, - Version::id(), - $latestCompatibleVersion, - ); - } - - if ($notLatest) { - printf( - 'The latest version is PHPUnit %s.' . PHP_EOL, - $latestVersion, - ); - } - - exit(TestRunner::SUCCESS_EXIT); - } - - /** - * Show the help message. - */ - protected function showHelp(): void - { - $this->printVersionString(); - (new Help)->writeToConsole(); - } - - /** - * Custom callback for test suite discovery. - */ - protected function handleCustomTestSuite(): void - { - } - - private function printVersionString(): void - { - if ($this->versionStringPrinted) { - return; - } - - print Version::getVersionString() . PHP_EOL . PHP_EOL; - - $this->versionStringPrinted = true; - } - - private function exitWithErrorMessage(string $message): void - { - $this->printVersionString(); - - print $message . PHP_EOL; - - exit(TestRunner::FAILURE_EXIT); - } - - private function handleListGroups(TestSuite $suite, bool $exit): int - { - $this->printVersionString(); - - $this->warnAboutConflictingOptions( - 'listGroups', - [ - 'filter', - 'groups', - 'excludeGroups', - 'testsuite', - ], - ); - - print 'Available test group(s):' . PHP_EOL; - - $groups = $suite->getGroups(); - sort($groups); - - foreach ($groups as $group) { - if (strpos($group, '__phpunit_') === 0) { - continue; - } - - printf( - ' - %s' . PHP_EOL, - $group, - ); - } - - if ($exit) { - exit(TestRunner::SUCCESS_EXIT); - } - - return TestRunner::SUCCESS_EXIT; - } - - /** - * @throws \PHPUnit\Framework\Exception - * @throws XmlConfiguration\Exception - */ - private function handleListSuites(bool $exit): int - { - $this->printVersionString(); - - $this->warnAboutConflictingOptions( - 'listSuites', - [ - 'filter', - 'groups', - 'excludeGroups', - 'testsuite', - ], - ); - - print 'Available test suite(s):' . PHP_EOL; - - foreach ($this->arguments['configurationObject']->testSuite() as $testSuite) { - printf( - ' - %s' . PHP_EOL, - $testSuite->name(), - ); - } - - if ($exit) { - exit(TestRunner::SUCCESS_EXIT); - } - - return TestRunner::SUCCESS_EXIT; - } - - /** - * @throws InvalidArgumentException - */ - private function handleListTests(TestSuite $suite, bool $exit): int - { - $this->printVersionString(); - - $this->warnAboutConflictingOptions( - 'listTests', - [ - 'filter', - 'groups', - 'excludeGroups', - ], - ); - - $renderer = new TextTestListRenderer; - - print $renderer->render($suite); - - if ($exit) { - exit(TestRunner::SUCCESS_EXIT); - } - - return TestRunner::SUCCESS_EXIT; - } - - /** - * @throws InvalidArgumentException - */ - private function handleListTestsXml(TestSuite $suite, string $target, bool $exit): int - { - $this->printVersionString(); - - $this->warnAboutConflictingOptions( - 'listTestsXml', - [ - 'filter', - 'groups', - 'excludeGroups', - ], - ); - - $renderer = new XmlTestListRenderer; - - file_put_contents($target, $renderer->render($suite)); - - printf( - 'Wrote list of tests that would have been run to %s' . PHP_EOL, - $target, - ); - - if ($exit) { - exit(TestRunner::SUCCESS_EXIT); - } - - return TestRunner::SUCCESS_EXIT; - } - - private function generateConfiguration(): void - { - $this->printVersionString(); - - print 'Generating phpunit.xml in ' . getcwd() . PHP_EOL . PHP_EOL; - print 'Bootstrap script (relative to path shown above; default: vendor/autoload.php): '; - - $bootstrapScript = trim(fgets(STDIN)); - - print 'Tests directory (relative to path shown above; default: tests): '; - - $testsDirectory = trim(fgets(STDIN)); - - print 'Source directory (relative to path shown above; default: src): '; - - $src = trim(fgets(STDIN)); - - print 'Cache directory (relative to path shown above; default: .phpunit.cache): '; - - $cacheDirectory = trim(fgets(STDIN)); - - if ($bootstrapScript === '') { - $bootstrapScript = 'vendor/autoload.php'; - } - - if ($testsDirectory === '') { - $testsDirectory = 'tests'; - } - - if ($src === '') { - $src = 'src'; - } - - if ($cacheDirectory === '') { - $cacheDirectory = '.phpunit.cache'; - } - - $generator = new Generator; - - file_put_contents( - 'phpunit.xml', - $generator->generateDefaultConfiguration( - Version::series(), - $bootstrapScript, - $testsDirectory, - $src, - $cacheDirectory, - ), - ); - - print PHP_EOL . 'Generated phpunit.xml in ' . getcwd() . '.' . PHP_EOL; - print 'Make sure to exclude the ' . $cacheDirectory . ' directory from version control.' . PHP_EOL; - - exit(TestRunner::SUCCESS_EXIT); - } - - private function migrateConfiguration(string $filename): void - { - $this->printVersionString(); - - $result = (new SchemaDetector)->detect($filename); - - if (!$result->detected()) { - print $filename . ' does not validate against any known schema.' . PHP_EOL; - - exit(TestRunner::EXCEPTION_EXIT); - } - - /** @psalm-suppress MissingThrowsDocblock */ - if ($result->version() === Version::series()) { - print $filename . ' does not need to be migrated.' . PHP_EOL; - - exit(TestRunner::EXCEPTION_EXIT); - } - - copy($filename, $filename . '.bak'); - - print 'Created backup: ' . $filename . '.bak' . PHP_EOL; - - try { - file_put_contents( - $filename, - (new Migrator)->migrate($filename), - ); - - print 'Migrated configuration: ' . $filename . PHP_EOL; - } catch (Throwable $t) { - print 'Migration failed: ' . $t->getMessage() . PHP_EOL; - - exit(TestRunner::EXCEPTION_EXIT); - } - - exit(TestRunner::SUCCESS_EXIT); - } - - private function handleCustomOptions(array $unrecognizedOptions): void - { - foreach ($unrecognizedOptions as $name => $value) { - if (isset($this->longOptions[$name])) { - $handler = $this->longOptions[$name]; - } - - $name .= '='; - - if (isset($this->longOptions[$name])) { - $handler = $this->longOptions[$name]; - } - - if (isset($handler) && is_callable([$this, $handler])) { - $this->{$handler}($value); - - unset($handler); - } - } - } - - private function handleWarmCoverageCache(XmlConfiguration\Configuration $configuration): void - { - $this->printVersionString(); - - if (isset($this->arguments['coverageCacheDirectory'])) { - $cacheDirectory = $this->arguments['coverageCacheDirectory']; - } elseif ($configuration->codeCoverage()->hasCacheDirectory()) { - $cacheDirectory = $configuration->codeCoverage()->cacheDirectory()->path(); - } else { - print 'Cache for static analysis has not been configured' . PHP_EOL; - - exit(TestRunner::EXCEPTION_EXIT); - } - - $filter = new Filter; - - if ($configuration->codeCoverage()->hasNonEmptyListOfFilesToBeIncludedInCodeCoverageReport()) { - (new FilterMapper)->map( - $filter, - $configuration->codeCoverage(), - ); - } elseif (isset($this->arguments['coverageFilter'])) { - if (!is_array($this->arguments['coverageFilter'])) { - $coverageFilterDirectories = [$this->arguments['coverageFilter']]; - } else { - $coverageFilterDirectories = $this->arguments['coverageFilter']; - } - - foreach ($coverageFilterDirectories as $coverageFilterDirectory) { - $filter->includeDirectory($coverageFilterDirectory); - } - } else { - print 'Filter for code coverage has not been configured' . PHP_EOL; - - exit(TestRunner::EXCEPTION_EXIT); - } - - $timer = new Timer; - $timer->start(); - - print 'Warming cache for static analysis ... '; - - (new CacheWarmer)->warmCache( - $cacheDirectory, - !$configuration->codeCoverage()->disableCodeCoverageIgnore(), - $configuration->codeCoverage()->ignoreDeprecatedCodeUnits(), - $filter, - ); - - print 'done [' . $timer->stop()->asString() . ']' . PHP_EOL; - - exit(TestRunner::SUCCESS_EXIT); - } - - private function configurationFileInDirectory(string $directory): ?string - { - $candidates = [ - $directory . '/phpunit.xml', - $directory . '/phpunit.xml.dist', - ]; - - foreach ($candidates as $candidate) { - if (is_file($candidate)) { - return realpath($candidate); - } - } - - return null; - } - - /** - * @psalm-param "listGroups"|"listSuites"|"listTests"|"listTestsXml"|"filter"|"groups"|"excludeGroups"|"testsuite" $key - * @psalm-param list<"listGroups"|"listSuites"|"listTests"|"listTestsXml"|"filter"|"groups"|"excludeGroups"|"testsuite"> $keys - */ - private function warnAboutConflictingOptions(string $key, array $keys): void - { - $warningPrinted = false; - - foreach ($keys as $_key) { - if (!empty($this->arguments[$_key])) { - printf( - 'The %s and %s options cannot be combined, %s is ignored' . PHP_EOL, - $this->mapKeyToOptionForWarning($_key), - $this->mapKeyToOptionForWarning($key), - $this->mapKeyToOptionForWarning($_key), - ); - - $warningPrinted = true; - } - } - - if ($warningPrinted) { - print PHP_EOL; - } - } - - /** - * @psalm-param "listGroups"|"listSuites"|"listTests"|"listTestsXml"|"filter"|"groups"|"excludeGroups"|"testsuite" $key - */ - private function mapKeyToOptionForWarning(string $key): string - { - switch ($key) { - case 'listGroups': - return '--list-groups'; - - case 'listSuites': - return '--list-suites'; - - case 'listTests': - return '--list-tests'; - - case 'listTestsXml': - return '--list-tests-xml'; - - case 'filter': - return '--filter'; - - case 'groups': - return '--group'; - - case 'excludeGroups': - return '--exclude-group'; - - case 'testsuite': - return '--testsuite'; - } - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Command/Command.php b/app/vendor/phpunit/phpunit/src/TextUI/Command/Command.php new file mode 100644 index 000000000..4194551e4 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Command/Command.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 PHPUnit\TextUI\Command; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +interface Command +{ + public function execute(): Result; +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Command/Commands/AtLeastVersionCommand.php b/app/vendor/phpunit/phpunit/src/TextUI/Command/Commands/AtLeastVersionCommand.php new file mode 100644 index 000000000..7bace86c8 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Command/Commands/AtLeastVersionCommand.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 PHPUnit\TextUI\Command; + +use function version_compare; +use PHPUnit\Runner\Version; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class AtLeastVersionCommand implements Command +{ + private string $version; + + public function __construct(string $version) + { + $this->version = $version; + } + + public function execute(): Result + { + if (version_compare(Version::id(), $this->version, '>=')) { + return Result::from(); + } + + return Result::from('', Result::FAILURE); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Command/Commands/CheckPhpConfigurationCommand.php b/app/vendor/phpunit/phpunit/src/TextUI/Command/Commands/CheckPhpConfigurationCommand.php new file mode 100644 index 000000000..7f684cc0e --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Command/Commands/CheckPhpConfigurationCommand.php @@ -0,0 +1,166 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Command; + +use const E_ALL; +use const PHP_EOL; +use function extension_loaded; +use function in_array; +use function ini_get; +use function max; +use function sprintf; +use function strlen; +use PHPUnit\Runner\Version; +use PHPUnit\Util\Color; +use SebastianBergmann\Environment\Console; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class CheckPhpConfigurationCommand implements Command +{ + private bool $colorize; + + public function __construct() + { + $this->colorize = (new Console)->hasColorSupport(); + } + + public function execute(): Result + { + $lines = []; + $shellExitCode = 0; + + foreach ($this->settings() as $name => $setting) { + foreach ($setting['requiredExtensions'] as $extension) { + if (!extension_loaded($extension)) { + // @codeCoverageIgnoreStart + continue 2; + // @codeCoverageIgnoreEnd + } + } + + $actualValue = ini_get($name); + + if (in_array($actualValue, $setting['expectedValues'], true)) { + $check = $this->ok(); + } else { + $check = $this->notOk($actualValue); + $shellExitCode = 1; + } + + $lines[] = [ + sprintf( + '%s = %s', + $name, + $setting['valueForConfiguration'], + ), + $check, + ]; + } + + $maxLength = 0; + + foreach ($lines as $line) { + $maxLength = max($maxLength, strlen($line[0])); + } + + $buffer = sprintf( + 'Checking whether PHP is configured according to https://docs.phpunit.de/en/%s/installation.html#configuring-php-for-development' . PHP_EOL . PHP_EOL, + Version::series(), + ); + + foreach ($lines as $line) { + $buffer .= sprintf( + '%-' . $maxLength . 's ... %s' . PHP_EOL, + $line[0], + $line[1], + ); + } + + return Result::from($buffer, $shellExitCode); + } + + /** + * @return non-empty-string + */ + private function ok(): string + { + if (!$this->colorize) { + return 'ok'; + } + + // @codeCoverageIgnoreStart + return Color::colorizeTextBox('fg-green, bold', 'ok'); + // @codeCoverageIgnoreEnd + } + + /** + * @return non-empty-string + */ + private function notOk(string $actualValue): string + { + $message = sprintf('not ok (%s)', $actualValue); + + if (!$this->colorize) { + return $message; + } + + // @codeCoverageIgnoreStart + return Color::colorizeTextBox('fg-red, bold', $message); + // @codeCoverageIgnoreEnd + } + + /** + * @return non-empty-array, valueForConfiguration: non-empty-string, requiredExtensions: list}> + */ + private function settings(): array + { + return [ + 'display_errors' => [ + 'expectedValues' => ['1'], + 'valueForConfiguration' => 'On', + 'requiredExtensions' => [], + ], + 'display_startup_errors' => [ + 'expectedValues' => ['1'], + 'valueForConfiguration' => 'On', + 'requiredExtensions' => [], + ], + 'error_reporting' => [ + 'expectedValues' => ['-1', (string) E_ALL], + 'valueForConfiguration' => '-1', + 'requiredExtensions' => [], + ], + 'xdebug.show_exception_trace' => [ + 'expectedValues' => ['0'], + 'valueForConfiguration' => '0', + 'requiredExtensions' => ['xdebug'], + ], + 'zend.assertions' => [ + 'expectedValues' => ['1'], + 'valueForConfiguration' => '1', + 'requiredExtensions' => [], + ], + 'assert.exception' => [ + 'expectedValues' => ['1'], + 'valueForConfiguration' => '1', + 'requiredExtensions' => [], + ], + 'memory_limit' => [ + 'expectedValues' => ['-1'], + 'valueForConfiguration' => '-1', + 'requiredExtensions' => [], + ], + ]; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Command/Commands/GenerateConfigurationCommand.php b/app/vendor/phpunit/phpunit/src/TextUI/Command/Commands/GenerateConfigurationCommand.php new file mode 100644 index 000000000..cb1a9ac95 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Command/Commands/GenerateConfigurationCommand.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 PHPUnit\TextUI\Command; + +use const PHP_EOL; +use const STDIN; +use function assert; +use function defined; +use function fgets; +use function file_put_contents; +use function getcwd; +use function is_file; +use function sprintf; +use function trim; +use PHPUnit\Runner\Version; +use PHPUnit\TextUI\XmlConfiguration\Generator; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class GenerateConfigurationCommand implements Command +{ + public function execute(): Result + { + $directory = getcwd(); + + print 'Generating phpunit.xml in ' . $directory . PHP_EOL . PHP_EOL; + print 'Bootstrap script (relative to path shown above; default: vendor/autoload.php): '; + + $bootstrapScript = $this->read(); + + print 'Tests directory (relative to path shown above; default: tests): '; + + $testsDirectory = $this->read(); + + print 'Source directory (relative to path shown above; default: src): '; + + $src = $this->read(); + + print 'Cache directory (relative to path shown above; default: .phpunit.cache): '; + + $cacheDirectory = $this->read(); + + if ($bootstrapScript === '') { + $bootstrapScript = 'vendor/autoload.php'; + } + + if ($testsDirectory === '') { + $testsDirectory = 'tests'; + } + + if ($src === '') { + $src = 'src'; + } + + if ($cacheDirectory === '') { + $cacheDirectory = '.phpunit.cache'; + } + + if (defined('PHPUNIT_COMPOSER_INSTALL') && + is_file($directory . '/vendor/phpunit/phpunit/phpunit.xsd')) { + $schemaLocation = 'vendor/phpunit/phpunit/phpunit.xsd'; + } else { + $schemaLocation = sprintf( + 'https://schema.phpunit.de/%s/phpunit.xsd', + Version::series(), + ); + } + + $generator = new Generator; + + $result = @file_put_contents( + $directory . '/phpunit.xml', + $generator->generateDefaultConfiguration( + $schemaLocation, + $bootstrapScript, + $testsDirectory, + $src, + $cacheDirectory, + ), + ); + + if ($result !== false) { + return Result::from( + sprintf( + PHP_EOL . 'Generated phpunit.xml in %s.' . PHP_EOL . + 'Make sure to exclude the %s directory from version control.' . PHP_EOL, + $directory, + $cacheDirectory, + ), + ); + } + + // @codeCoverageIgnoreStart + return Result::from( + sprintf( + PHP_EOL . 'Could not write phpunit.xml in %s.' . PHP_EOL, + $directory, + ), + Result::EXCEPTION, + ); + // @codeCoverageIgnoreEnd + } + + private function read(): string + { + $buffer = fgets(STDIN); + + assert($buffer !== false); + + return trim($buffer); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Command/Commands/ListGroupsCommand.php b/app/vendor/phpunit/phpunit/src/TextUI/Command/Commands/ListGroupsCommand.php new file mode 100644 index 000000000..94eb1355c --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Command/Commands/ListGroupsCommand.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 PHPUnit\TextUI\Command; + +use const PHP_EOL; +use function count; +use function ksort; +use function sprintf; +use function str_starts_with; +use PHPUnit\Framework\TestCase; +use PHPUnit\Runner\Phpt\TestCase as PhptTestCase; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ListGroupsCommand implements Command +{ + /** + * @var list + */ + private array $tests; + + /** + * @param list $tests + */ + public function __construct(array $tests) + { + $this->tests = $tests; + } + + public function execute(): Result + { + /** @var array $groups */ + $groups = []; + + foreach ($this->tests as $test) { + if ($test instanceof PhptTestCase) { + $_groups = ['default']; + } else { + $_groups = $test->groups(); + } + + foreach ($_groups as $group) { + if (!isset($groups[$group])) { + $groups[$group] = 1; + } else { + $groups[$group]++; + } + } + } + + ksort($groups); + + $buffer = sprintf( + 'Available test group%s:' . PHP_EOL, + count($groups) > 1 ? 's' : '', + ); + + foreach ($groups as $group => $numberOfTests) { + if (str_starts_with((string) $group, '__phpunit_')) { + continue; + } + + $buffer .= sprintf( + ' - %s (%d test%s)' . PHP_EOL, + (string) $group, + $numberOfTests, + $numberOfTests > 1 ? 's' : '', + ); + } + + return Result::from($buffer); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Command/Commands/ListTestFilesCommand.php b/app/vendor/phpunit/phpunit/src/TextUI/Command/Commands/ListTestFilesCommand.php new file mode 100644 index 000000000..6d7f875f4 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Command/Commands/ListTestFilesCommand.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Command; + +use const PHP_EOL; +use function array_unique; +use function assert; +use function sprintf; +use PHPUnit\Framework\TestCase; +use PHPUnit\Runner\Phpt\TestCase as PhptTestCase; +use ReflectionClass; +use ReflectionException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ListTestFilesCommand implements Command +{ + /** + * @var list + */ + private array $tests; + + /** + * @param list $tests + */ + public function __construct(array $tests) + { + $this->tests = $tests; + } + + /** + * @throws ReflectionException + */ + public function execute(): Result + { + $buffer = 'Available test files:' . PHP_EOL; + + $results = []; + + foreach ($this->tests as $test) { + if ($test instanceof TestCase) { + $name = (new ReflectionClass($test))->getFileName(); + + assert($name !== false); + + $results[] = $name; + + continue; + } + + $results[] = $test->getName(); + } + + foreach (array_unique($results) as $result) { + $buffer .= sprintf( + ' - %s' . PHP_EOL, + $result, + ); + } + + return Result::from($buffer); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Command/Commands/ListTestSuitesCommand.php b/app/vendor/phpunit/phpunit/src/TextUI/Command/Commands/ListTestSuitesCommand.php new file mode 100644 index 000000000..7f5c3b51e --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Command/Commands/ListTestSuitesCommand.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 PHPUnit\TextUI\Command; + +use const PHP_EOL; +use function assert; +use function count; +use function ksort; +use function sprintf; +use PHPUnit\Framework\TestSuite; +use PHPUnit\TextUI\Configuration\Registry; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ListTestSuitesCommand implements Command +{ + private TestSuite $testSuite; + + public function __construct(TestSuite $testSuite) + { + $this->testSuite = $testSuite; + } + + public function execute(): Result + { + /** @var array $suites */ + $suites = []; + + foreach ($this->testSuite->tests() as $test) { + assert($test instanceof TestSuite); + + $suites[$test->name()] = count($test->collect()); + } + + ksort($suites); + + $buffer = $this->warnAboutConflictingOptions(); + + $buffer .= sprintf( + 'Available test suite%s:' . PHP_EOL, + count($suites) > 1 ? 's' : '', + ); + + foreach ($suites as $suite => $numberOfTests) { + $buffer .= sprintf( + ' - %s (%d test%s)' . PHP_EOL, + $suite, + $numberOfTests, + $numberOfTests > 1 ? 's' : '', + ); + } + + return Result::from($buffer); + } + + private function warnAboutConflictingOptions(): string + { + $buffer = ''; + + $configuration = Registry::get(); + + if ($configuration->hasDefaultTestSuite()) { + $buffer .= 'The defaultTestSuite (XML) and --list-suites (CLI) options cannot be combined, only the default test suite is shown' . PHP_EOL; + } + + if ($configuration->includeTestSuites() !== [] && !$configuration->hasDefaultTestSuite()) { + $buffer .= 'The --testsuite and --list-suites options cannot be combined, --testsuite is ignored' . PHP_EOL; + } + + if ($configuration->hasFilter()) { + $buffer .= 'The --filter and --list-suites options cannot be combined, --filter is ignored' . PHP_EOL; + } + + if ($configuration->hasGroups()) { + $buffer .= 'The --group (CLI) and (XML) options cannot be combined with --list-suites, --group and are ignored' . PHP_EOL; + } + + if ($configuration->hasExcludeGroups()) { + $buffer .= 'The --exclude-group (CLI) and (XML) options cannot be combined with --list-suites, --exclude-group and are ignored' . PHP_EOL; + } + + if ($buffer !== '') { + $buffer .= PHP_EOL; + } + + return $buffer; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Command/Commands/ListTestsAsTextCommand.php b/app/vendor/phpunit/phpunit/src/TextUI/Command/Commands/ListTestsAsTextCommand.php new file mode 100644 index 000000000..c3d71b5d2 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Command/Commands/ListTestsAsTextCommand.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 PHPUnit\TextUI\Command; + +use const PHP_EOL; +use function count; +use function sprintf; +use function str_replace; +use PHPUnit\Framework\TestCase; +use PHPUnit\Runner\Phpt\TestCase as PhptTestCase; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ListTestsAsTextCommand implements Command +{ + /** + * @var list + */ + private array $tests; + + /** + * @param list $tests + */ + public function __construct(array $tests) + { + $this->tests = $tests; + } + + public function execute(): Result + { + $buffer = sprintf( + 'Available test%s:' . PHP_EOL, + count($this->tests) > 1 ? 's' : '', + ); + + foreach ($this->tests as $test) { + if ($test instanceof TestCase) { + $name = sprintf( + '%s::%s', + $test::class, + str_replace(' with data set ', '', $test->nameWithDataSet()), + ); + } else { + $name = $test->getName(); + } + + $buffer .= sprintf( + ' - %s' . PHP_EOL, + $name, + ); + } + + return Result::from($buffer); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Command/Commands/ListTestsAsXmlCommand.php b/app/vendor/phpunit/phpunit/src/TextUI/Command/Commands/ListTestsAsXmlCommand.php new file mode 100644 index 000000000..b3d680143 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Command/Commands/ListTestsAsXmlCommand.php @@ -0,0 +1,140 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Command; + +use const PHP_EOL; +use function assert; +use function file_put_contents; +use function ksort; +use function sprintf; +use PHPUnit\Framework\TestCase; +use PHPUnit\Runner\Phpt\TestCase as PhptTestCase; +use ReflectionClass; +use XMLWriter; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ListTestsAsXmlCommand implements Command +{ + /** + * @var list + */ + private array $tests; + private string $filename; + + /** + * @param list $tests + */ + public function __construct(array $tests, string $filename) + { + $this->tests = $tests; + $this->filename = $filename; + } + + public function execute(): Result + { + $writer = new XMLWriter; + + $writer->openMemory(); + $writer->setIndent(true); + $writer->startDocument(); + + $writer->startElement('testSuite'); + $writer->writeAttribute('xmlns', 'https://xml.phpunit.de/testSuite'); + + $writer->startElement('tests'); + + $currentTestClass = null; + $groups = []; + + foreach ($this->tests as $test) { + if ($test instanceof TestCase) { + foreach ($test->groups() as $group) { + if (!isset($groups[$group])) { + $groups[$group] = []; + } + + $groups[$group][] = $test->valueObjectForEvents()->id(); + } + + if ($test::class !== $currentTestClass) { + if ($currentTestClass !== null) { + $writer->endElement(); + } + + $file = (new ReflectionClass($test))->getFileName(); + + assert($file !== false); + + $writer->startElement('testClass'); + $writer->writeAttribute('name', $test::class); + $writer->writeAttribute('file', $file); + + $currentTestClass = $test::class; + } + + $writer->startElement('testMethod'); + $writer->writeAttribute('id', $test->valueObjectForEvents()->id()); + $writer->writeAttribute('name', $test->valueObjectForEvents()->methodName()); + $writer->endElement(); + + continue; + } + + if ($currentTestClass !== null) { + $writer->endElement(); + + $currentTestClass = null; + } + + $writer->startElement('phpt'); + $writer->writeAttribute('file', $test->getName()); + $writer->endElement(); + } + + if ($currentTestClass !== null) { + $writer->endElement(); + } + + $writer->endElement(); + + ksort($groups); + + $writer->startElement('groups'); + + foreach ($groups as $groupName => $testIds) { + $writer->startElement('group'); + $writer->writeAttribute('name', (string) $groupName); + + foreach ($testIds as $testId) { + $writer->startElement('test'); + $writer->writeAttribute('id', $testId); + $writer->endElement(); + } + + $writer->endElement(); + } + + $writer->endElement(); + $writer->endElement(); + + file_put_contents($this->filename, $writer->outputMemory()); + + return Result::from( + sprintf( + 'Wrote list of tests that would have been run to %s' . PHP_EOL, + $this->filename, + ), + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Command/Commands/MigrateConfigurationCommand.php b/app/vendor/phpunit/phpunit/src/TextUI/Command/Commands/MigrateConfigurationCommand.php new file mode 100644 index 000000000..507ff90f3 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Command/Commands/MigrateConfigurationCommand.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Command; + +use const PHP_EOL; +use function copy; +use function file_put_contents; +use function sprintf; +use PHPUnit\TextUI\XmlConfiguration\Migrator; +use Throwable; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class MigrateConfigurationCommand implements Command +{ + private string $filename; + + public function __construct(string $filename) + { + $this->filename = $filename; + } + + public function execute(): Result + { + try { + $migrated = (new Migrator)->migrate($this->filename); + + copy($this->filename, $this->filename . '.bak'); + + file_put_contents($this->filename, $migrated); + + return Result::from( + sprintf( + 'Created backup: %s.bak%sMigrated configuration: %s%s', + $this->filename, + PHP_EOL, + $this->filename, + PHP_EOL, + ), + ); + } catch (Throwable $t) { + return Result::from( + sprintf( + 'Migration of %s failed:%s%s%s', + $this->filename, + PHP_EOL, + $t->getMessage(), + PHP_EOL, + ), + Result::FAILURE, + ); + } + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Command/Commands/ShowHelpCommand.php b/app/vendor/phpunit/phpunit/src/TextUI/Command/Commands/ShowHelpCommand.php new file mode 100644 index 000000000..1fd04811f --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Command/Commands/ShowHelpCommand.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Command; + +use PHPUnit\TextUI\Help; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ShowHelpCommand implements Command +{ + private int $shellExitCode; + + public function __construct(int $shellExitCode) + { + $this->shellExitCode = $shellExitCode; + } + + public function execute(): Result + { + return Result::from( + (new Help)->generate(), + $this->shellExitCode, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Command/Commands/ShowVersionCommand.php b/app/vendor/phpunit/phpunit/src/TextUI/Command/Commands/ShowVersionCommand.php new file mode 100644 index 000000000..4455a3d23 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Command/Commands/ShowVersionCommand.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 PHPUnit\TextUI\Command; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ShowVersionCommand implements Command +{ + public function execute(): Result + { + return Result::from(); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Command/Commands/VersionCheckCommand.php b/app/vendor/phpunit/phpunit/src/TextUI/Command/Commands/VersionCheckCommand.php new file mode 100644 index 000000000..3e076ebee --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Command/Commands/VersionCheckCommand.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 PHPUnit\TextUI\Command; + +use const PHP_EOL; +use function assert; +use function sprintf; +use function version_compare; +use PHPUnit\Util\Http\Downloader; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class VersionCheckCommand implements Command +{ + private Downloader $downloader; + private int $majorVersionNumber; + private string $versionId; + + public function __construct(Downloader $downloader, int $majorVersionNumber, string $versionId) + { + $this->downloader = $downloader; + $this->majorVersionNumber = $majorVersionNumber; + $this->versionId = $versionId; + } + + public function execute(): Result + { + $latestVersion = $this->downloader->download('https://phar.phpunit.de/latest-version-of/phpunit'); + + assert($latestVersion !== false); + + $latestCompatibleVersion = $this->downloader->download('https://phar.phpunit.de/latest-version-of/phpunit-' . $this->majorVersionNumber); + + $notLatest = version_compare($latestVersion, $this->versionId, '>'); + $notLatestCompatible = false; + + if ($latestCompatibleVersion !== false) { + $notLatestCompatible = version_compare($latestCompatibleVersion, $this->versionId, '>'); + } + + if (!$notLatest && !$notLatestCompatible) { + return Result::from( + 'You are using the latest version of PHPUnit.' . PHP_EOL, + ); + } + + $buffer = 'You are not using the latest version of PHPUnit.' . PHP_EOL; + + if ($notLatestCompatible) { + $buffer .= sprintf( + 'The latest version compatible with PHPUnit %s is PHPUnit %s.' . PHP_EOL, + $this->versionId, + $latestCompatibleVersion, + ); + } + + if ($notLatest) { + $buffer .= sprintf( + 'The latest version is PHPUnit %s.' . PHP_EOL, + $latestVersion, + ); + } + + return Result::from($buffer, Result::FAILURE); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Command/Commands/WarmCodeCoverageCacheCommand.php b/app/vendor/phpunit/phpunit/src/TextUI/Command/Commands/WarmCodeCoverageCacheCommand.php new file mode 100644 index 000000000..7d1afafe3 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Command/Commands/WarmCodeCoverageCacheCommand.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 PHPUnit\TextUI\Command; + +use const PHP_EOL; +use function printf; +use PHPUnit\TextUI\Configuration\CodeCoverageFilterRegistry; +use PHPUnit\TextUI\Configuration\Configuration; +use PHPUnit\TextUI\Configuration\NoCoverageCacheDirectoryException; +use SebastianBergmann\CodeCoverage\StaticAnalysis\CacheWarmer; +use SebastianBergmann\Timer\NoActiveTimerException; +use SebastianBergmann\Timer\Timer; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @codeCoverageIgnore + */ +final readonly class WarmCodeCoverageCacheCommand implements Command +{ + private Configuration $configuration; + private CodeCoverageFilterRegistry $codeCoverageFilterRegistry; + + public function __construct(Configuration $configuration, CodeCoverageFilterRegistry $codeCoverageFilterRegistry) + { + $this->configuration = $configuration; + $this->codeCoverageFilterRegistry = $codeCoverageFilterRegistry; + } + + /** + * @throws NoActiveTimerException + * @throws NoCoverageCacheDirectoryException + */ + public function execute(): Result + { + if (!$this->configuration->hasCoverageCacheDirectory()) { + return Result::from( + 'Cache for static analysis has not been configured' . PHP_EOL, + Result::FAILURE, + ); + } + + $this->codeCoverageFilterRegistry->init($this->configuration, true); + + if (!$this->codeCoverageFilterRegistry->configured()) { + return Result::from( + 'Filter for code coverage has not been configured' . PHP_EOL, + Result::FAILURE, + ); + } + + $timer = new Timer; + $timer->start(); + + print 'Warming cache for static analysis ... '; + + /** @phpstan-ignore new.internalClass,method.internalClass */ + $statistics = (new CacheWarmer)->warmCache( + $this->configuration->coverageCacheDirectory(), + !$this->configuration->disableCodeCoverageIgnore(), + $this->configuration->ignoreDeprecatedCodeUnitsFromCodeCoverage(), + $this->codeCoverageFilterRegistry->get(), + ); + + printf( + '[%s]%s%s%d file%s processed, %d cache hit%s, %d cache miss%s%s', + $timer->stop()->asString(), + PHP_EOL, + PHP_EOL, + $statistics['cacheHits'] + $statistics['cacheMisses'], + ($statistics['cacheHits'] + $statistics['cacheMisses']) !== 1 ? 's' : '', + $statistics['cacheHits'], + $statistics['cacheHits'] !== 1 ? 's' : '', + $statistics['cacheMisses'], + $statistics['cacheMisses'] !== 1 ? 'es' : '', + PHP_EOL, + ); + + return Result::from(); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Command/Result.php b/app/vendor/phpunit/phpunit/src/TextUI/Command/Result.php new file mode 100644 index 000000000..ae4a3e27f --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Command/Result.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 PHPUnit\TextUI\Command; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Result +{ + public const int SUCCESS = 0; + public const int FAILURE = 1; + public const int EXCEPTION = 2; + public const int CRASH = 255; + private string $output; + private int $shellExitCode; + + public static function from(string $output = '', int $shellExitCode = self::SUCCESS): self + { + return new self($output, $shellExitCode); + } + + private function __construct(string $output, int $shellExitCode) + { + $this->output = $output; + $this->shellExitCode = $shellExitCode; + } + + public function output(): string + { + return $this->output; + } + + public function shellExitCode(): int + { + return $this->shellExitCode; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/BootstrapLoader.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/BootstrapLoader.php new file mode 100644 index 000000000..f52d66475 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/BootstrapLoader.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 PHPUnit\TextUI\Configuration; + +use const PHP_EOL; +use function in_array; +use function is_readable; +use function sprintf; +use PHPUnit\Event\Facade as EventFacade; +use Throwable; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class BootstrapLoader +{ + /** + * @throws BootstrapScriptDoesNotExistException + * @throws BootstrapScriptException + */ + public function handle(Configuration $configuration): void + { + if (!$configuration->hasBootstrap()) { + return; + } + + $this->load($configuration->bootstrap()); + + foreach ($configuration->bootstrapForTestSuite() as $testSuiteName => $bootstrapForTestSuite) { + if ($configuration->includeTestSuites() !== [] && !in_array($testSuiteName, $configuration->includeTestSuites(), true)) { + continue; + } + + if ($configuration->excludeTestSuites() !== [] && in_array($testSuiteName, $configuration->excludeTestSuites(), true)) { + continue; + } + + $this->load($bootstrapForTestSuite); + } + } + + /** + * @param non-empty-string $filename + */ + private function load(string $filename): void + { + if (!is_readable($filename)) { + throw new BootstrapScriptDoesNotExistException($filename); + } + + try { + include_once $filename; + } catch (Throwable $t) { + $message = sprintf( + 'Error in bootstrap script: %s:%s%s%s%s', + $t::class, + PHP_EOL, + $t->getMessage(), + PHP_EOL, + $t->getTraceAsString(), + ); + + while ($t = $t->getPrevious()) { + $message .= sprintf( + '%s%sPrevious error: %s:%s%s%s%s', + PHP_EOL, + PHP_EOL, + $t::class, + PHP_EOL, + $t->getMessage(), + PHP_EOL, + $t->getTraceAsString(), + ); + } + + throw new BootstrapScriptException($message); + } + + EventFacade::emitter()->testRunnerBootstrapFinished($filename); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Builder.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Builder.php new file mode 100644 index 000000000..6f9e81a15 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Builder.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use PHPUnit\TextUI\CliArguments\Builder as CliConfigurationBuilder; +use PHPUnit\TextUI\CliArguments\Exception as CliConfigurationException; +use PHPUnit\TextUI\CliArguments\XmlConfigurationFileFinder; +use PHPUnit\TextUI\XmlConfiguration\DefaultConfiguration; +use PHPUnit\TextUI\XmlConfiguration\Exception as XmlConfigurationException; +use PHPUnit\TextUI\XmlConfiguration\Loader; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @codeCoverageIgnore + */ +final readonly class Builder +{ + /** + * @param list $argv + * + * @throws ConfigurationCannotBeBuiltException + */ + public function build(array $argv): Configuration + { + try { + $cliConfiguration = (new CliConfigurationBuilder)->fromParameters($argv); + $configurationFile = (new XmlConfigurationFileFinder)->find($cliConfiguration); + $xmlConfiguration = DefaultConfiguration::create(); + + if ($configurationFile !== false) { + $xmlConfiguration = (new Loader)->load($configurationFile); + } + + return Registry::init( + $cliConfiguration, + $xmlConfiguration, + ); + } catch (CliConfigurationException|XmlConfigurationException $e) { + throw new ConfigurationCannotBeBuiltException( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Cli/Builder.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Cli/Builder.php new file mode 100644 index 000000000..c3d0e2a4b --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Cli/Builder.php @@ -0,0 +1,1411 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\CliArguments; + +use const DIRECTORY_SEPARATOR; +use function assert; +use function basename; +use function explode; +use function getcwd; +use function is_file; +use function is_numeric; +use function sprintf; +use function strtolower; +use PHPUnit\Event\Facade as EventFacade; +use PHPUnit\Runner\TestSuiteSorter; +use PHPUnit\Util\Filesystem; +use SebastianBergmann\CliParser\Exception as CliParserException; +use SebastianBergmann\CliParser\Parser as CliParser; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Builder +{ + /** + * @var non-empty-list + */ + private const array LONG_OPTIONS = [ + 'atleast-version=', + 'bootstrap=', + 'cache-result', + 'do-not-cache-result', + 'cache-directory=', + 'check-version', + 'check-php-configuration', + 'colors==', + 'columns=', + 'configuration=', + 'warm-coverage-cache', + 'coverage-filter=', + 'coverage-clover=', + 'coverage-cobertura=', + 'coverage-crap4j=', + 'coverage-html=', + 'coverage-openclover=', + 'coverage-php=', + 'coverage-text==', + 'only-summary-for-coverage-text', + 'show-uncovered-for-coverage-text', + 'coverage-xml=', + 'path-coverage', + 'disallow-test-output', + 'display-all-issues', + 'display-incomplete', + 'display-skipped', + 'display-deprecations', + 'display-phpunit-deprecations', + 'display-phpunit-notices', + 'display-errors', + 'display-notices', + 'display-warnings', + 'default-time-limit=', + 'enforce-time-limit', + 'exclude-group=', + 'filter=', + 'exclude-filter=', + 'generate-baseline=', + 'use-baseline=', + 'ignore-baseline', + 'generate-configuration', + 'globals-backup', + 'group=', + 'covers=', + 'uses=', + 'requires-php-extension=', + 'help', + 'resolve-dependencies', + 'ignore-dependencies', + 'include-path=', + 'list-groups', + 'list-suites', + 'list-test-files', + 'list-tests', + 'list-tests-xml=', + 'log-junit=', + 'log-otr=', + 'include-git-information', + 'log-teamcity=', + 'migrate-configuration', + 'no-configuration', + 'no-coverage', + 'no-logging', + 'no-extensions', + 'no-output', + 'no-progress', + 'no-results', + 'order-by=', + 'process-isolation', + 'do-not-report-useless-tests', + 'dont-report-useless-tests', + 'random-order', + 'random-order-seed=', + 'reverse-order', + 'reverse-list', + 'static-backup', + 'stderr', + 'fail-on-all-issues', + 'fail-on-deprecation', + 'fail-on-phpunit-deprecation', + 'fail-on-phpunit-notice', + 'fail-on-phpunit-warning', + 'fail-on-empty-test-suite', + 'fail-on-incomplete', + 'fail-on-notice', + 'fail-on-risky', + 'fail-on-skipped', + 'fail-on-warning', + 'do-not-fail-on-deprecation', + 'do-not-fail-on-phpunit-deprecation', + 'do-not-fail-on-phpunit-notice', + 'do-not-fail-on-phpunit-warning', + 'do-not-fail-on-empty-test-suite', + 'do-not-fail-on-incomplete', + 'do-not-fail-on-notice', + 'do-not-fail-on-risky', + 'do-not-fail-on-skipped', + 'do-not-fail-on-warning', + 'stop-on-defect', + 'stop-on-deprecation==', + 'stop-on-error', + 'stop-on-failure', + 'stop-on-incomplete', + 'stop-on-notice', + 'stop-on-risky', + 'stop-on-skipped', + 'stop-on-warning', + 'strict-coverage', + 'disable-coverage-ignore', + 'strict-global-state', + 'teamcity', + 'testdox', + 'testdox-summary', + 'testdox-html=', + 'testdox-text=', + 'test-suffix=', + 'testsuite=', + 'exclude-testsuite=', + 'log-events-text=', + 'log-events-verbose-text=', + 'version', + 'debug', + 'with-telemetry', + 'extension=', + ]; + + private const string SHORT_OPTIONS = 'd:c:h'; + + /** + * @var array + */ + private array $processed = []; + + /** + * @param list $parameters + * + * @throws Exception + */ + public function fromParameters(array $parameters): Configuration + { + try { + $options = (new CliParser)->parse( + $parameters, + self::SHORT_OPTIONS, + self::LONG_OPTIONS, + ); + } catch (CliParserException $e) { + throw new Exception( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + + $atLeastVersion = null; + $backupGlobals = null; + $backupStaticProperties = null; + $beStrictAboutChangesToGlobalState = null; + $bootstrap = null; + $cacheDirectory = null; + $cacheResult = null; + $checkPhpConfiguration = false; + $checkVersion = false; + $colors = null; + $columns = null; + $configuration = null; + $warmCoverageCache = false; + $coverageFilter = null; + $coverageClover = null; + $coverageCobertura = null; + $coverageCrap4J = null; + $coverageHtml = null; + $coverageOpenClover = null; + $coveragePhp = null; + $coverageText = null; + $coverageTextShowUncoveredFiles = null; + $coverageTextShowOnlySummary = null; + $coverageXml = null; + $pathCoverage = null; + $defaultTimeLimit = null; + $disableCodeCoverageIgnore = null; + $disallowTestOutput = null; + $displayAllIssues = null; + $displayIncomplete = null; + $displaySkipped = null; + $displayDeprecations = null; + $displayPhpunitDeprecations = null; + $displayPhpunitNotices = null; + $displayErrors = null; + $displayNotices = null; + $displayWarnings = null; + $enforceTimeLimit = null; + $excludeGroups = null; + $executionOrder = null; + $executionOrderDefects = null; + $failOnAllIssues = null; + $failOnDeprecation = null; + $failOnPhpunitDeprecation = null; + $failOnPhpunitNotice = null; + $failOnPhpunitWarning = null; + $failOnEmptyTestSuite = null; + $failOnIncomplete = null; + $failOnNotice = null; + $failOnRisky = null; + $failOnSkipped = null; + $failOnWarning = null; + $doNotFailOnDeprecation = null; + $doNotFailOnPhpunitDeprecation = null; + $doNotFailOnPhpunitNotice = null; + $doNotFailOnPhpunitWarning = null; + $doNotFailOnEmptyTestSuite = null; + $doNotFailOnIncomplete = null; + $doNotFailOnNotice = null; + $doNotFailOnRisky = null; + $doNotFailOnSkipped = null; + $doNotFailOnWarning = null; + $stopOnDefect = null; + $stopOnDeprecation = null; + $specificDeprecationToStopOn = null; + $stopOnError = null; + $stopOnFailure = null; + $stopOnIncomplete = null; + $stopOnNotice = null; + $stopOnRisky = null; + $stopOnSkipped = null; + $stopOnWarning = null; + $filter = null; + $excludeFilter = null; + $generateBaseline = null; + $useBaseline = null; + $ignoreBaseline = false; + $generateConfiguration = false; + $migrateConfiguration = false; + $groups = null; + $testsCovering = null; + $testsUsing = null; + $testsRequiringPhpExtension = null; + $help = false; + $includePath = null; + $iniSettings = []; + $junitLogfile = null; + $otrLogfile = null; + $includeGitInformation = null; + $listGroups = false; + $listSuites = false; + $listTestFiles = false; + $listTests = false; + $listTestsXml = null; + $noCoverage = null; + $noExtensions = null; + $noOutput = null; + $noProgress = null; + $noResults = null; + $noLogging = null; + $processIsolation = null; + $randomOrderSeed = null; + $reportUselessTests = null; + $resolveDependencies = null; + $reverseList = null; + $stderr = null; + $strictCoverage = null; + $teamcityLogfile = null; + $testdoxHtmlFile = null; + $testdoxTextFile = null; + $testSuffixes = null; + $testSuite = null; + $excludeTestSuite = null; + $useDefaultConfiguration = true; + $version = false; + $logEventsText = null; + $logEventsVerboseText = null; + $printerTeamCity = null; + $printerTestDox = null; + $printerTestDoxSummary = null; + $debug = false; + $withTelemetry = false; + $extensions = []; + + foreach ($options[0] as $option) { + $optionAllowedMultipleTimes = false; + + switch ($option[0]) { + case '--colors': + $colors = \PHPUnit\TextUI\Configuration\Configuration::COLOR_AUTO; + + if ($option[1] !== null) { + $colors = $option[1]; + } + + break; + + case '--bootstrap': + $bootstrap = $option[1]; + + break; + + case '--cache-directory': + $cacheDirectory = $option[1]; + + break; + + case '--cache-result': + $cacheResult = true; + + break; + + case '--do-not-cache-result': + $cacheResult = false; + + break; + + case '--columns': + if (is_numeric($option[1])) { + $columns = (int) $option[1]; + } elseif ($option[1] === 'max') { + $columns = 'max'; + } + + break; + + case 'c': + case '--configuration': + $configuration = $option[1]; + + break; + + case '--warm-coverage-cache': + $warmCoverageCache = true; + + break; + + case '--coverage-clover': + $coverageClover = $option[1]; + + break; + + case '--coverage-cobertura': + $coverageCobertura = $option[1]; + + break; + + case '--coverage-crap4j': + $coverageCrap4J = $option[1]; + + break; + + case '--coverage-html': + $coverageHtml = $option[1]; + + break; + + case '--coverage-php': + $coveragePhp = $option[1]; + + break; + + case '--coverage-openclover': + $coverageOpenClover = $option[1]; + + break; + + case '--coverage-text': + if ($option[1] === null) { + $option[1] = 'php://stdout'; + } + + $coverageText = $option[1]; + + break; + + case '--only-summary-for-coverage-text': + $coverageTextShowOnlySummary = true; + + break; + + case '--show-uncovered-for-coverage-text': + $coverageTextShowUncoveredFiles = true; + + break; + + case '--coverage-xml': + $coverageXml = $option[1]; + + break; + + case '--path-coverage': + $pathCoverage = true; + + break; + + case 'd': + $tmp = explode('=', $option[1]); + + if (isset($tmp[0])) { + assert($tmp[0] !== ''); + + if (isset($tmp[1])) { + assert($tmp[1] !== ''); + + $iniSettings[$tmp[0]] = $tmp[1]; + } else { + $iniSettings[$tmp[0]] = '1'; + } + } + + $optionAllowedMultipleTimes = true; + + break; + + case 'h': + case '--help': + $help = true; + + break; + + case '--filter': + $filter = $option[1]; + + break; + + case '--exclude-filter': + $excludeFilter = $option[1]; + + break; + + case '--testsuite': + $testSuite = $option[1]; + + break; + + case '--exclude-testsuite': + $excludeTestSuite = $option[1]; + + break; + + case '--generate-baseline': + $generateBaseline = $option[1]; + + if (basename($generateBaseline) === $generateBaseline) { + $generateBaseline = getcwd() . DIRECTORY_SEPARATOR . $generateBaseline; + } + + break; + + case '--use-baseline': + $useBaseline = $option[1]; + + if (basename($useBaseline) === $useBaseline && !is_file($useBaseline)) { + $useBaseline = getcwd() . DIRECTORY_SEPARATOR . $useBaseline; + } + + break; + + case '--ignore-baseline': + $ignoreBaseline = true; + + break; + + case '--generate-configuration': + $generateConfiguration = true; + + break; + + case '--migrate-configuration': + $migrateConfiguration = true; + + break; + + case '--group': + if ($groups === null) { + $groups = []; + } + + $groups[] = $option[1]; + + $optionAllowedMultipleTimes = true; + + break; + + case '--exclude-group': + if ($excludeGroups === null) { + $excludeGroups = []; + } + + $excludeGroups[] = $option[1]; + + $optionAllowedMultipleTimes = true; + + break; + + case '--covers': + if ($testsCovering === null) { + $testsCovering = []; + } + + $testsCovering[] = strtolower($option[1]); + + $optionAllowedMultipleTimes = true; + + break; + + case '--uses': + if ($testsUsing === null) { + $testsUsing = []; + } + + $testsUsing[] = strtolower($option[1]); + + $optionAllowedMultipleTimes = true; + + break; + + case '--requires-php-extension': + if ($testsRequiringPhpExtension === null) { + $testsRequiringPhpExtension = []; + } + + $testsRequiringPhpExtension[] = strtolower($option[1]); + + $optionAllowedMultipleTimes = true; + + break; + + case '--test-suffix': + if ($testSuffixes === null) { + $testSuffixes = []; + } + + $testSuffixes[] = $option[1]; + + $optionAllowedMultipleTimes = true; + + break; + + case '--include-path': + $includePath = $option[1]; + + break; + + case '--list-groups': + $listGroups = true; + + break; + + case '--list-suites': + $listSuites = true; + + break; + + case '--list-test-files': + $listTestFiles = true; + + break; + + case '--list-tests': + $listTests = true; + + break; + + case '--list-tests-xml': + $listTestsXml = $option[1]; + + break; + + case '--log-junit': + $junitLogfile = $option[1]; + + break; + + case '--log-otr': + $otrLogfile = $option[1]; + + break; + + case '--include-git-information': + $includeGitInformation = true; + + break; + + case '--log-teamcity': + $teamcityLogfile = $option[1]; + + break; + + case '--order-by': + foreach (explode(',', $option[1]) as $order) { + switch ($order) { + case 'default': + $executionOrder = TestSuiteSorter::ORDER_DEFAULT; + $executionOrderDefects = TestSuiteSorter::ORDER_DEFAULT; + $resolveDependencies = true; + + break; + + case 'defects': + $executionOrderDefects = TestSuiteSorter::ORDER_DEFECTS_FIRST; + + break; + + case 'depends': + $resolveDependencies = true; + + break; + + case 'duration': + $executionOrder = TestSuiteSorter::ORDER_DURATION; + + break; + + case 'no-depends': + $resolveDependencies = false; + + break; + + case 'random': + $executionOrder = TestSuiteSorter::ORDER_RANDOMIZED; + + break; + + case 'reverse': + $executionOrder = TestSuiteSorter::ORDER_REVERSED; + + break; + + case 'size': + $executionOrder = TestSuiteSorter::ORDER_SIZE; + + break; + + default: + throw new Exception( + sprintf( + 'unrecognized --order-by option: %s', + $order, + ), + ); + } + } + + break; + + case '--process-isolation': + $processIsolation = true; + + break; + + case '--stderr': + $stderr = true; + + break; + + case '--fail-on-all-issues': + $failOnAllIssues = true; + + break; + + case '--fail-on-deprecation': + $this->warnWhenOptionsConflict( + $doNotFailOnDeprecation, + '--fail-on-deprecation', + '--do-not-fail-on-deprecation', + ); + + $failOnDeprecation = true; + + break; + + case '--fail-on-phpunit-deprecation': + $this->warnWhenOptionsConflict( + $doNotFailOnPhpunitDeprecation, + '--fail-on-phpunit-deprecation', + '--do-not-fail-on-phpunit-deprecation', + ); + + $failOnPhpunitDeprecation = true; + + break; + + case '--fail-on-phpunit-notice': + $this->warnWhenOptionsConflict( + $doNotFailOnPhpunitNotice, + '--fail-on-phpunit-notice', + '--do-not-fail-on-phpunit-notice', + ); + + $failOnPhpunitNotice = true; + + break; + + case '--fail-on-phpunit-warning': + $this->warnWhenOptionsConflict( + $doNotFailOnPhpunitWarning, + '--fail-on-phpunit-warning', + '--do-not-fail-on-phpunit-warning', + ); + + $failOnPhpunitWarning = true; + + break; + + case '--fail-on-empty-test-suite': + $this->warnWhenOptionsConflict( + $doNotFailOnEmptyTestSuite, + '--fail-on-empty-test-suite', + '--do-not-fail-on-empty-test-suite', + ); + + $failOnEmptyTestSuite = true; + + break; + + case '--fail-on-incomplete': + $this->warnWhenOptionsConflict( + $doNotFailOnIncomplete, + '--fail-on-incomplete', + '--do-not-fail-on-incomplete', + ); + + $failOnIncomplete = true; + + break; + + case '--fail-on-notice': + $this->warnWhenOptionsConflict( + $doNotFailOnNotice, + '--fail-on-notice', + '--do-not-fail-on-notice', + ); + + $failOnNotice = true; + + break; + + case '--fail-on-risky': + $this->warnWhenOptionsConflict( + $doNotFailOnRisky, + '--fail-on-risky', + '--do-not-fail-on-risky', + ); + + $failOnRisky = true; + + break; + + case '--fail-on-skipped': + $this->warnWhenOptionsConflict( + $doNotFailOnSkipped, + '--fail-on-skipped', + '--do-not-fail-on-skipped', + ); + + $failOnSkipped = true; + + break; + + case '--fail-on-warning': + $this->warnWhenOptionsConflict( + $doNotFailOnWarning, + '--fail-on-warning', + '--do-not-fail-on-warning', + ); + + $failOnWarning = true; + + break; + + case '--do-not-fail-on-deprecation': + $this->warnWhenOptionsConflict( + $failOnDeprecation, + '--do-not-fail-on-deprecation', + '--fail-on-deprecation', + ); + + $doNotFailOnDeprecation = true; + + break; + + case '--do-not-fail-on-phpunit-deprecation': + $this->warnWhenOptionsConflict( + $failOnPhpunitDeprecation, + '--do-not-fail-on-phpunit-deprecation', + '--fail-on-phpunit-deprecation', + ); + + $doNotFailOnPhpunitDeprecation = true; + + break; + + case '--do-not-fail-on-phpunit-notice': + $this->warnWhenOptionsConflict( + $failOnPhpunitNotice, + '--do-not-fail-on-phpunit-notice', + '--fail-on-phpunit-notice', + ); + + $doNotFailOnPhpunitNotice = true; + + break; + + case '--do-not-fail-on-phpunit-warning': + $this->warnWhenOptionsConflict( + $failOnPhpunitWarning, + '--do-not-fail-on-phpunit-warning', + '--fail-on-phpunit-warning', + ); + + $doNotFailOnPhpunitWarning = true; + + break; + + case '--do-not-fail-on-empty-test-suite': + $this->warnWhenOptionsConflict( + $failOnEmptyTestSuite, + '--do-not-fail-on-empty-test-suite', + '--fail-on-empty-test-suite', + ); + + $doNotFailOnEmptyTestSuite = true; + + break; + + case '--do-not-fail-on-incomplete': + $this->warnWhenOptionsConflict( + $failOnIncomplete, + '--do-not-fail-on-incomplete', + '--fail-on-incomplete', + ); + + $doNotFailOnIncomplete = true; + + break; + + case '--do-not-fail-on-notice': + $this->warnWhenOptionsConflict( + $failOnNotice, + '--do-not-fail-on-notice', + '--fail-on-notice', + ); + + $doNotFailOnNotice = true; + + break; + + case '--do-not-fail-on-risky': + $this->warnWhenOptionsConflict( + $failOnRisky, + '--do-not-fail-on-risky', + '--fail-on-risky', + ); + + $doNotFailOnRisky = true; + + break; + + case '--do-not-fail-on-skipped': + $this->warnWhenOptionsConflict( + $failOnSkipped, + '--do-not-fail-on-skipped', + '--fail-on-skipped', + ); + + $doNotFailOnSkipped = true; + + break; + + case '--do-not-fail-on-warning': + $this->warnWhenOptionsConflict( + $failOnWarning, + '--do-not-fail-on-warning', + '--fail-on-warning', + ); + + $doNotFailOnWarning = true; + + break; + + case '--stop-on-defect': + $stopOnDefect = true; + + break; + + case '--stop-on-deprecation': + $stopOnDeprecation = true; + + if ($option[1] !== null) { + $specificDeprecationToStopOn = $option[1]; + } + + break; + + case '--stop-on-error': + $stopOnError = true; + + break; + + case '--stop-on-failure': + $stopOnFailure = true; + + break; + + case '--stop-on-incomplete': + $stopOnIncomplete = true; + + break; + + case '--stop-on-notice': + $stopOnNotice = true; + + break; + + case '--stop-on-risky': + $stopOnRisky = true; + + break; + + case '--stop-on-skipped': + $stopOnSkipped = true; + + break; + + case '--stop-on-warning': + $stopOnWarning = true; + + break; + + case '--teamcity': + $printerTeamCity = true; + + break; + + case '--testdox': + $printerTestDox = true; + + break; + + case '--testdox-summary': + $printerTestDoxSummary = true; + + break; + + case '--testdox-html': + $testdoxHtmlFile = $option[1]; + + break; + + case '--testdox-text': + $testdoxTextFile = $option[1]; + + break; + + case '--no-configuration': + $useDefaultConfiguration = false; + + break; + + case '--no-extensions': + $noExtensions = true; + + break; + + case '--no-coverage': + $noCoverage = true; + + break; + + case '--no-logging': + $noLogging = true; + + break; + + case '--no-output': + $noOutput = true; + + break; + + case '--no-progress': + $noProgress = true; + + break; + + case '--no-results': + $noResults = true; + + break; + + case '--globals-backup': + $backupGlobals = true; + + break; + + case '--static-backup': + $backupStaticProperties = true; + + break; + + case '--atleast-version': + $atLeastVersion = $option[1]; + + break; + + case '--version': + $version = true; + + break; + + case '--do-not-report-useless-tests': + $reportUselessTests = false; + + break; + + case '--dont-report-useless-tests': + EventFacade::emitter()->testRunnerTriggeredPhpunitDeprecation( + 'Option --dont-report-useless-tests is deprecated, use --do-not-report-useless-tests instead', + ); + + $reportUselessTests = false; + + break; + + case '--strict-coverage': + $strictCoverage = true; + + break; + + case '--disable-coverage-ignore': + $disableCodeCoverageIgnore = true; + + break; + + case '--strict-global-state': + $beStrictAboutChangesToGlobalState = true; + + break; + + case '--disallow-test-output': + $disallowTestOutput = true; + + break; + + case '--display-all-issues': + $displayAllIssues = true; + + break; + + case '--display-incomplete': + $displayIncomplete = true; + + break; + + case '--display-skipped': + $displaySkipped = true; + + break; + + case '--display-deprecations': + $displayDeprecations = true; + + break; + + case '--display-phpunit-deprecations': + $displayPhpunitDeprecations = true; + + break; + + case '--display-phpunit-notices': + $displayPhpunitNotices = true; + + break; + + case '--display-errors': + $displayErrors = true; + + break; + + case '--display-notices': + $displayNotices = true; + + break; + + case '--display-warnings': + $displayWarnings = true; + + break; + + case '--default-time-limit': + $defaultTimeLimit = (int) $option[1]; + + break; + + case '--enforce-time-limit': + $enforceTimeLimit = true; + + break; + + case '--reverse-list': + $reverseList = true; + + break; + + case '--check-php-configuration': + $checkPhpConfiguration = true; + + break; + + case '--check-version': + $checkVersion = true; + + break; + + case '--coverage-filter': + if ($coverageFilter === null) { + $coverageFilter = []; + } + + $coverageFilter[] = $option[1]; + + $optionAllowedMultipleTimes = true; + + break; + + case '--random-order': + $executionOrder = TestSuiteSorter::ORDER_RANDOMIZED; + + break; + + case '--random-order-seed': + $randomOrderSeed = (int) $option[1]; + + break; + + case '--resolve-dependencies': + $resolveDependencies = true; + + break; + + case '--ignore-dependencies': + $resolveDependencies = false; + + break; + + case '--reverse-order': + $executionOrder = TestSuiteSorter::ORDER_REVERSED; + + break; + + case '--log-events-text': + $logEventsText = Filesystem::resolveStreamOrFile($option[1]); + + if ($logEventsText === false) { + throw new Exception( + sprintf( + 'The path "%s" specified for the --log-events-text option could not be resolved', + $option[1], + ), + ); + } + + break; + + case '--log-events-verbose-text': + $logEventsVerboseText = Filesystem::resolveStreamOrFile($option[1]); + + if ($logEventsVerboseText === false) { + throw new Exception( + sprintf( + 'The path "%s" specified for the --log-events-verbose-text option could not be resolved', + $option[1], + ), + ); + } + + break; + + case '--debug': + $debug = true; + + break; + + case '--with-telemetry': + $withTelemetry = true; + + break; + + case '--extension': + $extensions[] = $option[1]; + + $optionAllowedMultipleTimes = true; + + break; + } + + if (!$optionAllowedMultipleTimes) { + $this->markProcessed($option[0]); + } + } + + if ($iniSettings === []) { + $iniSettings = null; + } + + if ($extensions === []) { + $extensions = null; + } + + return new Configuration( + $options[1], + $atLeastVersion, + $backupGlobals, + $backupStaticProperties, + $beStrictAboutChangesToGlobalState, + $bootstrap, + $cacheDirectory, + $cacheResult, + $checkPhpConfiguration, + $checkVersion, + $colors, + $columns, + $configuration, + $coverageClover, + $coverageCobertura, + $coverageCrap4J, + $coverageHtml, + $coverageOpenClover, + $coveragePhp, + $coverageText, + $coverageTextShowUncoveredFiles, + $coverageTextShowOnlySummary, + $coverageXml, + $pathCoverage, + $warmCoverageCache, + $defaultTimeLimit, + $disableCodeCoverageIgnore, + $disallowTestOutput, + $enforceTimeLimit, + $excludeGroups, + $executionOrder, + $executionOrderDefects, + $failOnAllIssues, + $failOnDeprecation, + $failOnPhpunitDeprecation, + $failOnPhpunitNotice, + $failOnPhpunitWarning, + $failOnEmptyTestSuite, + $failOnIncomplete, + $failOnNotice, + $failOnRisky, + $failOnSkipped, + $failOnWarning, + $doNotFailOnDeprecation, + $doNotFailOnPhpunitDeprecation, + $doNotFailOnPhpunitNotice, + $doNotFailOnPhpunitWarning, + $doNotFailOnEmptyTestSuite, + $doNotFailOnIncomplete, + $doNotFailOnNotice, + $doNotFailOnRisky, + $doNotFailOnSkipped, + $doNotFailOnWarning, + $stopOnDefect, + $stopOnDeprecation, + $specificDeprecationToStopOn, + $stopOnError, + $stopOnFailure, + $stopOnIncomplete, + $stopOnNotice, + $stopOnRisky, + $stopOnSkipped, + $stopOnWarning, + $filter, + $excludeFilter, + $generateBaseline, + $useBaseline, + $ignoreBaseline, + $generateConfiguration, + $migrateConfiguration, + $groups, + $testsCovering, + $testsUsing, + $testsRequiringPhpExtension, + $help, + $includePath, + $iniSettings, + $junitLogfile, + $otrLogfile, + $includeGitInformation, + $listGroups, + $listSuites, + $listTestFiles, + $listTests, + $listTestsXml, + $noCoverage, + $noExtensions, + $noOutput, + $noProgress, + $noResults, + $noLogging, + $processIsolation, + $randomOrderSeed, + $reportUselessTests, + $resolveDependencies, + $reverseList, + $stderr, + $strictCoverage, + $teamcityLogfile, + $testdoxHtmlFile, + $testdoxTextFile, + $testSuffixes, + $testSuite, + $excludeTestSuite, + $useDefaultConfiguration, + $displayAllIssues, + $displayIncomplete, + $displaySkipped, + $displayDeprecations, + $displayPhpunitDeprecations, + $displayPhpunitNotices, + $displayErrors, + $displayNotices, + $displayWarnings, + $version, + $coverageFilter, + $logEventsText, + $logEventsVerboseText, + $printerTeamCity, + $printerTestDox, + $printerTestDoxSummary, + $debug, + $withTelemetry, + $extensions, + ); + } + + /** + * @param non-empty-string $option + */ + private function markProcessed(string $option): void + { + if (!isset($this->processed[$option])) { + $this->processed[$option] = 1; + + return; + } + + $this->processed[$option]++; + + if ($this->processed[$option] === 2) { + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + 'Option %s cannot be used more than once', + $option, + ), + ); + } + } + + /** + * @param non-empty-string $option + */ + private function warnWhenOptionsConflict(?bool $current, string $option, string $opposite): void + { + if ($current === null) { + return; + } + + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + 'Options %s and %s cannot be used together', + $option, + $opposite, + ), + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Cli/Configuration.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Cli/Configuration.php new file mode 100644 index 000000000..d2d0948f8 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Cli/Configuration.php @@ -0,0 +1,2585 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\CliArguments; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class Configuration +{ + /** + * @var list + */ + private array $arguments; + private ?string $atLeastVersion; + private ?bool $backupGlobals; + private ?bool $backupStaticProperties; + private ?bool $beStrictAboutChangesToGlobalState; + private ?string $bootstrap; + private ?string $cacheDirectory; + private ?bool $cacheResult; + private bool $checkPhpConfiguration; + private bool $checkVersion; + private ?string $colors; + private null|int|string $columns; + private ?string $configurationFile; + + /** + * @var ?non-empty-list + */ + private ?array $coverageFilter; + private ?string $coverageClover; + private ?string $coverageCobertura; + private ?string $coverageCrap4J; + private ?string $coverageHtml; + private ?string $coverageOpenClover; + private ?string $coveragePhp; + private ?string $coverageText; + private ?bool $coverageTextShowUncoveredFiles; + private ?bool $coverageTextShowOnlySummary; + private ?string $coverageXml; + private ?bool $pathCoverage; + private bool $warmCoverageCache; + private ?int $defaultTimeLimit; + private ?bool $disableCodeCoverageIgnore; + private ?bool $disallowTestOutput; + private ?bool $enforceTimeLimit; + + /** + * @var ?non-empty-list + */ + private ?array $excludeGroups; + private ?int $executionOrder; + private ?int $executionOrderDefects; + private ?bool $failOnAllIssues; + private ?bool $failOnDeprecation; + private ?bool $failOnPhpunitDeprecation; + private ?bool $failOnPhpunitNotice; + private ?bool $failOnPhpunitWarning; + private ?bool $failOnEmptyTestSuite; + private ?bool $failOnIncomplete; + private ?bool $failOnNotice; + private ?bool $failOnRisky; + private ?bool $failOnSkipped; + private ?bool $failOnWarning; + private ?bool $doNotFailOnDeprecation; + private ?bool $doNotFailOnPhpunitDeprecation; + private ?bool $doNotFailOnPhpunitNotice; + private ?bool $doNotFailOnPhpunitWarning; + private ?bool $doNotFailOnEmptyTestSuite; + private ?bool $doNotFailOnIncomplete; + private ?bool $doNotFailOnNotice; + private ?bool $doNotFailOnRisky; + private ?bool $doNotFailOnSkipped; + private ?bool $doNotFailOnWarning; + private ?bool $stopOnDefect; + private ?bool $stopOnDeprecation; + private ?string $specificDeprecationToStopOn; + private ?bool $stopOnError; + private ?bool $stopOnFailure; + private ?bool $stopOnIncomplete; + private ?bool $stopOnNotice; + private ?bool $stopOnRisky; + private ?bool $stopOnSkipped; + private ?bool $stopOnWarning; + private ?string $filter; + private ?string $excludeFilter; + private ?string $generateBaseline; + private ?string $useBaseline; + private bool $ignoreBaseline; + private bool $generateConfiguration; + private bool $migrateConfiguration; + + /** + * @var ?non-empty-list + */ + private ?array $groups; + + /** + * @var ?non-empty-list + */ + private ?array $testsCovering; + + /** + * @var ?non-empty-list + */ + private ?array $testsUsing; + + /** + * @var ?non-empty-list + */ + private ?array $testsRequiringPhpExtension; + private bool $help; + private ?string $includePath; + + /** + * @var ?non-empty-array + */ + private ?array $iniSettings; + private ?string $junitLogfile; + private ?string $otrLogfile; + private ?bool $includeGitInformationInOtrLogfile; + private bool $listGroups; + private bool $listSuites; + private bool $listTestFiles; + private bool $listTests; + private ?string $listTestsXml; + private ?bool $noCoverage; + private ?bool $noExtensions; + private ?bool $noOutput; + private ?bool $noProgress; + private ?bool $noResults; + private ?bool $noLogging; + private ?bool $processIsolation; + private ?int $randomOrderSeed; + private ?bool $reportUselessTests; + private ?bool $resolveDependencies; + private ?bool $reverseList; + private ?bool $stderr; + private ?bool $strictCoverage; + private ?string $teamcityLogfile; + private ?bool $teamCityPrinter; + private ?string $testdoxHtmlFile; + private ?string $testdoxTextFile; + private ?bool $testdoxPrinter; + private ?bool $testdoxPrinterSummary; + + /** + * @var ?non-empty-list + */ + private ?array $testSuffixes; + private ?string $testSuite; + private ?string $excludeTestSuite; + private bool $useDefaultConfiguration; + private ?bool $displayDetailsOnAllIssues; + private ?bool $displayDetailsOnIncompleteTests; + private ?bool $displayDetailsOnSkippedTests; + private ?bool $displayDetailsOnTestsThatTriggerDeprecations; + private ?bool $displayDetailsOnPhpunitDeprecations; + private ?bool $displayDetailsOnPhpunitNotices; + private ?bool $displayDetailsOnTestsThatTriggerErrors; + private ?bool $displayDetailsOnTestsThatTriggerNotices; + private ?bool $displayDetailsOnTestsThatTriggerWarnings; + private bool $version; + private ?string $logEventsText; + private ?string $logEventsVerboseText; + private bool $debug; + private bool $withTelemetry; + + /** + * @var ?non-empty-list + */ + private ?array $extensions; + + /** + * @param list $arguments + * @param ?non-empty-list $excludeGroups + * @param ?non-empty-list $groups + * @param ?non-empty-list $testsCovering + * @param ?non-empty-list $testsUsing + * @param ?non-empty-list $testsRequiringPhpExtension + * @param ?non-empty-array $iniSettings + * @param ?non-empty-list $testSuffixes + * @param ?non-empty-list $coverageFilter + * @param ?non-empty-list $extensions + */ + public function __construct(array $arguments, ?string $atLeastVersion, ?bool $backupGlobals, ?bool $backupStaticProperties, ?bool $beStrictAboutChangesToGlobalState, ?string $bootstrap, ?string $cacheDirectory, ?bool $cacheResult, bool $checkPhpConfiguration, bool $checkVersion, ?string $colors, null|int|string $columns, ?string $configurationFile, ?string $coverageClover, ?string $coverageCobertura, ?string $coverageCrap4J, ?string $coverageHtml, ?string $coverageOpenClover, ?string $coveragePhp, ?string $coverageText, ?bool $coverageTextShowUncoveredFiles, ?bool $coverageTextShowOnlySummary, ?string $coverageXml, ?bool $pathCoverage, bool $warmCoverageCache, ?int $defaultTimeLimit, ?bool $disableCodeCoverageIgnore, ?bool $disallowTestOutput, ?bool $enforceTimeLimit, ?array $excludeGroups, ?int $executionOrder, ?int $executionOrderDefects, ?bool $failOnAllIssues, ?bool $failOnDeprecation, ?bool $failOnPhpunitDeprecation, ?bool $failOnPhpunitNotice, ?bool $failOnPhpunitWarning, ?bool $failOnEmptyTestSuite, ?bool $failOnIncomplete, ?bool $failOnNotice, ?bool $failOnRisky, ?bool $failOnSkipped, ?bool $failOnWarning, ?bool $doNotFailOnDeprecation, ?bool $doNotFailOnPhpunitDeprecation, ?bool $doNotFailOnPhpunitNotice, ?bool $doNotFailOnPhpunitWarning, ?bool $doNotFailOnEmptyTestSuite, ?bool $doNotFailOnIncomplete, ?bool $doNotFailOnNotice, ?bool $doNotFailOnRisky, ?bool $doNotFailOnSkipped, ?bool $doNotFailOnWarning, ?bool $stopOnDefect, ?bool $stopOnDeprecation, ?string $specificDeprecationToStopOn, ?bool $stopOnError, ?bool $stopOnFailure, ?bool $stopOnIncomplete, ?bool $stopOnNotice, ?bool $stopOnRisky, ?bool $stopOnSkipped, ?bool $stopOnWarning, ?string $filter, ?string $excludeFilter, ?string $generateBaseline, ?string $useBaseline, bool $ignoreBaseline, bool $generateConfiguration, bool $migrateConfiguration, ?array $groups, ?array $testsCovering, ?array $testsUsing, ?array $testsRequiringPhpExtension, bool $help, ?string $includePath, ?array $iniSettings, ?string $junitLogfile, ?string $otrLogfile, ?bool $includeGitInformation, bool $listGroups, bool $listSuites, bool $listTestFiles, bool $listTests, ?string $listTestsXml, ?bool $noCoverage, ?bool $noExtensions, ?bool $noOutput, ?bool $noProgress, ?bool $noResults, ?bool $noLogging, ?bool $processIsolation, ?int $randomOrderSeed, ?bool $reportUselessTests, ?bool $resolveDependencies, ?bool $reverseList, ?bool $stderr, ?bool $strictCoverage, ?string $teamcityLogfile, ?string $testdoxHtmlFile, ?string $testdoxTextFile, ?array $testSuffixes, ?string $testSuite, ?string $excludeTestSuite, bool $useDefaultConfiguration, ?bool $displayDetailsOnAllIssues, ?bool $displayDetailsOnIncompleteTests, ?bool $displayDetailsOnSkippedTests, ?bool $displayDetailsOnTestsThatTriggerDeprecations, ?bool $displayDetailsOnPhpunitDeprecations, ?bool $displayDetailsOnPhpunitNotices, ?bool $displayDetailsOnTestsThatTriggerErrors, ?bool $displayDetailsOnTestsThatTriggerNotices, ?bool $displayDetailsOnTestsThatTriggerWarnings, bool $version, ?array $coverageFilter, ?string $logEventsText, ?string $logEventsVerboseText, ?bool $printerTeamCity, ?bool $testdoxPrinter, ?bool $testdoxPrinterSummary, bool $debug, bool $withTelemetry, ?array $extensions) + { + $this->arguments = $arguments; + $this->atLeastVersion = $atLeastVersion; + $this->backupGlobals = $backupGlobals; + $this->backupStaticProperties = $backupStaticProperties; + $this->beStrictAboutChangesToGlobalState = $beStrictAboutChangesToGlobalState; + $this->bootstrap = $bootstrap; + $this->cacheDirectory = $cacheDirectory; + $this->cacheResult = $cacheResult; + $this->checkPhpConfiguration = $checkPhpConfiguration; + $this->checkVersion = $checkVersion; + $this->colors = $colors; + $this->columns = $columns; + $this->configurationFile = $configurationFile; + $this->coverageFilter = $coverageFilter; + $this->coverageClover = $coverageClover; + $this->coverageCobertura = $coverageCobertura; + $this->coverageCrap4J = $coverageCrap4J; + $this->coverageHtml = $coverageHtml; + $this->coverageOpenClover = $coverageOpenClover; + $this->coveragePhp = $coveragePhp; + $this->coverageText = $coverageText; + $this->coverageTextShowUncoveredFiles = $coverageTextShowUncoveredFiles; + $this->coverageTextShowOnlySummary = $coverageTextShowOnlySummary; + $this->coverageXml = $coverageXml; + $this->pathCoverage = $pathCoverage; + $this->warmCoverageCache = $warmCoverageCache; + $this->defaultTimeLimit = $defaultTimeLimit; + $this->disableCodeCoverageIgnore = $disableCodeCoverageIgnore; + $this->disallowTestOutput = $disallowTestOutput; + $this->enforceTimeLimit = $enforceTimeLimit; + $this->excludeGroups = $excludeGroups; + $this->executionOrder = $executionOrder; + $this->executionOrderDefects = $executionOrderDefects; + $this->failOnAllIssues = $failOnAllIssues; + $this->failOnDeprecation = $failOnDeprecation; + $this->failOnPhpunitDeprecation = $failOnPhpunitDeprecation; + $this->failOnPhpunitNotice = $failOnPhpunitNotice; + $this->failOnPhpunitWarning = $failOnPhpunitWarning; + $this->failOnEmptyTestSuite = $failOnEmptyTestSuite; + $this->failOnIncomplete = $failOnIncomplete; + $this->failOnNotice = $failOnNotice; + $this->failOnRisky = $failOnRisky; + $this->failOnSkipped = $failOnSkipped; + $this->failOnWarning = $failOnWarning; + $this->doNotFailOnDeprecation = $doNotFailOnDeprecation; + $this->doNotFailOnPhpunitDeprecation = $doNotFailOnPhpunitDeprecation; + $this->doNotFailOnPhpunitNotice = $doNotFailOnPhpunitNotice; + $this->doNotFailOnPhpunitWarning = $doNotFailOnPhpunitWarning; + $this->doNotFailOnEmptyTestSuite = $doNotFailOnEmptyTestSuite; + $this->doNotFailOnIncomplete = $doNotFailOnIncomplete; + $this->doNotFailOnNotice = $doNotFailOnNotice; + $this->doNotFailOnRisky = $doNotFailOnRisky; + $this->doNotFailOnSkipped = $doNotFailOnSkipped; + $this->doNotFailOnWarning = $doNotFailOnWarning; + $this->stopOnDefect = $stopOnDefect; + $this->stopOnDeprecation = $stopOnDeprecation; + $this->specificDeprecationToStopOn = $specificDeprecationToStopOn; + $this->stopOnError = $stopOnError; + $this->stopOnFailure = $stopOnFailure; + $this->stopOnIncomplete = $stopOnIncomplete; + $this->stopOnNotice = $stopOnNotice; + $this->stopOnRisky = $stopOnRisky; + $this->stopOnSkipped = $stopOnSkipped; + $this->stopOnWarning = $stopOnWarning; + $this->filter = $filter; + $this->excludeFilter = $excludeFilter; + $this->generateBaseline = $generateBaseline; + $this->useBaseline = $useBaseline; + $this->ignoreBaseline = $ignoreBaseline; + $this->generateConfiguration = $generateConfiguration; + $this->migrateConfiguration = $migrateConfiguration; + $this->groups = $groups; + $this->testsCovering = $testsCovering; + $this->testsUsing = $testsUsing; + $this->testsRequiringPhpExtension = $testsRequiringPhpExtension; + $this->help = $help; + $this->includePath = $includePath; + $this->iniSettings = $iniSettings; + $this->junitLogfile = $junitLogfile; + $this->otrLogfile = $otrLogfile; + $this->includeGitInformationInOtrLogfile = $includeGitInformation; + $this->listGroups = $listGroups; + $this->listSuites = $listSuites; + $this->listTestFiles = $listTestFiles; + $this->listTests = $listTests; + $this->listTestsXml = $listTestsXml; + $this->noCoverage = $noCoverage; + $this->noExtensions = $noExtensions; + $this->noOutput = $noOutput; + $this->noProgress = $noProgress; + $this->noResults = $noResults; + $this->noLogging = $noLogging; + $this->processIsolation = $processIsolation; + $this->randomOrderSeed = $randomOrderSeed; + $this->reportUselessTests = $reportUselessTests; + $this->resolveDependencies = $resolveDependencies; + $this->reverseList = $reverseList; + $this->stderr = $stderr; + $this->strictCoverage = $strictCoverage; + $this->teamcityLogfile = $teamcityLogfile; + $this->testdoxHtmlFile = $testdoxHtmlFile; + $this->testdoxTextFile = $testdoxTextFile; + $this->testSuffixes = $testSuffixes; + $this->testSuite = $testSuite; + $this->excludeTestSuite = $excludeTestSuite; + $this->useDefaultConfiguration = $useDefaultConfiguration; + $this->displayDetailsOnAllIssues = $displayDetailsOnAllIssues; + $this->displayDetailsOnIncompleteTests = $displayDetailsOnIncompleteTests; + $this->displayDetailsOnSkippedTests = $displayDetailsOnSkippedTests; + $this->displayDetailsOnTestsThatTriggerDeprecations = $displayDetailsOnTestsThatTriggerDeprecations; + $this->displayDetailsOnPhpunitDeprecations = $displayDetailsOnPhpunitDeprecations; + $this->displayDetailsOnPhpunitNotices = $displayDetailsOnPhpunitNotices; + $this->displayDetailsOnTestsThatTriggerErrors = $displayDetailsOnTestsThatTriggerErrors; + $this->displayDetailsOnTestsThatTriggerNotices = $displayDetailsOnTestsThatTriggerNotices; + $this->displayDetailsOnTestsThatTriggerWarnings = $displayDetailsOnTestsThatTriggerWarnings; + $this->version = $version; + $this->logEventsText = $logEventsText; + $this->logEventsVerboseText = $logEventsVerboseText; + $this->teamCityPrinter = $printerTeamCity; + $this->testdoxPrinter = $testdoxPrinter; + $this->testdoxPrinterSummary = $testdoxPrinterSummary; + $this->debug = $debug; + $this->withTelemetry = $withTelemetry; + $this->extensions = $extensions; + } + + /** + * @return list + */ + public function arguments(): array + { + return $this->arguments; + } + + /** + * @phpstan-assert-if-true !null $this->atLeastVersion + */ + public function hasAtLeastVersion(): bool + { + return $this->atLeastVersion !== null; + } + + /** + * @throws Exception + */ + public function atLeastVersion(): string + { + if (!$this->hasAtLeastVersion()) { + throw new Exception; + } + + return $this->atLeastVersion; + } + + /** + * @phpstan-assert-if-true !null $this->backupGlobals + */ + public function hasBackupGlobals(): bool + { + return $this->backupGlobals !== null; + } + + /** + * @throws Exception + */ + public function backupGlobals(): bool + { + if (!$this->hasBackupGlobals()) { + throw new Exception; + } + + return $this->backupGlobals; + } + + /** + * @phpstan-assert-if-true !null $this->backupStaticProperties + */ + public function hasBackupStaticProperties(): bool + { + return $this->backupStaticProperties !== null; + } + + /** + * @throws Exception + */ + public function backupStaticProperties(): bool + { + if (!$this->hasBackupStaticProperties()) { + throw new Exception; + } + + return $this->backupStaticProperties; + } + + /** + * @phpstan-assert-if-true !null $this->beStrictAboutChangesToGlobalState + */ + public function hasBeStrictAboutChangesToGlobalState(): bool + { + return $this->beStrictAboutChangesToGlobalState !== null; + } + + /** + * @throws Exception + */ + public function beStrictAboutChangesToGlobalState(): bool + { + if (!$this->hasBeStrictAboutChangesToGlobalState()) { + throw new Exception; + } + + return $this->beStrictAboutChangesToGlobalState; + } + + /** + * @phpstan-assert-if-true !null $this->bootstrap + */ + public function hasBootstrap(): bool + { + return $this->bootstrap !== null; + } + + /** + * @throws Exception + */ + public function bootstrap(): string + { + if (!$this->hasBootstrap()) { + throw new Exception; + } + + return $this->bootstrap; + } + + /** + * @phpstan-assert-if-true !null $this->cacheDirectory + */ + public function hasCacheDirectory(): bool + { + return $this->cacheDirectory !== null; + } + + /** + * @throws Exception + */ + public function cacheDirectory(): string + { + if (!$this->hasCacheDirectory()) { + throw new Exception; + } + + return $this->cacheDirectory; + } + + /** + * @phpstan-assert-if-true !null $this->cacheResult + */ + public function hasCacheResult(): bool + { + return $this->cacheResult !== null; + } + + /** + * @throws Exception + */ + public function cacheResult(): bool + { + if (!$this->hasCacheResult()) { + throw new Exception; + } + + return $this->cacheResult; + } + + public function checkPhpConfiguration(): bool + { + return $this->checkPhpConfiguration; + } + + public function checkVersion(): bool + { + return $this->checkVersion; + } + + /** + * @phpstan-assert-if-true !null $this->colors + */ + public function hasColors(): bool + { + return $this->colors !== null; + } + + /** + * @throws Exception + */ + public function colors(): string + { + if (!$this->hasColors()) { + throw new Exception; + } + + return $this->colors; + } + + /** + * @phpstan-assert-if-true !null $this->columns + */ + public function hasColumns(): bool + { + return $this->columns !== null; + } + + /** + * @throws Exception + */ + public function columns(): int|string + { + if (!$this->hasColumns()) { + throw new Exception; + } + + return $this->columns; + } + + /** + * @phpstan-assert-if-true !null $this->configurationFile + */ + public function hasConfigurationFile(): bool + { + return $this->configurationFile !== null; + } + + /** + * @throws Exception + */ + public function configurationFile(): string + { + if (!$this->hasConfigurationFile()) { + throw new Exception; + } + + return $this->configurationFile; + } + + /** + * @phpstan-assert-if-true !null $this->coverageFilter + */ + public function hasCoverageFilter(): bool + { + return $this->coverageFilter !== null; + } + + /** + * @throws Exception + * + * @return non-empty-list + */ + public function coverageFilter(): array + { + if (!$this->hasCoverageFilter()) { + throw new Exception; + } + + return $this->coverageFilter; + } + + /** + * @phpstan-assert-if-true !null $this->coverageClover + */ + public function hasCoverageClover(): bool + { + return $this->coverageClover !== null; + } + + /** + * @throws Exception + */ + public function coverageClover(): string + { + if (!$this->hasCoverageClover()) { + throw new Exception; + } + + return $this->coverageClover; + } + + /** + * @phpstan-assert-if-true !null $this->coverageCobertura + */ + public function hasCoverageCobertura(): bool + { + return $this->coverageCobertura !== null; + } + + /** + * @throws Exception + */ + public function coverageCobertura(): string + { + if (!$this->hasCoverageCobertura()) { + throw new Exception; + } + + return $this->coverageCobertura; + } + + /** + * @phpstan-assert-if-true !null $this->coverageCrap4J + */ + public function hasCoverageCrap4J(): bool + { + return $this->coverageCrap4J !== null; + } + + /** + * @throws Exception + */ + public function coverageCrap4J(): string + { + if (!$this->hasCoverageCrap4J()) { + throw new Exception; + } + + return $this->coverageCrap4J; + } + + /** + * @phpstan-assert-if-true !null $this->coverageHtml + */ + public function hasCoverageHtml(): bool + { + return $this->coverageHtml !== null; + } + + /** + * @throws Exception + */ + public function coverageHtml(): string + { + if (!$this->hasCoverageHtml()) { + throw new Exception; + } + + return $this->coverageHtml; + } + + /** + * @phpstan-assert-if-true !null $this->coverageOpenClover + */ + public function hasCoverageOpenClover(): bool + { + return $this->coverageOpenClover !== null; + } + + /** + * @throws Exception + */ + public function coverageOpenClover(): string + { + if (!$this->hasCoverageOpenClover()) { + throw new Exception; + } + + return $this->coverageOpenClover; + } + + /** + * @phpstan-assert-if-true !null $this->coveragePhp + */ + public function hasCoveragePhp(): bool + { + return $this->coveragePhp !== null; + } + + /** + * @throws Exception + */ + public function coveragePhp(): string + { + if (!$this->hasCoveragePhp()) { + throw new Exception; + } + + return $this->coveragePhp; + } + + /** + * @phpstan-assert-if-true !null $this->coverageText + */ + public function hasCoverageText(): bool + { + return $this->coverageText !== null; + } + + /** + * @throws Exception + */ + public function coverageText(): string + { + if (!$this->hasCoverageText()) { + throw new Exception; + } + + return $this->coverageText; + } + + /** + * @phpstan-assert-if-true !null $this->coverageTextShowUncoveredFiles + */ + public function hasCoverageTextShowUncoveredFiles(): bool + { + return $this->coverageTextShowUncoveredFiles !== null; + } + + /** + * @throws Exception + */ + public function coverageTextShowUncoveredFiles(): bool + { + if (!$this->hasCoverageTextShowUncoveredFiles()) { + throw new Exception; + } + + return $this->coverageTextShowUncoveredFiles; + } + + /** + * @phpstan-assert-if-true !null $this->coverageTextShowOnlySummary + */ + public function hasCoverageTextShowOnlySummary(): bool + { + return $this->coverageTextShowOnlySummary !== null; + } + + /** + * @throws Exception + */ + public function coverageTextShowOnlySummary(): bool + { + if (!$this->hasCoverageTextShowOnlySummary()) { + throw new Exception; + } + + return $this->coverageTextShowOnlySummary; + } + + /** + * @phpstan-assert-if-true !null $this->coverageXml + */ + public function hasCoverageXml(): bool + { + return $this->coverageXml !== null; + } + + /** + * @throws Exception + */ + public function coverageXml(): string + { + if (!$this->hasCoverageXml()) { + throw new Exception; + } + + return $this->coverageXml; + } + + /** + * @phpstan-assert-if-true !null $this->pathCoverage + */ + public function hasPathCoverage(): bool + { + return $this->pathCoverage !== null; + } + + /** + * @throws Exception + */ + public function pathCoverage(): bool + { + if (!$this->hasPathCoverage()) { + throw new Exception; + } + + return $this->pathCoverage; + } + + public function warmCoverageCache(): bool + { + return $this->warmCoverageCache; + } + + /** + * @phpstan-assert-if-true !null $this->defaultTimeLimit + */ + public function hasDefaultTimeLimit(): bool + { + return $this->defaultTimeLimit !== null; + } + + /** + * @throws Exception + */ + public function defaultTimeLimit(): int + { + if (!$this->hasDefaultTimeLimit()) { + throw new Exception; + } + + return $this->defaultTimeLimit; + } + + /** + * @phpstan-assert-if-true !null $this->disableCodeCoverageIgnore + */ + public function hasDisableCodeCoverageIgnore(): bool + { + return $this->disableCodeCoverageIgnore !== null; + } + + /** + * @throws Exception + */ + public function disableCodeCoverageIgnore(): bool + { + if (!$this->hasDisableCodeCoverageIgnore()) { + throw new Exception; + } + + return $this->disableCodeCoverageIgnore; + } + + /** + * @phpstan-assert-if-true !null $this->disallowTestOutput + */ + public function hasDisallowTestOutput(): bool + { + return $this->disallowTestOutput !== null; + } + + /** + * @throws Exception + */ + public function disallowTestOutput(): bool + { + if (!$this->hasDisallowTestOutput()) { + throw new Exception; + } + + return $this->disallowTestOutput; + } + + /** + * @phpstan-assert-if-true !null $this->enforceTimeLimit + */ + public function hasEnforceTimeLimit(): bool + { + return $this->enforceTimeLimit !== null; + } + + /** + * @throws Exception + */ + public function enforceTimeLimit(): bool + { + if (!$this->hasEnforceTimeLimit()) { + throw new Exception; + } + + return $this->enforceTimeLimit; + } + + /** + * @phpstan-assert-if-true !null $this->excludeGroups + */ + public function hasExcludeGroups(): bool + { + return $this->excludeGroups !== null; + } + + /** + * @throws Exception + * + * @return non-empty-list + */ + public function excludeGroups(): array + { + if (!$this->hasExcludeGroups()) { + throw new Exception; + } + + return $this->excludeGroups; + } + + /** + * @phpstan-assert-if-true !null $this->executionOrder + */ + public function hasExecutionOrder(): bool + { + return $this->executionOrder !== null; + } + + /** + * @throws Exception + */ + public function executionOrder(): int + { + if (!$this->hasExecutionOrder()) { + throw new Exception; + } + + return $this->executionOrder; + } + + /** + * @phpstan-assert-if-true !null $this->executionOrderDefects + */ + public function hasExecutionOrderDefects(): bool + { + return $this->executionOrderDefects !== null; + } + + /** + * @throws Exception + */ + public function executionOrderDefects(): int + { + if (!$this->hasExecutionOrderDefects()) { + throw new Exception; + } + + return $this->executionOrderDefects; + } + + /** + * @phpstan-assert-if-true !null $this->failOnAllIssues + */ + public function hasFailOnAllIssues(): bool + { + return $this->failOnAllIssues !== null; + } + + /** + * @throws Exception + */ + public function failOnAllIssues(): bool + { + if (!$this->hasFailOnAllIssues()) { + throw new Exception; + } + + return $this->failOnAllIssues; + } + + /** + * @phpstan-assert-if-true !null $this->failOnDeprecation + */ + public function hasFailOnDeprecation(): bool + { + return $this->failOnDeprecation !== null; + } + + /** + * @throws Exception + */ + public function failOnDeprecation(): bool + { + if (!$this->hasFailOnDeprecation()) { + throw new Exception; + } + + return $this->failOnDeprecation; + } + + /** + * @phpstan-assert-if-true !null $this->failOnPhpunitDeprecation + */ + public function hasFailOnPhpunitDeprecation(): bool + { + return $this->failOnPhpunitDeprecation !== null; + } + + /** + * @throws Exception + */ + public function failOnPhpunitDeprecation(): bool + { + if (!$this->hasFailOnPhpunitDeprecation()) { + throw new Exception; + } + + return $this->failOnPhpunitDeprecation; + } + + /** + * @phpstan-assert-if-true !null $this->failOnPhpunitNotice + */ + public function hasFailOnPhpunitNotice(): bool + { + return $this->failOnPhpunitNotice !== null; + } + + /** + * @throws Exception + */ + public function failOnPhpunitNotice(): bool + { + if (!$this->hasFailOnPhpunitNotice()) { + throw new Exception; + } + + return $this->failOnPhpunitNotice; + } + + /** + * @phpstan-assert-if-true !null $this->failOnPhpunitWarning + */ + public function hasFailOnPhpunitWarning(): bool + { + return $this->failOnPhpunitWarning !== null; + } + + /** + * @throws Exception + */ + public function failOnPhpunitWarning(): bool + { + if (!$this->hasFailOnPhpunitWarning()) { + throw new Exception; + } + + return $this->failOnPhpunitWarning; + } + + /** + * @phpstan-assert-if-true !null $this->failOnEmptyTestSuite + */ + public function hasFailOnEmptyTestSuite(): bool + { + return $this->failOnEmptyTestSuite !== null; + } + + /** + * @throws Exception + */ + public function failOnEmptyTestSuite(): bool + { + if (!$this->hasFailOnEmptyTestSuite()) { + throw new Exception; + } + + return $this->failOnEmptyTestSuite; + } + + /** + * @phpstan-assert-if-true !null $this->failOnIncomplete + */ + public function hasFailOnIncomplete(): bool + { + return $this->failOnIncomplete !== null; + } + + /** + * @throws Exception + */ + public function failOnIncomplete(): bool + { + if (!$this->hasFailOnIncomplete()) { + throw new Exception; + } + + return $this->failOnIncomplete; + } + + /** + * @phpstan-assert-if-true !null $this->failOnNotice + */ + public function hasFailOnNotice(): bool + { + return $this->failOnNotice !== null; + } + + /** + * @throws Exception + */ + public function failOnNotice(): bool + { + if (!$this->hasFailOnNotice()) { + throw new Exception; + } + + return $this->failOnNotice; + } + + /** + * @phpstan-assert-if-true !null $this->failOnRisky + */ + public function hasFailOnRisky(): bool + { + return $this->failOnRisky !== null; + } + + /** + * @throws Exception + */ + public function failOnRisky(): bool + { + if (!$this->hasFailOnRisky()) { + throw new Exception; + } + + return $this->failOnRisky; + } + + /** + * @phpstan-assert-if-true !null $this->failOnSkipped + */ + public function hasFailOnSkipped(): bool + { + return $this->failOnSkipped !== null; + } + + /** + * @throws Exception + */ + public function failOnSkipped(): bool + { + if (!$this->hasFailOnSkipped()) { + throw new Exception; + } + + return $this->failOnSkipped; + } + + /** + * @phpstan-assert-if-true !null $this->failOnWarning + */ + public function hasFailOnWarning(): bool + { + return $this->failOnWarning !== null; + } + + /** + * @throws Exception + */ + public function failOnWarning(): bool + { + if (!$this->hasFailOnWarning()) { + throw new Exception; + } + + return $this->failOnWarning; + } + + /** + * @phpstan-assert-if-true !null $this->doNotFailOnDeprecation + */ + public function hasDoNotFailOnDeprecation(): bool + { + return $this->doNotFailOnDeprecation !== null; + } + + /** + * @throws Exception + */ + public function doNotFailOnDeprecation(): bool + { + if (!$this->hasDoNotFailOnDeprecation()) { + throw new Exception; + } + + return $this->doNotFailOnDeprecation; + } + + /** + * @phpstan-assert-if-true !null $this->doNotFailOnPhpunitDeprecation + */ + public function hasDoNotFailOnPhpunitDeprecation(): bool + { + return $this->doNotFailOnPhpunitDeprecation !== null; + } + + /** + * @throws Exception + */ + public function doNotFailOnPhpunitDeprecation(): bool + { + if (!$this->hasDoNotFailOnPhpunitDeprecation()) { + throw new Exception; + } + + return $this->doNotFailOnPhpunitDeprecation; + } + + /** + * @phpstan-assert-if-true !null $this->doNotFailOnPhpunitNotice + */ + public function hasDoNotFailOnPhpunitNotice(): bool + { + return $this->doNotFailOnPhpunitNotice !== null; + } + + /** + * @throws Exception + */ + public function doNotFailOnPhpunitNotice(): bool + { + if (!$this->hasDoNotFailOnPhpunitNotice()) { + throw new Exception; + } + + return $this->doNotFailOnPhpunitNotice; + } + + /** + * @phpstan-assert-if-true !null $this->doNotFailOnPhpunitWarning + */ + public function hasDoNotFailOnPhpunitWarning(): bool + { + return $this->doNotFailOnPhpunitWarning !== null; + } + + /** + * @throws Exception + */ + public function doNotFailOnPhpunitWarning(): bool + { + if (!$this->hasDoNotFailOnPhpunitWarning()) { + throw new Exception; + } + + return $this->doNotFailOnPhpunitWarning; + } + + /** + * @phpstan-assert-if-true !null $this->doNotFailOnEmptyTestSuite + */ + public function hasDoNotFailOnEmptyTestSuite(): bool + { + return $this->doNotFailOnEmptyTestSuite !== null; + } + + /** + * @throws Exception + */ + public function doNotFailOnEmptyTestSuite(): bool + { + if (!$this->hasDoNotFailOnEmptyTestSuite()) { + throw new Exception; + } + + return $this->doNotFailOnEmptyTestSuite; + } + + /** + * @phpstan-assert-if-true !null $this->doNotFailOnIncomplete + */ + public function hasDoNotFailOnIncomplete(): bool + { + return $this->doNotFailOnIncomplete !== null; + } + + /** + * @throws Exception + */ + public function doNotFailOnIncomplete(): bool + { + if (!$this->hasDoNotFailOnIncomplete()) { + throw new Exception; + } + + return $this->doNotFailOnIncomplete; + } + + /** + * @phpstan-assert-if-true !null $this->doNotFailOnNotice + */ + public function hasDoNotFailOnNotice(): bool + { + return $this->doNotFailOnNotice !== null; + } + + /** + * @throws Exception + */ + public function doNotFailOnNotice(): bool + { + if (!$this->hasDoNotFailOnNotice()) { + throw new Exception; + } + + return $this->doNotFailOnNotice; + } + + /** + * @phpstan-assert-if-true !null $this->doNotFailOnRisky + */ + public function hasDoNotFailOnRisky(): bool + { + return $this->doNotFailOnRisky !== null; + } + + /** + * @throws Exception + */ + public function doNotFailOnRisky(): bool + { + if (!$this->hasDoNotFailOnRisky()) { + throw new Exception; + } + + return $this->doNotFailOnRisky; + } + + /** + * @phpstan-assert-if-true !null $this->doNotFailOnSkipped + */ + public function hasDoNotFailOnSkipped(): bool + { + return $this->doNotFailOnSkipped !== null; + } + + /** + * @throws Exception + */ + public function doNotFailOnSkipped(): bool + { + if (!$this->hasDoNotFailOnSkipped()) { + throw new Exception; + } + + return $this->doNotFailOnSkipped; + } + + /** + * @phpstan-assert-if-true !null $this->doNotFailOnWarning + */ + public function hasDoNotFailOnWarning(): bool + { + return $this->doNotFailOnWarning !== null; + } + + /** + * @throws Exception + */ + public function doNotFailOnWarning(): bool + { + if (!$this->hasDoNotFailOnWarning()) { + throw new Exception; + } + + return $this->doNotFailOnWarning; + } + + /** + * @phpstan-assert-if-true !null $this->stopOnDefect + */ + public function hasStopOnDefect(): bool + { + return $this->stopOnDefect !== null; + } + + /** + * @throws Exception + */ + public function stopOnDefect(): bool + { + if (!$this->hasStopOnDefect()) { + throw new Exception; + } + + return $this->stopOnDefect; + } + + /** + * @phpstan-assert-if-true !null $this->stopOnDeprecation + */ + public function hasStopOnDeprecation(): bool + { + return $this->stopOnDeprecation !== null; + } + + /** + * @throws Exception + */ + public function stopOnDeprecation(): bool + { + if (!$this->hasStopOnDeprecation()) { + throw new Exception; + } + + return $this->stopOnDeprecation; + } + + /** + * @phpstan-assert-if-true !null $this->specificDeprecationToStopOn + */ + public function hasSpecificDeprecationToStopOn(): bool + { + return $this->specificDeprecationToStopOn !== null; + } + + /** + * @throws Exception + */ + public function specificDeprecationToStopOn(): string + { + if (!$this->hasSpecificDeprecationToStopOn()) { + throw new Exception; + } + + return $this->specificDeprecationToStopOn; + } + + /** + * @phpstan-assert-if-true !null $this->stopOnError + */ + public function hasStopOnError(): bool + { + return $this->stopOnError !== null; + } + + /** + * @throws Exception + */ + public function stopOnError(): bool + { + if (!$this->hasStopOnError()) { + throw new Exception; + } + + return $this->stopOnError; + } + + /** + * @phpstan-assert-if-true !null $this->stopOnFailure + */ + public function hasStopOnFailure(): bool + { + return $this->stopOnFailure !== null; + } + + /** + * @throws Exception + */ + public function stopOnFailure(): bool + { + if (!$this->hasStopOnFailure()) { + throw new Exception; + } + + return $this->stopOnFailure; + } + + /** + * @phpstan-assert-if-true !null $this->stopOnIncomplete + */ + public function hasStopOnIncomplete(): bool + { + return $this->stopOnIncomplete !== null; + } + + /** + * @throws Exception + */ + public function stopOnIncomplete(): bool + { + if (!$this->hasStopOnIncomplete()) { + throw new Exception; + } + + return $this->stopOnIncomplete; + } + + /** + * @phpstan-assert-if-true !null $this->stopOnNotice + */ + public function hasStopOnNotice(): bool + { + return $this->stopOnNotice !== null; + } + + /** + * @throws Exception + */ + public function stopOnNotice(): bool + { + if (!$this->hasStopOnNotice()) { + throw new Exception; + } + + return $this->stopOnNotice; + } + + /** + * @phpstan-assert-if-true !null $this->stopOnRisky + */ + public function hasStopOnRisky(): bool + { + return $this->stopOnRisky !== null; + } + + /** + * @throws Exception + */ + public function stopOnRisky(): bool + { + if (!$this->hasStopOnRisky()) { + throw new Exception; + } + + return $this->stopOnRisky; + } + + /** + * @phpstan-assert-if-true !null $this->stopOnSkipped + */ + public function hasStopOnSkipped(): bool + { + return $this->stopOnSkipped !== null; + } + + /** + * @throws Exception + */ + public function stopOnSkipped(): bool + { + if (!$this->hasStopOnSkipped()) { + throw new Exception; + } + + return $this->stopOnSkipped; + } + + /** + * @phpstan-assert-if-true !null $this->stopOnWarning + */ + public function hasStopOnWarning(): bool + { + return $this->stopOnWarning !== null; + } + + /** + * @throws Exception + */ + public function stopOnWarning(): bool + { + if (!$this->hasStopOnWarning()) { + throw new Exception; + } + + return $this->stopOnWarning; + } + + /** + * @phpstan-assert-if-true !null $this->excludeFilter + */ + public function hasExcludeFilter(): bool + { + return $this->excludeFilter !== null; + } + + /** + * @throws Exception + */ + public function excludeFilter(): string + { + if (!$this->hasExcludeFilter()) { + throw new Exception; + } + + return $this->excludeFilter; + } + + /** + * @phpstan-assert-if-true !null $this->filter + */ + public function hasFilter(): bool + { + return $this->filter !== null; + } + + /** + * @throws Exception + */ + public function filter(): string + { + if (!$this->hasFilter()) { + throw new Exception; + } + + return $this->filter; + } + + /** + * @phpstan-assert-if-true !null $this->generateBaseline + */ + public function hasGenerateBaseline(): bool + { + return $this->generateBaseline !== null; + } + + /** + * @throws Exception + */ + public function generateBaseline(): string + { + if (!$this->hasGenerateBaseline()) { + throw new Exception; + } + + return $this->generateBaseline; + } + + /** + * @phpstan-assert-if-true !null $this->useBaseline + */ + public function hasUseBaseline(): bool + { + return $this->useBaseline !== null; + } + + /** + * @throws Exception + */ + public function useBaseline(): string + { + if (!$this->hasUseBaseline()) { + throw new Exception; + } + + return $this->useBaseline; + } + + public function ignoreBaseline(): bool + { + return $this->ignoreBaseline; + } + + public function generateConfiguration(): bool + { + return $this->generateConfiguration; + } + + public function migrateConfiguration(): bool + { + return $this->migrateConfiguration; + } + + /** + * @phpstan-assert-if-true !null $this->groups + */ + public function hasGroups(): bool + { + return $this->groups !== null; + } + + /** + * @throws Exception + * + * @return non-empty-list + */ + public function groups(): array + { + if (!$this->hasGroups()) { + throw new Exception; + } + + return $this->groups; + } + + /** + * @phpstan-assert-if-true !null $this->testsCovering + */ + public function hasTestsCovering(): bool + { + return $this->testsCovering !== null; + } + + /** + * @throws Exception + * + * @return non-empty-list + */ + public function testsCovering(): array + { + if (!$this->hasTestsCovering()) { + throw new Exception; + } + + return $this->testsCovering; + } + + /** + * @phpstan-assert-if-true !null $this->testsUsing + */ + public function hasTestsUsing(): bool + { + return $this->testsUsing !== null; + } + + /** + * @throws Exception + * + * @return non-empty-list + */ + public function testsUsing(): array + { + if (!$this->hasTestsUsing()) { + throw new Exception; + } + + return $this->testsUsing; + } + + /** + * @phpstan-assert-if-true !null $this->testsRequiringPhpExtension + */ + public function hasTestsRequiringPhpExtension(): bool + { + return $this->testsRequiringPhpExtension !== null; + } + + /** + * @throws Exception + * + * @return non-empty-list + */ + public function testsRequiringPhpExtension(): array + { + if (!$this->hasTestsRequiringPhpExtension()) { + throw new Exception; + } + + return $this->testsRequiringPhpExtension; + } + + public function help(): bool + { + return $this->help; + } + + /** + * @phpstan-assert-if-true !null $this->includePath + */ + public function hasIncludePath(): bool + { + return $this->includePath !== null; + } + + /** + * @throws Exception + */ + public function includePath(): string + { + if (!$this->hasIncludePath()) { + throw new Exception; + } + + return $this->includePath; + } + + /** + * @phpstan-assert-if-true !null $this->iniSettings + */ + public function hasIniSettings(): bool + { + return $this->iniSettings !== null; + } + + /** + * @throws Exception + * + * @return non-empty-array + */ + public function iniSettings(): array + { + if (!$this->hasIniSettings()) { + throw new Exception; + } + + return $this->iniSettings; + } + + /** + * @phpstan-assert-if-true !null $this->junitLogfile + */ + public function hasJunitLogfile(): bool + { + return $this->junitLogfile !== null; + } + + /** + * @throws Exception + */ + public function junitLogfile(): string + { + if (!$this->hasJunitLogfile()) { + throw new Exception; + } + + return $this->junitLogfile; + } + + /** + * @phpstan-assert-if-true !null $this->otrLogfile + */ + public function hasOtrLogfile(): bool + { + return $this->otrLogfile !== null; + } + + /** + * @throws Exception + */ + public function otrLogfile(): string + { + if (!$this->hasOtrLogfile()) { + throw new Exception; + } + + return $this->otrLogfile; + } + + /** + * @phpstan-assert-if-true !null $this->includeGitInformationInOtrLogfile + */ + public function hasIncludeGitInformationInOtrLogfile(): bool + { + return $this->includeGitInformationInOtrLogfile !== null; + } + + /** + * @throws Exception + */ + public function includeGitInformationInOtrLogfile(): bool + { + if (!$this->hasIncludeGitInformationInOtrLogfile()) { + throw new Exception; + } + + return $this->includeGitInformationInOtrLogfile; + } + + public function listGroups(): bool + { + return $this->listGroups; + } + + public function listSuites(): bool + { + return $this->listSuites; + } + + public function listTestFiles(): bool + { + return $this->listTestFiles; + } + + public function listTests(): bool + { + return $this->listTests; + } + + /** + * @phpstan-assert-if-true !null $this->listTestsXml + */ + public function hasListTestsXml(): bool + { + return $this->listTestsXml !== null; + } + + /** + * @throws Exception + */ + public function listTestsXml(): string + { + if (!$this->hasListTestsXml()) { + throw new Exception; + } + + return $this->listTestsXml; + } + + /** + * @phpstan-assert-if-true !null $this->noCoverage + */ + public function hasNoCoverage(): bool + { + return $this->noCoverage !== null; + } + + /** + * @throws Exception + */ + public function noCoverage(): bool + { + if (!$this->hasNoCoverage()) { + throw new Exception; + } + + return $this->noCoverage; + } + + /** + * @phpstan-assert-if-true !null $this->noExtensions + */ + public function hasNoExtensions(): bool + { + return $this->noExtensions !== null; + } + + /** + * @throws Exception + */ + public function noExtensions(): bool + { + if (!$this->hasNoExtensions()) { + throw new Exception; + } + + return $this->noExtensions; + } + + /** + * @phpstan-assert-if-true !null $this->noOutput + */ + public function hasNoOutput(): bool + { + return $this->noOutput !== null; + } + + /** + * @throws Exception + */ + public function noOutput(): bool + { + if ($this->noOutput === null) { + throw new Exception; + } + + return $this->noOutput; + } + + /** + * @phpstan-assert-if-true !null $this->noProgress + */ + public function hasNoProgress(): bool + { + return $this->noProgress !== null; + } + + /** + * @throws Exception + */ + public function noProgress(): bool + { + if ($this->noProgress === null) { + throw new Exception; + } + + return $this->noProgress; + } + + /** + * @phpstan-assert-if-true !null $this->noResults + */ + public function hasNoResults(): bool + { + return $this->noResults !== null; + } + + /** + * @throws Exception + */ + public function noResults(): bool + { + if ($this->noResults === null) { + throw new Exception; + } + + return $this->noResults; + } + + /** + * @phpstan-assert-if-true !null $this->noLogging + */ + public function hasNoLogging(): bool + { + return $this->noLogging !== null; + } + + /** + * @throws Exception + */ + public function noLogging(): bool + { + if (!$this->hasNoLogging()) { + throw new Exception; + } + + return $this->noLogging; + } + + /** + * @phpstan-assert-if-true !null $this->processIsolation + */ + public function hasProcessIsolation(): bool + { + return $this->processIsolation !== null; + } + + /** + * @throws Exception + */ + public function processIsolation(): bool + { + if (!$this->hasProcessIsolation()) { + throw new Exception; + } + + return $this->processIsolation; + } + + /** + * @phpstan-assert-if-true !null $this->randomOrderSeed + */ + public function hasRandomOrderSeed(): bool + { + return $this->randomOrderSeed !== null; + } + + /** + * @throws Exception + */ + public function randomOrderSeed(): int + { + if (!$this->hasRandomOrderSeed()) { + throw new Exception; + } + + return $this->randomOrderSeed; + } + + /** + * @phpstan-assert-if-true !null $this->reportUselessTests + */ + public function hasReportUselessTests(): bool + { + return $this->reportUselessTests !== null; + } + + /** + * @throws Exception + */ + public function reportUselessTests(): bool + { + if (!$this->hasReportUselessTests()) { + throw new Exception; + } + + return $this->reportUselessTests; + } + + /** + * @phpstan-assert-if-true !null $this->resolveDependencies + */ + public function hasResolveDependencies(): bool + { + return $this->resolveDependencies !== null; + } + + /** + * @throws Exception + */ + public function resolveDependencies(): bool + { + if (!$this->hasResolveDependencies()) { + throw new Exception; + } + + return $this->resolveDependencies; + } + + /** + * @phpstan-assert-if-true !null $this->reverseList + */ + public function hasReverseList(): bool + { + return $this->reverseList !== null; + } + + /** + * @throws Exception + */ + public function reverseList(): bool + { + if (!$this->hasReverseList()) { + throw new Exception; + } + + return $this->reverseList; + } + + /** + * @phpstan-assert-if-true !null $this->stderr + */ + public function hasStderr(): bool + { + return $this->stderr !== null; + } + + /** + * @throws Exception + */ + public function stderr(): bool + { + if (!$this->hasStderr()) { + throw new Exception; + } + + return $this->stderr; + } + + /** + * @phpstan-assert-if-true !null $this->strictCoverage + */ + public function hasStrictCoverage(): bool + { + return $this->strictCoverage !== null; + } + + /** + * @throws Exception + */ + public function strictCoverage(): bool + { + if (!$this->hasStrictCoverage()) { + throw new Exception; + } + + return $this->strictCoverage; + } + + /** + * @phpstan-assert-if-true !null $this->teamcityLogfile + */ + public function hasTeamcityLogfile(): bool + { + return $this->teamcityLogfile !== null; + } + + /** + * @throws Exception + */ + public function teamcityLogfile(): string + { + if (!$this->hasTeamcityLogfile()) { + throw new Exception; + } + + return $this->teamcityLogfile; + } + + /** + * @phpstan-assert-if-true !null $this->teamCityPrinter + */ + public function hasTeamCityPrinter(): bool + { + return $this->teamCityPrinter !== null; + } + + /** + * @throws Exception + */ + public function teamCityPrinter(): bool + { + if (!$this->hasTeamCityPrinter()) { + throw new Exception; + } + + return $this->teamCityPrinter; + } + + /** + * @phpstan-assert-if-true !null $this->testdoxHtmlFile + */ + public function hasTestdoxHtmlFile(): bool + { + return $this->testdoxHtmlFile !== null; + } + + /** + * @throws Exception + */ + public function testdoxHtmlFile(): string + { + if (!$this->hasTestdoxHtmlFile()) { + throw new Exception; + } + + return $this->testdoxHtmlFile; + } + + /** + * @phpstan-assert-if-true !null $this->testdoxTextFile + */ + public function hasTestdoxTextFile(): bool + { + return $this->testdoxTextFile !== null; + } + + /** + * @throws Exception + */ + public function testdoxTextFile(): string + { + if (!$this->hasTestdoxTextFile()) { + throw new Exception; + } + + return $this->testdoxTextFile; + } + + /** + * @phpstan-assert-if-true !null $this->testdoxPrinter + */ + public function hasTestDoxPrinter(): bool + { + return $this->testdoxPrinter !== null; + } + + /** + * @throws Exception + */ + public function testdoxPrinter(): bool + { + if (!$this->hasTestDoxPrinter()) { + throw new Exception; + } + + return $this->testdoxPrinter; + } + + /** + * @phpstan-assert-if-true !null $this->testdoxPrinterSummary + */ + public function hasTestDoxPrinterSummary(): bool + { + return $this->testdoxPrinterSummary !== null; + } + + /** + * @throws Exception + */ + public function testdoxPrinterSummary(): bool + { + if (!$this->hasTestDoxPrinterSummary()) { + throw new Exception; + } + + return $this->testdoxPrinterSummary; + } + + /** + * @phpstan-assert-if-true !null $this->testSuffixes + */ + public function hasTestSuffixes(): bool + { + return $this->testSuffixes !== null; + } + + /** + * @throws Exception + * + * @return non-empty-list + */ + public function testSuffixes(): array + { + if (!$this->hasTestSuffixes()) { + throw new Exception; + } + + return $this->testSuffixes; + } + + /** + * @phpstan-assert-if-true !null $this->testSuite + */ + public function hasTestSuite(): bool + { + return $this->testSuite !== null; + } + + /** + * @throws Exception + */ + public function testSuite(): string + { + if (!$this->hasTestSuite()) { + throw new Exception; + } + + return $this->testSuite; + } + + /** + * @phpstan-assert-if-true !null $this->excludeTestSuite + */ + public function hasExcludedTestSuite(): bool + { + return $this->excludeTestSuite !== null; + } + + /** + * @throws Exception + */ + public function excludedTestSuite(): string + { + if (!$this->hasExcludedTestSuite()) { + throw new Exception; + } + + return $this->excludeTestSuite; + } + + public function useDefaultConfiguration(): bool + { + return $this->useDefaultConfiguration; + } + + /** + * @phpstan-assert-if-true !null $this->displayDetailsOnAllIssues + */ + public function hasDisplayDetailsOnAllIssues(): bool + { + return $this->displayDetailsOnAllIssues !== null; + } + + /** + * @throws Exception + */ + public function displayDetailsOnAllIssues(): bool + { + if (!$this->hasDisplayDetailsOnAllIssues()) { + throw new Exception; + } + + return $this->displayDetailsOnAllIssues; + } + + /** + * @phpstan-assert-if-true !null $this->displayDetailsOnIncompleteTests + */ + public function hasDisplayDetailsOnIncompleteTests(): bool + { + return $this->displayDetailsOnIncompleteTests !== null; + } + + /** + * @throws Exception + */ + public function displayDetailsOnIncompleteTests(): bool + { + if (!$this->hasDisplayDetailsOnIncompleteTests()) { + throw new Exception; + } + + return $this->displayDetailsOnIncompleteTests; + } + + /** + * @phpstan-assert-if-true !null $this->displayDetailsOnSkippedTests + */ + public function hasDisplayDetailsOnSkippedTests(): bool + { + return $this->displayDetailsOnSkippedTests !== null; + } + + /** + * @throws Exception + */ + public function displayDetailsOnSkippedTests(): bool + { + if (!$this->hasDisplayDetailsOnSkippedTests()) { + throw new Exception; + } + + return $this->displayDetailsOnSkippedTests; + } + + /** + * @phpstan-assert-if-true !null $this->displayDetailsOnTestsThatTriggerDeprecations + */ + public function hasDisplayDetailsOnTestsThatTriggerDeprecations(): bool + { + return $this->displayDetailsOnTestsThatTriggerDeprecations !== null; + } + + /** + * @throws Exception + */ + public function displayDetailsOnTestsThatTriggerDeprecations(): bool + { + if (!$this->hasDisplayDetailsOnTestsThatTriggerDeprecations()) { + throw new Exception; + } + + return $this->displayDetailsOnTestsThatTriggerDeprecations; + } + + /** + * @phpstan-assert-if-true !null $this->displayDetailsOnPhpunitDeprecations + */ + public function hasDisplayDetailsOnPhpunitDeprecations(): bool + { + return $this->displayDetailsOnPhpunitDeprecations !== null; + } + + /** + * @throws Exception + */ + public function displayDetailsOnPhpunitDeprecations(): bool + { + if (!$this->hasDisplayDetailsOnPhpunitDeprecations()) { + throw new Exception; + } + + return $this->displayDetailsOnPhpunitDeprecations; + } + + /** + * @phpstan-assert-if-true !null $this->displayDetailsOnPhpunitNotices + */ + public function hasDisplayDetailsOnPhpunitNotices(): bool + { + return $this->displayDetailsOnPhpunitNotices !== null; + } + + /** + * @throws Exception + */ + public function displayDetailsOnPhpunitNotices(): bool + { + if (!$this->hasDisplayDetailsOnPhpunitNotices()) { + throw new Exception; + } + + return $this->displayDetailsOnPhpunitNotices; + } + + /** + * @phpstan-assert-if-true !null $this->displayDetailsOnTestsThatTriggerErrors + */ + public function hasDisplayDetailsOnTestsThatTriggerErrors(): bool + { + return $this->displayDetailsOnTestsThatTriggerErrors !== null; + } + + /** + * @throws Exception + */ + public function displayDetailsOnTestsThatTriggerErrors(): bool + { + if (!$this->hasDisplayDetailsOnTestsThatTriggerErrors()) { + throw new Exception; + } + + return $this->displayDetailsOnTestsThatTriggerErrors; + } + + /** + * @phpstan-assert-if-true !null $this->displayDetailsOnTestsThatTriggerNotices + */ + public function hasDisplayDetailsOnTestsThatTriggerNotices(): bool + { + return $this->displayDetailsOnTestsThatTriggerNotices !== null; + } + + /** + * @throws Exception + */ + public function displayDetailsOnTestsThatTriggerNotices(): bool + { + if (!$this->hasDisplayDetailsOnTestsThatTriggerNotices()) { + throw new Exception; + } + + return $this->displayDetailsOnTestsThatTriggerNotices; + } + + /** + * @phpstan-assert-if-true !null $this->displayDetailsOnTestsThatTriggerWarnings + */ + public function hasDisplayDetailsOnTestsThatTriggerWarnings(): bool + { + return $this->displayDetailsOnTestsThatTriggerWarnings !== null; + } + + /** + * @throws Exception + */ + public function displayDetailsOnTestsThatTriggerWarnings(): bool + { + if (!$this->hasDisplayDetailsOnTestsThatTriggerWarnings()) { + throw new Exception; + } + + return $this->displayDetailsOnTestsThatTriggerWarnings; + } + + public function version(): bool + { + return $this->version; + } + + /** + * @phpstan-assert-if-true !null $this->logEventsText + */ + public function hasLogEventsText(): bool + { + return $this->logEventsText !== null; + } + + /** + * @throws Exception + */ + public function logEventsText(): string + { + if (!$this->hasLogEventsText()) { + throw new Exception; + } + + return $this->logEventsText; + } + + /** + * @phpstan-assert-if-true !null $this->logEventsVerboseText + */ + public function hasLogEventsVerboseText(): bool + { + return $this->logEventsVerboseText !== null; + } + + /** + * @throws Exception + */ + public function logEventsVerboseText(): string + { + if (!$this->hasLogEventsVerboseText()) { + throw new Exception; + } + + return $this->logEventsVerboseText; + } + + public function debug(): bool + { + return $this->debug; + } + + public function withTelemetry(): bool + { + return $this->withTelemetry; + } + + /** + * @phpstan-assert-if-true !null $this->extensions + */ + public function hasExtensions(): bool + { + return $this->extensions !== null; + } + + /** + * @throws Exception + * + * @return non-empty-list + */ + public function extensions(): array + { + if (!$this->hasExtensions()) { + throw new Exception; + } + + return $this->extensions; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Cli/Exception.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Cli/Exception.php new file mode 100644 index 000000000..0d9a5a00e --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Cli/Exception.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 PHPUnit\TextUI\CliArguments; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Exception extends RuntimeException implements \PHPUnit\Exception +{ +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Cli/XmlConfigurationFileFinder.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Cli/XmlConfigurationFileFinder.php new file mode 100644 index 000000000..5357ef63d --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Cli/XmlConfigurationFileFinder.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\CliArguments; + +use function getcwd; +use function is_dir; +use function is_file; +use function realpath; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class XmlConfigurationFileFinder +{ + public function find(Configuration $configuration): false|string + { + $useDefaultConfiguration = $configuration->useDefaultConfiguration(); + + if ($configuration->hasConfigurationFile()) { + if (is_dir($configuration->configurationFile())) { + $candidate = $this->configurationFileInDirectory($configuration->configurationFile()); + + if ($candidate !== false) { + return $candidate; + } + + return false; + } + + return $configuration->configurationFile(); + } + + if ($useDefaultConfiguration) { + $directory = getcwd(); + + if ($directory !== false) { + $candidate = $this->configurationFileInDirectory($directory); + + if ($candidate !== false) { + return $candidate; + } + } + } + + return false; + } + + private function configurationFileInDirectory(string $directory): false|string + { + $candidates = [ + $directory . '/phpunit.xml', + $directory . '/phpunit.dist.xml', + $directory . '/phpunit.xml.dist', + ]; + + foreach ($candidates as $candidate) { + if (is_file($candidate)) { + return realpath($candidate); + } + } + + return false; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/CodeCoverageFilterRegistry.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/CodeCoverageFilterRegistry.php new file mode 100644 index 000000000..7051d2840 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/CodeCoverageFilterRegistry.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use function array_keys; +use function assert; +use SebastianBergmann\CodeCoverage\Filter; + +/** + * CLI options and XML configuration are static within a single PHPUnit process. + * It is therefore okay to use a Singleton registry here. + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class CodeCoverageFilterRegistry +{ + private static ?self $instance = null; + private ?Filter $filter = null; + private bool $configured = false; + + public static function instance(): self + { + if (self::$instance === null) { + self::$instance = new self; + } + + return self::$instance; + } + + /** + * @codeCoverageIgnore + */ + public function get(): Filter + { + assert($this->filter !== null); + + return $this->filter; + } + + /** + * @codeCoverageIgnore + */ + public function init(Configuration $configuration, bool $force = false): void + { + if (!$configuration->hasCoverageReport() && !$force) { + return; + } + + if ($this->configured && !$force) { + return; + } + + $this->filter = new Filter; + + if ($configuration->source()->notEmpty()) { + $this->filter->includeFiles(array_keys((new SourceMapper)->map($configuration->source()))); + + $this->configured = true; + } + } + + /** + * @codeCoverageIgnore + */ + public function configured(): bool + { + return $this->configured; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Configuration.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Configuration.php new file mode 100644 index 000000000..a6dfe62aa --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Configuration.php @@ -0,0 +1,1538 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use function explode; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Configuration +{ + public const string COLOR_NEVER = 'never'; + public const string COLOR_AUTO = 'auto'; + public const string COLOR_ALWAYS = 'always'; + public const string COLOR_DEFAULT = self::COLOR_NEVER; + + /** + * @var list + */ + private array $cliArguments; + private ?string $configurationFile; + private ?string $bootstrap; + + /** + * @var array + */ + private array $bootstrapForTestSuite; + private bool $cacheResult; + private ?string $cacheDirectory; + private ?string $coverageCacheDirectory; + private Source $source; + private bool $pathCoverage; + private ?string $coverageClover; + private ?string $coverageCobertura; + private ?string $coverageCrap4j; + private int $coverageCrap4jThreshold; + private ?string $coverageHtml; + private int $coverageHtmlLowUpperBound; + private int $coverageHtmlHighLowerBound; + private string $coverageHtmlColorSuccessLow; + private string $coverageHtmlColorSuccessMedium; + private string $coverageHtmlColorSuccessHigh; + private string $coverageHtmlColorWarning; + private string $coverageHtmlColorDanger; + private ?string $coverageHtmlCustomCssFile; + private ?string $coverageOpenClover; + private ?string $coveragePhp; + private ?string $coverageText; + private bool $coverageTextShowUncoveredFiles; + private bool $coverageTextShowOnlySummary; + private ?string $coverageXml; + private string $testResultCacheFile; + private bool $ignoreDeprecatedCodeUnitsFromCodeCoverage; + private bool $disableCodeCoverageIgnore; + private bool $failOnAllIssues; + private bool $failOnDeprecation; + private bool $failOnPhpunitDeprecation; + private bool $failOnPhpunitNotice; + private bool $failOnPhpunitWarning; + private bool $failOnEmptyTestSuite; + private bool $failOnIncomplete; + private bool $failOnNotice; + private bool $failOnRisky; + private bool $failOnSkipped; + private bool $failOnWarning; + private bool $doNotFailOnDeprecation; + private bool $doNotFailOnPhpunitDeprecation; + private bool $doNotFailOnPhpunitNotice; + private bool $doNotFailOnPhpunitWarning; + private bool $doNotFailOnEmptyTestSuite; + private bool $doNotFailOnIncomplete; + private bool $doNotFailOnNotice; + private bool $doNotFailOnRisky; + private bool $doNotFailOnSkipped; + private bool $doNotFailOnWarning; + private bool $stopOnDefect; + private bool $stopOnDeprecation; + private ?string $specificDeprecationToStopOn; + private bool $stopOnError; + private bool $stopOnFailure; + private bool $stopOnIncomplete; + private bool $stopOnNotice; + private bool $stopOnRisky; + private bool $stopOnSkipped; + private bool $stopOnWarning; + private bool $outputToStandardErrorStream; + private int $columns; + private bool $noExtensions; + + /** + * @var ?non-empty-string + */ + private ?string $pharExtensionDirectory; + + /** + * @var list}> + */ + private array $extensionBootstrappers; + private bool $backupGlobals; + private bool $backupStaticProperties; + private bool $beStrictAboutChangesToGlobalState; + private bool $colors; + private bool $processIsolation; + private bool $enforceTimeLimit; + private int $defaultTimeLimit; + private int $timeoutForSmallTests; + private int $timeoutForMediumTests; + private int $timeoutForLargeTests; + private bool $reportUselessTests; + private bool $strictCoverage; + private bool $disallowTestOutput; + private bool $displayDetailsOnAllIssues; + private bool $displayDetailsOnIncompleteTests; + private bool $displayDetailsOnSkippedTests; + private bool $displayDetailsOnTestsThatTriggerDeprecations; + private bool $displayDetailsOnPhpunitDeprecations; + private bool $displayDetailsOnPhpunitNotices; + private bool $displayDetailsOnTestsThatTriggerErrors; + private bool $displayDetailsOnTestsThatTriggerNotices; + private bool $displayDetailsOnTestsThatTriggerWarnings; + private bool $reverseDefectList; + private bool $requireCoverageMetadata; + private bool $noProgress; + private bool $noResults; + private bool $noOutput; + private int $executionOrder; + private int $executionOrderDefects; + private bool $resolveDependencies; + private ?string $logfileTeamcity; + private ?string $logfileJunit; + private ?string $logfileOtr; + private bool $includeGitInformationInOtrLogfile; + private ?string $logfileTestdoxHtml; + private ?string $logfileTestdoxText; + private ?string $logEventsText; + private ?string $logEventsVerboseText; + + /** + * @var ?non-empty-list + */ + private ?array $testsCovering; + + /** + * @var ?non-empty-list + */ + private ?array $testsUsing; + + /** + * @var ?non-empty-list + */ + private ?array $testsRequiringPhpExtension; + private bool $teamCityOutput; + private bool $testDoxOutput; + private bool $testDoxOutputSummary; + private ?string $filter; + private ?string $excludeFilter; + + /** + * @var list + */ + private array $groups; + + /** + * @var list + */ + private array $excludeGroups; + private int $randomOrderSeed; + private bool $includeUncoveredFiles; + private TestSuiteCollection $testSuite; + private string $includeTestSuite; + private string $excludeTestSuite; + private ?string $defaultTestSuite; + + /** + * @var non-empty-list + */ + private array $testSuffixes; + private Php $php; + private bool $controlGarbageCollector; + private int $numberOfTestsBeforeGarbageCollection; + + /** + * @var null|non-empty-string + */ + private ?string $generateBaseline; + private bool $debug; + private bool $withTelemetry; + + /** + * @var non-negative-int + */ + private int $shortenArraysForExportThreshold; + + /** + * @param list $cliArguments + * @param array $bootstrapForTestSuite + * @param ?non-empty-string $pharExtensionDirectory + * @param list}> $extensionBootstrappers + * @param ?non-empty-list $testsCovering + * @param ?non-empty-list $testsUsing + * @param ?non-empty-list $testsRequiringPhpExtension + * @param list $groups + * @param list $excludeGroups + * @param non-empty-list $testSuffixes + * @param null|non-empty-string $generateBaseline + * @param non-negative-int $shortenArraysForExportThreshold + */ + public function __construct(array $cliArguments, ?string $configurationFile, ?string $bootstrap, array $bootstrapForTestSuite, bool $cacheResult, ?string $cacheDirectory, ?string $coverageCacheDirectory, Source $source, string $testResultCacheFile, ?string $coverageClover, ?string $coverageCobertura, ?string $coverageCrap4j, int $coverageCrap4jThreshold, ?string $coverageHtml, int $coverageHtmlLowUpperBound, int $coverageHtmlHighLowerBound, string $coverageHtmlColorSuccessLow, string $coverageHtmlColorSuccessMedium, string $coverageHtmlColorSuccessHigh, string $coverageHtmlColorWarning, string $coverageHtmlColorDanger, ?string $coverageHtmlCustomCssFile, ?string $coverageOpenClover, ?string $coveragePhp, ?string $coverageText, bool $coverageTextShowUncoveredFiles, bool $coverageTextShowOnlySummary, ?string $coverageXml, bool $pathCoverage, bool $ignoreDeprecatedCodeUnitsFromCodeCoverage, bool $disableCodeCoverageIgnore, bool $failOnAllIssues, bool $failOnDeprecation, bool $failOnPhpunitDeprecation, bool $failOnPhpunitNotice, bool $failOnPhpunitWarning, bool $failOnEmptyTestSuite, bool $failOnIncomplete, bool $failOnNotice, bool $failOnRisky, bool $failOnSkipped, bool $failOnWarning, bool $doNotFailOnDeprecation, bool $doNotFailOnPhpunitDeprecation, bool $doNotFailOnPhpunitNotice, bool $doNotFailOnPhpunitWarning, bool $doNotFailOnEmptyTestSuite, bool $doNotFailOnIncomplete, bool $doNotFailOnNotice, bool $doNotFailOnRisky, bool $doNotFailOnSkipped, bool $doNotFailOnWarning, bool $stopOnDefect, bool $stopOnDeprecation, ?string $specificDeprecationToStopOn, bool $stopOnError, bool $stopOnFailure, bool $stopOnIncomplete, bool $stopOnNotice, bool $stopOnRisky, bool $stopOnSkipped, bool $stopOnWarning, bool $outputToStandardErrorStream, int $columns, bool $noExtensions, ?string $pharExtensionDirectory, array $extensionBootstrappers, bool $backupGlobals, bool $backupStaticProperties, bool $beStrictAboutChangesToGlobalState, bool $colors, bool $processIsolation, bool $enforceTimeLimit, int $defaultTimeLimit, int $timeoutForSmallTests, int $timeoutForMediumTests, int $timeoutForLargeTests, bool $reportUselessTests, bool $strictCoverage, bool $disallowTestOutput, bool $displayDetailsOnAllIssues, bool $displayDetailsOnIncompleteTests, bool $displayDetailsOnSkippedTests, bool $displayDetailsOnTestsThatTriggerDeprecations, bool $displayDetailsOnPhpunitDeprecations, bool $displayDetailsOnPhpunitNotices, bool $displayDetailsOnTestsThatTriggerErrors, bool $displayDetailsOnTestsThatTriggerNotices, bool $displayDetailsOnTestsThatTriggerWarnings, bool $reverseDefectList, bool $requireCoverageMetadata, bool $noProgress, bool $noResults, bool $noOutput, int $executionOrder, int $executionOrderDefects, bool $resolveDependencies, ?string $logfileTeamcity, ?string $logfileJunit, ?string $logfileOtr, bool $includeGitInformationInOtrLogfile, ?string $logfileTestdoxHtml, ?string $logfileTestdoxText, ?string $logEventsText, ?string $logEventsVerboseText, bool $teamCityOutput, bool $testDoxOutput, bool $testDoxOutputSummary, ?array $testsCovering, ?array $testsUsing, ?array $testsRequiringPhpExtension, ?string $filter, ?string $excludeFilter, array $groups, array $excludeGroups, int $randomOrderSeed, bool $includeUncoveredFiles, TestSuiteCollection $testSuite, string $includeTestSuite, string $excludeTestSuite, ?string $defaultTestSuite, array $testSuffixes, Php $php, bool $controlGarbageCollector, int $numberOfTestsBeforeGarbageCollection, ?string $generateBaseline, bool $debug, bool $withTelemetry, int $shortenArraysForExportThreshold) + { + $this->cliArguments = $cliArguments; + $this->configurationFile = $configurationFile; + $this->bootstrap = $bootstrap; + $this->bootstrapForTestSuite = $bootstrapForTestSuite; + $this->cacheResult = $cacheResult; + $this->cacheDirectory = $cacheDirectory; + $this->coverageCacheDirectory = $coverageCacheDirectory; + $this->source = $source; + $this->testResultCacheFile = $testResultCacheFile; + $this->coverageClover = $coverageClover; + $this->coverageCobertura = $coverageCobertura; + $this->coverageCrap4j = $coverageCrap4j; + $this->coverageCrap4jThreshold = $coverageCrap4jThreshold; + $this->coverageHtml = $coverageHtml; + $this->coverageHtmlLowUpperBound = $coverageHtmlLowUpperBound; + $this->coverageHtmlHighLowerBound = $coverageHtmlHighLowerBound; + $this->coverageHtmlColorSuccessLow = $coverageHtmlColorSuccessLow; + $this->coverageHtmlColorSuccessMedium = $coverageHtmlColorSuccessMedium; + $this->coverageHtmlColorSuccessHigh = $coverageHtmlColorSuccessHigh; + $this->coverageHtmlColorWarning = $coverageHtmlColorWarning; + $this->coverageHtmlColorDanger = $coverageHtmlColorDanger; + $this->coverageHtmlCustomCssFile = $coverageHtmlCustomCssFile; + $this->coverageOpenClover = $coverageOpenClover; + $this->coveragePhp = $coveragePhp; + $this->coverageText = $coverageText; + $this->coverageTextShowUncoveredFiles = $coverageTextShowUncoveredFiles; + $this->coverageTextShowOnlySummary = $coverageTextShowOnlySummary; + $this->coverageXml = $coverageXml; + $this->pathCoverage = $pathCoverage; + $this->ignoreDeprecatedCodeUnitsFromCodeCoverage = $ignoreDeprecatedCodeUnitsFromCodeCoverage; + $this->disableCodeCoverageIgnore = $disableCodeCoverageIgnore; + $this->failOnAllIssues = $failOnAllIssues; + $this->failOnDeprecation = $failOnDeprecation; + $this->failOnPhpunitDeprecation = $failOnPhpunitDeprecation; + $this->failOnPhpunitNotice = $failOnPhpunitNotice; + $this->failOnPhpunitWarning = $failOnPhpunitWarning; + $this->failOnEmptyTestSuite = $failOnEmptyTestSuite; + $this->failOnIncomplete = $failOnIncomplete; + $this->failOnNotice = $failOnNotice; + $this->failOnRisky = $failOnRisky; + $this->failOnSkipped = $failOnSkipped; + $this->failOnWarning = $failOnWarning; + $this->doNotFailOnDeprecation = $doNotFailOnDeprecation; + $this->doNotFailOnPhpunitDeprecation = $doNotFailOnPhpunitDeprecation; + $this->doNotFailOnPhpunitNotice = $doNotFailOnPhpunitNotice; + $this->doNotFailOnPhpunitWarning = $doNotFailOnPhpunitWarning; + $this->doNotFailOnEmptyTestSuite = $doNotFailOnEmptyTestSuite; + $this->doNotFailOnIncomplete = $doNotFailOnIncomplete; + $this->doNotFailOnNotice = $doNotFailOnNotice; + $this->doNotFailOnRisky = $doNotFailOnRisky; + $this->doNotFailOnSkipped = $doNotFailOnSkipped; + $this->doNotFailOnWarning = $doNotFailOnWarning; + $this->stopOnDefect = $stopOnDefect; + $this->stopOnDeprecation = $stopOnDeprecation; + $this->specificDeprecationToStopOn = $specificDeprecationToStopOn; + $this->stopOnError = $stopOnError; + $this->stopOnFailure = $stopOnFailure; + $this->stopOnIncomplete = $stopOnIncomplete; + $this->stopOnNotice = $stopOnNotice; + $this->stopOnRisky = $stopOnRisky; + $this->stopOnSkipped = $stopOnSkipped; + $this->stopOnWarning = $stopOnWarning; + $this->outputToStandardErrorStream = $outputToStandardErrorStream; + $this->columns = $columns; + $this->noExtensions = $noExtensions; + $this->pharExtensionDirectory = $pharExtensionDirectory; + $this->extensionBootstrappers = $extensionBootstrappers; + $this->backupGlobals = $backupGlobals; + $this->backupStaticProperties = $backupStaticProperties; + $this->beStrictAboutChangesToGlobalState = $beStrictAboutChangesToGlobalState; + $this->colors = $colors; + $this->processIsolation = $processIsolation; + $this->enforceTimeLimit = $enforceTimeLimit; + $this->defaultTimeLimit = $defaultTimeLimit; + $this->timeoutForSmallTests = $timeoutForSmallTests; + $this->timeoutForMediumTests = $timeoutForMediumTests; + $this->timeoutForLargeTests = $timeoutForLargeTests; + $this->reportUselessTests = $reportUselessTests; + $this->strictCoverage = $strictCoverage; + $this->disallowTestOutput = $disallowTestOutput; + $this->displayDetailsOnAllIssues = $displayDetailsOnAllIssues; + $this->displayDetailsOnIncompleteTests = $displayDetailsOnIncompleteTests; + $this->displayDetailsOnSkippedTests = $displayDetailsOnSkippedTests; + $this->displayDetailsOnTestsThatTriggerDeprecations = $displayDetailsOnTestsThatTriggerDeprecations; + $this->displayDetailsOnPhpunitDeprecations = $displayDetailsOnPhpunitDeprecations; + $this->displayDetailsOnPhpunitNotices = $displayDetailsOnPhpunitNotices; + $this->displayDetailsOnTestsThatTriggerErrors = $displayDetailsOnTestsThatTriggerErrors; + $this->displayDetailsOnTestsThatTriggerNotices = $displayDetailsOnTestsThatTriggerNotices; + $this->displayDetailsOnTestsThatTriggerWarnings = $displayDetailsOnTestsThatTriggerWarnings; + $this->reverseDefectList = $reverseDefectList; + $this->requireCoverageMetadata = $requireCoverageMetadata; + $this->noProgress = $noProgress; + $this->noResults = $noResults; + $this->noOutput = $noOutput; + $this->executionOrder = $executionOrder; + $this->executionOrderDefects = $executionOrderDefects; + $this->resolveDependencies = $resolveDependencies; + $this->logfileTeamcity = $logfileTeamcity; + $this->logfileJunit = $logfileJunit; + $this->logfileOtr = $logfileOtr; + $this->includeGitInformationInOtrLogfile = $includeGitInformationInOtrLogfile; + $this->logfileTestdoxHtml = $logfileTestdoxHtml; + $this->logfileTestdoxText = $logfileTestdoxText; + $this->logEventsText = $logEventsText; + $this->logEventsVerboseText = $logEventsVerboseText; + $this->teamCityOutput = $teamCityOutput; + $this->testDoxOutput = $testDoxOutput; + $this->testDoxOutputSummary = $testDoxOutputSummary; + $this->testsCovering = $testsCovering; + $this->testsUsing = $testsUsing; + $this->testsRequiringPhpExtension = $testsRequiringPhpExtension; + $this->filter = $filter; + $this->excludeFilter = $excludeFilter; + $this->groups = $groups; + $this->excludeGroups = $excludeGroups; + $this->randomOrderSeed = $randomOrderSeed; + $this->includeUncoveredFiles = $includeUncoveredFiles; + $this->testSuite = $testSuite; + $this->includeTestSuite = $includeTestSuite; + $this->excludeTestSuite = $excludeTestSuite; + $this->defaultTestSuite = $defaultTestSuite; + $this->testSuffixes = $testSuffixes; + $this->php = $php; + $this->controlGarbageCollector = $controlGarbageCollector; + $this->numberOfTestsBeforeGarbageCollection = $numberOfTestsBeforeGarbageCollection; + $this->generateBaseline = $generateBaseline; + $this->debug = $debug; + $this->withTelemetry = $withTelemetry; + $this->shortenArraysForExportThreshold = $shortenArraysForExportThreshold; + } + + /** + * @phpstan-assert-if-true !empty $this->cliArguments + */ + public function hasCliArguments(): bool + { + return $this->cliArguments !== []; + } + + /** + * @return list + */ + public function cliArguments(): array + { + return $this->cliArguments; + } + + /** + * @phpstan-assert-if-true !null $this->configurationFile + */ + public function hasConfigurationFile(): bool + { + return $this->configurationFile !== null; + } + + /** + * @throws NoConfigurationFileException + */ + public function configurationFile(): string + { + if (!$this->hasConfigurationFile()) { + throw new NoConfigurationFileException; + } + + return $this->configurationFile; + } + + /** + * @phpstan-assert-if-true !null $this->bootstrap + */ + public function hasBootstrap(): bool + { + return $this->bootstrap !== null; + } + + /** + * @throws NoBootstrapException + */ + public function bootstrap(): string + { + if (!$this->hasBootstrap()) { + throw new NoBootstrapException; + } + + return $this->bootstrap; + } + + /** + * @return array + */ + public function bootstrapForTestSuite(): array + { + return $this->bootstrapForTestSuite; + } + + public function cacheResult(): bool + { + return $this->cacheResult; + } + + /** + * @phpstan-assert-if-true !null $this->cacheDirectory + */ + public function hasCacheDirectory(): bool + { + return $this->cacheDirectory !== null; + } + + /** + * @throws NoCacheDirectoryException + */ + public function cacheDirectory(): string + { + if (!$this->hasCacheDirectory()) { + throw new NoCacheDirectoryException; + } + + return $this->cacheDirectory; + } + + /** + * @phpstan-assert-if-true !null $this->coverageCacheDirectory + */ + public function hasCoverageCacheDirectory(): bool + { + return $this->coverageCacheDirectory !== null; + } + + /** + * @throws NoCoverageCacheDirectoryException + */ + public function coverageCacheDirectory(): string + { + if (!$this->hasCoverageCacheDirectory()) { + throw new NoCoverageCacheDirectoryException; + } + + return $this->coverageCacheDirectory; + } + + public function source(): Source + { + return $this->source; + } + + public function testResultCacheFile(): string + { + return $this->testResultCacheFile; + } + + public function ignoreDeprecatedCodeUnitsFromCodeCoverage(): bool + { + return $this->ignoreDeprecatedCodeUnitsFromCodeCoverage; + } + + public function disableCodeCoverageIgnore(): bool + { + return $this->disableCodeCoverageIgnore; + } + + public function pathCoverage(): bool + { + return $this->pathCoverage; + } + + public function hasCoverageReport(): bool + { + return $this->hasCoverageClover() || + $this->hasCoverageCobertura() || + $this->hasCoverageCrap4j() || + $this->hasCoverageHtml() || + $this->hasCoverageOpenClover() || + $this->hasCoveragePhp() || + $this->hasCoverageText() || + $this->hasCoverageXml(); + } + + /** + * @phpstan-assert-if-true !null $this->coverageClover + */ + public function hasCoverageClover(): bool + { + return $this->coverageClover !== null; + } + + /** + * @throws CodeCoverageReportNotConfiguredException + */ + public function coverageClover(): string + { + if (!$this->hasCoverageClover()) { + throw new CodeCoverageReportNotConfiguredException; + } + + return $this->coverageClover; + } + + /** + * @phpstan-assert-if-true !null $this->coverageCobertura + */ + public function hasCoverageCobertura(): bool + { + return $this->coverageCobertura !== null; + } + + /** + * @throws CodeCoverageReportNotConfiguredException + */ + public function coverageCobertura(): string + { + if (!$this->hasCoverageCobertura()) { + throw new CodeCoverageReportNotConfiguredException; + } + + return $this->coverageCobertura; + } + + /** + * @phpstan-assert-if-true !null $this->coverageCrap4j + */ + public function hasCoverageCrap4j(): bool + { + return $this->coverageCrap4j !== null; + } + + /** + * @throws CodeCoverageReportNotConfiguredException + */ + public function coverageCrap4j(): string + { + if (!$this->hasCoverageCrap4j()) { + throw new CodeCoverageReportNotConfiguredException; + } + + return $this->coverageCrap4j; + } + + public function coverageCrap4jThreshold(): int + { + return $this->coverageCrap4jThreshold; + } + + /** + * @phpstan-assert-if-true !null $this->coverageHtml + */ + public function hasCoverageHtml(): bool + { + return $this->coverageHtml !== null; + } + + /** + * @throws CodeCoverageReportNotConfiguredException + */ + public function coverageHtml(): string + { + if (!$this->hasCoverageHtml()) { + throw new CodeCoverageReportNotConfiguredException; + } + + return $this->coverageHtml; + } + + public function coverageHtmlLowUpperBound(): int + { + return $this->coverageHtmlLowUpperBound; + } + + public function coverageHtmlHighLowerBound(): int + { + return $this->coverageHtmlHighLowerBound; + } + + public function coverageHtmlColorSuccessLow(): string + { + return $this->coverageHtmlColorSuccessLow; + } + + public function coverageHtmlColorSuccessMedium(): string + { + return $this->coverageHtmlColorSuccessMedium; + } + + public function coverageHtmlColorSuccessHigh(): string + { + return $this->coverageHtmlColorSuccessHigh; + } + + public function coverageHtmlColorWarning(): string + { + return $this->coverageHtmlColorWarning; + } + + public function coverageHtmlColorDanger(): string + { + return $this->coverageHtmlColorDanger; + } + + /** + * @phpstan-assert-if-true !null $this->coverageHtmlCustomCssFile + */ + public function hasCoverageHtmlCustomCssFile(): bool + { + return $this->coverageHtmlCustomCssFile !== null; + } + + /** + * @throws NoCustomCssFileException + */ + public function coverageHtmlCustomCssFile(): string + { + if (!$this->hasCoverageHtmlCustomCssFile()) { + throw new NoCustomCssFileException; + } + + return $this->coverageHtmlCustomCssFile; + } + + /** + * @phpstan-assert-if-true !null $this->coverageOpenClover + */ + public function hasCoverageOpenClover(): bool + { + return $this->coverageOpenClover !== null; + } + + /** + * @throws CodeCoverageReportNotConfiguredException + */ + public function coverageOpenClover(): string + { + if (!$this->hasCoverageOpenClover()) { + throw new CodeCoverageReportNotConfiguredException; + } + + return $this->coverageOpenClover; + } + + /** + * @phpstan-assert-if-true !null $this->coveragePhp + */ + public function hasCoveragePhp(): bool + { + return $this->coveragePhp !== null; + } + + /** + * @throws CodeCoverageReportNotConfiguredException + */ + public function coveragePhp(): string + { + if (!$this->hasCoveragePhp()) { + throw new CodeCoverageReportNotConfiguredException; + } + + return $this->coveragePhp; + } + + /** + * @phpstan-assert-if-true !null $this->coverageText + */ + public function hasCoverageText(): bool + { + return $this->coverageText !== null; + } + + /** + * @throws CodeCoverageReportNotConfiguredException + */ + public function coverageText(): string + { + if (!$this->hasCoverageText()) { + throw new CodeCoverageReportNotConfiguredException; + } + + return $this->coverageText; + } + + public function coverageTextShowUncoveredFiles(): bool + { + return $this->coverageTextShowUncoveredFiles; + } + + public function coverageTextShowOnlySummary(): bool + { + return $this->coverageTextShowOnlySummary; + } + + /** + * @phpstan-assert-if-true !null $this->coverageXml + */ + public function hasCoverageXml(): bool + { + return $this->coverageXml !== null; + } + + /** + * @throws CodeCoverageReportNotConfiguredException + */ + public function coverageXml(): string + { + if (!$this->hasCoverageXml()) { + throw new CodeCoverageReportNotConfiguredException; + } + + return $this->coverageXml; + } + + public function failOnAllIssues(): bool + { + return $this->failOnAllIssues; + } + + public function failOnDeprecation(): bool + { + return $this->failOnDeprecation; + } + + public function failOnPhpunitDeprecation(): bool + { + return $this->failOnPhpunitDeprecation; + } + + public function failOnPhpunitNotice(): bool + { + return $this->failOnPhpunitNotice; + } + + public function failOnPhpunitWarning(): bool + { + return $this->failOnPhpunitWarning; + } + + public function failOnEmptyTestSuite(): bool + { + return $this->failOnEmptyTestSuite; + } + + public function failOnIncomplete(): bool + { + return $this->failOnIncomplete; + } + + public function failOnNotice(): bool + { + return $this->failOnNotice; + } + + public function failOnRisky(): bool + { + return $this->failOnRisky; + } + + public function failOnSkipped(): bool + { + return $this->failOnSkipped; + } + + public function failOnWarning(): bool + { + return $this->failOnWarning; + } + + public function doNotFailOnDeprecation(): bool + { + return $this->doNotFailOnDeprecation; + } + + public function doNotFailOnPhpunitDeprecation(): bool + { + return $this->doNotFailOnPhpunitDeprecation; + } + + public function doNotFailOnPhpunitNotice(): bool + { + return $this->doNotFailOnPhpunitNotice; + } + + public function doNotFailOnPhpunitWarning(): bool + { + return $this->doNotFailOnPhpunitWarning; + } + + public function doNotFailOnEmptyTestSuite(): bool + { + return $this->doNotFailOnEmptyTestSuite; + } + + public function doNotFailOnIncomplete(): bool + { + return $this->doNotFailOnIncomplete; + } + + public function doNotFailOnNotice(): bool + { + return $this->doNotFailOnNotice; + } + + public function doNotFailOnRisky(): bool + { + return $this->doNotFailOnRisky; + } + + public function doNotFailOnSkipped(): bool + { + return $this->doNotFailOnSkipped; + } + + public function doNotFailOnWarning(): bool + { + return $this->doNotFailOnWarning; + } + + public function stopOnDefect(): bool + { + return $this->stopOnDefect; + } + + public function stopOnDeprecation(): bool + { + return $this->stopOnDeprecation; + } + + /** + * @phpstan-assert-if-true !null $this->specificDeprecationToStopOn + */ + public function hasSpecificDeprecationToStopOn(): bool + { + return $this->specificDeprecationToStopOn !== null; + } + + /** + * @throws SpecificDeprecationToStopOnNotConfiguredException + */ + public function specificDeprecationToStopOn(): string + { + if (!$this->hasSpecificDeprecationToStopOn()) { + throw new SpecificDeprecationToStopOnNotConfiguredException; + } + + return $this->specificDeprecationToStopOn; + } + + public function stopOnError(): bool + { + return $this->stopOnError; + } + + public function stopOnFailure(): bool + { + return $this->stopOnFailure; + } + + public function stopOnIncomplete(): bool + { + return $this->stopOnIncomplete; + } + + public function stopOnNotice(): bool + { + return $this->stopOnNotice; + } + + public function stopOnRisky(): bool + { + return $this->stopOnRisky; + } + + public function stopOnSkipped(): bool + { + return $this->stopOnSkipped; + } + + public function stopOnWarning(): bool + { + return $this->stopOnWarning; + } + + public function outputToStandardErrorStream(): bool + { + return $this->outputToStandardErrorStream; + } + + public function columns(): int + { + return $this->columns; + } + + public function noExtensions(): bool + { + return $this->noExtensions; + } + + /** + * @phpstan-assert-if-true !null $this->pharExtensionDirectory + */ + public function hasPharExtensionDirectory(): bool + { + return $this->pharExtensionDirectory !== null; + } + + /** + * @throws NoPharExtensionDirectoryException + * + * @return non-empty-string + */ + public function pharExtensionDirectory(): string + { + if (!$this->hasPharExtensionDirectory()) { + throw new NoPharExtensionDirectoryException; + } + + return $this->pharExtensionDirectory; + } + + /** + * @return list}> + */ + public function extensionBootstrappers(): array + { + return $this->extensionBootstrappers; + } + + public function backupGlobals(): bool + { + return $this->backupGlobals; + } + + public function backupStaticProperties(): bool + { + return $this->backupStaticProperties; + } + + public function beStrictAboutChangesToGlobalState(): bool + { + return $this->beStrictAboutChangesToGlobalState; + } + + public function colors(): bool + { + return $this->colors; + } + + public function processIsolation(): bool + { + return $this->processIsolation; + } + + public function enforceTimeLimit(): bool + { + return $this->enforceTimeLimit; + } + + public function defaultTimeLimit(): int + { + return $this->defaultTimeLimit; + } + + public function timeoutForSmallTests(): int + { + return $this->timeoutForSmallTests; + } + + public function timeoutForMediumTests(): int + { + return $this->timeoutForMediumTests; + } + + public function timeoutForLargeTests(): int + { + return $this->timeoutForLargeTests; + } + + public function reportUselessTests(): bool + { + return $this->reportUselessTests; + } + + public function strictCoverage(): bool + { + return $this->strictCoverage; + } + + public function disallowTestOutput(): bool + { + return $this->disallowTestOutput; + } + + public function displayDetailsOnAllIssues(): bool + { + return $this->displayDetailsOnAllIssues; + } + + public function displayDetailsOnIncompleteTests(): bool + { + return $this->displayDetailsOnIncompleteTests; + } + + public function displayDetailsOnSkippedTests(): bool + { + return $this->displayDetailsOnSkippedTests; + } + + public function displayDetailsOnTestsThatTriggerDeprecations(): bool + { + return $this->displayDetailsOnTestsThatTriggerDeprecations; + } + + public function displayDetailsOnPhpunitDeprecations(): bool + { + return $this->displayDetailsOnPhpunitDeprecations; + } + + public function displayDetailsOnPhpunitNotices(): bool + { + return $this->displayDetailsOnPhpunitNotices; + } + + public function displayDetailsOnTestsThatTriggerErrors(): bool + { + return $this->displayDetailsOnTestsThatTriggerErrors; + } + + public function displayDetailsOnTestsThatTriggerNotices(): bool + { + return $this->displayDetailsOnTestsThatTriggerNotices; + } + + public function displayDetailsOnTestsThatTriggerWarnings(): bool + { + return $this->displayDetailsOnTestsThatTriggerWarnings; + } + + public function reverseDefectList(): bool + { + return $this->reverseDefectList; + } + + public function requireCoverageMetadata(): bool + { + return $this->requireCoverageMetadata; + } + + public function noProgress(): bool + { + return $this->noProgress; + } + + public function noResults(): bool + { + return $this->noResults; + } + + public function noOutput(): bool + { + return $this->noOutput; + } + + public function executionOrder(): int + { + return $this->executionOrder; + } + + public function executionOrderDefects(): int + { + return $this->executionOrderDefects; + } + + public function resolveDependencies(): bool + { + return $this->resolveDependencies; + } + + /** + * @phpstan-assert-if-true !null $this->logfileTeamcity + */ + public function hasLogfileTeamcity(): bool + { + return $this->logfileTeamcity !== null; + } + + /** + * @throws LoggingNotConfiguredException + */ + public function logfileTeamcity(): string + { + if (!$this->hasLogfileTeamcity()) { + throw new LoggingNotConfiguredException; + } + + return $this->logfileTeamcity; + } + + /** + * @phpstan-assert-if-true !null $this->logfileJunit + */ + public function hasLogfileJunit(): bool + { + return $this->logfileJunit !== null; + } + + /** + * @throws LoggingNotConfiguredException + */ + public function logfileJunit(): string + { + if (!$this->hasLogfileJunit()) { + throw new LoggingNotConfiguredException; + } + + return $this->logfileJunit; + } + + /** + * @phpstan-assert-if-true !null $this->logfileOtr + */ + public function hasLogfileOtr(): bool + { + return $this->logfileOtr !== null; + } + + /** + * @throws LoggingNotConfiguredException + */ + public function logfileOtr(): string + { + if (!$this->hasLogfileOtr()) { + throw new LoggingNotConfiguredException; + } + + return $this->logfileOtr; + } + + public function includeGitInformationInOtrLogfile(): bool + { + return $this->includeGitInformationInOtrLogfile; + } + + /** + * @phpstan-assert-if-true !null $this->logfileTestdoxHtml + */ + public function hasLogfileTestdoxHtml(): bool + { + return $this->logfileTestdoxHtml !== null; + } + + /** + * @throws LoggingNotConfiguredException + */ + public function logfileTestdoxHtml(): string + { + if (!$this->hasLogfileTestdoxHtml()) { + throw new LoggingNotConfiguredException; + } + + return $this->logfileTestdoxHtml; + } + + /** + * @phpstan-assert-if-true !null $this->logfileTestdoxText + */ + public function hasLogfileTestdoxText(): bool + { + return $this->logfileTestdoxText !== null; + } + + /** + * @throws LoggingNotConfiguredException + */ + public function logfileTestdoxText(): string + { + if (!$this->hasLogfileTestdoxText()) { + throw new LoggingNotConfiguredException; + } + + return $this->logfileTestdoxText; + } + + /** + * @phpstan-assert-if-true !null $this->logEventsText + */ + public function hasLogEventsText(): bool + { + return $this->logEventsText !== null; + } + + /** + * @throws LoggingNotConfiguredException + */ + public function logEventsText(): string + { + if (!$this->hasLogEventsText()) { + throw new LoggingNotConfiguredException; + } + + return $this->logEventsText; + } + + /** + * @phpstan-assert-if-true !null $this->logEventsVerboseText + */ + public function hasLogEventsVerboseText(): bool + { + return $this->logEventsVerboseText !== null; + } + + /** + * @throws LoggingNotConfiguredException + */ + public function logEventsVerboseText(): string + { + if (!$this->hasLogEventsVerboseText()) { + throw new LoggingNotConfiguredException; + } + + return $this->logEventsVerboseText; + } + + public function outputIsTeamCity(): bool + { + return $this->teamCityOutput; + } + + public function outputIsTestDox(): bool + { + return $this->testDoxOutput; + } + + public function testDoxOutputWithSummary(): bool + { + return $this->testDoxOutputSummary; + } + + /** + * @phpstan-assert-if-true !empty $this->testsCovering + */ + public function hasTestsCovering(): bool + { + return $this->testsCovering !== null; + } + + /** + * @throws FilterNotConfiguredException + * + * @return list + */ + public function testsCovering(): array + { + if (!$this->hasTestsCovering()) { + throw new FilterNotConfiguredException; + } + + return $this->testsCovering; + } + + /** + * @phpstan-assert-if-true !empty $this->testsUsing + */ + public function hasTestsUsing(): bool + { + return $this->testsUsing !== null; + } + + /** + * @throws FilterNotConfiguredException + * + * @return list + */ + public function testsUsing(): array + { + if (!$this->hasTestsUsing()) { + throw new FilterNotConfiguredException; + } + + return $this->testsUsing; + } + + /** + * @phpstan-assert-if-true !empty $this->testsRequiringPhpExtension + */ + public function hasTestsRequiringPhpExtension(): bool + { + return $this->testsRequiringPhpExtension !== null; + } + + /** + * @throws FilterNotConfiguredException + * + * @return non-empty-list + */ + public function testsRequiringPhpExtension(): array + { + if (!$this->hasTestsRequiringPhpExtension()) { + throw new FilterNotConfiguredException; + } + + return $this->testsRequiringPhpExtension; + } + + /** + * @phpstan-assert-if-true !null $this->filter + */ + public function hasFilter(): bool + { + return $this->filter !== null; + } + + /** + * @throws FilterNotConfiguredException + */ + public function filter(): string + { + if (!$this->hasFilter()) { + throw new FilterNotConfiguredException; + } + + return $this->filter; + } + + /** + * @phpstan-assert-if-true !null $this->excludeFilter + */ + public function hasExcludeFilter(): bool + { + return $this->excludeFilter !== null; + } + + /** + * @throws FilterNotConfiguredException + */ + public function excludeFilter(): string + { + if (!$this->hasExcludeFilter()) { + throw new FilterNotConfiguredException; + } + + return $this->excludeFilter; + } + + /** + * @phpstan-assert-if-true !empty $this->groups + */ + public function hasGroups(): bool + { + return $this->groups !== []; + } + + /** + * @throws FilterNotConfiguredException + * + * @return non-empty-list + */ + public function groups(): array + { + if (!$this->hasGroups()) { + throw new FilterNotConfiguredException; + } + + return $this->groups; + } + + /** + * @phpstan-assert-if-true !empty $this->excludeGroups + */ + public function hasExcludeGroups(): bool + { + return $this->excludeGroups !== []; + } + + /** + * @throws FilterNotConfiguredException + * + * @return non-empty-list + */ + public function excludeGroups(): array + { + if (!$this->hasExcludeGroups()) { + throw new FilterNotConfiguredException; + } + + return $this->excludeGroups; + } + + public function randomOrderSeed(): int + { + return $this->randomOrderSeed; + } + + public function includeUncoveredFiles(): bool + { + return $this->includeUncoveredFiles; + } + + public function testSuite(): TestSuiteCollection + { + return $this->testSuite; + } + + /** + * @deprecated Use includeTestSuites() instead + */ + public function includeTestSuite(): string + { + return $this->includeTestSuite; + } + + /** + * @return list + */ + public function includeTestSuites(): array + { + if ($this->includeTestSuite === '') { + return []; + } + + return explode(',', $this->includeTestSuite); + } + + /** + * @deprecated Use excludeTestSuites() instead + */ + public function excludeTestSuite(): string + { + return $this->excludeTestSuite; + } + + /** + * @return list + */ + public function excludeTestSuites(): array + { + if ($this->excludeTestSuite === '') { + return []; + } + + return explode(',', $this->excludeTestSuite); + } + + /** + * @phpstan-assert-if-true !null $this->defaultTestSuite + */ + public function hasDefaultTestSuite(): bool + { + return $this->defaultTestSuite !== null; + } + + /** + * @throws NoDefaultTestSuiteException + */ + public function defaultTestSuite(): string + { + if (!$this->hasDefaultTestSuite()) { + throw new NoDefaultTestSuiteException; + } + + return $this->defaultTestSuite; + } + + /** + * @return non-empty-list + */ + public function testSuffixes(): array + { + return $this->testSuffixes; + } + + public function php(): Php + { + return $this->php; + } + + public function controlGarbageCollector(): bool + { + return $this->controlGarbageCollector; + } + + public function numberOfTestsBeforeGarbageCollection(): int + { + return $this->numberOfTestsBeforeGarbageCollection; + } + + /** + * @phpstan-assert-if-true !null $this->generateBaseline + */ + public function hasGenerateBaseline(): bool + { + return $this->generateBaseline !== null; + } + + /** + * @throws NoBaselineException + * + * @return non-empty-string + */ + public function generateBaseline(): string + { + if (!$this->hasGenerateBaseline()) { + throw new NoBaselineException; + } + + return $this->generateBaseline; + } + + public function debug(): bool + { + return $this->debug; + } + + public function withTelemetry(): bool + { + return $this->withTelemetry; + } + + /** + * @return non-negative-int + */ + public function shortenArraysForExportThreshold(): int + { + return $this->shortenArraysForExportThreshold; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Exception/BootstrapScriptDoesNotExistException.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Exception/BootstrapScriptDoesNotExistException.php new file mode 100644 index 000000000..6146df2b5 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Exception/BootstrapScriptDoesNotExistException.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 PHPUnit\TextUI\Configuration; + +use function sprintf; +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class BootstrapScriptDoesNotExistException extends RuntimeException implements Exception +{ + public function __construct(string $filename) + { + parent::__construct( + sprintf( + 'Cannot open bootstrap script "%s"', + $filename, + ), + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Exception/BootstrapScriptException.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Exception/BootstrapScriptException.php new file mode 100644 index 000000000..123b9aeea --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Exception/BootstrapScriptException.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 PHPUnit\TextUI\Configuration; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class BootstrapScriptException extends RuntimeException implements Exception +{ +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Exception/CannotFindSchemaException.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Exception/CannotFindSchemaException.php new file mode 100644 index 000000000..6eef052db --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Exception/CannotFindSchemaException.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use PHPUnit\TextUI\Configuration\Exception; +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class CannotFindSchemaException extends RuntimeException implements Exception +{ +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Exception/CodeCoverageReportNotConfiguredException.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Exception/CodeCoverageReportNotConfiguredException.php new file mode 100644 index 000000000..83faa0a2d --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Exception/CodeCoverageReportNotConfiguredException.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 PHPUnit\TextUI\Configuration; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class CodeCoverageReportNotConfiguredException extends RuntimeException implements Exception +{ +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Exception/ConfigurationCannotBeBuiltException.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Exception/ConfigurationCannotBeBuiltException.php new file mode 100644 index 000000000..e95e09428 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Exception/ConfigurationCannotBeBuiltException.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 PHPUnit\TextUI\Configuration; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ConfigurationCannotBeBuiltException extends RuntimeException implements Exception +{ +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Exception/Exception.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Exception/Exception.php new file mode 100644 index 000000000..dc49125a5 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Exception/Exception.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This interface is not covered by the backward compatibility promise for PHPUnit + */ +interface Exception extends \PHPUnit\TextUI\Exception +{ +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Exception/FilterNotConfiguredException.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Exception/FilterNotConfiguredException.php new file mode 100644 index 000000000..5ae4331f6 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Exception/FilterNotConfiguredException.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 PHPUnit\TextUI\Configuration; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class FilterNotConfiguredException extends RuntimeException implements Exception +{ +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Exception/LoggingNotConfiguredException.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Exception/LoggingNotConfiguredException.php new file mode 100644 index 000000000..63cf9b070 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Exception/LoggingNotConfiguredException.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 PHPUnit\TextUI\Configuration; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class LoggingNotConfiguredException extends RuntimeException implements Exception +{ +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Exception/NoBaselineException.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Exception/NoBaselineException.php new file mode 100644 index 000000000..7611dceb1 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Exception/NoBaselineException.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 PHPUnit\TextUI\Configuration; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class NoBaselineException extends RuntimeException implements Exception +{ +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Exception/NoBootstrapException.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Exception/NoBootstrapException.php new file mode 100644 index 000000000..ff1bddf0e --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Exception/NoBootstrapException.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 PHPUnit\TextUI\Configuration; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class NoBootstrapException extends RuntimeException implements Exception +{ +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Exception/NoCacheDirectoryException.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Exception/NoCacheDirectoryException.php new file mode 100644 index 000000000..215fe21f6 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Exception/NoCacheDirectoryException.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 PHPUnit\TextUI\Configuration; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class NoCacheDirectoryException extends RuntimeException implements Exception +{ +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Exception/NoConfigurationFileException.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Exception/NoConfigurationFileException.php new file mode 100644 index 000000000..f8ceb80ba --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Exception/NoConfigurationFileException.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 PHPUnit\TextUI\Configuration; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class NoConfigurationFileException extends RuntimeException implements Exception +{ +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Exception/NoCoverageCacheDirectoryException.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Exception/NoCoverageCacheDirectoryException.php new file mode 100644 index 000000000..113950b5d --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Exception/NoCoverageCacheDirectoryException.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 PHPUnit\TextUI\Configuration; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class NoCoverageCacheDirectoryException extends RuntimeException implements Exception +{ +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Exception/NoCustomCssFileException.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Exception/NoCustomCssFileException.php new file mode 100644 index 000000000..e524c8db5 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Exception/NoCustomCssFileException.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 PHPUnit\TextUI\Configuration; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class NoCustomCssFileException extends RuntimeException implements Exception +{ +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Exception/NoDefaultTestSuiteException.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Exception/NoDefaultTestSuiteException.php new file mode 100644 index 000000000..96e7a7ada --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Exception/NoDefaultTestSuiteException.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 PHPUnit\TextUI\Configuration; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class NoDefaultTestSuiteException extends RuntimeException implements Exception +{ +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Exception/NoPharExtensionDirectoryException.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Exception/NoPharExtensionDirectoryException.php new file mode 100644 index 000000000..ce573ca79 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Exception/NoPharExtensionDirectoryException.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 PHPUnit\TextUI\Configuration; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class NoPharExtensionDirectoryException extends RuntimeException implements Exception +{ +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Exception/SpecificDeprecationToStopOnNotConfiguredException.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Exception/SpecificDeprecationToStopOnNotConfiguredException.php new file mode 100644 index 000000000..73074db13 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Exception/SpecificDeprecationToStopOnNotConfiguredException.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 PHPUnit\TextUI\Configuration; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class SpecificDeprecationToStopOnNotConfiguredException extends RuntimeException implements Exception +{ +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Merger.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Merger.php new file mode 100644 index 000000000..8ee0b72ce --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Merger.php @@ -0,0 +1,1060 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use const DIRECTORY_SEPARATOR; +use const PATH_SEPARATOR; +use function array_diff; +use function assert; +use function dirname; +use function explode; +use function is_int; +use function realpath; +use function time; +use PHPUnit\Event\Facade as EventFacade; +use PHPUnit\Runner\TestSuiteSorter; +use PHPUnit\TextUI\CliArguments\Configuration as CliConfiguration; +use PHPUnit\TextUI\CliArguments\Exception; +use PHPUnit\TextUI\XmlConfiguration\Configuration as XmlConfiguration; +use PHPUnit\TextUI\XmlConfiguration\LoadedFromFileConfiguration; +use PHPUnit\TextUI\XmlConfiguration\SchemaDetector; +use PHPUnit\Util\Filesystem; +use SebastianBergmann\CodeCoverage\Report\Html\Colors; +use SebastianBergmann\CodeCoverage\Report\Thresholds; +use SebastianBergmann\Environment\Console; +use SebastianBergmann\Invoker\Invoker; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Merger +{ + /** + * @throws \PHPUnit\TextUI\XmlConfiguration\Exception + * @throws Exception + * @throws NoCustomCssFileException + */ + public function merge(CliConfiguration $cliConfiguration, XmlConfiguration $xmlConfiguration): Configuration + { + $configurationFile = null; + + if ($xmlConfiguration->wasLoadedFromFile()) { + assert($xmlConfiguration instanceof LoadedFromFileConfiguration); + + $configurationFile = $xmlConfiguration->filename(); + } + + $bootstrap = null; + + if ($cliConfiguration->hasBootstrap()) { + $bootstrap = $cliConfiguration->bootstrap(); + } elseif ($xmlConfiguration->phpunit()->hasBootstrap()) { + $bootstrap = $xmlConfiguration->phpunit()->bootstrap(); + } + + if ($cliConfiguration->hasCacheResult()) { + $cacheResult = $cliConfiguration->cacheResult(); + } else { + $cacheResult = $xmlConfiguration->phpunit()->cacheResult(); + } + + $cacheDirectory = null; + $coverageCacheDirectory = null; + + if ($cliConfiguration->hasCacheDirectory() && Filesystem::createDirectory($cliConfiguration->cacheDirectory())) { + $cacheDirectory = realpath($cliConfiguration->cacheDirectory()); + } elseif ($xmlConfiguration->phpunit()->hasCacheDirectory() && Filesystem::createDirectory($xmlConfiguration->phpunit()->cacheDirectory())) { + $cacheDirectory = realpath($xmlConfiguration->phpunit()->cacheDirectory()); + } + + if ($cacheDirectory !== null) { + $coverageCacheDirectory = $cacheDirectory . DIRECTORY_SEPARATOR . 'code-coverage'; + $testResultCacheFile = $cacheDirectory . DIRECTORY_SEPARATOR . 'test-results'; + } + + if (!isset($testResultCacheFile)) { + if ($xmlConfiguration->wasLoadedFromFile()) { + $testResultCacheFile = dirname(realpath($xmlConfiguration->filename())) . DIRECTORY_SEPARATOR . '.phpunit.result.cache'; + } else { + $candidate = realpath($_SERVER['PHP_SELF']); + + if ($candidate) { + $testResultCacheFile = dirname($candidate) . DIRECTORY_SEPARATOR . '.phpunit.result.cache'; + } else { + $testResultCacheFile = '.phpunit.result.cache'; + } + } + } + + if ($cliConfiguration->hasDisableCodeCoverageIgnore()) { + $disableCodeCoverageIgnore = $cliConfiguration->disableCodeCoverageIgnore(); + } else { + $disableCodeCoverageIgnore = $xmlConfiguration->codeCoverage()->disableCodeCoverageIgnore(); + } + + if ($cliConfiguration->hasFailOnAllIssues()) { + $failOnAllIssues = $cliConfiguration->failOnAllIssues(); + } else { + $failOnAllIssues = $xmlConfiguration->phpunit()->failOnAllIssues(); + } + + if ($cliConfiguration->hasFailOnDeprecation()) { + $failOnDeprecation = $cliConfiguration->failOnDeprecation(); + } else { + $failOnDeprecation = $xmlConfiguration->phpunit()->failOnDeprecation(); + } + + if ($cliConfiguration->hasFailOnPhpunitDeprecation()) { + $failOnPhpunitDeprecation = $cliConfiguration->failOnPhpunitDeprecation(); + } else { + $failOnPhpunitDeprecation = $xmlConfiguration->phpunit()->failOnPhpunitDeprecation(); + } + + if ($cliConfiguration->hasFailOnPhpunitNotice()) { + $failOnPhpunitNotice = $cliConfiguration->failOnPhpunitNotice(); + } else { + $failOnPhpunitNotice = $xmlConfiguration->phpunit()->failOnPhpunitNotice(); + } + + if ($cliConfiguration->hasFailOnPhpunitWarning()) { + $failOnPhpunitWarning = $cliConfiguration->failOnPhpunitWarning(); + } else { + $failOnPhpunitWarning = $xmlConfiguration->phpunit()->failOnPhpunitWarning(); + } + + if ($cliConfiguration->hasFailOnEmptyTestSuite()) { + $failOnEmptyTestSuite = $cliConfiguration->failOnEmptyTestSuite(); + } else { + $failOnEmptyTestSuite = $xmlConfiguration->phpunit()->failOnEmptyTestSuite(); + } + + if ($cliConfiguration->hasFailOnIncomplete()) { + $failOnIncomplete = $cliConfiguration->failOnIncomplete(); + } else { + $failOnIncomplete = $xmlConfiguration->phpunit()->failOnIncomplete(); + } + + if ($cliConfiguration->hasFailOnNotice()) { + $failOnNotice = $cliConfiguration->failOnNotice(); + } else { + $failOnNotice = $xmlConfiguration->phpunit()->failOnNotice(); + } + + if ($cliConfiguration->hasFailOnRisky()) { + $failOnRisky = $cliConfiguration->failOnRisky(); + } else { + $failOnRisky = $xmlConfiguration->phpunit()->failOnRisky(); + } + + if ($cliConfiguration->hasFailOnSkipped()) { + $failOnSkipped = $cliConfiguration->failOnSkipped(); + } else { + $failOnSkipped = $xmlConfiguration->phpunit()->failOnSkipped(); + } + + if ($cliConfiguration->hasFailOnWarning()) { + $failOnWarning = $cliConfiguration->failOnWarning(); + } else { + $failOnWarning = $xmlConfiguration->phpunit()->failOnWarning(); + } + + $doNotFailOnDeprecation = false; + + if ($cliConfiguration->hasDoNotFailOnDeprecation()) { + $doNotFailOnDeprecation = $cliConfiguration->doNotFailOnDeprecation(); + } + + $doNotFailOnPhpunitDeprecation = false; + + if ($cliConfiguration->hasDoNotFailOnPhpunitDeprecation()) { + $doNotFailOnPhpunitDeprecation = $cliConfiguration->doNotFailOnPhpunitDeprecation(); + } + + $doNotFailOnPhpunitNotice = false; + + if ($cliConfiguration->hasDoNotFailOnPhpunitNotice()) { + $doNotFailOnPhpunitNotice = $cliConfiguration->doNotFailOnPhpunitNotice(); + } + + $doNotFailOnPhpunitWarning = false; + + if ($cliConfiguration->hasDoNotFailOnPhpunitWarning()) { + $doNotFailOnPhpunitWarning = $cliConfiguration->doNotFailOnPhpunitWarning(); + } + + $doNotFailOnEmptyTestSuite = false; + + if ($cliConfiguration->hasDoNotFailOnEmptyTestSuite()) { + $doNotFailOnEmptyTestSuite = $cliConfiguration->doNotFailOnEmptyTestSuite(); + } + + $doNotFailOnIncomplete = false; + + if ($cliConfiguration->hasDoNotFailOnIncomplete()) { + $doNotFailOnIncomplete = $cliConfiguration->doNotFailOnIncomplete(); + } + + $doNotFailOnNotice = false; + + if ($cliConfiguration->hasDoNotFailOnNotice()) { + $doNotFailOnNotice = $cliConfiguration->doNotFailOnNotice(); + } + + $doNotFailOnRisky = false; + + if ($cliConfiguration->hasDoNotFailOnRisky()) { + $doNotFailOnRisky = $cliConfiguration->doNotFailOnRisky(); + } + + $doNotFailOnSkipped = false; + + if ($cliConfiguration->hasDoNotFailOnSkipped()) { + $doNotFailOnSkipped = $cliConfiguration->doNotFailOnSkipped(); + } + + $doNotFailOnWarning = false; + + if ($cliConfiguration->hasDoNotFailOnWarning()) { + $doNotFailOnWarning = $cliConfiguration->doNotFailOnWarning(); + } + + if ($cliConfiguration->hasStopOnDefect()) { + $stopOnDefect = $cliConfiguration->stopOnDefect(); + } else { + $stopOnDefect = $xmlConfiguration->phpunit()->stopOnDefect(); + } + + if ($cliConfiguration->hasStopOnDeprecation()) { + $stopOnDeprecation = $cliConfiguration->stopOnDeprecation(); + } else { + $stopOnDeprecation = $xmlConfiguration->phpunit()->stopOnDeprecation(); + } + + $specificDeprecationToStopOn = null; + + if ($cliConfiguration->hasSpecificDeprecationToStopOn()) { + $specificDeprecationToStopOn = $cliConfiguration->specificDeprecationToStopOn(); + } + + if ($cliConfiguration->hasStopOnError()) { + $stopOnError = $cliConfiguration->stopOnError(); + } else { + $stopOnError = $xmlConfiguration->phpunit()->stopOnError(); + } + + if ($cliConfiguration->hasStopOnFailure()) { + $stopOnFailure = $cliConfiguration->stopOnFailure(); + } else { + $stopOnFailure = $xmlConfiguration->phpunit()->stopOnFailure(); + } + + if ($cliConfiguration->hasStopOnIncomplete()) { + $stopOnIncomplete = $cliConfiguration->stopOnIncomplete(); + } else { + $stopOnIncomplete = $xmlConfiguration->phpunit()->stopOnIncomplete(); + } + + if ($cliConfiguration->hasStopOnNotice()) { + $stopOnNotice = $cliConfiguration->stopOnNotice(); + } else { + $stopOnNotice = $xmlConfiguration->phpunit()->stopOnNotice(); + } + + if ($cliConfiguration->hasStopOnRisky()) { + $stopOnRisky = $cliConfiguration->stopOnRisky(); + } else { + $stopOnRisky = $xmlConfiguration->phpunit()->stopOnRisky(); + } + + if ($cliConfiguration->hasStopOnSkipped()) { + $stopOnSkipped = $cliConfiguration->stopOnSkipped(); + } else { + $stopOnSkipped = $xmlConfiguration->phpunit()->stopOnSkipped(); + } + + if ($cliConfiguration->hasStopOnWarning()) { + $stopOnWarning = $cliConfiguration->stopOnWarning(); + } else { + $stopOnWarning = $xmlConfiguration->phpunit()->stopOnWarning(); + } + + if ($cliConfiguration->hasStderr() && $cliConfiguration->stderr()) { + $outputToStandardErrorStream = true; + } else { + $outputToStandardErrorStream = $xmlConfiguration->phpunit()->stderr(); + } + + if ($cliConfiguration->hasColumns()) { + $columns = $cliConfiguration->columns(); + } else { + $columns = $xmlConfiguration->phpunit()->columns(); + } + + if ($columns === 'max') { + $columns = (new Console)->getNumberOfColumns(); + } + + if ($columns < 16) { + $columns = 16; + + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning( + 'Less than 16 columns requested, number of columns set to 16', + ); + } + + assert(is_int($columns)); + + $noExtensions = false; + + if ($cliConfiguration->hasNoExtensions() && $cliConfiguration->noExtensions()) { + $noExtensions = true; + } + + $pharExtensionDirectory = null; + + if ($xmlConfiguration->phpunit()->hasExtensionsDirectory()) { + $pharExtensionDirectory = $xmlConfiguration->phpunit()->extensionsDirectory(); + } + + $extensionBootstrappers = []; + + if ($cliConfiguration->hasExtensions()) { + foreach ($cliConfiguration->extensions() as $extension) { + $extensionBootstrappers[] = [ + 'className' => $extension, + 'parameters' => [], + ]; + } + } + + foreach ($xmlConfiguration->extensions() as $extension) { + $extensionBootstrappers[] = [ + 'className' => $extension->className(), + 'parameters' => $extension->parameters(), + ]; + } + + if ($cliConfiguration->hasPathCoverage() && $cliConfiguration->pathCoverage()) { + $pathCoverage = $cliConfiguration->pathCoverage(); + } else { + $pathCoverage = $xmlConfiguration->codeCoverage()->pathCoverage(); + } + + $defaultColors = Colors::default(); + $defaultThresholds = Thresholds::default(); + + $coverageClover = null; + $coverageCobertura = null; + $coverageCrap4j = null; + $coverageCrap4jThreshold = 30; + $coverageHtml = null; + $coverageHtmlLowUpperBound = $defaultThresholds->lowUpperBound(); + $coverageHtmlHighLowerBound = $defaultThresholds->highLowerBound(); + $coverageHtmlColorSuccessLow = $defaultColors->successLow(); + $coverageHtmlColorSuccessMedium = $defaultColors->successMedium(); + $coverageHtmlColorSuccessHigh = $defaultColors->successHigh(); + $coverageHtmlColorWarning = $defaultColors->warning(); + $coverageHtmlColorDanger = $defaultColors->danger(); + $coverageHtmlCustomCssFile = null; + $coverageOpenClover = null; + $coveragePhp = null; + $coverageText = null; + $coverageTextShowUncoveredFiles = false; + $coverageTextShowOnlySummary = false; + $coverageXml = null; + $coverageFromXmlConfiguration = true; + + if ($cliConfiguration->hasNoCoverage() && $cliConfiguration->noCoverage()) { + $coverageFromXmlConfiguration = false; + } + + if ($cliConfiguration->hasCoverageClover()) { + $coverageClover = $cliConfiguration->coverageClover(); + } elseif ($coverageFromXmlConfiguration && $xmlConfiguration->codeCoverage()->hasClover()) { + $coverageClover = $xmlConfiguration->codeCoverage()->clover()->target()->path(); + } + + if ($cliConfiguration->hasCoverageCobertura()) { + $coverageCobertura = $cliConfiguration->coverageCobertura(); + } elseif ($coverageFromXmlConfiguration && $xmlConfiguration->codeCoverage()->hasCobertura()) { + $coverageCobertura = $xmlConfiguration->codeCoverage()->cobertura()->target()->path(); + } + + if ($xmlConfiguration->codeCoverage()->hasCrap4j()) { + $coverageCrap4jThreshold = $xmlConfiguration->codeCoverage()->crap4j()->threshold(); + } + + if ($cliConfiguration->hasCoverageCrap4J()) { + $coverageCrap4j = $cliConfiguration->coverageCrap4J(); + } elseif ($coverageFromXmlConfiguration && $xmlConfiguration->codeCoverage()->hasCrap4j()) { + $coverageCrap4j = $xmlConfiguration->codeCoverage()->crap4j()->target()->path(); + } + + if ($xmlConfiguration->codeCoverage()->hasHtml()) { + $coverageHtmlHighLowerBound = $xmlConfiguration->codeCoverage()->html()->highLowerBound(); + $coverageHtmlLowUpperBound = $xmlConfiguration->codeCoverage()->html()->lowUpperBound(); + + if ($coverageHtmlLowUpperBound > $coverageHtmlHighLowerBound) { + $coverageHtmlLowUpperBound = $defaultThresholds->lowUpperBound(); + $coverageHtmlHighLowerBound = $defaultThresholds->highLowerBound(); + } + + $coverageHtmlColorSuccessLow = $xmlConfiguration->codeCoverage()->html()->colorSuccessLow(); + $coverageHtmlColorSuccessMedium = $xmlConfiguration->codeCoverage()->html()->colorSuccessMedium(); + $coverageHtmlColorSuccessHigh = $xmlConfiguration->codeCoverage()->html()->colorSuccessHigh(); + $coverageHtmlColorWarning = $xmlConfiguration->codeCoverage()->html()->colorWarning(); + $coverageHtmlColorDanger = $xmlConfiguration->codeCoverage()->html()->colorDanger(); + + if ($xmlConfiguration->codeCoverage()->html()->hasCustomCssFile()) { + $coverageHtmlCustomCssFile = $xmlConfiguration->codeCoverage()->html()->customCssFile(); + } + } + + if ($cliConfiguration->hasCoverageHtml()) { + $coverageHtml = $cliConfiguration->coverageHtml(); + } elseif ($coverageFromXmlConfiguration && $xmlConfiguration->codeCoverage()->hasHtml()) { + $coverageHtml = $xmlConfiguration->codeCoverage()->html()->target()->path(); + } + + if ($cliConfiguration->hasCoverageOpenClover()) { + $coverageOpenClover = $cliConfiguration->coverageOpenClover(); + } elseif ($coverageFromXmlConfiguration && $xmlConfiguration->codeCoverage()->hasOpenClover()) { + $coverageOpenClover = $xmlConfiguration->codeCoverage()->openClover()->target()->path(); + } + + if ($cliConfiguration->hasCoveragePhp()) { + $coveragePhp = $cliConfiguration->coveragePhp(); + } elseif ($coverageFromXmlConfiguration && $xmlConfiguration->codeCoverage()->hasPhp()) { + $coveragePhp = $xmlConfiguration->codeCoverage()->php()->target()->path(); + } + + if ($xmlConfiguration->codeCoverage()->hasText()) { + $coverageTextShowUncoveredFiles = $xmlConfiguration->codeCoverage()->text()->showUncoveredFiles(); + $coverageTextShowOnlySummary = $xmlConfiguration->codeCoverage()->text()->showOnlySummary(); + } + + if ($cliConfiguration->hasCoverageTextShowUncoveredFiles()) { + $coverageTextShowUncoveredFiles = $cliConfiguration->coverageTextShowUncoveredFiles(); + } + + if ($cliConfiguration->hasCoverageTextShowOnlySummary()) { + $coverageTextShowOnlySummary = $cliConfiguration->coverageTextShowOnlySummary(); + } + + if ($cliConfiguration->hasCoverageText()) { + $coverageText = $cliConfiguration->coverageText(); + } elseif ($coverageFromXmlConfiguration && $xmlConfiguration->codeCoverage()->hasText()) { + $coverageText = $xmlConfiguration->codeCoverage()->text()->target()->path(); + } + + if ($cliConfiguration->hasCoverageXml()) { + $coverageXml = $cliConfiguration->coverageXml(); + } elseif ($coverageFromXmlConfiguration && $xmlConfiguration->codeCoverage()->hasXml()) { + $coverageXml = $xmlConfiguration->codeCoverage()->xml()->target()->path(); + } + + if ($cliConfiguration->hasBackupGlobals()) { + $backupGlobals = $cliConfiguration->backupGlobals(); + } else { + $backupGlobals = $xmlConfiguration->phpunit()->backupGlobals(); + } + + if ($cliConfiguration->hasBackupStaticProperties()) { + $backupStaticProperties = $cliConfiguration->backupStaticProperties(); + } else { + $backupStaticProperties = $xmlConfiguration->phpunit()->backupStaticProperties(); + } + + if ($cliConfiguration->hasBeStrictAboutChangesToGlobalState()) { + $beStrictAboutChangesToGlobalState = $cliConfiguration->beStrictAboutChangesToGlobalState(); + } else { + $beStrictAboutChangesToGlobalState = $xmlConfiguration->phpunit()->beStrictAboutChangesToGlobalState(); + } + + if ($cliConfiguration->hasProcessIsolation()) { + $processIsolation = $cliConfiguration->processIsolation(); + } else { + $processIsolation = $xmlConfiguration->phpunit()->processIsolation(); + } + + if ($cliConfiguration->hasEnforceTimeLimit()) { + $enforceTimeLimit = $cliConfiguration->enforceTimeLimit(); + } else { + $enforceTimeLimit = $xmlConfiguration->phpunit()->enforceTimeLimit(); + } + + if ($enforceTimeLimit && !(new Invoker)->canInvokeWithTimeout()) { + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning( + 'The pcntl extension is required for enforcing time limits', + ); + } + + if ($cliConfiguration->hasDefaultTimeLimit()) { + $defaultTimeLimit = $cliConfiguration->defaultTimeLimit(); + } else { + $defaultTimeLimit = $xmlConfiguration->phpunit()->defaultTimeLimit(); + } + + $timeoutForSmallTests = $xmlConfiguration->phpunit()->timeoutForSmallTests(); + $timeoutForMediumTests = $xmlConfiguration->phpunit()->timeoutForMediumTests(); + $timeoutForLargeTests = $xmlConfiguration->phpunit()->timeoutForLargeTests(); + + if ($cliConfiguration->hasReportUselessTests()) { + $reportUselessTests = $cliConfiguration->reportUselessTests(); + } else { + $reportUselessTests = $xmlConfiguration->phpunit()->beStrictAboutTestsThatDoNotTestAnything(); + } + + if ($cliConfiguration->hasStrictCoverage()) { + $strictCoverage = $cliConfiguration->strictCoverage(); + } else { + $strictCoverage = $xmlConfiguration->phpunit()->beStrictAboutCoverageMetadata(); + } + + if ($cliConfiguration->hasDisallowTestOutput()) { + $disallowTestOutput = $cliConfiguration->disallowTestOutput(); + } else { + $disallowTestOutput = $xmlConfiguration->phpunit()->beStrictAboutOutputDuringTests(); + } + + if ($cliConfiguration->hasDisplayDetailsOnAllIssues()) { + $displayDetailsOnAllIssues = $cliConfiguration->displayDetailsOnAllIssues(); + } else { + $displayDetailsOnAllIssues = $xmlConfiguration->phpunit()->displayDetailsOnAllIssues(); + } + + if ($cliConfiguration->hasDisplayDetailsOnIncompleteTests()) { + $displayDetailsOnIncompleteTests = $cliConfiguration->displayDetailsOnIncompleteTests(); + } else { + $displayDetailsOnIncompleteTests = $xmlConfiguration->phpunit()->displayDetailsOnIncompleteTests(); + } + + if ($cliConfiguration->hasDisplayDetailsOnSkippedTests()) { + $displayDetailsOnSkippedTests = $cliConfiguration->displayDetailsOnSkippedTests(); + } else { + $displayDetailsOnSkippedTests = $xmlConfiguration->phpunit()->displayDetailsOnSkippedTests(); + } + + if ($cliConfiguration->hasDisplayDetailsOnTestsThatTriggerDeprecations()) { + $displayDetailsOnTestsThatTriggerDeprecations = $cliConfiguration->displayDetailsOnTestsThatTriggerDeprecations(); + } else { + $displayDetailsOnTestsThatTriggerDeprecations = $xmlConfiguration->phpunit()->displayDetailsOnTestsThatTriggerDeprecations(); + } + + if ($cliConfiguration->hasDisplayDetailsOnPhpunitDeprecations()) { + $displayDetailsOnPhpunitDeprecations = $cliConfiguration->displayDetailsOnPhpunitDeprecations(); + } else { + $displayDetailsOnPhpunitDeprecations = $xmlConfiguration->phpunit()->displayDetailsOnPhpunitDeprecations(); + } + + if ($cliConfiguration->hasDisplayDetailsOnPhpunitNotices()) { + $displayDetailsOnPhpunitNotices = $cliConfiguration->displayDetailsOnPhpunitNotices(); + } else { + $displayDetailsOnPhpunitNotices = $xmlConfiguration->phpunit()->displayDetailsOnPhpunitNotices(); + } + + if ($cliConfiguration->hasDisplayDetailsOnTestsThatTriggerErrors()) { + $displayDetailsOnTestsThatTriggerErrors = $cliConfiguration->displayDetailsOnTestsThatTriggerErrors(); + } else { + $displayDetailsOnTestsThatTriggerErrors = $xmlConfiguration->phpunit()->displayDetailsOnTestsThatTriggerErrors(); + } + + if ($cliConfiguration->hasDisplayDetailsOnTestsThatTriggerNotices()) { + $displayDetailsOnTestsThatTriggerNotices = $cliConfiguration->displayDetailsOnTestsThatTriggerNotices(); + } else { + $displayDetailsOnTestsThatTriggerNotices = $xmlConfiguration->phpunit()->displayDetailsOnTestsThatTriggerNotices(); + } + + if ($cliConfiguration->hasDisplayDetailsOnTestsThatTriggerWarnings()) { + $displayDetailsOnTestsThatTriggerWarnings = $cliConfiguration->displayDetailsOnTestsThatTriggerWarnings(); + } else { + $displayDetailsOnTestsThatTriggerWarnings = $xmlConfiguration->phpunit()->displayDetailsOnTestsThatTriggerWarnings(); + } + + if ($cliConfiguration->hasReverseList()) { + $reverseDefectList = $cliConfiguration->reverseList(); + } else { + $reverseDefectList = $xmlConfiguration->phpunit()->reverseDefectList(); + } + + $requireCoverageMetadata = $xmlConfiguration->phpunit()->requireCoverageMetadata(); + + if ($cliConfiguration->hasExecutionOrder()) { + $executionOrder = $cliConfiguration->executionOrder(); + } else { + $executionOrder = $xmlConfiguration->phpunit()->executionOrder(); + } + + $executionOrderDefects = TestSuiteSorter::ORDER_DEFAULT; + + if ($cliConfiguration->hasExecutionOrderDefects()) { + $executionOrderDefects = $cliConfiguration->executionOrderDefects(); + } elseif ($xmlConfiguration->phpunit()->defectsFirst()) { + $executionOrderDefects = TestSuiteSorter::ORDER_DEFECTS_FIRST; + } + + if ($cliConfiguration->hasResolveDependencies()) { + $resolveDependencies = $cliConfiguration->resolveDependencies(); + } else { + $resolveDependencies = $xmlConfiguration->phpunit()->resolveDependencies(); + } + + $colors = false; + $colorsSupported = (new Console)->hasColorSupport(); + + if ($cliConfiguration->hasColors()) { + if ($cliConfiguration->colors() === Configuration::COLOR_ALWAYS) { + $colors = true; + } elseif ($colorsSupported && $cliConfiguration->colors() === Configuration::COLOR_AUTO) { + $colors = true; + } + } elseif ($xmlConfiguration->phpunit()->colors() === Configuration::COLOR_ALWAYS) { + $colors = true; + } elseif ($colorsSupported && $xmlConfiguration->phpunit()->colors() === Configuration::COLOR_AUTO) { + $colors = true; + } + + $logfileTeamcity = null; + $logfileJunit = null; + $logfileOtr = null; + $logfileTestdoxHtml = null; + $logfileTestdoxText = null; + $loggingFromXmlConfiguration = true; + + if ($cliConfiguration->hasNoLogging() && $cliConfiguration->noLogging()) { + $loggingFromXmlConfiguration = false; + } + + if ($cliConfiguration->hasTeamcityLogfile()) { + $logfileTeamcity = $cliConfiguration->teamcityLogfile(); + } elseif ($loggingFromXmlConfiguration && $xmlConfiguration->logging()->hasTeamCity()) { + $logfileTeamcity = $xmlConfiguration->logging()->teamCity()->target()->path(); + } + + if ($cliConfiguration->hasJunitLogfile()) { + $logfileJunit = $cliConfiguration->junitLogfile(); + } elseif ($loggingFromXmlConfiguration && $xmlConfiguration->logging()->hasJunit()) { + $logfileJunit = $xmlConfiguration->logging()->junit()->target()->path(); + } + + if ($cliConfiguration->hasOtrLogfile()) { + $logfileOtr = $cliConfiguration->otrLogfile(); + } elseif ($loggingFromXmlConfiguration && $xmlConfiguration->logging()->hasOtr()) { + $logfileOtr = $xmlConfiguration->logging()->otr()->target()->path(); + } + + $includeGitInformationInOtrLogfile = false; + + if ($cliConfiguration->hasIncludeGitInformationInOtrLogfile()) { + $includeGitInformationInOtrLogfile = $cliConfiguration->includeGitInformationInOtrLogfile(); + } elseif ($loggingFromXmlConfiguration && $xmlConfiguration->logging()->hasOtr()) { + $includeGitInformationInOtrLogfile = $xmlConfiguration->logging()->otr()->includeGitInformation(); + } + + if ($cliConfiguration->hasTestdoxHtmlFile()) { + $logfileTestdoxHtml = $cliConfiguration->testdoxHtmlFile(); + } elseif ($loggingFromXmlConfiguration && $xmlConfiguration->logging()->hasTestDoxHtml()) { + $logfileTestdoxHtml = $xmlConfiguration->logging()->testDoxHtml()->target()->path(); + } + + if ($cliConfiguration->hasTestdoxTextFile()) { + $logfileTestdoxText = $cliConfiguration->testdoxTextFile(); + } elseif ($loggingFromXmlConfiguration && $xmlConfiguration->logging()->hasTestDoxText()) { + $logfileTestdoxText = $xmlConfiguration->logging()->testDoxText()->target()->path(); + } + + $logEventsText = null; + + if ($cliConfiguration->hasLogEventsText()) { + $logEventsText = $cliConfiguration->logEventsText(); + } + + $logEventsVerboseText = null; + + if ($cliConfiguration->hasLogEventsVerboseText()) { + $logEventsVerboseText = $cliConfiguration->logEventsVerboseText(); + } + + $teamCityOutput = false; + + if ($cliConfiguration->hasTeamCityPrinter() && $cliConfiguration->teamCityPrinter()) { + $teamCityOutput = true; + } + + if ($cliConfiguration->hasTestDoxPrinter() && $cliConfiguration->testdoxPrinter()) { + $testDoxOutput = true; + } else { + $testDoxOutput = $xmlConfiguration->phpunit()->testdoxPrinter(); + } + + if ($cliConfiguration->hasTestDoxPrinterSummary() && $cliConfiguration->testdoxPrinterSummary()) { + $testDoxOutputSummary = true; + } else { + $testDoxOutputSummary = $xmlConfiguration->phpunit()->testdoxPrinterSummary(); + } + + $noProgress = false; + + if ($cliConfiguration->hasNoProgress() && $cliConfiguration->noProgress()) { + $noProgress = true; + } + + $noResults = false; + + if ($cliConfiguration->hasNoResults() && $cliConfiguration->noResults()) { + $noResults = true; + } + + $noOutput = false; + + if ($cliConfiguration->hasNoOutput() && $cliConfiguration->noOutput()) { + $noOutput = true; + } + + $testsCovering = null; + + if ($cliConfiguration->hasTestsCovering()) { + $testsCovering = $cliConfiguration->testsCovering(); + } + + $testsUsing = null; + + if ($cliConfiguration->hasTestsUsing()) { + $testsUsing = $cliConfiguration->testsUsing(); + } + + $testsRequiringPhpExtension = null; + + if ($cliConfiguration->hasTestsRequiringPhpExtension()) { + $testsRequiringPhpExtension = $cliConfiguration->testsRequiringPhpExtension(); + } + + $filter = null; + + if ($cliConfiguration->hasFilter()) { + $filter = $cliConfiguration->filter(); + } + + $excludeFilter = null; + + if ($cliConfiguration->hasExcludeFilter()) { + $excludeFilter = $cliConfiguration->excludeFilter(); + } + + if ($cliConfiguration->hasGroups()) { + $groups = $cliConfiguration->groups(); + } else { + $groups = $xmlConfiguration->groups()->include()->asArrayOfStrings(); + } + + if ($cliConfiguration->hasExcludeGroups()) { + $excludeGroups = $cliConfiguration->excludeGroups(); + } else { + $excludeGroups = $xmlConfiguration->groups()->exclude()->asArrayOfStrings(); + } + + $excludeGroups = array_diff($excludeGroups, $groups); + + if ($cliConfiguration->hasRandomOrderSeed()) { + $randomOrderSeed = $cliConfiguration->randomOrderSeed(); + } else { + $randomOrderSeed = time(); + } + + if ($xmlConfiguration->wasLoadedFromFile() && $xmlConfiguration->hasValidationErrors()) { + if ((new SchemaDetector)->detect($xmlConfiguration->filename())->detected()) { + EventFacade::emitter()->testRunnerTriggeredPhpunitDeprecation( + 'Your XML configuration validates against a deprecated schema. Migrate your XML configuration using "--migrate-configuration"!', + ); + } else { + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning( + "Test results may not be as expected because the XML configuration file did not pass validation:\n" . + $xmlConfiguration->validationErrors(), + ); + } + } + + $includeUncoveredFiles = $xmlConfiguration->codeCoverage()->includeUncoveredFiles(); + + $includePaths = []; + + if ($cliConfiguration->hasIncludePath()) { + foreach (explode(PATH_SEPARATOR, $cliConfiguration->includePath()) as $includePath) { + $includePaths[] = new Directory($includePath); + } + } + + foreach ($xmlConfiguration->php()->includePaths() as $includePath) { + $includePaths[] = $includePath; + } + + $iniSettings = []; + + if ($cliConfiguration->hasIniSettings()) { + foreach ($cliConfiguration->iniSettings() as $name => $value) { + $iniSettings[] = new IniSetting($name, $value); + } + } + + foreach ($xmlConfiguration->php()->iniSettings() as $iniSetting) { + $iniSettings[] = $iniSetting; + } + + $includeTestSuite = ''; + + if ($cliConfiguration->hasTestSuite()) { + $includeTestSuite = $cliConfiguration->testSuite(); + } elseif ($xmlConfiguration->phpunit()->hasDefaultTestSuite()) { + $includeTestSuite = $xmlConfiguration->phpunit()->defaultTestSuite(); + } + + $excludeTestSuite = ''; + + if ($cliConfiguration->hasExcludedTestSuite()) { + $excludeTestSuite = $cliConfiguration->excludedTestSuite(); + } + + $testSuffixes = ['Test.php', '.phpt']; + + if ($cliConfiguration->hasTestSuffixes()) { + $testSuffixes = $cliConfiguration->testSuffixes(); + } + + $sourceIncludeDirectories = []; + + if ($cliConfiguration->hasCoverageFilter()) { + foreach ($cliConfiguration->coverageFilter() as $directory) { + $sourceIncludeDirectories[] = new FilterDirectory($directory, '', '.php'); + } + } + + foreach ($xmlConfiguration->source()->includeDirectories() as $directory) { + $sourceIncludeDirectories[] = $directory; + } + + $sourceIncludeFiles = $xmlConfiguration->source()->includeFiles(); + $sourceExcludeDirectories = $xmlConfiguration->source()->excludeDirectories(); + $sourceExcludeFiles = $xmlConfiguration->source()->excludeFiles(); + + $useBaseline = null; + $generateBaseline = null; + + if (!$cliConfiguration->hasGenerateBaseline()) { + if ($cliConfiguration->hasUseBaseline()) { + $useBaseline = $cliConfiguration->useBaseline(); + } elseif ($xmlConfiguration->source()->hasBaseline()) { + $useBaseline = $xmlConfiguration->source()->baseline(); + } + } else { + $generateBaseline = $cliConfiguration->generateBaseline(); + } + + assert($useBaseline !== ''); + assert($generateBaseline !== ''); + + if ($failOnAllIssues) { + $displayDetailsOnAllIssues = true; + } + + if ($failOnDeprecation) { + $displayDetailsOnTestsThatTriggerDeprecations = true; + } + + if ($failOnPhpunitDeprecation) { + $displayDetailsOnPhpunitDeprecations = true; + } + + if ($failOnPhpunitNotice) { + $displayDetailsOnPhpunitNotices = true; + } + + if ($failOnNotice) { + $displayDetailsOnTestsThatTriggerNotices = true; + } + + if ($failOnWarning) { + $displayDetailsOnTestsThatTriggerWarnings = true; + } + + if ($failOnIncomplete) { + $displayDetailsOnIncompleteTests = true; + } + + if ($failOnSkipped) { + $displayDetailsOnSkippedTests = true; + } + + return new Configuration( + $cliConfiguration->arguments(), + $configurationFile, + $bootstrap, + $xmlConfiguration->phpunit()->bootstrapForTestSuite(), + $cacheResult, + $cacheDirectory, + $coverageCacheDirectory, + new Source( + $useBaseline, + $cliConfiguration->ignoreBaseline(), + FilterDirectoryCollection::fromArray($sourceIncludeDirectories), + $sourceIncludeFiles, + $sourceExcludeDirectories, + $sourceExcludeFiles, + $xmlConfiguration->source()->restrictNotices(), + $xmlConfiguration->source()->restrictWarnings(), + $xmlConfiguration->source()->ignoreSuppressionOfDeprecations(), + $xmlConfiguration->source()->ignoreSuppressionOfPhpDeprecations(), + $xmlConfiguration->source()->ignoreSuppressionOfErrors(), + $xmlConfiguration->source()->ignoreSuppressionOfNotices(), + $xmlConfiguration->source()->ignoreSuppressionOfPhpNotices(), + $xmlConfiguration->source()->ignoreSuppressionOfWarnings(), + $xmlConfiguration->source()->ignoreSuppressionOfPhpWarnings(), + $xmlConfiguration->source()->deprecationTriggers(), + $xmlConfiguration->source()->ignoreSelfDeprecations(), + $xmlConfiguration->source()->ignoreDirectDeprecations(), + $xmlConfiguration->source()->ignoreIndirectDeprecations(), + ), + $testResultCacheFile, + $coverageClover, + $coverageCobertura, + $coverageCrap4j, + $coverageCrap4jThreshold, + $coverageHtml, + $coverageHtmlLowUpperBound, + $coverageHtmlHighLowerBound, + $coverageHtmlColorSuccessLow, + $coverageHtmlColorSuccessMedium, + $coverageHtmlColorSuccessHigh, + $coverageHtmlColorWarning, + $coverageHtmlColorDanger, + $coverageHtmlCustomCssFile, + $coverageOpenClover, + $coveragePhp, + $coverageText, + $coverageTextShowUncoveredFiles, + $coverageTextShowOnlySummary, + $coverageXml, + $pathCoverage, + $xmlConfiguration->codeCoverage()->ignoreDeprecatedCodeUnits(), + $disableCodeCoverageIgnore, + $failOnAllIssues, + $failOnDeprecation, + $failOnPhpunitDeprecation, + $failOnPhpunitNotice, + $failOnPhpunitWarning, + $failOnEmptyTestSuite, + $failOnIncomplete, + $failOnNotice, + $failOnRisky, + $failOnSkipped, + $failOnWarning, + $doNotFailOnDeprecation, + $doNotFailOnPhpunitDeprecation, + $doNotFailOnPhpunitNotice, + $doNotFailOnPhpunitWarning, + $doNotFailOnEmptyTestSuite, + $doNotFailOnIncomplete, + $doNotFailOnNotice, + $doNotFailOnRisky, + $doNotFailOnSkipped, + $doNotFailOnWarning, + $stopOnDefect, + $stopOnDeprecation, + $specificDeprecationToStopOn, + $stopOnError, + $stopOnFailure, + $stopOnIncomplete, + $stopOnNotice, + $stopOnRisky, + $stopOnSkipped, + $stopOnWarning, + $outputToStandardErrorStream, + $columns, + $noExtensions, + $pharExtensionDirectory, + $extensionBootstrappers, + $backupGlobals, + $backupStaticProperties, + $beStrictAboutChangesToGlobalState, + $colors, + $processIsolation, + $enforceTimeLimit, + $defaultTimeLimit, + $timeoutForSmallTests, + $timeoutForMediumTests, + $timeoutForLargeTests, + $reportUselessTests, + $strictCoverage, + $disallowTestOutput, + $displayDetailsOnAllIssues, + $displayDetailsOnIncompleteTests, + $displayDetailsOnSkippedTests, + $displayDetailsOnTestsThatTriggerDeprecations, + $displayDetailsOnPhpunitDeprecations, + $displayDetailsOnPhpunitNotices, + $displayDetailsOnTestsThatTriggerErrors, + $displayDetailsOnTestsThatTriggerNotices, + $displayDetailsOnTestsThatTriggerWarnings, + $reverseDefectList, + $requireCoverageMetadata, + $noProgress, + $noResults, + $noOutput, + $executionOrder, + $executionOrderDefects, + $resolveDependencies, + $logfileTeamcity, + $logfileJunit, + $logfileOtr, + $includeGitInformationInOtrLogfile, + $logfileTestdoxHtml, + $logfileTestdoxText, + $logEventsText, + $logEventsVerboseText, + $teamCityOutput, + $testDoxOutput, + $testDoxOutputSummary, + $testsCovering, + $testsUsing, + $testsRequiringPhpExtension, + $filter, + $excludeFilter, + $groups, + $excludeGroups, + $randomOrderSeed, + $includeUncoveredFiles, + $xmlConfiguration->testSuite(), + $includeTestSuite, + $excludeTestSuite, + $xmlConfiguration->phpunit()->hasDefaultTestSuite() ? $xmlConfiguration->phpunit()->defaultTestSuite() : null, + $testSuffixes, + new Php( + DirectoryCollection::fromArray($includePaths), + IniSettingCollection::fromArray($iniSettings), + $xmlConfiguration->php()->constants(), + $xmlConfiguration->php()->globalVariables(), + $xmlConfiguration->php()->envVariables(), + $xmlConfiguration->php()->postVariables(), + $xmlConfiguration->php()->getVariables(), + $xmlConfiguration->php()->cookieVariables(), + $xmlConfiguration->php()->serverVariables(), + $xmlConfiguration->php()->filesVariables(), + $xmlConfiguration->php()->requestVariables(), + ), + $xmlConfiguration->phpunit()->controlGarbageCollector(), + $xmlConfiguration->phpunit()->numberOfTestsBeforeGarbageCollection(), + $generateBaseline, + $cliConfiguration->debug(), + $cliConfiguration->withTelemetry(), + $xmlConfiguration->phpunit()->shortenArraysForExportThreshold(), + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/PhpHandler.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/PhpHandler.php similarity index 95% rename from app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/PhpHandler.php rename to app/vendor/phpunit/phpunit/src/TextUI/Configuration/PhpHandler.php index f5969945b..3aa13160e 100644 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/PhpHandler.php +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/PhpHandler.php @@ -7,7 +7,7 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\TextUI\XmlConfiguration; +namespace PHPUnit\TextUI\Configuration; use const PATH_SEPARATOR; use function constant; @@ -20,9 +20,11 @@ use function putenv; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ -final class PhpHandler +final readonly class PhpHandler { public function handle(Php $configuration): void { diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Registry.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Registry.php new file mode 100644 index 000000000..ad8075235 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Registry.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 PHPUnit\TextUI\Configuration; + +use function assert; +use function file_get_contents; +use function file_put_contents; +use function serialize; +use function unserialize; +use PHPUnit\Event\Facade as EventFacade; +use PHPUnit\TextUI\CliArguments\Configuration as CliConfiguration; +use PHPUnit\TextUI\CliArguments\Exception; +use PHPUnit\TextUI\XmlConfiguration\Configuration as XmlConfiguration; +use PHPUnit\Util\VersionComparisonOperator; + +/** + * CLI options and XML configuration are static within a single PHPUnit process. + * It is therefore okay to use a Singleton registry here. + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Registry +{ + private static ?Configuration $instance = null; + + public static function saveTo(string $path): bool + { + $result = file_put_contents( + $path, + serialize(self::get()), + ); + + if ($result) { + return true; + } + + // @codeCoverageIgnoreStart + return false; + // @codeCoverageIgnoreEnd + } + + /** + * This method is used by the "run test(s) in separate process" templates. + * + * @noinspection PhpUnused + * + * @codeCoverageIgnore + */ + public static function loadFrom(string $path): void + { + $buffer = file_get_contents($path); + + assert($buffer !== false); + + self::$instance = unserialize( + $buffer, + [ + 'allowed_classes' => [ + Configuration::class, + Php::class, + ConstantCollection::class, + Constant::class, + IniSettingCollection::class, + IniSetting::class, + VariableCollection::class, + Variable::class, + DirectoryCollection::class, + Directory::class, + FileCollection::class, + File::class, + FilterDirectoryCollection::class, + FilterDirectory::class, + TestDirectoryCollection::class, + TestDirectory::class, + TestFileCollection::class, + TestFile::class, + TestSuiteCollection::class, + TestSuite::class, + VersionComparisonOperator::class, + Source::class, + ], + ], + ); + } + + public static function get(): Configuration + { + assert(self::$instance instanceof Configuration); + + return self::$instance; + } + + /** + * @throws \PHPUnit\TextUI\XmlConfiguration\Exception + * @throws Exception + * @throws NoCustomCssFileException + */ + public static function init(CliConfiguration $cliConfiguration, XmlConfiguration $xmlConfiguration): Configuration + { + self::$instance = (new Merger)->merge($cliConfiguration, $xmlConfiguration); + + EventFacade::emitter()->testRunnerConfigured(self::$instance); + + return self::$instance; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/SourceFilter.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/SourceFilter.php new file mode 100644 index 000000000..845a9b376 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/SourceFilter.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 PHPUnit\TextUI\Configuration; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class SourceFilter +{ + private static ?self $instance = null; + + /** + * @var array + */ + private readonly array $map; + + public static function instance(): self + { + if (self::$instance === null) { + self::$instance = new self( + (new SourceMapper)->map( + Registry::get()->source(), + ), + ); + } + + return self::$instance; + } + + /** + * @param array $map + */ + public function __construct(array $map) + { + $this->map = $map; + } + + public function includes(string $path): bool + { + return isset($this->map[$path]); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/SourceMapper.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/SourceMapper.php new file mode 100644 index 000000000..c2c548382 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/SourceMapper.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 PHPUnit\TextUI\Configuration; + +use function realpath; +use SebastianBergmann\FileIterator\Facade as FileIteratorFacade; +use SplObjectStorage; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class SourceMapper +{ + /** + * @var ?SplObjectStorage> + */ + private static ?SplObjectStorage $files = null; + + /** + * @return array + */ + public function map(Source $source): array + { + if (self::$files === null) { + self::$files = new SplObjectStorage; + } + + if (isset(self::$files[$source])) { + return self::$files[$source]; + } + + $files = []; + + $directories = $this->aggregateDirectories($source->includeDirectories()); + + foreach ($directories as $path => [$prefixes, $suffixes]) { + foreach ((new FileIteratorFacade)->getFilesAsArray($path, $suffixes, $prefixes) as $file) { + $file = realpath($file); + + if (!$file) { + continue; + } + + $files[$file] = true; + } + } + + foreach ($source->includeFiles() as $file) { + $file = realpath($file->path()); + + if (!$file) { + continue; + } + + $files[$file] = true; + } + + $directories = $this->aggregateDirectories($source->excludeDirectories()); + + foreach ($directories as $path => [$prefixes, $suffixes]) { + foreach ((new FileIteratorFacade)->getFilesAsArray($path, $suffixes, $prefixes) as $file) { + $file = realpath($file); + + if (!$file) { + continue; + } + + if (!isset($files[$file])) { + continue; + } + + unset($files[$file]); + } + } + + foreach ($source->excludeFiles() as $file) { + $file = realpath($file->path()); + + if (!$file) { + continue; + } + + if (!isset($files[$file])) { + continue; + } + + unset($files[$file]); + } + + self::$files[$source] = $files; + + return $files; + } + + /** + * @return array,list}> + */ + private function aggregateDirectories(FilterDirectoryCollection $directories): array + { + $aggregated = []; + + foreach ($directories as $directory) { + if (!isset($aggregated[$directory->path()])) { + $aggregated[$directory->path()] = [ + 0 => [], + 1 => [], + ]; + } + + $prefix = $directory->prefix(); + + if ($prefix !== '') { + $aggregated[$directory->path()][0][] = $prefix; + } + + $suffix = $directory->suffix(); + + if ($suffix !== '') { + $aggregated[$directory->path()][1][] = $suffix; + } + } + + return $aggregated; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/TestSuiteBuilder.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/TestSuiteBuilder.php new file mode 100644 index 000000000..0312aad76 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/TestSuiteBuilder.php @@ -0,0 +1,151 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use const PHP_EOL; +use function assert; +use function count; +use function is_dir; +use function is_file; +use function realpath; +use function str_ends_with; +use PHPUnit\Event\Facade as EventFacade; +use PHPUnit\Exception; +use PHPUnit\Framework\TestSuite; +use PHPUnit\Runner\TestSuiteLoader; +use PHPUnit\TextUI\RuntimeException; +use PHPUnit\TextUI\TestDirectoryNotFoundException; +use PHPUnit\TextUI\TestFileNotFoundException; +use PHPUnit\TextUI\XmlConfiguration\TestSuiteMapper; +use SebastianBergmann\FileIterator\Facade as FileIteratorFacade; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestSuiteBuilder +{ + /** + * @throws \PHPUnit\Framework\Exception + * @throws RuntimeException + * @throws TestDirectoryNotFoundException + * @throws TestFileNotFoundException + */ + public function build(Configuration $configuration): TestSuite + { + if ($configuration->hasCliArguments()) { + $arguments = []; + + foreach ($configuration->cliArguments() as $cliArgument) { + $argument = realpath($cliArgument); + + if (!$argument) { + throw new TestFileNotFoundException($cliArgument); + } + + $arguments[] = $argument; + } + + if (count($arguments) === 1) { + $testSuite = $this->testSuiteFromPath( + $arguments[0], + $configuration->testSuffixes(), + ); + } else { + $testSuite = $this->testSuiteFromPathList( + $arguments, + $configuration->testSuffixes(), + ); + } + } + + if (!isset($testSuite)) { + $xmlConfigurationFile = $configuration->hasConfigurationFile() ? $configuration->configurationFile() : 'Root Test Suite'; + + assert($xmlConfigurationFile !== ''); + + $testSuite = (new TestSuiteMapper)->map( + $xmlConfigurationFile, + $configuration->testSuite(), + $configuration->includeTestSuites(), + $configuration->excludeTestSuites(), + ); + } + + EventFacade::emitter()->testSuiteLoaded(\PHPUnit\Event\TestSuite\TestSuiteBuilder::from($testSuite)); + + return $testSuite; + } + + /** + * @param non-empty-string $path + * @param list $suffixes + * + * @throws \PHPUnit\Framework\Exception + */ + private function testSuiteFromPath(string $path, array $suffixes, ?TestSuite $suite = null): TestSuite + { + if (str_ends_with($path, '.phpt') && is_file($path)) { + if ($suite === null) { + $suite = TestSuite::empty($path); + } + + $suite->addTestFile($path); + + return $suite; + } + + if (is_dir($path)) { + $files = (new FileIteratorFacade)->getFilesAsArray($path, $suffixes); + + if ($suite === null) { + $suite = TestSuite::empty('CLI Arguments'); + } + + $suite->addTestFiles($files); + + return $suite; + } + + try { + $testClass = (new TestSuiteLoader)->load($path); + } catch (Exception $e) { + print $e->getMessage() . PHP_EOL; + + exit(1); + } + + if ($suite === null) { + return TestSuite::fromClassReflector($testClass); + } + + $suite->addTestSuite($testClass); + + return $suite; + } + + /** + * @param list $paths + * @param list $suffixes + * + * @throws \PHPUnit\Framework\Exception + */ + private function testSuiteFromPathList(array $paths, array $suffixes): TestSuite + { + $suite = TestSuite::empty('CLI Arguments'); + + foreach ($paths as $path) { + $this->testSuiteFromPath($path, $suffixes, $suite); + } + + return $suite; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/Constant.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/Constant.php new file mode 100644 index 000000000..0ff240dd0 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/Constant.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 PHPUnit\TextUI\Configuration; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class Constant +{ + private string $name; + private bool|string $value; + + public function __construct(string $name, bool|string $value) + { + $this->name = $name; + $this->value = $value; + } + + public function name(): string + { + return $this->name; + } + + public function value(): bool|string + { + return $this->value; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/ConstantCollection.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/ConstantCollection.php new file mode 100644 index 000000000..3e34d7434 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/ConstantCollection.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 PHPUnit\TextUI\Configuration; + +use function count; +use Countable; +use IteratorAggregate; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @immutable + * + * @template-implements IteratorAggregate + */ +final readonly class ConstantCollection implements Countable, IteratorAggregate +{ + /** + * @var list + */ + private array $constants; + + /** + * @param list $constants + */ + public static function fromArray(array $constants): self + { + return new self(...$constants); + } + + private function __construct(Constant ...$constants) + { + $this->constants = $constants; + } + + /** + * @return list + */ + public function asArray(): array + { + return $this->constants; + } + + public function count(): int + { + return count($this->constants); + } + + public function getIterator(): ConstantCollectionIterator + { + return new ConstantCollectionIterator($this); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/ConstantCollectionIterator.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/ConstantCollectionIterator.php new file mode 100644 index 000000000..f385b7faf --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/ConstantCollectionIterator.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 PHPUnit\TextUI\Configuration; + +use function count; +use Iterator; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @template-implements Iterator + */ +final class ConstantCollectionIterator implements Iterator +{ + /** + * @var list + */ + private readonly array $constants; + private int $position = 0; + + public function __construct(ConstantCollection $constants) + { + $this->constants = $constants->asArray(); + } + + public function rewind(): void + { + $this->position = 0; + } + + public function valid(): bool + { + return $this->position < count($this->constants); + } + + public function key(): int + { + return $this->position; + } + + public function current(): Constant + { + return $this->constants[$this->position]; + } + + public function next(): void + { + $this->position++; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/Directory.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/Directory.php new file mode 100644 index 000000000..f44e28b15 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/Directory.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class Directory +{ + private string $path; + + public function __construct(string $path) + { + $this->path = $path; + } + + public function path(): string + { + return $this->path; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/DirectoryCollection.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/DirectoryCollection.php new file mode 100644 index 000000000..dc1e840c9 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/DirectoryCollection.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 PHPUnit\TextUI\Configuration; + +use function count; +use Countable; +use IteratorAggregate; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @immutable + * + * @template-implements IteratorAggregate + */ +final readonly class DirectoryCollection implements Countable, IteratorAggregate +{ + /** + * @var list + */ + private array $directories; + + /** + * @param list $directories + */ + public static function fromArray(array $directories): self + { + return new self(...$directories); + } + + private function __construct(Directory ...$directories) + { + $this->directories = $directories; + } + + /** + * @return list + */ + public function asArray(): array + { + return $this->directories; + } + + public function count(): int + { + return count($this->directories); + } + + public function getIterator(): DirectoryCollectionIterator + { + return new DirectoryCollectionIterator($this); + } + + public function isEmpty(): bool + { + return $this->count() === 0; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/DirectoryCollectionIterator.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/DirectoryCollectionIterator.php new file mode 100644 index 000000000..73d2cff63 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/DirectoryCollectionIterator.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 PHPUnit\TextUI\Configuration; + +use function count; +use Iterator; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @template-implements Iterator + */ +final class DirectoryCollectionIterator implements Iterator +{ + /** + * @var list + */ + private readonly array $directories; + private int $position = 0; + + public function __construct(DirectoryCollection $directories) + { + $this->directories = $directories->asArray(); + } + + public function rewind(): void + { + $this->position = 0; + } + + public function valid(): bool + { + return $this->position < count($this->directories); + } + + public function key(): int + { + return $this->position; + } + + public function current(): Directory + { + return $this->directories[$this->position]; + } + + public function next(): void + { + $this->position++; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/ExtensionBootstrap.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/ExtensionBootstrap.php new file mode 100644 index 000000000..09430c7f3 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/ExtensionBootstrap.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class ExtensionBootstrap +{ + /** + * @var non-empty-string + */ + private string $className; + + /** + * @var array + */ + private array $parameters; + + /** + * @param non-empty-string $className + * @param array $parameters + */ + public function __construct(string $className, array $parameters) + { + $this->className = $className; + $this->parameters = $parameters; + } + + /** + * @return non-empty-string + */ + public function className(): string + { + return $this->className; + } + + /** + * @return array + */ + public function parameters(): array + { + return $this->parameters; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/ExtensionBootstrapCollection.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/ExtensionBootstrapCollection.php new file mode 100644 index 000000000..16ca1e070 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/ExtensionBootstrapCollection.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 PHPUnit\TextUI\Configuration; + +use IteratorAggregate; + +/** + * @template-implements IteratorAggregate + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class ExtensionBootstrapCollection implements IteratorAggregate +{ + /** + * @var list + */ + private array $extensionBootstraps; + + /** + * @param list $extensionBootstraps + */ + public static function fromArray(array $extensionBootstraps): self + { + return new self(...$extensionBootstraps); + } + + private function __construct(ExtensionBootstrap ...$extensionBootstraps) + { + $this->extensionBootstraps = $extensionBootstraps; + } + + /** + * @return list + */ + public function asArray(): array + { + return $this->extensionBootstraps; + } + + public function getIterator(): ExtensionBootstrapCollectionIterator + { + return new ExtensionBootstrapCollectionIterator($this); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/ExtensionBootstrapCollectionIterator.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/ExtensionBootstrapCollectionIterator.php new file mode 100644 index 000000000..0b5c20ba1 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/ExtensionBootstrapCollectionIterator.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 PHPUnit\TextUI\Configuration; + +use function count; +use Iterator; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @template-implements Iterator + */ +final class ExtensionBootstrapCollectionIterator implements Iterator +{ + /** + * @var list + */ + private readonly array $extensionBootstraps; + private int $position = 0; + + public function __construct(ExtensionBootstrapCollection $extensionBootstraps) + { + $this->extensionBootstraps = $extensionBootstraps->asArray(); + } + + public function rewind(): void + { + $this->position = 0; + } + + public function valid(): bool + { + return $this->position < count($this->extensionBootstraps); + } + + public function key(): int + { + return $this->position; + } + + public function current(): ExtensionBootstrap + { + return $this->extensionBootstraps[$this->position]; + } + + public function next(): void + { + $this->position++; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/File.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/File.php new file mode 100644 index 000000000..85900f47f --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/File.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 PHPUnit\TextUI\Configuration; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class File +{ + /** + * @var non-empty-string + */ + private string $path; + + /** + * @param non-empty-string $path + */ + public function __construct(string $path) + { + $this->path = $path; + } + + /** + * @return non-empty-string + */ + public function path(): string + { + return $this->path; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/FileCollection.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/FileCollection.php new file mode 100644 index 000000000..61522a5eb --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/FileCollection.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 PHPUnit\TextUI\Configuration; + +use function count; +use Countable; +use IteratorAggregate; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @immutable + * + * @template-implements IteratorAggregate + */ +final readonly class FileCollection implements Countable, IteratorAggregate +{ + /** + * @var list + */ + private array $files; + + /** + * @param list $files + */ + public static function fromArray(array $files): self + { + return new self(...$files); + } + + private function __construct(File ...$files) + { + $this->files = $files; + } + + /** + * @return list + */ + public function asArray(): array + { + return $this->files; + } + + public function count(): int + { + return count($this->files); + } + + public function notEmpty(): bool + { + return $this->files !== []; + } + + public function getIterator(): FileCollectionIterator + { + return new FileCollectionIterator($this); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/FileCollectionIterator.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/FileCollectionIterator.php new file mode 100644 index 000000000..91ec8e276 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/FileCollectionIterator.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 PHPUnit\TextUI\Configuration; + +use function count; +use Iterator; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @template-implements Iterator + */ +final class FileCollectionIterator implements Iterator +{ + /** + * @var list + */ + private readonly array $files; + private int $position = 0; + + public function __construct(FileCollection $files) + { + $this->files = $files->asArray(); + } + + public function rewind(): void + { + $this->position = 0; + } + + public function valid(): bool + { + return $this->position < count($this->files); + } + + public function key(): int + { + return $this->position; + } + + public function current(): File + { + return $this->files[$this->position]; + } + + public function next(): void + { + $this->position++; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/FilterDirectory.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/FilterDirectory.php new file mode 100644 index 000000000..52dcd1b13 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/FilterDirectory.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 PHPUnit\TextUI\Configuration; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class FilterDirectory +{ + /** + * @var non-empty-string + */ + private string $path; + private string $prefix; + private string $suffix; + + /** + * @param non-empty-string $path + */ + public function __construct(string $path, string $prefix, string $suffix) + { + $this->path = $path; + $this->prefix = $prefix; + $this->suffix = $suffix; + } + + /** + * @return non-empty-string + */ + public function path(): string + { + return $this->path; + } + + public function prefix(): string + { + return $this->prefix; + } + + public function suffix(): string + { + return $this->suffix; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/FilterDirectoryCollection.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/FilterDirectoryCollection.php new file mode 100644 index 000000000..147f0618f --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/FilterDirectoryCollection.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 PHPUnit\TextUI\Configuration; + +use function count; +use Countable; +use IteratorAggregate; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @immutable + * + * @template-implements IteratorAggregate + */ +final readonly class FilterDirectoryCollection implements Countable, IteratorAggregate +{ + /** + * @var list + */ + private array $directories; + + /** + * @param list $directories + */ + public static function fromArray(array $directories): self + { + return new self(...$directories); + } + + private function __construct(FilterDirectory ...$directories) + { + $this->directories = $directories; + } + + /** + * @return list + */ + public function asArray(): array + { + return $this->directories; + } + + public function count(): int + { + return count($this->directories); + } + + public function notEmpty(): bool + { + return $this->directories !== []; + } + + public function getIterator(): FilterDirectoryCollectionIterator + { + return new FilterDirectoryCollectionIterator($this); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/FilterDirectoryCollectionIterator.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/FilterDirectoryCollectionIterator.php new file mode 100644 index 000000000..737c752f4 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/FilterDirectoryCollectionIterator.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 PHPUnit\TextUI\Configuration; + +use function count; +use Iterator; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @template-implements Iterator + */ +final class FilterDirectoryCollectionIterator implements Iterator +{ + /** + * @var list + */ + private readonly array $directories; + private int $position = 0; + + public function __construct(FilterDirectoryCollection $directories) + { + $this->directories = $directories->asArray(); + } + + public function rewind(): void + { + $this->position = 0; + } + + public function valid(): bool + { + return $this->position < count($this->directories); + } + + public function key(): int + { + return $this->position; + } + + public function current(): FilterDirectory + { + return $this->directories[$this->position]; + } + + public function next(): void + { + $this->position++; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/Group.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/Group.php new file mode 100644 index 000000000..cb0bdc8aa --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/Group.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class Group +{ + private string $name; + + public function __construct(string $name) + { + $this->name = $name; + } + + public function name(): string + { + return $this->name; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/GroupCollection.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/GroupCollection.php new file mode 100644 index 000000000..8232e1f34 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/GroupCollection.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +use IteratorAggregate; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @immutable + * + * @template-implements IteratorAggregate + */ +final readonly class GroupCollection implements IteratorAggregate +{ + /** + * @var list + */ + private array $groups; + + /** + * @param list $groups + */ + public static function fromArray(array $groups): self + { + return new self(...$groups); + } + + private function __construct(Group ...$groups) + { + $this->groups = $groups; + } + + /** + * @return list + */ + public function asArray(): array + { + return $this->groups; + } + + /** + * @return list + */ + public function asArrayOfStrings(): array + { + $result = []; + + foreach ($this->groups as $group) { + $result[] = $group->name(); + } + + return $result; + } + + public function isEmpty(): bool + { + return $this->groups === []; + } + + public function getIterator(): GroupCollectionIterator + { + return new GroupCollectionIterator($this); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/GroupCollectionIterator.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/GroupCollectionIterator.php new file mode 100644 index 000000000..774808757 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/GroupCollectionIterator.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 PHPUnit\TextUI\Configuration; + +use function count; +use Iterator; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @template-implements Iterator + */ +final class GroupCollectionIterator implements Iterator +{ + /** + * @var list + */ + private readonly array $groups; + private int $position = 0; + + public function __construct(GroupCollection $groups) + { + $this->groups = $groups->asArray(); + } + + public function rewind(): void + { + $this->position = 0; + } + + public function valid(): bool + { + return $this->position < count($this->groups); + } + + public function key(): int + { + return $this->position; + } + + public function current(): Group + { + return $this->groups[$this->position]; + } + + public function next(): void + { + $this->position++; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/IniSetting.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/IniSetting.php new file mode 100644 index 000000000..b4d116655 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/IniSetting.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 PHPUnit\TextUI\Configuration; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class IniSetting +{ + private string $name; + private string $value; + + public function __construct(string $name, string $value) + { + $this->name = $name; + $this->value = $value; + } + + public function name(): string + { + return $this->name; + } + + public function value(): string + { + return $this->value; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/IniSettingCollection.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/IniSettingCollection.php new file mode 100644 index 000000000..abfd8fd24 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/IniSettingCollection.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 PHPUnit\TextUI\Configuration; + +use function count; +use Countable; +use IteratorAggregate; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @immutable + * + * @template-implements IteratorAggregate + */ +final readonly class IniSettingCollection implements Countable, IteratorAggregate +{ + /** + * @var list + */ + private array $iniSettings; + + /** + * @param list $iniSettings + */ + public static function fromArray(array $iniSettings): self + { + return new self(...$iniSettings); + } + + private function __construct(IniSetting ...$iniSettings) + { + $this->iniSettings = $iniSettings; + } + + /** + * @return list + */ + public function asArray(): array + { + return $this->iniSettings; + } + + public function count(): int + { + return count($this->iniSettings); + } + + public function getIterator(): IniSettingCollectionIterator + { + return new IniSettingCollectionIterator($this); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/IniSettingCollectionIterator.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/IniSettingCollectionIterator.php new file mode 100644 index 000000000..cb68c3dd1 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/IniSettingCollectionIterator.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 PHPUnit\TextUI\Configuration; + +use function count; +use Iterator; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @template-implements Iterator + */ +final class IniSettingCollectionIterator implements Iterator +{ + /** + * @var list + */ + private readonly array $iniSettings; + private int $position = 0; + + public function __construct(IniSettingCollection $iniSettings) + { + $this->iniSettings = $iniSettings->asArray(); + } + + public function rewind(): void + { + $this->position = 0; + } + + public function valid(): bool + { + return $this->position < count($this->iniSettings); + } + + public function key(): int + { + return $this->position; + } + + public function current(): IniSetting + { + return $this->iniSettings[$this->position]; + } + + public function next(): void + { + $this->position++; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/Php.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/Php.php new file mode 100644 index 000000000..0dc4735dc --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/Php.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 PHPUnit\TextUI\Configuration; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class Php +{ + private DirectoryCollection $includePaths; + private IniSettingCollection $iniSettings; + private ConstantCollection $constants; + private VariableCollection $globalVariables; + private VariableCollection $envVariables; + private VariableCollection $postVariables; + private VariableCollection $getVariables; + private VariableCollection $cookieVariables; + private VariableCollection $serverVariables; + private VariableCollection $filesVariables; + private VariableCollection $requestVariables; + + public function __construct(DirectoryCollection $includePaths, IniSettingCollection $iniSettings, ConstantCollection $constants, VariableCollection $globalVariables, VariableCollection $envVariables, VariableCollection $postVariables, VariableCollection $getVariables, VariableCollection $cookieVariables, VariableCollection $serverVariables, VariableCollection $filesVariables, VariableCollection $requestVariables) + { + $this->includePaths = $includePaths; + $this->iniSettings = $iniSettings; + $this->constants = $constants; + $this->globalVariables = $globalVariables; + $this->envVariables = $envVariables; + $this->postVariables = $postVariables; + $this->getVariables = $getVariables; + $this->cookieVariables = $cookieVariables; + $this->serverVariables = $serverVariables; + $this->filesVariables = $filesVariables; + $this->requestVariables = $requestVariables; + } + + public function includePaths(): DirectoryCollection + { + return $this->includePaths; + } + + public function iniSettings(): IniSettingCollection + { + return $this->iniSettings; + } + + public function constants(): ConstantCollection + { + return $this->constants; + } + + public function globalVariables(): VariableCollection + { + return $this->globalVariables; + } + + public function envVariables(): VariableCollection + { + return $this->envVariables; + } + + public function postVariables(): VariableCollection + { + return $this->postVariables; + } + + public function getVariables(): VariableCollection + { + return $this->getVariables; + } + + public function cookieVariables(): VariableCollection + { + return $this->cookieVariables; + } + + public function serverVariables(): VariableCollection + { + return $this->serverVariables; + } + + public function filesVariables(): VariableCollection + { + return $this->filesVariables; + } + + public function requestVariables(): VariableCollection + { + return $this->requestVariables; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/Source.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/Source.php new file mode 100644 index 000000000..b6f61eb07 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/Source.php @@ -0,0 +1,195 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class Source +{ + /** + * @var non-empty-string + */ + private ?string $baseline; + private bool $ignoreBaseline; + private FilterDirectoryCollection $includeDirectories; + private FileCollection $includeFiles; + private FilterDirectoryCollection $excludeDirectories; + private FileCollection $excludeFiles; + private bool $restrictNotices; + private bool $restrictWarnings; + private bool $ignoreSuppressionOfDeprecations; + private bool $ignoreSuppressionOfPhpDeprecations; + private bool $ignoreSuppressionOfErrors; + private bool $ignoreSuppressionOfNotices; + private bool $ignoreSuppressionOfPhpNotices; + private bool $ignoreSuppressionOfWarnings; + private bool $ignoreSuppressionOfPhpWarnings; + private bool $ignoreSelfDeprecations; + private bool $ignoreDirectDeprecations; + private bool $ignoreIndirectDeprecations; + + /** + * @var array{functions: list, methods: list} + */ + private array $deprecationTriggers; + + /** + * @param non-empty-string $baseline + * @param array{functions: list, methods: list} $deprecationTriggers + */ + public function __construct(?string $baseline, bool $ignoreBaseline, FilterDirectoryCollection $includeDirectories, FileCollection $includeFiles, FilterDirectoryCollection $excludeDirectories, FileCollection $excludeFiles, bool $restrictNotices, bool $restrictWarnings, bool $ignoreSuppressionOfDeprecations, bool $ignoreSuppressionOfPhpDeprecations, bool $ignoreSuppressionOfErrors, bool $ignoreSuppressionOfNotices, bool $ignoreSuppressionOfPhpNotices, bool $ignoreSuppressionOfWarnings, bool $ignoreSuppressionOfPhpWarnings, array $deprecationTriggers, bool $ignoreSelfDeprecations, bool $ignoreDirectDeprecations, bool $ignoreIndirectDeprecations) + { + $this->baseline = $baseline; + $this->ignoreBaseline = $ignoreBaseline; + $this->includeDirectories = $includeDirectories; + $this->includeFiles = $includeFiles; + $this->excludeDirectories = $excludeDirectories; + $this->excludeFiles = $excludeFiles; + $this->restrictNotices = $restrictNotices; + $this->restrictWarnings = $restrictWarnings; + $this->ignoreSuppressionOfDeprecations = $ignoreSuppressionOfDeprecations; + $this->ignoreSuppressionOfPhpDeprecations = $ignoreSuppressionOfPhpDeprecations; + $this->ignoreSuppressionOfErrors = $ignoreSuppressionOfErrors; + $this->ignoreSuppressionOfNotices = $ignoreSuppressionOfNotices; + $this->ignoreSuppressionOfPhpNotices = $ignoreSuppressionOfPhpNotices; + $this->ignoreSuppressionOfWarnings = $ignoreSuppressionOfWarnings; + $this->ignoreSuppressionOfPhpWarnings = $ignoreSuppressionOfPhpWarnings; + $this->deprecationTriggers = $deprecationTriggers; + $this->ignoreSelfDeprecations = $ignoreSelfDeprecations; + $this->ignoreDirectDeprecations = $ignoreDirectDeprecations; + $this->ignoreIndirectDeprecations = $ignoreIndirectDeprecations; + } + + /** + * @phpstan-assert-if-true !null $this->baseline + */ + public function useBaseline(): bool + { + return $this->hasBaseline() && !$this->ignoreBaseline; + } + + /** + * @phpstan-assert-if-true !null $this->baseline + */ + public function hasBaseline(): bool + { + return $this->baseline !== null; + } + + /** + * @throws NoBaselineException + * + * @return non-empty-string + */ + public function baseline(): string + { + if (!$this->hasBaseline()) { + throw new NoBaselineException; + } + + return $this->baseline; + } + + public function includeDirectories(): FilterDirectoryCollection + { + return $this->includeDirectories; + } + + public function includeFiles(): FileCollection + { + return $this->includeFiles; + } + + public function excludeDirectories(): FilterDirectoryCollection + { + return $this->excludeDirectories; + } + + public function excludeFiles(): FileCollection + { + return $this->excludeFiles; + } + + public function notEmpty(): bool + { + return $this->includeDirectories->notEmpty() || $this->includeFiles->notEmpty(); + } + + public function restrictNotices(): bool + { + return $this->restrictNotices; + } + + public function restrictWarnings(): bool + { + return $this->restrictWarnings; + } + + public function ignoreSuppressionOfDeprecations(): bool + { + return $this->ignoreSuppressionOfDeprecations; + } + + public function ignoreSuppressionOfPhpDeprecations(): bool + { + return $this->ignoreSuppressionOfPhpDeprecations; + } + + public function ignoreSuppressionOfErrors(): bool + { + return $this->ignoreSuppressionOfErrors; + } + + public function ignoreSuppressionOfNotices(): bool + { + return $this->ignoreSuppressionOfNotices; + } + + public function ignoreSuppressionOfPhpNotices(): bool + { + return $this->ignoreSuppressionOfPhpNotices; + } + + public function ignoreSuppressionOfWarnings(): bool + { + return $this->ignoreSuppressionOfWarnings; + } + + public function ignoreSuppressionOfPhpWarnings(): bool + { + return $this->ignoreSuppressionOfPhpWarnings; + } + + /** + * @return array{functions: list, methods: list} + */ + public function deprecationTriggers(): array + { + return $this->deprecationTriggers; + } + + public function ignoreSelfDeprecations(): bool + { + return $this->ignoreSelfDeprecations; + } + + public function ignoreDirectDeprecations(): bool + { + return $this->ignoreDirectDeprecations; + } + + public function ignoreIndirectDeprecations(): bool + { + return $this->ignoreIndirectDeprecations; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/TestDirectory.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/TestDirectory.php new file mode 100644 index 000000000..dfe301a94 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/TestDirectory.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 PHPUnit\TextUI\Configuration; + +use PHPUnit\Util\VersionComparisonOperator; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class TestDirectory +{ + /** + * @var non-empty-string + */ + private string $path; + private string $prefix; + private string $suffix; + private string $phpVersion; + private VersionComparisonOperator $phpVersionOperator; + + /** + * @var list + */ + private array $groups; + + /** + * @param non-empty-string $path + * @param list $groups + */ + public function __construct(string $path, string $prefix, string $suffix, string $phpVersion, VersionComparisonOperator $phpVersionOperator, array $groups) + { + $this->path = $path; + $this->prefix = $prefix; + $this->suffix = $suffix; + $this->phpVersion = $phpVersion; + $this->phpVersionOperator = $phpVersionOperator; + $this->groups = $groups; + } + + /** + * @return non-empty-string + */ + public function path(): string + { + return $this->path; + } + + public function prefix(): string + { + return $this->prefix; + } + + public function suffix(): string + { + return $this->suffix; + } + + public function phpVersion(): string + { + return $this->phpVersion; + } + + public function phpVersionOperator(): VersionComparisonOperator + { + return $this->phpVersionOperator; + } + + /** + * @return list + */ + public function groups(): array + { + return $this->groups; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/TestDirectoryCollection.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/TestDirectoryCollection.php new file mode 100644 index 000000000..ba867273c --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/TestDirectoryCollection.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 PHPUnit\TextUI\Configuration; + +use function count; +use Countable; +use IteratorAggregate; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @immutable + * + * @template-implements IteratorAggregate + */ +final readonly class TestDirectoryCollection implements Countable, IteratorAggregate +{ + /** + * @var list + */ + private array $directories; + + /** + * @param list $directories + */ + public static function fromArray(array $directories): self + { + return new self(...$directories); + } + + private function __construct(TestDirectory ...$directories) + { + $this->directories = $directories; + } + + /** + * @return list + */ + public function asArray(): array + { + return $this->directories; + } + + public function count(): int + { + return count($this->directories); + } + + public function getIterator(): TestDirectoryCollectionIterator + { + return new TestDirectoryCollectionIterator($this); + } + + public function isEmpty(): bool + { + return $this->count() === 0; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/TestDirectoryCollectionIterator.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/TestDirectoryCollectionIterator.php new file mode 100644 index 000000000..fa57410a6 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/TestDirectoryCollectionIterator.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 PHPUnit\TextUI\Configuration; + +use function count; +use Iterator; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @template-implements Iterator + */ +final class TestDirectoryCollectionIterator implements Iterator +{ + /** + * @var list + */ + private readonly array $directories; + private int $position = 0; + + public function __construct(TestDirectoryCollection $directories) + { + $this->directories = $directories->asArray(); + } + + public function rewind(): void + { + $this->position = 0; + } + + public function valid(): bool + { + return $this->position < count($this->directories); + } + + public function key(): int + { + return $this->position; + } + + public function current(): TestDirectory + { + return $this->directories[$this->position]; + } + + public function next(): void + { + $this->position++; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/TestFile.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/TestFile.php new file mode 100644 index 000000000..e658ff884 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/TestFile.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 PHPUnit\TextUI\Configuration; + +use PHPUnit\Util\VersionComparisonOperator; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class TestFile +{ + /** + * @var non-empty-string + */ + private string $path; + private string $phpVersion; + private VersionComparisonOperator $phpVersionOperator; + + /** + * @var list + */ + private array $groups; + + /** + * @param non-empty-string $path + * @param list $groups + */ + public function __construct(string $path, string $phpVersion, VersionComparisonOperator $phpVersionOperator, array $groups) + { + $this->path = $path; + $this->phpVersion = $phpVersion; + $this->phpVersionOperator = $phpVersionOperator; + $this->groups = $groups; + } + + /** + * @return non-empty-string + */ + public function path(): string + { + return $this->path; + } + + public function phpVersion(): string + { + return $this->phpVersion; + } + + public function phpVersionOperator(): VersionComparisonOperator + { + return $this->phpVersionOperator; + } + + /** + * @return list + */ + public function groups(): array + { + return $this->groups; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/TestFileCollection.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/TestFileCollection.php new file mode 100644 index 000000000..6d1ae2799 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/TestFileCollection.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 PHPUnit\TextUI\Configuration; + +use function count; +use Countable; +use IteratorAggregate; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @immutable + * + * @template-implements IteratorAggregate + */ +final readonly class TestFileCollection implements Countable, IteratorAggregate +{ + /** + * @var list + */ + private array $files; + + /** + * @param list $files + */ + public static function fromArray(array $files): self + { + return new self(...$files); + } + + private function __construct(TestFile ...$files) + { + $this->files = $files; + } + + /** + * @return list + */ + public function asArray(): array + { + return $this->files; + } + + public function count(): int + { + return count($this->files); + } + + public function getIterator(): TestFileCollectionIterator + { + return new TestFileCollectionIterator($this); + } + + public function isEmpty(): bool + { + return $this->count() === 0; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/TestFileCollectionIterator.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/TestFileCollectionIterator.php new file mode 100644 index 000000000..ed328e9ec --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/TestFileCollectionIterator.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 PHPUnit\TextUI\Configuration; + +use function count; +use Iterator; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @template-implements Iterator + */ +final class TestFileCollectionIterator implements Iterator +{ + /** + * @var list + */ + private readonly array $files; + private int $position = 0; + + public function __construct(TestFileCollection $files) + { + $this->files = $files->asArray(); + } + + public function rewind(): void + { + $this->position = 0; + } + + public function valid(): bool + { + return $this->position < count($this->files); + } + + public function key(): int + { + return $this->position; + } + + public function current(): TestFile + { + return $this->files[$this->position]; + } + + public function next(): void + { + $this->position++; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/TestSuite.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/TestSuite.php new file mode 100644 index 000000000..fdba72e0b --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/TestSuite.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 PHPUnit\TextUI\Configuration; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class TestSuite +{ + /** + * @var non-empty-string + */ + private string $name; + private TestDirectoryCollection $directories; + private TestFileCollection $files; + private FileCollection $exclude; + + /** + * @param non-empty-string $name + */ + public function __construct(string $name, TestDirectoryCollection $directories, TestFileCollection $files, FileCollection $exclude) + { + $this->name = $name; + $this->directories = $directories; + $this->files = $files; + $this->exclude = $exclude; + } + + /** + * @return non-empty-string + */ + public function name(): string + { + return $this->name; + } + + public function directories(): TestDirectoryCollection + { + return $this->directories; + } + + public function files(): TestFileCollection + { + return $this->files; + } + + public function exclude(): FileCollection + { + return $this->exclude; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/TestSuiteCollection.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/TestSuiteCollection.php new file mode 100644 index 000000000..26c9a6457 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/TestSuiteCollection.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 PHPUnit\TextUI\Configuration; + +use function count; +use Countable; +use IteratorAggregate; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @immutable + * + * @template-implements IteratorAggregate + */ +final readonly class TestSuiteCollection implements Countable, IteratorAggregate +{ + /** + * @var list + */ + private array $testSuites; + + /** + * @param list $testSuites + */ + public static function fromArray(array $testSuites): self + { + return new self(...$testSuites); + } + + private function __construct(TestSuite ...$testSuites) + { + $this->testSuites = $testSuites; + } + + /** + * @return list + */ + public function asArray(): array + { + return $this->testSuites; + } + + public function count(): int + { + return count($this->testSuites); + } + + public function getIterator(): TestSuiteCollectionIterator + { + return new TestSuiteCollectionIterator($this); + } + + public function isEmpty(): bool + { + return $this->count() === 0; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/TestSuiteCollectionIterator.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/TestSuiteCollectionIterator.php new file mode 100644 index 000000000..d0b0768a4 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/TestSuiteCollectionIterator.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 PHPUnit\TextUI\Configuration; + +use function count; +use Iterator; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @template-implements Iterator + */ +final class TestSuiteCollectionIterator implements Iterator +{ + /** + * @var list + */ + private readonly array $testSuites; + private int $position = 0; + + public function __construct(TestSuiteCollection $testSuites) + { + $this->testSuites = $testSuites->asArray(); + } + + public function rewind(): void + { + $this->position = 0; + } + + public function valid(): bool + { + return $this->position < count($this->testSuites); + } + + public function key(): int + { + return $this->position; + } + + public function current(): TestSuite + { + return $this->testSuites[$this->position]; + } + + public function next(): void + { + $this->position++; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/Variable.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/Variable.php new file mode 100644 index 000000000..cc0425c8c --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/Variable.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Configuration; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class Variable +{ + private string $name; + private mixed $value; + private bool $force; + + public function __construct(string $name, mixed $value, bool $force) + { + $this->name = $name; + $this->value = $value; + $this->force = $force; + } + + public function name(): string + { + return $this->name; + } + + public function value(): mixed + { + return $this->value; + } + + public function force(): bool + { + return $this->force; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/VariableCollection.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/VariableCollection.php new file mode 100644 index 000000000..77fb4cff9 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/VariableCollection.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 PHPUnit\TextUI\Configuration; + +use function count; +use Countable; +use IteratorAggregate; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @immutable + * + * @template-implements IteratorAggregate + */ +final readonly class VariableCollection implements Countable, IteratorAggregate +{ + /** + * @var list + */ + private array $variables; + + /** + * @param list $variables + */ + public static function fromArray(array $variables): self + { + return new self(...$variables); + } + + private function __construct(Variable ...$variables) + { + $this->variables = $variables; + } + + /** + * @return list + */ + public function asArray(): array + { + return $this->variables; + } + + public function count(): int + { + return count($this->variables); + } + + public function getIterator(): VariableCollectionIterator + { + return new VariableCollectionIterator($this); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/VariableCollectionIterator.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/VariableCollectionIterator.php new file mode 100644 index 000000000..2e32194c1 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Value/VariableCollectionIterator.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 PHPUnit\TextUI\Configuration; + +use function count; +use Iterator; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @template-implements Iterator + */ +final class VariableCollectionIterator implements Iterator +{ + /** + * @var list + */ + private readonly array $variables; + private int $position = 0; + + public function __construct(VariableCollection $variables) + { + $this->variables = $variables->asArray(); + } + + public function rewind(): void + { + $this->position = 0; + } + + public function valid(): bool + { + return $this->position < count($this->variables); + } + + public function key(): int + { + return $this->position; + } + + public function current(): Variable + { + return $this->variables[$this->position]; + } + + public function next(): void + { + $this->position++; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/CodeCoverage/CodeCoverage.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/CodeCoverage/CodeCoverage.php new file mode 100644 index 000000000..d66f58f1c --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/CodeCoverage/CodeCoverage.php @@ -0,0 +1,255 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage; + +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Clover; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Cobertura; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Crap4j; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Html; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\OpenClover; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Php; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Text; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Xml; +use PHPUnit\TextUI\XmlConfiguration\Exception; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class CodeCoverage +{ + private bool $pathCoverage; + private bool $includeUncoveredFiles; + private bool $ignoreDeprecatedCodeUnits; + private bool $disableCodeCoverageIgnore; + private ?Clover $clover; + private ?Cobertura $cobertura; + private ?Crap4j $crap4j; + private ?Html $html; + private ?OpenClover $openClover; + private ?Php $php; + private ?Text $text; + private ?Xml $xml; + + public function __construct(bool $pathCoverage, bool $includeUncoveredFiles, bool $ignoreDeprecatedCodeUnits, bool $disableCodeCoverageIgnore, ?Clover $clover, ?Cobertura $cobertura, ?Crap4j $crap4j, ?Html $html, ?OpenClover $openClover, ?Php $php, ?Text $text, ?Xml $xml) + { + $this->pathCoverage = $pathCoverage; + $this->includeUncoveredFiles = $includeUncoveredFiles; + $this->ignoreDeprecatedCodeUnits = $ignoreDeprecatedCodeUnits; + $this->disableCodeCoverageIgnore = $disableCodeCoverageIgnore; + $this->clover = $clover; + $this->cobertura = $cobertura; + $this->crap4j = $crap4j; + $this->html = $html; + $this->openClover = $openClover; + $this->php = $php; + $this->text = $text; + $this->xml = $xml; + } + + public function pathCoverage(): bool + { + return $this->pathCoverage; + } + + public function includeUncoveredFiles(): bool + { + return $this->includeUncoveredFiles; + } + + public function ignoreDeprecatedCodeUnits(): bool + { + return $this->ignoreDeprecatedCodeUnits; + } + + public function disableCodeCoverageIgnore(): bool + { + return $this->disableCodeCoverageIgnore; + } + + /** + * @phpstan-assert-if-true !null $this->clover + */ + public function hasClover(): bool + { + return $this->clover !== null; + } + + /** + * @throws Exception + */ + public function clover(): Clover + { + if (!$this->hasClover()) { + throw new Exception( + 'Code Coverage report "Clover XML" has not been configured', + ); + } + + return $this->clover; + } + + /** + * @phpstan-assert-if-true !null $this->cobertura + */ + public function hasCobertura(): bool + { + return $this->cobertura !== null; + } + + /** + * @throws Exception + */ + public function cobertura(): Cobertura + { + if (!$this->hasCobertura()) { + throw new Exception( + 'Code Coverage report "Cobertura XML" has not been configured', + ); + } + + return $this->cobertura; + } + + /** + * @phpstan-assert-if-true !null $this->crap4j + */ + public function hasCrap4j(): bool + { + return $this->crap4j !== null; + } + + /** + * @throws Exception + */ + public function crap4j(): Crap4j + { + if (!$this->hasCrap4j()) { + throw new Exception( + 'Code Coverage report "Crap4J" has not been configured', + ); + } + + return $this->crap4j; + } + + /** + * @phpstan-assert-if-true !null $this->html + */ + public function hasHtml(): bool + { + return $this->html !== null; + } + + /** + * @throws Exception + */ + public function html(): Html + { + if (!$this->hasHtml()) { + throw new Exception( + 'Code Coverage report "HTML" has not been configured', + ); + } + + return $this->html; + } + + /** + * @phpstan-assert-if-true !null $this->openClover + */ + public function hasOpenClover(): bool + { + return $this->openClover !== null; + } + + /** + * @throws Exception + */ + public function openClover(): OpenClover + { + if (!$this->hasOpenClover()) { + throw new Exception( + 'Code Coverage report "OpenClover XML" has not been configured', + ); + } + + return $this->openClover; + } + + /** + * @phpstan-assert-if-true !null $this->php + */ + public function hasPhp(): bool + { + return $this->php !== null; + } + + /** + * @throws Exception + */ + public function php(): Php + { + if (!$this->hasPhp()) { + throw new Exception( + 'Code Coverage report "PHP" has not been configured', + ); + } + + return $this->php; + } + + /** + * @phpstan-assert-if-true !null $this->text + */ + public function hasText(): bool + { + return $this->text !== null; + } + + /** + * @throws Exception + */ + public function text(): Text + { + if (!$this->hasText()) { + throw new Exception( + 'Code Coverage report "Text" has not been configured', + ); + } + + return $this->text; + } + + /** + * @phpstan-assert-if-true !null $this->xml + */ + public function hasXml(): bool + { + return $this->xml !== null; + } + + /** + * @throws Exception + */ + public function xml(): Xml + { + if (!$this->hasXml()) { + throw new Exception( + 'Code Coverage report "XML" has not been configured', + ); + } + + return $this->xml; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/CodeCoverage/Report/Clover.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/CodeCoverage/Report/Clover.php new file mode 100644 index 000000000..cdaf122e9 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/CodeCoverage/Report/Clover.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 PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report; + +use PHPUnit\TextUI\Configuration\File; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class Clover +{ + private File $target; + + public function __construct(File $target) + { + $this->target = $target; + } + + public function target(): File + { + return $this->target; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/CodeCoverage/Report/Cobertura.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/CodeCoverage/Report/Cobertura.php new file mode 100644 index 000000000..015dba394 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/CodeCoverage/Report/Cobertura.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 PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report; + +use PHPUnit\TextUI\Configuration\File; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class Cobertura +{ + private File $target; + + public function __construct(File $target) + { + $this->target = $target; + } + + public function target(): File + { + return $this->target; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Report/Crap4j.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/CodeCoverage/Report/Crap4j.php similarity index 75% rename from app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Report/Crap4j.php rename to app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/CodeCoverage/Report/Crap4j.php index 4904775dd..24aa66ddf 100644 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Report/Crap4j.php +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/CodeCoverage/Report/Crap4j.php @@ -9,24 +9,19 @@ */ namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report; -use PHPUnit\TextUI\XmlConfiguration\File; +use PHPUnit\TextUI\Configuration\File; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit * - * @psalm-immutable + * @immutable */ -final class Crap4j +final readonly class Crap4j { - /** - * @var File - */ - private $target; - - /** - * @var int - */ - private $threshold; + private File $target; + private int $threshold; public function __construct(File $target, int $threshold) { diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/CodeCoverage/Report/Html.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/CodeCoverage/Report/Html.php new file mode 100644 index 000000000..dde8880f1 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/CodeCoverage/Report/Html.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 PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report; + +use PHPUnit\TextUI\Configuration\Directory; +use PHPUnit\TextUI\Configuration\NoCustomCssFileException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class Html +{ + private Directory $target; + private int $lowUpperBound; + private int $highLowerBound; + private string $colorSuccessLow; + private string $colorSuccessMedium; + private string $colorSuccessHigh; + private string $colorWarning; + private string $colorDanger; + private ?string $customCssFile; + + public function __construct(Directory $target, int $lowUpperBound, int $highLowerBound, string $colorSuccessLow, string $colorSuccessMedium, string $colorSuccessHigh, string $colorWarning, string $colorDanger, ?string $customCssFile) + { + $this->target = $target; + $this->lowUpperBound = $lowUpperBound; + $this->highLowerBound = $highLowerBound; + $this->colorSuccessLow = $colorSuccessLow; + $this->colorSuccessMedium = $colorSuccessMedium; + $this->colorSuccessHigh = $colorSuccessHigh; + $this->colorWarning = $colorWarning; + $this->colorDanger = $colorDanger; + $this->customCssFile = $customCssFile; + } + + public function target(): Directory + { + return $this->target; + } + + public function lowUpperBound(): int + { + return $this->lowUpperBound; + } + + public function highLowerBound(): int + { + return $this->highLowerBound; + } + + public function colorSuccessLow(): string + { + return $this->colorSuccessLow; + } + + public function colorSuccessMedium(): string + { + return $this->colorSuccessMedium; + } + + public function colorSuccessHigh(): string + { + return $this->colorSuccessHigh; + } + + public function colorWarning(): string + { + return $this->colorWarning; + } + + public function colorDanger(): string + { + return $this->colorDanger; + } + + /** + * @phpstan-assert-if-true !null $this->customCssFile + */ + public function hasCustomCssFile(): bool + { + return $this->customCssFile !== null; + } + + /** + * @throws NoCustomCssFileException + */ + public function customCssFile(): string + { + if (!$this->hasCustomCssFile()) { + throw new NoCustomCssFileException; + } + + return $this->customCssFile; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/CodeCoverage/Report/OpenClover.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/CodeCoverage/Report/OpenClover.php new file mode 100644 index 000000000..e20a24d08 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/CodeCoverage/Report/OpenClover.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 PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report; + +use PHPUnit\TextUI\Configuration\File; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class OpenClover +{ + private File $target; + + public function __construct(File $target) + { + $this->target = $target; + } + + public function target(): File + { + return $this->target; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/CodeCoverage/Report/Php.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/CodeCoverage/Report/Php.php new file mode 100644 index 000000000..ae022e7a0 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/CodeCoverage/Report/Php.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 PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report; + +use PHPUnit\TextUI\Configuration\File; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class Php +{ + private File $target; + + public function __construct(File $target) + { + $this->target = $target; + } + + public function target(): File + { + return $this->target; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/CodeCoverage/Report/Text.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/CodeCoverage/Report/Text.php new file mode 100644 index 000000000..cf04d9101 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/CodeCoverage/Report/Text.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 PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report; + +use PHPUnit\TextUI\Configuration\File; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class Text +{ + private File $target; + private bool $showUncoveredFiles; + private bool $showOnlySummary; + + public function __construct(File $target, bool $showUncoveredFiles, bool $showOnlySummary) + { + $this->target = $target; + $this->showUncoveredFiles = $showUncoveredFiles; + $this->showOnlySummary = $showOnlySummary; + } + + public function target(): File + { + return $this->target; + } + + public function showUncoveredFiles(): bool + { + return $this->showUncoveredFiles; + } + + public function showOnlySummary(): bool + { + return $this->showOnlySummary; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/CodeCoverage/Report/Xml.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/CodeCoverage/Report/Xml.php new file mode 100644 index 000000000..62f99a061 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/CodeCoverage/Report/Xml.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 PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report; + +use PHPUnit\TextUI\Configuration\Directory; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class Xml +{ + private Directory $target; + + public function __construct(Directory $target) + { + $this->target = $target; + } + + public function target(): Directory + { + return $this->target; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Configuration.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Configuration.php new file mode 100644 index 000000000..378022b0b --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Configuration.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 PHPUnit\TextUI\XmlConfiguration; + +use PHPUnit\TextUI\Configuration\ExtensionBootstrapCollection; +use PHPUnit\TextUI\Configuration\Php; +use PHPUnit\TextUI\Configuration\Source; +use PHPUnit\TextUI\Configuration\TestSuiteCollection; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\CodeCoverage; +use PHPUnit\TextUI\XmlConfiguration\Logging\Logging; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +abstract readonly class Configuration +{ + private ExtensionBootstrapCollection $extensions; + private Source $source; + private CodeCoverage $codeCoverage; + private Groups $groups; + private Logging $logging; + private Php $php; + private PHPUnit $phpunit; + private TestSuiteCollection $testSuite; + + public function __construct(ExtensionBootstrapCollection $extensions, Source $source, CodeCoverage $codeCoverage, Groups $groups, Logging $logging, Php $php, PHPUnit $phpunit, TestSuiteCollection $testSuite) + { + $this->extensions = $extensions; + $this->source = $source; + $this->codeCoverage = $codeCoverage; + $this->groups = $groups; + $this->logging = $logging; + $this->php = $php; + $this->phpunit = $phpunit; + $this->testSuite = $testSuite; + } + + public function extensions(): ExtensionBootstrapCollection + { + return $this->extensions; + } + + public function source(): Source + { + return $this->source; + } + + public function codeCoverage(): CodeCoverage + { + return $this->codeCoverage; + } + + public function groups(): Groups + { + return $this->groups; + } + + public function logging(): Logging + { + return $this->logging; + } + + public function php(): Php + { + return $this->php; + } + + public function phpunit(): PHPUnit + { + return $this->phpunit; + } + + public function testSuite(): TestSuiteCollection + { + return $this->testSuite; + } + + /** + * @phpstan-assert-if-true DefaultConfiguration $this + */ + public function isDefault(): bool + { + return false; + } + + /** + * @phpstan-assert-if-true LoadedFromFileConfiguration $this + */ + public function wasLoadedFromFile(): bool + { + return false; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/DefaultConfiguration.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/DefaultConfiguration.php new file mode 100644 index 000000000..de16474a8 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/DefaultConfiguration.php @@ -0,0 +1,172 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use PHPUnit\Runner\TestSuiteSorter; +use PHPUnit\TextUI\Configuration\ConstantCollection; +use PHPUnit\TextUI\Configuration\DirectoryCollection; +use PHPUnit\TextUI\Configuration\ExtensionBootstrapCollection; +use PHPUnit\TextUI\Configuration\FileCollection; +use PHPUnit\TextUI\Configuration\FilterDirectoryCollection as CodeCoverageFilterDirectoryCollection; +use PHPUnit\TextUI\Configuration\GroupCollection; +use PHPUnit\TextUI\Configuration\IniSettingCollection; +use PHPUnit\TextUI\Configuration\Php; +use PHPUnit\TextUI\Configuration\Source; +use PHPUnit\TextUI\Configuration\TestSuiteCollection; +use PHPUnit\TextUI\Configuration\VariableCollection; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\CodeCoverage; +use PHPUnit\TextUI\XmlConfiguration\Logging\Logging; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class DefaultConfiguration extends Configuration +{ + public static function create(): self + { + return new self( + ExtensionBootstrapCollection::fromArray([]), + new Source( + null, + false, + CodeCoverageFilterDirectoryCollection::fromArray([]), + FileCollection::fromArray([]), + CodeCoverageFilterDirectoryCollection::fromArray([]), + FileCollection::fromArray([]), + false, + false, + false, + false, + false, + false, + false, + false, + false, + [ + 'functions' => [], + 'methods' => [], + ], + false, + false, + false, + ), + new CodeCoverage( + false, + true, + false, + false, + null, + null, + null, + null, + null, + null, + null, + null, + ), + new Groups( + GroupCollection::fromArray([]), + GroupCollection::fromArray([]), + ), + new Logging( + null, + null, + null, + null, + null, + ), + new Php( + DirectoryCollection::fromArray([]), + IniSettingCollection::fromArray([]), + ConstantCollection::fromArray([]), + VariableCollection::fromArray([]), + VariableCollection::fromArray([]), + VariableCollection::fromArray([]), + VariableCollection::fromArray([]), + VariableCollection::fromArray([]), + VariableCollection::fromArray([]), + VariableCollection::fromArray([]), + VariableCollection::fromArray([]), + ), + new PHPUnit( + null, + true, + 80, + \PHPUnit\TextUI\Configuration\Configuration::COLOR_DEFAULT, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + null, + [], + false, + false, + false, + false, + false, + true, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + null, + false, + false, + true, + false, + false, + 1, + 1, + 10, + 60, + null, + TestSuiteSorter::ORDER_DEFAULT, + true, + false, + false, + false, + false, + false, + false, + 100, + 10, + ), + TestSuiteCollection::fromArray([]), + ); + } + + public function isDefault(): bool + { + return true; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Exception.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Exception.php new file mode 100644 index 000000000..60c3c9acc --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Exception.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 PHPUnit\TextUI\XmlConfiguration; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Exception extends RuntimeException implements \PHPUnit\Exception +{ +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Generator.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Generator.php new file mode 100644 index 000000000..4fc4ca2e6 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Generator.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function str_replace; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Generator +{ + /** + * @var string + */ + private const string TEMPLATE = <<<'EOT' + + + + + {tests_directory} + + + + + + {src_directory} + + + + +EOT; + + public function generateDefaultConfiguration(string $schemaLocation, string $bootstrapScript, string $testsDirectory, string $srcDirectory, string $cacheDirectory): string + { + return str_replace( + [ + '{schema_location}', + '{bootstrap_script}', + '{tests_directory}', + '{src_directory}', + '{cache_directory}', + ], + [ + $schemaLocation, + $bootstrapScript, + $testsDirectory, + $srcDirectory, + $cacheDirectory, + ], + self::TEMPLATE, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Groups.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Groups.php new file mode 100644 index 000000000..1a7cc6b45 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Groups.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 PHPUnit\TextUI\XmlConfiguration; + +use PHPUnit\TextUI\Configuration\GroupCollection; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class Groups +{ + private GroupCollection $include; + private GroupCollection $exclude; + + public function __construct(GroupCollection $include, GroupCollection $exclude) + { + $this->include = $include; + $this->exclude = $exclude; + } + + public function hasInclude(): bool + { + return !$this->include->isEmpty(); + } + + public function include(): GroupCollection + { + return $this->include; + } + + public function hasExclude(): bool + { + return !$this->exclude->isEmpty(); + } + + public function exclude(): GroupCollection + { + return $this->exclude; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/LoadedFromFileConfiguration.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/LoadedFromFileConfiguration.php new file mode 100644 index 000000000..e69d137fe --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/LoadedFromFileConfiguration.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 PHPUnit\TextUI\XmlConfiguration; + +use PHPUnit\TextUI\Configuration\ExtensionBootstrapCollection; +use PHPUnit\TextUI\Configuration\Php; +use PHPUnit\TextUI\Configuration\Source; +use PHPUnit\TextUI\Configuration\TestSuiteCollection; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\CodeCoverage; +use PHPUnit\TextUI\XmlConfiguration\Logging\Logging; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class LoadedFromFileConfiguration extends Configuration +{ + /** + * @var non-empty-string + */ + private string $filename; + private ValidationResult $validationResult; + + /** + * @param non-empty-string $filename + */ + public function __construct(string $filename, ValidationResult $validationResult, ExtensionBootstrapCollection $extensions, Source $source, CodeCoverage $codeCoverage, Groups $groups, Logging $logging, Php $php, PHPUnit $phpunit, TestSuiteCollection $testSuite) + { + $this->filename = $filename; + $this->validationResult = $validationResult; + + parent::__construct( + $extensions, + $source, + $codeCoverage, + $groups, + $logging, + $php, + $phpunit, + $testSuite, + ); + } + + /** + * @return non-empty-string + */ + public function filename(): string + { + return $this->filename; + } + + public function hasValidationErrors(): bool + { + return $this->validationResult->hasValidationErrors(); + } + + public function validationErrors(): string + { + return $this->validationResult->asString(); + } + + public function wasLoadedFromFile(): bool + { + return true; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Loader.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Loader.php new file mode 100644 index 000000000..886959fd5 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Loader.php @@ -0,0 +1,1225 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use const DIRECTORY_SEPARATOR; +use const PHP_EOL; +use const PHP_VERSION; +use function assert; +use function defined; +use function dirname; +use function explode; +use function is_numeric; +use function preg_match; +use function realpath; +use function sprintf; +use function str_contains; +use function str_starts_with; +use function strlen; +use function strtolower; +use function substr; +use function trim; +use DOMDocument; +use DOMElement; +use DOMNode; +use DOMNodeList; +use DOMXPath; +use PHPUnit\Runner\TestSuiteSorter; +use PHPUnit\Runner\Version; +use PHPUnit\TextUI\Configuration\Configuration; +use PHPUnit\TextUI\Configuration\Constant; +use PHPUnit\TextUI\Configuration\ConstantCollection; +use PHPUnit\TextUI\Configuration\Directory; +use PHPUnit\TextUI\Configuration\DirectoryCollection; +use PHPUnit\TextUI\Configuration\ExtensionBootstrap; +use PHPUnit\TextUI\Configuration\ExtensionBootstrapCollection; +use PHPUnit\TextUI\Configuration\File; +use PHPUnit\TextUI\Configuration\FileCollection; +use PHPUnit\TextUI\Configuration\FilterDirectory; +use PHPUnit\TextUI\Configuration\FilterDirectoryCollection; +use PHPUnit\TextUI\Configuration\Group; +use PHPUnit\TextUI\Configuration\GroupCollection; +use PHPUnit\TextUI\Configuration\IniSetting; +use PHPUnit\TextUI\Configuration\IniSettingCollection; +use PHPUnit\TextUI\Configuration\Php; +use PHPUnit\TextUI\Configuration\Source; +use PHPUnit\TextUI\Configuration\TestDirectory; +use PHPUnit\TextUI\Configuration\TestDirectoryCollection; +use PHPUnit\TextUI\Configuration\TestFile; +use PHPUnit\TextUI\Configuration\TestFileCollection; +use PHPUnit\TextUI\Configuration\TestSuite as TestSuiteConfiguration; +use PHPUnit\TextUI\Configuration\TestSuiteCollection; +use PHPUnit\TextUI\Configuration\Variable; +use PHPUnit\TextUI\Configuration\VariableCollection; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\CodeCoverage; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Clover; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Cobertura; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Crap4j; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Html as CodeCoverageHtml; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\OpenClover; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Php as CodeCoveragePhp; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Text as CodeCoverageText; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Xml as CodeCoverageXml; +use PHPUnit\TextUI\XmlConfiguration\Logging\Junit; +use PHPUnit\TextUI\XmlConfiguration\Logging\Logging; +use PHPUnit\TextUI\XmlConfiguration\Logging\Otr; +use PHPUnit\TextUI\XmlConfiguration\Logging\TeamCity; +use PHPUnit\TextUI\XmlConfiguration\Logging\TestDox\Html as TestDoxHtml; +use PHPUnit\TextUI\XmlConfiguration\Logging\TestDox\Text as TestDoxText; +use PHPUnit\Util\VersionComparisonOperator; +use PHPUnit\Util\Xml\Loader as XmlLoader; +use PHPUnit\Util\Xml\XmlException; +use SebastianBergmann\CodeCoverage\Report\Html\Colors; +use SebastianBergmann\CodeCoverage\Report\Thresholds; +use Throwable; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Loader +{ + /** + * @throws Exception + */ + public function load(string $filename): LoadedFromFileConfiguration + { + try { + $document = (new XmlLoader)->loadFile($filename); + } catch (XmlException $e) { + throw new Exception( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + + $xpath = new DOMXPath($document); + + try { + $xsdFilename = (new SchemaFinder)->find(Version::series()); + } catch (CannotFindSchemaException $e) { + throw new Exception( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + + $configurationFileRealpath = realpath($filename); + + assert($configurationFileRealpath !== false && $configurationFileRealpath !== ''); + + $validationResult = (new Validator)->validate($document, $xsdFilename); + + try { + return new LoadedFromFileConfiguration( + $configurationFileRealpath, + $validationResult, + $this->extensions($xpath), + $this->source($configurationFileRealpath, $xpath), + $this->codeCoverage($configurationFileRealpath, $xpath), + $this->groups($xpath), + $this->logging($configurationFileRealpath, $xpath), + $this->php($configurationFileRealpath, $xpath), + $this->phpunit($configurationFileRealpath, $document, $xpath), + $this->testSuite($configurationFileRealpath, $xpath), + ); + } catch (Throwable $t) { + $message = sprintf( + 'Cannot load XML configuration file %s', + $configurationFileRealpath, + ); + + if ($validationResult->hasValidationErrors()) { + $message .= ' because it has validation errors:' . PHP_EOL . $validationResult->asString(); + } + + throw new Exception($message, previous: $t); + } + } + + private function logging(string $filename, DOMXPath $xpath): Logging + { + $junit = null; + $element = $this->element($xpath, 'logging/junit'); + + if ($element !== null) { + $junit = new Junit( + new File( + $this->toAbsolutePath( + $filename, + (string) $this->parseStringAttribute($element, 'outputFile'), + ), + ), + ); + } + + $otr = null; + $element = $this->element($xpath, 'logging/otr'); + + if ($element !== null) { + $otr = new Otr( + new File( + $this->toAbsolutePath( + $filename, + (string) $this->parseStringAttribute($element, 'outputFile'), + ), + ), + $this->parseBooleanAttribute($element, 'includeGitInformation', false), + ); + } + + $teamCity = null; + $element = $this->element($xpath, 'logging/teamcity'); + + if ($element !== null) { + $teamCity = new TeamCity( + new File( + $this->toAbsolutePath( + $filename, + (string) $this->parseStringAttribute($element, 'outputFile'), + ), + ), + ); + } + + $testDoxHtml = null; + $element = $this->element($xpath, 'logging/testdoxHtml'); + + if ($element !== null) { + $testDoxHtml = new TestDoxHtml( + new File( + $this->toAbsolutePath( + $filename, + (string) $this->parseStringAttribute($element, 'outputFile'), + ), + ), + ); + } + + $testDoxText = null; + $element = $this->element($xpath, 'logging/testdoxText'); + + if ($element !== null) { + $testDoxText = new TestDoxText( + new File( + $this->toAbsolutePath( + $filename, + (string) $this->parseStringAttribute($element, 'outputFile'), + ), + ), + ); + } + + return new Logging( + $junit, + $otr, + $teamCity, + $testDoxHtml, + $testDoxText, + ); + } + + private function extensions(DOMXPath $xpath): ExtensionBootstrapCollection + { + $extensionBootstrappers = []; + + $bootstrapNodes = $xpath->query('extensions/bootstrap'); + + assert($bootstrapNodes instanceof DOMNodeList); + + foreach ($bootstrapNodes as $bootstrap) { + assert($bootstrap instanceof DOMElement); + + $parameters = []; + + $parameterNodes = $xpath->query('parameter', $bootstrap); + + assert($parameterNodes instanceof DOMNodeList); + + foreach ($parameterNodes as $parameter) { + assert($parameter instanceof DOMElement); + + $parameters[$parameter->getAttribute('name')] = $parameter->getAttribute('value'); + } + + $className = $bootstrap->getAttribute('class'); + + assert($className !== ''); + + $extensionBootstrappers[] = new ExtensionBootstrap( + $className, + $parameters, + ); + } + + return ExtensionBootstrapCollection::fromArray($extensionBootstrappers); + } + + /** + * @return non-empty-string + */ + private function toAbsolutePath(string $filename, string $path): string + { + $path = trim($path); + + if (str_starts_with($path, '/')) { + return $path; + } + + // Matches the following on Windows: + // - \\NetworkComputer\Path + // - \\.\D: + // - \\.\c: + // - C:\Windows + // - C:\windows + // - C:/windows + // - c:/windows + if (defined('PHP_WINDOWS_VERSION_BUILD') && + $path !== '' && + ($path[0] === '\\' || (strlen($path) >= 3 && preg_match('#^[A-Z]:[/\\\]#i', substr($path, 0, 3))))) { + return $path; + } + + if (str_contains($path, '://')) { + return $path; + } + + return dirname($filename) . DIRECTORY_SEPARATOR . $path; + } + + private function source(string $filename, DOMXPath $xpath): Source + { + $baseline = null; + $restrictNotices = false; + $restrictWarnings = false; + $ignoreSuppressionOfDeprecations = false; + $ignoreSuppressionOfPhpDeprecations = false; + $ignoreSuppressionOfErrors = false; + $ignoreSuppressionOfNotices = false; + $ignoreSuppressionOfPhpNotices = false; + $ignoreSuppressionOfWarnings = false; + $ignoreSuppressionOfPhpWarnings = false; + $ignoreSelfDeprecations = false; + $ignoreDirectDeprecations = false; + $ignoreIndirectDeprecations = false; + + $element = $this->element($xpath, 'source'); + + if ($element !== null) { + $baseline = $this->parseStringAttribute($element, 'baseline'); + + if ($baseline !== null) { + $baseline = $this->toAbsolutePath($filename, $baseline); + } + + $restrictNotices = $this->parseBooleanAttribute($element, 'restrictNotices', false); + $restrictWarnings = $this->parseBooleanAttribute($element, 'restrictWarnings', false); + $ignoreSuppressionOfDeprecations = $this->parseBooleanAttribute($element, 'ignoreSuppressionOfDeprecations', false); + $ignoreSuppressionOfPhpDeprecations = $this->parseBooleanAttribute($element, 'ignoreSuppressionOfPhpDeprecations', false); + $ignoreSuppressionOfErrors = $this->parseBooleanAttribute($element, 'ignoreSuppressionOfErrors', false); + $ignoreSuppressionOfNotices = $this->parseBooleanAttribute($element, 'ignoreSuppressionOfNotices', false); + $ignoreSuppressionOfPhpNotices = $this->parseBooleanAttribute($element, 'ignoreSuppressionOfPhpNotices', false); + $ignoreSuppressionOfWarnings = $this->parseBooleanAttribute($element, 'ignoreSuppressionOfWarnings', false); + $ignoreSuppressionOfPhpWarnings = $this->parseBooleanAttribute($element, 'ignoreSuppressionOfPhpWarnings', false); + $ignoreSelfDeprecations = $this->parseBooleanAttribute($element, 'ignoreSelfDeprecations', false); + $ignoreDirectDeprecations = $this->parseBooleanAttribute($element, 'ignoreDirectDeprecations', false); + $ignoreIndirectDeprecations = $this->parseBooleanAttribute($element, 'ignoreIndirectDeprecations', false); + } + + $deprecationTriggers = [ + 'functions' => [], + 'methods' => [], + ]; + + $functionNodes = $xpath->query('source/deprecationTrigger/function'); + + assert($functionNodes instanceof DOMNodeList); + + foreach ($functionNodes as $functionNode) { + assert($functionNode instanceof DOMElement); + + $deprecationTriggers['functions'][] = $functionNode->textContent; + } + + $methodNodes = $xpath->query('source/deprecationTrigger/method'); + + assert($methodNodes instanceof DOMNodeList); + + foreach ($methodNodes as $methodNode) { + assert($methodNode instanceof DOMElement); + + $deprecationTriggers['methods'][] = $methodNode->textContent; + } + + return new Source( + $baseline, + false, + $this->readFilterDirectories($filename, $xpath, 'source/include/directory'), + $this->readFilterFiles($filename, $xpath, 'source/include/file'), + $this->readFilterDirectories($filename, $xpath, 'source/exclude/directory'), + $this->readFilterFiles($filename, $xpath, 'source/exclude/file'), + $restrictNotices, + $restrictWarnings, + $ignoreSuppressionOfDeprecations, + $ignoreSuppressionOfPhpDeprecations, + $ignoreSuppressionOfErrors, + $ignoreSuppressionOfNotices, + $ignoreSuppressionOfPhpNotices, + $ignoreSuppressionOfWarnings, + $ignoreSuppressionOfPhpWarnings, + $deprecationTriggers, + $ignoreSelfDeprecations, + $ignoreDirectDeprecations, + $ignoreIndirectDeprecations, + ); + } + + private function codeCoverage(string $filename, DOMXPath $xpath): CodeCoverage + { + $pathCoverage = false; + $includeUncoveredFiles = true; + $ignoreDeprecatedCodeUnits = false; + $disableCodeCoverageIgnore = false; + + $element = $this->element($xpath, 'coverage'); + + if ($element !== null) { + $pathCoverage = $this->parseBooleanAttribute( + $element, + 'pathCoverage', + false, + ); + + $includeUncoveredFiles = $this->parseBooleanAttribute( + $element, + 'includeUncoveredFiles', + true, + ); + + $ignoreDeprecatedCodeUnits = $this->parseBooleanAttribute( + $element, + 'ignoreDeprecatedCodeUnits', + false, + ); + + $disableCodeCoverageIgnore = $this->parseBooleanAttribute( + $element, + 'disableCodeCoverageIgnore', + false, + ); + } + + $clover = null; + $element = $this->element($xpath, 'coverage/report/clover'); + + if ($element !== null) { + $clover = new Clover( + new File( + $this->toAbsolutePath( + $filename, + (string) $this->parseStringAttribute($element, 'outputFile'), + ), + ), + ); + } + + $cobertura = null; + $element = $this->element($xpath, 'coverage/report/cobertura'); + + if ($element !== null) { + $cobertura = new Cobertura( + new File( + $this->toAbsolutePath( + $filename, + (string) $this->parseStringAttribute($element, 'outputFile'), + ), + ), + ); + } + + $crap4j = null; + $element = $this->element($xpath, 'coverage/report/crap4j'); + + if ($element !== null) { + $crap4j = new Crap4j( + new File( + $this->toAbsolutePath( + $filename, + (string) $this->parseStringAttribute($element, 'outputFile'), + ), + ), + $this->parseIntegerAttribute($element, 'threshold', 30), + ); + } + + $html = null; + $element = $this->element($xpath, 'coverage/report/html'); + + if ($element !== null) { + $defaultColors = Colors::default(); + $defaultThresholds = Thresholds::default(); + + $html = new CodeCoverageHtml( + new Directory( + $this->toAbsolutePath( + $filename, + (string) $this->parseStringAttribute($element, 'outputDirectory'), + ), + ), + $this->parseIntegerAttribute($element, 'lowUpperBound', $defaultThresholds->lowUpperBound()), + $this->parseIntegerAttribute($element, 'highLowerBound', $defaultThresholds->highLowerBound()), + $this->parseStringAttributeWithDefault($element, 'colorSuccessLow', $defaultColors->successLow()), + $this->parseStringAttributeWithDefault($element, 'colorSuccessMedium', $defaultColors->successMedium()), + $this->parseStringAttributeWithDefault($element, 'colorSuccessHigh', $defaultColors->successHigh()), + $this->parseStringAttributeWithDefault($element, 'colorWarning', $defaultColors->warning()), + $this->parseStringAttributeWithDefault($element, 'colorDanger', $defaultColors->danger()), + $this->parseStringAttribute($element, 'customCssFile'), + ); + } + + $openClover = null; + $element = $this->element($xpath, 'coverage/report/openclover'); + + if ($element !== null) { + $openClover = new OpenClover( + new File( + $this->toAbsolutePath( + $filename, + (string) $this->parseStringAttribute($element, 'outputFile'), + ), + ), + ); + } + + $php = null; + $element = $this->element($xpath, 'coverage/report/php'); + + if ($element !== null) { + $php = new CodeCoveragePhp( + new File( + $this->toAbsolutePath( + $filename, + (string) $this->parseStringAttribute($element, 'outputFile'), + ), + ), + ); + } + + $text = null; + $element = $this->element($xpath, 'coverage/report/text'); + + if ($element !== null) { + $text = new CodeCoverageText( + new File( + $this->toAbsolutePath( + $filename, + (string) $this->parseStringAttribute($element, 'outputFile'), + ), + ), + $this->parseBooleanAttribute($element, 'showUncoveredFiles', false), + $this->parseBooleanAttribute($element, 'showOnlySummary', false), + ); + } + + $xml = null; + $element = $this->element($xpath, 'coverage/report/xml'); + + if ($element !== null) { + $xml = new CodeCoverageXml( + new Directory( + $this->toAbsolutePath( + $filename, + (string) $this->parseStringAttribute($element, 'outputDirectory'), + ), + ), + ); + } + + return new CodeCoverage( + $pathCoverage, + $includeUncoveredFiles, + $ignoreDeprecatedCodeUnits, + $disableCodeCoverageIgnore, + $clover, + $cobertura, + $crap4j, + $html, + $openClover, + $php, + $text, + $xml, + ); + } + + private function booleanFromString(string $value, bool $default): bool + { + if (strtolower($value) === 'false') { + return false; + } + + if (strtolower($value) === 'true') { + return true; + } + + return $default; + } + + private function valueFromString(string $value): bool|string + { + if (strtolower($value) === 'false') { + return false; + } + + if (strtolower($value) === 'true') { + return true; + } + + return $value; + } + + private function readFilterDirectories(string $filename, DOMXPath $xpath, string $query): FilterDirectoryCollection + { + $directories = []; + + $directoryNodes = $xpath->query($query); + + assert($directoryNodes instanceof DOMNodeList); + + foreach ($directoryNodes as $directoryNode) { + assert($directoryNode instanceof DOMElement); + + $directoryPath = $directoryNode->textContent; + + if ($directoryPath === '') { + continue; + } + + $directories[] = new FilterDirectory( + $this->toAbsolutePath($filename, $directoryPath), + $directoryNode->hasAttribute('prefix') ? $directoryNode->getAttribute('prefix') : '', + $directoryNode->hasAttribute('suffix') ? $directoryNode->getAttribute('suffix') : '.php', + ); + } + + return FilterDirectoryCollection::fromArray($directories); + } + + private function readFilterFiles(string $filename, DOMXPath $xpath, string $query): FileCollection + { + $files = []; + + $fileNodes = $xpath->query($query); + + assert($fileNodes instanceof DOMNodeList); + + foreach ($fileNodes as $fileNode) { + assert($fileNode instanceof DOMNode); + + $filePath = $fileNode->textContent; + + if ($filePath !== '') { + $files[] = new File($this->toAbsolutePath($filename, $filePath)); + } + } + + return FileCollection::fromArray($files); + } + + private function groups(DOMXPath $xpath): Groups + { + $include = []; + $exclude = []; + + $groupNodes = $xpath->query('groups/include/group'); + + assert($groupNodes instanceof DOMNodeList); + + foreach ($groupNodes as $groupNode) { + assert($groupNode instanceof DOMNode); + + $include[] = new Group($groupNode->textContent); + } + + $groupNodes = $xpath->query('groups/exclude/group'); + + assert($groupNodes instanceof DOMNodeList); + + foreach ($groupNodes as $groupNode) { + assert($groupNode instanceof DOMNode); + + $exclude[] = new Group($groupNode->textContent); + } + + return new Groups( + GroupCollection::fromArray($include), + GroupCollection::fromArray($exclude), + ); + } + + private function parseBooleanAttribute(DOMElement $element, string $attribute, bool $default): bool + { + if (!$element->hasAttribute($attribute)) { + return $default; + } + + return $this->booleanFromString( + $element->getAttribute($attribute), + false, + ); + } + + private function parseIntegerAttribute(DOMElement $element, string $attribute, int $default): int + { + if (!$element->hasAttribute($attribute)) { + return $default; + } + + return $this->parseInteger( + $element->getAttribute($attribute), + $default, + ); + } + + private function parseStringAttribute(DOMElement $element, string $attribute): ?string + { + if (!$element->hasAttribute($attribute)) { + return null; + } + + return $element->getAttribute($attribute); + } + + private function parseStringAttributeWithDefault(DOMElement $element, string $attribute, string $default): string + { + if (!$element->hasAttribute($attribute)) { + return $default; + } + + return $element->getAttribute($attribute); + } + + private function parseInteger(string $value, int $default): int + { + if (is_numeric($value)) { + return (int) $value; + } + + return $default; + } + + private function php(string $filename, DOMXPath $xpath): Php + { + $includePaths = []; + + $includePathNodes = $xpath->query('php/includePath'); + + assert($includePathNodes instanceof DOMNodeList); + + foreach ($includePathNodes as $includePath) { + assert($includePath instanceof DOMNode); + + $path = $includePath->textContent; + + if ($path !== '') { + $includePaths[] = new Directory($this->toAbsolutePath($filename, $path)); + } + } + + $iniSettings = []; + + $iniNodes = $xpath->query('php/ini'); + + assert($iniNodes instanceof DOMNodeList); + + foreach ($iniNodes as $ini) { + assert($ini instanceof DOMElement); + + $iniSettings[] = new IniSetting( + $ini->getAttribute('name'), + $ini->getAttribute('value'), + ); + } + + $constants = []; + + $constNodes = $xpath->query('php/const'); + + assert($constNodes instanceof DOMNodeList); + + foreach ($constNodes as $constNode) { + assert($constNode instanceof DOMElement); + + $value = $constNode->getAttribute('value'); + + $constants[] = new Constant( + $constNode->getAttribute('name'), + $this->valueFromString($value), + ); + } + + $variables = [ + 'var' => [], + 'env' => [], + 'post' => [], + 'get' => [], + 'cookie' => [], + 'server' => [], + 'files' => [], + 'request' => [], + ]; + + foreach (['var', 'env', 'post', 'get', 'cookie', 'server', 'files', 'request'] as $array) { + $varNodes = $xpath->query('php/' . $array); + + assert($varNodes instanceof DOMNodeList); + + foreach ($varNodes as $var) { + assert($var instanceof DOMElement); + + $name = $var->getAttribute('name'); + $value = $var->getAttribute('value'); + $force = false; + $verbatim = false; + + if ($var->hasAttribute('force')) { + $force = $this->booleanFromString($var->getAttribute('force'), false); + } + + if ($var->hasAttribute('verbatim')) { + $verbatim = $this->booleanFromString($var->getAttribute('verbatim'), false); + } + + if (!$verbatim) { + $value = $this->valueFromString($value); + } + + $variables[$array][] = new Variable($name, $value, $force); + } + } + + return new Php( + DirectoryCollection::fromArray($includePaths), + IniSettingCollection::fromArray($iniSettings), + ConstantCollection::fromArray($constants), + VariableCollection::fromArray($variables['var']), + VariableCollection::fromArray($variables['env']), + VariableCollection::fromArray($variables['post']), + VariableCollection::fromArray($variables['get']), + VariableCollection::fromArray($variables['cookie']), + VariableCollection::fromArray($variables['server']), + VariableCollection::fromArray($variables['files']), + VariableCollection::fromArray($variables['request']), + ); + } + + private function phpunit(string $filename, DOMDocument $document, DOMXPath $xpath): PHPUnit + { + $executionOrder = TestSuiteSorter::ORDER_DEFAULT; + $defectsFirst = false; + $resolveDependencies = $this->parseBooleanAttribute($document->documentElement, 'resolveDependencies', true); + + if ($document->documentElement->hasAttribute('executionOrder')) { + foreach (explode(',', $document->documentElement->getAttribute('executionOrder')) as $order) { + switch ($order) { + case 'default': + $executionOrder = TestSuiteSorter::ORDER_DEFAULT; + $defectsFirst = false; + $resolveDependencies = true; + + break; + + case 'depends': + $resolveDependencies = true; + + break; + + case 'no-depends': + $resolveDependencies = false; + + break; + + case 'defects': + $defectsFirst = true; + + break; + + case 'duration': + $executionOrder = TestSuiteSorter::ORDER_DURATION; + + break; + + case 'random': + $executionOrder = TestSuiteSorter::ORDER_RANDOMIZED; + + break; + + case 'reverse': + $executionOrder = TestSuiteSorter::ORDER_REVERSED; + + break; + + case 'size': + $executionOrder = TestSuiteSorter::ORDER_SIZE; + + break; + } + } + } + + $cacheDirectory = $this->parseStringAttribute($document->documentElement, 'cacheDirectory'); + + if ($cacheDirectory !== null) { + $cacheDirectory = $this->toAbsolutePath($filename, $cacheDirectory); + } + + $bootstrap = $this->parseStringAttribute($document->documentElement, 'bootstrap'); + + if ($bootstrap !== null) { + $bootstrap = $this->toAbsolutePath($filename, $bootstrap); + } + + $extensionsDirectory = $this->parseStringAttribute($document->documentElement, 'extensionsDirectory'); + + if ($extensionsDirectory !== null) { + $extensionsDirectory = $this->toAbsolutePath($filename, $extensionsDirectory); + } + + $backupStaticProperties = false; + + if ($document->documentElement->hasAttribute('backupStaticProperties')) { + $backupStaticProperties = $this->parseBooleanAttribute($document->documentElement, 'backupStaticProperties', false); + } + + $requireCoverageMetadata = false; + + if ($document->documentElement->hasAttribute('requireCoverageMetadata')) { + $requireCoverageMetadata = $this->parseBooleanAttribute($document->documentElement, 'requireCoverageMetadata', false); + } + + $beStrictAboutCoverageMetadata = false; + + if ($document->documentElement->hasAttribute('beStrictAboutCoverageMetadata')) { + $beStrictAboutCoverageMetadata = $this->parseBooleanAttribute($document->documentElement, 'beStrictAboutCoverageMetadata', false); + } + + $shortenArraysForExportThreshold = $this->parseIntegerAttribute($document->documentElement, 'shortenArraysForExportThreshold', 10); + + if ($shortenArraysForExportThreshold < 0) { + $shortenArraysForExportThreshold = 0; + } + + return new PHPUnit( + $cacheDirectory, + $this->parseBooleanAttribute($document->documentElement, 'cacheResult', true), + $this->parseColumns($document), + $this->parseColors($document), + $this->parseBooleanAttribute($document->documentElement, 'stderr', false), + $this->parseBooleanAttribute($document->documentElement, 'displayDetailsOnAllIssues', false), + $this->parseBooleanAttribute($document->documentElement, 'displayDetailsOnIncompleteTests', false), + $this->parseBooleanAttribute($document->documentElement, 'displayDetailsOnSkippedTests', false), + $this->parseBooleanAttribute($document->documentElement, 'displayDetailsOnTestsThatTriggerDeprecations', false), + $this->parseBooleanAttribute($document->documentElement, 'displayDetailsOnPhpunitDeprecations', false), + $this->parseBooleanAttribute($document->documentElement, 'displayDetailsOnPhpunitNotices', false), + $this->parseBooleanAttribute($document->documentElement, 'displayDetailsOnTestsThatTriggerErrors', false), + $this->parseBooleanAttribute($document->documentElement, 'displayDetailsOnTestsThatTriggerNotices', false), + $this->parseBooleanAttribute($document->documentElement, 'displayDetailsOnTestsThatTriggerWarnings', false), + $this->parseBooleanAttribute($document->documentElement, 'reverseDefectList', false), + $requireCoverageMetadata, + $bootstrap, + $this->bootstrapForTestSuite($filename, $xpath), + $this->parseBooleanAttribute($document->documentElement, 'processIsolation', false), + $this->parseBooleanAttribute($document->documentElement, 'failOnAllIssues', false), + $this->parseBooleanAttribute($document->documentElement, 'failOnDeprecation', false), + $this->parseBooleanAttribute($document->documentElement, 'failOnPhpunitDeprecation', false), + $this->parseBooleanAttribute($document->documentElement, 'failOnPhpunitNotice', false), + $this->parseBooleanAttribute($document->documentElement, 'failOnPhpunitWarning', true), + $this->parseBooleanAttribute($document->documentElement, 'failOnEmptyTestSuite', false), + $this->parseBooleanAttribute($document->documentElement, 'failOnIncomplete', false), + $this->parseBooleanAttribute($document->documentElement, 'failOnNotice', false), + $this->parseBooleanAttribute($document->documentElement, 'failOnRisky', false), + $this->parseBooleanAttribute($document->documentElement, 'failOnSkipped', false), + $this->parseBooleanAttribute($document->documentElement, 'failOnWarning', false), + $this->parseBooleanAttribute($document->documentElement, 'stopOnDefect', false), + $this->parseBooleanAttribute($document->documentElement, 'stopOnDeprecation', false), + $this->parseBooleanAttribute($document->documentElement, 'stopOnError', false), + $this->parseBooleanAttribute($document->documentElement, 'stopOnFailure', false), + $this->parseBooleanAttribute($document->documentElement, 'stopOnIncomplete', false), + $this->parseBooleanAttribute($document->documentElement, 'stopOnNotice', false), + $this->parseBooleanAttribute($document->documentElement, 'stopOnRisky', false), + $this->parseBooleanAttribute($document->documentElement, 'stopOnSkipped', false), + $this->parseBooleanAttribute($document->documentElement, 'stopOnWarning', false), + $extensionsDirectory, + $this->parseBooleanAttribute($document->documentElement, 'beStrictAboutChangesToGlobalState', false), + $this->parseBooleanAttribute($document->documentElement, 'beStrictAboutOutputDuringTests', false), + $this->parseBooleanAttribute($document->documentElement, 'beStrictAboutTestsThatDoNotTestAnything', true), + $beStrictAboutCoverageMetadata, + $this->parseBooleanAttribute($document->documentElement, 'enforceTimeLimit', false), + $this->parseIntegerAttribute($document->documentElement, 'defaultTimeLimit', 1), + $this->parseIntegerAttribute($document->documentElement, 'timeoutForSmallTests', 1), + $this->parseIntegerAttribute($document->documentElement, 'timeoutForMediumTests', 10), + $this->parseIntegerAttribute($document->documentElement, 'timeoutForLargeTests', 60), + $this->parseStringAttribute($document->documentElement, 'defaultTestSuite'), + $executionOrder, + $resolveDependencies, + $defectsFirst, + $this->parseBooleanAttribute($document->documentElement, 'backupGlobals', false), + $backupStaticProperties, + $this->parseBooleanAttribute($document->documentElement, 'testdox', false), + $this->parseBooleanAttribute($document->documentElement, 'testdoxSummary', false), + $this->parseBooleanAttribute($document->documentElement, 'controlGarbageCollector', false), + $this->parseIntegerAttribute($document->documentElement, 'numberOfTestsBeforeGarbageCollection', 100), + $shortenArraysForExportThreshold, + ); + } + + private function parseColors(DOMDocument $document): string + { + $colors = Configuration::COLOR_DEFAULT; + + if ($document->documentElement->hasAttribute('colors')) { + /* only allow boolean for compatibility with previous versions + 'always' only allowed from command line */ + if ($this->booleanFromString($document->documentElement->getAttribute('colors'), false)) { + $colors = Configuration::COLOR_AUTO; + } else { + $colors = Configuration::COLOR_NEVER; + } + } + + return $colors; + } + + private function parseColumns(DOMDocument $document): int|string + { + $columns = 80; + + if ($document->documentElement->hasAttribute('columns')) { + $columns = $document->documentElement->getAttribute('columns'); + + if ($columns !== 'max') { + $columns = $this->parseInteger($columns, 80); + } + } + + return $columns; + } + + /** + * @return array + */ + private function bootstrapForTestSuite(string $filename, DOMXPath $xpath): array + { + $bootstrapForTestSuite = []; + + foreach ($this->parseTestSuiteElements($xpath) as $element) { + if (!$element->hasAttribute('bootstrap')) { + continue; + } + + $name = $element->getAttribute('name'); + $bootstrap = $element->getAttribute('bootstrap'); + + assert($name !== ''); + assert($bootstrap !== ''); + + $bootstrapForTestSuite[$name] = $this->toAbsolutePath($filename, $bootstrap); + } + + return $bootstrapForTestSuite; + } + + private function testSuite(string $filename, DOMXPath $xpath): TestSuiteCollection + { + $testSuites = []; + + foreach ($this->parseTestSuiteElements($xpath) as $element) { + $exclude = []; + + foreach ($element->getElementsByTagName('exclude') as $excludeNode) { + $excludeFile = $excludeNode->textContent; + + if ($excludeFile !== '') { + $exclude[] = new File($this->toAbsolutePath($filename, $excludeFile)); + } + } + + $directories = []; + + foreach ($element->getElementsByTagName('directory') as $directoryNode) { + assert($directoryNode instanceof DOMElement); + + $directory = $directoryNode->textContent; + + if ($directory === '') { + continue; + } + + $prefix = ''; + + if ($directoryNode->hasAttribute('prefix')) { + $prefix = $directoryNode->getAttribute('prefix'); + } + + $suffix = 'Test.php'; + + if ($directoryNode->hasAttribute('suffix')) { + $suffix = $directoryNode->getAttribute('suffix'); + } + + $phpVersion = PHP_VERSION; + + if ($directoryNode->hasAttribute('phpVersion')) { + $phpVersion = $directoryNode->getAttribute('phpVersion'); + } + + $phpVersionOperator = new VersionComparisonOperator('>='); + + if ($directoryNode->hasAttribute('phpVersionOperator')) { + $phpVersionOperator = new VersionComparisonOperator($directoryNode->getAttribute('phpVersionOperator')); + } + + $groups = []; + + if ($directoryNode->hasAttribute('groups')) { + foreach (explode(',', $directoryNode->getAttribute('groups')) as $group) { + $group = trim($group); + + if ($group === '') { + continue; + } + + $groups[] = $group; + } + } + + $directories[] = new TestDirectory( + $this->toAbsolutePath($filename, $directory), + $prefix, + $suffix, + $phpVersion, + $phpVersionOperator, + $groups, + ); + } + + $files = []; + + foreach ($element->getElementsByTagName('file') as $fileNode) { + assert($fileNode instanceof DOMElement); + + $file = $fileNode->textContent; + + if ($file === '') { + continue; + } + + $phpVersion = PHP_VERSION; + + if ($fileNode->hasAttribute('phpVersion')) { + $phpVersion = $fileNode->getAttribute('phpVersion'); + } + + $phpVersionOperator = new VersionComparisonOperator('>='); + + if ($fileNode->hasAttribute('phpVersionOperator')) { + $phpVersionOperator = new VersionComparisonOperator($fileNode->getAttribute('phpVersionOperator')); + } + + $groups = []; + + if ($fileNode->hasAttribute('groups')) { + foreach (explode(',', $fileNode->getAttribute('groups')) as $group) { + $group = trim($group); + + if ($group === '') { + continue; + } + + $groups[] = $group; + } + } + + $files[] = new TestFile( + $this->toAbsolutePath($filename, $file), + $phpVersion, + $phpVersionOperator, + $groups, + ); + } + + $name = $element->getAttribute('name'); + + assert($name !== ''); + + $testSuites[] = new TestSuiteConfiguration( + $name, + TestDirectoryCollection::fromArray($directories), + TestFileCollection::fromArray($files), + FileCollection::fromArray($exclude), + ); + } + + return TestSuiteCollection::fromArray($testSuites); + } + + /** + * @return list + */ + private function parseTestSuiteElements(DOMXPath $xpath): array + { + $elements = []; + + $testSuiteNodes = $xpath->query('testsuites/testsuite'); + + assert($testSuiteNodes instanceof DOMNodeList); + + if ($testSuiteNodes->length === 0) { + $testSuiteNodes = $xpath->query('testsuite'); + + assert($testSuiteNodes instanceof DOMNodeList); + } + + if ($testSuiteNodes->length === 1) { + $element = $testSuiteNodes->item(0); + + assert($element instanceof DOMElement); + + $elements[] = $element; + } else { + foreach ($testSuiteNodes as $testSuiteNode) { + assert($testSuiteNode instanceof DOMElement); + + $elements[] = $testSuiteNode; + } + } + + return $elements; + } + + private function element(DOMXPath $xpath, string $element): ?DOMElement + { + $nodes = $xpath->query($element); + + assert($nodes instanceof DOMNodeList); + + if ($nodes->length === 1) { + $node = $nodes->item(0); + + assert($node instanceof DOMElement); + + return $node; + } + + return null; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Logging/Junit.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Logging/Junit.php new file mode 100644 index 000000000..cf9878dfd --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Logging/Junit.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 PHPUnit\TextUI\XmlConfiguration\Logging; + +use PHPUnit\TextUI\Configuration\File; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class Junit +{ + private File $target; + + public function __construct(File $target) + { + $this->target = $target; + } + + public function target(): File + { + return $this->target; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Logging/Logging.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Logging/Logging.php new file mode 100644 index 000000000..63f4d89fc --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Logging/Logging.php @@ -0,0 +1,124 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration\Logging; + +use PHPUnit\TextUI\XmlConfiguration\Exception; +use PHPUnit\TextUI\XmlConfiguration\Logging\TestDox\Html as TestDoxHtml; +use PHPUnit\TextUI\XmlConfiguration\Logging\TestDox\Text as TestDoxText; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class Logging +{ + private ?Junit $junit; + private ?Otr $otr; + private ?TeamCity $teamCity; + private ?TestDoxHtml $testDoxHtml; + private ?TestDoxText $testDoxText; + + public function __construct(?Junit $junit, ?Otr $otr, ?TeamCity $teamCity, ?TestDoxHtml $testDoxHtml, ?TestDoxText $testDoxText) + { + $this->junit = $junit; + $this->otr = $otr; + $this->teamCity = $teamCity; + $this->testDoxHtml = $testDoxHtml; + $this->testDoxText = $testDoxText; + } + + public function hasJunit(): bool + { + return $this->junit !== null; + } + + /** + * @throws Exception + */ + public function junit(): Junit + { + if ($this->junit === null) { + throw new Exception('Logger "JUnit XML" is not configured'); + } + + return $this->junit; + } + + public function hasOtr(): bool + { + return $this->otr !== null; + } + + /** + * @throws Exception + */ + public function otr(): Otr + { + if ($this->otr === null) { + throw new Exception('Logger "Open Test Reporting XML" is not configured'); + } + + return $this->otr; + } + + public function hasTeamCity(): bool + { + return $this->teamCity !== null; + } + + /** + * @throws Exception + */ + public function teamCity(): TeamCity + { + if ($this->teamCity === null) { + throw new Exception('Logger "Team City" is not configured'); + } + + return $this->teamCity; + } + + public function hasTestDoxHtml(): bool + { + return $this->testDoxHtml !== null; + } + + /** + * @throws Exception + */ + public function testDoxHtml(): TestDoxHtml + { + if ($this->testDoxHtml === null) { + throw new Exception('Logger "TestDox HTML" is not configured'); + } + + return $this->testDoxHtml; + } + + public function hasTestDoxText(): bool + { + return $this->testDoxText !== null; + } + + /** + * @throws Exception + */ + public function testDoxText(): TestDoxText + { + if ($this->testDoxText === null) { + throw new Exception('Logger "TestDox Text" is not configured'); + } + + return $this->testDoxText; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Logging/Otr.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Logging/Otr.php new file mode 100644 index 000000000..25cfc9898 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Logging/Otr.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 PHPUnit\TextUI\XmlConfiguration\Logging; + +use PHPUnit\TextUI\Configuration\File; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class Otr +{ + private File $target; + private bool $includeGitInformation; + + public function __construct(File $target, bool $includeGitInformation) + { + $this->target = $target; + $this->includeGitInformation = $includeGitInformation; + } + + public function target(): File + { + return $this->target; + } + + public function includeGitInformation(): bool + { + return $this->includeGitInformation; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Logging/TeamCity.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Logging/TeamCity.php new file mode 100644 index 000000000..daf1ceccf --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Logging/TeamCity.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 PHPUnit\TextUI\XmlConfiguration\Logging; + +use PHPUnit\TextUI\Configuration\File; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class TeamCity +{ + private File $target; + + public function __construct(File $target) + { + $this->target = $target; + } + + public function target(): File + { + return $this->target; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Logging/TestDox/Html.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Logging/TestDox/Html.php new file mode 100644 index 000000000..60e9d4da3 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Logging/TestDox/Html.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 PHPUnit\TextUI\XmlConfiguration\Logging\TestDox; + +use PHPUnit\TextUI\Configuration\File; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class Html +{ + private File $target; + + public function __construct(File $target) + { + $this->target = $target; + } + + public function target(): File + { + return $this->target; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Logging/TestDox/Text.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Logging/TestDox/Text.php new file mode 100644 index 000000000..ed436c0a8 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Logging/TestDox/Text.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 PHPUnit\TextUI\XmlConfiguration\Logging\TestDox; + +use PHPUnit\TextUI\Configuration\File; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class Text +{ + private File $target; + + public function __construct(File $target) + { + $this->target = $target; + } + + public function target(): File + { + return $this->target; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/MigrationBuilder.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/MigrationBuilder.php new file mode 100644 index 000000000..6235c55f3 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/MigrationBuilder.php @@ -0,0 +1,111 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function version_compare; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class MigrationBuilder +{ + /** + * @var non-empty-array> + */ + private const array AVAILABLE_MIGRATIONS = [ + '8.5' => [ + RemoveLogTypes::class, + ], + + '9.2' => [ + RemoveCacheTokensAttribute::class, + IntroduceCoverageElement::class, + MoveAttributesFromRootToCoverage::class, + MoveAttributesFromFilterWhitelistToCoverage::class, + MoveWhitelistIncludesToCoverage::class, + MoveWhitelistExcludesToCoverage::class, + RemoveEmptyFilter::class, + CoverageCloverToReport::class, + CoverageCrap4jToReport::class, + CoverageHtmlToReport::class, + CoveragePhpToReport::class, + CoverageTextToReport::class, + CoverageXmlToReport::class, + ConvertLogTypes::class, + ], + + '9.5' => [ + RemoveListeners::class, + RemoveTestSuiteLoaderAttributes::class, + RemoveCacheResultFileAttribute::class, + RemoveCoverageElementCacheDirectoryAttribute::class, + RemoveCoverageElementProcessUncoveredFilesAttribute::class, + IntroduceCacheDirectoryAttribute::class, + RenameBackupStaticAttributesAttribute::class, + RemoveBeStrictAboutResourceUsageDuringSmallTestsAttribute::class, + RemoveBeStrictAboutTodoAnnotatedTestsAttribute::class, + RemovePrinterAttributes::class, + RemoveVerboseAttribute::class, + RenameForceCoversAnnotationAttribute::class, + RenameBeStrictAboutCoversAnnotationAttribute::class, + RemoveConversionToExceptionsAttributes::class, + RemoveNoInteractionAttribute::class, + RemoveLoggingElements::class, + RemoveTestDoxGroupsElement::class, + ], + + '10.0' => [ + MoveCoverageDirectoriesToSource::class, + ], + + '10.4' => [ + RemoveBeStrictAboutTodoAnnotatedTestsAttribute::class, + ], + + '10.5' => [ + RemoveRegisterMockObjectsFromTestArgumentsRecursivelyAttribute::class, + ], + + '11.0' => [ + ReplaceRestrictDeprecationsWithIgnoreDeprecations::class, + ], + + '11.1' => [ + RemoveCacheResultFileAttribute::class, + RemoveCoverageElementCacheDirectoryAttribute::class, + ], + + '11.2' => [ + RemoveBeStrictAboutTodoAnnotatedTestsAttribute::class, + ], + ]; + + /** + * @return non-empty-list + */ + public function build(string $fromVersion): array + { + $stack = [new UpdateSchemaLocation]; + + foreach (self::AVAILABLE_MIGRATIONS as $version => $migrations) { + if (version_compare($version, $fromVersion, '<')) { + continue; + } + + foreach ($migrations as $migration) { + $stack[] = new $migration; + } + } + + return $stack; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/MigrationException.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/MigrationException.php similarity index 82% rename from app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/MigrationException.php rename to app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/MigrationException.php index 9fa4068ce..bb35aca68 100644 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/MigrationException.php +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/MigrationException.php @@ -13,6 +13,8 @@ use RuntimeException; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class MigrationException extends RuntimeException implements Exception diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/ConvertLogTypes.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/ConvertLogTypes.php similarity index 89% rename from app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/ConvertLogTypes.php rename to app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/ConvertLogTypes.php index 697bbe082..81a0e322a 100644 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/ConvertLogTypes.php +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/ConvertLogTypes.php @@ -13,9 +13,11 @@ use DOMElement; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ -final class ConvertLogTypes implements Migration +final readonly class ConvertLogTypes implements Migration { public function migrate(DOMDocument $document): void { diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/CoverageCloverToReport.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/CoverageCloverToReport.php similarity index 80% rename from app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/CoverageCloverToReport.php rename to app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/CoverageCloverToReport.php index 5f1522b9c..0dfee46fd 100644 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/CoverageCloverToReport.php +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/CoverageCloverToReport.php @@ -12,9 +12,11 @@ use DOMElement; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ -final class CoverageCloverToReport extends LogToReportMigration +final readonly class CoverageCloverToReport extends LogToReportMigration { protected function forType(): string { @@ -24,6 +26,7 @@ protected function forType(): string protected function toReportFormat(DOMElement $logNode): DOMElement { $clover = $logNode->ownerDocument->createElement('clover'); + $clover->setAttribute('outputFile', $logNode->getAttribute('target')); return $clover; diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/CoverageCrap4jToReport.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/CoverageCrap4jToReport.php similarity index 82% rename from app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/CoverageCrap4jToReport.php rename to app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/CoverageCrap4jToReport.php index afbaaec18..f0aac5c7c 100644 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/CoverageCrap4jToReport.php +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/CoverageCrap4jToReport.php @@ -12,9 +12,11 @@ use DOMElement; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ -final class CoverageCrap4jToReport extends LogToReportMigration +final readonly class CoverageCrap4jToReport extends LogToReportMigration { protected function forType(): string { diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/CoverageHtmlToReport.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/CoverageHtmlToReport.php similarity index 82% rename from app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/CoverageHtmlToReport.php rename to app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/CoverageHtmlToReport.php index 7e12095b4..f6b7982d9 100644 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/CoverageHtmlToReport.php +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/CoverageHtmlToReport.php @@ -12,9 +12,11 @@ use DOMElement; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ -final class CoverageHtmlToReport extends LogToReportMigration +final readonly class CoverageHtmlToReport extends LogToReportMigration { protected function forType(): string { diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/CoveragePhpToReport.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/CoveragePhpToReport.php similarity index 80% rename from app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/CoveragePhpToReport.php rename to app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/CoveragePhpToReport.php index bfa10030b..7e362708b 100644 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/CoveragePhpToReport.php +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/CoveragePhpToReport.php @@ -12,9 +12,11 @@ use DOMElement; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ -final class CoveragePhpToReport extends LogToReportMigration +final readonly class CoveragePhpToReport extends LogToReportMigration { protected function forType(): string { diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/CoverageTextToReport.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/CoverageTextToReport.php similarity index 82% rename from app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/CoverageTextToReport.php rename to app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/CoverageTextToReport.php index 063d8df0c..d463cef8d 100644 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/CoverageTextToReport.php +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/CoverageTextToReport.php @@ -12,9 +12,11 @@ use DOMElement; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ -final class CoverageTextToReport extends LogToReportMigration +final readonly class CoverageTextToReport extends LogToReportMigration { protected function forType(): string { diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/CoverageXmlToReport.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/CoverageXmlToReport.php similarity index 80% rename from app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/CoverageXmlToReport.php rename to app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/CoverageXmlToReport.php index 480d7777e..3db899742 100644 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/CoverageXmlToReport.php +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/CoverageXmlToReport.php @@ -12,9 +12,11 @@ use DOMElement; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ -final class CoverageXmlToReport extends LogToReportMigration +final readonly class CoverageXmlToReport extends LogToReportMigration { protected function forType(): string { diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/IntroduceCacheDirectoryAttribute.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/IntroduceCacheDirectoryAttribute.php new file mode 100644 index 000000000..87624cc6d --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/IntroduceCacheDirectoryAttribute.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function assert; +use DOMDocument; +use DOMElement; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class IntroduceCacheDirectoryAttribute implements Migration +{ + public function migrate(DOMDocument $document): void + { + $root = $document->documentElement; + + assert($root instanceof DOMElement); + + if ($root->hasAttribute('cacheDirectory')) { + return; + } + + $root->setAttribute('cacheDirectory', '.phpunit.cache'); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/IntroduceCoverageElement.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/IntroduceCoverageElement.php similarity index 79% rename from app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/IntroduceCoverageElement.php rename to app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/IntroduceCoverageElement.php index de52857ee..9334c1f43 100644 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/IntroduceCoverageElement.php +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/IntroduceCoverageElement.php @@ -12,9 +12,11 @@ use DOMDocument; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ -final class IntroduceCoverageElement implements Migration +final readonly class IntroduceCoverageElement implements Migration { public function migrate(DOMDocument $document): void { diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/LogToReportMigration.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/LogToReportMigration.php similarity index 78% rename from app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/LogToReportMigration.php rename to app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/LogToReportMigration.php index c07de0ec7..08815cbcb 100644 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/LogToReportMigration.php +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/LogToReportMigration.php @@ -9,15 +9,18 @@ */ namespace PHPUnit\TextUI\XmlConfiguration; +use function assert; use function sprintf; use DOMDocument; use DOMElement; use DOMXPath; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ -abstract class LogToReportMigration implements Migration +abstract readonly class LogToReportMigration implements Migration { /** * @throws MigrationException @@ -48,6 +51,9 @@ public function migrate(DOMDocument $document): void $logNode->parentNode->removeChild($logNode); } + /** + * @param list $attributes + */ protected function migrateAttributes(DOMElement $src, DOMElement $dest, array $attributes): void { foreach ($attributes as $attr) { @@ -66,9 +72,18 @@ abstract protected function toReportFormat(DOMElement $logNode): DOMElement; private function findLogNode(DOMDocument $document): ?DOMElement { - $logNode = (new DOMXPath($document))->query( - sprintf('//logging/log[@type="%s"]', $this->forType()), - )->item(0); + $xpath = new DOMXPath($document); + + $logNode = $xpath->query( + sprintf( + '//logging/log[@type="%s"]', + $this->forType(), + ), + ); + + assert($logNode !== false); + + $logNode = $logNode->item(0); if (!$logNode instanceof DOMElement) { return null; diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/Migration.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/Migration.php similarity index 82% rename from app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/Migration.php rename to app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/Migration.php index fa4092a9f..05359a2d0 100644 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/Migration.php +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/Migration.php @@ -12,6 +12,8 @@ use DOMDocument; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ interface Migration diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/MoveAttributesFromFilterWhitelistToCoverage.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/MoveAttributesFromFilterWhitelistToCoverage.php similarity index 85% rename from app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/MoveAttributesFromFilterWhitelistToCoverage.php rename to app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/MoveAttributesFromFilterWhitelistToCoverage.php index a7aab5e51..685381698 100644 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/MoveAttributesFromFilterWhitelistToCoverage.php +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/MoveAttributesFromFilterWhitelistToCoverage.php @@ -13,9 +13,11 @@ use DOMElement; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ -final class MoveAttributesFromFilterWhitelistToCoverage implements Migration +final readonly class MoveAttributesFromFilterWhitelistToCoverage implements Migration { /** * @throws MigrationException @@ -24,7 +26,7 @@ public function migrate(DOMDocument $document): void { $whitelist = $document->getElementsByTagName('whitelist')->item(0); - if (!$whitelist) { + if ($whitelist === null) { return; } diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/MoveAttributesFromRootToCoverage.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/MoveAttributesFromRootToCoverage.php similarity index 83% rename from app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/MoveAttributesFromRootToCoverage.php rename to app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/MoveAttributesFromRootToCoverage.php index b86b259c3..f0e47b90f 100644 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/MoveAttributesFromRootToCoverage.php +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/MoveAttributesFromRootToCoverage.php @@ -9,13 +9,16 @@ */ namespace PHPUnit\TextUI\XmlConfiguration; +use function assert; use DOMDocument; use DOMElement; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ -final class MoveAttributesFromRootToCoverage implements Migration +final readonly class MoveAttributesFromRootToCoverage implements Migration { /** * @throws MigrationException @@ -29,6 +32,8 @@ public function migrate(DOMDocument $document): void $root = $document->documentElement; + assert($root instanceof DOMElement); + $coverage = $document->getElementsByTagName('coverage')->item(0); if (!$coverage instanceof DOMElement) { diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/MoveCoverageDirectoriesToSource.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/MoveCoverageDirectoriesToSource.php new file mode 100644 index 000000000..4dd37ea51 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/MoveCoverageDirectoriesToSource.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 PHPUnit\TextUI\XmlConfiguration; + +use function assert; +use DOMDocument; +use DOMElement; +use DOMXPath; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class MoveCoverageDirectoriesToSource implements Migration +{ + /** + * @throws MigrationException + */ + public function migrate(DOMDocument $document): void + { + $source = $document->getElementsByTagName('source')->item(0); + + if ($source !== null) { + return; + } + + $coverage = $document->getElementsByTagName('coverage')->item(0); + + if ($coverage === null) { + return; + } + + $root = $document->documentElement; + + assert($root instanceof DOMElement); + + $source = $document->createElement('source'); + $root->appendChild($source); + + $xpath = new DOMXPath($document); + + foreach (['include', 'exclude'] as $element) { + $nodes = $xpath->query('//coverage/' . $element); + + assert($nodes !== false); + + foreach (SnapshotNodeList::fromNodeList($nodes) as $node) { + $source->appendChild($node); + } + } + + if ($coverage->childElementCount !== 0) { + return; + } + + assert($coverage->parentNode !== null); + + $coverage->parentNode->removeChild($coverage); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/MoveWhitelistExcludesToCoverage.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/MoveWhitelistExcludesToCoverage.php similarity index 91% rename from app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/MoveWhitelistExcludesToCoverage.php rename to app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/MoveWhitelistExcludesToCoverage.php index 17d5f4db0..09641bb13 100644 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/MoveWhitelistExcludesToCoverage.php +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/MoveWhitelistExcludesToCoverage.php @@ -13,12 +13,13 @@ use function in_array; use DOMDocument; use DOMElement; -use PHPUnit\Util\Xml\SnapshotNodeList; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ -final class MoveWhitelistExcludesToCoverage implements Migration +final readonly class MoveWhitelistExcludesToCoverage implements Migration { /** * @throws MigrationException diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/MoveWhitelistIncludesToCoverage.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/MoveWhitelistIncludesToCoverage.php similarity index 88% rename from app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/MoveWhitelistIncludesToCoverage.php rename to app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/MoveWhitelistIncludesToCoverage.php index c75a6d84f..999012496 100644 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/MoveWhitelistIncludesToCoverage.php +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/MoveWhitelistIncludesToCoverage.php @@ -11,12 +11,13 @@ use DOMDocument; use DOMElement; -use PHPUnit\Util\Xml\SnapshotNodeList; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ -final class MoveWhitelistIncludesToCoverage implements Migration +final readonly class MoveWhitelistIncludesToCoverage implements Migration { /** * @throws MigrationException diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveBeStrictAboutResourceUsageDuringSmallTestsAttribute.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveBeStrictAboutResourceUsageDuringSmallTestsAttribute.php new file mode 100644 index 000000000..cdb90775c --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveBeStrictAboutResourceUsageDuringSmallTestsAttribute.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function assert; +use DOMDocument; +use DOMElement; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RemoveBeStrictAboutResourceUsageDuringSmallTestsAttribute implements Migration +{ + public function migrate(DOMDocument $document): void + { + $root = $document->documentElement; + + assert($root instanceof DOMElement); + + if ($root->hasAttribute('beStrictAboutResourceUsageDuringSmallTests')) { + $root->removeAttribute('beStrictAboutResourceUsageDuringSmallTests'); + } + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveBeStrictAboutTodoAnnotatedTestsAttribute.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveBeStrictAboutTodoAnnotatedTestsAttribute.php new file mode 100644 index 000000000..1858e67f4 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveBeStrictAboutTodoAnnotatedTestsAttribute.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function assert; +use DOMDocument; +use DOMElement; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RemoveBeStrictAboutTodoAnnotatedTestsAttribute implements Migration +{ + public function migrate(DOMDocument $document): void + { + $root = $document->documentElement; + + assert($root instanceof DOMElement); + + if ($root->hasAttribute('beStrictAboutTodoAnnotatedTests')) { + $root->removeAttribute('beStrictAboutTodoAnnotatedTests'); + } + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveCacheResultFileAttribute.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveCacheResultFileAttribute.php new file mode 100644 index 000000000..a5c51c4eb --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveCacheResultFileAttribute.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function assert; +use DOMDocument; +use DOMElement; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RemoveCacheResultFileAttribute implements Migration +{ + public function migrate(DOMDocument $document): void + { + $root = $document->documentElement; + + assert($root instanceof DOMElement); + + if ($root->hasAttribute('cacheResultFile')) { + $root->removeAttribute('cacheResultFile'); + } + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveCacheTokensAttribute.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveCacheTokensAttribute.php new file mode 100644 index 000000000..69bf38a21 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveCacheTokensAttribute.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function assert; +use DOMDocument; +use DOMElement; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RemoveCacheTokensAttribute implements Migration +{ + public function migrate(DOMDocument $document): void + { + $root = $document->documentElement; + + assert($root instanceof DOMElement); + + if ($root->hasAttribute('cacheTokens')) { + $root->removeAttribute('cacheTokens'); + } + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveConversionToExceptionsAttributes.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveConversionToExceptionsAttributes.php new file mode 100644 index 000000000..a908aee38 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveConversionToExceptionsAttributes.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function assert; +use DOMDocument; +use DOMElement; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RemoveConversionToExceptionsAttributes implements Migration +{ + public function migrate(DOMDocument $document): void + { + $root = $document->documentElement; + + assert($root instanceof DOMElement); + + if ($root->hasAttribute('convertDeprecationsToExceptions')) { + $root->removeAttribute('convertDeprecationsToExceptions'); + } + + if ($root->hasAttribute('convertErrorsToExceptions')) { + $root->removeAttribute('convertErrorsToExceptions'); + } + + if ($root->hasAttribute('convertNoticesToExceptions')) { + $root->removeAttribute('convertNoticesToExceptions'); + } + + if ($root->hasAttribute('convertWarningsToExceptions')) { + $root->removeAttribute('convertWarningsToExceptions'); + } + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveCoverageElementCacheDirectoryAttribute.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveCoverageElementCacheDirectoryAttribute.php new file mode 100644 index 000000000..c26d2071c --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveCoverageElementCacheDirectoryAttribute.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 PHPUnit\TextUI\XmlConfiguration; + +use DOMDocument; +use DOMElement; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RemoveCoverageElementCacheDirectoryAttribute implements Migration +{ + public function migrate(DOMDocument $document): void + { + $node = $document->getElementsByTagName('coverage')->item(0); + + if (!$node instanceof DOMElement || $node->parentNode === null) { + return; + } + + if ($node->hasAttribute('cacheDirectory')) { + $node->removeAttribute('cacheDirectory'); + } + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveCoverageElementProcessUncoveredFilesAttribute.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveCoverageElementProcessUncoveredFilesAttribute.php new file mode 100644 index 000000000..347686251 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveCoverageElementProcessUncoveredFilesAttribute.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 PHPUnit\TextUI\XmlConfiguration; + +use DOMDocument; +use DOMElement; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RemoveCoverageElementProcessUncoveredFilesAttribute implements Migration +{ + public function migrate(DOMDocument $document): void + { + $node = $document->getElementsByTagName('coverage')->item(0); + + if (!$node instanceof DOMElement || $node->parentNode === null) { + return; + } + + if ($node->hasAttribute('processUncoveredFiles')) { + $node->removeAttribute('processUncoveredFiles'); + } + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/RemoveEmptyFilter.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveEmptyFilter.php similarity index 89% rename from app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/RemoveEmptyFilter.php rename to app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveEmptyFilter.php index 8f1a6d547..a831e2051 100644 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/RemoveEmptyFilter.php +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveEmptyFilter.php @@ -14,9 +14,11 @@ use DOMElement; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ -final class RemoveEmptyFilter implements Migration +final readonly class RemoveEmptyFilter implements Migration { /** * @throws MigrationException diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveListeners.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveListeners.php new file mode 100644 index 000000000..bf2889965 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveListeners.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 PHPUnit\TextUI\XmlConfiguration; + +use DOMDocument; +use DOMElement; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RemoveListeners implements Migration +{ + public function migrate(DOMDocument $document): void + { + $node = $document->getElementsByTagName('listeners')->item(0); + + if (!$node instanceof DOMElement || $node->parentNode === null) { + return; + } + + $node->parentNode->removeChild($node); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/RemoveLogTypes.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveLogTypes.php similarity index 86% rename from app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/RemoveLogTypes.php rename to app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveLogTypes.php index 962ff13c3..46ee55e92 100644 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/RemoveLogTypes.php +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveLogTypes.php @@ -12,12 +12,13 @@ use function assert; use DOMDocument; use DOMElement; -use PHPUnit\Util\Xml\SnapshotNodeList; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ -final class RemoveLogTypes implements Migration +final readonly class RemoveLogTypes implements Migration { public function migrate(DOMDocument $document): void { diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveLoggingElements.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveLoggingElements.php new file mode 100644 index 000000000..ccccb8199 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveLoggingElements.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function assert; +use DOMDocument; +use DOMElement; +use DOMXPath; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RemoveLoggingElements implements Migration +{ + public function migrate(DOMDocument $document): void + { + $this->removeTestDoxElement($document); + $this->removeTextElement($document); + } + + private function removeTestDoxElement(DOMDocument $document): void + { + $nodes = (new DOMXPath($document))->query('logging/testdoxXml'); + + assert($nodes !== false); + + $node = $nodes->item(0); + + if (!$node instanceof DOMElement || $node->parentNode === null) { + return; + } + + $node->parentNode->removeChild($node); + } + + private function removeTextElement(DOMDocument $document): void + { + $nodes = (new DOMXPath($document))->query('logging/text'); + + assert($nodes !== false); + + $node = $nodes->item(0); + + if (!$node instanceof DOMElement || $node->parentNode === null) { + return; + } + + $node->parentNode->removeChild($node); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveNoInteractionAttribute.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveNoInteractionAttribute.php new file mode 100644 index 000000000..897cccd81 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveNoInteractionAttribute.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function assert; +use DOMDocument; +use DOMElement; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RemoveNoInteractionAttribute implements Migration +{ + public function migrate(DOMDocument $document): void + { + $root = $document->documentElement; + + assert($root instanceof DOMElement); + + if ($root->hasAttribute('noInteraction')) { + $root->removeAttribute('noInteraction'); + } + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RemovePrinterAttributes.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RemovePrinterAttributes.php new file mode 100644 index 000000000..84f4bcf1a --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RemovePrinterAttributes.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 PHPUnit\TextUI\XmlConfiguration; + +use function assert; +use DOMDocument; +use DOMElement; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RemovePrinterAttributes implements Migration +{ + public function migrate(DOMDocument $document): void + { + $root = $document->documentElement; + + assert($root instanceof DOMElement); + + if ($root->hasAttribute('printerClass')) { + $root->removeAttribute('printerClass'); + } + + if ($root->hasAttribute('printerFile')) { + $root->removeAttribute('printerFile'); + } + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveRegisterMockObjectsFromTestArgumentsRecursivelyAttribute.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveRegisterMockObjectsFromTestArgumentsRecursivelyAttribute.php new file mode 100644 index 000000000..e2ff30514 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveRegisterMockObjectsFromTestArgumentsRecursivelyAttribute.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function assert; +use DOMDocument; +use DOMElement; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RemoveRegisterMockObjectsFromTestArgumentsRecursivelyAttribute implements Migration +{ + public function migrate(DOMDocument $document): void + { + $root = $document->documentElement; + + assert($root instanceof DOMElement); + + if ($root->hasAttribute('registerMockObjectsFromTestArgumentsRecursively')) { + $root->removeAttribute('registerMockObjectsFromTestArgumentsRecursively'); + } + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveTestDoxGroupsElement.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveTestDoxGroupsElement.php new file mode 100644 index 000000000..ea5a69217 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveTestDoxGroupsElement.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 PHPUnit\TextUI\XmlConfiguration; + +use DOMDocument; +use DOMElement; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RemoveTestDoxGroupsElement implements Migration +{ + public function migrate(DOMDocument $document): void + { + $node = $document->getElementsByTagName('testdoxGroups')->item(0); + + if (!$node instanceof DOMElement || $node->parentNode === null) { + return; + } + + $node->parentNode->removeChild($node); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveTestSuiteLoaderAttributes.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveTestSuiteLoaderAttributes.php new file mode 100644 index 000000000..284dda2ef --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveTestSuiteLoaderAttributes.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 PHPUnit\TextUI\XmlConfiguration; + +use function assert; +use DOMDocument; +use DOMElement; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RemoveTestSuiteLoaderAttributes implements Migration +{ + public function migrate(DOMDocument $document): void + { + $root = $document->documentElement; + + assert($root instanceof DOMElement); + + if ($root->hasAttribute('testSuiteLoaderClass')) { + $root->removeAttribute('testSuiteLoaderClass'); + } + + if ($root->hasAttribute('testSuiteLoaderFile')) { + $root->removeAttribute('testSuiteLoaderFile'); + } + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveVerboseAttribute.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveVerboseAttribute.php new file mode 100644 index 000000000..d4aa66087 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RemoveVerboseAttribute.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use function assert; +use DOMDocument; +use DOMElement; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RemoveVerboseAttribute implements Migration +{ + public function migrate(DOMDocument $document): void + { + $root = $document->documentElement; + + assert($root instanceof DOMElement); + + if ($root->hasAttribute('verbose')) { + $root->removeAttribute('verbose'); + } + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RenameBackupStaticAttributesAttribute.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RenameBackupStaticAttributesAttribute.php new file mode 100644 index 000000000..c2de95ce3 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RenameBackupStaticAttributesAttribute.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 PHPUnit\TextUI\XmlConfiguration; + +use function assert; +use DOMDocument; +use DOMElement; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RenameBackupStaticAttributesAttribute implements Migration +{ + public function migrate(DOMDocument $document): void + { + $root = $document->documentElement; + + assert($root instanceof DOMElement); + + if ($root->hasAttribute('backupStaticProperties')) { + return; + } + + if (!$root->hasAttribute('backupStaticAttributes')) { + return; + } + + $root->setAttribute('backupStaticProperties', $root->getAttribute('backupStaticAttributes')); + $root->removeAttribute('backupStaticAttributes'); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RenameBeStrictAboutCoversAnnotationAttribute.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RenameBeStrictAboutCoversAnnotationAttribute.php new file mode 100644 index 000000000..dda890b66 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RenameBeStrictAboutCoversAnnotationAttribute.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 PHPUnit\TextUI\XmlConfiguration; + +use function assert; +use DOMDocument; +use DOMElement; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RenameBeStrictAboutCoversAnnotationAttribute implements Migration +{ + public function migrate(DOMDocument $document): void + { + $root = $document->documentElement; + + assert($root instanceof DOMElement); + + if ($root->hasAttribute('beStrictAboutCoverageMetadata')) { + return; + } + + if (!$root->hasAttribute('beStrictAboutCoversAnnotation')) { + return; + } + + $root->setAttribute('beStrictAboutCoverageMetadata', $root->getAttribute('beStrictAboutCoversAnnotation')); + $root->removeAttribute('beStrictAboutCoversAnnotation'); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RenameForceCoversAnnotationAttribute.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RenameForceCoversAnnotationAttribute.php new file mode 100644 index 000000000..707aff8a3 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/RenameForceCoversAnnotationAttribute.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 PHPUnit\TextUI\XmlConfiguration; + +use function assert; +use DOMDocument; +use DOMElement; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class RenameForceCoversAnnotationAttribute implements Migration +{ + public function migrate(DOMDocument $document): void + { + $root = $document->documentElement; + + assert($root instanceof DOMElement); + + if ($root->hasAttribute('requireCoverageMetadata')) { + return; + } + + if (!$root->hasAttribute('forceCoversAnnotation')) { + return; + } + + $root->setAttribute('requireCoverageMetadata', $root->getAttribute('forceCoversAnnotation')); + $root->removeAttribute('forceCoversAnnotation'); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/ReplaceRestrictDeprecationsWithIgnoreDeprecations.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/ReplaceRestrictDeprecationsWithIgnoreDeprecations.php new file mode 100644 index 000000000..12cb1e7f0 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/ReplaceRestrictDeprecationsWithIgnoreDeprecations.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 PHPUnit\TextUI\XmlConfiguration; + +use function assert; +use DOMDocument; +use DOMElement; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ReplaceRestrictDeprecationsWithIgnoreDeprecations implements Migration +{ + /** + * @throws MigrationException + */ + public function migrate(DOMDocument $document): void + { + $source = $document->getElementsByTagName('source')->item(0); + + if ($source === null) { + return; + } + + assert($source instanceof DOMElement); + + if (!$source->hasAttribute('restrictDeprecations')) { + return; + } + + $restrictDeprecations = $source->getAttribute('restrictDeprecations') === 'true'; + + $source->removeAttribute('restrictDeprecations'); + + if (!$restrictDeprecations || + $source->hasAttribute('ignoreIndirectDeprecations')) { + return; + } + + $source->setAttribute('ignoreIndirectDeprecations', 'true'); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/UpdateSchemaLocation.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/UpdateSchemaLocation.php new file mode 100644 index 000000000..35c8abe81 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrations/UpdateSchemaLocation.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 PHPUnit\TextUI\XmlConfiguration; + +use function assert; +use function str_contains; +use DOMDocument; +use DOMElement; +use PHPUnit\Runner\Version; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class UpdateSchemaLocation implements Migration +{ + private const string NAMESPACE_URI = 'http://www.w3.org/2001/XMLSchema-instance'; + private const string LOCAL_NAME_SCHEMA_LOCATION = 'noNamespaceSchemaLocation'; + + public function migrate(DOMDocument $document): void + { + $root = $document->documentElement; + + assert($root instanceof DOMElement); + + $existingSchemaLocation = $root->getAttributeNodeNS(self::NAMESPACE_URI, self::LOCAL_NAME_SCHEMA_LOCATION)->value; + + if (str_contains($existingSchemaLocation, '://') === false) { // If the current schema location is a relative path, don't update it + return; + } + + $root->setAttributeNS( + self::NAMESPACE_URI, + 'xsi:' . self::LOCAL_NAME_SCHEMA_LOCATION, + 'https://schema.phpunit.de/' . Version::series() . '/phpunit.xsd', + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrator.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrator.php new file mode 100644 index 000000000..4649dec28 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/Migrator.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 PHPUnit\TextUI\XmlConfiguration; + +use function assert; +use PHPUnit\Runner\Version; +use PHPUnit\Util\Xml\Loader as XmlLoader; +use PHPUnit\Util\Xml\XmlException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Migrator +{ + /** + * @throws Exception + * @throws MigrationException + * @throws XmlException + */ + public function migrate(string $filename): string + { + $origin = (new SchemaDetector)->detect($filename); + + if (!$origin->detected()) { + throw new Exception('The file does not validate against any known schema'); + } + + if ($origin->version() === Version::series()) { + throw new Exception('The file does not need to be migrated'); + } + + $configurationDocument = (new XmlLoader)->loadFile($filename); + + foreach ((new MigrationBuilder)->build($origin->version()) as $migration) { + $migration->migrate($configurationDocument); + } + + $configurationDocument->formatOutput = true; + $configurationDocument->preserveWhiteSpace = false; + + $xml = $configurationDocument->saveXML(); + + assert($xml !== false); + + return $xml; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Util/Xml/SnapshotNodeList.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/SnapshotNodeList.php similarity index 75% rename from app/vendor/phpunit/phpunit/src/Util/Xml/SnapshotNodeList.php rename to app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/SnapshotNodeList.php index e383678df..491c24eda 100644 --- a/app/vendor/phpunit/phpunit/src/Util/Xml/SnapshotNodeList.php +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Migration/SnapshotNodeList.php @@ -7,7 +7,7 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Util\Xml; +namespace PHPUnit\TextUI\XmlConfiguration; use function count; use ArrayIterator; @@ -17,6 +17,8 @@ use IteratorAggregate; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit * * @template-implements IteratorAggregate @@ -24,10 +26,13 @@ final class SnapshotNodeList implements Countable, IteratorAggregate { /** - * @var DOMNode[] + * @var list */ - private $nodes = []; + private array $nodes = []; + /** + * @param DOMNodeList $list + */ public static function fromNodeList(DOMNodeList $list): self { $snapshot = new self; @@ -44,6 +49,9 @@ public function count(): int return count($this->nodes); } + /** + * @return ArrayIterator + */ public function getIterator(): ArrayIterator { return new ArrayIterator($this->nodes); diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/PHPUnit.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/PHPUnit.php new file mode 100644 index 000000000..0b384e739 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/PHPUnit.php @@ -0,0 +1,530 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class PHPUnit +{ + private ?string $cacheDirectory; + private bool $cacheResult; + private int|string $columns; + private string $colors; + private bool $stderr; + private bool $displayDetailsOnAllIssues; + private bool $displayDetailsOnIncompleteTests; + private bool $displayDetailsOnSkippedTests; + private bool $displayDetailsOnTestsThatTriggerDeprecations; + private bool $displayDetailsOnPhpunitDeprecations; + private bool $displayDetailsOnPhpunitNotices; + private bool $displayDetailsOnTestsThatTriggerErrors; + private bool $displayDetailsOnTestsThatTriggerNotices; + private bool $displayDetailsOnTestsThatTriggerWarnings; + private bool $reverseDefectList; + private bool $requireCoverageMetadata; + private ?string $bootstrap; + + /** + * @var array + */ + private array $bootstrapForTestSuite; + private bool $processIsolation; + private bool $failOnAllIssues; + private bool $failOnDeprecation; + private bool $failOnPhpunitDeprecation; + private bool $failOnPhpunitNotice; + private bool $failOnPhpunitWarning; + private bool $failOnEmptyTestSuite; + private bool $failOnIncomplete; + private bool $failOnNotice; + private bool $failOnRisky; + private bool $failOnSkipped; + private bool $failOnWarning; + private bool $stopOnDefect; + private bool $stopOnDeprecation; + private bool $stopOnError; + private bool $stopOnFailure; + private bool $stopOnIncomplete; + private bool $stopOnNotice; + private bool $stopOnRisky; + private bool $stopOnSkipped; + private bool $stopOnWarning; + + /** + * @var ?non-empty-string + */ + private ?string $extensionsDirectory; + private bool $beStrictAboutChangesToGlobalState; + private bool $beStrictAboutOutputDuringTests; + private bool $beStrictAboutTestsThatDoNotTestAnything; + private bool $beStrictAboutCoverageMetadata; + private bool $enforceTimeLimit; + private int $defaultTimeLimit; + private int $timeoutForSmallTests; + private int $timeoutForMediumTests; + private int $timeoutForLargeTests; + private ?string $defaultTestSuite; + private int $executionOrder; + private bool $resolveDependencies; + private bool $defectsFirst; + private bool $backupGlobals; + private bool $backupStaticProperties; + private bool $testdoxPrinter; + private bool $testdoxPrinterSummary; + private bool $controlGarbageCollector; + private int $numberOfTestsBeforeGarbageCollection; + + /** + * @var non-negative-int + */ + private int $shortenArraysForExportThreshold; + + /** + * @param array $bootstrapForTestSuite + * @param ?non-empty-string $extensionsDirectory + * @param non-negative-int $shortenArraysForExportThreshold + */ + public function __construct(?string $cacheDirectory, bool $cacheResult, int|string $columns, string $colors, bool $stderr, bool $displayDetailsOnAllIssues, bool $displayDetailsOnIncompleteTests, bool $displayDetailsOnSkippedTests, bool $displayDetailsOnTestsThatTriggerDeprecations, bool $displayDetailsOnPhpunitDeprecations, bool $displayDetailsOnPhpunitNotices, bool $displayDetailsOnTestsThatTriggerErrors, bool $displayDetailsOnTestsThatTriggerNotices, bool $displayDetailsOnTestsThatTriggerWarnings, bool $reverseDefectList, bool $requireCoverageMetadata, ?string $bootstrap, array $bootstrapForTestSuite, bool $processIsolation, bool $failOnAllIssues, bool $failOnDeprecation, bool $failOnPhpunitDeprecation, bool $failOnPhpunitNotice, bool $failOnPhpunitWarning, bool $failOnEmptyTestSuite, bool $failOnIncomplete, bool $failOnNotice, bool $failOnRisky, bool $failOnSkipped, bool $failOnWarning, bool $stopOnDefect, bool $stopOnDeprecation, bool $stopOnError, bool $stopOnFailure, bool $stopOnIncomplete, bool $stopOnNotice, bool $stopOnRisky, bool $stopOnSkipped, bool $stopOnWarning, ?string $extensionsDirectory, bool $beStrictAboutChangesToGlobalState, bool $beStrictAboutOutputDuringTests, bool $beStrictAboutTestsThatDoNotTestAnything, bool $beStrictAboutCoverageMetadata, bool $enforceTimeLimit, int $defaultTimeLimit, int $timeoutForSmallTests, int $timeoutForMediumTests, int $timeoutForLargeTests, ?string $defaultTestSuite, int $executionOrder, bool $resolveDependencies, bool $defectsFirst, bool $backupGlobals, bool $backupStaticProperties, bool $testdoxPrinter, bool $testdoxPrinterSummary, bool $controlGarbageCollector, int $numberOfTestsBeforeGarbageCollection, int $shortenArraysForExportThreshold) + { + $this->cacheDirectory = $cacheDirectory; + $this->cacheResult = $cacheResult; + $this->columns = $columns; + $this->colors = $colors; + $this->stderr = $stderr; + $this->displayDetailsOnAllIssues = $displayDetailsOnAllIssues; + $this->displayDetailsOnIncompleteTests = $displayDetailsOnIncompleteTests; + $this->displayDetailsOnSkippedTests = $displayDetailsOnSkippedTests; + $this->displayDetailsOnTestsThatTriggerDeprecations = $displayDetailsOnTestsThatTriggerDeprecations; + $this->displayDetailsOnPhpunitDeprecations = $displayDetailsOnPhpunitDeprecations; + $this->displayDetailsOnPhpunitNotices = $displayDetailsOnPhpunitNotices; + $this->displayDetailsOnTestsThatTriggerErrors = $displayDetailsOnTestsThatTriggerErrors; + $this->displayDetailsOnTestsThatTriggerNotices = $displayDetailsOnTestsThatTriggerNotices; + $this->displayDetailsOnTestsThatTriggerWarnings = $displayDetailsOnTestsThatTriggerWarnings; + $this->reverseDefectList = $reverseDefectList; + $this->requireCoverageMetadata = $requireCoverageMetadata; + $this->bootstrap = $bootstrap; + $this->bootstrapForTestSuite = $bootstrapForTestSuite; + $this->processIsolation = $processIsolation; + $this->failOnAllIssues = $failOnAllIssues; + $this->failOnDeprecation = $failOnDeprecation; + $this->failOnPhpunitDeprecation = $failOnPhpunitDeprecation; + $this->failOnPhpunitNotice = $failOnPhpunitNotice; + $this->failOnPhpunitWarning = $failOnPhpunitWarning; + $this->failOnEmptyTestSuite = $failOnEmptyTestSuite; + $this->failOnIncomplete = $failOnIncomplete; + $this->failOnNotice = $failOnNotice; + $this->failOnRisky = $failOnRisky; + $this->failOnSkipped = $failOnSkipped; + $this->failOnWarning = $failOnWarning; + $this->stopOnDefect = $stopOnDefect; + $this->stopOnDeprecation = $stopOnDeprecation; + $this->stopOnError = $stopOnError; + $this->stopOnFailure = $stopOnFailure; + $this->stopOnIncomplete = $stopOnIncomplete; + $this->stopOnNotice = $stopOnNotice; + $this->stopOnRisky = $stopOnRisky; + $this->stopOnSkipped = $stopOnSkipped; + $this->stopOnWarning = $stopOnWarning; + $this->extensionsDirectory = $extensionsDirectory; + $this->beStrictAboutChangesToGlobalState = $beStrictAboutChangesToGlobalState; + $this->beStrictAboutOutputDuringTests = $beStrictAboutOutputDuringTests; + $this->beStrictAboutTestsThatDoNotTestAnything = $beStrictAboutTestsThatDoNotTestAnything; + $this->beStrictAboutCoverageMetadata = $beStrictAboutCoverageMetadata; + $this->enforceTimeLimit = $enforceTimeLimit; + $this->defaultTimeLimit = $defaultTimeLimit; + $this->timeoutForSmallTests = $timeoutForSmallTests; + $this->timeoutForMediumTests = $timeoutForMediumTests; + $this->timeoutForLargeTests = $timeoutForLargeTests; + $this->defaultTestSuite = $defaultTestSuite; + $this->executionOrder = $executionOrder; + $this->resolveDependencies = $resolveDependencies; + $this->defectsFirst = $defectsFirst; + $this->backupGlobals = $backupGlobals; + $this->backupStaticProperties = $backupStaticProperties; + $this->testdoxPrinter = $testdoxPrinter; + $this->testdoxPrinterSummary = $testdoxPrinterSummary; + $this->controlGarbageCollector = $controlGarbageCollector; + $this->numberOfTestsBeforeGarbageCollection = $numberOfTestsBeforeGarbageCollection; + $this->shortenArraysForExportThreshold = $shortenArraysForExportThreshold; + } + + /** + * @phpstan-assert-if-true !null $this->cacheDirectory + */ + public function hasCacheDirectory(): bool + { + return $this->cacheDirectory !== null; + } + + /** + * @throws Exception + */ + public function cacheDirectory(): string + { + if (!$this->hasCacheDirectory()) { + throw new Exception('Cache directory is not configured'); + } + + return $this->cacheDirectory; + } + + public function cacheResult(): bool + { + return $this->cacheResult; + } + + public function columns(): int|string + { + return $this->columns; + } + + public function colors(): string + { + return $this->colors; + } + + public function stderr(): bool + { + return $this->stderr; + } + + public function displayDetailsOnAllIssues(): bool + { + return $this->displayDetailsOnAllIssues; + } + + public function displayDetailsOnIncompleteTests(): bool + { + return $this->displayDetailsOnIncompleteTests; + } + + public function displayDetailsOnSkippedTests(): bool + { + return $this->displayDetailsOnSkippedTests; + } + + public function displayDetailsOnTestsThatTriggerDeprecations(): bool + { + return $this->displayDetailsOnTestsThatTriggerDeprecations; + } + + public function displayDetailsOnPhpunitDeprecations(): bool + { + return $this->displayDetailsOnPhpunitDeprecations; + } + + public function displayDetailsOnPhpunitNotices(): bool + { + return $this->displayDetailsOnPhpunitNotices; + } + + public function displayDetailsOnTestsThatTriggerErrors(): bool + { + return $this->displayDetailsOnTestsThatTriggerErrors; + } + + public function displayDetailsOnTestsThatTriggerNotices(): bool + { + return $this->displayDetailsOnTestsThatTriggerNotices; + } + + public function displayDetailsOnTestsThatTriggerWarnings(): bool + { + return $this->displayDetailsOnTestsThatTriggerWarnings; + } + + public function reverseDefectList(): bool + { + return $this->reverseDefectList; + } + + public function requireCoverageMetadata(): bool + { + return $this->requireCoverageMetadata; + } + + /** + * @phpstan-assert-if-true !null $this->bootstrap + */ + public function hasBootstrap(): bool + { + return $this->bootstrap !== null; + } + + /** + * @throws Exception + */ + public function bootstrap(): string + { + if (!$this->hasBootstrap()) { + throw new Exception('Bootstrap script is not configured'); + } + + return $this->bootstrap; + } + + /** + * @return array + */ + public function bootstrapForTestSuite(): array + { + return $this->bootstrapForTestSuite; + } + + public function processIsolation(): bool + { + return $this->processIsolation; + } + + public function failOnAllIssues(): bool + { + return $this->failOnAllIssues; + } + + public function failOnDeprecation(): bool + { + return $this->failOnDeprecation; + } + + public function failOnPhpunitDeprecation(): bool + { + return $this->failOnPhpunitDeprecation; + } + + public function failOnPhpunitNotice(): bool + { + return $this->failOnPhpunitNotice; + } + + public function failOnPhpunitWarning(): bool + { + return $this->failOnPhpunitWarning; + } + + public function failOnEmptyTestSuite(): bool + { + return $this->failOnEmptyTestSuite; + } + + public function failOnIncomplete(): bool + { + return $this->failOnIncomplete; + } + + public function failOnNotice(): bool + { + return $this->failOnNotice; + } + + public function failOnRisky(): bool + { + return $this->failOnRisky; + } + + public function failOnSkipped(): bool + { + return $this->failOnSkipped; + } + + public function failOnWarning(): bool + { + return $this->failOnWarning; + } + + public function stopOnDefect(): bool + { + return $this->stopOnDefect; + } + + public function stopOnDeprecation(): bool + { + return $this->stopOnDeprecation; + } + + public function stopOnError(): bool + { + return $this->stopOnError; + } + + public function stopOnFailure(): bool + { + return $this->stopOnFailure; + } + + public function stopOnIncomplete(): bool + { + return $this->stopOnIncomplete; + } + + public function stopOnNotice(): bool + { + return $this->stopOnNotice; + } + + public function stopOnRisky(): bool + { + return $this->stopOnRisky; + } + + public function stopOnSkipped(): bool + { + return $this->stopOnSkipped; + } + + public function stopOnWarning(): bool + { + return $this->stopOnWarning; + } + + /** + * @phpstan-assert-if-true !null $this->extensionsDirectory + */ + public function hasExtensionsDirectory(): bool + { + return $this->extensionsDirectory !== null; + } + + /** + * @throws Exception + * + * @return non-empty-string + */ + public function extensionsDirectory(): string + { + if (!$this->hasExtensionsDirectory()) { + throw new Exception('Extensions directory is not configured'); + } + + return $this->extensionsDirectory; + } + + public function beStrictAboutChangesToGlobalState(): bool + { + return $this->beStrictAboutChangesToGlobalState; + } + + public function beStrictAboutOutputDuringTests(): bool + { + return $this->beStrictAboutOutputDuringTests; + } + + public function beStrictAboutTestsThatDoNotTestAnything(): bool + { + return $this->beStrictAboutTestsThatDoNotTestAnything; + } + + public function beStrictAboutCoverageMetadata(): bool + { + return $this->beStrictAboutCoverageMetadata; + } + + public function enforceTimeLimit(): bool + { + return $this->enforceTimeLimit; + } + + public function defaultTimeLimit(): int + { + return $this->defaultTimeLimit; + } + + public function timeoutForSmallTests(): int + { + return $this->timeoutForSmallTests; + } + + public function timeoutForMediumTests(): int + { + return $this->timeoutForMediumTests; + } + + public function timeoutForLargeTests(): int + { + return $this->timeoutForLargeTests; + } + + /** + * @phpstan-assert-if-true !null $this->defaultTestSuite + */ + public function hasDefaultTestSuite(): bool + { + return $this->defaultTestSuite !== null; + } + + /** + * @throws Exception + */ + public function defaultTestSuite(): string + { + if (!$this->hasDefaultTestSuite()) { + throw new Exception('Default test suite is not configured'); + } + + return $this->defaultTestSuite; + } + + public function executionOrder(): int + { + return $this->executionOrder; + } + + public function resolveDependencies(): bool + { + return $this->resolveDependencies; + } + + public function defectsFirst(): bool + { + return $this->defectsFirst; + } + + public function backupGlobals(): bool + { + return $this->backupGlobals; + } + + public function backupStaticProperties(): bool + { + return $this->backupStaticProperties; + } + + public function testdoxPrinter(): bool + { + return $this->testdoxPrinter; + } + + public function testdoxPrinterSummary(): bool + { + return $this->testdoxPrinterSummary; + } + + public function controlGarbageCollector(): bool + { + return $this->controlGarbageCollector; + } + + public function numberOfTestsBeforeGarbageCollection(): int + { + return $this->numberOfTestsBeforeGarbageCollection; + } + + /** + * @return non-negative-int + */ + public function shortenArraysForExportThreshold(): int + { + return $this->shortenArraysForExportThreshold; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/SchemaDetector/FailedSchemaDetectionResult.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/SchemaDetector/FailedSchemaDetectionResult.php new file mode 100644 index 000000000..5bd282c8c --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/SchemaDetector/FailedSchemaDetectionResult.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 PHPUnit\TextUI\XmlConfiguration; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class FailedSchemaDetectionResult extends SchemaDetectionResult +{ +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/SchemaDetector/SchemaDetectionResult.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/SchemaDetector/SchemaDetectionResult.php new file mode 100644 index 000000000..aa855b0cb --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/SchemaDetector/SchemaDetectionResult.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 PHPUnit\TextUI\XmlConfiguration; + +use PHPUnit\Util\Xml\XmlException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +abstract readonly class SchemaDetectionResult +{ + /** + * @phpstan-assert-if-true SuccessfulSchemaDetectionResult $this + */ + public function detected(): bool + { + return false; + } + + /** + * @throws XmlException + */ + public function version(): string + { + throw new XmlException('No supported schema was detected'); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/SchemaDetector/SchemaDetector.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/SchemaDetector/SchemaDetector.php new file mode 100644 index 000000000..5f55f5f20 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/SchemaDetector/SchemaDetector.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 PHPUnit\TextUI\XmlConfiguration; + +use PHPUnit\Util\Xml\Loader; +use PHPUnit\Util\Xml\XmlException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class SchemaDetector +{ + /** + * @throws XmlException + */ + public function detect(string $filename): SchemaDetectionResult + { + $document = (new Loader)->loadFile($filename); + + $schemaFinder = new SchemaFinder; + + foreach ($schemaFinder->available() as $candidate) { + $schema = (new SchemaFinder)->find($candidate); + + if (!(new Validator)->validate($document, $schema)->hasValidationErrors()) { + return new SuccessfulSchemaDetectionResult($candidate); + } + } + + return new FailedSchemaDetectionResult; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/SchemaDetector/SuccessfulSchemaDetectionResult.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/SchemaDetector/SuccessfulSchemaDetectionResult.php new file mode 100644 index 000000000..18658f87f --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/SchemaDetector/SuccessfulSchemaDetectionResult.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 PHPUnit\TextUI\XmlConfiguration; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class SuccessfulSchemaDetectionResult extends SchemaDetectionResult +{ + /** + * @var non-empty-string + */ + private string $version; + + /** + * @param non-empty-string $version + */ + public function __construct(string $version) + { + $this->version = $version; + } + + public function detected(): bool + { + return true; + } + + /** + * @return non-empty-string + */ + public function version(): string + { + return $this->version; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Util/Xml/SchemaFinder.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/SchemaFinder.php similarity index 79% rename from app/vendor/phpunit/phpunit/src/Util/Xml/SchemaFinder.php rename to app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/SchemaFinder.php index eb5f4f155..39d25cfad 100644 --- a/app/vendor/phpunit/phpunit/src/Util/Xml/SchemaFinder.php +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/SchemaFinder.php @@ -7,7 +7,7 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace PHPUnit\Util\Xml; +namespace PHPUnit\TextUI\XmlConfiguration; use function assert; use function defined; @@ -18,12 +18,14 @@ use PHPUnit\Runner\Version; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ -final class SchemaFinder +final readonly class SchemaFinder { /** - * @psalm-return non-empty-list + * @return non-empty-list */ public function available(): array { @@ -36,7 +38,7 @@ public function available(): array $version = $file->getBasename('.xsd'); - assert(!empty($version)); + assert($version !== ''); $result[] = $version; } @@ -47,7 +49,7 @@ public function available(): array } /** - * @throws Exception + * @throws CannotFindSchemaException */ public function find(string $version): string { @@ -58,7 +60,7 @@ public function find(string $version): string } if (!is_file($filename)) { - throw new Exception( + throw new CannotFindSchemaException( sprintf( 'Schema for PHPUnit %s is not available', $version, @@ -75,6 +77,6 @@ private function path(): string return __PHPUNIT_PHAR_ROOT__ . '/'; } - return __DIR__ . '/../../../'; + return __DIR__ . '/../../../../'; } } diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/TestSuiteMapper.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/TestSuiteMapper.php new file mode 100644 index 000000000..66bd611c1 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/TestSuiteMapper.php @@ -0,0 +1,150 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\XmlConfiguration; + +use const PHP_VERSION; +use function in_array; +use function is_dir; +use function is_file; +use function sprintf; +use function str_contains; +use function version_compare; +use PHPUnit\Event\Facade as EventFacade; +use PHPUnit\Framework\Exception as FrameworkException; +use PHPUnit\Framework\TestSuite as TestSuiteObject; +use PHPUnit\TextUI\Configuration\TestSuiteCollection; +use PHPUnit\TextUI\RuntimeException; +use PHPUnit\TextUI\TestDirectoryNotFoundException; +use PHPUnit\TextUI\TestFileNotFoundException; +use SebastianBergmann\FileIterator\Facade; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestSuiteMapper +{ + /** + * @param non-empty-string $xmlConfigurationFile + * @param list $includeTestSuites + * @param list $excludeTestSuites + * + * @throws RuntimeException + * @throws TestDirectoryNotFoundException + * @throws TestFileNotFoundException + */ + public function map(string $xmlConfigurationFile, TestSuiteCollection $configuredTestSuites, array $includeTestSuites, array $excludeTestSuites): TestSuiteObject + { + try { + $result = TestSuiteObject::empty($xmlConfigurationFile); + $processed = []; + + foreach ($configuredTestSuites as $configuredTestSuite) { + if ($includeTestSuites !== [] && !in_array($configuredTestSuite->name(), $includeTestSuites, true)) { + continue; + } + + if ($excludeTestSuites !== [] && in_array($configuredTestSuite->name(), $excludeTestSuites, true)) { + continue; + } + + $testSuiteName = $configuredTestSuite->name(); + $exclude = []; + + foreach ($configuredTestSuite->exclude()->asArray() as $file) { + $exclude[] = $file->path(); + } + + $testSuite = TestSuiteObject::empty($configuredTestSuite->name()); + $empty = true; + + foreach ($configuredTestSuite->directories() as $directory) { + if (!str_contains($directory->path(), '*') && !is_dir($directory->path())) { + throw new TestDirectoryNotFoundException($directory->path()); + } + + if (!version_compare(PHP_VERSION, $directory->phpVersion(), $directory->phpVersionOperator()->asString())) { + continue; + } + + $files = (new Facade)->getFilesAsArray( + $directory->path(), + $directory->suffix(), + $directory->prefix(), + $exclude, + ); + + $groups = $directory->groups(); + + foreach ($files as $file) { + if (isset($processed[$file])) { + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + 'Cannot add file %s to test suite "%s" as it was already added to test suite "%s"', + $file, + $testSuiteName, + $processed[$file], + ), + ); + + continue; + } + + $processed[$file] = $testSuiteName; + $empty = false; + + $testSuite->addTestFile($file, $groups); + } + } + + foreach ($configuredTestSuite->files() as $file) { + if (!is_file($file->path())) { + throw new TestFileNotFoundException($file->path()); + } + + if (!version_compare(PHP_VERSION, $file->phpVersion(), $file->phpVersionOperator()->asString())) { + continue; + } + + if (isset($processed[$file->path()])) { + EventFacade::emitter()->testRunnerTriggeredPhpunitWarning( + sprintf( + 'Cannot add file %s to test suite "%s" as it was already added to test suite "%s"', + $file->path(), + $testSuiteName, + $processed[$file->path()], + ), + ); + + continue; + } + + $processed[$file->path()] = $testSuiteName; + $empty = false; + + $testSuite->addTestFile($file->path(), $file->groups()); + } + + if (!$empty) { + $result->addTest($testSuite); + } + } + + return $result; + } catch (FrameworkException $e) { + throw new RuntimeException( + $e->getMessage(), + $e->getCode(), + $e, + ); + } + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Validator/ValidationResult.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Validator/ValidationResult.php new file mode 100644 index 000000000..95fe473d6 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Validator/ValidationResult.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 PHPUnit\TextUI\XmlConfiguration; + +use const PHP_EOL; +use function sprintf; +use function trim; +use LibXMLError; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @immutable + */ +final readonly class ValidationResult +{ + /** + * @var array> + */ + private array $validationErrors; + + /** + * @param array $errors + */ + public static function fromArray(array $errors): self + { + $validationErrors = []; + + foreach ($errors as $error) { + if (!isset($validationErrors[$error->line])) { + $validationErrors[$error->line] = []; + } + + $validationErrors[$error->line][] = trim($error->message); + } + + return new self($validationErrors); + } + + /** + * @param array> $validationErrors + */ + private function __construct(array $validationErrors) + { + $this->validationErrors = $validationErrors; + } + + public function hasValidationErrors(): bool + { + return $this->validationErrors !== []; + } + + public function asString(): string + { + $buffer = ''; + + foreach ($this->validationErrors as $line => $validationErrorsOnLine) { + $buffer .= sprintf(PHP_EOL . ' Line %d:' . PHP_EOL, $line); + + foreach ($validationErrorsOnLine as $validationError) { + $buffer .= sprintf(' - %s' . PHP_EOL, $validationError); + } + } + + return $buffer; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Validator/Validator.php b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Validator/Validator.php new file mode 100644 index 000000000..cc3a93dd2 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Validator/Validator.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 PHPUnit\TextUI\XmlConfiguration; + +use function assert; +use function file_get_contents; +use function libxml_clear_errors; +use function libxml_get_errors; +use function libxml_use_internal_errors; +use DOMDocument; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Validator +{ + public function validate(DOMDocument $document, string $xsdFilename): ValidationResult + { + $buffer = file_get_contents($xsdFilename); + + assert($buffer !== false); + + $originalErrorHandling = libxml_use_internal_errors(true); + + $document->schemaValidateSource($buffer); + + $errors = libxml_get_errors(); + libxml_clear_errors(); + libxml_use_internal_errors($originalErrorHandling); + + return ValidationResult::fromArray($errors); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/DefaultResultPrinter.php b/app/vendor/phpunit/phpunit/src/TextUI/DefaultResultPrinter.php deleted file mode 100644 index 09de8588a..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/DefaultResultPrinter.php +++ /dev/null @@ -1,585 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI; - -use const PHP_EOL; -use function array_map; -use function array_reverse; -use function count; -use function floor; -use function implode; -use function in_array; -use function is_int; -use function max; -use function preg_split; -use function sprintf; -use function str_pad; -use function str_repeat; -use function strlen; -use function trim; -use function vsprintf; -use PHPUnit\Framework\AssertionFailedError; -use PHPUnit\Framework\Exception; -use PHPUnit\Framework\InvalidArgumentException; -use PHPUnit\Framework\Test; -use PHPUnit\Framework\TestCase; -use PHPUnit\Framework\TestFailure; -use PHPUnit\Framework\TestResult; -use PHPUnit\Framework\TestSuite; -use PHPUnit\Framework\Warning; -use PHPUnit\Runner\PhptTestCase; -use PHPUnit\Util\Color; -use PHPUnit\Util\Printer; -use SebastianBergmann\Environment\Console; -use SebastianBergmann\Timer\ResourceUsageFormatter; -use SebastianBergmann\Timer\Timer; -use Throwable; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -class DefaultResultPrinter extends Printer implements ResultPrinter -{ - public const EVENT_TEST_START = 0; - public const EVENT_TEST_END = 1; - public const EVENT_TESTSUITE_START = 2; - public const EVENT_TESTSUITE_END = 3; - public const COLOR_NEVER = 'never'; - public const COLOR_AUTO = 'auto'; - public const COLOR_ALWAYS = 'always'; - public const COLOR_DEFAULT = self::COLOR_NEVER; - private const AVAILABLE_COLORS = [self::COLOR_NEVER, self::COLOR_AUTO, self::COLOR_ALWAYS]; - - /** - * @var int - */ - protected $column = 0; - - /** - * @var int - */ - protected $maxColumn; - - /** - * @var bool - */ - protected $lastTestFailed = false; - - /** - * @var int - */ - protected $numAssertions = 0; - - /** - * @var int - */ - protected $numTests = -1; - - /** - * @var int - */ - protected $numTestsRun = 0; - - /** - * @var int - */ - protected $numTestsWidth; - - /** - * @var bool - */ - protected $colors = false; - - /** - * @var bool - */ - protected $debug = false; - - /** - * @var bool - */ - protected $verbose = false; - - /** - * @var int - */ - private $numberOfColumns; - - /** - * @var bool - */ - private $reverse; - - /** - * @var bool - */ - private $defectListPrinted = false; - - /** - * @var Timer - */ - private $timer; - - /** - * Constructor. - * - * @param null|resource|string $out - * @param int|string $numberOfColumns - * - * @throws Exception - */ - public function __construct($out = null, bool $verbose = false, string $colors = self::COLOR_DEFAULT, bool $debug = false, $numberOfColumns = 80, bool $reverse = false) - { - parent::__construct($out); - - if (!in_array($colors, self::AVAILABLE_COLORS, true)) { - throw InvalidArgumentException::create( - 3, - vsprintf('value from "%s", "%s" or "%s"', self::AVAILABLE_COLORS), - ); - } - - if (!is_int($numberOfColumns) && $numberOfColumns !== 'max') { - throw InvalidArgumentException::create(5, 'integer or "max"'); - } - - $console = new Console; - $maxNumberOfColumns = $console->getNumberOfColumns(); - - if ($numberOfColumns === 'max' || ($numberOfColumns !== 80 && $numberOfColumns > $maxNumberOfColumns)) { - $numberOfColumns = $maxNumberOfColumns; - } - - $this->numberOfColumns = $numberOfColumns; - $this->verbose = $verbose; - $this->debug = $debug; - $this->reverse = $reverse; - - if ($colors === self::COLOR_AUTO && $console->hasColorSupport()) { - $this->colors = true; - } else { - $this->colors = (self::COLOR_ALWAYS === $colors); - } - - $this->timer = new Timer; - - $this->timer->start(); - } - - public function printResult(TestResult $result): void - { - $this->printHeader($result); - $this->printErrors($result); - $this->printWarnings($result); - $this->printFailures($result); - $this->printRisky($result); - - if ($this->verbose) { - $this->printIncompletes($result); - $this->printSkipped($result); - } - - $this->printFooter($result); - } - - /** - * An error occurred. - */ - public function addError(Test $test, Throwable $t, float $time): void - { - $this->writeProgressWithColor('fg-red, bold', 'E'); - $this->lastTestFailed = true; - } - - /** - * A failure occurred. - */ - public function addFailure(Test $test, AssertionFailedError $e, float $time): void - { - $this->writeProgressWithColor('bg-red, fg-white', 'F'); - $this->lastTestFailed = true; - } - - /** - * A warning occurred. - */ - public function addWarning(Test $test, Warning $e, float $time): void - { - $this->writeProgressWithColor('fg-yellow, bold', 'W'); - $this->lastTestFailed = true; - } - - /** - * Incomplete test. - */ - public function addIncompleteTest(Test $test, Throwable $t, float $time): void - { - $this->writeProgressWithColor('fg-yellow, bold', 'I'); - $this->lastTestFailed = true; - } - - /** - * Risky test. - */ - public function addRiskyTest(Test $test, Throwable $t, float $time): void - { - $this->writeProgressWithColor('fg-yellow, bold', 'R'); - $this->lastTestFailed = true; - } - - /** - * Skipped test. - */ - public function addSkippedTest(Test $test, Throwable $t, float $time): void - { - $this->writeProgressWithColor('fg-cyan, bold', 'S'); - $this->lastTestFailed = true; - } - - /** - * A testsuite started. - */ - public function startTestSuite(TestSuite $suite): void - { - if ($this->numTests == -1) { - $this->numTests = count($suite); - $this->numTestsWidth = strlen((string) $this->numTests); - $this->maxColumn = $this->numberOfColumns - strlen(' / (XXX%)') - (2 * $this->numTestsWidth); - } - } - - /** - * A testsuite ended. - */ - public function endTestSuite(TestSuite $suite): void - { - } - - /** - * A test started. - */ - public function startTest(Test $test): void - { - if ($this->debug) { - $this->write( - sprintf( - "Test '%s' started\n", - \PHPUnit\Util\Test::describeAsString($test), - ), - ); - } - } - - /** - * A test ended. - */ - public function endTest(Test $test, float $time): void - { - if ($this->debug) { - $this->write( - sprintf( - "Test '%s' ended\n", - \PHPUnit\Util\Test::describeAsString($test), - ), - ); - } - - if (!$this->lastTestFailed) { - $this->writeProgress('.'); - } - - if ($test instanceof TestCase) { - $this->numAssertions += $test->getNumAssertions(); - } elseif ($test instanceof PhptTestCase) { - $this->numAssertions++; - } - - $this->lastTestFailed = false; - - if ($test instanceof TestCase && !$test->hasExpectationOnOutput()) { - $this->write($test->getActualOutput()); - } - } - - protected function printDefects(array $defects, string $type): void - { - $count = count($defects); - - if ($count == 0) { - return; - } - - if ($this->defectListPrinted) { - $this->write("\n--\n\n"); - } - - $this->write( - sprintf( - "There %s %d %s%s:\n", - ($count == 1) ? 'was' : 'were', - $count, - $type, - ($count == 1) ? '' : 's', - ), - ); - - $i = 1; - - if ($this->reverse) { - $defects = array_reverse($defects); - } - - foreach ($defects as $defect) { - $this->printDefect($defect, $i++); - } - - $this->defectListPrinted = true; - } - - protected function printDefect(TestFailure $defect, int $count): void - { - $this->printDefectHeader($defect, $count); - $this->printDefectTrace($defect); - } - - protected function printDefectHeader(TestFailure $defect, int $count): void - { - $this->write( - sprintf( - "\n%d) %s\n", - $count, - $defect->getTestName(), - ), - ); - } - - protected function printDefectTrace(TestFailure $defect): void - { - $e = $defect->thrownException(); - - $this->write((string) $e); - - while ($e = $e->getPrevious()) { - $this->write("\nCaused by\n" . trim((string) $e) . "\n"); - } - } - - protected function printErrors(TestResult $result): void - { - $this->printDefects($result->errors(), 'error'); - } - - protected function printFailures(TestResult $result): void - { - $this->printDefects($result->failures(), 'failure'); - } - - protected function printWarnings(TestResult $result): void - { - $this->printDefects($result->warnings(), 'warning'); - } - - protected function printIncompletes(TestResult $result): void - { - $this->printDefects($result->notImplemented(), 'incomplete test'); - } - - protected function printRisky(TestResult $result): void - { - $this->printDefects($result->risky(), 'risky test'); - } - - protected function printSkipped(TestResult $result): void - { - $this->printDefects($result->skipped(), 'skipped test'); - } - - protected function printHeader(TestResult $result): void - { - if (count($result) > 0) { - $this->write(PHP_EOL . PHP_EOL . (new ResourceUsageFormatter)->resourceUsage($this->timer->stop()) . PHP_EOL . PHP_EOL); - } - } - - protected function printFooter(TestResult $result): void - { - if (count($result) === 0) { - $this->writeWithColor( - 'fg-black, bg-yellow', - 'No tests executed!', - ); - - return; - } - - if ($result->wasSuccessfulAndNoTestIsRiskyOrSkippedOrIncomplete()) { - $this->writeWithColor( - 'fg-black, bg-green', - sprintf( - 'OK (%d test%s, %d assertion%s)', - count($result), - (count($result) === 1) ? '' : 's', - $this->numAssertions, - ($this->numAssertions === 1) ? '' : 's', - ), - ); - - return; - } - - $color = 'fg-black, bg-yellow'; - - if ($result->wasSuccessful()) { - if ($this->verbose || !$result->allHarmless()) { - $this->write("\n"); - } - - $this->writeWithColor( - $color, - 'OK, but incomplete, skipped, or risky tests!', - ); - } else { - $this->write("\n"); - - if ($result->errorCount()) { - $color = 'fg-white, bg-red'; - - $this->writeWithColor( - $color, - 'ERRORS!', - ); - } elseif ($result->failureCount()) { - $color = 'fg-white, bg-red'; - - $this->writeWithColor( - $color, - 'FAILURES!', - ); - } elseif ($result->warningCount()) { - $color = 'fg-black, bg-yellow'; - - $this->writeWithColor( - $color, - 'WARNINGS!', - ); - } - } - - $this->writeCountString(count($result), 'Tests', $color, true); - $this->writeCountString($this->numAssertions, 'Assertions', $color, true); - $this->writeCountString($result->errorCount(), 'Errors', $color); - $this->writeCountString($result->failureCount(), 'Failures', $color); - $this->writeCountString($result->warningCount(), 'Warnings', $color); - $this->writeCountString($result->skippedCount(), 'Skipped', $color); - $this->writeCountString($result->notImplementedCount(), 'Incomplete', $color); - $this->writeCountString($result->riskyCount(), 'Risky', $color); - $this->writeWithColor($color, '.'); - } - - protected function writeProgress(string $progress): void - { - if ($this->debug) { - return; - } - - $this->write($progress); - $this->column++; - $this->numTestsRun++; - - if ($this->column == $this->maxColumn || $this->numTestsRun == $this->numTests) { - if ($this->numTestsRun == $this->numTests) { - $this->write(str_repeat(' ', $this->maxColumn - $this->column)); - } - - $this->write( - sprintf( - ' %' . $this->numTestsWidth . 'd / %' . - $this->numTestsWidth . 'd (%3s%%)', - $this->numTestsRun, - $this->numTests, - floor(($this->numTestsRun / $this->numTests) * 100), - ), - ); - - if ($this->column == $this->maxColumn) { - $this->writeNewLine(); - } - } - } - - protected function writeNewLine(): void - { - $this->column = 0; - $this->write("\n"); - } - - /** - * Formats a buffer with a specified ANSI color sequence if colors are - * enabled. - */ - protected function colorizeTextBox(string $color, string $buffer): string - { - if (!$this->colors) { - return $buffer; - } - - $lines = preg_split('/\r\n|\r|\n/', $buffer); - $padding = max(array_map('\strlen', $lines)); - - $styledLines = []; - - foreach ($lines as $line) { - $styledLines[] = Color::colorize($color, str_pad($line, $padding)); - } - - return implode(PHP_EOL, $styledLines); - } - - /** - * Writes a buffer out with a color sequence if colors are enabled. - */ - protected function writeWithColor(string $color, string $buffer, bool $lf = true): void - { - $this->write($this->colorizeTextBox($color, $buffer)); - - if ($lf) { - $this->write(PHP_EOL); - } - } - - /** - * Writes progress with a color sequence if colors are enabled. - */ - protected function writeProgressWithColor(string $color, string $buffer): void - { - $buffer = $this->colorizeTextBox($color, $buffer); - $this->writeProgress($buffer); - } - - private function writeCountString(int $count, string $name, string $color, bool $always = false): void - { - static $first = true; - - if ($always || $count > 0) { - $this->writeWithColor( - $color, - sprintf( - '%s%s: %d', - !$first ? ', ' : '', - $name, - $count, - ), - false, - ); - - $first = false; - } - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Exception/CannotOpenSocketException.php b/app/vendor/phpunit/phpunit/src/TextUI/Exception/CannotOpenSocketException.php new file mode 100644 index 000000000..519d13785 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Exception/CannotOpenSocketException.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 PHPUnit\TextUI; + +use function sprintf; +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class CannotOpenSocketException extends RuntimeException implements Exception +{ + public function __construct(string $hostname, int $port) + { + parent::__construct( + sprintf( + 'Cannot open socket %s:%d', + $hostname, + $port, + ), + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Exception/Exception.php b/app/vendor/phpunit/phpunit/src/TextUI/Exception/Exception.php index ee2ae4ffa..6b370ca07 100644 --- a/app/vendor/phpunit/phpunit/src/TextUI/Exception/Exception.php +++ b/app/vendor/phpunit/phpunit/src/TextUI/Exception/Exception.php @@ -12,6 +12,8 @@ use Throwable; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This interface is not covered by the backward compatibility promise for PHPUnit */ interface Exception extends Throwable diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Exception/InvalidSocketException.php b/app/vendor/phpunit/phpunit/src/TextUI/Exception/InvalidSocketException.php new file mode 100644 index 000000000..441afd2a1 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Exception/InvalidSocketException.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 PHPUnit\TextUI; + +use function sprintf; +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class InvalidSocketException extends RuntimeException implements Exception +{ + public function __construct(string $socket) + { + parent::__construct( + sprintf( + '"%s" does not match "socket://hostname:port" format', + $socket, + ), + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Exception/ReflectionException.php b/app/vendor/phpunit/phpunit/src/TextUI/Exception/ReflectionException.php deleted file mode 100644 index 74e9d25dd..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/Exception/ReflectionException.php +++ /dev/null @@ -1,19 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI; - -use RuntimeException; - -/** - * @internal This interface is not covered by the backward compatibility promise for PHPUnit - */ -final class ReflectionException extends RuntimeException implements Exception -{ -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Exception/RuntimeException.php b/app/vendor/phpunit/phpunit/src/TextUI/Exception/RuntimeException.php index 790a84634..875a0487c 100644 --- a/app/vendor/phpunit/phpunit/src/TextUI/Exception/RuntimeException.php +++ b/app/vendor/phpunit/phpunit/src/TextUI/Exception/RuntimeException.php @@ -10,7 +10,9 @@ namespace PHPUnit\TextUI; /** - * @internal This interface is not covered by the backward compatibility promise for PHPUnit + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class RuntimeException extends \RuntimeException implements Exception { diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Exception/TestDirectoryNotFoundException.php b/app/vendor/phpunit/phpunit/src/TextUI/Exception/TestDirectoryNotFoundException.php index af387beae..9b35390cd 100644 --- a/app/vendor/phpunit/phpunit/src/TextUI/Exception/TestDirectoryNotFoundException.php +++ b/app/vendor/phpunit/phpunit/src/TextUI/Exception/TestDirectoryNotFoundException.php @@ -13,7 +13,9 @@ use RuntimeException; /** - * @internal This interface is not covered by the backward compatibility promise for PHPUnit + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class TestDirectoryNotFoundException extends RuntimeException implements Exception { diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Exception/TestFileNotFoundException.php b/app/vendor/phpunit/phpunit/src/TextUI/Exception/TestFileNotFoundException.php index 3b534f7e7..46c9df806 100644 --- a/app/vendor/phpunit/phpunit/src/TextUI/Exception/TestFileNotFoundException.php +++ b/app/vendor/phpunit/phpunit/src/TextUI/Exception/TestFileNotFoundException.php @@ -13,7 +13,9 @@ use RuntimeException; /** - * @internal This interface is not covered by the backward compatibility promise for PHPUnit + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class TestFileNotFoundException extends RuntimeException implements Exception { diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Help.php b/app/vendor/phpunit/phpunit/src/TextUI/Help.php index cfa51acb1..639a5be38 100644 --- a/app/vendor/phpunit/phpunit/src/TextUI/Help.php +++ b/app/vendor/phpunit/phpunit/src/TextUI/Help.php @@ -23,26 +23,16 @@ use SebastianBergmann\Environment\Console; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class Help { - private const LEFT_MARGIN = ' '; - - /** - * @var int Number of columns required to write the longest option name to the console - */ - private $maxArgLength = 0; - - /** - * @var int Number of columns left for the description field after padding and option - */ - private $maxDescLength; - - /** - * @var bool Use color highlights for sections, options and parameters - */ - private $hasColor = false; + private const string LEFT_MARGIN = ' '; + private int $lengthOfLongestOptionName = 0; + private readonly int $columnsAvailableForDescription; + private bool $hasColor; public function __construct(?int $width = null, ?bool $withColor = null) { @@ -59,221 +49,280 @@ public function __construct(?int $width = null, ?bool $withColor = null) foreach ($this->elements() as $options) { foreach ($options as $option) { if (isset($option['arg'])) { - $this->maxArgLength = max($this->maxArgLength, isset($option['arg']) ? strlen($option['arg']) : 0); + $this->lengthOfLongestOptionName = max($this->lengthOfLongestOptionName, strlen($option['arg'])); } } } - $this->maxDescLength = $width - $this->maxArgLength - 4; + $this->columnsAvailableForDescription = $width - $this->lengthOfLongestOptionName - 4; } - /** - * Write the help file to the CLI, adapting width and colors to the console. - */ - public function writeToConsole(): void + public function generate(): string { if ($this->hasColor) { - $this->writeWithColor(); - } else { - $this->writePlaintext(); + return $this->writeWithColor(); } + + return $this->writeWithoutColor(); } - private function writePlaintext(): void + private function writeWithoutColor(): string { + $buffer = ''; + foreach ($this->elements() as $section => $options) { - print "{$section}:" . PHP_EOL; + $buffer .= "{$section}:" . PHP_EOL; if ($section !== 'Usage') { - print PHP_EOL; + $buffer .= PHP_EOL; } foreach ($options as $option) { if (isset($option['spacer'])) { - print PHP_EOL; + $buffer .= PHP_EOL; } if (isset($option['text'])) { - print self::LEFT_MARGIN . $option['text'] . PHP_EOL; + $buffer .= self::LEFT_MARGIN . $option['text'] . PHP_EOL; } if (isset($option['arg'])) { - $arg = str_pad($option['arg'], $this->maxArgLength); - print self::LEFT_MARGIN . $arg . ' ' . $option['desc'] . PHP_EOL; + $arg = str_pad($option['arg'], $this->lengthOfLongestOptionName); + + $buffer .= self::LEFT_MARGIN . $arg . ' ' . $option['desc'] . PHP_EOL; } } - print PHP_EOL; + $buffer .= PHP_EOL; } + + return $buffer; } - private function writeWithColor(): void + private function writeWithColor(): string { + $buffer = ''; + foreach ($this->elements() as $section => $options) { - print Color::colorize('fg-yellow', "{$section}:") . PHP_EOL; + $buffer .= Color::colorize('fg-yellow', "{$section}:") . PHP_EOL; + + if ($section !== 'Usage') { + $buffer .= PHP_EOL; + } foreach ($options as $option) { if (isset($option['spacer'])) { - print PHP_EOL; + $buffer .= PHP_EOL; } if (isset($option['text'])) { - print self::LEFT_MARGIN . $option['text'] . PHP_EOL; + $buffer .= self::LEFT_MARGIN . $option['text'] . PHP_EOL; } if (isset($option['arg'])) { - $arg = Color::colorize('fg-green', str_pad($option['arg'], $this->maxArgLength)); + $arg = Color::colorize('fg-green', str_pad($option['arg'], $this->lengthOfLongestOptionName)); $arg = preg_replace_callback( '/(<[^>]+>)/', - static function ($matches) - { - return Color::colorize('fg-cyan', $matches[0]); - }, + static fn (array $matches) => Color::colorize('fg-cyan', $matches[0]), $arg, ); - $desc = explode(PHP_EOL, wordwrap($option['desc'], $this->maxDescLength, PHP_EOL)); - print self::LEFT_MARGIN . $arg . ' ' . $desc[0] . PHP_EOL; + $desc = explode(PHP_EOL, wordwrap($option['desc'], $this->columnsAvailableForDescription, PHP_EOL)); + + $buffer .= self::LEFT_MARGIN . $arg . ' ' . $desc[0] . PHP_EOL; for ($i = 1; $i < count($desc); $i++) { - print str_repeat(' ', $this->maxArgLength + 3) . $desc[$i] . PHP_EOL; + $buffer .= str_repeat(' ', $this->lengthOfLongestOptionName + 3) . $desc[$i] . PHP_EOL; } } } - print PHP_EOL; + $buffer .= PHP_EOL; } + + return $buffer; } /** - * @psalm-return array> + * @return array> */ private function elements(): array { $elements = [ 'Usage' => [ - ['text' => 'phpunit [options] UnitTest.php'], - ['text' => 'phpunit [options] '], + ['text' => 'phpunit [options] ...'], ], - 'Code Coverage Options' => [ - ['arg' => '--coverage-clover ', 'desc' => 'Generate code coverage report in Clover XML format'], - ['arg' => '--coverage-cobertura ', 'desc' => 'Generate code coverage report in Cobertura XML format'], - ['arg' => '--coverage-crap4j ', 'desc' => 'Generate code coverage report in Crap4J XML format'], - ['arg' => '--coverage-html ', 'desc' => 'Generate code coverage report in HTML format'], - ['arg' => '--coverage-php ', 'desc' => 'Export PHP_CodeCoverage object to file'], - ['arg' => '--coverage-text=', 'desc' => 'Generate code coverage report in text format [default: standard output]'], - ['arg' => '--coverage-xml ', 'desc' => 'Generate code coverage report in PHPUnit XML format'], - ['arg' => '--coverage-cache ', 'desc' => 'Cache static analysis results'], - ['arg' => '--warm-coverage-cache', 'desc' => 'Warm static analysis cache'], - ['arg' => '--coverage-filter ', 'desc' => 'Include in code coverage analysis'], - ['arg' => '--path-coverage', 'desc' => 'Perform path coverage analysis'], - ['arg' => '--disable-coverage-ignore', 'desc' => 'Disable annotations for ignoring code coverage'], - ['arg' => '--no-coverage', 'desc' => 'Ignore code coverage configuration'], - ], - - 'Logging Options' => [ - ['arg' => '--log-junit ', 'desc' => 'Log test execution in JUnit XML format to file'], - ['arg' => '--log-teamcity ', 'desc' => 'Log test execution in TeamCity format to file'], - ['arg' => '--testdox-html ', 'desc' => 'Write agile documentation in HTML format to file'], - ['arg' => '--testdox-text ', 'desc' => 'Write agile documentation in Text format to file'], - ['arg' => '--testdox-xml ', 'desc' => 'Write agile documentation in XML format to file'], - ['arg' => '--reverse-list', 'desc' => 'Print defects in reverse order'], - ['arg' => '--no-logging', 'desc' => 'Ignore logging configuration'], + 'Configuration' => [ + ['arg' => '--bootstrap ', 'desc' => 'A PHP script that is included before the tests run'], + ['arg' => '-c|--configuration ', 'desc' => 'Read configuration from XML file'], + ['arg' => '--no-configuration', 'desc' => 'Ignore default configuration file (phpunit.xml)'], + ['arg' => '--extension ', 'desc' => 'Register test runner extension with bootstrap '], + ['arg' => '--no-extensions', 'desc' => 'Do not register test runner extensions'], + ['arg' => '--include-path ', 'desc' => 'Prepend PHP\'s include_path with given path(s)'], + ['arg' => '-d ', 'desc' => 'Sets a php.ini value'], + ['arg' => '--cache-directory ', 'desc' => 'Specify cache directory'], + ['arg' => '--generate-configuration', 'desc' => 'Generate configuration file with suggested settings'], + ['arg' => '--migrate-configuration', 'desc' => 'Migrate configuration file to current format'], + ['arg' => '--generate-baseline ', 'desc' => 'Generate baseline for issues'], + ['arg' => '--use-baseline ', 'desc' => 'Use baseline to ignore issues'], + ['arg' => '--ignore-baseline', 'desc' => 'Do not use baseline to ignore issues'], ], - 'Test Selection Options' => [ + 'Selection' => [ ['arg' => '--list-suites', 'desc' => 'List available test suites'], - ['arg' => '--testsuite ', 'desc' => 'Filter which testsuite to run'], + ['arg' => '--testsuite ', 'desc' => 'Only run tests from the specified test suite(s)'], + ['arg' => '--exclude-testsuite ', 'desc' => 'Exclude tests from the specified test suite(s)'], ['arg' => '--list-groups', 'desc' => 'List available test groups'], - ['arg' => '--group ', 'desc' => 'Only runs tests from the specified group(s)'], + ['arg' => '--group ', 'desc' => 'Only run tests from the specified group(s)'], ['arg' => '--exclude-group ', 'desc' => 'Exclude tests from the specified group(s)'], - ['arg' => '--covers ', 'desc' => 'Only runs tests annotated with "@covers "'], - ['arg' => '--uses ', 'desc' => 'Only runs tests annotated with "@uses "'], + ['arg' => '--covers ', 'desc' => 'Only run tests that intend to cover '], + ['arg' => '--uses ', 'desc' => 'Only run tests that intend to use '], + ['arg' => '--requires-php-extension ', 'desc' => 'Only run tests that require PHP extension '], + ['arg' => '--list-test-files', 'desc' => 'List available test files'], ['arg' => '--list-tests', 'desc' => 'List available tests'], ['arg' => '--list-tests-xml ', 'desc' => 'List available tests in XML format'], ['arg' => '--filter ', 'desc' => 'Filter which tests to run'], + ['arg' => '--exclude-filter ', 'desc' => 'Exclude tests for the specified filter pattern'], ['arg' => '--test-suffix ', 'desc' => 'Only search for test in files with specified suffix(es). Default: Test.php,.phpt'], ], - 'Test Execution Options' => [ - ['arg' => '--dont-report-useless-tests', 'desc' => 'Do not report tests that do not test anything'], - ['arg' => '--strict-coverage', 'desc' => 'Be strict about @covers annotation usage'], + 'Execution' => [ + ['arg' => '--process-isolation', 'desc' => 'Run each test in a separate PHP process'], + ['arg' => '--globals-backup', 'desc' => 'Backup and restore $GLOBALS for each test'], + ['arg' => '--static-backup', 'desc' => 'Backup and restore static properties for each test'], + ['spacer' => ''], + + ['arg' => '--strict-coverage', 'desc' => 'Be strict about code coverage metadata'], ['arg' => '--strict-global-state', 'desc' => 'Be strict about changes to global state'], ['arg' => '--disallow-test-output', 'desc' => 'Be strict about output during tests'], - ['arg' => '--disallow-resource-usage', 'desc' => 'Be strict about resource usage during small tests'], ['arg' => '--enforce-time-limit', 'desc' => 'Enforce time limit based on test size'], - ['arg' => '--default-time-limit ', 'desc' => 'Timeout in seconds for tests without @small, @medium or @large'], - ['arg' => '--disallow-todo-tests', 'desc' => 'Disallow @todo-annotated tests'], + ['arg' => '--default-time-limit ', 'desc' => 'Timeout in seconds for tests that have no declared size'], + ['arg' => '--do-not-report-useless-tests', 'desc' => 'Do not report tests that do not test anything'], ['spacer' => ''], - ['arg' => '--process-isolation', 'desc' => 'Run each test in a separate PHP process'], - ['arg' => '--globals-backup', 'desc' => 'Backup and restore $GLOBALS for each test'], - ['arg' => '--static-backup', 'desc' => 'Backup and restore static attributes for each test'], + ['arg' => '--stop-on-defect', 'desc' => 'Stop after first error, failure, warning, or risky test'], + ['arg' => '--stop-on-error', 'desc' => 'Stop after first error'], + ['arg' => '--stop-on-failure', 'desc' => 'Stop after first failure'], + ['arg' => '--stop-on-warning', 'desc' => 'Stop after first warning'], + ['arg' => '--stop-on-risky', 'desc' => 'Stop after first risky test'], + ['arg' => '--stop-on-deprecation', 'desc' => 'Stop after first test that triggered a deprecation'], + ['arg' => '--stop-on-notice', 'desc' => 'Stop after first test that triggered a notice'], + ['arg' => '--stop-on-skipped', 'desc' => 'Stop after first skipped test'], + ['arg' => '--stop-on-incomplete', 'desc' => 'Stop after first incomplete test'], ['spacer' => ''], - ['arg' => '--colors ', 'desc' => 'Use colors in output ("never", "auto" or "always")'], + ['arg' => '--fail-on-empty-test-suite', 'desc' => 'Signal failure using shell exit code when no tests were run'], + ['arg' => '--fail-on-warning', 'desc' => 'Signal failure using shell exit code when a warning was triggered'], + ['arg' => '--fail-on-risky', 'desc' => 'Signal failure using shell exit code when a test was considered risky'], + ['arg' => '--fail-on-deprecation', 'desc' => 'Signal failure using shell exit code when a deprecation was triggered'], + ['arg' => '--fail-on-phpunit-deprecation', 'desc' => 'Signal failure using shell exit code when a PHPUnit deprecation was triggered'], + ['arg' => '--fail-on-phpunit-notice', 'desc' => 'Signal failure using shell exit code when a PHPUnit notice was triggered'], + ['arg' => '--fail-on-phpunit-warning', 'desc' => 'Signal failure using shell exit code when a PHPUnit warning was triggered'], + ['arg' => '--fail-on-notice', 'desc' => 'Signal failure using shell exit code when a notice was triggered'], + ['arg' => '--fail-on-skipped', 'desc' => 'Signal failure using shell exit code when a test was skipped'], + ['arg' => '--fail-on-incomplete', 'desc' => 'Signal failure using shell exit code when a test was marked incomplete'], + ['arg' => '--fail-on-all-issues', 'desc' => 'Signal failure using shell exit code when an issue is triggered'], + ['spacer' => ''], + + ['arg' => '--do-not-fail-on-empty-test-suite', 'desc' => 'Do not signal failure using shell exit code when no tests were run'], + ['arg' => '--do-not-fail-on-warning', 'desc' => 'Do not signal failure using shell exit code when a warning was triggered'], + ['arg' => '--do-not-fail-on-risky', 'desc' => 'Do not signal failure using shell exit code when a test was considered risky'], + ['arg' => '--do-not-fail-on-deprecation', 'desc' => 'Do not signal failure using shell exit code when a deprecation was triggered'], + ['arg' => '--do-not-fail-on-phpunit-deprecation', 'desc' => 'Do not signal failure using shell exit code when a PHPUnit deprecation was triggered'], + ['arg' => '--do-not-fail-on-phpunit-notice', 'desc' => 'Do not signal failure using shell exit code when a PHPUnit notice was triggered'], + ['arg' => '--do-not-fail-on-phpunit-warning', 'desc' => 'Do not signal failure using shell exit code when a PHPUnit warning was triggered'], + ['arg' => '--do-not-fail-on-notice', 'desc' => 'Do not signal failure using shell exit code when a notice was triggered'], + ['arg' => '--do-not-fail-on-skipped', 'desc' => 'Do not signal failure using shell exit code when a test was skipped'], + ['arg' => '--do-not-fail-on-incomplete', 'desc' => 'Do not signal failure using shell exit code when a test was marked incomplete'], + ['spacer' => ''], + + ['arg' => '--cache-result', 'desc' => 'Write test results to cache file'], + ['arg' => '--do-not-cache-result', 'desc' => 'Do not write test results to cache file'], + ['spacer' => ''], + + ['arg' => '--order-by ', 'desc' => 'Run tests in order: default|defects|depends|duration|no-depends|random|reverse|size'], + ['arg' => '--random-order-seed ', 'desc' => 'Use the specified random seed when running tests in random order'], + ], + + 'Reporting' => [ + ['arg' => '--colors ', 'desc' => 'Use colors in output ("never", "auto" or "always")'], ['arg' => '--columns ', 'desc' => 'Number of columns to use for progress output'], ['arg' => '--columns max', 'desc' => 'Use maximum number of columns for progress output'], ['arg' => '--stderr', 'desc' => 'Write to STDERR instead of STDOUT'], - ['arg' => '--stop-on-defect', 'desc' => 'Stop execution upon first not-passed test'], - ['arg' => '--stop-on-error', 'desc' => 'Stop execution upon first error'], - ['arg' => '--stop-on-failure', 'desc' => 'Stop execution upon first error or failure'], - ['arg' => '--stop-on-warning', 'desc' => 'Stop execution upon first warning'], - ['arg' => '--stop-on-risky', 'desc' => 'Stop execution upon first risky test'], - ['arg' => '--stop-on-skipped', 'desc' => 'Stop execution upon first skipped test'], - ['arg' => '--stop-on-incomplete', 'desc' => 'Stop execution upon first incomplete test'], - ['arg' => '--fail-on-incomplete', 'desc' => 'Treat incomplete tests as failures'], - ['arg' => '--fail-on-risky', 'desc' => 'Treat risky tests as failures'], - ['arg' => '--fail-on-skipped', 'desc' => 'Treat skipped tests as failures'], - ['arg' => '--fail-on-warning', 'desc' => 'Treat tests with warnings as failures'], - ['arg' => '-v|--verbose', 'desc' => 'Output more verbose information'], - ['arg' => '--debug', 'desc' => 'Display debugging information'], ['spacer' => ''], - ['arg' => '--repeat ', 'desc' => 'Runs the test(s) repeatedly'], - ['arg' => '--teamcity', 'desc' => 'Report test execution progress in TeamCity format'], - ['arg' => '--testdox', 'desc' => 'Report test execution progress in TestDox format'], - ['arg' => '--testdox-group', 'desc' => 'Only include tests from the specified group(s)'], - ['arg' => '--testdox-exclude-group', 'desc' => 'Exclude tests from the specified group(s)'], - ['arg' => '--no-interaction', 'desc' => 'Disable TestDox progress animation'], - ['arg' => '--printer ', 'desc' => 'TestListener implementation to use'], + ['arg' => '--no-progress', 'desc' => 'Disable output of test execution progress'], + ['arg' => '--no-results', 'desc' => 'Disable output of test results'], + ['arg' => '--no-output', 'desc' => 'Disable all output'], ['spacer' => ''], - ['arg' => '--order-by ', 'desc' => 'Run tests in order: default|defects|duration|no-depends|random|reverse|size'], - ['arg' => '--random-order-seed ', 'desc' => 'Use a specific random seed for random order'], - ['arg' => '--cache-result', 'desc' => 'Write test results to cache file'], - ['arg' => '--do-not-cache-result', 'desc' => 'Do not write test results to cache file'], + ['arg' => '--display-incomplete', 'desc' => 'Display details for incomplete tests'], + ['arg' => '--display-skipped', 'desc' => 'Display details for skipped tests'], + ['arg' => '--display-deprecations', 'desc' => 'Display details for deprecations triggered by tests'], + ['arg' => '--display-phpunit-deprecations', 'desc' => 'Display details for PHPUnit deprecations'], + ['arg' => '--display-phpunit-notices', 'desc' => 'Display details for PHPUnit notices'], + ['arg' => '--display-errors', 'desc' => 'Display details for errors triggered by tests'], + ['arg' => '--display-notices', 'desc' => 'Display details for notices triggered by tests'], + ['arg' => '--display-warnings', 'desc' => 'Display details for warnings triggered by tests'], + ['arg' => '--display-all-issues', 'desc' => 'Display details for all issues that are triggered'], + ['arg' => '--reverse-list', 'desc' => 'Print defects in reverse order'], + ['spacer' => ''], + + ['arg' => '--teamcity', 'desc' => 'Replace default progress and result output with TeamCity format'], + ['arg' => '--testdox', 'desc' => 'Replace default result output with TestDox format'], + ['arg' => '--testdox-summary', 'desc' => 'Repeat TestDox output for tests with errors, failures, or issues'], + ['spacer' => ''], + + ['arg' => '--debug', 'desc' => 'Replace default progress and result output with debugging information'], + ['arg' => '--with-telemetry', 'desc' => 'Include telemetry information in debugging information output'], ], - 'Configuration Options' => [ - ['arg' => '--prepend ', 'desc' => 'A PHP script that is included as early as possible'], - ['arg' => '--bootstrap ', 'desc' => 'A PHP script that is included before the tests run'], - ['arg' => '-c|--configuration ', 'desc' => 'Read configuration from XML file'], - ['arg' => '--no-configuration', 'desc' => 'Ignore default configuration file (phpunit.xml)'], - ['arg' => '--extensions ', 'desc' => 'A comma separated list of PHPUnit extensions to load'], - ['arg' => '--no-extensions', 'desc' => 'Do not load PHPUnit extensions'], - ['arg' => '--include-path ', 'desc' => 'Prepend PHP\'s include_path with given path(s)'], - ['arg' => '-d ', 'desc' => 'Sets a php.ini value'], - ['arg' => '--cache-result-file ', 'desc' => 'Specify result cache path and filename'], - ['arg' => '--generate-configuration', 'desc' => 'Generate configuration file with suggested settings'], - ['arg' => '--migrate-configuration', 'desc' => 'Migrate configuration file to current format'], + 'Logging' => [ + ['arg' => '--log-junit ', 'desc' => 'Write test results in JUnit XML format to file'], + ['arg' => '--log-otr ', 'desc' => 'Write test results in Open Test Reporting XML format to file'], + ['arg' => '--include-git-information', 'desc' => 'Include Git information in Open Test Reporting XML logfile'], + ['arg' => '--log-teamcity ', 'desc' => 'Write test results in TeamCity format to file'], + ['arg' => '--testdox-html ', 'desc' => 'Write test results in TestDox format (HTML) to file'], + ['arg' => '--testdox-text ', 'desc' => 'Write test results in TestDox format (plain text) to file'], + ['arg' => '--log-events-text ', 'desc' => 'Stream events as plain text to file'], + ['arg' => '--log-events-verbose-text ', 'desc' => 'Stream events as plain text with extended information to file'], + ['arg' => '--no-logging', 'desc' => 'Ignore logging configured in the XML configuration file'], + ], + + 'Code Coverage' => [ + ['arg' => '--coverage-clover ', 'desc' => 'Write code coverage report in Clover XML format to file'], + ['arg' => '--coverage-openclover ', 'desc' => 'Write code coverage report in OpenClover XML format to file'], + ['arg' => '--coverage-cobertura ', 'desc' => 'Write code coverage report in Cobertura XML format to file'], + ['arg' => '--coverage-crap4j ', 'desc' => 'Write code coverage report in Crap4J XML format to file'], + ['arg' => '--coverage-html ', 'desc' => 'Write code coverage report in HTML format to directory'], + ['arg' => '--coverage-php ', 'desc' => 'Write serialized code coverage data to file'], + ['arg' => '--coverage-text=', 'desc' => 'Write code coverage report in text format to file [default: standard output]'], + ['arg' => '--only-summary-for-coverage-text', 'desc' => 'Option for code coverage report in text format: only show summary'], + ['arg' => '--show-uncovered-for-coverage-text', 'desc' => 'Option for code coverage report in text format: show uncovered files'], + ['arg' => '--coverage-xml ', 'desc' => 'Write code coverage report in XML format to directory'], + ['arg' => '--warm-coverage-cache', 'desc' => 'Warm static analysis cache'], + ['arg' => '--coverage-filter ', 'desc' => 'Include in code coverage reporting'], + ['arg' => '--path-coverage', 'desc' => 'Report path coverage in addition to line coverage'], + ['arg' => '--disable-coverage-ignore', 'desc' => 'Disable metadata for ignoring code coverage'], + ['arg' => '--no-coverage', 'desc' => 'Ignore code coverage reporting configured in the XML configuration file'], ], ]; if (defined('__PHPUNIT_PHAR__')) { - $elements['PHAR Options'] = [ + $elements['PHAR'] = [ ['arg' => '--manifest', 'desc' => 'Print Software Bill of Materials (SBOM) in plain-text format'], ['arg' => '--sbom', 'desc' => 'Print Software Bill of Materials (SBOM) in CycloneDX XML format'], ['arg' => '--composer-lock', 'desc' => 'Print composer.lock file used to build the PHAR'], ]; } - $elements['Miscellaneous Options'] = [ + $elements['Miscellaneous'] = [ ['arg' => '-h|--help', 'desc' => 'Prints this usage information'], ['arg' => '--version', 'desc' => 'Prints the version and exits'], - ['arg' => '--atleast-version ', 'desc' => 'Checks that version is greater than min and exits'], + ['arg' => '--atleast-version ', 'desc' => 'Checks that version is greater than and exits'], ['arg' => '--check-version', 'desc' => 'Checks whether PHPUnit is the latest version and exits'], + ['arg' => '--check-php-configuration', 'desc' => 'Checks whether PHP configuration follows best practices'], ]; return $elements; diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/ProgressPrinter.php b/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/ProgressPrinter.php new file mode 100644 index 000000000..bcee39960 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/ProgressPrinter.php @@ -0,0 +1,418 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output\Default\ProgressPrinter; + +use function floor; +use function sprintf; +use function str_repeat; +use function strlen; +use PHPUnit\Event\Facade; +use PHPUnit\Event\Test\DeprecationTriggered; +use PHPUnit\Event\Test\Errored; +use PHPUnit\Event\Test\ErrorTriggered; +use PHPUnit\Event\Test\NoticeTriggered; +use PHPUnit\Event\Test\PhpDeprecationTriggered; +use PHPUnit\Event\Test\PhpNoticeTriggered; +use PHPUnit\Event\Test\PhpunitWarningTriggered; +use PHPUnit\Event\Test\PhpWarningTriggered; +use PHPUnit\Event\Test\WarningTriggered; +use PHPUnit\Event\TestRunner\ChildProcessErrored; +use PHPUnit\Event\TestRunner\ExecutionStarted; +use PHPUnit\Framework\TestStatus\TestStatus; +use PHPUnit\TextUI\Configuration\Source; +use PHPUnit\TextUI\Configuration\SourceFilter; +use PHPUnit\TextUI\Output\Printer; +use PHPUnit\Util\Color; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ProgressPrinter +{ + private readonly Printer $printer; + private readonly bool $colors; + private readonly int $numberOfColumns; + private readonly Source $source; + private int $column = 0; + private int $numberOfTests = 0; + private int $numberOfTestsWidth = 0; + private int $maxColumn = 0; + private int $numberOfTestsRun = 0; + private ?TestStatus $status = null; + private bool $prepared = false; + private bool $childProcessErrored = false; + + public function __construct(Printer $printer, Facade $facade, bool $colors, int $numberOfColumns, Source $source) + { + $this->printer = $printer; + $this->colors = $colors; + $this->numberOfColumns = $numberOfColumns; + $this->source = $source; + + $this->registerSubscribers($facade); + } + + public function testRunnerExecutionStarted(ExecutionStarted $event): void + { + $this->numberOfTestsRun = 0; + $this->numberOfTests = $event->testSuite()->count(); + $this->numberOfTestsWidth = strlen((string) $this->numberOfTests); + $this->column = 0; + $this->maxColumn = $this->numberOfColumns - strlen(' / (XXX%)') - (2 * $this->numberOfTestsWidth); + } + + public function beforeTestClassMethodErrored(): void + { + $this->printProgressForError(); + $this->updateTestStatus(TestStatus::error()); + } + + public function testPrepared(): void + { + $this->prepared = true; + } + + public function testSkipped(): void + { + if (!$this->prepared) { + $this->printProgressForSkipped(); + } else { + $this->updateTestStatus(TestStatus::skipped()); + } + } + + public function testMarkedIncomplete(): void + { + $this->updateTestStatus(TestStatus::incomplete()); + } + + public function testTriggeredNotice(NoticeTriggered $event): void + { + if ($event->ignoredByBaseline()) { + return; + } + + if ($this->source->restrictNotices() && + !SourceFilter::instance()->includes($event->file())) { + return; + } + + if (!$this->source->ignoreSuppressionOfNotices() && $event->wasSuppressed()) { + return; + } + + $this->updateTestStatus(TestStatus::notice()); + } + + public function testTriggeredPhpNotice(PhpNoticeTriggered $event): void + { + if ($event->ignoredByBaseline()) { + return; + } + + if ($this->source->restrictNotices() && + !SourceFilter::instance()->includes($event->file())) { + return; + } + + if (!$this->source->ignoreSuppressionOfPhpNotices() && $event->wasSuppressed()) { + return; + } + + $this->updateTestStatus(TestStatus::notice()); + } + + public function testTriggeredDeprecation(DeprecationTriggered $event): void + { + if ($event->ignoredByBaseline() || $event->ignoredByTest()) { + return; + } + + if ($this->source->ignoreSelfDeprecations() && + ($event->trigger()->isTest() || $event->trigger()->isSelf())) { + return; + } + + if ($this->source->ignoreDirectDeprecations() && $event->trigger()->isDirect()) { + return; + } + + if ($this->source->ignoreIndirectDeprecations() && $event->trigger()->isIndirect()) { + return; + } + + if (!$this->source->ignoreSuppressionOfDeprecations() && $event->wasSuppressed()) { + return; + } + + $this->updateTestStatus(TestStatus::deprecation()); + } + + public function testTriggeredPhpDeprecation(PhpDeprecationTriggered $event): void + { + if ($event->ignoredByBaseline() || $event->ignoredByTest()) { + return; + } + + if ($this->source->ignoreSelfDeprecations() && + ($event->trigger()->isTest() || $event->trigger()->isSelf())) { + return; + } + + if ($this->source->ignoreDirectDeprecations() && $event->trigger()->isDirect()) { + return; + } + + if ($this->source->ignoreIndirectDeprecations() && $event->trigger()->isIndirect()) { + return; + } + + if (!$this->source->ignoreSuppressionOfPhpDeprecations() && $event->wasSuppressed()) { + return; + } + + $this->updateTestStatus(TestStatus::deprecation()); + } + + public function testTriggeredPhpunitDeprecation(): void + { + $this->updateTestStatus(TestStatus::deprecation()); + } + + public function testConsideredRisky(): void + { + $this->updateTestStatus(TestStatus::risky()); + } + + public function testTriggeredWarning(WarningTriggered $event): void + { + if ($event->ignoredByBaseline()) { + return; + } + + if ($this->source->restrictWarnings() && + !SourceFilter::instance()->includes($event->file())) { + return; + } + + if (!$this->source->ignoreSuppressionOfWarnings() && $event->wasSuppressed()) { + return; + } + + $this->updateTestStatus(TestStatus::warning()); + } + + public function testTriggeredPhpWarning(PhpWarningTriggered $event): void + { + if ($event->ignoredByBaseline()) { + return; + } + + if ($this->source->restrictWarnings() && + !SourceFilter::instance()->includes($event->file())) { + return; + } + + if (!$this->source->ignoreSuppressionOfPhpWarnings() && $event->wasSuppressed()) { + return; + } + + $this->updateTestStatus(TestStatus::warning()); + } + + public function testTriggeredPhpunitWarning(PhpunitWarningTriggered $event): void + { + if ($event->ignoredByTest()) { + return; + } + + $this->updateTestStatus(TestStatus::warning()); + } + + public function testTriggeredError(ErrorTriggered $event): void + { + if (!$this->source->ignoreSuppressionOfErrors() && $event->wasSuppressed()) { + return; + } + + $this->updateTestStatus(TestStatus::error()); + } + + public function testFailed(): void + { + $this->updateTestStatus(TestStatus::failure()); + } + + public function testErrored(Errored $event): void + { + if ($this->childProcessErrored) { + $this->updateTestStatus(TestStatus::error()); + + return; + } + + if (!$this->prepared) { + $this->printProgressForError(); + } else { + $this->updateTestStatus(TestStatus::error()); + } + } + + public function testFinished(): void + { + if ($this->status === null) { + $this->printProgressForSuccess(); + } elseif ($this->status->isSkipped()) { + $this->printProgressForSkipped(); + } elseif ($this->status->isIncomplete()) { + $this->printProgressForIncomplete(); + } elseif ($this->status->isRisky()) { + $this->printProgressForRisky(); + } elseif ($this->status->isNotice()) { + $this->printProgressForNotice(); + } elseif ($this->status->isDeprecation()) { + $this->printProgressForDeprecation(); + } elseif ($this->status->isWarning()) { + $this->printProgressForWarning(); + } elseif ($this->status->isFailure()) { + $this->printProgressForFailure(); + } else { + $this->printProgressForError(); + } + + $this->status = null; + $this->prepared = false; + $this->childProcessErrored = false; + } + + public function childProcessErrored(ChildProcessErrored $event): void + { + $this->childProcessErrored = true; + } + + private function registerSubscribers(Facade $facade): void + { + $facade->registerSubscribers( + new BeforeTestClassMethodErroredSubscriber($this), + new TestConsideredRiskySubscriber($this), + new TestErroredSubscriber($this), + new TestFailedSubscriber($this), + new TestFinishedSubscriber($this), + new TestMarkedIncompleteSubscriber($this), + new TestPreparedSubscriber($this), + new TestRunnerExecutionStartedSubscriber($this), + new TestSkippedSubscriber($this), + new TestTriggeredDeprecationSubscriber($this), + new TestTriggeredNoticeSubscriber($this), + new TestTriggeredPhpDeprecationSubscriber($this), + new TestTriggeredPhpNoticeSubscriber($this), + new TestTriggeredPhpunitDeprecationSubscriber($this), + new TestTriggeredPhpunitWarningSubscriber($this), + new TestTriggeredPhpWarningSubscriber($this), + new TestTriggeredWarningSubscriber($this), + new ChildProcessErroredSubscriber($this), + ); + } + + private function updateTestStatus(TestStatus $status): void + { + if ($this->status !== null && + $this->status->isMoreImportantThan($status)) { + return; + } + + $this->status = $status; + } + + private function printProgressForSuccess(): void + { + $this->printProgress('.'); + } + + private function printProgressForSkipped(): void + { + $this->printProgressWithColor('fg-cyan, bold', 'S'); + } + + private function printProgressForIncomplete(): void + { + $this->printProgressWithColor('fg-yellow, bold', 'I'); + } + + private function printProgressForNotice(): void + { + $this->printProgressWithColor('fg-yellow, bold', 'N'); + } + + private function printProgressForDeprecation(): void + { + $this->printProgressWithColor('fg-yellow, bold', 'D'); + } + + private function printProgressForRisky(): void + { + $this->printProgressWithColor('fg-yellow, bold', 'R'); + } + + private function printProgressForWarning(): void + { + $this->printProgressWithColor('fg-yellow, bold', 'W'); + } + + private function printProgressForFailure(): void + { + $this->printProgressWithColor('bg-red, fg-white', 'F'); + } + + private function printProgressForError(): void + { + $this->printProgressWithColor('fg-red, bold', 'E'); + } + + private function printProgressWithColor(string $color, string $progress): void + { + if ($this->colors) { + $progress = Color::colorizeTextBox($color, $progress); + } + + $this->printProgress($progress); + } + + private function printProgress(string $progress): void + { + $this->printer->print($progress); + + $this->column++; + $this->numberOfTestsRun++; + + if ($this->column === $this->maxColumn || $this->numberOfTestsRun === $this->numberOfTests) { + if ($this->numberOfTestsRun === $this->numberOfTests) { + $this->printer->print(str_repeat(' ', $this->maxColumn - $this->column)); + } + + $this->printer->print( + sprintf( + ' %' . $this->numberOfTestsWidth . 'd / %' . + $this->numberOfTestsWidth . 'd (%3s%%)', + $this->numberOfTestsRun, + $this->numberOfTests, + floor(($this->numberOfTestsRun / $this->numberOfTests) * 100), + ), + ); + + if ($this->column === $this->maxColumn) { + $this->column = 0; + $this->printer->print("\n"); + } + } + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/BeforeTestClassMethodErroredSubscriber.php b/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/BeforeTestClassMethodErroredSubscriber.php new file mode 100644 index 000000000..2984cdda8 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/BeforeTestClassMethodErroredSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output\Default\ProgressPrinter; + +use PHPUnit\Event\Test\BeforeFirstTestMethodErrored; +use PHPUnit\Event\Test\BeforeFirstTestMethodErroredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class BeforeTestClassMethodErroredSubscriber extends Subscriber implements BeforeFirstTestMethodErroredSubscriber +{ + public function notify(BeforeFirstTestMethodErrored $event): void + { + $this->printer()->beforeTestClassMethodErrored(); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/ChildProcessErroredSubscriber.php b/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/ChildProcessErroredSubscriber.php new file mode 100644 index 000000000..643694076 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/ChildProcessErroredSubscriber.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output\Default\ProgressPrinter; + +use PHPUnit\Event\TestRunner\ChildProcessErrored; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ChildProcessErroredSubscriber extends Subscriber implements \PHPUnit\Event\TestRunner\ChildProcessErroredSubscriber +{ + public function notify(ChildProcessErrored $event): void + { + $this->printer()->childProcessErrored($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/Subscriber.php b/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/Subscriber.php new file mode 100644 index 000000000..32515ee34 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/Subscriber.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output\Default\ProgressPrinter; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +abstract readonly class Subscriber +{ + private ProgressPrinter $printer; + + public function __construct(ProgressPrinter $printer) + { + $this->printer = $printer; + } + + protected function printer(): ProgressPrinter + { + return $this->printer; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestConsideredRiskySubscriber.php b/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestConsideredRiskySubscriber.php new file mode 100644 index 000000000..e5b57c6d3 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestConsideredRiskySubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output\Default\ProgressPrinter; + +use PHPUnit\Event\Test\ConsideredRisky; +use PHPUnit\Event\Test\ConsideredRiskySubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestConsideredRiskySubscriber extends Subscriber implements ConsideredRiskySubscriber +{ + public function notify(ConsideredRisky $event): void + { + $this->printer()->testConsideredRisky(); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestErroredSubscriber.php b/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestErroredSubscriber.php new file mode 100644 index 000000000..333407552 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestErroredSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output\Default\ProgressPrinter; + +use PHPUnit\Event\Test\Errored; +use PHPUnit\Event\Test\ErroredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestErroredSubscriber extends Subscriber implements ErroredSubscriber +{ + public function notify(Errored $event): void + { + $this->printer()->testErrored($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestFailedSubscriber.php b/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestFailedSubscriber.php new file mode 100644 index 000000000..9109d1b31 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestFailedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output\Default\ProgressPrinter; + +use PHPUnit\Event\Test\Failed; +use PHPUnit\Event\Test\FailedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestFailedSubscriber extends Subscriber implements FailedSubscriber +{ + public function notify(Failed $event): void + { + $this->printer()->testFailed(); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestFinishedSubscriber.php b/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestFinishedSubscriber.php new file mode 100644 index 000000000..e4b4ca5ee --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestFinishedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output\Default\ProgressPrinter; + +use PHPUnit\Event\Test\Finished; +use PHPUnit\Event\Test\FinishedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestFinishedSubscriber extends Subscriber implements FinishedSubscriber +{ + public function notify(Finished $event): void + { + $this->printer()->testFinished(); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestMarkedIncompleteSubscriber.php b/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestMarkedIncompleteSubscriber.php new file mode 100644 index 000000000..8b4450880 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestMarkedIncompleteSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output\Default\ProgressPrinter; + +use PHPUnit\Event\Test\MarkedIncomplete; +use PHPUnit\Event\Test\MarkedIncompleteSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestMarkedIncompleteSubscriber extends Subscriber implements MarkedIncompleteSubscriber +{ + public function notify(MarkedIncomplete $event): void + { + $this->printer()->testMarkedIncomplete(); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestPreparedSubscriber.php b/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestPreparedSubscriber.php new file mode 100644 index 000000000..d99f2fa41 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestPreparedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output\Default\ProgressPrinter; + +use PHPUnit\Event\Test\Prepared; +use PHPUnit\Event\Test\PreparedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestPreparedSubscriber extends Subscriber implements PreparedSubscriber +{ + public function notify(Prepared $event): void + { + $this->printer()->testPrepared(); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestRunnerExecutionStartedSubscriber.php b/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestRunnerExecutionStartedSubscriber.php new file mode 100644 index 000000000..78e104f6d --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestRunnerExecutionStartedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output\Default\ProgressPrinter; + +use PHPUnit\Event\TestRunner\ExecutionStarted; +use PHPUnit\Event\TestRunner\ExecutionStartedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestRunnerExecutionStartedSubscriber extends Subscriber implements ExecutionStartedSubscriber +{ + public function notify(ExecutionStarted $event): void + { + $this->printer()->testRunnerExecutionStarted($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestSkippedSubscriber.php b/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestSkippedSubscriber.php new file mode 100644 index 000000000..a2f4e2556 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestSkippedSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output\Default\ProgressPrinter; + +use PHPUnit\Event\Test\Skipped; +use PHPUnit\Event\Test\SkippedSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestSkippedSubscriber extends Subscriber implements SkippedSubscriber +{ + public function notify(Skipped $event): void + { + $this->printer()->testSkipped(); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredDeprecationSubscriber.php b/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredDeprecationSubscriber.php new file mode 100644 index 000000000..16a4ccf96 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredDeprecationSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output\Default\ProgressPrinter; + +use PHPUnit\Event\Test\DeprecationTriggered; +use PHPUnit\Event\Test\DeprecationTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredDeprecationSubscriber extends Subscriber implements DeprecationTriggeredSubscriber +{ + public function notify(DeprecationTriggered $event): void + { + $this->printer()->testTriggeredDeprecation($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredErrorSubscriber.php b/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredErrorSubscriber.php new file mode 100644 index 000000000..1f89911b4 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredErrorSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output\Default\ProgressPrinter; + +use PHPUnit\Event\Test\ErrorTriggered; +use PHPUnit\Event\Test\ErrorTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredErrorSubscriber extends Subscriber implements ErrorTriggeredSubscriber +{ + public function notify(ErrorTriggered $event): void + { + $this->printer()->testTriggeredError($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredNoticeSubscriber.php b/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredNoticeSubscriber.php new file mode 100644 index 000000000..0639f0272 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredNoticeSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output\Default\ProgressPrinter; + +use PHPUnit\Event\Test\NoticeTriggered; +use PHPUnit\Event\Test\NoticeTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredNoticeSubscriber extends Subscriber implements NoticeTriggeredSubscriber +{ + public function notify(NoticeTriggered $event): void + { + $this->printer()->testTriggeredNotice($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredPhpDeprecationSubscriber.php b/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredPhpDeprecationSubscriber.php new file mode 100644 index 000000000..550250c21 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredPhpDeprecationSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output\Default\ProgressPrinter; + +use PHPUnit\Event\Test\PhpDeprecationTriggered; +use PHPUnit\Event\Test\PhpDeprecationTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredPhpDeprecationSubscriber extends Subscriber implements PhpDeprecationTriggeredSubscriber +{ + public function notify(PhpDeprecationTriggered $event): void + { + $this->printer()->testTriggeredPhpDeprecation($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredPhpNoticeSubscriber.php b/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredPhpNoticeSubscriber.php new file mode 100644 index 000000000..299b898c0 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredPhpNoticeSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output\Default\ProgressPrinter; + +use PHPUnit\Event\Test\PhpNoticeTriggered; +use PHPUnit\Event\Test\PhpNoticeTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredPhpNoticeSubscriber extends Subscriber implements PhpNoticeTriggeredSubscriber +{ + public function notify(PhpNoticeTriggered $event): void + { + $this->printer()->testTriggeredPhpNotice($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredPhpWarningSubscriber.php b/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredPhpWarningSubscriber.php new file mode 100644 index 000000000..a4ff81c22 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredPhpWarningSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output\Default\ProgressPrinter; + +use PHPUnit\Event\Test\PhpWarningTriggered; +use PHPUnit\Event\Test\PhpWarningTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredPhpWarningSubscriber extends Subscriber implements PhpWarningTriggeredSubscriber +{ + public function notify(PhpWarningTriggered $event): void + { + $this->printer()->testTriggeredPhpWarning($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredPhpunitDeprecationSubscriber.php b/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredPhpunitDeprecationSubscriber.php new file mode 100644 index 000000000..62311a012 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredPhpunitDeprecationSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output\Default\ProgressPrinter; + +use PHPUnit\Event\Test\PhpunitDeprecationTriggered; +use PHPUnit\Event\Test\PhpunitDeprecationTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredPhpunitDeprecationSubscriber extends Subscriber implements PhpunitDeprecationTriggeredSubscriber +{ + public function notify(PhpunitDeprecationTriggered $event): void + { + $this->printer()->testTriggeredPhpunitDeprecation(); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredPhpunitWarningSubscriber.php b/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredPhpunitWarningSubscriber.php new file mode 100644 index 000000000..7d0aed1ff --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredPhpunitWarningSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output\Default\ProgressPrinter; + +use PHPUnit\Event\Test\PhpunitWarningTriggered; +use PHPUnit\Event\Test\PhpunitWarningTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredPhpunitWarningSubscriber extends Subscriber implements PhpunitWarningTriggeredSubscriber +{ + public function notify(PhpunitWarningTriggered $event): void + { + $this->printer()->testTriggeredPhpunitWarning($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredWarningSubscriber.php b/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredWarningSubscriber.php new file mode 100644 index 000000000..620458422 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ProgressPrinter/Subscriber/TestTriggeredWarningSubscriber.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output\Default\ProgressPrinter; + +use PHPUnit\Event\Test\WarningTriggered; +use PHPUnit\Event\Test\WarningTriggeredSubscriber; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestTriggeredWarningSubscriber extends Subscriber implements WarningTriggeredSubscriber +{ + public function notify(WarningTriggered $event): void + { + $this->printer()->testTriggeredWarning($event); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ResultPrinter.php b/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ResultPrinter.php new file mode 100644 index 000000000..555382db4 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/ResultPrinter.php @@ -0,0 +1,696 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output\Default; + +use const PHP_EOL; +use function array_keys; +use function array_merge; +use function array_reverse; +use function array_unique; +use function assert; +use function count; +use function explode; +use function ksort; +use function range; +use function sprintf; +use function str_starts_with; +use function strlen; +use function substr; +use function trim; +use PHPUnit\Event\Code\Test; +use PHPUnit\Event\Code\TestMethod; +use PHPUnit\Event\Test\AfterLastTestMethodErrored; +use PHPUnit\Event\Test\BeforeFirstTestMethodErrored; +use PHPUnit\Event\Test\ConsideredRisky; +use PHPUnit\Event\Test\DeprecationTriggered; +use PHPUnit\Event\Test\ErrorTriggered; +use PHPUnit\Event\Test\NoticeTriggered; +use PHPUnit\Event\Test\PhpDeprecationTriggered; +use PHPUnit\Event\Test\PhpNoticeTriggered; +use PHPUnit\Event\Test\PhpunitDeprecationTriggered; +use PHPUnit\Event\Test\PhpunitErrorTriggered; +use PHPUnit\Event\Test\PhpunitNoticeTriggered; +use PHPUnit\Event\Test\PhpunitWarningTriggered; +use PHPUnit\Event\Test\PhpWarningTriggered; +use PHPUnit\Event\Test\WarningTriggered; +use PHPUnit\TestRunner\TestResult\Issues\Issue; +use PHPUnit\TestRunner\TestResult\TestResult; +use PHPUnit\TextUI\Output\Printer; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class ResultPrinter +{ + private readonly Printer $printer; + private readonly bool $displayPhpunitDeprecations; + private readonly bool $displayPhpunitErrors; + private readonly bool $displayPhpunitNotices; + private readonly bool $displayPhpunitWarnings; + private readonly bool $displayTestsWithErrors; + private readonly bool $displayTestsWithFailedAssertions; + private readonly bool $displayRiskyTests; + private readonly bool $displayDetailsOnIncompleteTests; + private readonly bool $displayDetailsOnSkippedTests; + private readonly bool $displayDetailsOnTestsThatTriggerDeprecations; + private readonly bool $displayDetailsOnTestsThatTriggerErrors; + private readonly bool $displayDetailsOnTestsThatTriggerNotices; + private readonly bool $displayDetailsOnTestsThatTriggerWarnings; + private readonly bool $displayDefectsInReverseOrder; + private bool $listPrinted = false; + + public function __construct(Printer $printer, bool $displayPhpunitDeprecations, bool $displayPhpunitErrors, bool $displayPhpunitNotices, bool $displayPhpunitWarnings, bool $displayTestsWithErrors, bool $displayTestsWithFailedAssertions, bool $displayRiskyTests, bool $displayDetailsOnIncompleteTests, bool $displayDetailsOnSkippedTests, bool $displayDetailsOnTestsThatTriggerDeprecations, bool $displayDetailsOnTestsThatTriggerErrors, bool $displayDetailsOnTestsThatTriggerNotices, bool $displayDetailsOnTestsThatTriggerWarnings, bool $displayDefectsInReverseOrder) + { + $this->printer = $printer; + $this->displayPhpunitDeprecations = $displayPhpunitDeprecations; + $this->displayPhpunitErrors = $displayPhpunitErrors; + $this->displayPhpunitNotices = $displayPhpunitNotices; + $this->displayPhpunitWarnings = $displayPhpunitWarnings; + $this->displayTestsWithErrors = $displayTestsWithErrors; + $this->displayTestsWithFailedAssertions = $displayTestsWithFailedAssertions; + $this->displayRiskyTests = $displayRiskyTests; + $this->displayDetailsOnIncompleteTests = $displayDetailsOnIncompleteTests; + $this->displayDetailsOnSkippedTests = $displayDetailsOnSkippedTests; + $this->displayDetailsOnTestsThatTriggerDeprecations = $displayDetailsOnTestsThatTriggerDeprecations; + $this->displayDetailsOnTestsThatTriggerErrors = $displayDetailsOnTestsThatTriggerErrors; + $this->displayDetailsOnTestsThatTriggerNotices = $displayDetailsOnTestsThatTriggerNotices; + $this->displayDetailsOnTestsThatTriggerWarnings = $displayDetailsOnTestsThatTriggerWarnings; + $this->displayDefectsInReverseOrder = $displayDefectsInReverseOrder; + } + + public function print(TestResult $result, bool $stackTraceForDeprecations = false): void + { + if ($this->displayPhpunitErrors) { + $this->printPhpunitErrors($result); + } + + if ($this->displayPhpunitWarnings) { + $this->printTestRunnerWarnings($result); + } + + if ($this->displayPhpunitDeprecations) { + $this->printTestRunnerDeprecations($result); + } + + if ($this->displayPhpunitNotices) { + $this->printTestRunnerNotices($result); + } + + if ($this->displayTestsWithErrors) { + $this->printTestsWithErrors($result); + } + + if ($this->displayTestsWithFailedAssertions) { + $this->printTestsWithFailedAssertions($result); + } + + if ($this->displayPhpunitWarnings) { + $this->printDetailsOnTestsThatTriggeredPhpunitWarnings($result); + } + + if ($this->displayPhpunitDeprecations) { + $this->printDetailsOnTestsThatTriggeredPhpunitDeprecations($result); + } + + if ($this->displayRiskyTests) { + $this->printRiskyTests($result); + } + + if ($this->displayPhpunitNotices) { + $this->printDetailsOnTestsThatTriggeredPhpunitNotices($result); + } + + if ($this->displayDetailsOnIncompleteTests) { + $this->printIncompleteTests($result); + } + + if ($this->displayDetailsOnSkippedTests) { + $this->printSkippedTestSuites($result); + $this->printSkippedTests($result); + } + + if ($this->displayDetailsOnTestsThatTriggerErrors) { + $this->printIssueList('error', $result->errors()); + } + + if ($this->displayDetailsOnTestsThatTriggerWarnings) { + $this->printIssueList('PHP warning', $result->phpWarnings()); + $this->printIssueList('warning', $result->warnings()); + } + + if ($this->displayDetailsOnTestsThatTriggerNotices) { + $this->printIssueList('PHP notice', $result->phpNotices()); + $this->printIssueList('notice', $result->notices()); + } + + if ($this->displayDetailsOnTestsThatTriggerDeprecations) { + $this->printIssueList('PHP deprecation', $result->phpDeprecations()); + $this->printIssueList('deprecation', $result->deprecations(), $stackTraceForDeprecations); + } + } + + private function printPhpunitErrors(TestResult $result): void + { + if (!$result->hasTestTriggeredPhpunitErrorEvents()) { + return; + } + + $elements = $this->mapTestsWithIssuesEventsToElements($result->testTriggeredPhpunitErrorEvents()); + + $this->printListHeaderWithNumber($elements['numberOfTestsWithIssues'], 'PHPUnit error'); + $this->printList($elements['elements']); + } + + private function printDetailsOnTestsThatTriggeredPhpunitDeprecations(TestResult $result): void + { + if (!$result->hasTestTriggeredPhpunitDeprecationEvents()) { + return; + } + + $elements = $this->mapTestsWithIssuesEventsToElements($result->testTriggeredPhpunitDeprecationEvents()); + + $this->printListHeaderWithNumberOfTestsAndNumberOfIssues( + $elements['numberOfTestsWithIssues'], + $elements['numberOfIssues'], + 'PHPUnit deprecation', + ); + + $this->printList($elements['elements']); + } + + private function printDetailsOnTestsThatTriggeredPhpunitNotices(TestResult $result): void + { + if (!$result->hasTestTriggeredPhpunitNoticeEvents()) { + return; + } + + $elements = $this->mapTestsWithIssuesEventsToElements($result->testTriggeredPhpunitNoticeEvents()); + + $this->printListHeaderWithNumberOfTestsAndNumberOfIssues( + $elements['numberOfTestsWithIssues'], + $elements['numberOfIssues'], + 'PHPUnit notice', + ); + + $this->printList($elements['elements']); + } + + private function printTestRunnerNotices(TestResult $result): void + { + if (!$result->hasTestRunnerTriggeredNoticeEvents()) { + return; + } + + $elements = []; + $messages = []; + + foreach ($result->testRunnerTriggeredNoticeEvents() as $event) { + if (isset($messages[$event->message()])) { + continue; + } + + $elements[] = [ + 'title' => $event->message(), + 'body' => '', + ]; + + $messages[$event->message()] = true; + } + + $this->printListHeaderWithNumber(count($elements), 'PHPUnit test runner notice'); + $this->printList($elements); + } + + private function printTestRunnerWarnings(TestResult $result): void + { + if (!$result->hasTestRunnerTriggeredWarningEvents()) { + return; + } + + $elements = []; + $messages = []; + + foreach ($result->testRunnerTriggeredWarningEvents() as $event) { + if (isset($messages[$event->message()])) { + continue; + } + + $elements[] = [ + 'title' => $event->message(), + 'body' => '', + ]; + + $messages[$event->message()] = true; + } + + $this->printListHeaderWithNumber(count($elements), 'PHPUnit test runner warning'); + $this->printList($elements); + } + + private function printTestRunnerDeprecations(TestResult $result): void + { + if (!$result->hasTestRunnerTriggeredDeprecationEvents()) { + return; + } + + $elements = []; + + foreach ($result->testRunnerTriggeredDeprecationEvents() as $event) { + $elements[] = [ + 'title' => $event->message(), + 'body' => '', + ]; + } + + $this->printListHeaderWithNumber(count($elements), 'PHPUnit test runner deprecation'); + $this->printList($elements); + } + + private function printDetailsOnTestsThatTriggeredPhpunitWarnings(TestResult $result): void + { + if (!$result->hasTestTriggeredPhpunitWarningEvents()) { + return; + } + + $elements = $this->mapTestsWithIssuesEventsToElements($result->testTriggeredPhpunitWarningEvents()); + + $this->printListHeaderWithNumberOfTestsAndNumberOfIssues( + $elements['numberOfTestsWithIssues'], + $elements['numberOfIssues'], + 'PHPUnit warning', + ); + + $this->printList($elements['elements']); + } + + private function printTestsWithErrors(TestResult $result): void + { + if (!$result->hasTestErroredEvents()) { + return; + } + + $elements = []; + + foreach ($result->testErroredEvents() as $event) { + if ($event instanceof AfterLastTestMethodErrored || $event instanceof BeforeFirstTestMethodErrored) { + $title = $event->testClassName(); + } else { + $title = $this->name($event->test()); + } + + $elements[] = [ + 'title' => $title, + 'body' => $event->throwable()->asString(), + ]; + } + + $this->printListHeaderWithNumber(count($elements), 'error'); + $this->printList($elements); + } + + private function printTestsWithFailedAssertions(TestResult $result): void + { + if (!$result->hasTestFailedEvents()) { + return; + } + + $elements = []; + + foreach ($result->testFailedEvents() as $event) { + $body = $event->throwable()->asString(); + + if (str_starts_with($body, 'AssertionError: ')) { + $body = substr($body, strlen('AssertionError: ')); + } + + $elements[] = [ + 'title' => $this->name($event->test()), + 'body' => $body, + ]; + } + + $this->printListHeaderWithNumber(count($elements), 'failure'); + $this->printList($elements); + } + + private function printRiskyTests(TestResult $result): void + { + if (!$result->hasTestConsideredRiskyEvents()) { + return; + } + + $elements = $this->mapTestsWithIssuesEventsToElements($result->testConsideredRiskyEvents()); + + $this->printListHeaderWithNumber($elements['numberOfTestsWithIssues'], 'risky test'); + $this->printList($elements['elements']); + } + + private function printIncompleteTests(TestResult $result): void + { + if (!$result->hasTestMarkedIncompleteEvents()) { + return; + } + + $elements = []; + + foreach ($result->testMarkedIncompleteEvents() as $event) { + $elements[] = [ + 'title' => $this->name($event->test()), + 'body' => $event->throwable()->asString(), + ]; + } + + $this->printListHeaderWithNumber(count($elements), 'incomplete test'); + $this->printList($elements); + } + + private function printSkippedTestSuites(TestResult $result): void + { + if (!$result->hasTestSuiteSkippedEvents()) { + return; + } + + $elements = []; + + foreach ($result->testSuiteSkippedEvents() as $event) { + $elements[] = [ + 'title' => $event->testSuite()->name(), + 'body' => $event->message(), + ]; + } + + $this->printListHeaderWithNumber(count($elements), 'skipped test suite'); + $this->printList($elements); + } + + private function printSkippedTests(TestResult $result): void + { + if (!$result->hasTestSkippedEvents()) { + return; + } + + $elements = []; + + foreach ($result->testSkippedEvents() as $event) { + $elements[] = [ + 'title' => $this->name($event->test()), + 'body' => $event->message(), + ]; + } + + $this->printListHeaderWithNumber(count($elements), 'skipped test'); + $this->printList($elements); + } + + /** + * @param non-empty-string $type + * @param list $issues + */ + private function printIssueList(string $type, array $issues, bool $stackTrace = false): void + { + if ($issues === []) { + return; + } + + $numberOfUniqueIssues = count($issues); + $triggeringTests = []; + + foreach ($issues as $issue) { + $triggeringTests = array_merge($triggeringTests, array_keys($issue->triggeringTests())); + } + + $numberOfTests = count(array_unique($triggeringTests)); + unset($triggeringTests); + + $this->printListHeader( + sprintf( + '%d test%s triggered %d %s%s:' . PHP_EOL . PHP_EOL, + $numberOfTests, + $numberOfTests !== 1 ? 's' : '', + $numberOfUniqueIssues, + $type, + $numberOfUniqueIssues !== 1 ? 's' : '', + ), + ); + + $i = 1; + + foreach ($issues as $issue) { + $title = sprintf( + '%s:%d', + $issue->file(), + $issue->line(), + ); + + $body = trim($issue->description()) . PHP_EOL . PHP_EOL; + + if ($stackTrace && $issue->hasStackTrace()) { + $body .= trim($issue->stackTrace()) . PHP_EOL . PHP_EOL; + } + + if (!$issue->triggeredInTest()) { + $body .= 'Triggered by:'; + + $triggeringTests = $issue->triggeringTests(); + + ksort($triggeringTests); + + foreach ($triggeringTests as $triggeringTest) { + $body .= PHP_EOL . PHP_EOL . '* ' . $triggeringTest['test']->id(); + + if ($triggeringTest['count'] > 1) { + $body .= sprintf( + ' (%d times)', + $triggeringTest['count'], + ); + } + + if ($triggeringTest['test']->isTestMethod()) { + $body .= PHP_EOL . ' ' . $triggeringTest['test']->file() . ':' . $triggeringTest['test']->line(); + } + } + } + + $this->printIssueListElement($i++, $title, $body); + + $this->printer->print(PHP_EOL); + } + } + + private function printListHeaderWithNumberOfTestsAndNumberOfIssues(int $numberOfTestsWithIssues, int $numberOfIssues, string $type): void + { + $this->printListHeader( + sprintf( + "%d test%s triggered %d %s%s:\n\n", + $numberOfTestsWithIssues, + $numberOfTestsWithIssues !== 1 ? 's' : '', + $numberOfIssues, + $type, + $numberOfIssues !== 1 ? 's' : '', + ), + ); + } + + private function printListHeaderWithNumber(int $number, string $type): void + { + $this->printListHeader( + sprintf( + "There %s %d %s%s:\n\n", + ($number === 1) ? 'was' : 'were', + $number, + $type, + ($number === 1) ? '' : 's', + ), + ); + } + + private function printListHeader(string $header): void + { + if ($this->listPrinted) { + $this->printer->print("--\n\n"); + } + + $this->listPrinted = true; + + $this->printer->print($header); + } + + /** + * @param list $elements + */ + private function printList(array $elements): void + { + $i = 1; + + if ($this->displayDefectsInReverseOrder) { + $elements = array_reverse($elements); + } + + foreach ($elements as $element) { + $this->printListElement($i++, $element['title'], $element['body']); + } + + $this->printer->print("\n"); + } + + private function printListElement(int $number, string $title, string $body): void + { + $body = trim($body); + + $this->printer->print( + sprintf( + "%s%d) %s\n%s%s", + $number > 1 ? "\n" : '', + $number, + $title, + $body, + $body !== '' ? "\n" : '', + ), + ); + } + + private function printIssueListElement(int $number, string $title, string $body): void + { + $body = trim($body); + + $this->printer->print( + sprintf( + "%d) %s\n%s%s", + $number, + $title, + $body, + $body !== '' ? "\n" : '', + ), + ); + } + + private function name(Test $test): string + { + if ($test->isTestMethod()) { + assert($test instanceof TestMethod); + + if (!$test->testData()->hasDataFromDataProvider()) { + return $test->nameWithClass(); + } + + return $test->className() . '::' . $test->methodName() . $test->testData()->dataFromDataProvider()->dataAsStringForResultOutput(); + } + + return $test->name(); + } + + /** + * @param array> $events + * + * @return array{numberOfTestsWithIssues: int, numberOfIssues: int, elements: list} + */ + private function mapTestsWithIssuesEventsToElements(array $events): array + { + $elements = []; + $issues = 0; + + foreach ($events as $reasons) { + $test = $reasons[0]->test(); + $testLocation = $this->testLocation($test); + $title = $this->name($test); + $body = ''; + $first = true; + $single = count($reasons) === 1; + + foreach ($reasons as $reason) { + if ($first) { + $first = false; + } else { + $body .= PHP_EOL; + } + + $body .= $this->reasonMessage($reason, $single); + $body .= $this->reasonLocation($reason, $single); + + $issues++; + } + + if ($testLocation !== '') { + $body .= $testLocation; + } + + $elements[] = [ + 'title' => $title, + 'body' => $body, + ]; + } + + return [ + 'numberOfTestsWithIssues' => count($events), + 'numberOfIssues' => $issues, + 'elements' => $elements, + ]; + } + + private function testLocation(Test $test): string + { + if (!$test->isTestMethod()) { + return ''; + } + + assert($test instanceof TestMethod); + + return sprintf( + '%s%s:%d%s', + PHP_EOL, + $test->file(), + $test->line(), + PHP_EOL, + ); + } + + private function reasonMessage(ConsideredRisky|DeprecationTriggered|ErrorTriggered|NoticeTriggered|PhpDeprecationTriggered|PhpNoticeTriggered|PhpunitDeprecationTriggered|PhpunitErrorTriggered|PhpunitNoticeTriggered|PhpunitWarningTriggered|PhpWarningTriggered|WarningTriggered $reason, bool $single): string + { + $message = trim($reason->message()); + + if ($single) { + return $message . PHP_EOL; + } + + $lines = explode(PHP_EOL, $message); + $buffer = '* ' . $lines[0] . PHP_EOL; + + if (count($lines) > 1) { + foreach (range(1, count($lines) - 1) as $line) { + $buffer .= ' ' . $lines[$line] . PHP_EOL; + } + } + + return $buffer; + } + + private function reasonLocation(ConsideredRisky|DeprecationTriggered|ErrorTriggered|NoticeTriggered|PhpDeprecationTriggered|PhpNoticeTriggered|PhpunitDeprecationTriggered|PhpunitErrorTriggered|PhpunitNoticeTriggered|PhpunitWarningTriggered|PhpWarningTriggered|WarningTriggered $reason, bool $single): string + { + if (!$reason instanceof DeprecationTriggered && + !$reason instanceof PhpDeprecationTriggered && + !$reason instanceof ErrorTriggered && + !$reason instanceof NoticeTriggered && + !$reason instanceof PhpNoticeTriggered && + !$reason instanceof WarningTriggered && + !$reason instanceof PhpWarningTriggered) { + return ''; + } + + return sprintf( + '%s%s:%d%s', + $single ? '' : ' ', + $reason->file(), + $reason->line(), + PHP_EOL, + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/UnexpectedOutputPrinter.php b/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/UnexpectedOutputPrinter.php new file mode 100644 index 000000000..b51d51060 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Output/Default/UnexpectedOutputPrinter.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output\Default; + +use PHPUnit\Event\Facade; +use PHPUnit\Event\Test\PrintedUnexpectedOutput; +use PHPUnit\Event\Test\PrintedUnexpectedOutputSubscriber; +use PHPUnit\TextUI\Output\Printer; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final readonly class UnexpectedOutputPrinter implements PrintedUnexpectedOutputSubscriber +{ + private Printer $printer; + + public function __construct(Printer $printer, Facade $facade) + { + $this->printer = $printer; + + $facade->registerSubscriber($this); + } + + public function notify(PrintedUnexpectedOutput $event): void + { + $this->printer->print($event->output()); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Output/Facade.php b/app/vendor/phpunit/phpunit/src/TextUI/Output/Facade.php new file mode 100644 index 000000000..2a50017b0 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Output/Facade.php @@ -0,0 +1,275 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output; + +use const PHP_EOL; +use function assert; +use PHPUnit\Event\Facade as EventFacade; +use PHPUnit\Logging\TeamCity\TeamCityLogger; +use PHPUnit\Logging\TestDox\TestResultCollection; +use PHPUnit\Runner\DirectoryDoesNotExistException; +use PHPUnit\TestRunner\TestResult\TestResult; +use PHPUnit\TextUI\CannotOpenSocketException; +use PHPUnit\TextUI\Configuration\Configuration; +use PHPUnit\TextUI\InvalidSocketException; +use PHPUnit\TextUI\Output\Default\ProgressPrinter\ProgressPrinter as DefaultProgressPrinter; +use PHPUnit\TextUI\Output\Default\ResultPrinter as DefaultResultPrinter; +use PHPUnit\TextUI\Output\Default\UnexpectedOutputPrinter; +use PHPUnit\TextUI\Output\TestDox\ResultPrinter as TestDoxResultPrinter; +use SebastianBergmann\Timer\Duration; +use SebastianBergmann\Timer\ResourceUsageFormatter; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class Facade +{ + private static ?Printer $printer = null; + private static ?DefaultResultPrinter $defaultResultPrinter = null; + private static ?TestDoxResultPrinter $testDoxResultPrinter = null; + private static ?SummaryPrinter $summaryPrinter = null; + private static bool $defaultProgressPrinter = false; + + public static function init(Configuration $configuration, bool $extensionReplacesProgressOutput, bool $extensionReplacesResultOutput): Printer + { + self::createPrinter($configuration); + + assert(self::$printer !== null); + + if ($configuration->debug()) { + return self::$printer; + } + + self::createUnexpectedOutputPrinter(); + + if (!$extensionReplacesProgressOutput) { + self::createProgressPrinter($configuration); + } + + if (!$extensionReplacesResultOutput) { + self::createResultPrinter($configuration); + self::createSummaryPrinter($configuration); + } + + if ($configuration->outputIsTeamCity()) { + new TeamCityLogger( + DefaultPrinter::standardOutput(), + EventFacade::instance(), + ); + } + + return self::$printer; + } + + /** + * @param ?array $testDoxResult + */ + public static function printResult(TestResult $result, ?array $testDoxResult, Duration $duration, bool $stackTraceForDeprecations): void + { + assert(self::$printer !== null); + + if ($result->numberOfTestsRun() > 0) { + if (self::$defaultProgressPrinter) { + self::$printer->print(PHP_EOL . PHP_EOL); + } + + self::$printer->print((new ResourceUsageFormatter)->resourceUsage($duration) . PHP_EOL . PHP_EOL); + } + + if (self::$testDoxResultPrinter !== null && $testDoxResult !== null) { + self::$testDoxResultPrinter->print($result, $testDoxResult); + } + + if (self::$defaultResultPrinter !== null) { + self::$defaultResultPrinter->print($result, $stackTraceForDeprecations); + } + + if (self::$summaryPrinter !== null) { + self::$summaryPrinter->print($result); + } + } + + /** + * @throws CannotOpenSocketException + * @throws DirectoryDoesNotExistException + * @throws InvalidSocketException + */ + public static function printerFor(string $target): Printer + { + if ($target === 'php://stdout') { + if (!self::$printer instanceof NullPrinter) { + return self::$printer; + } + + return DefaultPrinter::standardOutput(); + } + + return DefaultPrinter::from($target); + } + + private static function createPrinter(Configuration $configuration): void + { + $printerNeeded = false; + + if ($configuration->debug()) { + $printerNeeded = true; + } + + if ($configuration->outputIsTeamCity()) { + $printerNeeded = true; + } + + if ($configuration->outputIsTestDox()) { + $printerNeeded = true; + } + + if (!$configuration->noOutput() && !$configuration->noProgress()) { + $printerNeeded = true; + } + + if (!$configuration->noOutput() && !$configuration->noResults()) { + $printerNeeded = true; + } + + if ($printerNeeded) { + if ($configuration->outputToStandardErrorStream()) { + self::$printer = DefaultPrinter::standardError(); + + return; + } + + self::$printer = DefaultPrinter::standardOutput(); + + return; + } + + self::$printer = new NullPrinter; + } + + private static function createProgressPrinter(Configuration $configuration): void + { + assert(self::$printer !== null); + + if (!self::useDefaultProgressPrinter($configuration)) { + return; + } + + new DefaultProgressPrinter( + self::$printer, + EventFacade::instance(), + $configuration->colors(), + $configuration->columns(), + $configuration->source(), + ); + + self::$defaultProgressPrinter = true; + } + + private static function useDefaultProgressPrinter(Configuration $configuration): bool + { + if ($configuration->noOutput()) { + return false; + } + + if ($configuration->noProgress()) { + return false; + } + + if ($configuration->outputIsTeamCity()) { + return false; + } + + return true; + } + + private static function createResultPrinter(Configuration $configuration): void + { + assert(self::$printer !== null); + + if ($configuration->outputIsTestDox()) { + self::$defaultResultPrinter = new DefaultResultPrinter( + self::$printer, + $configuration->displayDetailsOnPhpunitDeprecations() || $configuration->displayDetailsOnAllIssues(), + true, + $configuration->displayDetailsOnPhpunitNotices() || $configuration->displayDetailsOnAllIssues(), + true, + false, + false, + true, + false, + false, + $configuration->displayDetailsOnTestsThatTriggerDeprecations() || $configuration->displayDetailsOnAllIssues(), + $configuration->displayDetailsOnTestsThatTriggerErrors() || $configuration->displayDetailsOnAllIssues(), + $configuration->displayDetailsOnTestsThatTriggerNotices() || $configuration->displayDetailsOnAllIssues(), + $configuration->displayDetailsOnTestsThatTriggerWarnings() || $configuration->displayDetailsOnAllIssues(), + $configuration->reverseDefectList(), + ); + } + + if ($configuration->outputIsTestDox()) { + self::$testDoxResultPrinter = new TestDoxResultPrinter( + self::$printer, + $configuration->colors(), + $configuration->columns(), + $configuration->testDoxOutputWithSummary(), + ); + } + + if ($configuration->noOutput() || $configuration->noResults()) { + return; + } + + if (self::$defaultResultPrinter !== null) { + return; + } + + self::$defaultResultPrinter = new DefaultResultPrinter( + self::$printer, + $configuration->displayDetailsOnPhpunitDeprecations() || $configuration->displayDetailsOnAllIssues(), + true, + $configuration->displayDetailsOnPhpunitNotices() || $configuration->displayDetailsOnAllIssues(), + true, + true, + true, + true, + $configuration->displayDetailsOnIncompleteTests() || $configuration->displayDetailsOnAllIssues(), + $configuration->displayDetailsOnSkippedTests() || $configuration->displayDetailsOnAllIssues(), + $configuration->displayDetailsOnTestsThatTriggerDeprecations() || $configuration->displayDetailsOnAllIssues(), + $configuration->displayDetailsOnTestsThatTriggerErrors() || $configuration->displayDetailsOnAllIssues(), + $configuration->displayDetailsOnTestsThatTriggerNotices() || $configuration->displayDetailsOnAllIssues(), + $configuration->displayDetailsOnTestsThatTriggerWarnings() || $configuration->displayDetailsOnAllIssues(), + $configuration->reverseDefectList(), + ); + } + + private static function createSummaryPrinter(Configuration $configuration): void + { + assert(self::$printer !== null); + + if (($configuration->noOutput() || $configuration->noResults()) && + !($configuration->outputIsTeamCity() || $configuration->outputIsTestDox())) { + return; + } + + self::$summaryPrinter = new SummaryPrinter( + self::$printer, + $configuration->colors(), + ); + } + + private static function createUnexpectedOutputPrinter(): void + { + assert(self::$printer !== null); + + new UnexpectedOutputPrinter(self::$printer, EventFacade::instance()); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Output/Printer/DefaultPrinter.php b/app/vendor/phpunit/phpunit/src/TextUI/Output/Printer/DefaultPrinter.php new file mode 100644 index 000000000..382f48154 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Output/Printer/DefaultPrinter.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 PHPUnit\TextUI\Output; + +use function assert; +use function count; +use function dirname; +use function explode; +use function fclose; +use function fopen; +use function fsockopen; +use function fwrite; +use function str_replace; +use function str_starts_with; +use PHPUnit\Runner\DirectoryDoesNotExistException; +use PHPUnit\TextUI\CannotOpenSocketException; +use PHPUnit\TextUI\InvalidSocketException; +use PHPUnit\Util\Filesystem; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class DefaultPrinter implements Printer +{ + /** + * @var closed-resource|resource + */ + private $stream; + private readonly bool $isPhpStream; + private bool $isOpen; + + /** + * @throws CannotOpenSocketException + * @throws DirectoryDoesNotExistException + * @throws InvalidSocketException + */ + public static function from(string $out): self + { + return new self($out); + } + + /** + * @throws CannotOpenSocketException + * @throws DirectoryDoesNotExistException + * @throws InvalidSocketException + */ + public static function standardOutput(): self + { + return new self('php://stdout'); + } + + /** + * @throws CannotOpenSocketException + * @throws DirectoryDoesNotExistException + * @throws InvalidSocketException + */ + public static function standardError(): self + { + return new self('php://stderr'); + } + + /** + * @throws CannotOpenSocketException + * @throws DirectoryDoesNotExistException + * @throws InvalidSocketException + */ + private function __construct(string $out) + { + $this->isPhpStream = str_starts_with($out, 'php://'); + + if (str_starts_with($out, 'socket://')) { + $tmp = explode(':', str_replace('socket://', '', $out)); + + if (count($tmp) !== 2) { + throw new InvalidSocketException($out); + } + + $stream = @fsockopen($tmp[0], (int) $tmp[1]); + + if ($stream === false) { + throw new CannotOpenSocketException($tmp[0], (int) $tmp[1]); + } + + $this->stream = $stream; + $this->isOpen = true; + + return; + } + + if (!$this->isPhpStream && !Filesystem::createDirectory(dirname($out))) { + throw new DirectoryDoesNotExistException(dirname($out)); + } + + $stream = fopen($out, 'wb'); + + assert($stream !== false); + + $this->stream = $stream; + $this->isOpen = true; + } + + public function print(string $buffer): void + { + assert($this->isOpen); + + fwrite($this->stream, $buffer); + } + + public function flush(): void + { + if ($this->isOpen && $this->isPhpStream) { + fclose($this->stream); + + $this->isOpen = false; + } + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Output/Printer/NullPrinter.php b/app/vendor/phpunit/phpunit/src/TextUI/Output/Printer/NullPrinter.php new file mode 100644 index 000000000..5e6b7ddf2 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Output/Printer/NullPrinter.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class NullPrinter implements Printer +{ + public function print(string $buffer): void + { + } + + public function flush(): void + { + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Output/Printer/Printer.php b/app/vendor/phpunit/phpunit/src/TextUI/Output/Printer/Printer.php new file mode 100644 index 000000000..c9b0fb970 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Output/Printer/Printer.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This interface is not covered by the backward compatibility promise for PHPUnit + */ +interface Printer +{ + public function print(string $buffer): void; + + public function flush(): void; +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Output/SummaryPrinter.php b/app/vendor/phpunit/phpunit/src/TextUI/Output/SummaryPrinter.php new file mode 100644 index 000000000..4f3e66183 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Output/SummaryPrinter.php @@ -0,0 +1,161 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output; + +use const PHP_EOL; +use function sprintf; +use PHPUnit\TestRunner\TestResult\TestResult; +use PHPUnit\Util\Color; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class SummaryPrinter +{ + private readonly Printer $printer; + private readonly bool $colors; + private bool $countPrinted = false; + + public function __construct(Printer $printer, bool $colors) + { + $this->printer = $printer; + $this->colors = $colors; + } + + public function print(TestResult $result): void + { + if ($result->numberOfTestsRun() === 0) { + $this->printWithColor( + 'fg-black, bg-yellow', + 'No tests executed!', + ); + + return; + } + + if ($result->wasSuccessful() && + !$result->hasIssues() && + !$result->hasTestSuiteSkippedEvents() && + !$result->hasTestSkippedEvents()) { + $this->printWithColor( + 'fg-black, bg-green', + sprintf( + 'OK (%d test%s, %d assertion%s)', + $result->numberOfTestsRun(), + $result->numberOfTestsRun() === 1 ? '' : 's', + $result->numberOfAssertions(), + $result->numberOfAssertions() === 1 ? '' : 's', + ), + ); + + $this->printNumberOfIssuesIgnoredByBaseline($result); + + return; + } + + $color = 'fg-black, bg-yellow'; + + if ($result->wasSuccessful()) { + if ($result->hasIssues()) { + $this->printWithColor( + $color, + 'OK, but there were issues!', + ); + } else { + $this->printWithColor( + $color, + 'OK, but some tests were skipped!', + ); + } + } else { + if ($result->hasTestErroredEvents() || $result->hasTestTriggeredPhpunitErrorEvents()) { + $color = 'fg-white, bg-red'; + + $this->printWithColor( + $color, + 'ERRORS!', + ); + } else { + $color = 'fg-white, bg-red'; + + $this->printWithColor( + $color, + 'FAILURES!', + ); + } + } + + $this->printCountString($result->numberOfTestsRun(), 'Tests', $color, true); + $this->printCountString($result->numberOfAssertions(), 'Assertions', $color, true); + $this->printCountString($result->numberOfErrors(), 'Errors', $color); + $this->printCountString($result->numberOfTestFailedEvents(), 'Failures', $color); + $this->printCountString($result->numberOfPhpunitWarnings(), 'PHPUnit Warnings', $color); + $this->printCountString($result->numberOfWarnings(), 'Warnings', $color); + $this->printCountString($result->numberOfPhpOrUserDeprecations(), 'Deprecations', $color); + $this->printCountString($result->numberOfPhpunitDeprecations(), 'PHPUnit Deprecations', $color); + $this->printCountString($result->numberOfPhpunitNotices(), 'PHPUnit Notices', $color); + $this->printCountString($result->numberOfNotices(), 'Notices', $color); + $this->printCountString($result->numberOfTestSuiteSkippedEvents() + $result->numberOfTestSkippedEvents(), 'Skipped', $color); + $this->printCountString($result->numberOfTestMarkedIncompleteEvents(), 'Incomplete', $color); + $this->printCountString($result->numberOfTestsWithTestConsideredRiskyEvents(), 'Risky', $color); + $this->printWithColor($color, '.'); + + $this->printNumberOfIssuesIgnoredByBaseline($result); + } + + private function printCountString(int $count, string $name, string $color, bool $always = false): void + { + if ($always || $count > 0) { + $this->printWithColor( + $color, + sprintf( + '%s%s: %d', + $this->countPrinted ? ', ' : '', + $name, + $count, + ), + false, + ); + + $this->countPrinted = true; + } + } + + private function printWithColor(string $color, string $buffer, bool $lf = true): void + { + if ($this->colors) { + $buffer = Color::colorizeTextBox($color, $buffer); + } + + $this->printer->print($buffer); + + if ($lf) { + $this->printer->print(PHP_EOL); + } + } + + private function printNumberOfIssuesIgnoredByBaseline(TestResult $result): void + { + if ($result->hasIssuesIgnoredByBaseline()) { + $this->printer->print( + sprintf( + '%s%d issue%s %s ignored by baseline.%s', + PHP_EOL, + $result->numberOfIssuesIgnoredByBaseline(), + $result->numberOfIssuesIgnoredByBaseline() > 1 ? 's' : '', + $result->numberOfIssuesIgnoredByBaseline() > 1 ? 'were' : 'was', + PHP_EOL, + ), + ); + } + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/Output/TestDox/ResultPrinter.php b/app/vendor/phpunit/phpunit/src/TextUI/Output/TestDox/ResultPrinter.php new file mode 100644 index 000000000..27ae3cede --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/Output/TestDox/ResultPrinter.php @@ -0,0 +1,502 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI\Output\TestDox; + +use const PHP_EOL; +use function array_map; +use function explode; +use function implode; +use function preg_match; +use function preg_split; +use function rtrim; +use function sprintf; +use function str_starts_with; +use function trim; +use PHPUnit\Event\Code\Throwable; +use PHPUnit\Event\Test\AfterLastTestMethodErrored; +use PHPUnit\Event\Test\BeforeFirstTestMethodErrored; +use PHPUnit\Framework\TestStatus\TestStatus; +use PHPUnit\Logging\TestDox\TestResult as TestDoxTestResult; +use PHPUnit\Logging\TestDox\TestResultCollection; +use PHPUnit\TestRunner\TestResult\TestResult; +use PHPUnit\TextUI\Output\Printer; +use PHPUnit\Util\Color; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ResultPrinter +{ + private Printer $printer; + private bool $colors; + private int $columns; + private bool $printSummary; + + public function __construct(Printer $printer, bool $colors, int $columns, bool $printSummary) + { + $this->printer = $printer; + $this->colors = $colors; + $this->columns = $columns; + $this->printSummary = $printSummary; + } + + /** + * @param array $tests + */ + public function print(TestResult $result, array $tests): void + { + $this->doPrint($tests, false); + + if ($this->printSummary) { + $this->printer->print('Summary of tests with errors, failures, or issues:' . PHP_EOL . PHP_EOL); + + $this->doPrint($tests, true); + } + + $beforeFirstTestMethodErrored = []; + $afterLastTestMethodErrored = []; + + foreach ($result->testErroredEvents() as $error) { + if ($error instanceof BeforeFirstTestMethodErrored) { + $beforeFirstTestMethodErrored[$error->calledMethod()->className() . '::' . $error->calledMethod()->methodName()] = $error; + } + + if ($error instanceof AfterLastTestMethodErrored) { + $afterLastTestMethodErrored[$error->calledMethod()->className() . '::' . $error->calledMethod()->methodName()] = $error; + } + } + + $this->printBeforeClassOrAfterClassErrors( + 'before-first-test', + $beforeFirstTestMethodErrored, + ); + + $this->printBeforeClassOrAfterClassErrors( + 'after-last-test', + $afterLastTestMethodErrored, + ); + } + + /** + * @param array $tests + */ + private function doPrint(array $tests, bool $onlySummary): void + { + foreach ($tests as $prettifiedClassName => $_tests) { + $print = true; + + if ($onlySummary) { + $found = false; + + foreach ($_tests as $test) { + if ($test->status()->isSuccess()) { + continue; + } + + $found = true; + + break; + } + + if (!$found) { + $print = false; + } + } + + if (!$print) { + continue; + } + + $this->printPrettifiedClassName($prettifiedClassName); + + foreach ($_tests as $test) { + if ($onlySummary && $test->status()->isSuccess()) { + continue; + } + + $this->printTestResult($test); + } + + $this->printer->print(PHP_EOL); + } + } + + private function printPrettifiedClassName(string $prettifiedClassName): void + { + $buffer = $prettifiedClassName; + + if ($this->colors) { + $buffer = Color::colorizeTextBox('underlined', $buffer); + } + + $this->printer->print($buffer . PHP_EOL); + } + + private function printTestResult(TestDoxTestResult $test): void + { + $this->printTestResultHeader($test); + $this->printTestResultBody($test); + } + + private function printTestResultHeader(TestDoxTestResult $test): void + { + $buffer = ' ' . $this->symbolFor($test->status()) . ' '; + + if ($this->colors) { + $this->printer->print( + Color::colorizeTextBox( + $this->colorFor($test->status()), + $buffer, + ), + ); + } else { + $this->printer->print($buffer); + } + + $this->printer->print($test->test()->testDox()->prettifiedMethodName($this->colors) . PHP_EOL); + } + + private function printTestResultBody(TestDoxTestResult $test): void + { + if ($test->status()->isSuccess()) { + return; + } + + if (!$test->hasThrowable()) { + return; + } + + $this->printTestResultBodyStart($test); + $this->printThrowable($test->status(), $test->throwable()); + $this->printTestResultBodyEnd($test); + } + + private function printTestResultBodyStart(TestDoxTestResult $test): void + { + $this->printer->print( + $this->prefixLines( + $this->prefixFor('start', $test->status()), + '', + ), + ); + + $this->printer->print(PHP_EOL); + } + + private function printTestResultBodyEnd(TestDoxTestResult $test): void + { + $this->printer->print(PHP_EOL); + + $this->printer->print( + $this->prefixLines( + $this->prefixFor('last', $test->status()), + '', + ), + ); + + $this->printer->print(PHP_EOL); + } + + private function printThrowable(TestStatus $status, Throwable $throwable): void + { + $message = trim($throwable->description()); + $stackTrace = $this->formatStackTrace($throwable->stackTrace()); + $diff = ''; + + if ($message !== '' && $this->colors) { + ['message' => $message, 'diff' => $diff] = $this->colorizeMessageAndDiff( + $message, + $this->messageColorFor($status), + ); + } + + if ($message !== '') { + $this->printer->print( + $this->prefixLines( + $this->prefixFor('message', $status), + $message, + ), + ); + + $this->printer->print(PHP_EOL); + } + + if ($diff !== '') { + $this->printer->print( + $this->prefixLines( + $this->prefixFor('diff', $status), + $diff, + ), + ); + + $this->printer->print(PHP_EOL); + } + + if ($stackTrace !== '') { + if ($message !== '' || $diff !== '') { + $tracePrefix = $this->prefixFor('default', $status); + } else { + $tracePrefix = $this->prefixFor('trace', $status); + } + + $this->printer->print( + $this->prefixLines($tracePrefix, PHP_EOL . $stackTrace), + ); + } + + if ($throwable->hasPrevious()) { + $this->printer->print(PHP_EOL); + + $this->printer->print( + $this->prefixLines( + $this->prefixFor('default', $status), + ' ', + ), + ); + + $this->printer->print(PHP_EOL); + + $this->printer->print( + $this->prefixLines( + $this->prefixFor('default', $status), + 'Caused by:', + ), + ); + + $this->printer->print(PHP_EOL); + + $this->printThrowable($status, $throwable->previous()); + } + } + + /** + * @return array{message: string, diff: string} + */ + private function colorizeMessageAndDiff(string $buffer, string $style): array + { + $lines = []; + + if ($buffer !== '') { + $lines = array_map('\rtrim', explode(PHP_EOL, $buffer)); + } + + $message = []; + $diff = []; + $insideDiff = false; + + foreach ($lines as $line) { + if ($line === '--- Expected') { + $insideDiff = true; + } + + if (!$insideDiff) { + $message[] = $line; + } else { + if (str_starts_with($line, '-')) { + $line = Color::colorize('fg-red', Color::visualizeWhitespace($line, true)); + } elseif (str_starts_with($line, '+')) { + $line = Color::colorize('fg-green', Color::visualizeWhitespace($line, true)); + } elseif ($line === '@@ @@') { + $line = Color::colorize('fg-cyan', $line); + } + + $diff[] = $line; + } + } + + $message = implode(PHP_EOL, $message); + $diff = implode(PHP_EOL, $diff); + + if ($message !== '') { + // Testdox output has a left-margin of 5; keep right-margin to prevent terminal scrolling + $message = Color::colorizeTextBox($style, $message, $this->columns - 7); + } + + return [ + 'message' => $message, + 'diff' => $diff, + ]; + } + + private function formatStackTrace(string $stackTrace): string + { + if (!$this->colors) { + return rtrim($stackTrace); + } + + $lines = []; + $previousPath = ''; + + foreach (explode(PHP_EOL, $stackTrace) as $line) { + if (preg_match('/^(.*):(\d+)$/', $line, $matches) > 0) { + $lines[] = Color::colorizePath($matches[1], $previousPath) . Color::dim(':') . Color::colorize('fg-blue', $matches[2]) . "\n"; + $previousPath = $matches[1]; + + continue; + } + + $lines[] = $line; + $previousPath = ''; + } + + return rtrim(implode('', $lines)); + } + + private function prefixLines(string $prefix, string $message): string + { + $lines = preg_split('/\r\n|\r|\n/', $message); + + if ($lines === false) { + $lines = []; + } + + return implode( + PHP_EOL, + array_map( + static fn (string $line) => ' ' . $prefix . ($line !== '' ? ' ' . $line : ''), + $lines, + ), + ); + } + + /** + * @param 'default'|'diff'|'last'|'message'|'start'|'trace' $type + */ + private function prefixFor(string $type, TestStatus $status): string + { + if (!$this->colors) { + return '│'; + } + + return Color::colorize( + $this->colorFor($status), + match ($type) { + 'default' => '│', + 'start' => '┐', + 'message' => '├', + 'diff' => '┊', + 'trace' => '╵', + 'last' => '┴', + }, + ); + } + + private function colorFor(TestStatus $status): string + { + if ($status->isSuccess()) { + return 'fg-green'; + } + + if ($status->isError()) { + return 'fg-yellow'; + } + + if ($status->isFailure()) { + return 'fg-red'; + } + + if ($status->isSkipped()) { + return 'fg-cyan'; + } + + if ($status->isIncomplete() || $status->isDeprecation() || $status->isNotice() || $status->isRisky() || $status->isWarning()) { + return 'fg-yellow'; + } + + return 'fg-blue'; + } + + private function messageColorFor(TestStatus $status): string + { + if ($status->isSuccess()) { + return ''; + } + + if ($status->isError()) { + return 'bg-yellow,fg-black'; + } + + if ($status->isFailure()) { + return 'bg-red,fg-white'; + } + + if ($status->isSkipped()) { + return 'fg-cyan'; + } + + if ($status->isIncomplete() || $status->isDeprecation() || $status->isNotice() || $status->isRisky() || $status->isWarning()) { + return 'fg-yellow'; + } + + return 'fg-white,bg-blue'; + } + + private function symbolFor(TestStatus $status): string + { + if ($status->isSuccess()) { + return '✔'; + } + + if ($status->isError() || $status->isFailure()) { + return '✘'; + } + + if ($status->isSkipped()) { + return '↩'; + } + + if ($status->isDeprecation() || $status->isNotice() || $status->isRisky() || $status->isWarning()) { + return '⚠'; + } + + if ($status->isIncomplete()) { + return '∅'; + } + + return '?'; + } + + /** + * @param 'after-last-test'|'before-first-test' $type + * @param array $errors + */ + private function printBeforeClassOrAfterClassErrors(string $type, array $errors): void + { + if ($errors === []) { + return; + } + + $this->printer->print( + sprintf( + 'These %s methods errored:' . PHP_EOL . PHP_EOL, + $type, + ), + ); + + $index = 0; + + foreach ($errors as $method => $error) { + $this->printer->print( + sprintf( + '%d) %s' . PHP_EOL, + ++$index, + $method, + ), + ); + + $this->printer->print(trim($error->throwable()->description()) . PHP_EOL . PHP_EOL); + $this->printer->print($this->formatStackTrace($error->throwable()->stackTrace()) . PHP_EOL); + } + + $this->printer->print(PHP_EOL); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/ResultPrinter.php b/app/vendor/phpunit/phpunit/src/TextUI/ResultPrinter.php deleted file mode 100644 index ec89f6006..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/ResultPrinter.php +++ /dev/null @@ -1,23 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI; - -use PHPUnit\Framework\TestListener; -use PHPUnit\Framework\TestResult; - -/** - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - */ -interface ResultPrinter extends TestListener -{ - public function printResult(TestResult $result): void; - - public function write(string $buffer): void; -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/ShellExitCodeCalculator.php b/app/vendor/phpunit/phpunit/src/TextUI/ShellExitCodeCalculator.php new file mode 100644 index 000000000..c566a3832 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/ShellExitCodeCalculator.php @@ -0,0 +1,184 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI; + +use PHPUnit\TestRunner\TestResult\TestResult; +use PHPUnit\TextUI\Configuration\Configuration; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ShellExitCodeCalculator +{ + private const int SUCCESS_EXIT = 0; + private const int FAILURE_EXIT = 1; + private const int EXCEPTION_EXIT = 2; + + public function calculate(Configuration $configuration, TestResult $result): int + { + $failOnDeprecation = false; + $failOnPhpunitDeprecation = false; + $failOnPhpunitNotice = false; + $failOnPhpunitWarning = true; + $failOnEmptyTestSuite = false; + $failOnIncomplete = false; + $failOnNotice = false; + $failOnRisky = false; + $failOnSkipped = false; + $failOnWarning = false; + + if ($configuration->failOnAllIssues()) { + $failOnDeprecation = true; + $failOnPhpunitDeprecation = true; + $failOnPhpunitNotice = true; + $failOnPhpunitWarning = true; + $failOnEmptyTestSuite = true; + $failOnIncomplete = true; + $failOnNotice = true; + $failOnRisky = true; + $failOnSkipped = true; + $failOnWarning = true; + } + + if ($configuration->failOnDeprecation()) { + $failOnDeprecation = true; + } + + if ($configuration->doNotFailOnDeprecation()) { + $failOnDeprecation = false; + } + + if ($configuration->failOnPhpunitDeprecation()) { + $failOnPhpunitDeprecation = true; + } + + if ($configuration->doNotFailOnPhpunitDeprecation()) { + $failOnPhpunitDeprecation = false; + } + + if ($configuration->failOnPhpunitNotice()) { + $failOnPhpunitNotice = true; + } + + if ($configuration->doNotFailOnPhpunitNotice()) { + $failOnPhpunitNotice = false; + } + + if ($configuration->failOnPhpunitWarning()) { + $failOnPhpunitWarning = true; + } + + if ($configuration->doNotFailOnPhpunitWarning()) { + $failOnPhpunitWarning = false; + } + + if ($configuration->failOnEmptyTestSuite()) { + $failOnEmptyTestSuite = true; + } + + if ($configuration->doNotFailOnEmptyTestSuite()) { + $failOnEmptyTestSuite = false; + } + + if ($configuration->failOnIncomplete()) { + $failOnIncomplete = true; + } + + if ($configuration->doNotFailOnIncomplete()) { + $failOnIncomplete = false; + } + + if ($configuration->failOnNotice()) { + $failOnNotice = true; + } + + if ($configuration->doNotFailOnNotice()) { + $failOnNotice = false; + } + + if ($configuration->failOnRisky()) { + $failOnRisky = true; + } + + if ($configuration->doNotFailOnRisky()) { + $failOnRisky = false; + } + + if ($configuration->failOnSkipped()) { + $failOnSkipped = true; + } + + if ($configuration->doNotFailOnSkipped()) { + $failOnSkipped = false; + } + + if ($configuration->failOnWarning()) { + $failOnWarning = true; + } + + if ($configuration->doNotFailOnWarning()) { + $failOnWarning = false; + } + + $returnCode = self::FAILURE_EXIT; + + if ($result->wasSuccessful()) { + $returnCode = self::SUCCESS_EXIT; + } + + if ($failOnEmptyTestSuite && !$result->hasTests()) { + $returnCode = self::FAILURE_EXIT; + } + + if ($failOnDeprecation && $result->hasPhpOrUserDeprecations()) { + $returnCode = self::FAILURE_EXIT; + } + + if ($failOnPhpunitDeprecation && $result->hasPhpunitDeprecations()) { + $returnCode = self::FAILURE_EXIT; + } + + if ($failOnPhpunitNotice && $result->hasPhpunitNotices()) { + $returnCode = self::FAILURE_EXIT; + } + + if ($failOnPhpunitWarning && $result->hasPhpunitWarnings()) { + $returnCode = self::FAILURE_EXIT; + } + + if ($failOnIncomplete && $result->hasIncompleteTests()) { + $returnCode = self::FAILURE_EXIT; + } + + if ($failOnNotice && $result->hasNotices()) { + $returnCode = self::FAILURE_EXIT; + } + + if ($failOnRisky && $result->hasRiskyTests()) { + $returnCode = self::FAILURE_EXIT; + } + + if ($failOnSkipped && $result->hasSkippedTests()) { + $returnCode = self::FAILURE_EXIT; + } + + if ($failOnWarning && $result->hasWarnings()) { + $returnCode = self::FAILURE_EXIT; + } + + if ($result->hasErrors()) { + $returnCode = self::EXCEPTION_EXIT; + } + + return $returnCode; + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/TestRunner.php b/app/vendor/phpunit/phpunit/src/TextUI/TestRunner.php index 1b23a3dcf..2363ca290 100644 --- a/app/vendor/phpunit/phpunit/src/TextUI/TestRunner.php +++ b/app/vendor/phpunit/phpunit/src/TextUI/TestRunner.php @@ -9,1254 +9,68 @@ */ namespace PHPUnit\TextUI; -use const PHP_EOL; -use const PHP_SAPI; -use const PHP_VERSION; -use function array_diff; -use function array_map; -use function array_merge; -use function assert; -use function class_exists; -use function count; -use function dirname; -use function file_put_contents; -use function htmlspecialchars; -use function is_array; -use function is_int; -use function is_string; use function mt_srand; -use function range; -use function realpath; -use function sort; -use function sprintf; -use function time; -use PHPUnit\Framework\Exception; -use PHPUnit\Framework\TestResult; +use PHPUnit\Event; use PHPUnit\Framework\TestSuite; -use PHPUnit\Runner\AfterLastTestHook; -use PHPUnit\Runner\BaseTestRunner; -use PHPUnit\Runner\BeforeFirstTestHook; -use PHPUnit\Runner\DefaultTestResultCache; -use PHPUnit\Runner\Extension\ExtensionHandler; -use PHPUnit\Runner\Filter\ExcludeGroupFilterIterator; -use PHPUnit\Runner\Filter\Factory; -use PHPUnit\Runner\Filter\IncludeGroupFilterIterator; -use PHPUnit\Runner\Filter\NameFilterIterator; -use PHPUnit\Runner\Hook; -use PHPUnit\Runner\NullTestResultCache; -use PHPUnit\Runner\ResultCacheExtension; -use PHPUnit\Runner\StandardTestSuiteLoader; -use PHPUnit\Runner\TestHook; -use PHPUnit\Runner\TestListenerAdapter; -use PHPUnit\Runner\TestSuiteLoader; +use PHPUnit\Runner\ResultCache\ResultCache; use PHPUnit\Runner\TestSuiteSorter; -use PHPUnit\Runner\Version; -use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\FilterMapper; -use PHPUnit\TextUI\XmlConfiguration\Configuration; -use PHPUnit\TextUI\XmlConfiguration\Loader; -use PHPUnit\TextUI\XmlConfiguration\PhpHandler; -use PHPUnit\Util\Filesystem; -use PHPUnit\Util\Log\JUnit; -use PHPUnit\Util\Log\TeamCity; -use PHPUnit\Util\Printer; -use PHPUnit\Util\TestDox\CliTestDoxPrinter; -use PHPUnit\Util\TestDox\HtmlResultPrinter; -use PHPUnit\Util\TestDox\TextResultPrinter; -use PHPUnit\Util\TestDox\XmlResultPrinter; -use PHPUnit\Util\XdebugFilterScriptGenerator; -use PHPUnit\Util\Xml\SchemaDetector; -use ReflectionClass; -use ReflectionException; -use SebastianBergmann\CodeCoverage\CodeCoverage; -use SebastianBergmann\CodeCoverage\Driver\Selector; -use SebastianBergmann\CodeCoverage\Exception as CodeCoverageException; -use SebastianBergmann\CodeCoverage\Filter as CodeCoverageFilter; -use SebastianBergmann\CodeCoverage\Report\Clover as CloverReport; -use SebastianBergmann\CodeCoverage\Report\Cobertura as CoberturaReport; -use SebastianBergmann\CodeCoverage\Report\Crap4j as Crap4jReport; -use SebastianBergmann\CodeCoverage\Report\Html\Facade as HtmlReport; -use SebastianBergmann\CodeCoverage\Report\PHP as PhpReport; -use SebastianBergmann\CodeCoverage\Report\Text as TextReport; -use SebastianBergmann\CodeCoverage\Report\Xml\Facade as XmlReport; -use SebastianBergmann\Comparator\Comparator; -use SebastianBergmann\Environment\Runtime; -use SebastianBergmann\Invoker\Invoker; -use SebastianBergmann\Timer\Timer; +use PHPUnit\TextUI\Configuration\Configuration; +use Throwable; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ -final class TestRunner extends BaseTestRunner +final class TestRunner { - public const SUCCESS_EXIT = 0; - public const FAILURE_EXIT = 1; - public const EXCEPTION_EXIT = 2; - - /** - * @var CodeCoverageFilter - */ - private $codeCoverageFilter; - - /** - * @var TestSuiteLoader - */ - private $loader; - - /** - * @var ResultPrinter - */ - private $printer; - - /** - * @var bool - */ - private $messagePrinted = false; - - /** - * @var Hook[] - */ - private $extensions = []; - - /** - * @var Timer - */ - private $timer; - - public function __construct(?TestSuiteLoader $loader = null, ?CodeCoverageFilter $filter = null) - { - if ($filter === null) { - $filter = new CodeCoverageFilter; - } - - $this->codeCoverageFilter = $filter; - $this->loader = $loader; - $this->timer = new Timer; - } - /** - * @throws \PHPUnit\Runner\Exception - * @throws Exception - * @throws XmlConfiguration\Exception + * @throws RuntimeException */ - public function run(TestSuite $suite, array $arguments = [], array $warnings = [], bool $exit = true): TestResult + public function run(Configuration $configuration, ResultCache $resultCache, TestSuite $suite): void { - if (isset($arguments['configuration'])) { - $GLOBALS['__PHPUNIT_CONFIGURATION_FILE'] = $arguments['configuration']; - } - - $this->handleConfiguration($arguments); - - $warnings = array_merge($warnings, $arguments['warnings']); - - if (is_int($arguments['columns']) && $arguments['columns'] < 16) { - $arguments['columns'] = 16; - $tooFewColumnsRequested = true; - } - - if (isset($arguments['bootstrap'])) { - $GLOBALS['__PHPUNIT_BOOTSTRAP'] = $arguments['bootstrap']; - } - - if ($arguments['backupGlobals'] === true) { - $suite->setBackupGlobals(true); - } - - if ($arguments['backupStaticAttributes'] === true) { - $suite->setBackupStaticAttributes(true); - } - - if ($arguments['beStrictAboutChangesToGlobalState'] === true) { - $suite->setBeStrictAboutChangesToGlobalState(true); - } - - if ($arguments['executionOrder'] === TestSuiteSorter::ORDER_RANDOMIZED) { - mt_srand($arguments['randomOrderSeed']); - } - - if ($arguments['cacheResult']) { - if (!isset($arguments['cacheResultFile'])) { - if (isset($arguments['configurationObject'])) { - assert($arguments['configurationObject'] instanceof Configuration); - - $cacheLocation = $arguments['configurationObject']->filename(); - } else { - $cacheLocation = $_SERVER['PHP_SELF']; - } - - $arguments['cacheResultFile'] = null; - - $cacheResultFile = realpath($cacheLocation); - - if ($cacheResultFile !== false) { - $arguments['cacheResultFile'] = dirname($cacheResultFile); - } - } - - $cache = new DefaultTestResultCache($arguments['cacheResultFile']); - - $this->addExtension(new ResultCacheExtension($cache)); - } - - if ($arguments['executionOrder'] !== TestSuiteSorter::ORDER_DEFAULT || $arguments['executionOrderDefects'] !== TestSuiteSorter::ORDER_DEFAULT || $arguments['resolveDependencies']) { - $cache = $cache ?? new NullTestResultCache; - - $cache->load(); - - $sorter = new TestSuiteSorter($cache); - - $sorter->reorderTestsInSuite($suite, $arguments['executionOrder'], $arguments['resolveDependencies'], $arguments['executionOrderDefects']); - $originalExecutionOrder = $sorter->getOriginalExecutionOrder(); - - unset($sorter); - } - - if (is_int($arguments['repeat']) && $arguments['repeat'] > 0) { - $_suite = new TestSuite; - - /* @noinspection PhpUnusedLocalVariableInspection */ - foreach (range(1, $arguments['repeat']) as $step) { - $_suite->addTest($suite); - } - - $suite = $_suite; - - unset($_suite); - } - - $result = $this->createTestResult(); - - $listener = new TestListenerAdapter; - $listenerNeeded = false; - - foreach ($this->extensions as $extension) { - if ($extension instanceof TestHook) { - $listener->add($extension); - - $listenerNeeded = true; - } - } - - if ($listenerNeeded) { - $result->addListener($listener); - } - - unset($listener, $listenerNeeded); - - if ($arguments['convertDeprecationsToExceptions']) { - $result->convertDeprecationsToExceptions(true); - } - - if (!$arguments['convertErrorsToExceptions']) { - $result->convertErrorsToExceptions(false); - } - - if (!$arguments['convertNoticesToExceptions']) { - $result->convertNoticesToExceptions(false); - } - - if (!$arguments['convertWarningsToExceptions']) { - $result->convertWarningsToExceptions(false); - } - - if ($arguments['stopOnError']) { - $result->stopOnError(true); - } - - if ($arguments['stopOnFailure']) { - $result->stopOnFailure(true); - } - - if ($arguments['stopOnWarning']) { - $result->stopOnWarning(true); - } - - if ($arguments['stopOnIncomplete']) { - $result->stopOnIncomplete(true); - } - - if ($arguments['stopOnRisky']) { - $result->stopOnRisky(true); - } - - if ($arguments['stopOnSkipped']) { - $result->stopOnSkipped(true); - } - - if ($arguments['stopOnDefect']) { - $result->stopOnDefect(true); - } - - if ($arguments['registerMockObjectsFromTestArgumentsRecursively']) { - $result->setRegisterMockObjectsFromTestArgumentsRecursively(true); - } - - if ($this->printer === null) { - if (isset($arguments['printer'])) { - if ($arguments['printer'] instanceof ResultPrinter) { - $this->printer = $arguments['printer']; - } elseif (is_string($arguments['printer']) && class_exists($arguments['printer'], false)) { - try { - $reflector = new ReflectionClass($arguments['printer']); - - if ($reflector->implementsInterface(ResultPrinter::class)) { - $this->printer = $this->createPrinter($arguments['printer'], $arguments); - } - - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - $e->getCode(), - $e, - ); - } - // @codeCoverageIgnoreEnd - } - } else { - $this->printer = $this->createPrinter(DefaultResultPrinter::class, $arguments); - } - } + try { + Event\Facade::emitter()->testRunnerStarted(); - if (isset($originalExecutionOrder) && $this->printer instanceof CliTestDoxPrinter) { - assert($this->printer instanceof CliTestDoxPrinter); - - $this->printer->setOriginalExecutionOrder($originalExecutionOrder); - $this->printer->setShowProgressAnimation(!$arguments['noInteraction']); - } - - $this->write(Version::getVersionString() . "\n"); - - foreach ($arguments['listeners'] as $listener) { - $result->addListener($listener); - } - - $result->addListener($this->printer); - - $coverageFilterFromConfigurationFile = false; - $coverageFilterFromOption = false; - $codeCoverageReports = 0; - - if (isset($arguments['testdoxHTMLFile'])) { - $result->addListener( - new HtmlResultPrinter( - $arguments['testdoxHTMLFile'], - $arguments['testdoxGroups'], - $arguments['testdoxExcludeGroups'], - ), - ); - } - - if (isset($arguments['testdoxTextFile'])) { - $result->addListener( - new TextResultPrinter( - $arguments['testdoxTextFile'], - $arguments['testdoxGroups'], - $arguments['testdoxExcludeGroups'], - ), - ); - } - - if (isset($arguments['testdoxXMLFile'])) { - $result->addListener( - new XmlResultPrinter( - $arguments['testdoxXMLFile'], - ), - ); - } - - if (isset($arguments['teamcityLogfile'])) { - $result->addListener( - new TeamCity($arguments['teamcityLogfile']), - ); - } - - if (isset($arguments['junitLogfile'])) { - $result->addListener( - new JUnit( - $arguments['junitLogfile'], - $arguments['reportUselessTests'], - ), - ); - } - - if (isset($arguments['coverageClover'])) { - $codeCoverageReports++; - } - - if (isset($arguments['coverageCobertura'])) { - $codeCoverageReports++; - } - - if (isset($arguments['coverageCrap4J'])) { - $codeCoverageReports++; - } - - if (isset($arguments['coverageHtml'])) { - $codeCoverageReports++; - } - - if (isset($arguments['coveragePHP'])) { - $codeCoverageReports++; - } - - if (isset($arguments['coverageText'])) { - $codeCoverageReports++; - } - - if (isset($arguments['coverageXml'])) { - $codeCoverageReports++; - } - - if ($codeCoverageReports > 0 || isset($arguments['xdebugFilterFile'])) { - if (isset($arguments['coverageFilter'])) { - if (!is_array($arguments['coverageFilter'])) { - $coverageFilterDirectories = [$arguments['coverageFilter']]; - } else { - $coverageFilterDirectories = $arguments['coverageFilter']; - } - - foreach ($coverageFilterDirectories as $coverageFilterDirectory) { - $this->codeCoverageFilter->includeDirectory($coverageFilterDirectory); - } - - $coverageFilterFromOption = true; - } - - if (isset($arguments['configurationObject'])) { - assert($arguments['configurationObject'] instanceof Configuration); - - $codeCoverageConfiguration = $arguments['configurationObject']->codeCoverage(); - - if ($codeCoverageConfiguration->hasNonEmptyListOfFilesToBeIncludedInCodeCoverageReport()) { - $coverageFilterFromConfigurationFile = true; - - (new FilterMapper)->map( - $this->codeCoverageFilter, - $codeCoverageConfiguration, - ); - } + if ($configuration->executionOrder() === TestSuiteSorter::ORDER_RANDOMIZED) { + mt_srand($configuration->randomOrderSeed()); } - } - if ($codeCoverageReports > 0) { - try { - if (isset($codeCoverageConfiguration) && - ($codeCoverageConfiguration->pathCoverage() || (isset($arguments['pathCoverage']) && $arguments['pathCoverage'] === true))) { - $codeCoverageDriver = (new Selector)->forLineAndPathCoverage($this->codeCoverageFilter); - } else { - $codeCoverageDriver = (new Selector)->forLineCoverage($this->codeCoverageFilter); - } + if ($configuration->executionOrder() !== TestSuiteSorter::ORDER_DEFAULT || + $configuration->executionOrderDefects() !== TestSuiteSorter::ORDER_DEFAULT || + $configuration->resolveDependencies()) { + $resultCache->load(); - $codeCoverage = new CodeCoverage( - $codeCoverageDriver, - $this->codeCoverageFilter, + (new TestSuiteSorter($resultCache))->reorderTestsInSuite( + $suite, + $configuration->executionOrder(), + $configuration->resolveDependencies(), + $configuration->executionOrderDefects(), ); - if (isset($codeCoverageConfiguration) && $codeCoverageConfiguration->hasCacheDirectory()) { - $codeCoverage->cacheStaticAnalysis($codeCoverageConfiguration->cacheDirectory()->path()); - } - - if (isset($arguments['coverageCacheDirectory'])) { - $codeCoverage->cacheStaticAnalysis($arguments['coverageCacheDirectory']); - } - - $codeCoverage->excludeSubclassesOfThisClassFromUnintentionallyCoveredCodeCheck(Comparator::class); - - if ($arguments['strictCoverage']) { - $codeCoverage->enableCheckForUnintentionallyCoveredCode(); - } - - if (isset($arguments['ignoreDeprecatedCodeUnitsFromCodeCoverage'])) { - if ($arguments['ignoreDeprecatedCodeUnitsFromCodeCoverage']) { - $codeCoverage->ignoreDeprecatedCode(); - } else { - $codeCoverage->doNotIgnoreDeprecatedCode(); - } - } - - if (isset($arguments['disableCodeCoverageIgnore'])) { - if ($arguments['disableCodeCoverageIgnore']) { - $codeCoverage->disableAnnotationsForIgnoringCode(); - } else { - $codeCoverage->enableAnnotationsForIgnoringCode(); - } - } - - if (isset($arguments['configurationObject'])) { - $codeCoverageConfiguration = $arguments['configurationObject']->codeCoverage(); - - if ($codeCoverageConfiguration->hasNonEmptyListOfFilesToBeIncludedInCodeCoverageReport()) { - if ($codeCoverageConfiguration->includeUncoveredFiles()) { - $codeCoverage->includeUncoveredFiles(); - } else { - $codeCoverage->excludeUncoveredFiles(); - } - - if ($codeCoverageConfiguration->processUncoveredFiles()) { - $codeCoverage->processUncoveredFiles(); - } else { - $codeCoverage->doNotProcessUncoveredFiles(); - } - } - } - - if ($this->codeCoverageFilter->isEmpty()) { - if (!$coverageFilterFromConfigurationFile && !$coverageFilterFromOption) { - $warnings[] = 'No filter is configured, code coverage will not be processed'; - } else { - $warnings[] = 'Incorrect filter configuration, code coverage will not be processed'; - } - - unset($codeCoverage); - } - } catch (CodeCoverageException $e) { - $warnings[] = $e->getMessage(); - } - } - - if ($arguments['verbose']) { - if (PHP_SAPI === 'phpdbg') { - $this->writeMessage('Runtime', 'PHPDBG ' . PHP_VERSION); - } else { - $runtime = 'PHP ' . PHP_VERSION; - - if (isset($codeCoverageDriver)) { - $runtime .= ' with ' . $codeCoverageDriver->nameAndVersion(); - } - - $this->writeMessage('Runtime', $runtime); - } - - if (isset($arguments['configurationObject'])) { - assert($arguments['configurationObject'] instanceof Configuration); - - $this->writeMessage( - 'Configuration', - $arguments['configurationObject']->filename(), + Event\Facade::emitter()->testSuiteSorted( + $configuration->executionOrder(), + $configuration->executionOrderDefects(), + $configuration->resolveDependencies(), ); } - foreach ($arguments['loadedExtensions'] as $extension) { - $this->writeMessage( - 'Extension', - $extension, - ); - } - - foreach ($arguments['notLoadedExtensions'] as $extension) { - $this->writeMessage( - 'Extension', - $extension, - ); - } - } + (new TestSuiteFilterProcessor)->process($configuration, $suite); - if ($arguments['executionOrder'] === TestSuiteSorter::ORDER_RANDOMIZED) { - $this->writeMessage( - 'Random Seed', - (string) $arguments['randomOrderSeed'], + Event\Facade::emitter()->testRunnerExecutionStarted( + Event\TestSuite\TestSuiteBuilder::from($suite), ); - } - - if (isset($tooFewColumnsRequested)) { - $warnings[] = 'Less than 16 columns requested, number of columns set to 16'; - } - if ((new Runtime)->discardsComments()) { - $warnings[] = 'opcache.save_comments=0 set; annotations will not work'; - } - - if (isset($arguments['conflictBetweenPrinterClassAndTestdox'])) { - $warnings[] = 'Directives printerClass and testdox are mutually exclusive'; - } - - $warnings = array_merge($warnings, $suite->warnings()); - sort($warnings); - - foreach ($warnings as $warning) { - $this->writeMessage('Warning', $warning); - } - - if (isset($arguments['configurationObject'])) { - assert($arguments['configurationObject'] instanceof Configuration); - - if ($arguments['configurationObject']->hasValidationErrors()) { - if ((new SchemaDetector)->detect($arguments['configurationObject']->filename())->detected()) { - $this->writeMessage('Warning', 'Your XML configuration validates against a deprecated schema.'); - $this->writeMessage('Suggestion', 'Migrate your XML configuration using "--migrate-configuration"!'); - } else { - $this->write( - "\n Warning - The configuration file did not pass validation!\n The following problems have been detected:\n", - ); - - $this->write($arguments['configurationObject']->validationErrors()); - - $this->write("\n Test results may not be as expected.\n\n"); - } - } - } - - if (isset($arguments['xdebugFilterFile'], $codeCoverageConfiguration)) { - $this->write(PHP_EOL . 'Please note that --dump-xdebug-filter and --prepend are deprecated and will be removed in PHPUnit 10.' . PHP_EOL); - - $script = (new XdebugFilterScriptGenerator)->generate($codeCoverageConfiguration); - - if ($arguments['xdebugFilterFile'] !== 'php://stdout' && $arguments['xdebugFilterFile'] !== 'php://stderr' && !Filesystem::createDirectory(dirname($arguments['xdebugFilterFile']))) { - $this->write(sprintf('Cannot write Xdebug filter script to %s ' . PHP_EOL, $arguments['xdebugFilterFile'])); - - exit(self::EXCEPTION_EXIT); - } - - file_put_contents($arguments['xdebugFilterFile'], $script); - - $this->write(sprintf('Wrote Xdebug filter script to %s ' . PHP_EOL . PHP_EOL, $arguments['xdebugFilterFile'])); - - exit(self::SUCCESS_EXIT); - } - - $this->write("\n"); - - if (isset($codeCoverage)) { - $result->setCodeCoverage($codeCoverage); - } - - $result->beStrictAboutTestsThatDoNotTestAnything($arguments['reportUselessTests']); - $result->beStrictAboutOutputDuringTests($arguments['disallowTestOutput']); - $result->beStrictAboutTodoAnnotatedTests($arguments['disallowTodoAnnotatedTests']); - $result->beStrictAboutResourceUsageDuringSmallTests($arguments['beStrictAboutResourceUsageDuringSmallTests']); - - if ($arguments['enforceTimeLimit'] === true && !(new Invoker)->canInvokeWithTimeout()) { - $this->writeMessage('Error', 'PHP extension pcntl is required for enforcing time limits'); - } + $suite->run(); - $result->enforceTimeLimit($arguments['enforceTimeLimit']); - $result->setDefaultTimeLimit($arguments['defaultTimeLimit']); - $result->setTimeoutForSmallTests($arguments['timeoutForSmallTests']); - $result->setTimeoutForMediumTests($arguments['timeoutForMediumTests']); - $result->setTimeoutForLargeTests($arguments['timeoutForLargeTests']); - - if (isset($arguments['forceCoversAnnotation']) && $arguments['forceCoversAnnotation'] === true) { - $result->forceCoversAnnotation(); - } - - $this->processSuiteFilters($suite, $arguments); - $suite->setRunTestInSeparateProcess($arguments['processIsolation']); - - foreach ($this->extensions as $extension) { - if ($extension instanceof BeforeFirstTestHook) { - $extension->executeBeforeFirstTest(); - } - } - - $suite->run($result); - - foreach ($this->extensions as $extension) { - if ($extension instanceof AfterLastTestHook) { - $extension->executeAfterLastTest(); - } - } - - $result->flushListeners(); - $this->printer->printResult($result); - - if (isset($codeCoverage)) { - if (isset($arguments['coveragePHP'])) { - $this->codeCoverageGenerationStart('PHP'); - - try { - $writer = new PhpReport; - $writer->process($codeCoverage, $arguments['coveragePHP']); - - $this->codeCoverageGenerationSucceeded(); - - unset($writer); - } catch (CodeCoverageException $e) { - $this->codeCoverageGenerationFailed($e); - } - } - - if (isset($arguments['coverageClover'])) { - $this->codeCoverageGenerationStart('Clover XML'); - - try { - $writer = new CloverReport; - $writer->process($codeCoverage, $arguments['coverageClover']); - - $this->codeCoverageGenerationSucceeded(); - - unset($writer); - } catch (CodeCoverageException $e) { - $this->codeCoverageGenerationFailed($e); - } - } - - if (isset($arguments['coverageCobertura'])) { - $this->codeCoverageGenerationStart('Cobertura XML'); - - try { - $writer = new CoberturaReport; - $writer->process($codeCoverage, $arguments['coverageCobertura']); - - $this->codeCoverageGenerationSucceeded(); - - unset($writer); - } catch (CodeCoverageException $e) { - $this->codeCoverageGenerationFailed($e); - } - } - - if (isset($arguments['coverageCrap4J'])) { - $this->codeCoverageGenerationStart('Crap4J XML'); - - try { - $writer = new Crap4jReport($arguments['crap4jThreshold']); - $writer->process($codeCoverage, $arguments['coverageCrap4J']); - - $this->codeCoverageGenerationSucceeded(); - - unset($writer); - } catch (CodeCoverageException $e) { - $this->codeCoverageGenerationFailed($e); - } - } - - if (isset($arguments['coverageHtml'])) { - $this->codeCoverageGenerationStart('HTML'); - - try { - $writer = new HtmlReport( - $arguments['reportLowUpperBound'], - $arguments['reportHighLowerBound'], - sprintf( - ' and PHPUnit %s', - Version::id(), - ), - ); - - $writer->process($codeCoverage, $arguments['coverageHtml']); - - $this->codeCoverageGenerationSucceeded(); - - unset($writer); - } catch (CodeCoverageException $e) { - $this->codeCoverageGenerationFailed($e); - } - } - - if (isset($arguments['coverageText'])) { - if ($arguments['coverageText'] === 'php://stdout') { - $outputStream = $this->printer; - $colors = $arguments['colors'] && $arguments['colors'] !== DefaultResultPrinter::COLOR_NEVER; - } else { - $outputStream = new Printer($arguments['coverageText']); - $colors = false; - } - - $processor = new TextReport( - $arguments['reportLowUpperBound'], - $arguments['reportHighLowerBound'], - $arguments['coverageTextShowUncoveredFiles'], - $arguments['coverageTextShowOnlySummary'], - ); - - $outputStream->write( - $processor->process($codeCoverage, $colors), - ); - } - - if (isset($arguments['coverageXml'])) { - $this->codeCoverageGenerationStart('PHPUnit XML'); - - try { - $writer = new XmlReport(Version::id()); - $writer->process($codeCoverage, $arguments['coverageXml']); - - $this->codeCoverageGenerationSucceeded(); - - unset($writer); - } catch (CodeCoverageException $e) { - $this->codeCoverageGenerationFailed($e); - } - } - } - - if ($exit) { - if (isset($arguments['failOnEmptyTestSuite']) && $arguments['failOnEmptyTestSuite'] === true && count($result) === 0) { - exit(self::FAILURE_EXIT); - } - - if ($result->wasSuccessfulIgnoringWarnings()) { - if ($arguments['failOnRisky'] && !$result->allHarmless()) { - exit(self::FAILURE_EXIT); - } - - if ($arguments['failOnWarning'] && $result->warningCount() > 0) { - exit(self::FAILURE_EXIT); - } - - if ($arguments['failOnIncomplete'] && $result->notImplementedCount() > 0) { - exit(self::FAILURE_EXIT); - } - - if ($arguments['failOnSkipped'] && $result->skippedCount() > 0) { - exit(self::FAILURE_EXIT); - } - - exit(self::SUCCESS_EXIT); - } - - if ($result->errorCount() > 0) { - exit(self::EXCEPTION_EXIT); - } - - if ($result->failureCount() > 0) { - exit(self::FAILURE_EXIT); - } - } - - return $result; - } - - /** - * Returns the loader to be used. - */ - public function getLoader(): TestSuiteLoader - { - if ($this->loader === null) { - $this->loader = new StandardTestSuiteLoader; - } - - return $this->loader; - } - - public function addExtension(Hook $extension): void - { - $this->extensions[] = $extension; - } - - /** - * Override to define how to handle a failed loading of - * a test suite. - */ - protected function runFailed(string $message): void - { - $this->write($message . PHP_EOL); - - exit(self::FAILURE_EXIT); - } - - private function createTestResult(): TestResult - { - return new TestResult; - } - - private function write(string $buffer): void - { - if (PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg') { - $buffer = htmlspecialchars($buffer); - } - - if ($this->printer !== null) { - $this->printer->write($buffer); - } else { - print $buffer; - } - } - - /** - * @throws Exception - * @throws XmlConfiguration\Exception - */ - private function handleConfiguration(array &$arguments): void - { - if (!isset($arguments['configurationObject']) && isset($arguments['configuration'])) { - $arguments['configurationObject'] = (new Loader)->load($arguments['configuration']); - } - - if (!isset($arguments['warnings'])) { - $arguments['warnings'] = []; - } - - $arguments['debug'] = $arguments['debug'] ?? false; - $arguments['filter'] = $arguments['filter'] ?? false; - $arguments['listeners'] = $arguments['listeners'] ?? []; - - if (isset($arguments['configurationObject'])) { - (new PhpHandler)->handle($arguments['configurationObject']->php()); - - $codeCoverageConfiguration = $arguments['configurationObject']->codeCoverage(); - - if (!isset($arguments['noCoverage'])) { - if (!isset($arguments['coverageClover']) && $codeCoverageConfiguration->hasClover()) { - $arguments['coverageClover'] = $codeCoverageConfiguration->clover()->target()->path(); - } - - if (!isset($arguments['coverageCobertura']) && $codeCoverageConfiguration->hasCobertura()) { - $arguments['coverageCobertura'] = $codeCoverageConfiguration->cobertura()->target()->path(); - } - - if (!isset($arguments['coverageCrap4J']) && $codeCoverageConfiguration->hasCrap4j()) { - $arguments['coverageCrap4J'] = $codeCoverageConfiguration->crap4j()->target()->path(); - - if (!isset($arguments['crap4jThreshold'])) { - $arguments['crap4jThreshold'] = $codeCoverageConfiguration->crap4j()->threshold(); - } - } - - if (!isset($arguments['coverageHtml']) && $codeCoverageConfiguration->hasHtml()) { - $arguments['coverageHtml'] = $codeCoverageConfiguration->html()->target()->path(); - - if (!isset($arguments['reportLowUpperBound'])) { - $arguments['reportLowUpperBound'] = $codeCoverageConfiguration->html()->lowUpperBound(); - } - - if (!isset($arguments['reportHighLowerBound'])) { - $arguments['reportHighLowerBound'] = $codeCoverageConfiguration->html()->highLowerBound(); - } - } - - if (!isset($arguments['coveragePHP']) && $codeCoverageConfiguration->hasPhp()) { - $arguments['coveragePHP'] = $codeCoverageConfiguration->php()->target()->path(); - } - - if (!isset($arguments['coverageText']) && $codeCoverageConfiguration->hasText()) { - $arguments['coverageText'] = $codeCoverageConfiguration->text()->target()->path(); - $arguments['coverageTextShowUncoveredFiles'] = $codeCoverageConfiguration->text()->showUncoveredFiles(); - $arguments['coverageTextShowOnlySummary'] = $codeCoverageConfiguration->text()->showOnlySummary(); - } - - if (!isset($arguments['coverageXml']) && $codeCoverageConfiguration->hasXml()) { - $arguments['coverageXml'] = $codeCoverageConfiguration->xml()->target()->path(); - } - } - - $phpunitConfiguration = $arguments['configurationObject']->phpunit(); - - $arguments['backupGlobals'] = $arguments['backupGlobals'] ?? $phpunitConfiguration->backupGlobals(); - $arguments['backupStaticAttributes'] = $arguments['backupStaticAttributes'] ?? $phpunitConfiguration->backupStaticAttributes(); - $arguments['beStrictAboutChangesToGlobalState'] = $arguments['beStrictAboutChangesToGlobalState'] ?? $phpunitConfiguration->beStrictAboutChangesToGlobalState(); - $arguments['cacheResult'] = $arguments['cacheResult'] ?? $phpunitConfiguration->cacheResult(); - $arguments['colors'] = $arguments['colors'] ?? $phpunitConfiguration->colors(); - $arguments['convertDeprecationsToExceptions'] = $arguments['convertDeprecationsToExceptions'] ?? $phpunitConfiguration->convertDeprecationsToExceptions(); - $arguments['convertErrorsToExceptions'] = $arguments['convertErrorsToExceptions'] ?? $phpunitConfiguration->convertErrorsToExceptions(); - $arguments['convertNoticesToExceptions'] = $arguments['convertNoticesToExceptions'] ?? $phpunitConfiguration->convertNoticesToExceptions(); - $arguments['convertWarningsToExceptions'] = $arguments['convertWarningsToExceptions'] ?? $phpunitConfiguration->convertWarningsToExceptions(); - $arguments['processIsolation'] = $arguments['processIsolation'] ?? $phpunitConfiguration->processIsolation(); - $arguments['stopOnDefect'] = $arguments['stopOnDefect'] ?? $phpunitConfiguration->stopOnDefect(); - $arguments['stopOnError'] = $arguments['stopOnError'] ?? $phpunitConfiguration->stopOnError(); - $arguments['stopOnFailure'] = $arguments['stopOnFailure'] ?? $phpunitConfiguration->stopOnFailure(); - $arguments['stopOnWarning'] = $arguments['stopOnWarning'] ?? $phpunitConfiguration->stopOnWarning(); - $arguments['stopOnIncomplete'] = $arguments['stopOnIncomplete'] ?? $phpunitConfiguration->stopOnIncomplete(); - $arguments['stopOnRisky'] = $arguments['stopOnRisky'] ?? $phpunitConfiguration->stopOnRisky(); - $arguments['stopOnSkipped'] = $arguments['stopOnSkipped'] ?? $phpunitConfiguration->stopOnSkipped(); - $arguments['failOnEmptyTestSuite'] = $arguments['failOnEmptyTestSuite'] ?? $phpunitConfiguration->failOnEmptyTestSuite(); - $arguments['failOnIncomplete'] = $arguments['failOnIncomplete'] ?? $phpunitConfiguration->failOnIncomplete(); - $arguments['failOnRisky'] = $arguments['failOnRisky'] ?? $phpunitConfiguration->failOnRisky(); - $arguments['failOnSkipped'] = $arguments['failOnSkipped'] ?? $phpunitConfiguration->failOnSkipped(); - $arguments['failOnWarning'] = $arguments['failOnWarning'] ?? $phpunitConfiguration->failOnWarning(); - $arguments['enforceTimeLimit'] = $arguments['enforceTimeLimit'] ?? $phpunitConfiguration->enforceTimeLimit(); - $arguments['defaultTimeLimit'] = $arguments['defaultTimeLimit'] ?? $phpunitConfiguration->defaultTimeLimit(); - $arguments['timeoutForSmallTests'] = $arguments['timeoutForSmallTests'] ?? $phpunitConfiguration->timeoutForSmallTests(); - $arguments['timeoutForMediumTests'] = $arguments['timeoutForMediumTests'] ?? $phpunitConfiguration->timeoutForMediumTests(); - $arguments['timeoutForLargeTests'] = $arguments['timeoutForLargeTests'] ?? $phpunitConfiguration->timeoutForLargeTests(); - $arguments['reportUselessTests'] = $arguments['reportUselessTests'] ?? $phpunitConfiguration->beStrictAboutTestsThatDoNotTestAnything(); - $arguments['strictCoverage'] = $arguments['strictCoverage'] ?? $phpunitConfiguration->beStrictAboutCoversAnnotation(); - $arguments['ignoreDeprecatedCodeUnitsFromCodeCoverage'] = $arguments['ignoreDeprecatedCodeUnitsFromCodeCoverage'] ?? $codeCoverageConfiguration->ignoreDeprecatedCodeUnits(); - $arguments['disallowTestOutput'] = $arguments['disallowTestOutput'] ?? $phpunitConfiguration->beStrictAboutOutputDuringTests(); - $arguments['disallowTodoAnnotatedTests'] = $arguments['disallowTodoAnnotatedTests'] ?? $phpunitConfiguration->beStrictAboutTodoAnnotatedTests(); - $arguments['beStrictAboutResourceUsageDuringSmallTests'] = $arguments['beStrictAboutResourceUsageDuringSmallTests'] ?? $phpunitConfiguration->beStrictAboutResourceUsageDuringSmallTests(); - $arguments['verbose'] = $arguments['verbose'] ?? $phpunitConfiguration->verbose(); - $arguments['reverseDefectList'] = $arguments['reverseDefectList'] ?? $phpunitConfiguration->reverseDefectList(); - $arguments['forceCoversAnnotation'] = $arguments['forceCoversAnnotation'] ?? $phpunitConfiguration->forceCoversAnnotation(); - $arguments['disableCodeCoverageIgnore'] = $arguments['disableCodeCoverageIgnore'] ?? $codeCoverageConfiguration->disableCodeCoverageIgnore(); - $arguments['registerMockObjectsFromTestArgumentsRecursively'] = $arguments['registerMockObjectsFromTestArgumentsRecursively'] ?? $phpunitConfiguration->registerMockObjectsFromTestArgumentsRecursively(); - $arguments['noInteraction'] = $arguments['noInteraction'] ?? $phpunitConfiguration->noInteraction(); - $arguments['executionOrder'] = $arguments['executionOrder'] ?? $phpunitConfiguration->executionOrder(); - $arguments['resolveDependencies'] = $arguments['resolveDependencies'] ?? $phpunitConfiguration->resolveDependencies(); - - if (!isset($arguments['bootstrap']) && $phpunitConfiguration->hasBootstrap()) { - $arguments['bootstrap'] = $phpunitConfiguration->bootstrap(); - } - - if (!isset($arguments['cacheResultFile']) && $phpunitConfiguration->hasCacheResultFile()) { - $arguments['cacheResultFile'] = $phpunitConfiguration->cacheResultFile(); - } - - if (!isset($arguments['executionOrderDefects'])) { - $arguments['executionOrderDefects'] = $phpunitConfiguration->defectsFirst() ? TestSuiteSorter::ORDER_DEFECTS_FIRST : TestSuiteSorter::ORDER_DEFAULT; - } - - if ($phpunitConfiguration->conflictBetweenPrinterClassAndTestdox()) { - $arguments['conflictBetweenPrinterClassAndTestdox'] = true; - } - - $groupCliArgs = []; - - if (!empty($arguments['groups'])) { - $groupCliArgs = $arguments['groups']; - } - - $groupConfiguration = $arguments['configurationObject']->groups(); - - if (!isset($arguments['groups']) && $groupConfiguration->hasInclude()) { - $arguments['groups'] = $groupConfiguration->include()->asArrayOfStrings(); - } - - if (!isset($arguments['excludeGroups']) && $groupConfiguration->hasExclude()) { - $arguments['excludeGroups'] = array_diff($groupConfiguration->exclude()->asArrayOfStrings(), $groupCliArgs); - } - - if (!isset($arguments['noExtensions'])) { - $extensionHandler = new ExtensionHandler; - - foreach ($arguments['configurationObject']->extensions() as $extension) { - $extensionHandler->registerExtension($extension, $this); - } - - foreach ($arguments['configurationObject']->listeners() as $listener) { - $arguments['listeners'][] = $extensionHandler->createTestListenerInstance($listener); - } - - unset($extensionHandler); - } - - foreach ($arguments['unavailableExtensions'] as $extension) { - $arguments['warnings'][] = sprintf( - 'Extension "%s" is not available', - $extension, - ); - } - - $loggingConfiguration = $arguments['configurationObject']->logging(); - - if (!isset($arguments['noLogging'])) { - if ($loggingConfiguration->hasText()) { - $arguments['listeners'][] = new DefaultResultPrinter( - $loggingConfiguration->text()->target()->path(), - true, - ); - } - - if (!isset($arguments['teamcityLogfile']) && $loggingConfiguration->hasTeamCity()) { - $arguments['teamcityLogfile'] = $loggingConfiguration->teamCity()->target()->path(); - } - - if (!isset($arguments['junitLogfile']) && $loggingConfiguration->hasJunit()) { - $arguments['junitLogfile'] = $loggingConfiguration->junit()->target()->path(); - } - - if (!isset($arguments['testdoxHTMLFile']) && $loggingConfiguration->hasTestDoxHtml()) { - $arguments['testdoxHTMLFile'] = $loggingConfiguration->testDoxHtml()->target()->path(); - } - - if (!isset($arguments['testdoxTextFile']) && $loggingConfiguration->hasTestDoxText()) { - $arguments['testdoxTextFile'] = $loggingConfiguration->testDoxText()->target()->path(); - } - - if (!isset($arguments['testdoxXMLFile']) && $loggingConfiguration->hasTestDoxXml()) { - $arguments['testdoxXMLFile'] = $loggingConfiguration->testDoxXml()->target()->path(); - } - } - - $testdoxGroupConfiguration = $arguments['configurationObject']->testdoxGroups(); - - if (!isset($arguments['testdoxGroups']) && $testdoxGroupConfiguration->hasInclude()) { - $arguments['testdoxGroups'] = $testdoxGroupConfiguration->include()->asArrayOfStrings(); - } - - if (!isset($arguments['testdoxExcludeGroups']) && $testdoxGroupConfiguration->hasExclude()) { - $arguments['testdoxExcludeGroups'] = $testdoxGroupConfiguration->exclude()->asArrayOfStrings(); - } - } - - $extensionHandler = new ExtensionHandler; - - foreach ($arguments['extensions'] as $extension) { - $extensionHandler->registerExtension($extension, $this); - } - - unset($extensionHandler); - - $arguments['backupGlobals'] = $arguments['backupGlobals'] ?? null; - $arguments['backupStaticAttributes'] = $arguments['backupStaticAttributes'] ?? null; - $arguments['beStrictAboutChangesToGlobalState'] = $arguments['beStrictAboutChangesToGlobalState'] ?? null; - $arguments['beStrictAboutResourceUsageDuringSmallTests'] = $arguments['beStrictAboutResourceUsageDuringSmallTests'] ?? false; - $arguments['cacheResult'] = $arguments['cacheResult'] ?? true; - $arguments['colors'] = $arguments['colors'] ?? DefaultResultPrinter::COLOR_DEFAULT; - $arguments['columns'] = $arguments['columns'] ?? 80; - $arguments['convertDeprecationsToExceptions'] = $arguments['convertDeprecationsToExceptions'] ?? false; - $arguments['convertErrorsToExceptions'] = $arguments['convertErrorsToExceptions'] ?? true; - $arguments['convertNoticesToExceptions'] = $arguments['convertNoticesToExceptions'] ?? true; - $arguments['convertWarningsToExceptions'] = $arguments['convertWarningsToExceptions'] ?? true; - $arguments['crap4jThreshold'] = $arguments['crap4jThreshold'] ?? 30; - $arguments['disallowTestOutput'] = $arguments['disallowTestOutput'] ?? false; - $arguments['disallowTodoAnnotatedTests'] = $arguments['disallowTodoAnnotatedTests'] ?? false; - $arguments['defaultTimeLimit'] = $arguments['defaultTimeLimit'] ?? 0; - $arguments['enforceTimeLimit'] = $arguments['enforceTimeLimit'] ?? false; - $arguments['excludeGroups'] = $arguments['excludeGroups'] ?? []; - $arguments['executionOrder'] = $arguments['executionOrder'] ?? TestSuiteSorter::ORDER_DEFAULT; - $arguments['executionOrderDefects'] = $arguments['executionOrderDefects'] ?? TestSuiteSorter::ORDER_DEFAULT; - $arguments['failOnIncomplete'] = $arguments['failOnIncomplete'] ?? false; - $arguments['failOnRisky'] = $arguments['failOnRisky'] ?? false; - $arguments['failOnSkipped'] = $arguments['failOnSkipped'] ?? false; - $arguments['failOnWarning'] = $arguments['failOnWarning'] ?? false; - $arguments['groups'] = $arguments['groups'] ?? []; - $arguments['noInteraction'] = $arguments['noInteraction'] ?? false; - $arguments['processIsolation'] = $arguments['processIsolation'] ?? false; - $arguments['randomOrderSeed'] = $arguments['randomOrderSeed'] ?? time(); - $arguments['registerMockObjectsFromTestArgumentsRecursively'] = $arguments['registerMockObjectsFromTestArgumentsRecursively'] ?? false; - $arguments['repeat'] = $arguments['repeat'] ?? false; - $arguments['reportHighLowerBound'] = $arguments['reportHighLowerBound'] ?? 90; - $arguments['reportLowUpperBound'] = $arguments['reportLowUpperBound'] ?? 50; - $arguments['reportUselessTests'] = $arguments['reportUselessTests'] ?? true; - $arguments['reverseList'] = $arguments['reverseList'] ?? false; - $arguments['resolveDependencies'] = $arguments['resolveDependencies'] ?? true; - $arguments['stopOnError'] = $arguments['stopOnError'] ?? false; - $arguments['stopOnFailure'] = $arguments['stopOnFailure'] ?? false; - $arguments['stopOnIncomplete'] = $arguments['stopOnIncomplete'] ?? false; - $arguments['stopOnRisky'] = $arguments['stopOnRisky'] ?? false; - $arguments['stopOnSkipped'] = $arguments['stopOnSkipped'] ?? false; - $arguments['stopOnWarning'] = $arguments['stopOnWarning'] ?? false; - $arguments['stopOnDefect'] = $arguments['stopOnDefect'] ?? false; - $arguments['strictCoverage'] = $arguments['strictCoverage'] ?? false; - $arguments['testdoxExcludeGroups'] = $arguments['testdoxExcludeGroups'] ?? []; - $arguments['testdoxGroups'] = $arguments['testdoxGroups'] ?? []; - $arguments['timeoutForLargeTests'] = $arguments['timeoutForLargeTests'] ?? 60; - $arguments['timeoutForMediumTests'] = $arguments['timeoutForMediumTests'] ?? 10; - $arguments['timeoutForSmallTests'] = $arguments['timeoutForSmallTests'] ?? 1; - $arguments['verbose'] = $arguments['verbose'] ?? false; - - if ($arguments['reportLowUpperBound'] > $arguments['reportHighLowerBound']) { - $arguments['reportLowUpperBound'] = 50; - $arguments['reportHighLowerBound'] = 90; - } - } - - private function processSuiteFilters(TestSuite $suite, array $arguments): void - { - if (!$arguments['filter'] && - empty($arguments['groups']) && - empty($arguments['excludeGroups']) && - empty($arguments['testsCovering']) && - empty($arguments['testsUsing'])) { - return; - } - - $filterFactory = new Factory; - - if (!empty($arguments['excludeGroups'])) { - $filterFactory->addFilter( - new ReflectionClass(ExcludeGroupFilterIterator::class), - $arguments['excludeGroups'], - ); - } - - if (!empty($arguments['groups'])) { - $filterFactory->addFilter( - new ReflectionClass(IncludeGroupFilterIterator::class), - $arguments['groups'], - ); - } - - if (!empty($arguments['testsCovering'])) { - $filterFactory->addFilter( - new ReflectionClass(IncludeGroupFilterIterator::class), - array_map( - static function (string $name): string - { - return '__phpunit_covers_' . $name; - }, - $arguments['testsCovering'], - ), - ); - } - - if (!empty($arguments['testsUsing'])) { - $filterFactory->addFilter( - new ReflectionClass(IncludeGroupFilterIterator::class), - array_map( - static function (string $name): string - { - return '__phpunit_uses_' . $name; - }, - $arguments['testsUsing'], - ), + Event\Facade::emitter()->testRunnerExecutionFinished(); + Event\Facade::emitter()->testRunnerFinished(); + } catch (Throwable $t) { + throw new RuntimeException( + $t->getMessage(), + (int) $t->getCode(), + $t, ); } - - if ($arguments['filter']) { - $filterFactory->addFilter( - new ReflectionClass(NameFilterIterator::class), - $arguments['filter'], - ); - } - - $suite->injectFilter($filterFactory); - } - - private function writeMessage(string $type, string $message): void - { - if (!$this->messagePrinted) { - $this->write("\n"); - } - - $this->write( - sprintf( - "%-15s%s\n", - $type . ':', - $message, - ), - ); - - $this->messagePrinted = true; - } - - private function createPrinter(string $class, array $arguments): ResultPrinter - { - $object = new $class( - (isset($arguments['stderr']) && $arguments['stderr'] === true) ? 'php://stderr' : null, - $arguments['verbose'], - $arguments['colors'], - $arguments['debug'], - $arguments['columns'], - $arguments['reverseList'], - ); - - assert($object instanceof ResultPrinter); - - return $object; - } - - private function codeCoverageGenerationStart(string $format): void - { - $this->write( - sprintf( - "\nGenerating code coverage report in %s format ... ", - $format, - ), - ); - - $this->timer->start(); - } - - private function codeCoverageGenerationSucceeded(): void - { - $this->write( - sprintf( - "done [%s]\n", - $this->timer->stop()->asString(), - ), - ); - } - - private function codeCoverageGenerationFailed(\Exception $e): void - { - $this->write( - sprintf( - "failed [%s]\n%s\n", - $this->timer->stop()->asString(), - $e->getMessage(), - ), - ); } } diff --git a/app/vendor/phpunit/phpunit/src/TextUI/TestSuiteFilterProcessor.php b/app/vendor/phpunit/phpunit/src/TextUI/TestSuiteFilterProcessor.php new file mode 100644 index 000000000..c0e4beb19 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/TextUI/TestSuiteFilterProcessor.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\TextUI; + +use function array_map; +use PHPUnit\Event; +use PHPUnit\Framework\TestSuite; +use PHPUnit\Runner\Filter\Factory; +use PHPUnit\TextUI\Configuration\Configuration; +use PHPUnit\TextUI\Configuration\FilterNotConfiguredException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class TestSuiteFilterProcessor +{ + /** + * @throws Event\RuntimeException + * @throws FilterNotConfiguredException + */ + public function process(Configuration $configuration, TestSuite $suite): void + { + $factory = new Factory; + + if (!$configuration->hasFilter() && + !$configuration->hasGroups() && + !$configuration->hasExcludeGroups() && + !$configuration->hasExcludeFilter() && + !$configuration->hasTestsCovering() && + !$configuration->hasTestsUsing() && + !$configuration->hasTestsRequiringPhpExtension()) { + return; + } + + if ($configuration->hasExcludeGroups()) { + $factory->addExcludeGroupFilter( + $configuration->excludeGroups(), + ); + } + + if ($configuration->hasGroups()) { + $factory->addIncludeGroupFilter( + $configuration->groups(), + ); + } + + if ($configuration->hasTestsCovering()) { + $factory->addIncludeGroupFilter( + array_map( + static fn (string $name): string => '__phpunit_covers_' . $name, + $configuration->testsCovering(), + ), + ); + } + + if ($configuration->hasTestsUsing()) { + $factory->addIncludeGroupFilter( + array_map( + static fn (string $name): string => '__phpunit_uses_' . $name, + $configuration->testsUsing(), + ), + ); + } + + if ($configuration->hasTestsRequiringPhpExtension()) { + $factory->addIncludeGroupFilter( + array_map( + static fn (string $name): string => '__phpunit_requires_php_extension' . $name, + $configuration->testsRequiringPhpExtension(), + ), + ); + } + + if ($configuration->hasExcludeFilter()) { + $factory->addExcludeNameFilter( + $configuration->excludeFilter(), + ); + } + + if ($configuration->hasFilter()) { + $factory->addIncludeNameFilter( + $configuration->filter(), + ); + } + + $suite->injectFilter($factory); + + Event\Facade::emitter()->testSuiteFiltered( + Event\TestSuite\TestSuiteBuilder::from($suite), + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/TestSuiteMapper.php b/app/vendor/phpunit/phpunit/src/TextUI/TestSuiteMapper.php deleted file mode 100644 index a0ea593ae..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/TestSuiteMapper.php +++ /dev/null @@ -1,103 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI; - -use const PHP_VERSION; -use function explode; -use function in_array; -use function is_dir; -use function is_file; -use function strpos; -use function version_compare; -use PHPUnit\Framework\Exception as FrameworkException; -use PHPUnit\Framework\TestSuite as TestSuiteObject; -use PHPUnit\TextUI\XmlConfiguration\TestSuiteCollection; -use SebastianBergmann\FileIterator\Facade; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class TestSuiteMapper -{ - /** - * @throws RuntimeException - * @throws TestDirectoryNotFoundException - * @throws TestFileNotFoundException - */ - public function map(TestSuiteCollection $configuration, string $filter): TestSuiteObject - { - try { - $filterAsArray = $filter ? explode(',', $filter) : []; - $result = new TestSuiteObject; - - foreach ($configuration as $testSuiteConfiguration) { - if (!empty($filterAsArray) && !in_array($testSuiteConfiguration->name(), $filterAsArray, true)) { - continue; - } - - $testSuite = new TestSuiteObject($testSuiteConfiguration->name()); - $testSuiteEmpty = true; - - $exclude = []; - - foreach ($testSuiteConfiguration->exclude()->asArray() as $file) { - $exclude[] = $file->path(); - } - - foreach ($testSuiteConfiguration->directories() as $directory) { - if (!version_compare(PHP_VERSION, $directory->phpVersion(), $directory->phpVersionOperator()->asString())) { - continue; - } - - $files = (new Facade)->getFilesAsArray( - $directory->path(), - $directory->suffix(), - $directory->prefix(), - $exclude, - ); - - if (!empty($files)) { - $testSuite->addTestFiles($files); - - $testSuiteEmpty = false; - } elseif (strpos($directory->path(), '*') === false && !is_dir($directory->path())) { - throw new TestDirectoryNotFoundException($directory->path()); - } - } - - foreach ($testSuiteConfiguration->files() as $file) { - if (!is_file($file->path())) { - throw new TestFileNotFoundException($file->path()); - } - - if (!version_compare(PHP_VERSION, $file->phpVersion(), $file->phpVersionOperator()->asString())) { - continue; - } - - $testSuite->addTestFile($file->path()); - - $testSuiteEmpty = false; - } - - if (!$testSuiteEmpty) { - $result->addTest($testSuite); - } - } - - return $result; - } catch (FrameworkException $e) { - throw new RuntimeException( - $e->getMessage(), - $e->getCode(), - $e, - ); - } - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/CodeCoverage.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/CodeCoverage.php deleted file mode 100644 index 191113c6e..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/CodeCoverage.php +++ /dev/null @@ -1,363 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage; - -use function count; -use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Filter\DirectoryCollection; -use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Clover; -use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Cobertura; -use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Crap4j; -use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Html; -use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Php; -use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Text; -use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Xml; -use PHPUnit\TextUI\XmlConfiguration\Directory; -use PHPUnit\TextUI\XmlConfiguration\Exception; -use PHPUnit\TextUI\XmlConfiguration\FileCollection; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @psalm-immutable - */ -final class CodeCoverage -{ - /** - * @var ?Directory - */ - private $cacheDirectory; - - /** - * @var DirectoryCollection - */ - private $directories; - - /** - * @var FileCollection - */ - private $files; - - /** - * @var DirectoryCollection - */ - private $excludeDirectories; - - /** - * @var FileCollection - */ - private $excludeFiles; - - /** - * @var bool - */ - private $pathCoverage; - - /** - * @var bool - */ - private $includeUncoveredFiles; - - /** - * @var bool - */ - private $processUncoveredFiles; - - /** - * @var bool - */ - private $ignoreDeprecatedCodeUnits; - - /** - * @var bool - */ - private $disableCodeCoverageIgnore; - - /** - * @var ?Clover - */ - private $clover; - - /** - * @var ?Cobertura - */ - private $cobertura; - - /** - * @var ?Crap4j - */ - private $crap4j; - - /** - * @var ?Html - */ - private $html; - - /** - * @var ?Php - */ - private $php; - - /** - * @var ?Text - */ - private $text; - - /** - * @var ?Xml - */ - private $xml; - - public function __construct(?Directory $cacheDirectory, DirectoryCollection $directories, FileCollection $files, DirectoryCollection $excludeDirectories, FileCollection $excludeFiles, bool $pathCoverage, bool $includeUncoveredFiles, bool $processUncoveredFiles, bool $ignoreDeprecatedCodeUnits, bool $disableCodeCoverageIgnore, ?Clover $clover, ?Cobertura $cobertura, ?Crap4j $crap4j, ?Html $html, ?Php $php, ?Text $text, ?Xml $xml) - { - $this->cacheDirectory = $cacheDirectory; - $this->directories = $directories; - $this->files = $files; - $this->excludeDirectories = $excludeDirectories; - $this->excludeFiles = $excludeFiles; - $this->pathCoverage = $pathCoverage; - $this->includeUncoveredFiles = $includeUncoveredFiles; - $this->processUncoveredFiles = $processUncoveredFiles; - $this->ignoreDeprecatedCodeUnits = $ignoreDeprecatedCodeUnits; - $this->disableCodeCoverageIgnore = $disableCodeCoverageIgnore; - $this->clover = $clover; - $this->cobertura = $cobertura; - $this->crap4j = $crap4j; - $this->html = $html; - $this->php = $php; - $this->text = $text; - $this->xml = $xml; - } - - /** - * @psalm-assert-if-true !null $this->cacheDirectory - */ - public function hasCacheDirectory(): bool - { - return $this->cacheDirectory !== null; - } - - /** - * @throws Exception - */ - public function cacheDirectory(): Directory - { - if (!$this->hasCacheDirectory()) { - throw new Exception( - 'No cache directory has been configured', - ); - } - - return $this->cacheDirectory; - } - - public function hasNonEmptyListOfFilesToBeIncludedInCodeCoverageReport(): bool - { - return count($this->directories) > 0 || count($this->files) > 0; - } - - public function directories(): DirectoryCollection - { - return $this->directories; - } - - public function files(): FileCollection - { - return $this->files; - } - - public function excludeDirectories(): DirectoryCollection - { - return $this->excludeDirectories; - } - - public function excludeFiles(): FileCollection - { - return $this->excludeFiles; - } - - public function pathCoverage(): bool - { - return $this->pathCoverage; - } - - public function includeUncoveredFiles(): bool - { - return $this->includeUncoveredFiles; - } - - public function ignoreDeprecatedCodeUnits(): bool - { - return $this->ignoreDeprecatedCodeUnits; - } - - public function disableCodeCoverageIgnore(): bool - { - return $this->disableCodeCoverageIgnore; - } - - public function processUncoveredFiles(): bool - { - return $this->processUncoveredFiles; - } - - /** - * @psalm-assert-if-true !null $this->clover - */ - public function hasClover(): bool - { - return $this->clover !== null; - } - - /** - * @throws Exception - */ - public function clover(): Clover - { - if (!$this->hasClover()) { - throw new Exception( - 'Code Coverage report "Clover XML" has not been configured', - ); - } - - return $this->clover; - } - - /** - * @psalm-assert-if-true !null $this->cobertura - */ - public function hasCobertura(): bool - { - return $this->cobertura !== null; - } - - /** - * @throws Exception - */ - public function cobertura(): Cobertura - { - if (!$this->hasCobertura()) { - throw new Exception( - 'Code Coverage report "Cobertura XML" has not been configured', - ); - } - - return $this->cobertura; - } - - /** - * @psalm-assert-if-true !null $this->crap4j - */ - public function hasCrap4j(): bool - { - return $this->crap4j !== null; - } - - /** - * @throws Exception - */ - public function crap4j(): Crap4j - { - if (!$this->hasCrap4j()) { - throw new Exception( - 'Code Coverage report "Crap4J" has not been configured', - ); - } - - return $this->crap4j; - } - - /** - * @psalm-assert-if-true !null $this->html - */ - public function hasHtml(): bool - { - return $this->html !== null; - } - - /** - * @throws Exception - */ - public function html(): Html - { - if (!$this->hasHtml()) { - throw new Exception( - 'Code Coverage report "HTML" has not been configured', - ); - } - - return $this->html; - } - - /** - * @psalm-assert-if-true !null $this->php - */ - public function hasPhp(): bool - { - return $this->php !== null; - } - - /** - * @throws Exception - */ - public function php(): Php - { - if (!$this->hasPhp()) { - throw new Exception( - 'Code Coverage report "PHP" has not been configured', - ); - } - - return $this->php; - } - - /** - * @psalm-assert-if-true !null $this->text - */ - public function hasText(): bool - { - return $this->text !== null; - } - - /** - * @throws Exception - */ - public function text(): Text - { - if (!$this->hasText()) { - throw new Exception( - 'Code Coverage report "Text" has not been configured', - ); - } - - return $this->text; - } - - /** - * @psalm-assert-if-true !null $this->xml - */ - public function hasXml(): bool - { - return $this->xml !== null; - } - - /** - * @throws Exception - */ - public function xml(): Xml - { - if (!$this->hasXml()) { - throw new Exception( - 'Code Coverage report "XML" has not been configured', - ); - } - - return $this->xml; - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Filter/Directory.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Filter/Directory.php deleted file mode 100644 index 91659f4dc..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Filter/Directory.php +++ /dev/null @@ -1,66 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Filter; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @psalm-immutable - */ -final class Directory -{ - /** - * @var string - */ - private $path; - - /** - * @var string - */ - private $prefix; - - /** - * @var string - */ - private $suffix; - - /** - * @var string - */ - private $group; - - public function __construct(string $path, string $prefix, string $suffix, string $group) - { - $this->path = $path; - $this->prefix = $prefix; - $this->suffix = $suffix; - $this->group = $group; - } - - public function path(): string - { - return $this->path; - } - - public function prefix(): string - { - return $this->prefix; - } - - public function suffix(): string - { - return $this->suffix; - } - - public function group(): string - { - return $this->group; - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Filter/DirectoryCollection.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Filter/DirectoryCollection.php deleted file mode 100644 index 88ec1e384..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Filter/DirectoryCollection.php +++ /dev/null @@ -1,60 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Filter; - -use function count; -use Countable; -use IteratorAggregate; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @psalm-immutable - * - * @template-implements IteratorAggregate - */ -final class DirectoryCollection implements Countable, IteratorAggregate -{ - /** - * @var Directory[] - */ - private $directories; - - /** - * @param Directory[] $directories - */ - public static function fromArray(array $directories): self - { - return new self(...$directories); - } - - private function __construct(Directory ...$directories) - { - $this->directories = $directories; - } - - /** - * @return Directory[] - */ - public function asArray(): array - { - return $this->directories; - } - - public function count(): int - { - return count($this->directories); - } - - public function getIterator(): DirectoryCollectionIterator - { - return new DirectoryCollectionIterator($this); - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Filter/DirectoryCollectionIterator.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Filter/DirectoryCollectionIterator.php deleted file mode 100644 index f2fee25d4..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Filter/DirectoryCollectionIterator.php +++ /dev/null @@ -1,68 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Filter; - -use function count; -use function iterator_count; -use Countable; -use Iterator; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @template-implements Iterator - */ -final class DirectoryCollectionIterator implements Countable, Iterator -{ - /** - * @var Directory[] - */ - private $directories; - - /** - * @var int - */ - private $position; - - public function __construct(DirectoryCollection $directories) - { - $this->directories = $directories->asArray(); - } - - public function count(): int - { - return iterator_count($this); - } - - public function rewind(): void - { - $this->position = 0; - } - - public function valid(): bool - { - return $this->position < count($this->directories); - } - - public function key(): int - { - return $this->position; - } - - public function current(): Directory - { - return $this->directories[$this->position]; - } - - public function next(): void - { - $this->position++; - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/FilterMapper.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/FilterMapper.php deleted file mode 100644 index 82be60323..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/FilterMapper.php +++ /dev/null @@ -1,45 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage; - -use SebastianBergmann\CodeCoverage\Filter; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class FilterMapper -{ - public function map(Filter $filter, CodeCoverage $configuration): void - { - foreach ($configuration->directories() as $directory) { - $filter->includeDirectory( - $directory->path(), - $directory->suffix(), - $directory->prefix(), - ); - } - - foreach ($configuration->files() as $file) { - $filter->includeFile($file->path()); - } - - foreach ($configuration->excludeDirectories() as $directory) { - $filter->excludeDirectory( - $directory->path(), - $directory->suffix(), - $directory->prefix(), - ); - } - - foreach ($configuration->excludeFiles() as $file) { - $filter->excludeFile($file->path()); - } - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Report/Clover.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Report/Clover.php deleted file mode 100644 index b1094ec31..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Report/Clover.php +++ /dev/null @@ -1,35 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report; - -use PHPUnit\TextUI\XmlConfiguration\File; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @psalm-immutable - */ -final class Clover -{ - /** - * @var File - */ - private $target; - - public function __construct(File $target) - { - $this->target = $target; - } - - public function target(): File - { - return $this->target; - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Report/Cobertura.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Report/Cobertura.php deleted file mode 100644 index f831ac098..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Report/Cobertura.php +++ /dev/null @@ -1,35 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report; - -use PHPUnit\TextUI\XmlConfiguration\File; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @psalm-immutable - */ -final class Cobertura -{ - /** - * @var File - */ - private $target; - - public function __construct(File $target) - { - $this->target = $target; - } - - public function target(): File - { - return $this->target; - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Report/Html.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Report/Html.php deleted file mode 100644 index ce3d0284a..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Report/Html.php +++ /dev/null @@ -1,57 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report; - -use PHPUnit\TextUI\XmlConfiguration\Directory; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @psalm-immutable - */ -final class Html -{ - /** - * @var Directory - */ - private $target; - - /** - * @var int - */ - private $lowUpperBound; - - /** - * @var int - */ - private $highLowerBound; - - public function __construct(Directory $target, int $lowUpperBound, int $highLowerBound) - { - $this->target = $target; - $this->lowUpperBound = $lowUpperBound; - $this->highLowerBound = $highLowerBound; - } - - public function target(): Directory - { - return $this->target; - } - - public function lowUpperBound(): int - { - return $this->lowUpperBound; - } - - public function highLowerBound(): int - { - return $this->highLowerBound; - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Report/Php.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Report/Php.php deleted file mode 100644 index dc5d32eab..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Report/Php.php +++ /dev/null @@ -1,35 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report; - -use PHPUnit\TextUI\XmlConfiguration\File; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @psalm-immutable - */ -final class Php -{ - /** - * @var File - */ - private $target; - - public function __construct(File $target) - { - $this->target = $target; - } - - public function target(): File - { - return $this->target; - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Report/Text.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Report/Text.php deleted file mode 100644 index cb7470d3b..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Report/Text.php +++ /dev/null @@ -1,57 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report; - -use PHPUnit\TextUI\XmlConfiguration\File; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @psalm-immutable - */ -final class Text -{ - /** - * @var File - */ - private $target; - - /** - * @var bool - */ - private $showUncoveredFiles; - - /** - * @var bool - */ - private $showOnlySummary; - - public function __construct(File $target, bool $showUncoveredFiles, bool $showOnlySummary) - { - $this->target = $target; - $this->showUncoveredFiles = $showUncoveredFiles; - $this->showOnlySummary = $showOnlySummary; - } - - public function target(): File - { - return $this->target; - } - - public function showUncoveredFiles(): bool - { - return $this->showUncoveredFiles; - } - - public function showOnlySummary(): bool - { - return $this->showOnlySummary; - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Report/Xml.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Report/Xml.php deleted file mode 100644 index 34073bd59..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/CodeCoverage/Report/Xml.php +++ /dev/null @@ -1,35 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report; - -use PHPUnit\TextUI\XmlConfiguration\Directory; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @psalm-immutable - */ -final class Xml -{ - /** - * @var Directory - */ - private $target; - - public function __construct(Directory $target) - { - $this->target = $target; - } - - public function target(): Directory - { - return $this->target; - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Configuration.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Configuration.php deleted file mode 100644 index 4067e2f89..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Configuration.php +++ /dev/null @@ -1,152 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\CodeCoverage; -use PHPUnit\TextUI\XmlConfiguration\Logging\Logging; -use PHPUnit\Util\Xml\ValidationResult; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @psalm-immutable - */ -final class Configuration -{ - /** - * @var string - */ - private $filename; - - /** - * @var ValidationResult - */ - private $validationResult; - - /** - * @var ExtensionCollection - */ - private $extensions; - - /** - * @var CodeCoverage - */ - private $codeCoverage; - - /** - * @var Groups - */ - private $groups; - - /** - * @var Groups - */ - private $testdoxGroups; - - /** - * @var ExtensionCollection - */ - private $listeners; - - /** - * @var Logging - */ - private $logging; - - /** - * @var Php - */ - private $php; - - /** - * @var PHPUnit - */ - private $phpunit; - - /** - * @var TestSuiteCollection - */ - private $testSuite; - - public function __construct(string $filename, ValidationResult $validationResult, ExtensionCollection $extensions, CodeCoverage $codeCoverage, Groups $groups, Groups $testdoxGroups, ExtensionCollection $listeners, Logging $logging, Php $php, PHPUnit $phpunit, TestSuiteCollection $testSuite) - { - $this->filename = $filename; - $this->validationResult = $validationResult; - $this->extensions = $extensions; - $this->codeCoverage = $codeCoverage; - $this->groups = $groups; - $this->testdoxGroups = $testdoxGroups; - $this->listeners = $listeners; - $this->logging = $logging; - $this->php = $php; - $this->phpunit = $phpunit; - $this->testSuite = $testSuite; - } - - public function filename(): string - { - return $this->filename; - } - - public function hasValidationErrors(): bool - { - return $this->validationResult->hasValidationErrors(); - } - - public function validationErrors(): string - { - return $this->validationResult->asString(); - } - - public function extensions(): ExtensionCollection - { - return $this->extensions; - } - - public function codeCoverage(): CodeCoverage - { - return $this->codeCoverage; - } - - public function groups(): Groups - { - return $this->groups; - } - - public function testdoxGroups(): Groups - { - return $this->testdoxGroups; - } - - public function listeners(): ExtensionCollection - { - return $this->listeners; - } - - public function logging(): Logging - { - return $this->logging; - } - - public function php(): Php - { - return $this->php; - } - - public function phpunit(): PHPUnit - { - return $this->phpunit; - } - - public function testSuite(): TestSuiteCollection - { - return $this->testSuite; - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Exception.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Exception.php deleted file mode 100644 index 162b37e88..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Exception.php +++ /dev/null @@ -1,19 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use RuntimeException; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class Exception extends RuntimeException implements \PHPUnit\Exception -{ -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Filesystem/Directory.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Filesystem/Directory.php deleted file mode 100644 index b0fdf64fb..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Filesystem/Directory.php +++ /dev/null @@ -1,33 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @psalm-immutable - */ -final class Directory -{ - /** - * @var string - */ - private $path; - - public function __construct(string $path) - { - $this->path = $path; - } - - public function path(): string - { - return $this->path; - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Filesystem/DirectoryCollection.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Filesystem/DirectoryCollection.php deleted file mode 100644 index cb8408925..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Filesystem/DirectoryCollection.php +++ /dev/null @@ -1,65 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use function count; -use Countable; -use IteratorAggregate; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @psalm-immutable - * - * @template-implements IteratorAggregate - */ -final class DirectoryCollection implements Countable, IteratorAggregate -{ - /** - * @var Directory[] - */ - private $directories; - - /** - * @param Directory[] $directories - */ - public static function fromArray(array $directories): self - { - return new self(...$directories); - } - - private function __construct(Directory ...$directories) - { - $this->directories = $directories; - } - - /** - * @return Directory[] - */ - public function asArray(): array - { - return $this->directories; - } - - public function count(): int - { - return count($this->directories); - } - - public function getIterator(): DirectoryCollectionIterator - { - return new DirectoryCollectionIterator($this); - } - - public function isEmpty(): bool - { - return $this->count() === 0; - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Filesystem/DirectoryCollectionIterator.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Filesystem/DirectoryCollectionIterator.php deleted file mode 100644 index 4b0927447..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Filesystem/DirectoryCollectionIterator.php +++ /dev/null @@ -1,68 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use function count; -use function iterator_count; -use Countable; -use Iterator; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @template-implements Iterator - */ -final class DirectoryCollectionIterator implements Countable, Iterator -{ - /** - * @var Directory[] - */ - private $directories; - - /** - * @var int - */ - private $position; - - public function __construct(DirectoryCollection $directories) - { - $this->directories = $directories->asArray(); - } - - public function count(): int - { - return iterator_count($this); - } - - public function rewind(): void - { - $this->position = 0; - } - - public function valid(): bool - { - return $this->position < count($this->directories); - } - - public function key(): int - { - return $this->position; - } - - public function current(): Directory - { - return $this->directories[$this->position]; - } - - public function next(): void - { - $this->position++; - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Filesystem/File.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Filesystem/File.php deleted file mode 100644 index 6bdd1c249..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Filesystem/File.php +++ /dev/null @@ -1,33 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @psalm-immutable - */ -final class File -{ - /** - * @var string - */ - private $path; - - public function __construct(string $path) - { - $this->path = $path; - } - - public function path(): string - { - return $this->path; - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Filesystem/FileCollection.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Filesystem/FileCollection.php deleted file mode 100644 index 60e7e401b..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Filesystem/FileCollection.php +++ /dev/null @@ -1,65 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use function count; -use Countable; -use IteratorAggregate; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @psalm-immutable - * - * @template-implements IteratorAggregate - */ -final class FileCollection implements Countable, IteratorAggregate -{ - /** - * @var File[] - */ - private $files; - - /** - * @param File[] $files - */ - public static function fromArray(array $files): self - { - return new self(...$files); - } - - private function __construct(File ...$files) - { - $this->files = $files; - } - - /** - * @return File[] - */ - public function asArray(): array - { - return $this->files; - } - - public function count(): int - { - return count($this->files); - } - - public function getIterator(): FileCollectionIterator - { - return new FileCollectionIterator($this); - } - - public function isEmpty(): bool - { - return $this->count() === 0; - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Filesystem/FileCollectionIterator.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Filesystem/FileCollectionIterator.php deleted file mode 100644 index 0ce4273d1..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Filesystem/FileCollectionIterator.php +++ /dev/null @@ -1,68 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use function count; -use function iterator_count; -use Countable; -use Iterator; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @template-implements Iterator - */ -final class FileCollectionIterator implements Countable, Iterator -{ - /** - * @var File[] - */ - private $files; - - /** - * @var int - */ - private $position; - - public function __construct(FileCollection $files) - { - $this->files = $files->asArray(); - } - - public function count(): int - { - return iterator_count($this); - } - - public function rewind(): void - { - $this->position = 0; - } - - public function valid(): bool - { - return $this->position < count($this->files); - } - - public function key(): int - { - return $this->position; - } - - public function current(): File - { - return $this->files[$this->position]; - } - - public function next(): void - { - $this->position++; - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Generator.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Generator.php deleted file mode 100644 index 9f6a812a4..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Generator.php +++ /dev/null @@ -1,73 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use function str_replace; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class Generator -{ - /** - * @var string - */ - private const TEMPLATE = <<<'EOT' - - - - - {tests_directory} - - - - - - {src_directory} - - - - -EOT; - - public function generateDefaultConfiguration(string $phpunitVersion, string $bootstrapScript, string $testsDirectory, string $srcDirectory, string $cacheDirectory): string - { - return str_replace( - [ - '{phpunit_version}', - '{bootstrap_script}', - '{tests_directory}', - '{src_directory}', - '{cache_directory}', - ], - [ - $phpunitVersion, - $bootstrapScript, - $testsDirectory, - $srcDirectory, - $cacheDirectory, - ], - self::TEMPLATE, - ); - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Group/Group.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Group/Group.php deleted file mode 100644 index bb0d9252a..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Group/Group.php +++ /dev/null @@ -1,33 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @psalm-immutable - */ -final class Group -{ - /** - * @var string - */ - private $name; - - public function __construct(string $name) - { - $this->name = $name; - } - - public function name(): string - { - return $this->name; - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Group/GroupCollection.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Group/GroupCollection.php deleted file mode 100644 index 735d8af18..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Group/GroupCollection.php +++ /dev/null @@ -1,72 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use IteratorAggregate; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @psalm-immutable - * - * @template-implements IteratorAggregate - */ -final class GroupCollection implements IteratorAggregate -{ - /** - * @var Group[] - */ - private $groups; - - /** - * @param Group[] $groups - */ - public static function fromArray(array $groups): self - { - return new self(...$groups); - } - - private function __construct(Group ...$groups) - { - $this->groups = $groups; - } - - /** - * @return Group[] - */ - public function asArray(): array - { - return $this->groups; - } - - /** - * @return string[] - */ - public function asArrayOfStrings(): array - { - $result = []; - - foreach ($this->groups as $group) { - $result[] = $group->name(); - } - - return $result; - } - - public function isEmpty(): bool - { - return empty($this->groups); - } - - public function getIterator(): GroupCollectionIterator - { - return new GroupCollectionIterator($this); - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Group/GroupCollectionIterator.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Group/GroupCollectionIterator.php deleted file mode 100644 index 843a708e5..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Group/GroupCollectionIterator.php +++ /dev/null @@ -1,68 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use function count; -use function iterator_count; -use Countable; -use Iterator; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @template-implements Iterator - */ -final class GroupCollectionIterator implements Countable, Iterator -{ - /** - * @var Group[] - */ - private $groups; - - /** - * @var int - */ - private $position; - - public function __construct(GroupCollection $groups) - { - $this->groups = $groups->asArray(); - } - - public function count(): int - { - return iterator_count($this); - } - - public function rewind(): void - { - $this->position = 0; - } - - public function valid(): bool - { - return $this->position < count($this->groups); - } - - public function key(): int - { - return $this->position; - } - - public function current(): Group - { - return $this->groups[$this->position]; - } - - public function next(): void - { - $this->position++; - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Group/Groups.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Group/Groups.php deleted file mode 100644 index 0604ce328..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Group/Groups.php +++ /dev/null @@ -1,54 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @psalm-immutable - */ -final class Groups -{ - /** - * @var GroupCollection - */ - private $include; - - /** - * @var GroupCollection - */ - private $exclude; - - public function __construct(GroupCollection $include, GroupCollection $exclude) - { - $this->include = $include; - $this->exclude = $exclude; - } - - public function hasInclude(): bool - { - return !$this->include->isEmpty(); - } - - public function include(): GroupCollection - { - return $this->include; - } - - public function hasExclude(): bool - { - return !$this->exclude->isEmpty(); - } - - public function exclude(): GroupCollection - { - return $this->exclude; - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Loader.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Loader.php deleted file mode 100644 index fa677212c..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Loader.php +++ /dev/null @@ -1,1274 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use const DIRECTORY_SEPARATOR; -use const PHP_VERSION; -use function assert; -use function defined; -use function dirname; -use function explode; -use function is_file; -use function is_numeric; -use function preg_match; -use function stream_resolve_include_path; -use function strlen; -use function strpos; -use function strtolower; -use function substr; -use function trim; -use DOMDocument; -use DOMElement; -use DOMNode; -use DOMNodeList; -use DOMXPath; -use PHPUnit\Runner\TestSuiteSorter; -use PHPUnit\Runner\Version; -use PHPUnit\TextUI\DefaultResultPrinter; -use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\CodeCoverage; -use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Filter\Directory as FilterDirectory; -use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Filter\DirectoryCollection as FilterDirectoryCollection; -use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Clover; -use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Cobertura; -use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Crap4j; -use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Html as CodeCoverageHtml; -use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Php as CodeCoveragePhp; -use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Text as CodeCoverageText; -use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Xml as CodeCoverageXml; -use PHPUnit\TextUI\XmlConfiguration\Logging\Junit; -use PHPUnit\TextUI\XmlConfiguration\Logging\Logging; -use PHPUnit\TextUI\XmlConfiguration\Logging\TeamCity; -use PHPUnit\TextUI\XmlConfiguration\Logging\TestDox\Html as TestDoxHtml; -use PHPUnit\TextUI\XmlConfiguration\Logging\TestDox\Text as TestDoxText; -use PHPUnit\TextUI\XmlConfiguration\Logging\TestDox\Xml as TestDoxXml; -use PHPUnit\TextUI\XmlConfiguration\Logging\Text; -use PHPUnit\TextUI\XmlConfiguration\TestSuite as TestSuiteConfiguration; -use PHPUnit\TextUI\XmlConfigurationTest; -use PHPUnit\Util\TestDox\CliTestDoxPrinter; -use PHPUnit\Util\VersionComparisonOperator; -use PHPUnit\Util\Xml; -use PHPUnit\Util\Xml\Exception as XmlException; -use PHPUnit\Util\Xml\Loader as XmlLoader; -use PHPUnit\Util\Xml\SchemaFinder; -use PHPUnit\Util\Xml\Validator; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class Loader -{ - /** - * @throws Exception - */ - public function load(string $filename): Configuration - { - try { - $document = (new XmlLoader)->loadFile($filename, false, true, true); - } catch (XmlException $e) { - throw new Exception( - $e->getMessage(), - $e->getCode(), - $e, - ); - } - - $xpath = new DOMXPath($document); - - try { - $xsdFilename = (new SchemaFinder)->find(Version::series()); - } catch (XmlException $e) { - throw new Exception( - $e->getMessage(), - $e->getCode(), - $e, - ); - } - - return new Configuration( - $filename, - (new Validator)->validate($document, $xsdFilename), - $this->extensions($filename, $xpath), - $this->codeCoverage($filename, $xpath, $document), - $this->groups($xpath), - $this->testdoxGroups($xpath), - $this->listeners($filename, $xpath), - $this->logging($filename, $xpath), - $this->php($filename, $xpath), - $this->phpunit($filename, $document), - $this->testSuite($filename, $xpath), - ); - } - - public function logging(string $filename, DOMXPath $xpath): Logging - { - if ($xpath->query('logging/log')->length !== 0) { - return $this->legacyLogging($filename, $xpath); - } - - $junit = null; - $element = $this->element($xpath, 'logging/junit'); - - if ($element) { - $junit = new Junit( - new File( - $this->toAbsolutePath( - $filename, - (string) $this->getStringAttribute($element, 'outputFile'), - ), - ), - ); - } - - $text = null; - $element = $this->element($xpath, 'logging/text'); - - if ($element) { - $text = new Text( - new File( - $this->toAbsolutePath( - $filename, - (string) $this->getStringAttribute($element, 'outputFile'), - ), - ), - ); - } - - $teamCity = null; - $element = $this->element($xpath, 'logging/teamcity'); - - if ($element) { - $teamCity = new TeamCity( - new File( - $this->toAbsolutePath( - $filename, - (string) $this->getStringAttribute($element, 'outputFile'), - ), - ), - ); - } - - $testDoxHtml = null; - $element = $this->element($xpath, 'logging/testdoxHtml'); - - if ($element) { - $testDoxHtml = new TestDoxHtml( - new File( - $this->toAbsolutePath( - $filename, - (string) $this->getStringAttribute($element, 'outputFile'), - ), - ), - ); - } - - $testDoxText = null; - $element = $this->element($xpath, 'logging/testdoxText'); - - if ($element) { - $testDoxText = new TestDoxText( - new File( - $this->toAbsolutePath( - $filename, - (string) $this->getStringAttribute($element, 'outputFile'), - ), - ), - ); - } - - $testDoxXml = null; - $element = $this->element($xpath, 'logging/testdoxXml'); - - if ($element) { - $testDoxXml = new TestDoxXml( - new File( - $this->toAbsolutePath( - $filename, - (string) $this->getStringAttribute($element, 'outputFile'), - ), - ), - ); - } - - return new Logging( - $junit, - $text, - $teamCity, - $testDoxHtml, - $testDoxText, - $testDoxXml, - ); - } - - public function legacyLogging(string $filename, DOMXPath $xpath): Logging - { - $junit = null; - $teamCity = null; - $testDoxHtml = null; - $testDoxText = null; - $testDoxXml = null; - $text = null; - - foreach ($xpath->query('logging/log') as $log) { - assert($log instanceof DOMElement); - - $type = (string) $log->getAttribute('type'); - $target = (string) $log->getAttribute('target'); - - if (!$target) { - continue; - } - - $target = $this->toAbsolutePath($filename, $target); - - switch ($type) { - case 'plain': - $text = new Text( - new File($target), - ); - - break; - - case 'junit': - $junit = new Junit( - new File($target), - ); - - break; - - case 'teamcity': - $teamCity = new TeamCity( - new File($target), - ); - - break; - - case 'testdox-html': - $testDoxHtml = new TestDoxHtml( - new File($target), - ); - - break; - - case 'testdox-text': - $testDoxText = new TestDoxText( - new File($target), - ); - - break; - - case 'testdox-xml': - $testDoxXml = new TestDoxXml( - new File($target), - ); - - break; - } - } - - return new Logging( - $junit, - $text, - $teamCity, - $testDoxHtml, - $testDoxText, - $testDoxXml, - ); - } - - private function extensions(string $filename, DOMXPath $xpath): ExtensionCollection - { - $extensions = []; - - foreach ($xpath->query('extensions/extension') as $extension) { - assert($extension instanceof DOMElement); - - $extensions[] = $this->getElementConfigurationParameters($filename, $extension); - } - - return ExtensionCollection::fromArray($extensions); - } - - private function getElementConfigurationParameters(string $filename, DOMElement $element): Extension - { - /** @psalm-var class-string $class */ - $class = (string) $element->getAttribute('class'); - $file = ''; - $arguments = $this->getConfigurationArguments($filename, $element->childNodes); - - if ($element->getAttribute('file')) { - $file = $this->toAbsolutePath( - $filename, - (string) $element->getAttribute('file'), - true, - ); - } - - return new Extension($class, $file, $arguments); - } - - private function toAbsolutePath(string $filename, string $path, bool $useIncludePath = false): string - { - $path = trim($path); - - if (strpos($path, '/') === 0) { - return $path; - } - - // Matches the following on Windows: - // - \\NetworkComputer\Path - // - \\.\D: - // - \\.\c: - // - C:\Windows - // - C:\windows - // - C:/windows - // - c:/windows - if (defined('PHP_WINDOWS_VERSION_BUILD') && - ($path[0] === '\\' || (strlen($path) >= 3 && preg_match('#^[A-Z]\:[/\\\]#i', substr($path, 0, 3))))) { - return $path; - } - - if (strpos($path, '://') !== false) { - return $path; - } - - $file = dirname($filename) . DIRECTORY_SEPARATOR . $path; - - if ($useIncludePath && !is_file($file)) { - $includePathFile = stream_resolve_include_path($path); - - if ($includePathFile) { - $file = $includePathFile; - } - } - - return $file; - } - - private function getConfigurationArguments(string $filename, DOMNodeList $nodes): array - { - $arguments = []; - - if ($nodes->length === 0) { - return $arguments; - } - - foreach ($nodes as $node) { - if (!$node instanceof DOMElement) { - continue; - } - - if ($node->tagName !== 'arguments') { - continue; - } - - foreach ($node->childNodes as $argument) { - if (!$argument instanceof DOMElement) { - continue; - } - - if ($argument->tagName === 'file' || $argument->tagName === 'directory') { - $arguments[] = $this->toAbsolutePath($filename, (string) $argument->textContent); - } else { - $arguments[] = Xml::xmlToVariable($argument); - } - } - } - - return $arguments; - } - - private function codeCoverage(string $filename, DOMXPath $xpath, DOMDocument $document): CodeCoverage - { - if ($xpath->query('filter/whitelist')->length !== 0) { - return $this->legacyCodeCoverage($filename, $xpath, $document); - } - - $cacheDirectory = null; - $pathCoverage = false; - $includeUncoveredFiles = true; - $processUncoveredFiles = false; - $ignoreDeprecatedCodeUnits = false; - $disableCodeCoverageIgnore = false; - - $element = $this->element($xpath, 'coverage'); - - if ($element) { - $cacheDirectory = $this->getStringAttribute($element, 'cacheDirectory'); - - if ($cacheDirectory !== null) { - $cacheDirectory = new Directory( - $this->toAbsolutePath($filename, $cacheDirectory), - ); - } - - $pathCoverage = $this->getBooleanAttribute( - $element, - 'pathCoverage', - false, - ); - - $includeUncoveredFiles = $this->getBooleanAttribute( - $element, - 'includeUncoveredFiles', - true, - ); - - $processUncoveredFiles = $this->getBooleanAttribute( - $element, - 'processUncoveredFiles', - false, - ); - - $ignoreDeprecatedCodeUnits = $this->getBooleanAttribute( - $element, - 'ignoreDeprecatedCodeUnits', - false, - ); - - $disableCodeCoverageIgnore = $this->getBooleanAttribute( - $element, - 'disableCodeCoverageIgnore', - false, - ); - } - - $clover = null; - $element = $this->element($xpath, 'coverage/report/clover'); - - if ($element) { - $clover = new Clover( - new File( - $this->toAbsolutePath( - $filename, - (string) $this->getStringAttribute($element, 'outputFile'), - ), - ), - ); - } - - $cobertura = null; - $element = $this->element($xpath, 'coverage/report/cobertura'); - - if ($element) { - $cobertura = new Cobertura( - new File( - $this->toAbsolutePath( - $filename, - (string) $this->getStringAttribute($element, 'outputFile'), - ), - ), - ); - } - - $crap4j = null; - $element = $this->element($xpath, 'coverage/report/crap4j'); - - if ($element) { - $crap4j = new Crap4j( - new File( - $this->toAbsolutePath( - $filename, - (string) $this->getStringAttribute($element, 'outputFile'), - ), - ), - $this->getIntegerAttribute($element, 'threshold', 30), - ); - } - - $html = null; - $element = $this->element($xpath, 'coverage/report/html'); - - if ($element) { - $html = new CodeCoverageHtml( - new Directory( - $this->toAbsolutePath( - $filename, - (string) $this->getStringAttribute($element, 'outputDirectory'), - ), - ), - $this->getIntegerAttribute($element, 'lowUpperBound', 50), - $this->getIntegerAttribute($element, 'highLowerBound', 90), - ); - } - - $php = null; - $element = $this->element($xpath, 'coverage/report/php'); - - if ($element) { - $php = new CodeCoveragePhp( - new File( - $this->toAbsolutePath( - $filename, - (string) $this->getStringAttribute($element, 'outputFile'), - ), - ), - ); - } - - $text = null; - $element = $this->element($xpath, 'coverage/report/text'); - - if ($element) { - $text = new CodeCoverageText( - new File( - $this->toAbsolutePath( - $filename, - (string) $this->getStringAttribute($element, 'outputFile'), - ), - ), - $this->getBooleanAttribute($element, 'showUncoveredFiles', false), - $this->getBooleanAttribute($element, 'showOnlySummary', false), - ); - } - - $xml = null; - $element = $this->element($xpath, 'coverage/report/xml'); - - if ($element) { - $xml = new CodeCoverageXml( - new Directory( - $this->toAbsolutePath( - $filename, - (string) $this->getStringAttribute($element, 'outputDirectory'), - ), - ), - ); - } - - return new CodeCoverage( - $cacheDirectory, - $this->readFilterDirectories($filename, $xpath, 'coverage/include/directory'), - $this->readFilterFiles($filename, $xpath, 'coverage/include/file'), - $this->readFilterDirectories($filename, $xpath, 'coverage/exclude/directory'), - $this->readFilterFiles($filename, $xpath, 'coverage/exclude/file'), - $pathCoverage, - $includeUncoveredFiles, - $processUncoveredFiles, - $ignoreDeprecatedCodeUnits, - $disableCodeCoverageIgnore, - $clover, - $cobertura, - $crap4j, - $html, - $php, - $text, - $xml, - ); - } - - /** - * @deprecated - */ - private function legacyCodeCoverage(string $filename, DOMXPath $xpath, DOMDocument $document): CodeCoverage - { - $ignoreDeprecatedCodeUnits = $this->getBooleanAttribute( - $document->documentElement, - 'ignoreDeprecatedCodeUnitsFromCodeCoverage', - false, - ); - - $disableCodeCoverageIgnore = $this->getBooleanAttribute( - $document->documentElement, - 'disableCodeCoverageIgnore', - false, - ); - - $includeUncoveredFiles = true; - $processUncoveredFiles = false; - - $element = $this->element($xpath, 'filter/whitelist'); - - if ($element) { - if ($element->hasAttribute('addUncoveredFilesFromWhitelist')) { - $includeUncoveredFiles = (bool) $this->getBoolean( - (string) $element->getAttribute('addUncoveredFilesFromWhitelist'), - true, - ); - } - - if ($element->hasAttribute('processUncoveredFilesFromWhitelist')) { - $processUncoveredFiles = (bool) $this->getBoolean( - (string) $element->getAttribute('processUncoveredFilesFromWhitelist'), - false, - ); - } - } - - $clover = null; - $cobertura = null; - $crap4j = null; - $html = null; - $php = null; - $text = null; - $xml = null; - - foreach ($xpath->query('logging/log') as $log) { - assert($log instanceof DOMElement); - - $type = (string) $log->getAttribute('type'); - $target = (string) $log->getAttribute('target'); - - if (!$target) { - continue; - } - - $target = $this->toAbsolutePath($filename, $target); - - switch ($type) { - case 'coverage-clover': - $clover = new Clover( - new File($target), - ); - - break; - - case 'coverage-cobertura': - $cobertura = new Cobertura( - new File($target), - ); - - break; - - case 'coverage-crap4j': - $crap4j = new Crap4j( - new File($target), - $this->getIntegerAttribute($log, 'threshold', 30), - ); - - break; - - case 'coverage-html': - $html = new CodeCoverageHtml( - new Directory($target), - $this->getIntegerAttribute($log, 'lowUpperBound', 50), - $this->getIntegerAttribute($log, 'highLowerBound', 90), - ); - - break; - - case 'coverage-php': - $php = new CodeCoveragePhp( - new File($target), - ); - - break; - - case 'coverage-text': - $text = new CodeCoverageText( - new File($target), - $this->getBooleanAttribute($log, 'showUncoveredFiles', false), - $this->getBooleanAttribute($log, 'showOnlySummary', false), - ); - - break; - - case 'coverage-xml': - $xml = new CodeCoverageXml( - new Directory($target), - ); - - break; - } - } - - return new CodeCoverage( - null, - $this->readFilterDirectories($filename, $xpath, 'filter/whitelist/directory'), - $this->readFilterFiles($filename, $xpath, 'filter/whitelist/file'), - $this->readFilterDirectories($filename, $xpath, 'filter/whitelist/exclude/directory'), - $this->readFilterFiles($filename, $xpath, 'filter/whitelist/exclude/file'), - false, - $includeUncoveredFiles, - $processUncoveredFiles, - $ignoreDeprecatedCodeUnits, - $disableCodeCoverageIgnore, - $clover, - $cobertura, - $crap4j, - $html, - $php, - $text, - $xml, - ); - } - - /** - * If $value is 'false' or 'true', this returns the value that $value represents. - * Otherwise, returns $default, which may be a string in rare cases. - * - * @see XmlConfigurationTest::testPHPConfigurationIsReadCorrectly - * - * @param bool|string $default - * - * @return bool|string - */ - private function getBoolean(string $value, $default) - { - if (strtolower($value) === 'false') { - return false; - } - - if (strtolower($value) === 'true') { - return true; - } - - return $default; - } - - private function readFilterDirectories(string $filename, DOMXPath $xpath, string $query): FilterDirectoryCollection - { - $directories = []; - - foreach ($xpath->query($query) as $directoryNode) { - assert($directoryNode instanceof DOMElement); - - $directoryPath = (string) $directoryNode->textContent; - - if (!$directoryPath) { - continue; - } - - $directories[] = new FilterDirectory( - $this->toAbsolutePath($filename, $directoryPath), - $directoryNode->hasAttribute('prefix') ? (string) $directoryNode->getAttribute('prefix') : '', - $directoryNode->hasAttribute('suffix') ? (string) $directoryNode->getAttribute('suffix') : '.php', - $directoryNode->hasAttribute('group') ? (string) $directoryNode->getAttribute('group') : 'DEFAULT', - ); - } - - return FilterDirectoryCollection::fromArray($directories); - } - - private function readFilterFiles(string $filename, DOMXPath $xpath, string $query): FileCollection - { - $files = []; - - foreach ($xpath->query($query) as $file) { - assert($file instanceof DOMNode); - - $filePath = (string) $file->textContent; - - if ($filePath) { - $files[] = new File($this->toAbsolutePath($filename, $filePath)); - } - } - - return FileCollection::fromArray($files); - } - - private function groups(DOMXPath $xpath): Groups - { - return $this->parseGroupConfiguration($xpath, 'groups'); - } - - private function testdoxGroups(DOMXPath $xpath): Groups - { - return $this->parseGroupConfiguration($xpath, 'testdoxGroups'); - } - - private function parseGroupConfiguration(DOMXPath $xpath, string $root): Groups - { - $include = []; - $exclude = []; - - foreach ($xpath->query($root . '/include/group') as $group) { - assert($group instanceof DOMNode); - - $include[] = new Group((string) $group->textContent); - } - - foreach ($xpath->query($root . '/exclude/group') as $group) { - assert($group instanceof DOMNode); - - $exclude[] = new Group((string) $group->textContent); - } - - return new Groups( - GroupCollection::fromArray($include), - GroupCollection::fromArray($exclude), - ); - } - - private function listeners(string $filename, DOMXPath $xpath): ExtensionCollection - { - $listeners = []; - - foreach ($xpath->query('listeners/listener') as $listener) { - assert($listener instanceof DOMElement); - - $listeners[] = $this->getElementConfigurationParameters($filename, $listener); - } - - return ExtensionCollection::fromArray($listeners); - } - - private function getBooleanAttribute(DOMElement $element, string $attribute, bool $default): bool - { - if (!$element->hasAttribute($attribute)) { - return $default; - } - - return (bool) $this->getBoolean( - (string) $element->getAttribute($attribute), - false, - ); - } - - private function getIntegerAttribute(DOMElement $element, string $attribute, int $default): int - { - if (!$element->hasAttribute($attribute)) { - return $default; - } - - return $this->getInteger( - (string) $element->getAttribute($attribute), - $default, - ); - } - - private function getStringAttribute(DOMElement $element, string $attribute): ?string - { - if (!$element->hasAttribute($attribute)) { - return null; - } - - return (string) $element->getAttribute($attribute); - } - - private function getInteger(string $value, int $default): int - { - if (is_numeric($value)) { - return (int) $value; - } - - return $default; - } - - private function php(string $filename, DOMXPath $xpath): Php - { - $includePaths = []; - - foreach ($xpath->query('php/includePath') as $includePath) { - assert($includePath instanceof DOMNode); - - $path = (string) $includePath->textContent; - - if ($path) { - $includePaths[] = new Directory($this->toAbsolutePath($filename, $path)); - } - } - - $iniSettings = []; - - foreach ($xpath->query('php/ini') as $ini) { - assert($ini instanceof DOMElement); - - $iniSettings[] = new IniSetting( - (string) $ini->getAttribute('name'), - (string) $ini->getAttribute('value'), - ); - } - - $constants = []; - - foreach ($xpath->query('php/const') as $const) { - assert($const instanceof DOMElement); - - $value = (string) $const->getAttribute('value'); - - $constants[] = new Constant( - (string) $const->getAttribute('name'), - $this->getBoolean($value, $value), - ); - } - - $variables = [ - 'var' => [], - 'env' => [], - 'post' => [], - 'get' => [], - 'cookie' => [], - 'server' => [], - 'files' => [], - 'request' => [], - ]; - - foreach (['var', 'env', 'post', 'get', 'cookie', 'server', 'files', 'request'] as $array) { - foreach ($xpath->query('php/' . $array) as $var) { - assert($var instanceof DOMElement); - - $name = (string) $var->getAttribute('name'); - $value = (string) $var->getAttribute('value'); - $force = false; - $verbatim = false; - - if ($var->hasAttribute('force')) { - $force = (bool) $this->getBoolean($var->getAttribute('force'), false); - } - - if ($var->hasAttribute('verbatim')) { - $verbatim = $this->getBoolean($var->getAttribute('verbatim'), false); - } - - if (!$verbatim) { - $value = $this->getBoolean($value, $value); - } - - $variables[$array][] = new Variable($name, $value, $force); - } - } - - return new Php( - DirectoryCollection::fromArray($includePaths), - IniSettingCollection::fromArray($iniSettings), - ConstantCollection::fromArray($constants), - VariableCollection::fromArray($variables['var']), - VariableCollection::fromArray($variables['env']), - VariableCollection::fromArray($variables['post']), - VariableCollection::fromArray($variables['get']), - VariableCollection::fromArray($variables['cookie']), - VariableCollection::fromArray($variables['server']), - VariableCollection::fromArray($variables['files']), - VariableCollection::fromArray($variables['request']), - ); - } - - private function phpunit(string $filename, DOMDocument $document): PHPUnit - { - $executionOrder = TestSuiteSorter::ORDER_DEFAULT; - $defectsFirst = false; - $resolveDependencies = $this->getBooleanAttribute($document->documentElement, 'resolveDependencies', true); - - if ($document->documentElement->hasAttribute('executionOrder')) { - foreach (explode(',', $document->documentElement->getAttribute('executionOrder')) as $order) { - switch ($order) { - case 'default': - $executionOrder = TestSuiteSorter::ORDER_DEFAULT; - $defectsFirst = false; - $resolveDependencies = true; - - break; - - case 'depends': - $resolveDependencies = true; - - break; - - case 'no-depends': - $resolveDependencies = false; - - break; - - case 'defects': - $defectsFirst = true; - - break; - - case 'duration': - $executionOrder = TestSuiteSorter::ORDER_DURATION; - - break; - - case 'random': - $executionOrder = TestSuiteSorter::ORDER_RANDOMIZED; - - break; - - case 'reverse': - $executionOrder = TestSuiteSorter::ORDER_REVERSED; - - break; - - case 'size': - $executionOrder = TestSuiteSorter::ORDER_SIZE; - - break; - } - } - } - - $printerClass = $this->getStringAttribute($document->documentElement, 'printerClass'); - $testdox = $this->getBooleanAttribute($document->documentElement, 'testdox', false); - $conflictBetweenPrinterClassAndTestdox = false; - - if ($testdox) { - if ($printerClass !== null) { - $conflictBetweenPrinterClassAndTestdox = true; - } - - $printerClass = CliTestDoxPrinter::class; - } - - $cacheResultFile = $this->getStringAttribute($document->documentElement, 'cacheResultFile'); - - if ($cacheResultFile !== null) { - $cacheResultFile = $this->toAbsolutePath($filename, $cacheResultFile); - } - - $bootstrap = $this->getStringAttribute($document->documentElement, 'bootstrap'); - - if ($bootstrap !== null) { - $bootstrap = $this->toAbsolutePath($filename, $bootstrap); - } - - $extensionsDirectory = $this->getStringAttribute($document->documentElement, 'extensionsDirectory'); - - if ($extensionsDirectory !== null) { - $extensionsDirectory = $this->toAbsolutePath($filename, $extensionsDirectory); - } - - $testSuiteLoaderFile = $this->getStringAttribute($document->documentElement, 'testSuiteLoaderFile'); - - if ($testSuiteLoaderFile !== null) { - $testSuiteLoaderFile = $this->toAbsolutePath($filename, $testSuiteLoaderFile); - } - - $printerFile = $this->getStringAttribute($document->documentElement, 'printerFile'); - - if ($printerFile !== null) { - $printerFile = $this->toAbsolutePath($filename, $printerFile); - } - - return new PHPUnit( - $this->getBooleanAttribute($document->documentElement, 'cacheResult', true), - $cacheResultFile, - $this->getColumns($document), - $this->getColors($document), - $this->getBooleanAttribute($document->documentElement, 'stderr', false), - $this->getBooleanAttribute($document->documentElement, 'noInteraction', false), - $this->getBooleanAttribute($document->documentElement, 'verbose', false), - $this->getBooleanAttribute($document->documentElement, 'reverseDefectList', false), - $this->getBooleanAttribute($document->documentElement, 'convertDeprecationsToExceptions', false), - $this->getBooleanAttribute($document->documentElement, 'convertErrorsToExceptions', true), - $this->getBooleanAttribute($document->documentElement, 'convertNoticesToExceptions', true), - $this->getBooleanAttribute($document->documentElement, 'convertWarningsToExceptions', true), - $this->getBooleanAttribute($document->documentElement, 'forceCoversAnnotation', false), - $bootstrap, - $this->getBooleanAttribute($document->documentElement, 'processIsolation', false), - $this->getBooleanAttribute($document->documentElement, 'failOnEmptyTestSuite', false), - $this->getBooleanAttribute($document->documentElement, 'failOnIncomplete', false), - $this->getBooleanAttribute($document->documentElement, 'failOnRisky', false), - $this->getBooleanAttribute($document->documentElement, 'failOnSkipped', false), - $this->getBooleanAttribute($document->documentElement, 'failOnWarning', false), - $this->getBooleanAttribute($document->documentElement, 'stopOnDefect', false), - $this->getBooleanAttribute($document->documentElement, 'stopOnError', false), - $this->getBooleanAttribute($document->documentElement, 'stopOnFailure', false), - $this->getBooleanAttribute($document->documentElement, 'stopOnWarning', false), - $this->getBooleanAttribute($document->documentElement, 'stopOnIncomplete', false), - $this->getBooleanAttribute($document->documentElement, 'stopOnRisky', false), - $this->getBooleanAttribute($document->documentElement, 'stopOnSkipped', false), - $extensionsDirectory, - $this->getStringAttribute($document->documentElement, 'testSuiteLoaderClass'), - $testSuiteLoaderFile, - $printerClass, - $printerFile, - $this->getBooleanAttribute($document->documentElement, 'beStrictAboutChangesToGlobalState', false), - $this->getBooleanAttribute($document->documentElement, 'beStrictAboutOutputDuringTests', false), - $this->getBooleanAttribute($document->documentElement, 'beStrictAboutResourceUsageDuringSmallTests', false), - $this->getBooleanAttribute($document->documentElement, 'beStrictAboutTestsThatDoNotTestAnything', true), - $this->getBooleanAttribute($document->documentElement, 'beStrictAboutTodoAnnotatedTests', false), - $this->getBooleanAttribute($document->documentElement, 'beStrictAboutCoversAnnotation', false), - $this->getBooleanAttribute($document->documentElement, 'enforceTimeLimit', false), - $this->getIntegerAttribute($document->documentElement, 'defaultTimeLimit', 1), - $this->getIntegerAttribute($document->documentElement, 'timeoutForSmallTests', 1), - $this->getIntegerAttribute($document->documentElement, 'timeoutForMediumTests', 10), - $this->getIntegerAttribute($document->documentElement, 'timeoutForLargeTests', 60), - $this->getStringAttribute($document->documentElement, 'defaultTestSuite'), - $executionOrder, - $resolveDependencies, - $defectsFirst, - $this->getBooleanAttribute($document->documentElement, 'backupGlobals', false), - $this->getBooleanAttribute($document->documentElement, 'backupStaticAttributes', false), - $this->getBooleanAttribute($document->documentElement, 'registerMockObjectsFromTestArgumentsRecursively', false), - $conflictBetweenPrinterClassAndTestdox, - ); - } - - private function getColors(DOMDocument $document): string - { - $colors = DefaultResultPrinter::COLOR_DEFAULT; - - if ($document->documentElement->hasAttribute('colors')) { - /* only allow boolean for compatibility with previous versions - 'always' only allowed from command line */ - if ($this->getBoolean($document->documentElement->getAttribute('colors'), false)) { - $colors = DefaultResultPrinter::COLOR_AUTO; - } else { - $colors = DefaultResultPrinter::COLOR_NEVER; - } - } - - return $colors; - } - - /** - * @return int|string - */ - private function getColumns(DOMDocument $document) - { - $columns = 80; - - if ($document->documentElement->hasAttribute('columns')) { - $columns = (string) $document->documentElement->getAttribute('columns'); - - if ($columns !== 'max') { - $columns = $this->getInteger($columns, 80); - } - } - - return $columns; - } - - private function testSuite(string $filename, DOMXPath $xpath): TestSuiteCollection - { - $testSuites = []; - - foreach ($this->getTestSuiteElements($xpath) as $element) { - $exclude = []; - - foreach ($element->getElementsByTagName('exclude') as $excludeNode) { - $excludeFile = (string) $excludeNode->textContent; - - if ($excludeFile) { - $exclude[] = new File($this->toAbsolutePath($filename, $excludeFile)); - } - } - - $directories = []; - - foreach ($element->getElementsByTagName('directory') as $directoryNode) { - assert($directoryNode instanceof DOMElement); - - $directory = (string) $directoryNode->textContent; - - if (empty($directory)) { - continue; - } - - $prefix = ''; - - if ($directoryNode->hasAttribute('prefix')) { - $prefix = (string) $directoryNode->getAttribute('prefix'); - } - - $suffix = 'Test.php'; - - if ($directoryNode->hasAttribute('suffix')) { - $suffix = (string) $directoryNode->getAttribute('suffix'); - } - - $phpVersion = PHP_VERSION; - - if ($directoryNode->hasAttribute('phpVersion')) { - $phpVersion = (string) $directoryNode->getAttribute('phpVersion'); - } - - $phpVersionOperator = new VersionComparisonOperator('>='); - - if ($directoryNode->hasAttribute('phpVersionOperator')) { - $phpVersionOperator = new VersionComparisonOperator((string) $directoryNode->getAttribute('phpVersionOperator')); - } - - $directories[] = new TestDirectory( - $this->toAbsolutePath($filename, $directory), - $prefix, - $suffix, - $phpVersion, - $phpVersionOperator, - ); - } - - $files = []; - - foreach ($element->getElementsByTagName('file') as $fileNode) { - assert($fileNode instanceof DOMElement); - - $file = (string) $fileNode->textContent; - - if (empty($file)) { - continue; - } - - $phpVersion = PHP_VERSION; - - if ($fileNode->hasAttribute('phpVersion')) { - $phpVersion = (string) $fileNode->getAttribute('phpVersion'); - } - - $phpVersionOperator = new VersionComparisonOperator('>='); - - if ($fileNode->hasAttribute('phpVersionOperator')) { - $phpVersionOperator = new VersionComparisonOperator((string) $fileNode->getAttribute('phpVersionOperator')); - } - - $files[] = new TestFile( - $this->toAbsolutePath($filename, $file), - $phpVersion, - $phpVersionOperator, - ); - } - - $testSuites[] = new TestSuiteConfiguration( - (string) $element->getAttribute('name'), - TestDirectoryCollection::fromArray($directories), - TestFileCollection::fromArray($files), - FileCollection::fromArray($exclude), - ); - } - - return TestSuiteCollection::fromArray($testSuites); - } - - /** - * @return DOMElement[] - */ - private function getTestSuiteElements(DOMXPath $xpath): array - { - /** @var DOMElement[] $elements */ - $elements = []; - - $testSuiteNodes = $xpath->query('testsuites/testsuite'); - - if ($testSuiteNodes->length === 0) { - $testSuiteNodes = $xpath->query('testsuite'); - } - - if ($testSuiteNodes->length === 1) { - $element = $testSuiteNodes->item(0); - - assert($element instanceof DOMElement); - - $elements[] = $element; - } else { - foreach ($testSuiteNodes as $testSuiteNode) { - assert($testSuiteNode instanceof DOMElement); - - $elements[] = $testSuiteNode; - } - } - - return $elements; - } - - private function element(DOMXPath $xpath, string $element): ?DOMElement - { - $nodes = $xpath->query($element); - - if ($nodes->length === 1) { - $node = $nodes->item(0); - - assert($node instanceof DOMElement); - - return $node; - } - - return null; - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Logging/Junit.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Logging/Junit.php deleted file mode 100644 index 9fca18523..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Logging/Junit.php +++ /dev/null @@ -1,35 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration\Logging; - -use PHPUnit\TextUI\XmlConfiguration\File; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @psalm-immutable - */ -final class Junit -{ - /** - * @var File - */ - private $target; - - public function __construct(File $target) - { - $this->target = $target; - } - - public function target(): File - { - return $this->target; - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Logging/Logging.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Logging/Logging.php deleted file mode 100644 index bce030193..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Logging/Logging.php +++ /dev/null @@ -1,147 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration\Logging; - -use PHPUnit\TextUI\XmlConfiguration\Exception; -use PHPUnit\TextUI\XmlConfiguration\Logging\TestDox\Html as TestDoxHtml; -use PHPUnit\TextUI\XmlConfiguration\Logging\TestDox\Text as TestDoxText; -use PHPUnit\TextUI\XmlConfiguration\Logging\TestDox\Xml as TestDoxXml; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @psalm-immutable - */ -final class Logging -{ - /** - * @var ?Junit - */ - private $junit; - - /** - * @var ?Text - */ - private $text; - - /** - * @var ?TeamCity - */ - private $teamCity; - - /** - * @var ?TestDoxHtml - */ - private $testDoxHtml; - - /** - * @var ?TestDoxText - */ - private $testDoxText; - - /** - * @var ?TestDoxXml - */ - private $testDoxXml; - - public function __construct(?Junit $junit, ?Text $text, ?TeamCity $teamCity, ?TestDoxHtml $testDoxHtml, ?TestDoxText $testDoxText, ?TestDoxXml $testDoxXml) - { - $this->junit = $junit; - $this->text = $text; - $this->teamCity = $teamCity; - $this->testDoxHtml = $testDoxHtml; - $this->testDoxText = $testDoxText; - $this->testDoxXml = $testDoxXml; - } - - public function hasJunit(): bool - { - return $this->junit !== null; - } - - public function junit(): Junit - { - if ($this->junit === null) { - throw new Exception('Logger "JUnit XML" is not configured'); - } - - return $this->junit; - } - - public function hasText(): bool - { - return $this->text !== null; - } - - public function text(): Text - { - if ($this->text === null) { - throw new Exception('Logger "Text" is not configured'); - } - - return $this->text; - } - - public function hasTeamCity(): bool - { - return $this->teamCity !== null; - } - - public function teamCity(): TeamCity - { - if ($this->teamCity === null) { - throw new Exception('Logger "Team City" is not configured'); - } - - return $this->teamCity; - } - - public function hasTestDoxHtml(): bool - { - return $this->testDoxHtml !== null; - } - - public function testDoxHtml(): TestDoxHtml - { - if ($this->testDoxHtml === null) { - throw new Exception('Logger "TestDox HTML" is not configured'); - } - - return $this->testDoxHtml; - } - - public function hasTestDoxText(): bool - { - return $this->testDoxText !== null; - } - - public function testDoxText(): TestDoxText - { - if ($this->testDoxText === null) { - throw new Exception('Logger "TestDox Text" is not configured'); - } - - return $this->testDoxText; - } - - public function hasTestDoxXml(): bool - { - return $this->testDoxXml !== null; - } - - public function testDoxXml(): TestDoxXml - { - if ($this->testDoxXml === null) { - throw new Exception('Logger "TestDox XML" is not configured'); - } - - return $this->testDoxXml; - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Logging/TeamCity.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Logging/TeamCity.php deleted file mode 100644 index 804a7ea66..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Logging/TeamCity.php +++ /dev/null @@ -1,35 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration\Logging; - -use PHPUnit\TextUI\XmlConfiguration\File; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @psalm-immutable - */ -final class TeamCity -{ - /** - * @var File - */ - private $target; - - public function __construct(File $target) - { - $this->target = $target; - } - - public function target(): File - { - return $this->target; - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Logging/TestDox/Html.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Logging/TestDox/Html.php deleted file mode 100644 index 5b198352c..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Logging/TestDox/Html.php +++ /dev/null @@ -1,35 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration\Logging\TestDox; - -use PHPUnit\TextUI\XmlConfiguration\File; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @psalm-immutable - */ -final class Html -{ - /** - * @var File - */ - private $target; - - public function __construct(File $target) - { - $this->target = $target; - } - - public function target(): File - { - return $this->target; - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Logging/TestDox/Text.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Logging/TestDox/Text.php deleted file mode 100644 index 5c742d3a7..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Logging/TestDox/Text.php +++ /dev/null @@ -1,35 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration\Logging\TestDox; - -use PHPUnit\TextUI\XmlConfiguration\File; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @psalm-immutable - */ -final class Text -{ - /** - * @var File - */ - private $target; - - public function __construct(File $target) - { - $this->target = $target; - } - - public function target(): File - { - return $this->target; - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Logging/TestDox/Xml.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Logging/TestDox/Xml.php deleted file mode 100644 index 92dd3b7ba..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Logging/TestDox/Xml.php +++ /dev/null @@ -1,35 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration\Logging\TestDox; - -use PHPUnit\TextUI\XmlConfiguration\File; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @psalm-immutable - */ -final class Xml -{ - /** - * @var File - */ - private $target; - - public function __construct(File $target) - { - $this->target = $target; - } - - public function target(): File - { - return $this->target; - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Logging/Text.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Logging/Text.php deleted file mode 100644 index fd37942f7..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Logging/Text.php +++ /dev/null @@ -1,35 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration\Logging; - -use PHPUnit\TextUI\XmlConfiguration\File; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @psalm-immutable - */ -final class Text -{ - /** - * @var File - */ - private $target; - - public function __construct(File $target) - { - $this->target = $target; - } - - public function target(): File - { - return $this->target; - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/MigrationBuilder.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/MigrationBuilder.php deleted file mode 100644 index a6b264231..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/MigrationBuilder.php +++ /dev/null @@ -1,62 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use function version_compare; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class MigrationBuilder -{ - private const AVAILABLE_MIGRATIONS = [ - '8.5' => [ - RemoveLogTypes::class, - ], - - '9.2' => [ - RemoveCacheTokensAttribute::class, - IntroduceCoverageElement::class, - MoveAttributesFromRootToCoverage::class, - MoveAttributesFromFilterWhitelistToCoverage::class, - MoveWhitelistIncludesToCoverage::class, - MoveWhitelistExcludesToCoverage::class, - RemoveEmptyFilter::class, - CoverageCloverToReport::class, - CoverageCrap4jToReport::class, - CoverageHtmlToReport::class, - CoveragePhpToReport::class, - CoverageTextToReport::class, - CoverageXmlToReport::class, - ConvertLogTypes::class, - UpdateSchemaLocationTo93::class, - ], - ]; - - /** - * @throws MigrationBuilderException - */ - public function build(string $fromVersion): array - { - $stack = []; - - foreach (self::AVAILABLE_MIGRATIONS as $version => $migrations) { - if (version_compare($version, $fromVersion, '<')) { - continue; - } - - foreach ($migrations as $migration) { - $stack[] = new $migration; - } - } - - return $stack; - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/MigrationBuilderException.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/MigrationBuilderException.php deleted file mode 100644 index ebee6e917..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/MigrationBuilderException.php +++ /dev/null @@ -1,20 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use PHPUnit\Exception; -use RuntimeException; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class MigrationBuilderException extends RuntimeException implements Exception -{ -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/RemoveCacheTokensAttribute.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/RemoveCacheTokensAttribute.php deleted file mode 100644 index 0eec12ac1..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/RemoveCacheTokensAttribute.php +++ /dev/null @@ -1,27 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use DOMDocument; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class RemoveCacheTokensAttribute implements Migration -{ - public function migrate(DOMDocument $document): void - { - $root = $document->documentElement; - - if ($root->hasAttribute('cacheTokens')) { - $root->removeAttribute('cacheTokens'); - } - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/UpdateSchemaLocationTo93.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/UpdateSchemaLocationTo93.php deleted file mode 100644 index ddcfcf071..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrations/UpdateSchemaLocationTo93.php +++ /dev/null @@ -1,27 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use DOMDocument; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class UpdateSchemaLocationTo93 implements Migration -{ - public function migrate(DOMDocument $document): void - { - $document->documentElement->setAttributeNS( - 'http://www.w3.org/2001/XMLSchema-instance', - 'xsi:noNamespaceSchemaLocation', - 'https://schema.phpunit.de/9.3/phpunit.xsd', - ); - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrator.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrator.php deleted file mode 100644 index 57bc9f2eb..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/Migration/Migrator.php +++ /dev/null @@ -1,57 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use function sprintf; -use PHPUnit\Util\Xml\Exception as XmlException; -use PHPUnit\Util\Xml\Loader as XmlLoader; -use PHPUnit\Util\Xml\SchemaDetector; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class Migrator -{ - /** - * @throws Exception - * @throws MigrationBuilderException - * @throws MigrationException - * @throws XmlException - */ - public function migrate(string $filename): string - { - $origin = (new SchemaDetector)->detect($filename); - - if (!$origin->detected()) { - throw new Exception( - sprintf( - '"%s" is not a valid PHPUnit XML configuration file that can be migrated', - $filename, - ), - ); - } - - $configurationDocument = (new XmlLoader)->loadFile( - $filename, - false, - true, - true, - ); - - foreach ((new MigrationBuilder)->build($origin->version()) as $migration) { - $migration->migrate($configurationDocument); - } - - $configurationDocument->formatOutput = true; - $configurationDocument->preserveWhiteSpace = false; - - return $configurationDocument->saveXML(); - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/Constant.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/Constant.php deleted file mode 100644 index 6d4bc94cc..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/Constant.php +++ /dev/null @@ -1,44 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @psalm-immutable - */ -final class Constant -{ - /** - * @var string - */ - private $name; - - /** - * @var mixed - */ - private $value; - - public function __construct(string $name, $value) - { - $this->name = $name; - $this->value = $value; - } - - public function name(): string - { - return $this->name; - } - - public function value() - { - return $this->value; - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/ConstantCollection.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/ConstantCollection.php deleted file mode 100644 index 440b0b0bf..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/ConstantCollection.php +++ /dev/null @@ -1,60 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use function count; -use Countable; -use IteratorAggregate; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @psalm-immutable - * - * @template-implements IteratorAggregate - */ -final class ConstantCollection implements Countable, IteratorAggregate -{ - /** - * @var Constant[] - */ - private $constants; - - /** - * @param Constant[] $constants - */ - public static function fromArray(array $constants): self - { - return new self(...$constants); - } - - private function __construct(Constant ...$constants) - { - $this->constants = $constants; - } - - /** - * @return Constant[] - */ - public function asArray(): array - { - return $this->constants; - } - - public function count(): int - { - return count($this->constants); - } - - public function getIterator(): ConstantCollectionIterator - { - return new ConstantCollectionIterator($this); - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/ConstantCollectionIterator.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/ConstantCollectionIterator.php deleted file mode 100644 index 623de961c..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/ConstantCollectionIterator.php +++ /dev/null @@ -1,68 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use function count; -use function iterator_count; -use Countable; -use Iterator; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @template-implements Iterator - */ -final class ConstantCollectionIterator implements Countable, Iterator -{ - /** - * @var Constant[] - */ - private $constants; - - /** - * @var int - */ - private $position; - - public function __construct(ConstantCollection $constants) - { - $this->constants = $constants->asArray(); - } - - public function count(): int - { - return iterator_count($this); - } - - public function rewind(): void - { - $this->position = 0; - } - - public function valid(): bool - { - return $this->position < count($this->constants); - } - - public function key(): int - { - return $this->position; - } - - public function current(): Constant - { - return $this->constants[$this->position]; - } - - public function next(): void - { - $this->position++; - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/IniSetting.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/IniSetting.php deleted file mode 100644 index 4786618d1..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/IniSetting.php +++ /dev/null @@ -1,44 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @psalm-immutable - */ -final class IniSetting -{ - /** - * @var string - */ - private $name; - - /** - * @var string - */ - private $value; - - public function __construct(string $name, string $value) - { - $this->name = $name; - $this->value = $value; - } - - public function name(): string - { - return $this->name; - } - - public function value(): string - { - return $this->value; - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/IniSettingCollection.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/IniSettingCollection.php deleted file mode 100644 index 28e40d93a..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/IniSettingCollection.php +++ /dev/null @@ -1,60 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use function count; -use Countable; -use IteratorAggregate; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @psalm-immutable - * - * @template-implements IteratorAggregate - */ -final class IniSettingCollection implements Countable, IteratorAggregate -{ - /** - * @var IniSetting[] - */ - private $iniSettings; - - /** - * @param IniSetting[] $iniSettings - */ - public static function fromArray(array $iniSettings): self - { - return new self(...$iniSettings); - } - - private function __construct(IniSetting ...$iniSettings) - { - $this->iniSettings = $iniSettings; - } - - /** - * @return IniSetting[] - */ - public function asArray(): array - { - return $this->iniSettings; - } - - public function count(): int - { - return count($this->iniSettings); - } - - public function getIterator(): IniSettingCollectionIterator - { - return new IniSettingCollectionIterator($this); - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/IniSettingCollectionIterator.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/IniSettingCollectionIterator.php deleted file mode 100644 index 6c348b48a..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/IniSettingCollectionIterator.php +++ /dev/null @@ -1,68 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use function count; -use function iterator_count; -use Countable; -use Iterator; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @template-implements Iterator - */ -final class IniSettingCollectionIterator implements Countable, Iterator -{ - /** - * @var IniSetting[] - */ - private $iniSettings; - - /** - * @var int - */ - private $position; - - public function __construct(IniSettingCollection $iniSettings) - { - $this->iniSettings = $iniSettings->asArray(); - } - - public function count(): int - { - return iterator_count($this); - } - - public function rewind(): void - { - $this->position = 0; - } - - public function valid(): bool - { - return $this->position < count($this->iniSettings); - } - - public function key(): int - { - return $this->position; - } - - public function current(): IniSetting - { - return $this->iniSettings[$this->position]; - } - - public function next(): void - { - $this->position++; - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/Php.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/Php.php deleted file mode 100644 index c1e9c6fd1..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/Php.php +++ /dev/null @@ -1,143 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @psalm-immutable - */ -final class Php -{ - /** - * @var DirectoryCollection - */ - private $includePaths; - - /** - * @var IniSettingCollection - */ - private $iniSettings; - - /** - * @var ConstantCollection - */ - private $constants; - - /** - * @var VariableCollection - */ - private $globalVariables; - - /** - * @var VariableCollection - */ - private $envVariables; - - /** - * @var VariableCollection - */ - private $postVariables; - - /** - * @var VariableCollection - */ - private $getVariables; - - /** - * @var VariableCollection - */ - private $cookieVariables; - - /** - * @var VariableCollection - */ - private $serverVariables; - - /** - * @var VariableCollection - */ - private $filesVariables; - - /** - * @var VariableCollection - */ - private $requestVariables; - - public function __construct(DirectoryCollection $includePaths, IniSettingCollection $iniSettings, ConstantCollection $constants, VariableCollection $globalVariables, VariableCollection $envVariables, VariableCollection $postVariables, VariableCollection $getVariables, VariableCollection $cookieVariables, VariableCollection $serverVariables, VariableCollection $filesVariables, VariableCollection $requestVariables) - { - $this->includePaths = $includePaths; - $this->iniSettings = $iniSettings; - $this->constants = $constants; - $this->globalVariables = $globalVariables; - $this->envVariables = $envVariables; - $this->postVariables = $postVariables; - $this->getVariables = $getVariables; - $this->cookieVariables = $cookieVariables; - $this->serverVariables = $serverVariables; - $this->filesVariables = $filesVariables; - $this->requestVariables = $requestVariables; - } - - public function includePaths(): DirectoryCollection - { - return $this->includePaths; - } - - public function iniSettings(): IniSettingCollection - { - return $this->iniSettings; - } - - public function constants(): ConstantCollection - { - return $this->constants; - } - - public function globalVariables(): VariableCollection - { - return $this->globalVariables; - } - - public function envVariables(): VariableCollection - { - return $this->envVariables; - } - - public function postVariables(): VariableCollection - { - return $this->postVariables; - } - - public function getVariables(): VariableCollection - { - return $this->getVariables; - } - - public function cookieVariables(): VariableCollection - { - return $this->cookieVariables; - } - - public function serverVariables(): VariableCollection - { - return $this->serverVariables; - } - - public function filesVariables(): VariableCollection - { - return $this->filesVariables; - } - - public function requestVariables(): VariableCollection - { - return $this->requestVariables; - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/Variable.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/Variable.php deleted file mode 100644 index 37c572ae3..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/Variable.php +++ /dev/null @@ -1,55 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @psalm-immutable - */ -final class Variable -{ - /** - * @var string - */ - private $name; - - /** - * @var mixed - */ - private $value; - - /** - * @var bool - */ - private $force; - - public function __construct(string $name, $value, bool $force) - { - $this->name = $name; - $this->value = $value; - $this->force = $force; - } - - public function name(): string - { - return $this->name; - } - - public function value() - { - return $this->value; - } - - public function force(): bool - { - return $this->force; - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/VariableCollection.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/VariableCollection.php deleted file mode 100644 index 6662db649..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/VariableCollection.php +++ /dev/null @@ -1,60 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use function count; -use Countable; -use IteratorAggregate; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @psalm-immutable - * - * @template-implements IteratorAggregate - */ -final class VariableCollection implements Countable, IteratorAggregate -{ - /** - * @var Variable[] - */ - private $variables; - - /** - * @param Variable[] $variables - */ - public static function fromArray(array $variables): self - { - return new self(...$variables); - } - - private function __construct(Variable ...$variables) - { - $this->variables = $variables; - } - - /** - * @return Variable[] - */ - public function asArray(): array - { - return $this->variables; - } - - public function count(): int - { - return count($this->variables); - } - - public function getIterator(): VariableCollectionIterator - { - return new VariableCollectionIterator($this); - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/VariableCollectionIterator.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/VariableCollectionIterator.php deleted file mode 100644 index 032d0be1e..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHP/VariableCollectionIterator.php +++ /dev/null @@ -1,68 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use function count; -use function iterator_count; -use Countable; -use Iterator; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @template-implements Iterator - */ -final class VariableCollectionIterator implements Countable, Iterator -{ - /** - * @var Variable[] - */ - private $variables; - - /** - * @var int - */ - private $position; - - public function __construct(VariableCollection $variables) - { - $this->variables = $variables->asArray(); - } - - public function count(): int - { - return iterator_count($this); - } - - public function rewind(): void - { - $this->position = 0; - } - - public function valid(): bool - { - return $this->position < count($this->variables); - } - - public function key(): int - { - return $this->position; - } - - public function current(): Variable - { - return $this->variables[$this->position]; - } - - public function next(): void - { - $this->position++; - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHPUnit/Extension.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHPUnit/Extension.php deleted file mode 100644 index 09fe8cc91..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHPUnit/Extension.php +++ /dev/null @@ -1,73 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @psalm-immutable - */ -final class Extension -{ - /** - * @var string - * - * @psalm-var class-string - */ - private $className; - - /** - * @var string - */ - private $sourceFile; - - /** - * @var array - */ - private $arguments; - - /** - * @psalm-param class-string $className - */ - public function __construct(string $className, string $sourceFile, array $arguments) - { - $this->className = $className; - $this->sourceFile = $sourceFile; - $this->arguments = $arguments; - } - - /** - * @psalm-return class-string - */ - public function className(): string - { - return $this->className; - } - - public function hasSourceFile(): bool - { - return $this->sourceFile !== ''; - } - - public function sourceFile(): string - { - return $this->sourceFile; - } - - public function hasArguments(): bool - { - return !empty($this->arguments); - } - - public function arguments(): array - { - return $this->arguments; - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHPUnit/ExtensionCollection.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHPUnit/ExtensionCollection.php deleted file mode 100644 index 76d07ebc9..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHPUnit/ExtensionCollection.php +++ /dev/null @@ -1,53 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use IteratorAggregate; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @psalm-immutable - * - * @template-implements IteratorAggregate - */ -final class ExtensionCollection implements IteratorAggregate -{ - /** - * @var Extension[] - */ - private $extensions; - - /** - * @param Extension[] $extensions - */ - public static function fromArray(array $extensions): self - { - return new self(...$extensions); - } - - private function __construct(Extension ...$extensions) - { - $this->extensions = $extensions; - } - - /** - * @return Extension[] - */ - public function asArray(): array - { - return $this->extensions; - } - - public function getIterator(): ExtensionCollectionIterator - { - return new ExtensionCollectionIterator($this); - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHPUnit/ExtensionCollectionIterator.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHPUnit/ExtensionCollectionIterator.php deleted file mode 100644 index a9fc1af8e..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHPUnit/ExtensionCollectionIterator.php +++ /dev/null @@ -1,68 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use function count; -use function iterator_count; -use Countable; -use Iterator; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @template-implements Iterator - */ -final class ExtensionCollectionIterator implements Countable, Iterator -{ - /** - * @var Extension[] - */ - private $extensions; - - /** - * @var int - */ - private $position; - - public function __construct(ExtensionCollection $extensions) - { - $this->extensions = $extensions->asArray(); - } - - public function count(): int - { - return iterator_count($this); - } - - public function rewind(): void - { - $this->position = 0; - } - - public function valid(): bool - { - return $this->position < count($this->extensions); - } - - public function key(): int - { - return $this->position; - } - - public function current(): Extension - { - return $this->extensions[$this->position]; - } - - public function next(): void - { - $this->position++; - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHPUnit/PHPUnit.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHPUnit/PHPUnit.php deleted file mode 100644 index 5b3ce9b8d..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/PHPUnit/PHPUnit.php +++ /dev/null @@ -1,715 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @psalm-immutable - */ -final class PHPUnit -{ - /** - * @var bool - */ - private $cacheResult; - - /** - * @var ?string - */ - private $cacheResultFile; - - /** - * @var int|string - */ - private $columns; - - /** - * @var string - */ - private $colors; - - /** - * @var bool - */ - private $stderr; - - /** - * @var bool - */ - private $noInteraction; - - /** - * @var bool - */ - private $verbose; - - /** - * @var bool - */ - private $reverseDefectList; - - /** - * @var bool - */ - private $convertDeprecationsToExceptions; - - /** - * @var bool - */ - private $convertErrorsToExceptions; - - /** - * @var bool - */ - private $convertNoticesToExceptions; - - /** - * @var bool - */ - private $convertWarningsToExceptions; - - /** - * @var bool - */ - private $forceCoversAnnotation; - - /** - * @var ?string - */ - private $bootstrap; - - /** - * @var bool - */ - private $processIsolation; - - /** - * @var bool - */ - private $failOnEmptyTestSuite; - - /** - * @var bool - */ - private $failOnIncomplete; - - /** - * @var bool - */ - private $failOnRisky; - - /** - * @var bool - */ - private $failOnSkipped; - - /** - * @var bool - */ - private $failOnWarning; - - /** - * @var bool - */ - private $stopOnDefect; - - /** - * @var bool - */ - private $stopOnError; - - /** - * @var bool - */ - private $stopOnFailure; - - /** - * @var bool - */ - private $stopOnWarning; - - /** - * @var bool - */ - private $stopOnIncomplete; - - /** - * @var bool - */ - private $stopOnRisky; - - /** - * @var bool - */ - private $stopOnSkipped; - - /** - * @var ?string - */ - private $extensionsDirectory; - - /** - * @var ?string - * - * @deprecated see https://github.com/sebastianbergmann/phpunit/issues/4039 - */ - private $testSuiteLoaderClass; - - /** - * @var ?string - * - * @deprecated see https://github.com/sebastianbergmann/phpunit/issues/4039 - */ - private $testSuiteLoaderFile; - - /** - * @var ?string - */ - private $printerClass; - - /** - * @var ?string - */ - private $printerFile; - - /** - * @var bool - */ - private $beStrictAboutChangesToGlobalState; - - /** - * @var bool - */ - private $beStrictAboutOutputDuringTests; - - /** - * @var bool - */ - private $beStrictAboutResourceUsageDuringSmallTests; - - /** - * @var bool - */ - private $beStrictAboutTestsThatDoNotTestAnything; - - /** - * @var bool - */ - private $beStrictAboutTodoAnnotatedTests; - - /** - * @var bool - */ - private $beStrictAboutCoversAnnotation; - - /** - * @var bool - */ - private $enforceTimeLimit; - - /** - * @var int - */ - private $defaultTimeLimit; - - /** - * @var int - */ - private $timeoutForSmallTests; - - /** - * @var int - */ - private $timeoutForMediumTests; - - /** - * @var int - */ - private $timeoutForLargeTests; - - /** - * @var ?string - */ - private $defaultTestSuite; - - /** - * @var int - */ - private $executionOrder; - - /** - * @var bool - */ - private $resolveDependencies; - - /** - * @var bool - */ - private $defectsFirst; - - /** - * @var bool - */ - private $backupGlobals; - - /** - * @var bool - */ - private $backupStaticAttributes; - - /** - * @var bool - */ - private $registerMockObjectsFromTestArgumentsRecursively; - - /** - * @var bool - */ - private $conflictBetweenPrinterClassAndTestdox; - - public function __construct(bool $cacheResult, ?string $cacheResultFile, $columns, string $colors, bool $stderr, bool $noInteraction, bool $verbose, bool $reverseDefectList, bool $convertDeprecationsToExceptions, bool $convertErrorsToExceptions, bool $convertNoticesToExceptions, bool $convertWarningsToExceptions, bool $forceCoversAnnotation, ?string $bootstrap, bool $processIsolation, bool $failOnEmptyTestSuite, bool $failOnIncomplete, bool $failOnRisky, bool $failOnSkipped, bool $failOnWarning, bool $stopOnDefect, bool $stopOnError, bool $stopOnFailure, bool $stopOnWarning, bool $stopOnIncomplete, bool $stopOnRisky, bool $stopOnSkipped, ?string $extensionsDirectory, ?string $testSuiteLoaderClass, ?string $testSuiteLoaderFile, ?string $printerClass, ?string $printerFile, bool $beStrictAboutChangesToGlobalState, bool $beStrictAboutOutputDuringTests, bool $beStrictAboutResourceUsageDuringSmallTests, bool $beStrictAboutTestsThatDoNotTestAnything, bool $beStrictAboutTodoAnnotatedTests, bool $beStrictAboutCoversAnnotation, bool $enforceTimeLimit, int $defaultTimeLimit, int $timeoutForSmallTests, int $timeoutForMediumTests, int $timeoutForLargeTests, ?string $defaultTestSuite, int $executionOrder, bool $resolveDependencies, bool $defectsFirst, bool $backupGlobals, bool $backupStaticAttributes, bool $registerMockObjectsFromTestArgumentsRecursively, bool $conflictBetweenPrinterClassAndTestdox) - { - $this->cacheResult = $cacheResult; - $this->cacheResultFile = $cacheResultFile; - $this->columns = $columns; - $this->colors = $colors; - $this->stderr = $stderr; - $this->noInteraction = $noInteraction; - $this->verbose = $verbose; - $this->reverseDefectList = $reverseDefectList; - $this->convertDeprecationsToExceptions = $convertDeprecationsToExceptions; - $this->convertErrorsToExceptions = $convertErrorsToExceptions; - $this->convertNoticesToExceptions = $convertNoticesToExceptions; - $this->convertWarningsToExceptions = $convertWarningsToExceptions; - $this->forceCoversAnnotation = $forceCoversAnnotation; - $this->bootstrap = $bootstrap; - $this->processIsolation = $processIsolation; - $this->failOnEmptyTestSuite = $failOnEmptyTestSuite; - $this->failOnIncomplete = $failOnIncomplete; - $this->failOnRisky = $failOnRisky; - $this->failOnSkipped = $failOnSkipped; - $this->failOnWarning = $failOnWarning; - $this->stopOnDefect = $stopOnDefect; - $this->stopOnError = $stopOnError; - $this->stopOnFailure = $stopOnFailure; - $this->stopOnWarning = $stopOnWarning; - $this->stopOnIncomplete = $stopOnIncomplete; - $this->stopOnRisky = $stopOnRisky; - $this->stopOnSkipped = $stopOnSkipped; - $this->extensionsDirectory = $extensionsDirectory; - $this->testSuiteLoaderClass = $testSuiteLoaderClass; - $this->testSuiteLoaderFile = $testSuiteLoaderFile; - $this->printerClass = $printerClass; - $this->printerFile = $printerFile; - $this->beStrictAboutChangesToGlobalState = $beStrictAboutChangesToGlobalState; - $this->beStrictAboutOutputDuringTests = $beStrictAboutOutputDuringTests; - $this->beStrictAboutResourceUsageDuringSmallTests = $beStrictAboutResourceUsageDuringSmallTests; - $this->beStrictAboutTestsThatDoNotTestAnything = $beStrictAboutTestsThatDoNotTestAnything; - $this->beStrictAboutTodoAnnotatedTests = $beStrictAboutTodoAnnotatedTests; - $this->beStrictAboutCoversAnnotation = $beStrictAboutCoversAnnotation; - $this->enforceTimeLimit = $enforceTimeLimit; - $this->defaultTimeLimit = $defaultTimeLimit; - $this->timeoutForSmallTests = $timeoutForSmallTests; - $this->timeoutForMediumTests = $timeoutForMediumTests; - $this->timeoutForLargeTests = $timeoutForLargeTests; - $this->defaultTestSuite = $defaultTestSuite; - $this->executionOrder = $executionOrder; - $this->resolveDependencies = $resolveDependencies; - $this->defectsFirst = $defectsFirst; - $this->backupGlobals = $backupGlobals; - $this->backupStaticAttributes = $backupStaticAttributes; - $this->registerMockObjectsFromTestArgumentsRecursively = $registerMockObjectsFromTestArgumentsRecursively; - $this->conflictBetweenPrinterClassAndTestdox = $conflictBetweenPrinterClassAndTestdox; - } - - public function cacheResult(): bool - { - return $this->cacheResult; - } - - /** - * @psalm-assert-if-true !null $this->cacheResultFile - */ - public function hasCacheResultFile(): bool - { - return $this->cacheResultFile !== null; - } - - /** - * @throws Exception - */ - public function cacheResultFile(): string - { - if (!$this->hasCacheResultFile()) { - throw new Exception('Cache result file is not configured'); - } - - return (string) $this->cacheResultFile; - } - - public function columns() - { - return $this->columns; - } - - public function colors(): string - { - return $this->colors; - } - - public function stderr(): bool - { - return $this->stderr; - } - - public function noInteraction(): bool - { - return $this->noInteraction; - } - - public function verbose(): bool - { - return $this->verbose; - } - - public function reverseDefectList(): bool - { - return $this->reverseDefectList; - } - - public function convertDeprecationsToExceptions(): bool - { - return $this->convertDeprecationsToExceptions; - } - - public function convertErrorsToExceptions(): bool - { - return $this->convertErrorsToExceptions; - } - - public function convertNoticesToExceptions(): bool - { - return $this->convertNoticesToExceptions; - } - - public function convertWarningsToExceptions(): bool - { - return $this->convertWarningsToExceptions; - } - - public function forceCoversAnnotation(): bool - { - return $this->forceCoversAnnotation; - } - - /** - * @psalm-assert-if-true !null $this->bootstrap - */ - public function hasBootstrap(): bool - { - return $this->bootstrap !== null; - } - - /** - * @throws Exception - */ - public function bootstrap(): string - { - if (!$this->hasBootstrap()) { - throw new Exception('Bootstrap script is not configured'); - } - - return (string) $this->bootstrap; - } - - public function processIsolation(): bool - { - return $this->processIsolation; - } - - public function failOnEmptyTestSuite(): bool - { - return $this->failOnEmptyTestSuite; - } - - public function failOnIncomplete(): bool - { - return $this->failOnIncomplete; - } - - public function failOnRisky(): bool - { - return $this->failOnRisky; - } - - public function failOnSkipped(): bool - { - return $this->failOnSkipped; - } - - public function failOnWarning(): bool - { - return $this->failOnWarning; - } - - public function stopOnDefect(): bool - { - return $this->stopOnDefect; - } - - public function stopOnError(): bool - { - return $this->stopOnError; - } - - public function stopOnFailure(): bool - { - return $this->stopOnFailure; - } - - public function stopOnWarning(): bool - { - return $this->stopOnWarning; - } - - public function stopOnIncomplete(): bool - { - return $this->stopOnIncomplete; - } - - public function stopOnRisky(): bool - { - return $this->stopOnRisky; - } - - public function stopOnSkipped(): bool - { - return $this->stopOnSkipped; - } - - /** - * @psalm-assert-if-true !null $this->extensionsDirectory - */ - public function hasExtensionsDirectory(): bool - { - return $this->extensionsDirectory !== null; - } - - /** - * @throws Exception - */ - public function extensionsDirectory(): string - { - if (!$this->hasExtensionsDirectory()) { - throw new Exception('Extensions directory is not configured'); - } - - return (string) $this->extensionsDirectory; - } - - /** - * @psalm-assert-if-true !null $this->testSuiteLoaderClass - * - * @deprecated see https://github.com/sebastianbergmann/phpunit/issues/4039 - */ - public function hasTestSuiteLoaderClass(): bool - { - return $this->testSuiteLoaderClass !== null; - } - - /** - * @throws Exception - * - * @deprecated see https://github.com/sebastianbergmann/phpunit/issues/4039 - */ - public function testSuiteLoaderClass(): string - { - if (!$this->hasTestSuiteLoaderClass()) { - throw new Exception('TestSuiteLoader class is not configured'); - } - - return (string) $this->testSuiteLoaderClass; - } - - /** - * @psalm-assert-if-true !null $this->testSuiteLoaderFile - * - * @deprecated see https://github.com/sebastianbergmann/phpunit/issues/4039 - */ - public function hasTestSuiteLoaderFile(): bool - { - return $this->testSuiteLoaderFile !== null; - } - - /** - * @throws Exception - * - * @deprecated see https://github.com/sebastianbergmann/phpunit/issues/4039 - */ - public function testSuiteLoaderFile(): string - { - if (!$this->hasTestSuiteLoaderFile()) { - throw new Exception('TestSuiteLoader sourcecode file is not configured'); - } - - return (string) $this->testSuiteLoaderFile; - } - - /** - * @psalm-assert-if-true !null $this->printerClass - */ - public function hasPrinterClass(): bool - { - return $this->printerClass !== null; - } - - /** - * @throws Exception - */ - public function printerClass(): string - { - if (!$this->hasPrinterClass()) { - throw new Exception('ResultPrinter class is not configured'); - } - - return (string) $this->printerClass; - } - - /** - * @psalm-assert-if-true !null $this->printerFile - */ - public function hasPrinterFile(): bool - { - return $this->printerFile !== null; - } - - /** - * @throws Exception - */ - public function printerFile(): string - { - if (!$this->hasPrinterFile()) { - throw new Exception('ResultPrinter sourcecode file is not configured'); - } - - return (string) $this->printerFile; - } - - public function beStrictAboutChangesToGlobalState(): bool - { - return $this->beStrictAboutChangesToGlobalState; - } - - public function beStrictAboutOutputDuringTests(): bool - { - return $this->beStrictAboutOutputDuringTests; - } - - public function beStrictAboutResourceUsageDuringSmallTests(): bool - { - return $this->beStrictAboutResourceUsageDuringSmallTests; - } - - public function beStrictAboutTestsThatDoNotTestAnything(): bool - { - return $this->beStrictAboutTestsThatDoNotTestAnything; - } - - public function beStrictAboutTodoAnnotatedTests(): bool - { - return $this->beStrictAboutTodoAnnotatedTests; - } - - public function beStrictAboutCoversAnnotation(): bool - { - return $this->beStrictAboutCoversAnnotation; - } - - public function enforceTimeLimit(): bool - { - return $this->enforceTimeLimit; - } - - public function defaultTimeLimit(): int - { - return $this->defaultTimeLimit; - } - - public function timeoutForSmallTests(): int - { - return $this->timeoutForSmallTests; - } - - public function timeoutForMediumTests(): int - { - return $this->timeoutForMediumTests; - } - - public function timeoutForLargeTests(): int - { - return $this->timeoutForLargeTests; - } - - /** - * @psalm-assert-if-true !null $this->defaultTestSuite - */ - public function hasDefaultTestSuite(): bool - { - return $this->defaultTestSuite !== null; - } - - /** - * @throws Exception - */ - public function defaultTestSuite(): string - { - if (!$this->hasDefaultTestSuite()) { - throw new Exception('Default test suite is not configured'); - } - - return (string) $this->defaultTestSuite; - } - - public function executionOrder(): int - { - return $this->executionOrder; - } - - public function resolveDependencies(): bool - { - return $this->resolveDependencies; - } - - public function defectsFirst(): bool - { - return $this->defectsFirst; - } - - public function backupGlobals(): bool - { - return $this->backupGlobals; - } - - public function backupStaticAttributes(): bool - { - return $this->backupStaticAttributes; - } - - public function registerMockObjectsFromTestArgumentsRecursively(): bool - { - return $this->registerMockObjectsFromTestArgumentsRecursively; - } - - public function conflictBetweenPrinterClassAndTestdox(): bool - { - return $this->conflictBetweenPrinterClassAndTestdox; - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestDirectory.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestDirectory.php deleted file mode 100644 index ecefbb7c9..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestDirectory.php +++ /dev/null @@ -1,79 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use PHPUnit\Util\VersionComparisonOperator; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @psalm-immutable - */ -final class TestDirectory -{ - /** - * @var string - */ - private $path; - - /** - * @var string - */ - private $prefix; - - /** - * @var string - */ - private $suffix; - - /** - * @var string - */ - private $phpVersion; - - /** - * @var VersionComparisonOperator - */ - private $phpVersionOperator; - - public function __construct(string $path, string $prefix, string $suffix, string $phpVersion, VersionComparisonOperator $phpVersionOperator) - { - $this->path = $path; - $this->prefix = $prefix; - $this->suffix = $suffix; - $this->phpVersion = $phpVersion; - $this->phpVersionOperator = $phpVersionOperator; - } - - public function path(): string - { - return $this->path; - } - - public function prefix(): string - { - return $this->prefix; - } - - public function suffix(): string - { - return $this->suffix; - } - - public function phpVersion(): string - { - return $this->phpVersion; - } - - public function phpVersionOperator(): VersionComparisonOperator - { - return $this->phpVersionOperator; - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestDirectoryCollection.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestDirectoryCollection.php deleted file mode 100644 index 5f581c210..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestDirectoryCollection.php +++ /dev/null @@ -1,65 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use function count; -use Countable; -use IteratorAggregate; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @psalm-immutable - * - * @template-implements IteratorAggregate - */ -final class TestDirectoryCollection implements Countable, IteratorAggregate -{ - /** - * @var TestDirectory[] - */ - private $directories; - - /** - * @param TestDirectory[] $directories - */ - public static function fromArray(array $directories): self - { - return new self(...$directories); - } - - private function __construct(TestDirectory ...$directories) - { - $this->directories = $directories; - } - - /** - * @return TestDirectory[] - */ - public function asArray(): array - { - return $this->directories; - } - - public function count(): int - { - return count($this->directories); - } - - public function getIterator(): TestDirectoryCollectionIterator - { - return new TestDirectoryCollectionIterator($this); - } - - public function isEmpty(): bool - { - return $this->count() === 0; - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestDirectoryCollectionIterator.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestDirectoryCollectionIterator.php deleted file mode 100644 index b2312a384..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestDirectoryCollectionIterator.php +++ /dev/null @@ -1,68 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use function count; -use function iterator_count; -use Countable; -use Iterator; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @template-implements Iterator - */ -final class TestDirectoryCollectionIterator implements Countable, Iterator -{ - /** - * @var TestDirectory[] - */ - private $directories; - - /** - * @var int - */ - private $position; - - public function __construct(TestDirectoryCollection $directories) - { - $this->directories = $directories->asArray(); - } - - public function count(): int - { - return iterator_count($this); - } - - public function rewind(): void - { - $this->position = 0; - } - - public function valid(): bool - { - return $this->position < count($this->directories); - } - - public function key(): int - { - return $this->position; - } - - public function current(): TestDirectory - { - return $this->directories[$this->position]; - } - - public function next(): void - { - $this->position++; - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestFile.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestFile.php deleted file mode 100644 index 21d1cf7b1..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestFile.php +++ /dev/null @@ -1,57 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use PHPUnit\Util\VersionComparisonOperator; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @psalm-immutable - */ -final class TestFile -{ - /** - * @var string - */ - private $path; - - /** - * @var string - */ - private $phpVersion; - - /** - * @var VersionComparisonOperator - */ - private $phpVersionOperator; - - public function __construct(string $path, string $phpVersion, VersionComparisonOperator $phpVersionOperator) - { - $this->path = $path; - $this->phpVersion = $phpVersion; - $this->phpVersionOperator = $phpVersionOperator; - } - - public function path(): string - { - return $this->path; - } - - public function phpVersion(): string - { - return $this->phpVersion; - } - - public function phpVersionOperator(): VersionComparisonOperator - { - return $this->phpVersionOperator; - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestFileCollection.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestFileCollection.php deleted file mode 100644 index 27ba9bd28..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestFileCollection.php +++ /dev/null @@ -1,65 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use function count; -use Countable; -use IteratorAggregate; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @psalm-immutable - * - * @template-implements IteratorAggregate - */ -final class TestFileCollection implements Countable, IteratorAggregate -{ - /** - * @var TestFile[] - */ - private $files; - - /** - * @param TestFile[] $files - */ - public static function fromArray(array $files): self - { - return new self(...$files); - } - - private function __construct(TestFile ...$files) - { - $this->files = $files; - } - - /** - * @return TestFile[] - */ - public function asArray(): array - { - return $this->files; - } - - public function count(): int - { - return count($this->files); - } - - public function getIterator(): TestFileCollectionIterator - { - return new TestFileCollectionIterator($this); - } - - public function isEmpty(): bool - { - return $this->count() === 0; - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestFileCollectionIterator.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestFileCollectionIterator.php deleted file mode 100644 index 45a5f160c..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestFileCollectionIterator.php +++ /dev/null @@ -1,68 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use function count; -use function iterator_count; -use Countable; -use Iterator; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @template-implements Iterator - */ -final class TestFileCollectionIterator implements Countable, Iterator -{ - /** - * @var TestFile[] - */ - private $files; - - /** - * @var int - */ - private $position; - - public function __construct(TestFileCollection $files) - { - $this->files = $files->asArray(); - } - - public function count(): int - { - return iterator_count($this); - } - - public function rewind(): void - { - $this->position = 0; - } - - public function valid(): bool - { - return $this->position < count($this->files); - } - - public function key(): int - { - return $this->position; - } - - public function current(): TestFile - { - return $this->files[$this->position]; - } - - public function next(): void - { - $this->position++; - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestSuite.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestSuite.php deleted file mode 100644 index 035376cbb..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestSuite.php +++ /dev/null @@ -1,66 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @psalm-immutable - */ -final class TestSuite -{ - /** - * @var string - */ - private $name; - - /** - * @var TestDirectoryCollection - */ - private $directories; - - /** - * @var TestFileCollection - */ - private $files; - - /** - * @var FileCollection - */ - private $exclude; - - public function __construct(string $name, TestDirectoryCollection $directories, TestFileCollection $files, FileCollection $exclude) - { - $this->name = $name; - $this->directories = $directories; - $this->files = $files; - $this->exclude = $exclude; - } - - public function name(): string - { - return $this->name; - } - - public function directories(): TestDirectoryCollection - { - return $this->directories; - } - - public function files(): TestFileCollection - { - return $this->files; - } - - public function exclude(): FileCollection - { - return $this->exclude; - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestSuiteCollection.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestSuiteCollection.php deleted file mode 100644 index f632e5194..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestSuiteCollection.php +++ /dev/null @@ -1,65 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use function count; -use Countable; -use IteratorAggregate; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @psalm-immutable - * - * @template-implements IteratorAggregate - */ -final class TestSuiteCollection implements Countable, IteratorAggregate -{ - /** - * @var TestSuite[] - */ - private $testSuites; - - /** - * @param TestSuite[] $testSuites - */ - public static function fromArray(array $testSuites): self - { - return new self(...$testSuites); - } - - private function __construct(TestSuite ...$testSuites) - { - $this->testSuites = $testSuites; - } - - /** - * @return TestSuite[] - */ - public function asArray(): array - { - return $this->testSuites; - } - - public function count(): int - { - return count($this->testSuites); - } - - public function getIterator(): TestSuiteCollectionIterator - { - return new TestSuiteCollectionIterator($this); - } - - public function isEmpty(): bool - { - return $this->count() === 0; - } -} diff --git a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestSuiteCollectionIterator.php b/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestSuiteCollectionIterator.php deleted file mode 100644 index 42d03db0f..000000000 --- a/app/vendor/phpunit/phpunit/src/TextUI/XmlConfiguration/TestSuite/TestSuiteCollectionIterator.php +++ /dev/null @@ -1,68 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\TextUI\XmlConfiguration; - -use function count; -use function iterator_count; -use Countable; -use Iterator; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @template-implements Iterator - */ -final class TestSuiteCollectionIterator implements Countable, Iterator -{ - /** - * @var TestSuite[] - */ - private $testSuites; - - /** - * @var int - */ - private $position; - - public function __construct(TestSuiteCollection $testSuites) - { - $this->testSuites = $testSuites->asArray(); - } - - public function count(): int - { - return iterator_count($this); - } - - public function rewind(): void - { - $this->position = 0; - } - - public function valid(): bool - { - return $this->position < count($this->testSuites); - } - - public function key(): int - { - return $this->position; - } - - public function current(): TestSuite - { - return $this->testSuites[$this->position]; - } - - public function next(): void - { - $this->position++; - } -} diff --git a/app/vendor/phpunit/phpunit/src/Util/Annotation/DocBlock.php b/app/vendor/phpunit/phpunit/src/Util/Annotation/DocBlock.php deleted file mode 100644 index 764bbbfb4..000000000 --- a/app/vendor/phpunit/phpunit/src/Util/Annotation/DocBlock.php +++ /dev/null @@ -1,546 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util\Annotation; - -use const JSON_ERROR_NONE; -use const PREG_OFFSET_CAPTURE; -use function array_filter; -use function array_key_exists; -use function array_map; -use function array_merge; -use function array_pop; -use function array_slice; -use function array_values; -use function count; -use function explode; -use function file; -use function implode; -use function is_array; -use function is_int; -use function json_decode; -use function json_last_error; -use function json_last_error_msg; -use function preg_match; -use function preg_match_all; -use function preg_replace; -use function preg_split; -use function realpath; -use function rtrim; -use function sprintf; -use function str_replace; -use function strlen; -use function strpos; -use function strtolower; -use function substr; -use function trim; -use PharIo\Version\VersionConstraintParser; -use PHPUnit\Framework\InvalidDataProviderException; -use PHPUnit\Framework\SkippedTestError; -use PHPUnit\Framework\Warning; -use PHPUnit\Util\Exception; -use PHPUnit\Util\InvalidDataSetException; -use ReflectionClass; -use ReflectionException; -use ReflectionFunctionAbstract; -use ReflectionMethod; -use Reflector; -use Traversable; - -/** - * This is an abstraction around a PHPUnit-specific docBlock, - * allowing us to ask meaningful questions about a specific - * reflection symbol. - * - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class DocBlock -{ - /** - * @todo This constant should be private (it's public because of TestTest::testGetProvidedDataRegEx) - */ - public const REGEX_DATA_PROVIDER = '/@dataProvider\s+([a-zA-Z0-9._:-\\\\x7f-\xff]+)/'; - - private const REGEX_REQUIRES_VERSION = '/@requires\s+(?PPHP(?:Unit)?)\s+(?P[<>=!]{0,2})\s*(?P[\d\.-]+(dev|(RC|alpha|beta)[\d\.])?)[ \t]*\r?$/m'; - private const REGEX_REQUIRES_VERSION_CONSTRAINT = '/@requires\s+(?PPHP(?:Unit)?)\s+(?P[\d\t \-.|~^]+)[ \t]*\r?$/m'; - private const REGEX_REQUIRES_OS = '/@requires\s+(?POS(?:FAMILY)?)\s+(?P.+?)[ \t]*\r?$/m'; - private const REGEX_REQUIRES_SETTING = '/@requires\s+(?Psetting)\s+(?P([^ ]+?))\s*(?P[\w\.-]+[\w\.]?)?[ \t]*\r?$/m'; - private const REGEX_REQUIRES = '/@requires\s+(?Pfunction|extension)\s+(?P([^\s<>=!]+))\s*(?P[<>=!]{0,2})\s*(?P[\d\.-]+[\d\.]?)?[ \t]*\r?$/m'; - private const REGEX_TEST_WITH = '/@testWith\s+/'; - - /** @var string */ - private $docComment; - - /** @var bool */ - private $isMethod; - - /** @var array> pre-parsed annotations indexed by name and occurrence index */ - private $symbolAnnotations; - - /** - * @var null|array - * - * @psalm-var null|(array{ - * __OFFSET: array&array{__FILE: string}, - * setting?: array, - * extension_versions?: array - * }&array< - * string, - * string|array{version: string, operator: string}|array{constraint: string}|array - * >) - */ - private $parsedRequirements; - - /** @var int */ - private $startLine; - - /** @var int */ - private $endLine; - - /** @var string */ - private $fileName; - - /** @var string */ - private $name; - - /** - * @var string - * - * @psalm-var class-string - */ - private $className; - - public static function ofClass(ReflectionClass $class): self - { - $className = $class->getName(); - - return new self( - (string) $class->getDocComment(), - false, - self::extractAnnotationsFromReflector($class), - $class->getStartLine(), - $class->getEndLine(), - $class->getFileName(), - $className, - $className, - ); - } - - /** - * @psalm-param class-string $classNameInHierarchy - */ - public static function ofMethod(ReflectionMethod $method, string $classNameInHierarchy): self - { - return new self( - (string) $method->getDocComment(), - true, - self::extractAnnotationsFromReflector($method), - $method->getStartLine(), - $method->getEndLine(), - $method->getFileName(), - $method->getName(), - $classNameInHierarchy, - ); - } - - /** - * Note: we do not preserve an instance of the reflection object, since it cannot be safely (de-)serialized. - * - * @param array> $symbolAnnotations - * - * @psalm-param class-string $className - */ - private function __construct(string $docComment, bool $isMethod, array $symbolAnnotations, int $startLine, int $endLine, string $fileName, string $name, string $className) - { - $this->docComment = $docComment; - $this->isMethod = $isMethod; - $this->symbolAnnotations = $symbolAnnotations; - $this->startLine = $startLine; - $this->endLine = $endLine; - $this->fileName = $fileName; - $this->name = $name; - $this->className = $className; - } - - /** - * @psalm-return array{ - * __OFFSET: array&array{__FILE: string}, - * setting?: array, - * extension_versions?: array - * }&array< - * string, - * string|array{version: string, operator: string}|array{constraint: string}|array - * > - * - * @throws Warning if the requirements version constraint is not well-formed - */ - public function requirements(): array - { - if ($this->parsedRequirements !== null) { - return $this->parsedRequirements; - } - - $offset = $this->startLine; - $requires = []; - $recordedSettings = []; - $extensionVersions = []; - $recordedOffsets = [ - '__FILE' => realpath($this->fileName), - ]; - - // Trim docblock markers, split it into lines and rewind offset to start of docblock - $lines = preg_replace(['#^/\*{2}#', '#\*/$#'], '', preg_split('/\r\n|\r|\n/', $this->docComment)); - $offset -= count($lines); - - foreach ($lines as $line) { - if (preg_match(self::REGEX_REQUIRES_OS, $line, $matches)) { - $requires[$matches['name']] = $matches['value']; - $recordedOffsets[$matches['name']] = $offset; - } - - if (preg_match(self::REGEX_REQUIRES_VERSION, $line, $matches)) { - $requires[$matches['name']] = [ - 'version' => $matches['version'], - 'operator' => $matches['operator'], - ]; - $recordedOffsets[$matches['name']] = $offset; - } - - if (preg_match(self::REGEX_REQUIRES_VERSION_CONSTRAINT, $line, $matches)) { - if (!empty($requires[$matches['name']])) { - $offset++; - - continue; - } - - try { - $versionConstraintParser = new VersionConstraintParser; - - $requires[$matches['name'] . '_constraint'] = [ - 'constraint' => $versionConstraintParser->parse(trim($matches['constraint'])), - ]; - $recordedOffsets[$matches['name'] . '_constraint'] = $offset; - } catch (\PharIo\Version\Exception $e) { - throw new Warning($e->getMessage(), $e->getCode(), $e); - } - } - - if (preg_match(self::REGEX_REQUIRES_SETTING, $line, $matches)) { - $recordedSettings[$matches['setting']] = $matches['value']; - $recordedOffsets['__SETTING_' . $matches['setting']] = $offset; - } - - if (preg_match(self::REGEX_REQUIRES, $line, $matches)) { - $name = $matches['name'] . 's'; - - if (!isset($requires[$name])) { - $requires[$name] = []; - } - - $requires[$name][] = $matches['value']; - $recordedOffsets[$matches['name'] . '_' . $matches['value']] = $offset; - - if ($name === 'extensions' && !empty($matches['version'])) { - $extensionVersions[$matches['value']] = [ - 'version' => $matches['version'], - 'operator' => $matches['operator'], - ]; - } - } - - $offset++; - } - - return $this->parsedRequirements = array_merge( - $requires, - ['__OFFSET' => $recordedOffsets], - array_filter([ - 'setting' => $recordedSettings, - 'extension_versions' => $extensionVersions, - ]), - ); - } - - /** - * Returns the provided data for a method. - * - * @throws Exception - */ - public function getProvidedData(): ?array - { - /** @noinspection SuspiciousBinaryOperationInspection */ - $data = $this->getDataFromDataProviderAnnotation($this->docComment) ?? $this->getDataFromTestWithAnnotation($this->docComment); - - if ($data === null) { - return null; - } - - if ($data === []) { - throw new SkippedTestError; - } - - foreach ($data as $key => $value) { - if (!is_array($value)) { - throw new InvalidDataSetException( - sprintf( - 'Data set %s is invalid.', - is_int($key) ? '#' . $key : '"' . $key . '"', - ), - ); - } - } - - return $data; - } - - /** - * @psalm-return array - */ - public function getInlineAnnotations(): array - { - $code = file($this->fileName); - $lineNumber = $this->startLine; - $startLine = $this->startLine - 1; - $endLine = $this->endLine - 1; - $codeLines = array_slice($code, $startLine, $endLine - $startLine + 1); - $annotations = []; - - foreach ($codeLines as $line) { - if (preg_match('#/\*\*?\s*@(?P[A-Za-z_-]+)(?:[ \t]+(?P.*?))?[ \t]*\r?\*/$#m', $line, $matches)) { - $annotations[strtolower($matches['name'])] = [ - 'line' => $lineNumber, - 'value' => $matches['value'], - ]; - } - - $lineNumber++; - } - - return $annotations; - } - - public function symbolAnnotations(): array - { - return $this->symbolAnnotations; - } - - public function isHookToBeExecutedBeforeClass(): bool - { - return $this->isMethod && - false !== strpos($this->docComment, '@beforeClass'); - } - - public function isHookToBeExecutedAfterClass(): bool - { - return $this->isMethod && - false !== strpos($this->docComment, '@afterClass'); - } - - public function isToBeExecutedBeforeTest(): bool - { - return 1 === preg_match('/@before\b/', $this->docComment); - } - - public function isToBeExecutedAfterTest(): bool - { - return 1 === preg_match('/@after\b/', $this->docComment); - } - - public function isToBeExecutedAsPreCondition(): bool - { - return 1 === preg_match('/@preCondition\b/', $this->docComment); - } - - public function isToBeExecutedAsPostCondition(): bool - { - return 1 === preg_match('/@postCondition\b/', $this->docComment); - } - - private function getDataFromDataProviderAnnotation(string $docComment): ?array - { - $methodName = null; - $className = $this->className; - - if ($this->isMethod) { - $methodName = $this->name; - } - - if (!preg_match_all(self::REGEX_DATA_PROVIDER, $docComment, $matches)) { - return null; - } - - $result = []; - - foreach ($matches[1] as $match) { - $dataProviderMethodNameNamespace = explode('\\', $match); - $leaf = explode('::', array_pop($dataProviderMethodNameNamespace)); - $dataProviderMethodName = array_pop($leaf); - - if (empty($dataProviderMethodNameNamespace)) { - $dataProviderMethodNameNamespace = ''; - } else { - $dataProviderMethodNameNamespace = implode('\\', $dataProviderMethodNameNamespace) . '\\'; - } - - if (empty($leaf)) { - $dataProviderClassName = $className; - } else { - /** @psalm-var class-string $dataProviderClassName */ - $dataProviderClassName = $dataProviderMethodNameNamespace . array_pop($leaf); - } - - try { - $dataProviderClass = new ReflectionClass($dataProviderClassName); - - $dataProviderMethod = $dataProviderClass->getMethod( - $dataProviderMethodName, - ); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - $e->getCode(), - $e, - ); - // @codeCoverageIgnoreEnd - } - - if ($dataProviderMethod->isStatic()) { - $object = null; - } else { - $object = $dataProviderClass->newInstance(); - } - - if ($dataProviderMethod->getNumberOfParameters() === 0) { - $data = $dataProviderMethod->invoke($object); - } else { - $data = $dataProviderMethod->invoke($object, $methodName); - } - - if ($data instanceof Traversable) { - $origData = $data; - $data = []; - - foreach ($origData as $key => $value) { - if (is_int($key)) { - $data[] = $value; - } elseif (array_key_exists($key, $data)) { - throw new InvalidDataProviderException( - sprintf( - 'The key "%s" has already been defined in the data provider "%s".', - $key, - $match, - ), - ); - } else { - $data[$key] = $value; - } - } - } - - if (is_array($data)) { - $result = array_merge($result, $data); - } - } - - return $result; - } - - /** - * @throws Exception - */ - private function getDataFromTestWithAnnotation(string $docComment): ?array - { - $docComment = $this->cleanUpMultiLineAnnotation($docComment); - - if (!preg_match(self::REGEX_TEST_WITH, $docComment, $matches, PREG_OFFSET_CAPTURE)) { - return null; - } - - $offset = strlen($matches[0][0]) + $matches[0][1]; - $annotationContent = substr($docComment, $offset); - $data = []; - - foreach (explode("\n", $annotationContent) as $candidateRow) { - $candidateRow = trim($candidateRow); - - if ($candidateRow[0] !== '[') { - break; - } - - $dataSet = json_decode($candidateRow, true); - - if (json_last_error() !== JSON_ERROR_NONE) { - throw new Exception( - 'The data set for the @testWith annotation cannot be parsed: ' . json_last_error_msg(), - ); - } - - $data[] = $dataSet; - } - - if (!$data) { - throw new Exception('The data set for the @testWith annotation cannot be parsed.'); - } - - return $data; - } - - private function cleanUpMultiLineAnnotation(string $docComment): string - { - // removing initial ' * ' for docComment - $docComment = str_replace("\r\n", "\n", $docComment); - $docComment = preg_replace('/\n\s*\*\s?/', "\n", $docComment); - $docComment = (string) substr($docComment, 0, -1); - - return rtrim($docComment, "\n"); - } - - /** @return array> */ - private static function parseDocBlock(string $docBlock): array - { - // Strip away the docblock header and footer to ease parsing of one line annotations - $docBlock = (string) substr($docBlock, 3, -2); - $annotations = []; - - if (preg_match_all('/@(?P[A-Za-z_-]+)(?:[ \t]+(?P.*?))?[ \t]*\r?$/m', $docBlock, $matches)) { - $numMatches = count($matches[0]); - - for ($i = 0; $i < $numMatches; $i++) { - $annotations[$matches['name'][$i]][] = (string) $matches['value'][$i]; - } - } - - return $annotations; - } - - /** @param ReflectionClass|ReflectionFunctionAbstract $reflector */ - private static function extractAnnotationsFromReflector(Reflector $reflector): array - { - $annotations = []; - - if ($reflector instanceof ReflectionClass) { - $annotations = array_merge( - $annotations, - ...array_map( - static function (ReflectionClass $trait): array - { - return self::parseDocBlock((string) $trait->getDocComment()); - }, - array_values($reflector->getTraits()), - ), - ); - } - - return array_merge( - $annotations, - self::parseDocBlock((string) $reflector->getDocComment()), - ); - } -} diff --git a/app/vendor/phpunit/phpunit/src/Util/Annotation/Registry.php b/app/vendor/phpunit/phpunit/src/Util/Annotation/Registry.php deleted file mode 100644 index a34cb9ade..000000000 --- a/app/vendor/phpunit/phpunit/src/Util/Annotation/Registry.php +++ /dev/null @@ -1,95 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util\Annotation; - -use function array_key_exists; -use PHPUnit\Util\Exception; -use ReflectionClass; -use ReflectionException; -use ReflectionMethod; - -/** - * Reflection information, and therefore DocBlock information, is static within - * a single PHP process. It is therefore okay to use a Singleton registry here. - * - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class Registry -{ - /** @var null|self */ - private static $instance; - - /** @var array indexed by class name */ - private $classDocBlocks = []; - - /** @var array> indexed by class name and method name */ - private $methodDocBlocks = []; - - public static function getInstance(): self - { - return self::$instance ?? self::$instance = new self; - } - - private function __construct() - { - } - - /** - * @throws Exception - * - * @psalm-param class-string $class - */ - public function forClassName(string $class): DocBlock - { - if (array_key_exists($class, $this->classDocBlocks)) { - return $this->classDocBlocks[$class]; - } - - try { - $reflection = new ReflectionClass($class); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - $e->getCode(), - $e, - ); - } - // @codeCoverageIgnoreEnd - - return $this->classDocBlocks[$class] = DocBlock::ofClass($reflection); - } - - /** - * @throws Exception - * - * @psalm-param class-string $classInHierarchy - */ - public function forMethod(string $classInHierarchy, string $method): DocBlock - { - if (isset($this->methodDocBlocks[$classInHierarchy][$method])) { - return $this->methodDocBlocks[$classInHierarchy][$method]; - } - - try { - $reflection = new ReflectionMethod($classInHierarchy, $method); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - $e->getCode(), - $e, - ); - } - // @codeCoverageIgnoreEnd - - return $this->methodDocBlocks[$classInHierarchy][$method] = DocBlock::ofMethod($reflection, $classInHierarchy); - } -} diff --git a/app/vendor/phpunit/phpunit/src/Util/Blacklist.php b/app/vendor/phpunit/phpunit/src/Util/Blacklist.php deleted file mode 100644 index 3b416e142..000000000 --- a/app/vendor/phpunit/phpunit/src/Util/Blacklist.php +++ /dev/null @@ -1,41 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util; - -/** - * @deprecated Use ExcludeList instead - * - * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit - */ -final class Blacklist -{ - public static function addDirectory(string $directory): void - { - ExcludeList::addDirectory($directory); - } - - /** - * @throws Exception - * - * @return string[] - */ - public function getBlacklistedDirectories(): array - { - return (new ExcludeList)->getExcludedDirectories(); - } - - /** - * @throws Exception - */ - public function isBlacklisted(string $file): bool - { - return (new ExcludeList)->isExcluded($file); - } -} diff --git a/app/vendor/phpunit/phpunit/src/Util/Cloner.php b/app/vendor/phpunit/phpunit/src/Util/Cloner.php deleted file mode 100644 index 38bd59ff9..000000000 --- a/app/vendor/phpunit/phpunit/src/Util/Cloner.php +++ /dev/null @@ -1,34 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util; - -use Throwable; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class Cloner -{ - /** - * @psalm-template OriginalType - * - * @psalm-param OriginalType $original - * - * @psalm-return OriginalType - */ - public static function clone(object $original): object - { - try { - return clone $original; - } catch (Throwable $t) { - return $original; - } - } -} diff --git a/app/vendor/phpunit/phpunit/src/Util/Color.php b/app/vendor/phpunit/phpunit/src/Util/Color.php index ee0f412df..c225e37b7 100644 --- a/app/vendor/phpunit/phpunit/src/Util/Color.php +++ b/app/vendor/phpunit/phpunit/src/Util/Color.php @@ -10,36 +10,41 @@ namespace PHPUnit\Util; use const DIRECTORY_SEPARATOR; -use function array_keys; +use const PHP_EOL; use function array_map; -use function array_values; +use function array_walk; use function count; use function explode; use function implode; +use function max; use function min; use function preg_replace; use function preg_replace_callback; +use function preg_split; use function sprintf; +use function str_pad; use function strtr; use function trim; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final class Color { /** - * @var array + * @var non-empty-array */ - private const WHITESPACE_MAP = [ + private const array WHITESPACE_MAP = [ ' ' => '·', "\t" => '⇥', ]; /** - * @var array + * @var non-empty-array */ - private const WHITESPACE_EOL_MAP = [ + private const array WHITESPACE_EOL_MAP = [ ' ' => '·', "\t" => '⇥', "\n" => '↵', @@ -47,9 +52,9 @@ final class Color ]; /** - * @var array + * @var non-empty-array */ - private static $ansiCodes = [ + private const array ANSI_CODES = [ 'reset' => '0', 'bold' => '1', 'dim' => '2', @@ -85,29 +90,46 @@ public static function colorize(string $color, string $buffer): string $styles = []; foreach ($codes as $code) { - if (isset(self::$ansiCodes[$code])) { - $styles[] = self::$ansiCodes[$code] ?? ''; + if (isset(self::ANSI_CODES[$code])) { + $styles[] = self::ANSI_CODES[$code]; } } - if (empty($styles)) { + if ($styles === []) { return $buffer; } return self::optimizeColor(sprintf("\x1b[%sm", implode(';', $styles)) . $buffer . "\x1b[0m"); } - public static function colorizePath(string $path, ?string $prevPath = null, bool $colorizeFilename = false): string + public static function colorizeTextBox(string $color, string $buffer, ?int $columns = null): string { - if ($prevPath === null) { - $prevPath = ''; + $lines = preg_split('/\r\n|\r|\n/', $buffer); + $maxBoxWidth = max(array_map('\strlen', $lines)); + + if ($columns !== null) { + $maxBoxWidth = min($maxBoxWidth, $columns); } - $path = explode(DIRECTORY_SEPARATOR, $path); - $prevPath = explode(DIRECTORY_SEPARATOR, $prevPath); + array_walk($lines, static function (string &$line) use ($color, $maxBoxWidth): void + { + $line = self::colorize($color, str_pad($line, $maxBoxWidth)); + }); + + return implode(PHP_EOL, $lines); + } - for ($i = 0; $i < min(count($path), count($prevPath)); $i++) { - if ($path[$i] == $prevPath[$i]) { + public static function colorizePath(string $path, ?string $previousPath = null, bool $colorizeFilename = false): string + { + if ($previousPath === null) { + $previousPath = ''; + } + + $path = explode(DIRECTORY_SEPARATOR, $path); + $previousPath = explode(DIRECTORY_SEPARATOR, $previousPath); + + for ($i = 0; $i < min(count($path), count($previousPath)); $i++) { + if ($path[$i] === $previousPath[$i]) { $path[$i] = self::dim($path[$i]); } } @@ -115,11 +137,8 @@ public static function colorizePath(string $path, ?string $prevPath = null, bool if ($colorizeFilename) { $last = count($path) - 1; $path[$last] = preg_replace_callback( - '/([\-_\.]+|phpt$)/', - static function ($matches) - { - return self::dim($matches[0]); - }, + '/([\-_.]+|phpt$)/', + static fn (array $matches) => self::dim($matches[0]), $path[$last], ); } @@ -140,20 +159,27 @@ public static function visualizeWhitespace(string $buffer, bool $visualizeEOL = { $replaceMap = $visualizeEOL ? self::WHITESPACE_EOL_MAP : self::WHITESPACE_MAP; - return preg_replace_callback('/\s+/', static function ($matches) use ($replaceMap) - { - return self::dim(strtr($matches[0], $replaceMap)); - }, $buffer); + return preg_replace_callback( + '/\s+/', + static fn (array $matches) => self::dim(strtr($matches[0], $replaceMap)), + $buffer, + ); } private static function optimizeColor(string $buffer): string { - $patterns = [ - "/\e\\[22m\e\\[2m/" => '', - "/\e\\[([^m]*)m\e\\[([1-9][0-9;]*)m/" => "\e[$1;$2m", - "/(\e\\[[^m]*m)+(\e\\[0m)/" => '$2', - ]; - - return preg_replace(array_keys($patterns), array_values($patterns), $buffer); + return preg_replace( + [ + "/\e\\[22m\e\\[2m/", + "/\e\\[([^m]*)m\e\\[([1-9][0-9;]*)m/", + "/(\e\\[[^m]*m)+(\e\\[0m)/", + ], + [ + '', + "\e[$1;$2m", + '$2', + ], + $buffer, + ); } } diff --git a/app/vendor/phpunit/phpunit/src/Util/ErrorHandler.php b/app/vendor/phpunit/phpunit/src/Util/ErrorHandler.php deleted file mode 100644 index f28c89646..000000000 --- a/app/vendor/phpunit/phpunit/src/Util/ErrorHandler.php +++ /dev/null @@ -1,164 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util; - -use const E_DEPRECATED; -use const E_NOTICE; -use const E_USER_DEPRECATED; -use const E_USER_NOTICE; -use const E_USER_WARNING; -use const E_WARNING; -use function defined; -use function error_reporting; -use function restore_error_handler; -use function set_error_handler; -use PHPUnit\Framework\Error\Deprecated; -use PHPUnit\Framework\Error\Error; -use PHPUnit\Framework\Error\Notice; -use PHPUnit\Framework\Error\Warning; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class ErrorHandler -{ - /** - * @var bool - */ - private $convertDeprecationsToExceptions; - - /** - * @var bool - */ - private $convertErrorsToExceptions; - - /** - * @var bool - */ - private $convertNoticesToExceptions; - - /** - * @var bool - */ - private $convertWarningsToExceptions; - - /** - * @var bool - */ - private $registered = false; - - public static function invokeIgnoringWarnings(callable $callable) - { - set_error_handler( - static function ($errorNumber, $errorString) - { - if ($errorNumber === E_WARNING) { - return; - } - - return false; - }, - ); - - $result = $callable(); - - restore_error_handler(); - - return $result; - } - - public function __construct(bool $convertDeprecationsToExceptions, bool $convertErrorsToExceptions, bool $convertNoticesToExceptions, bool $convertWarningsToExceptions) - { - $this->convertDeprecationsToExceptions = $convertDeprecationsToExceptions; - $this->convertErrorsToExceptions = $convertErrorsToExceptions; - $this->convertNoticesToExceptions = $convertNoticesToExceptions; - $this->convertWarningsToExceptions = $convertWarningsToExceptions; - } - - public function __invoke(int $errorNumber, string $errorString, string $errorFile, int $errorLine): bool - { - /* - * Do not raise an exception when the error suppression operator (@) was used. - * - * @see https://github.com/sebastianbergmann/phpunit/issues/3739 - */ - if (!($errorNumber & error_reporting())) { - return false; - } - - /** - * E_STRICT is deprecated since PHP 8.4. - * - * @see https://github.com/sebastianbergmann/phpunit/issues/5956 - */ - if (defined('E_STRICT') && $errorNumber === 2048) { - $errorNumber = E_NOTICE; - } - - switch ($errorNumber) { - case E_NOTICE: - case E_USER_NOTICE: - if (!$this->convertNoticesToExceptions) { - return false; - } - - throw new Notice($errorString, $errorNumber, $errorFile, $errorLine); - - case E_WARNING: - case E_USER_WARNING: - if (!$this->convertWarningsToExceptions) { - return false; - } - - throw new Warning($errorString, $errorNumber, $errorFile, $errorLine); - - case E_DEPRECATED: - case E_USER_DEPRECATED: - if (!$this->convertDeprecationsToExceptions) { - return false; - } - - throw new Deprecated($errorString, $errorNumber, $errorFile, $errorLine); - - default: - if (!$this->convertErrorsToExceptions) { - return false; - } - - throw new Error($errorString, $errorNumber, $errorFile, $errorLine); - } - } - - public function register(): void - { - if ($this->registered) { - return; - } - - $oldErrorHandler = set_error_handler($this); - - if ($oldErrorHandler !== null) { - restore_error_handler(); - - return; - } - - $this->registered = true; - } - - public function unregister(): void - { - if (!$this->registered) { - return; - } - - restore_error_handler(); - } -} diff --git a/app/vendor/phpunit/phpunit/src/Util/Exception.php b/app/vendor/phpunit/phpunit/src/Util/Exception.php deleted file mode 100644 index 6bcb3d140..000000000 --- a/app/vendor/phpunit/phpunit/src/Util/Exception.php +++ /dev/null @@ -1,19 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util; - -use RuntimeException; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class Exception extends RuntimeException implements \PHPUnit\Exception -{ -} diff --git a/app/vendor/phpunit/phpunit/src/Util/Exception/Exception.php b/app/vendor/phpunit/phpunit/src/Util/Exception/Exception.php new file mode 100644 index 000000000..58f42db77 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Util/Exception/Exception.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 PHPUnit\Util; + +use Throwable; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This interface is not covered by the backward compatibility promise for PHPUnit + */ +interface Exception extends Throwable +{ +} diff --git a/app/vendor/phpunit/phpunit/src/Util/Exception/InvalidDirectoryException.php b/app/vendor/phpunit/phpunit/src/Util/Exception/InvalidDirectoryException.php new file mode 100644 index 000000000..623af2ded --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Util/Exception/InvalidDirectoryException.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 PHPUnit\Util; + +use function sprintf; +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class InvalidDirectoryException extends RuntimeException implements Exception +{ + public function __construct(string $directory) + { + parent::__construct( + sprintf( + '"%s" is not a directory', + $directory, + ), + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Util/Exception/InvalidJsonException.php b/app/vendor/phpunit/phpunit/src/Util/Exception/InvalidJsonException.php new file mode 100644 index 000000000..224f7115c --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Util/Exception/InvalidJsonException.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 PHPUnit\Util; + +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class InvalidJsonException extends RuntimeException implements Exception +{ +} diff --git a/app/vendor/phpunit/phpunit/src/Util/Exception/InvalidVersionOperatorException.php b/app/vendor/phpunit/phpunit/src/Util/Exception/InvalidVersionOperatorException.php new file mode 100644 index 000000000..bc2fe9a0e --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Util/Exception/InvalidVersionOperatorException.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 PHPUnit\Util; + +use function sprintf; +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class InvalidVersionOperatorException extends RuntimeException implements Exception +{ + public function __construct(string $operator) + { + parent::__construct( + sprintf( + '"%s" is not a valid version_compare() operator', + $operator, + ), + ); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Util/Exception/PhpProcessException.php b/app/vendor/phpunit/phpunit/src/Util/Exception/PhpProcessException.php new file mode 100644 index 000000000..05069ef05 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Util/Exception/PhpProcessException.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util\PHP; + +use PHPUnit\Util\Exception; +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class PhpProcessException extends RuntimeException implements Exception +{ +} diff --git a/app/vendor/phpunit/phpunit/src/Util/Exception/XmlException.php b/app/vendor/phpunit/phpunit/src/Util/Exception/XmlException.php new file mode 100644 index 000000000..127e1ecaf --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Util/Exception/XmlException.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util\Xml; + +use PHPUnit\Util\Exception; +use RuntimeException; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class XmlException extends RuntimeException implements Exception +{ +} diff --git a/app/vendor/phpunit/phpunit/src/Util/ExcludeList.php b/app/vendor/phpunit/phpunit/src/Util/ExcludeList.php index d539ec571..7ec8d4273 100644 --- a/app/vendor/phpunit/phpunit/src/Util/ExcludeList.php +++ b/app/vendor/phpunit/phpunit/src/Util/ExcludeList.php @@ -9,18 +9,17 @@ */ namespace PHPUnit\Util; -use const DIRECTORY_SEPARATOR; +use const PHP_OS_FAMILY; +use function assert; use function class_exists; use function defined; use function dirname; use function is_dir; use function realpath; -use function sprintf; -use function strpos; +use function str_starts_with; use function sys_get_temp_dir; use Composer\Autoload\ClassLoader; use DeepCopy\DeepCopy; -use Doctrine\Instantiator\Instantiator; use PharIo\Manifest\Manifest; use PharIo\Version\Version as PharIoVersion; use PhpParser\Parser; @@ -28,8 +27,6 @@ use ReflectionClass; use SebastianBergmann\CliParser\Parser as CliParser; use SebastianBergmann\CodeCoverage\CodeCoverage; -use SebastianBergmann\CodeUnit\CodeUnit; -use SebastianBergmann\CodeUnitReverseLookup\Wizard; use SebastianBergmann\Comparator\Comparator; use SebastianBergmann\Complexity\Calculator; use SebastianBergmann\Diff\Diff; @@ -42,11 +39,11 @@ use SebastianBergmann\ObjectEnumerator\Enumerator; use SebastianBergmann\ObjectReflector\ObjectReflector; use SebastianBergmann\RecursionContext\Context; -use SebastianBergmann\ResourceOperations\ResourceOperations; use SebastianBergmann\Template\Template; use SebastianBergmann\Timer\Timer; use SebastianBergmann\Type\TypeName; use SebastianBergmann\Version; +use staabm\SideEffectsDetector\SideEffectsDetector; use TheSeer\Tokenizer\Tokenizer; /** @@ -55,15 +52,12 @@ final class ExcludeList { /** - * @var array + * @var non-empty-array */ - private const EXCLUDED_CLASS_NAMES = [ + private const array EXCLUDED_CLASS_NAMES = [ // composer ClassLoader::class => 1, - // doctrine/instantiator - Instantiator::class => 1, - // myclabs/deepcopy DeepCopy::class => 1, @@ -76,9 +70,6 @@ final class ExcludeList // phar-io/version PharIoVersion::class => 1, - // phpdocumentor/type-resolver - Type::class => 1, - // phpunit/phpunit TestCase::class => 2, @@ -100,12 +91,6 @@ final class ExcludeList // sebastian/cli-parser CliParser::class => 1, - // sebastian/code-unit - CodeUnit::class => 1, - - // sebastian/code-unit-reverse-lookup - Wizard::class => 1, - // sebastian/comparator Comparator::class => 1, @@ -136,68 +121,73 @@ final class ExcludeList // sebastian/recursion-context Context::class => 1, - // sebastian/resource-operations - ResourceOperations::class => 1, - // sebastian/type TypeName::class => 1, // sebastian/version Version::class => 1, + // staabm/side-effects-detector + SideEffectsDetector::class => 1, + // theseer/tokenizer Tokenizer::class => 1, ]; /** - * @var string[] + * @var list */ - private static $directories = []; + private static array $directories = []; + private static bool $initialized = false; + private readonly bool $enabled; /** - * @var bool + * @param non-empty-string $directory + * + * @throws InvalidDirectoryException */ - private static $initialized = false; - public static function addDirectory(string $directory): void { if (!is_dir($directory)) { - throw new Exception( - sprintf( - '"%s" is not a directory', - $directory, - ), - ); + throw new InvalidDirectoryException($directory); + } + + $directory = realpath($directory); + + assert($directory !== false); + + self::$directories[] = $directory; + } + + public function __construct(?bool $enabled = null) + { + if ($enabled === null) { + $enabled = !defined('PHPUNIT_TESTSUITE'); } - self::$directories[] = realpath($directory); + $this->enabled = $enabled; } /** - * @throws Exception - * - * @return string[] + * @return list */ public function getExcludedDirectories(): array { - $this->initialize(); + self::initialize(); return self::$directories; } - /** - * @throws Exception - */ public function isExcluded(string $file): bool { - if (defined('PHPUNIT_TESTSUITE')) { + if (!$this->enabled) { return false; } - $this->initialize(); + self::initialize(); foreach (self::$directories as $directory) { - if (strpos($file, $directory) === 0) { + if (str_starts_with($file, $directory)) { return true; } } @@ -205,10 +195,7 @@ public function isExcluded(string $file): bool return false; } - /** - * @throws Exception - */ - private function initialize(): void + private static function initialize(): void { if (self::$initialized) { return; @@ -228,11 +215,16 @@ private function initialize(): void self::$directories[] = $directory; } - // Hide process isolation workaround on Windows. - if (DIRECTORY_SEPARATOR === '\\') { - // tempnam() prefix is limited to first 3 chars. - // @see https://php.net/manual/en/function.tempnam.php + /** + * Hide process isolation workaround on Windows: + * tempnam() prefix is limited to first 3 characters. + * + * @see https://php.net/manual/en/function.tempnam.php + */ + if (PHP_OS_FAMILY === 'Windows') { + // @codeCoverageIgnoreStart self::$directories[] = sys_get_temp_dir() . '\\PHP'; + // @codeCoverageIgnoreEnd } self::$initialized = true; diff --git a/app/vendor/phpunit/phpunit/src/Util/Exporter.php b/app/vendor/phpunit/phpunit/src/Util/Exporter.php new file mode 100644 index 000000000..182499e9d --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Util/Exporter.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util; + +use PHPUnit\TextUI\Configuration\Registry as ConfigurationRegistry; +use SebastianBergmann\Exporter\Exporter as OriginalExporter; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + */ +final class Exporter +{ + private static ?OriginalExporter $exporter = null; + + public static function export(mixed $value): string + { + return self::exporter()->export($value); + } + + /** + * @param array $data + */ + public static function shortenedRecursiveExport(array $data): string + { + return self::exporter()->shortenedRecursiveExport($data); + } + + public static function shortenedExport(mixed $value): string + { + return self::exporter()->shortenedExport($value); + } + + private static function exporter(): OriginalExporter + { + if (self::$exporter !== null) { + return self::$exporter; + } + + self::$exporter = new OriginalExporter( + ConfigurationRegistry::get()->shortenArraysForExportThreshold(), + ); + + return self::$exporter; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Util/FileLoader.php b/app/vendor/phpunit/phpunit/src/Util/FileLoader.php deleted file mode 100644 index e0a665061..000000000 --- a/app/vendor/phpunit/phpunit/src/Util/FileLoader.php +++ /dev/null @@ -1,84 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util; - -use const DIRECTORY_SEPARATOR; -use function array_diff; -use function array_keys; -use function fopen; -use function get_defined_vars; -use function sprintf; -use function stream_resolve_include_path; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class FileLoader -{ - /** - * Checks if a PHP sourcecode file is readable. The sourcecode file is loaded through the load() method. - * - * As a fallback, PHP looks in the directory of the file executing the stream_resolve_include_path function. - * We do not want to load the Test.php file here, so skip it if it found that. - * PHP prioritizes the include_path setting, so if the current directory is in there, it will first look in the - * current working directory. - * - * @throws Exception - */ - public static function checkAndLoad(string $filename): string - { - $includePathFilename = stream_resolve_include_path($filename); - - $localFile = __DIR__ . DIRECTORY_SEPARATOR . $filename; - - if (!$includePathFilename || - $includePathFilename === $localFile || - !self::isReadable($includePathFilename)) { - throw new Exception( - sprintf('Cannot open file "%s".' . "\n", $filename), - ); - } - - self::load($includePathFilename); - - return $includePathFilename; - } - - /** - * Loads a PHP sourcefile. - */ - public static function load(string $filename): void - { - $oldVariableNames = array_keys(get_defined_vars()); - - /** - * @noinspection PhpIncludeInspection - * - * @psalm-suppress UnresolvableInclude - */ - include_once $filename; - - $newVariables = get_defined_vars(); - - foreach (array_diff(array_keys($newVariables), $oldVariableNames) as $variableName) { - if ($variableName !== 'oldVariableNames') { - $GLOBALS[$variableName] = $newVariables[$variableName]; - } - } - } - - /** - * @see https://github.com/sebastianbergmann/phpunit/pull/2751 - */ - private static function isReadable(string $filename): bool - { - return @fopen($filename, 'r') !== false; - } -} diff --git a/app/vendor/phpunit/phpunit/src/Util/Filesystem.php b/app/vendor/phpunit/phpunit/src/Util/Filesystem.php index 886829d2d..3da540423 100644 --- a/app/vendor/phpunit/phpunit/src/Util/Filesystem.php +++ b/app/vendor/phpunit/phpunit/src/Util/Filesystem.php @@ -10,32 +10,42 @@ namespace PHPUnit\Util; use const DIRECTORY_SEPARATOR; +use function basename; +use function dirname; use function is_dir; use function mkdir; -use function str_replace; +use function realpath; +use function str_starts_with; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ -final class Filesystem +final readonly class Filesystem { + public static function createDirectory(string $directory): bool + { + return !(!is_dir($directory) && !@mkdir($directory, 0o777, true) && !is_dir($directory)); + } + /** - * Maps class names to source file names. + * @param non-empty-string $path * - * - PEAR CS: Foo_Bar_Baz -> Foo/Bar/Baz.php - * - Namespace: Foo\Bar\Baz -> Foo/Bar/Baz.php + * @return false|non-empty-string */ - public static function classNameToFilename(string $className): string + public static function resolveStreamOrFile(string $path): false|string { - return str_replace( - ['_', '\\'], - DIRECTORY_SEPARATOR, - $className, - ) . '.php'; - } + if (str_starts_with($path, 'php://') || str_starts_with($path, 'socket://')) { + return $path; + } - public static function createDirectory(string $directory): bool - { - return !(!is_dir($directory) && !@mkdir($directory, 0777, true) && !is_dir($directory)); + $directory = dirname($path); + + if (is_dir($directory)) { + return realpath($directory) . DIRECTORY_SEPARATOR . basename($path); + } + + return false; } } diff --git a/app/vendor/phpunit/phpunit/src/Util/Filter.php b/app/vendor/phpunit/phpunit/src/Util/Filter.php index 94b7e77d9..122e5c065 100644 --- a/app/vendor/phpunit/phpunit/src/Util/Filter.php +++ b/app/vendor/phpunit/phpunit/src/Util/Filter.php @@ -12,57 +12,67 @@ use function array_unshift; use function defined; use function in_array; +use function is_array; use function is_file; use function realpath; use function sprintf; -use function strpos; +use function str_starts_with; use PHPUnit\Framework\Exception; -use PHPUnit\Framework\SyntheticError; +use PHPUnit\Framework\PhptAssertionFailedError; use Throwable; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ -final class Filter +final readonly class Filter { /** * @throws Exception */ - public static function getFilteredStacktrace(Throwable $t): string + public static function stackTraceFromThrowableAsString(Throwable $t, bool $unwrap = true): string { - $filteredStacktrace = ''; - - if ($t instanceof SyntheticError) { - $eTrace = $t->getSyntheticTrace(); - $eFile = $t->getSyntheticFile(); - $eLine = $t->getSyntheticLine(); + if ($t instanceof PhptAssertionFailedError) { + $stackTrace = $t->syntheticTrace(); + $file = $t->syntheticFile(); + $line = $t->syntheticLine(); } elseif ($t instanceof Exception) { - $eTrace = $t->getSerializableTrace(); - $eFile = $t->getFile(); - $eLine = $t->getLine(); + $stackTrace = $t->getSerializableTrace(); + $file = $t->getFile(); + $line = $t->getLine(); } else { - if ($t->getPrevious()) { + if ($unwrap && $t->getPrevious() !== null) { $t = $t->getPrevious(); } - $eTrace = $t->getTrace(); - $eFile = $t->getFile(); - $eLine = $t->getLine(); + $stackTrace = $t->getTrace(); + $file = $t->getFile(); + $line = $t->getLine(); } - if (!self::frameExists($eTrace, $eFile, $eLine)) { + if (!self::frameExists($stackTrace, $file, $line)) { array_unshift( - $eTrace, - ['file' => $eFile, 'line' => $eLine], + $stackTrace, + ['file' => $file, 'line' => $line], ); } + return self::stackTraceAsString($stackTrace); + } + + /** + * @param list $frames + */ + private static function stackTraceAsString(array $frames): string + { + $buffer = ''; $prefix = defined('__PHPUNIT_PHAR_ROOT__') ? __PHPUNIT_PHAR_ROOT__ : false; $excludeList = new ExcludeList; - foreach ($eTrace as $frame) { + foreach ($frames as $frame) { if (self::shouldPrintFrame($frame, $prefix, $excludeList)) { - $filteredStacktrace .= sprintf( + $buffer .= sprintf( "%s:%s\n", $frame['file'], $frame['line'] ?? '?', @@ -70,38 +80,48 @@ public static function getFilteredStacktrace(Throwable $t): string } } - return $filteredStacktrace; + return $buffer; } - private static function shouldPrintFrame(array $frame, $prefix, ExcludeList $excludeList): bool + /** + * @param array{file?: non-empty-string} $frame + */ + private static function shouldPrintFrame(array $frame, false|string $prefix, ExcludeList $excludeList): bool { if (!isset($frame['file'])) { return false; } $file = $frame['file']; - $fileIsNotPrefixed = $prefix === false || strpos($file, $prefix) !== 0; + $fileIsNotPrefixed = $prefix === false || !str_starts_with($file, $prefix); // @see https://github.com/sebastianbergmann/phpunit/issues/4033 if (isset($GLOBALS['_SERVER']['SCRIPT_NAME'])) { $script = realpath($GLOBALS['_SERVER']['SCRIPT_NAME']); } else { + // @codeCoverageIgnoreStart $script = ''; + // @codeCoverageIgnoreEnd } - return is_file($file) && + return $fileIsNotPrefixed && + $file !== $script && self::fileIsExcluded($file, $excludeList) && - $fileIsNotPrefixed && - $file !== $script; + is_file($file); } private static function fileIsExcluded(string $file, ExcludeList $excludeList): bool { - return (empty($GLOBALS['__PHPUNIT_ISOLATION_EXCLUDE_LIST']) || + return (!isset($GLOBALS['__PHPUNIT_ISOLATION_EXCLUDE_LIST']) || + !is_array($GLOBALS['__PHPUNIT_ISOLATION_EXCLUDE_LIST']) || + $GLOBALS['__PHPUNIT_ISOLATION_EXCLUDE_LIST'] === [] || !in_array($file, $GLOBALS['__PHPUNIT_ISOLATION_EXCLUDE_LIST'], true)) && !$excludeList->isExcluded($file); } + /** + * @param list $trace + */ private static function frameExists(array $trace, string $file, int $line): bool { foreach ($trace as $frame) { diff --git a/app/vendor/phpunit/phpunit/src/Util/GlobalState.php b/app/vendor/phpunit/phpunit/src/Util/GlobalState.php index 18b5a6466..717ad4dc4 100644 --- a/app/vendor/phpunit/phpunit/src/Util/GlobalState.php +++ b/app/vendor/phpunit/phpunit/src/Util/GlobalState.php @@ -11,9 +11,9 @@ use const PHP_MAJOR_VERSION; use const PHP_MINOR_VERSION; -use function array_keys; use function array_reverse; use function array_shift; +use function assert; use function defined; use function get_defined_constants; use function get_included_files; @@ -25,21 +25,23 @@ use function preg_match; use function serialize; use function sprintf; -use function strpos; +use function str_ends_with; +use function str_starts_with; use function strtr; -use function substr; use function var_export; use Closure; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ -final class GlobalState +final readonly class GlobalState { /** - * @var string[] + * @var non-empty-list */ - private const SUPER_GLOBAL_ARRAYS = [ + private const array SUPER_GLOBAL_ARRAYS = [ '_ENV', '_POST', '_GET', @@ -50,9 +52,9 @@ final class GlobalState ]; /** - * @psalm-var array> + * @var non-empty-array> */ - private const DEPRECATED_INI_SETTINGS = [ + private const array DEPRECATED_INI_SETTINGS = [ '7.3' => [ 'iconv.input_encoding' => true, 'iconv.output_encoding' => true, @@ -131,7 +133,7 @@ public static function getIncludedFilesAsString(): string } /** - * @param string[] $files + * @param list $files * * @throws Exception */ @@ -142,29 +144,35 @@ public static function processIncludedFilesAsString(array $files): string $result = ''; if (defined('__PHPUNIT_PHAR__')) { + // @codeCoverageIgnoreStart $prefix = 'phar://' . __PHPUNIT_PHAR__ . '/'; + // @codeCoverageIgnoreEnd } // Do not process bootstrap script array_shift($files); // If bootstrap script was a Composer bin proxy, skip the second entry as well - if (substr(strtr($files[0], '\\', '/'), -24) === '/phpunit/phpunit/phpunit') { + if (str_ends_with(strtr($files[0], '\\', '/'), '/phpunit/phpunit/phpunit')) { + // @codeCoverageIgnoreStart array_shift($files); + // @codeCoverageIgnoreEnd } foreach (array_reverse($files) as $file) { - if (!empty($GLOBALS['__PHPUNIT_ISOLATION_EXCLUDE_LIST']) && + if (isset($GLOBALS['__PHPUNIT_ISOLATION_EXCLUDE_LIST']) && + is_array($GLOBALS['__PHPUNIT_ISOLATION_EXCLUDE_LIST']) && + $GLOBALS['__PHPUNIT_ISOLATION_EXCLUDE_LIST'] !== [] && in_array($file, $GLOBALS['__PHPUNIT_ISOLATION_EXCLUDE_LIST'], true)) { continue; } - if ($prefix !== false && strpos($file, $prefix) === 0) { + if ($prefix !== false && str_starts_with($file, $prefix)) { continue; } // Skip virtual file system protocols - if (preg_match('/^(vfs|phpvfs[a-z0-9]+):/', $file)) { + if (preg_match('/^(vfs|phpvfs[a-z0-9]+):/', $file) > 0) { continue; } @@ -180,7 +188,11 @@ public static function getIniSettingsAsString(): string { $result = ''; - foreach (ini_get_all(null, false) as $key => $value) { + $iniSettings = ini_get_all(null, false); + + assert($iniSettings !== false); + + foreach ($iniSettings as $key => $value) { if (self::isIniSettingDeprecated($key)) { continue; } @@ -220,8 +232,8 @@ public static function getGlobalsAsString(): string foreach (self::SUPER_GLOBAL_ARRAYS as $superGlobalArray) { if (isset($GLOBALS[$superGlobalArray]) && is_array($GLOBALS[$superGlobalArray])) { - foreach (array_keys($GLOBALS[$superGlobalArray]) as $key) { - if ($GLOBALS[$superGlobalArray][$key] instanceof Closure) { + foreach ($GLOBALS[$superGlobalArray] as $key => $value) { + if ($value instanceof Closure) { continue; } @@ -238,12 +250,12 @@ public static function getGlobalsAsString(): string $excludeList = self::SUPER_GLOBAL_ARRAYS; $excludeList[] = 'GLOBALS'; - foreach (array_keys($GLOBALS) as $key) { - if (!$GLOBALS[$key] instanceof Closure && !in_array($key, $excludeList, true)) { + foreach ($GLOBALS as $key => $value) { + if (!$value instanceof Closure && !in_array($key, $excludeList, true)) { $result .= sprintf( '$GLOBALS[\'%s\'] = %s;' . "\n", $key, - self::exportVariable($GLOBALS[$key]), + self::exportVariable($value), ); } } @@ -251,7 +263,7 @@ public static function getGlobalsAsString(): string return $result; } - private static function exportVariable($variable): string + private static function exportVariable(mixed $variable): string { if (is_scalar($variable) || $variable === null || (is_array($variable) && self::arrayOnlyContainsScalars($variable))) { @@ -261,6 +273,9 @@ private static function exportVariable($variable): string return 'unserialize(' . var_export(serialize($variable), true) . ')'; } + /** + * @param array $array + */ private static function arrayOnlyContainsScalars(array $array): bool { $result = true; diff --git a/app/vendor/phpunit/phpunit/src/Util/Http/Downloader.php b/app/vendor/phpunit/phpunit/src/Util/Http/Downloader.php new file mode 100644 index 000000000..a9af2e38f --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Util/Http/Downloader.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 PHPUnit\Util\Http; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +interface Downloader +{ + /** + * @param non-empty-string $url + */ + public function download(string $url): false|string; +} diff --git a/app/vendor/phpunit/phpunit/src/Util/Http/PhpDownloader.php b/app/vendor/phpunit/phpunit/src/Util/Http/PhpDownloader.php new file mode 100644 index 000000000..5969c0426 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Util/Http/PhpDownloader.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util\Http; + +use function file_get_contents; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * @codeCoverageIgnore + */ +final class PhpDownloader implements Downloader +{ + /** + * @param non-empty-string $url + */ + public function download(string $url): false|string + { + return file_get_contents($url); + } +} diff --git a/app/vendor/phpunit/phpunit/src/Util/InvalidDataSetException.php b/app/vendor/phpunit/phpunit/src/Util/InvalidDataSetException.php deleted file mode 100644 index 13dcc0e09..000000000 --- a/app/vendor/phpunit/phpunit/src/Util/InvalidDataSetException.php +++ /dev/null @@ -1,20 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util; - -use PHPUnit\Exception; -use RuntimeException; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class InvalidDataSetException extends RuntimeException implements Exception -{ -} diff --git a/app/vendor/phpunit/phpunit/src/Util/Json.php b/app/vendor/phpunit/phpunit/src/Util/Json.php index 0428bc037..cbe959acc 100644 --- a/app/vendor/phpunit/phpunit/src/Util/Json.php +++ b/app/vendor/phpunit/phpunit/src/Util/Json.php @@ -9,54 +9,56 @@ */ namespace PHPUnit\Util; +use const JSON_ERROR_NONE; use const JSON_PRETTY_PRINT; use const JSON_UNESCAPED_SLASHES; use const JSON_UNESCAPED_UNICODE; -use function count; -use function is_array; +use const SORT_STRING; +use function assert; use function is_object; +use function is_scalar; use function json_decode; use function json_encode; use function json_last_error; use function ksort; -use PHPUnit\Framework\Exception; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ -final class Json +final readonly class Json { /** - * Prettify json string. - * - * @throws Exception + * @throws InvalidJsonException */ public static function prettify(string $json): string { $decodedJson = json_decode($json, false); - if (json_last_error()) { - throw new Exception( - 'Cannot prettify invalid json', - ); + if (json_last_error() !== JSON_ERROR_NONE) { + throw new InvalidJsonException; } - return json_encode($decodedJson, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + $result = json_encode($decodedJson, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + + assert($result !== false); + + return $result; } /** - * To allow comparison of JSON strings, first process them into a consistent - * format so that they can be compared as strings. + * Element 0 is true and element 1 is null when JSON decoding did not work. + * * Element 0 is false and element 1 has the decoded value when JSON decoding did work. + * * This is used to avoid ambiguity with JSON strings consisting entirely of 'null' or 'false'. * - * @return array ($error, $canonicalized_json) The $error parameter is used - * to indicate an error decoding the json. This is used to avoid ambiguity - * with JSON strings consisting entirely of 'null' or 'false'. + * @return array{0: false, 1: mixed}|array{0: true, 1: null} */ public static function canonicalize(string $json): array { $decodedJson = json_decode($json); - if (json_last_error()) { + if (json_last_error() !== JSON_ERROR_NONE) { return [true, null]; } @@ -73,26 +75,32 @@ public static function canonicalize(string $json): array * Sort all array keys to ensure both the expected and actual values have * their keys in the same order. */ - private static function recursiveSort(&$json): void + private static function recursiveSort(mixed &$json): void { - if (!is_array($json)) { - // If the object is not empty, change it to an associative array - // so we can sort the keys (and we will still re-encode it - // correctly, since PHP encodes associative arrays as JSON objects.) - // But EMPTY objects MUST remain empty objects. (Otherwise we will - // re-encode it as a JSON array rather than a JSON object.) - // See #2919. - if (is_object($json) && count((array) $json) > 0) { - $json = (array) $json; - } else { - return; - } + if ($json === null || $json === [] || is_scalar($json)) { + return; } - ksort($json); + $isObject = is_object($json); - foreach ($json as $key => &$value) { + if ($isObject) { + // Objects need to be sorted during canonicalization to ensure + // correct comparsion since JSON objects are unordered. It must be + // kept as an object so that the value correctly stays as a JSON + // object instead of potentially being converted to an array. This + // approach ensures that numeric string JSON keys are preserved and + // don't risk being flattened due to PHP's array semantics. + // See #2919, #4584, #4674 + $json = (array) $json; + ksort($json, SORT_STRING); + } + + foreach ($json as &$value) { self::recursiveSort($value); } + + if ($isObject) { + $json = (object) $json; + } } } diff --git a/app/vendor/phpunit/phpunit/src/Util/Log/JUnit.php b/app/vendor/phpunit/phpunit/src/Util/Log/JUnit.php deleted file mode 100644 index c7ba4fc56..000000000 --- a/app/vendor/phpunit/phpunit/src/Util/Log/JUnit.php +++ /dev/null @@ -1,424 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util\Log; - -use function class_exists; -use function get_class; -use function method_exists; -use function sprintf; -use function str_replace; -use function trim; -use DOMDocument; -use DOMElement; -use PHPUnit\Framework\AssertionFailedError; -use PHPUnit\Framework\ExceptionWrapper; -use PHPUnit\Framework\SelfDescribing; -use PHPUnit\Framework\Test; -use PHPUnit\Framework\TestFailure; -use PHPUnit\Framework\TestListener; -use PHPUnit\Framework\TestSuite; -use PHPUnit\Framework\Warning; -use PHPUnit\Util\Exception; -use PHPUnit\Util\Filter; -use PHPUnit\Util\Printer; -use PHPUnit\Util\Xml; -use ReflectionClass; -use ReflectionException; -use Throwable; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class JUnit extends Printer implements TestListener -{ - /** - * @var DOMDocument - */ - private $document; - - /** - * @var DOMElement - */ - private $root; - - /** - * @var bool - */ - private $reportRiskyTests = false; - - /** - * @var DOMElement[] - */ - private $testSuites = []; - - /** - * @var int[] - */ - private $testSuiteTests = [0]; - - /** - * @var int[] - */ - private $testSuiteAssertions = [0]; - - /** - * @var int[] - */ - private $testSuiteErrors = [0]; - - /** - * @var int[] - */ - private $testSuiteWarnings = [0]; - - /** - * @var int[] - */ - private $testSuiteFailures = [0]; - - /** - * @var int[] - */ - private $testSuiteSkipped = [0]; - - /** - * @var int[] - */ - private $testSuiteTimes = [0]; - - /** - * @var int - */ - private $testSuiteLevel = 0; - - /** - * @var DOMElement - */ - private $currentTestCase; - - /** - * @param null|mixed $out - */ - public function __construct($out = null, bool $reportRiskyTests = false) - { - $this->document = new DOMDocument('1.0', 'UTF-8'); - $this->document->formatOutput = true; - - $this->root = $this->document->createElement('testsuites'); - $this->document->appendChild($this->root); - - parent::__construct($out); - - $this->reportRiskyTests = $reportRiskyTests; - } - - /** - * Flush buffer and close output. - */ - public function flush(): void - { - $this->write($this->getXML()); - - parent::flush(); - } - - /** - * An error occurred. - */ - public function addError(Test $test, Throwable $t, float $time): void - { - $this->doAddFault($test, $t, 'error'); - $this->testSuiteErrors[$this->testSuiteLevel]++; - } - - /** - * A warning occurred. - */ - public function addWarning(Test $test, Warning $e, float $time): void - { - $this->doAddFault($test, $e, 'warning'); - $this->testSuiteWarnings[$this->testSuiteLevel]++; - } - - /** - * A failure occurred. - */ - public function addFailure(Test $test, AssertionFailedError $e, float $time): void - { - $this->doAddFault($test, $e, 'failure'); - $this->testSuiteFailures[$this->testSuiteLevel]++; - } - - /** - * Incomplete test. - */ - public function addIncompleteTest(Test $test, Throwable $t, float $time): void - { - $this->doAddSkipped(); - } - - /** - * Risky test. - */ - public function addRiskyTest(Test $test, Throwable $t, float $time): void - { - if (!$this->reportRiskyTests) { - return; - } - - $this->doAddFault($test, $t, 'error'); - $this->testSuiteErrors[$this->testSuiteLevel]++; - } - - /** - * Skipped test. - */ - public function addSkippedTest(Test $test, Throwable $t, float $time): void - { - $this->doAddSkipped(); - } - - /** - * A testsuite started. - */ - public function startTestSuite(TestSuite $suite): void - { - $testSuite = $this->document->createElement('testsuite'); - $testSuite->setAttribute('name', $suite->getName()); - - if (class_exists($suite->getName(), false)) { - try { - $class = new ReflectionClass($suite->getName()); - - $testSuite->setAttribute('file', $class->getFileName()); - } catch (ReflectionException $e) { - } - } - - if ($this->testSuiteLevel > 0) { - $this->testSuites[$this->testSuiteLevel]->appendChild($testSuite); - } else { - $this->root->appendChild($testSuite); - } - - $this->testSuiteLevel++; - $this->testSuites[$this->testSuiteLevel] = $testSuite; - $this->testSuiteTests[$this->testSuiteLevel] = 0; - $this->testSuiteAssertions[$this->testSuiteLevel] = 0; - $this->testSuiteErrors[$this->testSuiteLevel] = 0; - $this->testSuiteWarnings[$this->testSuiteLevel] = 0; - $this->testSuiteFailures[$this->testSuiteLevel] = 0; - $this->testSuiteSkipped[$this->testSuiteLevel] = 0; - $this->testSuiteTimes[$this->testSuiteLevel] = 0; - } - - /** - * A testsuite ended. - */ - public function endTestSuite(TestSuite $suite): void - { - $this->testSuites[$this->testSuiteLevel]->setAttribute( - 'tests', - (string) $this->testSuiteTests[$this->testSuiteLevel], - ); - - $this->testSuites[$this->testSuiteLevel]->setAttribute( - 'assertions', - (string) $this->testSuiteAssertions[$this->testSuiteLevel], - ); - - $this->testSuites[$this->testSuiteLevel]->setAttribute( - 'errors', - (string) $this->testSuiteErrors[$this->testSuiteLevel], - ); - - $this->testSuites[$this->testSuiteLevel]->setAttribute( - 'warnings', - (string) $this->testSuiteWarnings[$this->testSuiteLevel], - ); - - $this->testSuites[$this->testSuiteLevel]->setAttribute( - 'failures', - (string) $this->testSuiteFailures[$this->testSuiteLevel], - ); - - $this->testSuites[$this->testSuiteLevel]->setAttribute( - 'skipped', - (string) $this->testSuiteSkipped[$this->testSuiteLevel], - ); - - $this->testSuites[$this->testSuiteLevel]->setAttribute( - 'time', - sprintf('%F', $this->testSuiteTimes[$this->testSuiteLevel]), - ); - - if ($this->testSuiteLevel > 1) { - $this->testSuiteTests[$this->testSuiteLevel - 1] += $this->testSuiteTests[$this->testSuiteLevel]; - $this->testSuiteAssertions[$this->testSuiteLevel - 1] += $this->testSuiteAssertions[$this->testSuiteLevel]; - $this->testSuiteErrors[$this->testSuiteLevel - 1] += $this->testSuiteErrors[$this->testSuiteLevel]; - $this->testSuiteWarnings[$this->testSuiteLevel - 1] += $this->testSuiteWarnings[$this->testSuiteLevel]; - $this->testSuiteFailures[$this->testSuiteLevel - 1] += $this->testSuiteFailures[$this->testSuiteLevel]; - $this->testSuiteSkipped[$this->testSuiteLevel - 1] += $this->testSuiteSkipped[$this->testSuiteLevel]; - $this->testSuiteTimes[$this->testSuiteLevel - 1] += $this->testSuiteTimes[$this->testSuiteLevel]; - } - - $this->testSuiteLevel--; - } - - /** - * A test started. - */ - public function startTest(Test $test): void - { - $usesDataprovider = false; - - if (method_exists($test, 'usesDataProvider')) { - $usesDataprovider = $test->usesDataProvider(); - } - - $testCase = $this->document->createElement('testcase'); - $testCase->setAttribute('name', $test->getName()); - - try { - $class = new ReflectionClass($test); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - $e->getCode(), - $e, - ); - } - // @codeCoverageIgnoreEnd - - $methodName = $test->getName(!$usesDataprovider); - - if ($class->hasMethod($methodName)) { - try { - $method = $class->getMethod($methodName); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - $e->getCode(), - $e, - ); - } - // @codeCoverageIgnoreEnd - - $testCase->setAttribute('class', $class->getName()); - $testCase->setAttribute('classname', str_replace('\\', '.', $class->getName())); - $testCase->setAttribute('file', $class->getFileName()); - $testCase->setAttribute('line', (string) $method->getStartLine()); - } - - $this->currentTestCase = $testCase; - } - - /** - * A test ended. - */ - public function endTest(Test $test, float $time): void - { - $numAssertions = 0; - - if (method_exists($test, 'getNumAssertions')) { - $numAssertions = $test->getNumAssertions(); - } - - $this->testSuiteAssertions[$this->testSuiteLevel] += $numAssertions; - - $this->currentTestCase->setAttribute( - 'assertions', - (string) $numAssertions, - ); - - $this->currentTestCase->setAttribute( - 'time', - sprintf('%F', $time), - ); - - $this->testSuites[$this->testSuiteLevel]->appendChild( - $this->currentTestCase, - ); - - $this->testSuiteTests[$this->testSuiteLevel]++; - $this->testSuiteTimes[$this->testSuiteLevel] += $time; - - $testOutput = ''; - - if (method_exists($test, 'hasOutput') && method_exists($test, 'getActualOutput')) { - $testOutput = $test->hasOutput() ? $test->getActualOutput() : ''; - } - - if (!empty($testOutput)) { - $systemOut = $this->document->createElement( - 'system-out', - Xml::prepareString($testOutput), - ); - - $this->currentTestCase->appendChild($systemOut); - } - - $this->currentTestCase = null; - } - - /** - * Returns the XML as a string. - */ - public function getXML(): string - { - return $this->document->saveXML(); - } - - private function doAddFault(Test $test, Throwable $t, string $type): void - { - if ($this->currentTestCase === null) { - return; - } - - if ($test instanceof SelfDescribing) { - $buffer = $test->toString() . "\n"; - } else { - $buffer = ''; - } - - $buffer .= trim( - TestFailure::exceptionToString($t) . "\n" . - Filter::getFilteredStacktrace($t), - ); - - $fault = $this->document->createElement( - $type, - Xml::prepareString($buffer), - ); - - if ($t instanceof ExceptionWrapper) { - $fault->setAttribute('type', $t->getClassName()); - } else { - $fault->setAttribute('type', get_class($t)); - } - - $this->currentTestCase->appendChild($fault); - } - - private function doAddSkipped(): void - { - if ($this->currentTestCase === null) { - return; - } - - $skipped = $this->document->createElement('skipped'); - - $this->currentTestCase->appendChild($skipped); - - $this->testSuiteSkipped[$this->testSuiteLevel]++; - } -} diff --git a/app/vendor/phpunit/phpunit/src/Util/Log/TeamCity.php b/app/vendor/phpunit/phpunit/src/Util/Log/TeamCity.php deleted file mode 100644 index 3ba687a11..000000000 --- a/app/vendor/phpunit/phpunit/src/Util/Log/TeamCity.php +++ /dev/null @@ -1,384 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util\Log; - -use const PHP_EOL; -use function class_exists; -use function count; -use function explode; -use function get_class; -use function getmypid; -use function ini_get; -use function is_bool; -use function is_scalar; -use function method_exists; -use function print_r; -use function round; -use function str_replace; -use function stripos; -use PHPUnit\Framework\AssertionFailedError; -use PHPUnit\Framework\ExceptionWrapper; -use PHPUnit\Framework\ExpectationFailedException; -use PHPUnit\Framework\Test; -use PHPUnit\Framework\TestCase; -use PHPUnit\Framework\TestFailure; -use PHPUnit\Framework\TestResult; -use PHPUnit\Framework\TestSuite; -use PHPUnit\Framework\Warning; -use PHPUnit\TextUI\DefaultResultPrinter; -use PHPUnit\Util\Exception; -use PHPUnit\Util\Filter; -use ReflectionClass; -use ReflectionException; -use SebastianBergmann\Comparator\ComparisonFailure; -use Throwable; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class TeamCity extends DefaultResultPrinter -{ - /** - * @var bool - */ - private $isSummaryTestCountPrinted = false; - - /** - * @var string - */ - private $startedTestName; - - /** - * @var false|int - */ - private $flowId; - - public function printResult(TestResult $result): void - { - $this->printHeader($result); - $this->printFooter($result); - } - - /** - * An error occurred. - */ - public function addError(Test $test, Throwable $t, float $time): void - { - $this->printEvent( - 'testFailed', - [ - 'name' => $test->getName(), - 'message' => self::getMessage($t), - 'details' => self::getDetails($t), - 'duration' => self::toMilliseconds($time), - ], - ); - } - - /** - * A warning occurred. - */ - public function addWarning(Test $test, Warning $e, float $time): void - { - $this->write(self::getMessage($e) . PHP_EOL); - } - - /** - * A failure occurred. - */ - public function addFailure(Test $test, AssertionFailedError $e, float $time): void - { - $parameters = [ - 'name' => $test->getName(), - 'message' => self::getMessage($e), - 'details' => self::getDetails($e), - 'duration' => self::toMilliseconds($time), - ]; - - if ($e instanceof ExpectationFailedException) { - $comparisonFailure = $e->getComparisonFailure(); - - if ($comparisonFailure instanceof ComparisonFailure) { - $expectedString = $comparisonFailure->getExpectedAsString(); - - if ($expectedString === null || empty($expectedString)) { - $expectedString = self::getPrimitiveValueAsString($comparisonFailure->getExpected()); - } - - $actualString = $comparisonFailure->getActualAsString(); - - if ($actualString === null || empty($actualString)) { - $actualString = self::getPrimitiveValueAsString($comparisonFailure->getActual()); - } - - if ($actualString !== null && $expectedString !== null) { - $parameters['type'] = 'comparisonFailure'; - $parameters['actual'] = $actualString; - $parameters['expected'] = $expectedString; - } - } - } - - $this->printEvent('testFailed', $parameters); - } - - /** - * Incomplete test. - */ - public function addIncompleteTest(Test $test, Throwable $t, float $time): void - { - $this->printIgnoredTest($test->getName(), $t, $time); - } - - /** - * Risky test. - */ - public function addRiskyTest(Test $test, Throwable $t, float $time): void - { - $this->addError($test, $t, $time); - } - - /** - * Skipped test. - */ - public function addSkippedTest(Test $test, Throwable $t, float $time): void - { - $testName = $test->getName(); - - if ($this->startedTestName !== $testName) { - $this->startTest($test); - $this->printIgnoredTest($testName, $t, $time); - $this->endTest($test, $time); - } else { - $this->printIgnoredTest($testName, $t, $time); - } - } - - public function printIgnoredTest(string $testName, Throwable $t, float $time): void - { - $this->printEvent( - 'testIgnored', - [ - 'name' => $testName, - 'message' => self::getMessage($t), - 'details' => self::getDetails($t), - 'duration' => self::toMilliseconds($time), - ], - ); - } - - /** - * A testsuite started. - */ - public function startTestSuite(TestSuite $suite): void - { - if (stripos(ini_get('disable_functions'), 'getmypid') === false) { - $this->flowId = getmypid(); - } else { - $this->flowId = false; - } - - if (!$this->isSummaryTestCountPrinted) { - $this->isSummaryTestCountPrinted = true; - - $this->printEvent( - 'testCount', - ['count' => count($suite)], - ); - } - - $suiteName = $suite->getName(); - - if (empty($suiteName)) { - return; - } - - $parameters = ['name' => $suiteName]; - - if (class_exists($suiteName, false)) { - $fileName = self::getFileName($suiteName); - $parameters['locationHint'] = "php_qn://{$fileName}::\\{$suiteName}"; - } else { - $split = explode('::', $suiteName); - - if (count($split) === 2 && class_exists($split[0]) && method_exists($split[0], $split[1])) { - $fileName = self::getFileName($split[0]); - $parameters['locationHint'] = "php_qn://{$fileName}::\\{$suiteName}"; - $parameters['name'] = $split[1]; - } - } - - $this->printEvent('testSuiteStarted', $parameters); - } - - /** - * A testsuite ended. - */ - public function endTestSuite(TestSuite $suite): void - { - $suiteName = $suite->getName(); - - if (empty($suiteName)) { - return; - } - - $parameters = ['name' => $suiteName]; - - if (!class_exists($suiteName, false)) { - $split = explode('::', $suiteName); - - if (count($split) === 2 && class_exists($split[0]) && method_exists($split[0], $split[1])) { - $parameters['name'] = $split[1]; - } - } - - $this->printEvent('testSuiteFinished', $parameters); - } - - /** - * A test started. - */ - public function startTest(Test $test): void - { - $testName = $test->getName(); - $this->startedTestName = $testName; - $params = ['name' => $testName]; - - if ($test instanceof TestCase) { - $className = get_class($test); - $fileName = self::getFileName($className); - $params['locationHint'] = "php_qn://{$fileName}::\\{$className}::{$testName}"; - } - - $this->printEvent('testStarted', $params); - } - - /** - * A test ended. - */ - public function endTest(Test $test, float $time): void - { - parent::endTest($test, $time); - - $this->printEvent( - 'testFinished', - [ - 'name' => $test->getName(), - 'duration' => self::toMilliseconds($time), - ], - ); - } - - protected function writeProgress(string $progress): void - { - } - - private function printEvent(string $eventName, array $params = []): void - { - $this->write("\n##teamcity[{$eventName}"); - - if ($this->flowId) { - $params['flowId'] = $this->flowId; - } - - foreach ($params as $key => $value) { - $escapedValue = self::escapeValue((string) $value); - $this->write(" {$key}='{$escapedValue}'"); - } - - $this->write("]\n"); - } - - private static function getMessage(Throwable $t): string - { - $message = ''; - - if ($t instanceof ExceptionWrapper) { - if ($t->getClassName() !== '') { - $message .= $t->getClassName(); - } - - if ($message !== '' && $t->getMessage() !== '') { - $message .= ' : '; - } - } - - return $message . $t->getMessage(); - } - - private static function getDetails(Throwable $t): string - { - $stackTrace = Filter::getFilteredStacktrace($t); - $previous = $t instanceof ExceptionWrapper ? $t->getPreviousWrapped() : $t->getPrevious(); - - while ($previous) { - $stackTrace .= "\nCaused by\n" . - TestFailure::exceptionToString($previous) . "\n" . - Filter::getFilteredStacktrace($previous); - - $previous = $previous instanceof ExceptionWrapper ? - $previous->getPreviousWrapped() : $previous->getPrevious(); - } - - return ' ' . str_replace("\n", "\n ", $stackTrace); - } - - private static function getPrimitiveValueAsString($value): ?string - { - if ($value === null) { - return 'null'; - } - - if (is_bool($value)) { - return $value ? 'true' : 'false'; - } - - if (is_scalar($value)) { - return print_r($value, true); - } - - return null; - } - - private static function escapeValue(string $text): string - { - return str_replace( - ['|', "'", "\n", "\r", ']', '['], - ['||', "|'", '|n', '|r', '|]', '|['], - $text, - ); - } - - /** - * @param string $className - */ - private static function getFileName($className): string - { - try { - return (new ReflectionClass($className))->getFileName(); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - $e->getCode(), - $e, - ); - } - // @codeCoverageIgnoreEnd - } - - /** - * @param float $time microseconds - */ - private static function toMilliseconds(float $time): int - { - return (int) round($time * 1000); - } -} diff --git a/app/vendor/phpunit/phpunit/src/Util/PHP/AbstractPhpProcess.php b/app/vendor/phpunit/phpunit/src/Util/PHP/AbstractPhpProcess.php deleted file mode 100644 index 9620846a6..000000000 --- a/app/vendor/phpunit/phpunit/src/Util/PHP/AbstractPhpProcess.php +++ /dev/null @@ -1,427 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util\PHP; - -use const DIRECTORY_SEPARATOR; -use const PHP_SAPI; -use function array_keys; -use function array_merge; -use function assert; -use function escapeshellarg; -use function file_exists; -use function file_get_contents; -use function ini_get_all; -use function restore_error_handler; -use function set_error_handler; -use function sprintf; -use function str_replace; -use function strpos; -use function strrpos; -use function substr; -use function trim; -use function unlink; -use function unserialize; -use __PHP_Incomplete_Class; -use ErrorException; -use PHPUnit\Framework\AssertionFailedError; -use PHPUnit\Framework\Exception; -use PHPUnit\Framework\SyntheticError; -use PHPUnit\Framework\Test; -use PHPUnit\Framework\TestCase; -use PHPUnit\Framework\TestFailure; -use PHPUnit\Framework\TestResult; -use SebastianBergmann\Environment\Runtime; -use SebastianBergmann\RecursionContext\InvalidArgumentException; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -abstract class AbstractPhpProcess -{ - /** - * @var Runtime - */ - protected $runtime; - - /** - * @var bool - */ - protected $stderrRedirection = false; - - /** - * @var string - */ - protected $stdin = ''; - - /** - * @var string - */ - protected $args = ''; - - /** - * @var array - */ - protected $env = []; - - /** - * @var int - */ - protected $timeout = 0; - - public static function factory(): self - { - if (DIRECTORY_SEPARATOR === '\\') { - return new WindowsPhpProcess; - } - - return new DefaultPhpProcess; - } - - public function __construct() - { - $this->runtime = new Runtime; - } - - /** - * Defines if should use STDERR redirection or not. - * - * Then $stderrRedirection is TRUE, STDERR is redirected to STDOUT. - */ - public function setUseStderrRedirection(bool $stderrRedirection): void - { - $this->stderrRedirection = $stderrRedirection; - } - - /** - * Returns TRUE if uses STDERR redirection or FALSE if not. - */ - public function useStderrRedirection(): bool - { - return $this->stderrRedirection; - } - - /** - * Sets the input string to be sent via STDIN. - */ - public function setStdin(string $stdin): void - { - $this->stdin = $stdin; - } - - /** - * Returns the input string to be sent via STDIN. - */ - public function getStdin(): string - { - return $this->stdin; - } - - /** - * Sets the string of arguments to pass to the php job. - */ - public function setArgs(string $args): void - { - $this->args = $args; - } - - /** - * Returns the string of arguments to pass to the php job. - */ - public function getArgs(): string - { - return $this->args; - } - - /** - * Sets the array of environment variables to start the child process with. - * - * @param array $env - */ - public function setEnv(array $env): void - { - $this->env = $env; - } - - /** - * Returns the array of environment variables to start the child process with. - */ - public function getEnv(): array - { - return $this->env; - } - - /** - * Sets the amount of seconds to wait before timing out. - */ - public function setTimeout(int $timeout): void - { - $this->timeout = $timeout; - } - - /** - * Returns the amount of seconds to wait before timing out. - */ - public function getTimeout(): int - { - return $this->timeout; - } - - /** - * Runs a single test in a separate PHP process. - * - * @throws InvalidArgumentException - */ - public function runTestJob(string $job, Test $test, TestResult $result, string $processResultFile): void - { - $result->startTest($test); - - $processResult = ''; - $_result = $this->runJob($job); - - if (file_exists($processResultFile)) { - $processResult = file_get_contents($processResultFile); - - @unlink($processResultFile); - } - - $this->processChildResult( - $test, - $result, - $processResult, - $_result['stderr'], - ); - } - - /** - * Returns the command based into the configurations. - */ - public function getCommand(array $settings, ?string $file = null): string - { - $command = $this->runtime->getBinary(); - - if ($this->runtime->hasPCOV()) { - $settings = array_merge( - $settings, - $this->runtime->getCurrentSettings( - array_keys(ini_get_all('pcov')), - ), - ); - } elseif ($this->runtime->hasXdebug()) { - $settings = array_merge( - $settings, - $this->runtime->getCurrentSettings( - array_keys(ini_get_all('xdebug')), - ), - ); - } - - $command .= $this->settingsToParameters($settings); - - if (PHP_SAPI === 'phpdbg') { - $command .= ' -qrr'; - - if (!$file) { - $command .= 's='; - } - } - - if ($file) { - $command .= ' ' . escapeshellarg($file); - } - - if ($this->args) { - if (!$file) { - $command .= ' --'; - } - $command .= ' ' . $this->args; - } - - if ($this->stderrRedirection) { - $command .= ' 2>&1'; - } - - return $command; - } - - /** - * Runs a single job (PHP code) using a separate PHP process. - */ - abstract public function runJob(string $job, array $settings = []): array; - - protected function settingsToParameters(array $settings): string - { - $buffer = ''; - - foreach ($settings as $setting) { - $buffer .= ' -d ' . escapeshellarg($setting); - } - - return $buffer; - } - - /** - * Processes the TestResult object from an isolated process. - * - * @throws InvalidArgumentException - */ - private function processChildResult(Test $test, TestResult $result, string $stdout, string $stderr): void - { - $time = 0; - - if (!empty($stderr)) { - $result->addError( - $test, - new Exception(trim($stderr)), - $time, - ); - } else { - set_error_handler( - /** - * @throws ErrorException - */ - static function ($errno, $errstr, $errfile, $errline): void - { - throw new ErrorException($errstr, $errno, $errno, $errfile, $errline); - }, - ); - - try { - if (strpos($stdout, "#!/usr/bin/env php\n") === 0) { - $stdout = substr($stdout, 19); - } - - $childResult = unserialize(str_replace("#!/usr/bin/env php\n", '', $stdout)); - restore_error_handler(); - - if ($childResult === false) { - $result->addFailure( - $test, - new AssertionFailedError('Test was run in child process and ended unexpectedly'), - $time, - ); - } - } catch (ErrorException $e) { - restore_error_handler(); - $childResult = false; - - $result->addError( - $test, - new Exception(trim($stdout), 0, $e), - $time, - ); - } - - if ($childResult !== false) { - if (!empty($childResult['output'])) { - $output = $childResult['output']; - } - - /* @var TestCase $test */ - - $test->setResult($childResult['testResult']); - $test->addToAssertionCount($childResult['numAssertions']); - - $childResult = $childResult['result']; - assert($childResult instanceof TestResult); - - if ($result->getCollectCodeCoverageInformation()) { - $result->getCodeCoverage()->merge( - $childResult->getCodeCoverage(), - ); - } - - $time = $childResult->time(); - $notImplemented = $childResult->notImplemented(); - $risky = $childResult->risky(); - $skipped = $childResult->skipped(); - $errors = $childResult->errors(); - $warnings = $childResult->warnings(); - $failures = $childResult->failures(); - - if (!empty($notImplemented)) { - $result->addError( - $test, - $this->getException($notImplemented[0]), - $time, - ); - } elseif (!empty($risky)) { - $result->addError( - $test, - $this->getException($risky[0]), - $time, - ); - } elseif (!empty($skipped)) { - $result->addError( - $test, - $this->getException($skipped[0]), - $time, - ); - } elseif (!empty($errors)) { - $result->addError( - $test, - $this->getException($errors[0]), - $time, - ); - } elseif (!empty($warnings)) { - $result->addWarning( - $test, - $this->getException($warnings[0]), - $time, - ); - } elseif (!empty($failures)) { - $result->addFailure( - $test, - $this->getException($failures[0]), - $time, - ); - } - } - } - - $result->endTest($test, $time); - - if (!empty($output)) { - print $output; - } - } - - /** - * Gets the thrown exception from a PHPUnit\Framework\TestFailure. - * - * @see https://github.com/sebastianbergmann/phpunit/issues/74 - */ - private function getException(TestFailure $error): Exception - { - $exception = $error->thrownException(); - - if ($exception instanceof __PHP_Incomplete_Class) { - $exceptionArray = []; - - foreach ((array) $exception as $key => $value) { - $key = substr($key, strrpos($key, "\0") + 1); - $exceptionArray[$key] = $value; - } - - $exception = new SyntheticError( - sprintf( - '%s: %s', - $exceptionArray['_PHP_Incomplete_Class_Name'], - $exceptionArray['message'], - ), - $exceptionArray['code'], - $exceptionArray['file'], - $exceptionArray['line'], - $exceptionArray['trace'], - ); - } - - return $exception; - } -} diff --git a/app/vendor/phpunit/phpunit/src/Util/PHP/DefaultJobRunner.php b/app/vendor/phpunit/phpunit/src/Util/PHP/DefaultJobRunner.php new file mode 100644 index 000000000..073e4e4d4 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Util/PHP/DefaultJobRunner.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 PHPUnit\Util\PHP; + +use const PHP_BINARY; +use const PHP_SAPI; +use function array_keys; +use function array_merge; +use function array_values; +use function assert; +use function fclose; +use function file_put_contents; +use function function_exists; +use function fwrite; +use function ini_get_all; +use function is_array; +use function is_resource; +use function proc_close; +use function proc_open; +use function stream_get_contents; +use function sys_get_temp_dir; +use function tempnam; +use function trim; +use function unlink; +use function xdebug_is_debugger_active; +use PHPUnit\Event\Facade; +use PHPUnit\Runner\CodeCoverage; +use SebastianBergmann\Environment\Runtime; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class DefaultJobRunner extends JobRunner +{ + /** + * @throws PhpProcessException + */ + public function run(Job $job): Result + { + $temporaryFile = null; + + if ($job->hasInput()) { + $temporaryFile = tempnam(sys_get_temp_dir(), 'phpunit_'); + + if ($temporaryFile === false || + file_put_contents($temporaryFile, $job->code()) === false) { + // @codeCoverageIgnoreStart + throw new PhpProcessException( + 'Unable to write temporary file', + ); + // @codeCoverageIgnoreEnd + } + + $job = new Job( + $job->input(), + $job->phpSettings(), + $job->environmentVariables(), + $job->arguments(), + null, + $job->redirectErrors(), + ); + } + + assert($temporaryFile !== ''); + + return $this->runProcess($job, $temporaryFile); + } + + /** + * @param ?non-empty-string $temporaryFile + * + * @throws PhpProcessException + */ + private function runProcess(Job $job, ?string $temporaryFile): Result + { + $environmentVariables = null; + + if ($job->hasEnvironmentVariables()) { + /** @phpstan-ignore nullCoalesce.variable */ + $environmentVariables = $_SERVER ?? []; + + unset($environmentVariables['argv'], $environmentVariables['argc']); + + $environmentVariables = array_merge($environmentVariables, $job->environmentVariables()); + + foreach ($environmentVariables as $key => $value) { + if (is_array($value)) { + unset($environmentVariables[$key]); + } + } + + unset($key, $value); + } + + $pipeSpec = [ + 0 => ['pipe', 'r'], + 1 => ['pipe', 'w'], + 2 => ['pipe', 'w'], + ]; + + if ($job->redirectErrors()) { + $pipeSpec[2] = ['redirect', 1]; + } + + $process = proc_open( + $this->buildCommand($job, $temporaryFile), + $pipeSpec, + $pipes, + null, + $environmentVariables, + ); + + if (!is_resource($process)) { + // @codeCoverageIgnoreStart + throw new PhpProcessException( + 'Unable to spawn worker process', + ); + // @codeCoverageIgnoreEnd + } + + Facade::emitter()->childProcessStarted(); + + fwrite($pipes[0], $job->code()); + fclose($pipes[0]); + + $stdout = ''; + $stderr = ''; + + if (isset($pipes[1])) { + $stdout = stream_get_contents($pipes[1]); + + fclose($pipes[1]); + } + + if (isset($pipes[2])) { + $stderr = stream_get_contents($pipes[2]); + + fclose($pipes[2]); + } + + proc_close($process); + + if ($temporaryFile !== null) { + unlink($temporaryFile); + } + + assert($stdout !== false); + assert($stderr !== false); + + return new Result($stdout, $stderr); + } + + /** + * @return non-empty-list + */ + private function buildCommand(Job $job, ?string $file): array + { + $runtime = new Runtime; + $command = [PHP_BINARY]; + $phpSettings = $job->phpSettings(); + + if ($runtime->hasPCOV()) { + $pcovSettings = ini_get_all('pcov'); + + assert($pcovSettings !== false); + + $phpSettings = array_merge( + $phpSettings, + $runtime->getCurrentSettings( + array_keys($pcovSettings), + ), + ); + } elseif ($runtime->hasXdebug()) { + assert(function_exists('xdebug_is_debugger_active')); + + $xdebugSettings = ini_get_all('xdebug'); + + assert($xdebugSettings !== false); + + $phpSettings = array_merge( + $phpSettings, + $runtime->getCurrentSettings( + array_keys($xdebugSettings), + ), + ); + + if (!CodeCoverage::instance()->isActive() && + xdebug_is_debugger_active() === false) { + $phpSettings['xdebug.mode'] = 'xdebug.mode=off'; + } + } + + $command = array_merge($command, $this->settingsToParameters(array_values($phpSettings))); + + if (PHP_SAPI === 'phpdbg') { + $command[] = '-qrr'; + + if ($file === null) { + $command[] = 's='; + } + } + + if ($file !== null) { + $command[] = '-f'; + $command[] = $file; + } + + if ($job->hasArguments()) { + if ($file === null) { + $command[] = '--'; + } + + foreach ($job->arguments() as $argument) { + $command[] = trim($argument); + } + } + + return $command; + } + + /** + * @param list $settings + * + * @return list + */ + private function settingsToParameters(array $settings): array + { + $buffer = []; + + foreach ($settings as $setting) { + $buffer[] = '-d'; + $buffer[] = $setting; + } + + return $buffer; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Util/PHP/DefaultPhpProcess.php b/app/vendor/phpunit/phpunit/src/Util/PHP/DefaultPhpProcess.php deleted file mode 100644 index 64974f1df..000000000 --- a/app/vendor/phpunit/phpunit/src/Util/PHP/DefaultPhpProcess.php +++ /dev/null @@ -1,236 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util\PHP; - -use function array_merge; -use function fclose; -use function file_put_contents; -use function fread; -use function fwrite; -use function is_array; -use function is_resource; -use function proc_close; -use function proc_open; -use function proc_terminate; -use function rewind; -use function sprintf; -use function stream_get_contents; -use function stream_select; -use function sys_get_temp_dir; -use function tempnam; -use function unlink; -use PHPUnit\Framework\Exception; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -class DefaultPhpProcess extends AbstractPhpProcess -{ - /** - * @var string - */ - protected $tempFile; - - /** - * Runs a single job (PHP code) using a separate PHP process. - * - * @throws Exception - */ - public function runJob(string $job, array $settings = []): array - { - if ($this->stdin || $this->useTemporaryFile()) { - if (!($this->tempFile = tempnam(sys_get_temp_dir(), 'PHPUnit')) || - file_put_contents($this->tempFile, $job) === false) { - throw new Exception( - 'Unable to write temporary file', - ); - } - - $job = $this->stdin; - } - - return $this->runProcess($job, $settings); - } - - /** - * Returns an array of file handles to be used in place of pipes. - */ - protected function getHandles(): array - { - return []; - } - - /** - * Handles creating the child process and returning the STDOUT and STDERR. - * - * @throws Exception - */ - protected function runProcess(string $job, array $settings): array - { - $handles = $this->getHandles(); - - $env = null; - - if ($this->env) { - $env = $_SERVER ?? []; - unset($env['argv'], $env['argc']); - $env = array_merge($env, $this->env); - - foreach ($env as $envKey => $envVar) { - if (is_array($envVar)) { - unset($env[$envKey]); - } - } - } - - $pipeSpec = [ - 0 => $handles[0] ?? ['pipe', 'r'], - 1 => $handles[1] ?? ['pipe', 'w'], - 2 => $handles[2] ?? ['pipe', 'w'], - ]; - - $process = proc_open( - $this->getCommand($settings, $this->tempFile), - $pipeSpec, - $pipes, - null, - $env, - ); - - if (!is_resource($process)) { - throw new Exception( - 'Unable to spawn worker process', - ); - } - - if ($job) { - $this->process($pipes[0], $job); - } - - fclose($pipes[0]); - - $stderr = $stdout = ''; - - if ($this->timeout) { - unset($pipes[0]); - - while (true) { - $r = $pipes; - $w = null; - $e = null; - - $n = @stream_select($r, $w, $e, $this->timeout); - - if ($n === false) { - break; - } - - if ($n === 0) { - proc_terminate($process, 9); - - throw new Exception( - sprintf( - 'Job execution aborted after %d seconds', - $this->timeout, - ), - ); - } - - if ($n > 0) { - foreach ($r as $pipe) { - $pipeOffset = 0; - - foreach ($pipes as $i => $origPipe) { - if ($pipe === $origPipe) { - $pipeOffset = $i; - - break; - } - } - - if (!$pipeOffset) { - break; - } - - $line = fread($pipe, 8192); - - if ($line === '' || $line === false) { - fclose($pipes[$pipeOffset]); - - unset($pipes[$pipeOffset]); - } elseif ($pipeOffset === 1) { - $stdout .= $line; - } else { - $stderr .= $line; - } - } - - if (empty($pipes)) { - break; - } - } - } - } else { - if (isset($pipes[1])) { - $stdout = stream_get_contents($pipes[1]); - - fclose($pipes[1]); - } - - if (isset($pipes[2])) { - $stderr = stream_get_contents($pipes[2]); - - fclose($pipes[2]); - } - } - - if (isset($handles[1])) { - rewind($handles[1]); - - $stdout = stream_get_contents($handles[1]); - - fclose($handles[1]); - } - - if (isset($handles[2])) { - rewind($handles[2]); - - $stderr = stream_get_contents($handles[2]); - - fclose($handles[2]); - } - - proc_close($process); - - $this->cleanup(); - - return ['stdout' => $stdout, 'stderr' => $stderr]; - } - - /** - * @param resource $pipe - */ - protected function process($pipe, string $job): void - { - fwrite($pipe, $job); - } - - protected function cleanup(): void - { - if ($this->tempFile) { - unlink($this->tempFile); - } - } - - protected function useTemporaryFile(): bool - { - return false; - } -} diff --git a/app/vendor/phpunit/phpunit/src/Util/PHP/Job.php b/app/vendor/phpunit/phpunit/src/Util/PHP/Job.php new file mode 100644 index 000000000..5405590ac --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Util/PHP/Job.php @@ -0,0 +1,138 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util\PHP; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Job +{ + /** + * @var non-empty-string + */ + private string $code; + + /** + * @var list + */ + private array $phpSettings; + + /** + * @var array + */ + private array $environmentVariables; + + /** + * @var list + */ + private array $arguments; + + /** + * @var ?non-empty-string + */ + private ?string $input; + private bool $redirectErrors; + + /** + * @param non-empty-string $code + * @param list $phpSettings + * @param array $environmentVariables + * @param list $arguments + * @param ?non-empty-string $input + */ + public function __construct(string $code, array $phpSettings = [], array $environmentVariables = [], array $arguments = [], ?string $input = null, bool $redirectErrors = false) + { + $this->code = $code; + $this->phpSettings = $phpSettings; + $this->environmentVariables = $environmentVariables; + $this->arguments = $arguments; + $this->input = $input; + $this->redirectErrors = $redirectErrors; + } + + /** + * @return non-empty-string + */ + public function code(): string + { + return $this->code; + } + + /** + * @return list + */ + public function phpSettings(): array + { + return $this->phpSettings; + } + + /** + * @phpstan-assert-if-true !empty $this->environmentVariables + */ + public function hasEnvironmentVariables(): bool + { + return $this->environmentVariables !== []; + } + + /** + * @return array + */ + public function environmentVariables(): array + { + return $this->environmentVariables; + } + + /** + * @phpstan-assert-if-true !empty $this->arguments + */ + public function hasArguments(): bool + { + return $this->arguments !== []; + } + + /** + * @return list + */ + public function arguments(): array + { + return $this->arguments; + } + + /** + * @phpstan-assert-if-true !empty $this->input + */ + public function hasInput(): bool + { + return $this->input !== null; + } + + /** + * @throws PhpProcessException + * + * @return non-empty-string + */ + public function input(): string + { + if ($this->input === null) { + throw new PhpProcessException('No input specified'); + } + + return $this->input; + } + + public function redirectErrors(): bool + { + return $this->redirectErrors; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Util/PHP/JobRunner.php b/app/vendor/phpunit/phpunit/src/Util/PHP/JobRunner.php new file mode 100644 index 000000000..6805a0c44 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Util/PHP/JobRunner.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 PHPUnit\Util\PHP; + +use function assert; +use function file_get_contents; +use function is_file; +use function unlink; +use PHPUnit\Event\Facade as EventFacade; +use PHPUnit\Framework\ChildProcessResultProcessor; +use PHPUnit\Framework\Test; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +abstract readonly class JobRunner +{ + private ChildProcessResultProcessor $processor; + + public function __construct(ChildProcessResultProcessor $processor) + { + $this->processor = $processor; + } + + /** + * @param non-empty-string $processResultFile + */ + final public function runTestJob(Job $job, string $processResultFile, Test $test): void + { + $result = $this->run($job); + + $processResult = ''; + + if (is_file($processResultFile)) { + $processResult = file_get_contents($processResultFile); + + assert($processResult !== false); + + @unlink($processResultFile); + } + + $this->processor->process( + $test, + $processResult, + $result->stderr(), + ); + + EventFacade::emitter()->childProcessFinished($result->stdout(), $result->stderr()); + } + + abstract public function run(Job $job): Result; +} diff --git a/app/vendor/phpunit/phpunit/src/Util/PHP/JobRunnerRegistry.php b/app/vendor/phpunit/phpunit/src/Util/PHP/JobRunnerRegistry.php new file mode 100644 index 000000000..94e22a4e4 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Util/PHP/JobRunnerRegistry.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 PHPUnit\Util\PHP; + +use PHPUnit\Event\Facade; +use PHPUnit\Framework\ChildProcessResultProcessor; +use PHPUnit\Framework\Test; +use PHPUnit\Runner\CodeCoverage; +use PHPUnit\TestRunner\TestResult\PassedTests; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class JobRunnerRegistry +{ + private static ?JobRunner $runner = null; + + public static function run(Job $job): Result + { + return self::runner()->run($job); + } + + /** + * @param non-empty-string $processResultFile + */ + public static function runTestJob(Job $job, string $processResultFile, Test $test): void + { + self::runner()->runTestJob($job, $processResultFile, $test); + } + + public static function set(JobRunner $runner): void + { + self::$runner = $runner; + } + + private static function runner(): JobRunner + { + if (self::$runner === null) { + self::$runner = new DefaultJobRunner( + new ChildProcessResultProcessor( + Facade::instance(), + Facade::emitter(), + PassedTests::instance(), + CodeCoverage::instance(), + ), + ); + } + + return self::$runner; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Util/PHP/Result.php b/app/vendor/phpunit/phpunit/src/Util/PHP/Result.php new file mode 100644 index 000000000..ed0582260 --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Util/PHP/Result.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 PHPUnit\Util\PHP; + +/** + * @immutable + * + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Result +{ + private string $stdout; + private string $stderr; + + public function __construct(string $stdout, string $stderr) + { + $this->stdout = $stdout; + $this->stderr = $stderr; + } + + public function stdout(): string + { + return $this->stdout; + } + + public function stderr(): string + { + return $this->stderr; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Util/PHP/Template/PhptTestCase.tpl b/app/vendor/phpunit/phpunit/src/Util/PHP/Template/PhptTestCase.tpl deleted file mode 100644 index f23a0d1ae..000000000 --- a/app/vendor/phpunit/phpunit/src/Util/PHP/Template/PhptTestCase.tpl +++ /dev/null @@ -1,57 +0,0 @@ -{driverMethod}($filter), - $filter - ); - - if ({codeCoverageCacheDirectory}) { - $coverage->cacheStaticAnalysis({codeCoverageCacheDirectory}); - } - - $coverage->start(__FILE__); -} - -register_shutdown_function( - function() use ($coverage) { - $output = null; - - if ($coverage) { - $output = $coverage->stop(); - } - - file_put_contents('{coverageFile}', serialize($output)); - } -); - -ob_end_clean(); - -require '{job}'; diff --git a/app/vendor/phpunit/phpunit/src/Util/PHP/Template/TestCaseClass.tpl b/app/vendor/phpunit/phpunit/src/Util/PHP/Template/TestCaseClass.tpl deleted file mode 100644 index 0486d1164..000000000 --- a/app/vendor/phpunit/phpunit/src/Util/PHP/Template/TestCaseClass.tpl +++ /dev/null @@ -1,123 +0,0 @@ -{driverMethod}($filter), - $filter - ); - - if ({cachesStaticAnalysis}) { - $codeCoverage->cacheStaticAnalysis(unserialize('{codeCoverageCacheDirectory}')); - } - - $result->setCodeCoverage($codeCoverage); - } - - $result->beStrictAboutTestsThatDoNotTestAnything({isStrictAboutTestsThatDoNotTestAnything}); - $result->beStrictAboutOutputDuringTests({isStrictAboutOutputDuringTests}); - $result->enforceTimeLimit({enforcesTimeLimit}); - $result->beStrictAboutTodoAnnotatedTests({isStrictAboutTodoAnnotatedTests}); - $result->beStrictAboutResourceUsageDuringSmallTests({isStrictAboutResourceUsageDuringSmallTests}); - - $test = new {className}('{name}', unserialize('{data}'), '{dataName}'); - $test->setDependencyInput(unserialize('{dependencyInput}')); - $test->setInIsolation(true); - - ob_end_clean(); - $test->run($result); - $output = ''; - if (!$test->hasExpectationOnOutput()) { - $output = $test->getActualOutput(); - } - - ini_set('xdebug.scream', '0'); - - @rewind(STDOUT); /* @ as not every STDOUT target stream is rewindable */ - if ($stdout = @stream_get_contents(STDOUT)) { - $output = $stdout . $output; - $streamMetaData = stream_get_meta_data(STDOUT); - if (!empty($streamMetaData['stream_type']) && 'STDIO' === $streamMetaData['stream_type']) { - @ftruncate(STDOUT, 0); - @rewind(STDOUT); - } - } - - file_put_contents( - '{processResultFile}', - serialize( - [ - 'testResult' => $test->getResult(), - 'numAssertions' => $test->getNumAssertions(), - 'result' => $result, - 'output' => $output - ] - ) - ); -} - -$configurationFilePath = '{configurationFilePath}'; - -if ('' !== $configurationFilePath) { - $configuration = (new Loader)->load($configurationFilePath); - - (new PhpHandler)->handle($configuration->php()); - - unset($configuration); -} - -function __phpunit_error_handler($errno, $errstr, $errfile, $errline) -{ - return true; -} - -set_error_handler('__phpunit_error_handler'); - -{constants} -{included_files} -{globals} - -restore_error_handler(); - -if (isset($GLOBALS['__PHPUNIT_BOOTSTRAP'])) { - require_once $GLOBALS['__PHPUNIT_BOOTSTRAP']; - unset($GLOBALS['__PHPUNIT_BOOTSTRAP']); -} - -__phpunit_run_isolated_test(); diff --git a/app/vendor/phpunit/phpunit/src/Util/PHP/Template/TestCaseMethod.tpl b/app/vendor/phpunit/phpunit/src/Util/PHP/Template/TestCaseMethod.tpl deleted file mode 100644 index 067934dbc..000000000 --- a/app/vendor/phpunit/phpunit/src/Util/PHP/Template/TestCaseMethod.tpl +++ /dev/null @@ -1,126 +0,0 @@ -{driverMethod}($filter), - $filter - ); - - if ({cachesStaticAnalysis}) { - $codeCoverage->cacheStaticAnalysis(unserialize('{codeCoverageCacheDirectory}')); - } - - $result->setCodeCoverage($codeCoverage); - } - - $result->beStrictAboutTestsThatDoNotTestAnything({isStrictAboutTestsThatDoNotTestAnything}); - $result->beStrictAboutOutputDuringTests({isStrictAboutOutputDuringTests}); - $result->enforceTimeLimit({enforcesTimeLimit}); - $result->beStrictAboutTodoAnnotatedTests({isStrictAboutTodoAnnotatedTests}); - $result->beStrictAboutResourceUsageDuringSmallTests({isStrictAboutResourceUsageDuringSmallTests}); - - $test = new {className}('{methodName}', unserialize('{data}'), '{dataName}'); - \assert($test instanceof TestCase); - - $test->setDependencyInput(unserialize('{dependencyInput}')); - $test->setInIsolation(true); - - ob_end_clean(); - $test->run($result); - $output = ''; - if (!$test->hasExpectationOnOutput()) { - $output = $test->getActualOutput(); - } - - ini_set('xdebug.scream', '0'); - - @rewind(STDOUT); /* @ as not every STDOUT target stream is rewindable */ - if ($stdout = @stream_get_contents(STDOUT)) { - $output = $stdout . $output; - $streamMetaData = stream_get_meta_data(STDOUT); - if (!empty($streamMetaData['stream_type']) && 'STDIO' === $streamMetaData['stream_type']) { - @ftruncate(STDOUT, 0); - @rewind(STDOUT); - } - } - - file_put_contents( - '{processResultFile}', - serialize( - [ - 'testResult' => $test->getResult(), - 'numAssertions' => $test->getNumAssertions(), - 'result' => $result, - 'output' => $output - ] - ) - ); -} - -$configurationFilePath = '{configurationFilePath}'; - -if ('' !== $configurationFilePath) { - $configuration = (new Loader)->load($configurationFilePath); - - (new PhpHandler)->handle($configuration->php()); - - unset($configuration); -} - -function __phpunit_error_handler($errno, $errstr, $errfile, $errline) -{ - return true; -} - -set_error_handler('__phpunit_error_handler'); - -{constants} -{included_files} -{globals} - -restore_error_handler(); - -if (isset($GLOBALS['__PHPUNIT_BOOTSTRAP'])) { - require_once $GLOBALS['__PHPUNIT_BOOTSTRAP']; - unset($GLOBALS['__PHPUNIT_BOOTSTRAP']); -} - -__phpunit_run_isolated_test(); diff --git a/app/vendor/phpunit/phpunit/src/Util/PHP/WindowsPhpProcess.php b/app/vendor/phpunit/phpunit/src/Util/PHP/WindowsPhpProcess.php deleted file mode 100644 index 338fea717..000000000 --- a/app/vendor/phpunit/phpunit/src/Util/PHP/WindowsPhpProcess.php +++ /dev/null @@ -1,52 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util\PHP; - -use const PHP_MAJOR_VERSION; -use function tmpfile; -use PHPUnit\Framework\Exception; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @see https://bugs.php.net/bug.php?id=51800 - */ -final class WindowsPhpProcess extends DefaultPhpProcess -{ - public function getCommand(array $settings, ?string $file = null): string - { - if (PHP_MAJOR_VERSION < 8) { - return '"' . parent::getCommand($settings, $file) . '"'; - } - - return parent::getCommand($settings, $file); - } - - /** - * @throws Exception - */ - protected function getHandles(): array - { - if (false === $stdout_handle = tmpfile()) { - throw new Exception( - 'A temporary file could not be created; verify that your TEMP environment variable is writable', - ); - } - - return [ - 1 => $stdout_handle, - ]; - } - - protected function useTemporaryFile(): bool - { - return true; - } -} diff --git a/app/vendor/phpunit/phpunit/src/Util/Printer.php b/app/vendor/phpunit/phpunit/src/Util/Printer.php deleted file mode 100644 index 311d49433..000000000 --- a/app/vendor/phpunit/phpunit/src/Util/Printer.php +++ /dev/null @@ -1,116 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util; - -use const ENT_COMPAT; -use const ENT_SUBSTITUTE; -use const PHP_SAPI; -use function assert; -use function count; -use function dirname; -use function explode; -use function fclose; -use function fopen; -use function fsockopen; -use function fwrite; -use function htmlspecialchars; -use function is_resource; -use function is_string; -use function sprintf; -use function str_replace; -use function strncmp; -use function strpos; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -class Printer -{ - /** - * @psalm-var closed-resource|resource - */ - private $stream; - - /** - * @var bool - */ - private $isPhpStream; - - /** - * @param null|resource|string $out - * - * @throws Exception - */ - public function __construct($out = null) - { - if (is_resource($out)) { - $this->stream = $out; - - return; - } - - if (!is_string($out)) { - return; - } - - if (strpos($out, 'socket://') === 0) { - $tmp = explode(':', str_replace('socket://', '', $out)); - - if (count($tmp) !== 2) { - throw new Exception( - sprintf( - '"%s" does not match "socket://hostname:port" format', - $out, - ), - ); - } - - $this->stream = fsockopen($tmp[0], (int) $tmp[1]); - - return; - } - - if (strpos($out, 'php://') === false && !Filesystem::createDirectory(dirname($out))) { - throw new Exception( - sprintf( - 'Directory "%s" was not created', - dirname($out), - ), - ); - } - - $this->stream = fopen($out, 'wb'); - $this->isPhpStream = strncmp($out, 'php://', 6) !== 0; - } - - public function write(string $buffer): void - { - if ($this->stream) { - assert(is_resource($this->stream)); - - fwrite($this->stream, $buffer); - } else { - if (PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg') { - $buffer = htmlspecialchars($buffer, ENT_COMPAT | ENT_SUBSTITUTE); - } - - print $buffer; - } - } - - public function flush(): void - { - if ($this->stream && $this->isPhpStream) { - assert(is_resource($this->stream)); - - fclose($this->stream); - } - } -} diff --git a/app/vendor/phpunit/phpunit/src/Util/Reflection.php b/app/vendor/phpunit/phpunit/src/Util/Reflection.php index 3ffc2d5e1..b61c19b9b 100644 --- a/app/vendor/phpunit/phpunit/src/Util/Reflection.php +++ b/app/vendor/phpunit/phpunit/src/Util/Reflection.php @@ -9,53 +9,107 @@ */ namespace PHPUnit\Util; +use function array_keys; +use function array_merge; +use function array_reverse; +use function assert; use PHPUnit\Framework\Assert; use PHPUnit\Framework\TestCase; use ReflectionClass; +use ReflectionException; use ReflectionMethod; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ -final class Reflection +final readonly class Reflection { /** - * @psalm-return list + * @param class-string $className + * @param non-empty-string $methodName + * + * @return array{file: non-empty-string, line: non-negative-int} */ - public function publicMethodsInTestClass(ReflectionClass $class): array + public static function sourceLocationFor(string $className, string $methodName): array { - return $this->filterMethods($class, ReflectionMethod::IS_PUBLIC); + try { + $reflector = new ReflectionMethod($className, $methodName); + + $file = $reflector->getFileName(); + $line = $reflector->getStartLine(); + } catch (ReflectionException) { + $file = 'unknown'; + $line = 0; + } + + assert($file !== false && $file !== ''); + assert($line !== false && $line >= 0); + + return [ + 'file' => $file, + 'line' => $line, + ]; } /** - * @psalm-return list + * @param ReflectionClass $class + * + * @return list */ - public function methodsInTestClass(ReflectionClass $class): array + public static function publicMethodsDeclaredDirectlyInTestClass(ReflectionClass $class): array { - return $this->filterMethods($class, null); + return self::filterAndSortMethods($class, ReflectionMethod::IS_PUBLIC, true); } /** - * @psalm-return list + * @param ReflectionClass $class + * + * @return list */ - private function filterMethods(ReflectionClass $class, ?int $filter): array + public static function methodsDeclaredDirectlyInTestClass(ReflectionClass $class): array { - $methods = []; + return self::filterAndSortMethods($class, null, false); + } + + /** + * @param ReflectionClass $class + * + * @return list + */ + private static function filterAndSortMethods(ReflectionClass $class, ?int $filter, bool $sortHighestToLowest): array + { + $methodsByClass = []; - // PHP <7.3.5 throw error when null is passed - // to ReflectionClass::getMethods() when strict_types is enabled. - $classMethods = $filter === null ? $class->getMethods() : $class->getMethods($filter); + foreach ($class->getMethods($filter) as $method) { + $declaringClassName = $method->getDeclaringClass()->getName(); - foreach ($classMethods as $method) { - if ($method->getDeclaringClass()->getName() === TestCase::class) { + if ($declaringClassName === TestCase::class) { continue; } - if ($method->getDeclaringClass()->getName() === Assert::class) { + if ($declaringClassName === Assert::class) { continue; } - $methods[] = $method; + if (!isset($methodsByClass[$declaringClassName])) { + $methodsByClass[$declaringClassName] = []; + } + + $methodsByClass[$declaringClassName][] = $method; + } + + $classNames = array_keys($methodsByClass); + + if ($sortHighestToLowest) { + $classNames = array_reverse($classNames); + } + + $methods = []; + + foreach ($classNames as $className) { + $methods = array_merge($methods, $methodsByClass[$className]); } return $methods; diff --git a/app/vendor/phpunit/phpunit/src/Util/RegularExpression.php b/app/vendor/phpunit/phpunit/src/Util/RegularExpression.php deleted file mode 100644 index 1e97d6c2f..000000000 --- a/app/vendor/phpunit/phpunit/src/Util/RegularExpression.php +++ /dev/null @@ -1,31 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util; - -use function preg_match; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class RegularExpression -{ - /** - * @return false|int - */ - public static function safeMatch(string $pattern, string $subject) - { - return ErrorHandler::invokeIgnoringWarnings( - static function () use ($pattern, $subject) - { - return preg_match($pattern, $subject); - }, - ); - } -} diff --git a/app/vendor/phpunit/phpunit/src/Util/Test.php b/app/vendor/phpunit/phpunit/src/Util/Test.php index 4d6319b9d..03c5c9658 100644 --- a/app/vendor/phpunit/phpunit/src/Util/Test.php +++ b/app/vendor/phpunit/phpunit/src/Util/Test.php @@ -9,571 +9,36 @@ */ namespace PHPUnit\Util; -use const PHP_OS; -use const PHP_VERSION; -use function addcslashes; -use function array_flip; -use function array_key_exists; -use function array_merge; -use function array_unique; -use function array_unshift; -use function class_exists; -use function count; -use function explode; -use function extension_loaded; -use function function_exists; -use function get_class; -use function ini_get; -use function interface_exists; -use function is_array; -use function is_int; -use function method_exists; -use function phpversion; -use function preg_match; -use function preg_replace; -use function sprintf; -use function strncmp; -use function strpos; +use const DEBUG_BACKTRACE_IGNORE_ARGS; +use const DEBUG_BACKTRACE_PROVIDE_OBJECT; +use function debug_backtrace; +use function in_array; +use function str_starts_with; use function strtolower; -use function trim; -use function version_compare; -use PHPUnit\Framework\CodeCoverageException; -use PHPUnit\Framework\ExecutionOrderDependency; -use PHPUnit\Framework\InvalidCoversTargetException; -use PHPUnit\Framework\SelfDescribing; +use PHPUnit\Event\Code\NoTestCaseObjectOnCallStackException; use PHPUnit\Framework\TestCase; -use PHPUnit\Framework\Warning; -use PHPUnit\Runner\Version; -use PHPUnit\Util\Annotation\Registry; -use ReflectionClass; -use ReflectionException; +use PHPUnit\Metadata\Parser\Registry; use ReflectionMethod; -use SebastianBergmann\CodeUnit\CodeUnitCollection; -use SebastianBergmann\CodeUnit\InvalidCodeUnitException; -use SebastianBergmann\CodeUnit\Mapper; -use SebastianBergmann\Environment\OperatingSystem; -use SebastianBergmann\RecursionContext\InvalidArgumentException; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ -final class Test +final readonly class Test { /** - * @var int - */ - public const UNKNOWN = -1; - - /** - * @var int - */ - public const SMALL = 0; - - /** - * @var int - */ - public const MEDIUM = 1; - - /** - * @var int - */ - public const LARGE = 2; - - /** - * @var array - */ - private static $hookMethods = []; - - /** - * @throws InvalidArgumentException - */ - public static function describe(\PHPUnit\Framework\Test $test): array - { - if ($test instanceof TestCase) { - return [get_class($test), $test->getName()]; - } - - if ($test instanceof SelfDescribing) { - return ['', $test->toString()]; - } - - return ['', get_class($test)]; - } - - public static function describeAsString(\PHPUnit\Framework\Test $test): string - { - if ($test instanceof SelfDescribing) { - return $test->toString(); - } - - return get_class($test); - } - - /** - * @throws CodeCoverageException - * - * @return array|bool - * - * @psalm-param class-string $className + * @throws NoTestCaseObjectOnCallStackException */ - public static function getLinesToBeCovered(string $className, string $methodName) + public static function currentTestCase(): TestCase { - $annotations = self::parseTestMethodAnnotations( - $className, - $methodName, - ); - - if (!self::shouldCoversAnnotationBeUsed($annotations)) { - return false; - } - - return self::getLinesToBeCoveredOrUsed($className, $methodName, 'covers'); - } - - /** - * Returns lines of code specified with the @uses annotation. - * - * @throws CodeCoverageException - * - * @psalm-param class-string $className - */ - public static function getLinesToBeUsed(string $className, string $methodName): array - { - return self::getLinesToBeCoveredOrUsed($className, $methodName, 'uses'); - } - - public static function requiresCodeCoverageDataCollection(TestCase $test): bool - { - $annotations = self::parseTestMethodAnnotations( - get_class($test), - $test->getName(false), - ); - - // If there is no @covers annotation but a @coversNothing annotation on - // the test method then code coverage data does not need to be collected - if (isset($annotations['method']['coversNothing'])) { - // @see https://github.com/sebastianbergmann/phpunit/issues/4947#issuecomment-1084480950 - // return false; - } - - // If there is at least one @covers annotation then - // code coverage data needs to be collected - if (isset($annotations['method']['covers'])) { - return true; - } - - // If there is no @covers annotation but a @coversNothing annotation - // then code coverage data does not need to be collected - if (isset($annotations['class']['coversNothing'])) { - // @see https://github.com/sebastianbergmann/phpunit/issues/4947#issuecomment-1084480950 - // return false; - } - - // If there is no @coversNothing annotation then - // code coverage data may be collected - return true; - } - - /** - * @throws Exception - * - * @psalm-param class-string $className - */ - public static function getRequirements(string $className, string $methodName): array - { - return self::mergeArraysRecursively( - Registry::getInstance()->forClassName($className)->requirements(), - Registry::getInstance()->forMethod($className, $methodName)->requirements(), - ); - } - - /** - * Returns the missing requirements for a test. - * - * @throws Exception - * @throws Warning - * - * @psalm-param class-string $className - */ - public static function getMissingRequirements(string $className, string $methodName): array - { - $required = self::getRequirements($className, $methodName); - $missing = []; - $hint = null; - - if (!empty($required['PHP'])) { - $operator = new VersionComparisonOperator(empty($required['PHP']['operator']) ? '>=' : $required['PHP']['operator']); - - if (!version_compare(PHP_VERSION, $required['PHP']['version'], $operator->asString())) { - $missing[] = sprintf('PHP %s %s is required.', $operator->asString(), $required['PHP']['version']); - $hint = 'PHP'; - } - } elseif (!empty($required['PHP_constraint'])) { - $version = new \PharIo\Version\Version(self::sanitizeVersionNumber(PHP_VERSION)); - - if (!$required['PHP_constraint']['constraint']->complies($version)) { - $missing[] = sprintf( - 'PHP version does not match the required constraint %s.', - $required['PHP_constraint']['constraint']->asString(), - ); - - $hint = 'PHP_constraint'; - } - } - - if (!empty($required['PHPUnit'])) { - $phpunitVersion = Version::id(); - - $operator = new VersionComparisonOperator(empty($required['PHPUnit']['operator']) ? '>=' : $required['PHPUnit']['operator']); - - if (!version_compare($phpunitVersion, $required['PHPUnit']['version'], $operator->asString())) { - $missing[] = sprintf('PHPUnit %s %s is required.', $operator->asString(), $required['PHPUnit']['version']); - $hint = $hint ?? 'PHPUnit'; - } - } elseif (!empty($required['PHPUnit_constraint'])) { - $phpunitVersion = new \PharIo\Version\Version(self::sanitizeVersionNumber(Version::id())); - - if (!$required['PHPUnit_constraint']['constraint']->complies($phpunitVersion)) { - $missing[] = sprintf( - 'PHPUnit version does not match the required constraint %s.', - $required['PHPUnit_constraint']['constraint']->asString(), - ); - - $hint = $hint ?? 'PHPUnit_constraint'; - } - } - - if (!empty($required['OSFAMILY']) && $required['OSFAMILY'] !== (new OperatingSystem)->getFamily()) { - $missing[] = sprintf('Operating system %s is required.', $required['OSFAMILY']); - $hint = $hint ?? 'OSFAMILY'; - } - - if (!empty($required['OS'])) { - $requiredOsPattern = sprintf('/%s/i', addcslashes($required['OS'], '/')); - - if (!preg_match($requiredOsPattern, PHP_OS)) { - $missing[] = sprintf('Operating system matching %s is required.', $requiredOsPattern); - $hint = $hint ?? 'OS'; - } - } - - if (!empty($required['functions'])) { - foreach ($required['functions'] as $function) { - $pieces = explode('::', $function); - - if (count($pieces) === 2 && class_exists($pieces[0]) && method_exists($pieces[0], $pieces[1])) { - continue; - } - - if (function_exists($function)) { - continue; - } - - $missing[] = sprintf('Function %s is required.', $function); - $hint = $hint ?? 'function_' . $function; - } - } - - if (!empty($required['setting'])) { - foreach ($required['setting'] as $setting => $value) { - if (ini_get($setting) !== $value) { - $missing[] = sprintf('Setting "%s" must be "%s".', $setting, $value); - $hint = $hint ?? '__SETTING_' . $setting; - } - } - } - - if (!empty($required['extensions'])) { - foreach ($required['extensions'] as $extension) { - if (isset($required['extension_versions'][$extension])) { - continue; - } - - if (!extension_loaded($extension)) { - $missing[] = sprintf('Extension %s is required.', $extension); - $hint = $hint ?? 'extension_' . $extension; - } - } - } - - if (!empty($required['extension_versions'])) { - foreach ($required['extension_versions'] as $extension => $req) { - $actualVersion = phpversion($extension); - - $operator = new VersionComparisonOperator(empty($req['operator']) ? '>=' : $req['operator']); - - if ($actualVersion === false || !version_compare($actualVersion, $req['version'], $operator->asString())) { - $missing[] = sprintf('Extension %s %s %s is required.', $extension, $operator->asString(), $req['version']); - $hint = $hint ?? 'extension_' . $extension; - } + foreach (debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT | DEBUG_BACKTRACE_IGNORE_ARGS) as $frame) { + if (isset($frame['object']) && $frame['object'] instanceof TestCase) { + return $frame['object']; } } - if ($hint && isset($required['__OFFSET'])) { - array_unshift($missing, '__OFFSET_FILE=' . $required['__OFFSET']['__FILE']); - array_unshift($missing, '__OFFSET_LINE=' . ($required['__OFFSET'][$hint] ?? 1)); - } - - return $missing; - } - - /** - * Returns the provided data for a method. - * - * @throws Exception - * - * @psalm-param class-string $className - */ - public static function getProvidedData(string $className, string $methodName): ?array - { - return Registry::getInstance()->forMethod($className, $methodName)->getProvidedData(); - } - - /** - * @psalm-param class-string $className - */ - public static function parseTestMethodAnnotations(string $className, ?string $methodName = null): array - { - $registry = Registry::getInstance(); - - if ($methodName !== null) { - try { - return [ - 'method' => $registry->forMethod($className, $methodName)->symbolAnnotations(), - 'class' => $registry->forClassName($className)->symbolAnnotations(), - ]; - } catch (Exception $methodNotFound) { - // ignored - } - } - - return [ - 'method' => null, - 'class' => $registry->forClassName($className)->symbolAnnotations(), - ]; - } - - /** - * @psalm-param class-string $className - */ - public static function getInlineAnnotations(string $className, string $methodName): array - { - return Registry::getInstance()->forMethod($className, $methodName)->getInlineAnnotations(); - } - - /** @psalm-param class-string $className */ - public static function getBackupSettings(string $className, string $methodName): array - { - return [ - 'backupGlobals' => self::getBooleanAnnotationSetting( - $className, - $methodName, - 'backupGlobals', - ), - 'backupStaticAttributes' => self::getBooleanAnnotationSetting( - $className, - $methodName, - 'backupStaticAttributes', - ), - ]; - } - - /** - * @psalm-param class-string $className - * - * @return ExecutionOrderDependency[] - */ - public static function getDependencies(string $className, string $methodName): array - { - $annotations = self::parseTestMethodAnnotations( - $className, - $methodName, - ); - - $dependsAnnotations = $annotations['class']['depends'] ?? []; - - if (isset($annotations['method']['depends'])) { - $dependsAnnotations = array_merge( - $dependsAnnotations, - $annotations['method']['depends'], - ); - } - - // Normalize dependency name to className::methodName - $dependencies = []; - - foreach ($dependsAnnotations as $value) { - $dependencies[] = ExecutionOrderDependency::createFromDependsAnnotation($className, $value); - } - - return array_unique($dependencies); - } - - /** @psalm-param class-string $className */ - public static function getGroups(string $className, ?string $methodName = ''): array - { - $annotations = self::parseTestMethodAnnotations( - $className, - $methodName, - ); - - $groups = []; - - if (isset($annotations['method']['author'])) { - $groups[] = $annotations['method']['author']; - } elseif (isset($annotations['class']['author'])) { - $groups[] = $annotations['class']['author']; - } - - if (isset($annotations['class']['group'])) { - $groups[] = $annotations['class']['group']; - } - - if (isset($annotations['method']['group'])) { - $groups[] = $annotations['method']['group']; - } - - if (isset($annotations['class']['ticket'])) { - $groups[] = $annotations['class']['ticket']; - } - - if (isset($annotations['method']['ticket'])) { - $groups[] = $annotations['method']['ticket']; - } - - foreach (['method', 'class'] as $element) { - foreach (['small', 'medium', 'large'] as $size) { - if (isset($annotations[$element][$size])) { - $groups[] = [$size]; - - break 2; - } - } - } - - foreach (['method', 'class'] as $element) { - if (isset($annotations[$element]['covers'])) { - foreach ($annotations[$element]['covers'] as $coversTarget) { - $groups[] = ['__phpunit_covers_' . self::canonicalizeName($coversTarget)]; - } - } - - if (isset($annotations[$element]['uses'])) { - foreach ($annotations[$element]['uses'] as $usesTarget) { - $groups[] = ['__phpunit_uses_' . self::canonicalizeName($usesTarget)]; - } - } - } - - return array_unique(array_merge([], ...$groups)); - } - - /** @psalm-param class-string $className */ - public static function getSize(string $className, ?string $methodName): int - { - $groups = array_flip(self::getGroups($className, $methodName)); - - if (isset($groups['large'])) { - return self::LARGE; - } - - if (isset($groups['medium'])) { - return self::MEDIUM; - } - - if (isset($groups['small'])) { - return self::SMALL; - } - - return self::UNKNOWN; - } - - /** @psalm-param class-string $className */ - public static function getProcessIsolationSettings(string $className, string $methodName): bool - { - $annotations = self::parseTestMethodAnnotations( - $className, - $methodName, - ); - - return isset($annotations['class']['runTestsInSeparateProcesses']) || isset($annotations['method']['runInSeparateProcess']); - } - - /** @psalm-param class-string $className */ - public static function getClassProcessIsolationSettings(string $className, string $methodName): bool - { - $annotations = self::parseTestMethodAnnotations( - $className, - $methodName, - ); - - return isset($annotations['class']['runClassInSeparateProcess']); - } - - /** @psalm-param class-string $className */ - public static function getPreserveGlobalStateSettings(string $className, string $methodName): ?bool - { - return self::getBooleanAnnotationSetting( - $className, - $methodName, - 'preserveGlobalState', - ); - } - - /** @psalm-param class-string $className */ - public static function getHookMethods(string $className): array - { - if (!class_exists($className, false)) { - return self::emptyHookMethodsArray(); - } - - if (!isset(self::$hookMethods[$className])) { - self::$hookMethods[$className] = self::emptyHookMethodsArray(); - - try { - foreach ((new Reflection)->methodsInTestClass(new ReflectionClass($className)) as $method) { - $docBlock = Registry::getInstance()->forMethod($className, $method->getName()); - - if ($method->isStatic()) { - if ($docBlock->isHookToBeExecutedBeforeClass()) { - array_unshift( - self::$hookMethods[$className]['beforeClass'], - $method->getName(), - ); - } - - if ($docBlock->isHookToBeExecutedAfterClass()) { - self::$hookMethods[$className]['afterClass'][] = $method->getName(); - } - } - - if ($docBlock->isToBeExecutedBeforeTest()) { - array_unshift( - self::$hookMethods[$className]['before'], - $method->getName(), - ); - } - - if ($docBlock->isToBeExecutedAsPreCondition()) { - array_unshift( - self::$hookMethods[$className]['preCondition'], - $method->getName(), - ); - } - - if ($docBlock->isToBeExecutedAsPostCondition()) { - self::$hookMethods[$className]['postCondition'][] = $method->getName(); - } - - if ($docBlock->isToBeExecutedAfterTest()) { - self::$hookMethods[$className]['after'][] = $method->getName(); - } - } - } catch (ReflectionException $e) { - } - } - - return self::$hookMethods[$className]; + throw new NoTestCaseObjectOnCallStackException; } public static function isTestMethod(ReflectionMethod $method): bool @@ -582,203 +47,40 @@ public static function isTestMethod(ReflectionMethod $method): bool return false; } - if (strpos($method->getName(), 'test') === 0) { + if (str_starts_with($method->getName(), 'test')) { return true; } - return array_key_exists( - 'test', - Registry::getInstance()->forMethod( - $method->getDeclaringClass()->getName(), - $method->getName(), - ) - ->symbolAnnotations(), + $metadata = Registry::parser()->forMethod( + $method->getDeclaringClass()->getName(), + $method->getName(), ); - } - /** - * @throws CodeCoverageException - * - * @psalm-param class-string $className - */ - private static function getLinesToBeCoveredOrUsed(string $className, string $methodName, string $mode): array - { - $annotations = self::parseTestMethodAnnotations( - $className, - $methodName, - ); - - $classShortcut = null; - - if (!empty($annotations['class'][$mode . 'DefaultClass'])) { - if (count($annotations['class'][$mode . 'DefaultClass']) > 1) { - throw new CodeCoverageException( - sprintf( - 'More than one @%sClass annotation in class or interface "%s".', - $mode, - $className, - ), - ); - } - - $classShortcut = $annotations['class'][$mode . 'DefaultClass'][0]; - } - - $list = $annotations['class'][$mode] ?? []; - - if (isset($annotations['method'][$mode])) { - $list = array_merge($list, $annotations['method'][$mode]); - } - - $codeUnits = CodeUnitCollection::fromArray([]); - $mapper = new Mapper; - - foreach (array_unique($list) as $element) { - if ($classShortcut && strncmp($element, '::', 2) === 0) { - $element = $classShortcut . $element; - } - - $element = preg_replace('/[\s()]+$/', '', $element); - $element = explode(' ', $element); - $element = $element[0]; - - if ($mode === 'covers' && interface_exists($element)) { - throw new InvalidCoversTargetException( - sprintf( - 'Trying to @cover interface "%s".', - $element, - ), - ); - } - - try { - $codeUnits = $codeUnits->mergeWith($mapper->stringToCodeUnits($element)); - } catch (InvalidCodeUnitException $e) { - throw new InvalidCoversTargetException( - sprintf( - '"@%s %s" is invalid', - $mode, - $element, - ), - $e->getCode(), - $e, - ); - } - } - - return $mapper->codeUnitsToSourceLines($codeUnits); + return $metadata->isTest()->isNotEmpty(); } - private static function emptyHookMethodsArray(): array + public static function isHookMethod(ReflectionMethod $method): bool { - return [ - 'beforeClass' => ['setUpBeforeClass'], - 'before' => ['setUp'], - 'preCondition' => ['assertPreConditions'], - 'postCondition' => ['assertPostConditions'], - 'after' => ['tearDown'], - 'afterClass' => ['tearDownAfterClass'], + $defaultNames = [ + 'setupbeforeclass', + 'setup', + 'assertpreconditions', + 'assertpostconditions', + 'teardown', + 'teardownafterclass', ]; - } - - /** @psalm-param class-string $className */ - private static function getBooleanAnnotationSetting(string $className, ?string $methodName, string $settingName): ?bool - { - $annotations = self::parseTestMethodAnnotations( - $className, - $methodName, - ); - - if (isset($annotations['method'][$settingName])) { - if ($annotations['method'][$settingName][0] === 'enabled') { - return true; - } - if ($annotations['method'][$settingName][0] === 'disabled') { - return false; - } - } - - if (isset($annotations['class'][$settingName])) { - if ($annotations['class'][$settingName][0] === 'enabled') { - return true; - } - - if ($annotations['class'][$settingName][0] === 'disabled') { - return false; - } - } - - return null; - } - - /** - * Trims any extensions from version string that follows after - * the .[.] format. - */ - private static function sanitizeVersionNumber(string $version) - { - return preg_replace( - '/^(\d+\.\d+(?:.\d+)?).*$/', - '$1', - $version, - ); - } - - private static function shouldCoversAnnotationBeUsed(array $annotations): bool - { - if (isset($annotations['method']['coversNothing'])) { - return false; - } - - if (isset($annotations['method']['covers'])) { + if (in_array(strtolower($method->getName()), $defaultNames, true)) { return true; } - if (isset($annotations['class']['coversNothing'])) { - return false; - } - - return true; - } - - /** - * Merge two arrays together. - * - * If an integer key exists in both arrays and preserveNumericKeys is false, the value - * from the second array will be appended to the first array. If both values are arrays, they - * are merged together, else the value of the second array overwrites the one of the first array. - * - * This implementation is copied from https://github.com/zendframework/zend-stdlib/blob/76b653c5e99b40eccf5966e3122c90615134ae46/src/ArrayUtils.php - * - * Zend Framework (http://framework.zend.com/) - * - * @see http://github.com/zendframework/zf2 for the canonical source repository - * - * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) - * @license http://framework.zend.com/license/new-bsd New BSD License - */ - private static function mergeArraysRecursively(array $a, array $b): array - { - foreach ($b as $key => $value) { - if (array_key_exists($key, $a)) { - if (is_int($key)) { - $a[] = $value; - } elseif (is_array($value) && is_array($a[$key])) { - $a[$key] = self::mergeArraysRecursively($a[$key], $value); - } else { - $a[$key] = $value; - } - } else { - $a[$key] = $value; - } - } - - return $a; - } + $metadata = Registry::parser()->forMethod($method->getDeclaringClass()->getName(), $method->getName()); - private static function canonicalizeName(string $name): string - { - return strtolower(trim($name, '\\')); + return $metadata->isBeforeClass()->isNotEmpty() || + $metadata->isBefore()->isNotEmpty() || + $metadata->isPreCondition()->isNotEmpty() || + $metadata->isPostCondition()->isNotEmpty() || + $metadata->isAfter()->isNotEmpty() || + $metadata->isAfterClass()->isNotEmpty(); } } diff --git a/app/vendor/phpunit/phpunit/src/Util/TestDox/CliTestDoxPrinter.php b/app/vendor/phpunit/phpunit/src/Util/TestDox/CliTestDoxPrinter.php deleted file mode 100644 index 3348957de..000000000 --- a/app/vendor/phpunit/phpunit/src/Util/TestDox/CliTestDoxPrinter.php +++ /dev/null @@ -1,384 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util\TestDox; - -use const PHP_EOL; -use function array_map; -use function ceil; -use function count; -use function explode; -use function get_class; -use function implode; -use function preg_match; -use function sprintf; -use function strlen; -use function strpos; -use function trim; -use PHPUnit\Framework\Exception; -use PHPUnit\Framework\Test; -use PHPUnit\Framework\TestCase; -use PHPUnit\Framework\TestFailure; -use PHPUnit\Framework\TestResult; -use PHPUnit\Runner\BaseTestRunner; -use PHPUnit\Runner\PhptTestCase; -use PHPUnit\Util\Color; -use PHPUnit\Util\Filter; -use SebastianBergmann\RecursionContext\InvalidArgumentException; -use SebastianBergmann\Timer\ResourceUsageFormatter; -use SebastianBergmann\Timer\Timer; -use Throwable; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -class CliTestDoxPrinter extends TestDoxPrinter -{ - /** - * The default Testdox left margin for messages is a vertical line. - */ - private const PREFIX_SIMPLE = [ - 'default' => '│', - 'start' => '│', - 'message' => '│', - 'diff' => '│', - 'trace' => '│', - 'last' => '│', - ]; - - /** - * Colored Testdox use box-drawing for a more textured map of the message. - */ - private const PREFIX_DECORATED = [ - 'default' => '│', - 'start' => '┐', - 'message' => '├', - 'diff' => '┊', - 'trace' => '╵', - 'last' => '┴', - ]; - - private const SPINNER_ICONS = [ - " \e[36m◐\e[0m running tests", - " \e[36m◓\e[0m running tests", - " \e[36m◑\e[0m running tests", - " \e[36m◒\e[0m running tests", - ]; - private const STATUS_STYLES = [ - BaseTestRunner::STATUS_PASSED => [ - 'symbol' => '✔', - 'color' => 'fg-green', - ], - BaseTestRunner::STATUS_ERROR => [ - 'symbol' => '✘', - 'color' => 'fg-yellow', - 'message' => 'bg-yellow,fg-black', - ], - BaseTestRunner::STATUS_FAILURE => [ - 'symbol' => '✘', - 'color' => 'fg-red', - 'message' => 'bg-red,fg-white', - ], - BaseTestRunner::STATUS_SKIPPED => [ - 'symbol' => '↩', - 'color' => 'fg-cyan', - 'message' => 'fg-cyan', - ], - BaseTestRunner::STATUS_RISKY => [ - 'symbol' => '☢', - 'color' => 'fg-yellow', - 'message' => 'fg-yellow', - ], - BaseTestRunner::STATUS_INCOMPLETE => [ - 'symbol' => '∅', - 'color' => 'fg-yellow', - 'message' => 'fg-yellow', - ], - BaseTestRunner::STATUS_WARNING => [ - 'symbol' => '⚠', - 'color' => 'fg-yellow', - 'message' => 'fg-yellow', - ], - BaseTestRunner::STATUS_UNKNOWN => [ - 'symbol' => '?', - 'color' => 'fg-blue', - 'message' => 'fg-white,bg-blue', - ], - ]; - - /** - * @var int[] - */ - private $nonSuccessfulTestResults = []; - - /** - * @var Timer - */ - private $timer; - - /** - * @param null|resource|string $out - * @param int|string $numberOfColumns - * - * @throws Exception - */ - public function __construct($out = null, bool $verbose = false, string $colors = self::COLOR_DEFAULT, bool $debug = false, $numberOfColumns = 80, bool $reverse = false) - { - parent::__construct($out, $verbose, $colors, $debug, $numberOfColumns, $reverse); - - $this->timer = new Timer; - - $this->timer->start(); - } - - public function printResult(TestResult $result): void - { - $this->printHeader($result); - - $this->printNonSuccessfulTestsSummary($result->count()); - - $this->printFooter($result); - } - - protected function printHeader(TestResult $result): void - { - $this->write("\n" . (new ResourceUsageFormatter)->resourceUsage($this->timer->stop()) . "\n\n"); - } - - protected function formatClassName(Test $test): string - { - if ($test instanceof TestCase) { - return $this->prettifier->prettifyTestClass(get_class($test)); - } - - return get_class($test); - } - - /** - * @throws InvalidArgumentException - */ - protected function registerTestResult(Test $test, ?Throwable $t, int $status, float $time, bool $verbose): void - { - if ($status !== BaseTestRunner::STATUS_PASSED) { - $this->nonSuccessfulTestResults[] = $this->testIndex; - } - - parent::registerTestResult($test, $t, $status, $time, $verbose); - } - - /** - * @throws InvalidArgumentException - */ - protected function formatTestName(Test $test): string - { - if ($test instanceof TestCase) { - return $this->prettifier->prettifyTestCase($test); - } - - return parent::formatTestName($test); - } - - protected function writeTestResult(array $prevResult, array $result): void - { - // spacer line for new suite headers and after verbose messages - if ($prevResult['testName'] !== '' && - (!empty($prevResult['message']) || $prevResult['className'] !== $result['className'])) { - $this->write(PHP_EOL); - } - - // suite header - if ($prevResult['className'] !== $result['className']) { - $this->write($this->colorizeTextBox('underlined', $result['className']) . PHP_EOL); - } - - // test result line - if ($this->colors && $result['className'] === PhptTestCase::class) { - $testName = Color::colorizePath($result['testName'], $prevResult['testName'], true); - } else { - $testName = $result['testMethod']; - } - - $style = self::STATUS_STYLES[$result['status']]; - $line = sprintf( - ' %s %s%s' . PHP_EOL, - $this->colorizeTextBox($style['color'], $style['symbol']), - $testName, - $this->verbose ? ' ' . $this->formatRuntime($result['time'], $style['color']) : '', - ); - - $this->write($line); - - // additional information when verbose - $this->write($result['message']); - } - - protected function formatThrowable(Throwable $t, ?int $status = null): string - { - return trim(TestFailure::exceptionToString($t)); - } - - protected function colorizeMessageAndDiff(string $style, string $buffer): array - { - $lines = $buffer ? array_map('\rtrim', explode(PHP_EOL, $buffer)) : []; - $message = []; - $diff = []; - $insideDiff = false; - - foreach ($lines as $line) { - if ($line === '--- Expected') { - $insideDiff = true; - } - - if (!$insideDiff) { - $message[] = $line; - } else { - if (strpos($line, '-') === 0) { - $line = Color::colorize('fg-red', Color::visualizeWhitespace($line, true)); - } elseif (strpos($line, '+') === 0) { - $line = Color::colorize('fg-green', Color::visualizeWhitespace($line, true)); - } elseif ($line === '@@ @@') { - $line = Color::colorize('fg-cyan', $line); - } - $diff[] = $line; - } - } - $diff = implode(PHP_EOL, $diff); - - if (!empty($message)) { - $message = $this->colorizeTextBox($style, implode(PHP_EOL, $message)); - } - - return [$message, $diff]; - } - - protected function formatStacktrace(Throwable $t): string - { - $trace = Filter::getFilteredStacktrace($t); - - if (!$this->colors) { - return $trace; - } - - $lines = []; - $prevPath = ''; - - foreach (explode(PHP_EOL, $trace) as $line) { - if (preg_match('/^(.*):(\d+)$/', $line, $matches)) { - $lines[] = Color::colorizePath($matches[1], $prevPath) . - Color::dim(':') . - Color::colorize('fg-blue', $matches[2]) . - "\n"; - $prevPath = $matches[1]; - } else { - $lines[] = $line; - $prevPath = ''; - } - } - - return implode('', $lines); - } - - protected function formatTestResultMessage(Throwable $t, array $result, ?string $prefix = null): string - { - $message = $this->formatThrowable($t, $result['status']); - $diff = ''; - - if (!($this->verbose || $result['verbose'])) { - return ''; - } - - if ($message && $this->colors) { - $style = self::STATUS_STYLES[$result['status']]['message'] ?? ''; - [$message, $diff] = $this->colorizeMessageAndDiff($style, $message); - } - - if ($prefix === null || !$this->colors) { - $prefix = self::PREFIX_SIMPLE; - } - - if ($this->colors) { - $color = self::STATUS_STYLES[$result['status']]['color'] ?? ''; - $prefix = array_map(static function ($p) use ($color) - { - return Color::colorize($color, $p); - }, self::PREFIX_DECORATED); - } - - $trace = $this->formatStacktrace($t); - $out = $this->prefixLines($prefix['start'], PHP_EOL) . PHP_EOL; - - if ($message) { - $out .= $this->prefixLines($prefix['message'], $message . PHP_EOL) . PHP_EOL; - } - - if ($diff) { - $out .= $this->prefixLines($prefix['diff'], $diff . PHP_EOL) . PHP_EOL; - } - - if ($trace) { - if ($message || $diff) { - $out .= $this->prefixLines($prefix['default'], PHP_EOL) . PHP_EOL; - } - $out .= $this->prefixLines($prefix['trace'], $trace . PHP_EOL) . PHP_EOL; - } - $out .= $this->prefixLines($prefix['last'], PHP_EOL) . PHP_EOL; - - return $out; - } - - protected function drawSpinner(): void - { - if ($this->colors) { - $id = $this->spinState % count(self::SPINNER_ICONS); - $this->write(self::SPINNER_ICONS[$id]); - } - } - - protected function undrawSpinner(): void - { - if ($this->colors) { - $id = $this->spinState % count(self::SPINNER_ICONS); - $this->write("\e[1K\e[" . strlen(self::SPINNER_ICONS[$id]) . 'D'); - } - } - - private function formatRuntime(float $time, string $color = ''): string - { - if (!$this->colors) { - return sprintf('[%.2f ms]', $time * 1000); - } - - if ($time > 1) { - $color = 'fg-magenta'; - } - - return Color::colorize($color, ' ' . (int) ceil($time * 1000) . ' ' . Color::dim('ms')); - } - - private function printNonSuccessfulTestsSummary(int $numberOfExecutedTests): void - { - if (empty($this->nonSuccessfulTestResults)) { - return; - } - - if ((count($this->nonSuccessfulTestResults) / $numberOfExecutedTests) >= 0.7) { - return; - } - - $this->write("Summary of non-successful tests:\n\n"); - - $prevResult = $this->getEmptyTestResult(); - - foreach ($this->nonSuccessfulTestResults as $testIndex) { - $result = $this->testResults[$testIndex]; - $this->writeTestResult($prevResult, $result); - $prevResult = $result; - } - } -} diff --git a/app/vendor/phpunit/phpunit/src/Util/TestDox/HtmlResultPrinter.php b/app/vendor/phpunit/phpunit/src/Util/TestDox/HtmlResultPrinter.php deleted file mode 100644 index d08bfad49..000000000 --- a/app/vendor/phpunit/phpunit/src/Util/TestDox/HtmlResultPrinter.php +++ /dev/null @@ -1,159 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util\TestDox; - -use function sprintf; -use PHPUnit\Framework\TestResult; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class HtmlResultPrinter extends ResultPrinter -{ - /** - * @var string - */ - private const PAGE_HEADER = <<<'EOT' - - - - - Test Documentation - - - -EOT; - - /** - * @var string - */ - private const CLASS_HEADER = <<<'EOT' - -

    %s

    -
      - -EOT; - - /** - * @var string - */ - private const CLASS_FOOTER = <<<'EOT' -
    -EOT; - - /** - * @var string - */ - private const PAGE_FOOTER = <<<'EOT' - - - -EOT; - - public function printResult(TestResult $result): void - { - } - - /** - * Handler for 'start run' event. - */ - protected function startRun(): void - { - $this->write(self::PAGE_HEADER); - } - - /** - * Handler for 'start class' event. - */ - protected function startClass(string $name): void - { - $this->write( - sprintf( - self::CLASS_HEADER, - $this->currentTestClassPrettified, - ), - ); - } - - /** - * Handler for 'on test' event. - */ - protected function onTest(string $name, bool $success = true): void - { - $this->write( - sprintf( - "
  • %s
  • \n", - $success ? 'success' : 'defect', - $name, - ), - ); - } - - /** - * Handler for 'end class' event. - */ - protected function endClass(string $name): void - { - $this->write(self::CLASS_FOOTER); - } - - /** - * Handler for 'end run' event. - */ - protected function endRun(): void - { - $this->write(self::PAGE_FOOTER); - } -} diff --git a/app/vendor/phpunit/phpunit/src/Util/TestDox/NamePrettifier.php b/app/vendor/phpunit/phpunit/src/Util/TestDox/NamePrettifier.php deleted file mode 100644 index f67cff68b..000000000 --- a/app/vendor/phpunit/phpunit/src/Util/TestDox/NamePrettifier.php +++ /dev/null @@ -1,313 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util\TestDox; - -use function array_key_exists; -use function array_keys; -use function array_map; -use function array_pop; -use function array_values; -use function explode; -use function get_class; -use function gettype; -use function implode; -use function in_array; -use function is_bool; -use function is_float; -use function is_int; -use function is_numeric; -use function is_object; -use function is_scalar; -use function is_string; -use function ord; -use function preg_quote; -use function preg_replace; -use function range; -use function sprintf; -use function str_replace; -use function strlen; -use function strpos; -use function strtolower; -use function strtoupper; -use function substr; -use function trim; -use PHPUnit\Framework\TestCase; -use PHPUnit\Util\Color; -use PHPUnit\Util\Exception as UtilException; -use PHPUnit\Util\Test; -use ReflectionException; -use ReflectionMethod; -use ReflectionObject; -use SebastianBergmann\Exporter\Exporter; -use SebastianBergmann\RecursionContext\InvalidArgumentException; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class NamePrettifier -{ - /** - * @var string[] - */ - private $strings = []; - - /** - * @var bool - */ - private $useColor; - - public function __construct(bool $useColor = false) - { - $this->useColor = $useColor; - } - - /** - * Prettifies the name of a test class. - * - * @psalm-param class-string $className - */ - public function prettifyTestClass(string $className): string - { - try { - $annotations = Test::parseTestMethodAnnotations($className); - - if (isset($annotations['class']['testdox'][0])) { - return $annotations['class']['testdox'][0]; - } - } catch (UtilException $e) { - // ignore, determine className by parsing the provided name - } - - $parts = explode('\\', $className); - $className = array_pop($parts); - - if (substr($className, -1 * strlen('Test')) === 'Test') { - $className = substr($className, 0, strlen($className) - strlen('Test')); - } - - if (strpos($className, 'Tests') === 0) { - $className = substr($className, strlen('Tests')); - } elseif (strpos($className, 'Test') === 0) { - $className = substr($className, strlen('Test')); - } - - if (empty($className)) { - $className = 'UnnamedTests'; - } - - if (!empty($parts)) { - $parts[] = $className; - $fullyQualifiedName = implode('\\', $parts); - } else { - $fullyQualifiedName = $className; - } - - $result = preg_replace('/(?<=[[:lower:]])(?=[[:upper:]])/u', ' ', $className); - - if ($fullyQualifiedName !== $className) { - return $result . ' (' . $fullyQualifiedName . ')'; - } - - return $result; - } - - /** - * @throws InvalidArgumentException - */ - public function prettifyTestCase(TestCase $test): string - { - $annotations = Test::parseTestMethodAnnotations( - get_class($test), - $test->getName(false), - ); - - $annotationWithPlaceholders = false; - - $callback = static function (string $variable): string - { - return sprintf('/%s(?=\b)/', preg_quote($variable, '/')); - }; - - if (isset($annotations['method']['testdox'][0])) { - $result = $annotations['method']['testdox'][0]; - - if (strpos($result, '$') !== false) { - $annotation = $annotations['method']['testdox'][0]; - $providedData = $this->mapTestMethodParameterNamesToProvidedDataValues($test); - $variables = array_map($callback, array_keys($providedData)); - - $result = trim(preg_replace($variables, $providedData, $annotation)); - - $annotationWithPlaceholders = true; - } - } else { - $result = $this->prettifyTestMethod($test->getName(false)); - } - - if (!$annotationWithPlaceholders && $test->usesDataProvider()) { - $result .= $this->prettifyDataSet($test); - } - - return $result; - } - - public function prettifyDataSet(TestCase $test): string - { - if (!$this->useColor) { - return $test->getDataSetAsString(false); - } - - if (is_int($test->dataName())) { - $data = Color::dim(' with data set ') . Color::colorize('fg-cyan', (string) $test->dataName()); - } else { - $data = Color::dim(' with ') . Color::colorize('fg-cyan', Color::visualizeWhitespace((string) $test->dataName())); - } - - return $data; - } - - /** - * Prettifies the name of a test method. - */ - public function prettifyTestMethod(string $name): string - { - $buffer = ''; - - if ($name === '') { - return $buffer; - } - - $string = (string) preg_replace('#\d+$#', '', $name, -1, $count); - - if (in_array($string, $this->strings, true)) { - $name = $string; - } elseif ($count === 0) { - $this->strings[] = $string; - } - - if (strpos($name, 'test_') === 0) { - $name = substr($name, 5); - } elseif (strpos($name, 'test') === 0) { - $name = substr($name, 4); - } - - if ($name === '') { - return $buffer; - } - - $name[0] = strtoupper($name[0]); - - if (strpos($name, '_') !== false) { - return trim(str_replace('_', ' ', $name)); - } - - $wasNumeric = false; - - foreach (range(0, strlen($name) - 1) as $i) { - if ($i > 0 && ord($name[$i]) >= 65 && ord($name[$i]) <= 90) { - $buffer .= ' ' . strtolower($name[$i]); - } else { - $isNumeric = is_numeric($name[$i]); - - if (!$wasNumeric && $isNumeric) { - $buffer .= ' '; - $wasNumeric = true; - } - - if ($wasNumeric && !$isNumeric) { - $wasNumeric = false; - } - - $buffer .= $name[$i]; - } - } - - return $buffer; - } - - /** - * @throws InvalidArgumentException - */ - private function mapTestMethodParameterNamesToProvidedDataValues(TestCase $test): array - { - try { - $reflector = new ReflectionMethod(get_class($test), $test->getName(false)); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new UtilException( - $e->getMessage(), - $e->getCode(), - $e, - ); - } - // @codeCoverageIgnoreEnd - - $providedData = []; - $providedDataValues = array_values($test->getProvidedData()); - $i = 0; - - $providedData['$_dataName'] = $test->dataName(); - - foreach ($reflector->getParameters() as $parameter) { - if (!array_key_exists($i, $providedDataValues) && $parameter->isDefaultValueAvailable()) { - try { - $providedDataValues[$i] = $parameter->getDefaultValue(); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new UtilException( - $e->getMessage(), - $e->getCode(), - $e, - ); - } - // @codeCoverageIgnoreEnd - } - - $value = $providedDataValues[$i++] ?? null; - - if (is_object($value)) { - $reflector = new ReflectionObject($value); - - if ($reflector->hasMethod('__toString')) { - $value = (string) $value; - } else { - $value = get_class($value); - } - } - - if (!is_scalar($value)) { - $value = gettype($value); - } - - if (is_bool($value) || is_int($value) || is_float($value)) { - $value = (new Exporter)->export($value); - } - - if (is_string($value) && $value === '') { - if ($this->useColor) { - $value = Color::colorize('dim,underlined', 'empty'); - } else { - $value = "''"; - } - } - - $providedData['$' . $parameter->getName()] = $value; - } - - if ($this->useColor) { - $providedData = array_map(static function ($value) - { - return Color::colorize('fg-cyan', Color::visualizeWhitespace((string) $value, true)); - }, $providedData); - } - - return $providedData; - } -} diff --git a/app/vendor/phpunit/phpunit/src/Util/TestDox/ResultPrinter.php b/app/vendor/phpunit/phpunit/src/Util/TestDox/ResultPrinter.php deleted file mode 100644 index 841279e8e..000000000 --- a/app/vendor/phpunit/phpunit/src/Util/TestDox/ResultPrinter.php +++ /dev/null @@ -1,345 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util\TestDox; - -use function get_class; -use function in_array; -use PHPUnit\Framework\AssertionFailedError; -use PHPUnit\Framework\ErrorTestCase; -use PHPUnit\Framework\Exception; -use PHPUnit\Framework\Test; -use PHPUnit\Framework\TestCase; -use PHPUnit\Framework\TestSuite; -use PHPUnit\Framework\Warning; -use PHPUnit\Framework\WarningTestCase; -use PHPUnit\Runner\BaseTestRunner; -use PHPUnit\TextUI\ResultPrinter as ResultPrinterInterface; -use PHPUnit\Util\Printer; -use SebastianBergmann\RecursionContext\InvalidArgumentException; -use Throwable; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -abstract class ResultPrinter extends Printer implements ResultPrinterInterface -{ - /** - * @var NamePrettifier - */ - protected $prettifier; - - /** - * @var string - */ - protected $testClass = ''; - - /** - * @var int - */ - protected $testStatus; - - /** - * @var array - */ - protected $tests = []; - - /** - * @var int - */ - protected $successful = 0; - - /** - * @var int - */ - protected $warned = 0; - - /** - * @var int - */ - protected $failed = 0; - - /** - * @var int - */ - protected $risky = 0; - - /** - * @var int - */ - protected $skipped = 0; - - /** - * @var int - */ - protected $incomplete = 0; - - /** - * @var null|string - */ - protected $currentTestClassPrettified; - - /** - * @var null|string - */ - protected $currentTestMethodPrettified; - - /** - * @var array - */ - private $groups; - - /** - * @var array - */ - private $excludeGroups; - - /** - * @param resource $out - * - * @throws Exception - */ - public function __construct($out = null, array $groups = [], array $excludeGroups = []) - { - parent::__construct($out); - - $this->groups = $groups; - $this->excludeGroups = $excludeGroups; - - $this->prettifier = new NamePrettifier; - $this->startRun(); - } - - /** - * Flush buffer and close output. - */ - public function flush(): void - { - $this->doEndClass(); - $this->endRun(); - - parent::flush(); - } - - /** - * An error occurred. - */ - public function addError(Test $test, Throwable $t, float $time): void - { - if (!$this->isOfInterest($test)) { - return; - } - - $this->testStatus = BaseTestRunner::STATUS_ERROR; - $this->failed++; - } - - /** - * A warning occurred. - */ - public function addWarning(Test $test, Warning $e, float $time): void - { - if (!$this->isOfInterest($test)) { - return; - } - - $this->testStatus = BaseTestRunner::STATUS_WARNING; - $this->warned++; - } - - /** - * A failure occurred. - */ - public function addFailure(Test $test, AssertionFailedError $e, float $time): void - { - if (!$this->isOfInterest($test)) { - return; - } - - $this->testStatus = BaseTestRunner::STATUS_FAILURE; - $this->failed++; - } - - /** - * Incomplete test. - */ - public function addIncompleteTest(Test $test, Throwable $t, float $time): void - { - if (!$this->isOfInterest($test)) { - return; - } - - $this->testStatus = BaseTestRunner::STATUS_INCOMPLETE; - $this->incomplete++; - } - - /** - * Risky test. - */ - public function addRiskyTest(Test $test, Throwable $t, float $time): void - { - if (!$this->isOfInterest($test)) { - return; - } - - $this->testStatus = BaseTestRunner::STATUS_RISKY; - $this->risky++; - } - - /** - * Skipped test. - */ - public function addSkippedTest(Test $test, Throwable $t, float $time): void - { - if (!$this->isOfInterest($test)) { - return; - } - - $this->testStatus = BaseTestRunner::STATUS_SKIPPED; - $this->skipped++; - } - - /** - * A testsuite started. - */ - public function startTestSuite(TestSuite $suite): void - { - } - - /** - * A testsuite ended. - */ - public function endTestSuite(TestSuite $suite): void - { - } - - /** - * A test started. - * - * @throws InvalidArgumentException - */ - public function startTest(Test $test): void - { - if (!$this->isOfInterest($test)) { - return; - } - - $class = get_class($test); - - if ($this->testClass !== $class) { - if ($this->testClass !== '') { - $this->doEndClass(); - } - - $this->currentTestClassPrettified = $this->prettifier->prettifyTestClass($class); - $this->testClass = $class; - $this->tests = []; - - $this->startClass($class); - } - - if ($test instanceof TestCase) { - $this->currentTestMethodPrettified = $this->prettifier->prettifyTestCase($test); - } - - $this->testStatus = BaseTestRunner::STATUS_PASSED; - } - - /** - * A test ended. - */ - public function endTest(Test $test, float $time): void - { - if (!$this->isOfInterest($test)) { - return; - } - - $this->tests[] = [$this->currentTestMethodPrettified, $this->testStatus]; - - $this->currentTestClassPrettified = null; - $this->currentTestMethodPrettified = null; - } - - protected function doEndClass(): void - { - foreach ($this->tests as $test) { - $this->onTest($test[0], $test[1] === BaseTestRunner::STATUS_PASSED); - } - - $this->endClass($this->testClass); - } - - /** - * Handler for 'start run' event. - */ - protected function startRun(): void - { - } - - /** - * Handler for 'start class' event. - */ - protected function startClass(string $name): void - { - } - - /** - * Handler for 'on test' event. - */ - protected function onTest(string $name, bool $success = true): void - { - } - - /** - * Handler for 'end class' event. - */ - protected function endClass(string $name): void - { - } - - /** - * Handler for 'end run' event. - */ - protected function endRun(): void - { - } - - private function isOfInterest(Test $test): bool - { - if (!$test instanceof TestCase) { - return false; - } - - if ($test instanceof ErrorTestCase || $test instanceof WarningTestCase) { - return false; - } - - if (!empty($this->groups)) { - foreach ($test->getGroups() as $group) { - if (in_array($group, $this->groups, true)) { - return true; - } - } - - return false; - } - - if (!empty($this->excludeGroups)) { - foreach ($test->getGroups() as $group) { - if (in_array($group, $this->excludeGroups, true)) { - return false; - } - } - - return true; - } - - return true; - } -} diff --git a/app/vendor/phpunit/phpunit/src/Util/TestDox/TestDoxPrinter.php b/app/vendor/phpunit/phpunit/src/Util/TestDox/TestDoxPrinter.php deleted file mode 100644 index 9e1fdab1a..000000000 --- a/app/vendor/phpunit/phpunit/src/Util/TestDox/TestDoxPrinter.php +++ /dev/null @@ -1,392 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util\TestDox; - -use const PHP_EOL; -use function array_map; -use function get_class; -use function implode; -use function method_exists; -use function preg_split; -use function trim; -use PHPUnit\Framework\AssertionFailedError; -use PHPUnit\Framework\Exception; -use PHPUnit\Framework\Reorderable; -use PHPUnit\Framework\Test; -use PHPUnit\Framework\TestCase; -use PHPUnit\Framework\TestFailure; -use PHPUnit\Framework\TestResult; -use PHPUnit\Framework\TestSuite; -use PHPUnit\Framework\Warning; -use PHPUnit\Runner\BaseTestRunner; -use PHPUnit\Runner\PhptTestCase; -use PHPUnit\TextUI\DefaultResultPrinter; -use PHPUnit\Util\Filter; -use SebastianBergmann\RecursionContext\InvalidArgumentException; -use Throwable; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -class TestDoxPrinter extends DefaultResultPrinter -{ - /** - * @var NamePrettifier - */ - protected $prettifier; - - /** - * @var int The number of test results received from the TestRunner - */ - protected $testIndex = 0; - - /** - * @var int The number of test results already sent to the output - */ - protected $testFlushIndex = 0; - - /** - * @var array Buffer for test results - */ - protected $testResults = []; - - /** - * @var array Lookup table for testname to testResults[index] - */ - protected $testNameResultIndex = []; - - /** - * @var bool - */ - protected $enableOutputBuffer = false; - - /** - * @var array array - */ - protected $originalExecutionOrder = []; - - /** - * @var int - */ - protected $spinState = 0; - - /** - * @var bool - */ - protected $showProgress = true; - - /** - * @param null|resource|string $out - * @param int|string $numberOfColumns - * - * @throws Exception - */ - public function __construct($out = null, bool $verbose = false, string $colors = self::COLOR_DEFAULT, bool $debug = false, $numberOfColumns = 80, bool $reverse = false) - { - parent::__construct($out, $verbose, $colors, $debug, $numberOfColumns, $reverse); - - $this->prettifier = new NamePrettifier($this->colors); - } - - public function setOriginalExecutionOrder(array $order): void - { - $this->originalExecutionOrder = $order; - $this->enableOutputBuffer = !empty($order); - } - - public function setShowProgressAnimation(bool $showProgress): void - { - $this->showProgress = $showProgress; - } - - public function printResult(TestResult $result): void - { - } - - /** - * @throws InvalidArgumentException - */ - public function endTest(Test $test, float $time): void - { - if (!$test instanceof TestCase && !$test instanceof PhptTestCase && !$test instanceof TestSuite) { - return; - } - - if ($this->testHasPassed()) { - $this->registerTestResult($test, null, BaseTestRunner::STATUS_PASSED, $time, false); - } - - if ($test instanceof TestCase || $test instanceof PhptTestCase) { - $this->testIndex++; - } - - parent::endTest($test, $time); - } - - /** - * @throws InvalidArgumentException - */ - public function addError(Test $test, Throwable $t, float $time): void - { - $this->registerTestResult($test, $t, BaseTestRunner::STATUS_ERROR, $time, true); - } - - /** - * @throws InvalidArgumentException - */ - public function addWarning(Test $test, Warning $e, float $time): void - { - $this->registerTestResult($test, $e, BaseTestRunner::STATUS_WARNING, $time, true); - } - - /** - * @throws InvalidArgumentException - */ - public function addFailure(Test $test, AssertionFailedError $e, float $time): void - { - $this->registerTestResult($test, $e, BaseTestRunner::STATUS_FAILURE, $time, true); - } - - /** - * @throws InvalidArgumentException - */ - public function addIncompleteTest(Test $test, Throwable $t, float $time): void - { - $this->registerTestResult($test, $t, BaseTestRunner::STATUS_INCOMPLETE, $time, false); - } - - /** - * @throws InvalidArgumentException - */ - public function addRiskyTest(Test $test, Throwable $t, float $time): void - { - $this->registerTestResult($test, $t, BaseTestRunner::STATUS_RISKY, $time, false); - } - - /** - * @throws InvalidArgumentException - */ - public function addSkippedTest(Test $test, Throwable $t, float $time): void - { - $this->registerTestResult($test, $t, BaseTestRunner::STATUS_SKIPPED, $time, false); - } - - public function writeProgress(string $progress): void - { - $this->flushOutputBuffer(); - } - - public function flush(): void - { - $this->flushOutputBuffer(true); - } - - /** - * @throws InvalidArgumentException - */ - protected function registerTestResult(Test $test, ?Throwable $t, int $status, float $time, bool $verbose): void - { - $testName = $test instanceof Reorderable ? $test->sortId() : $test->getName(); - - $result = [ - 'className' => $this->formatClassName($test), - 'testName' => $testName, - 'testMethod' => $this->formatTestName($test), - 'message' => '', - 'status' => $status, - 'time' => $time, - 'verbose' => $verbose, - ]; - - if ($t !== null) { - $result['message'] = $this->formatTestResultMessage($t, $result); - } - - $this->testResults[$this->testIndex] = $result; - $this->testNameResultIndex[$testName] = $this->testIndex; - } - - protected function formatTestName(Test $test): string - { - return method_exists($test, 'getName') ? $test->getName() : ''; - } - - protected function formatClassName(Test $test): string - { - return get_class($test); - } - - protected function testHasPassed(): bool - { - if (!isset($this->testResults[$this->testIndex]['status'])) { - return true; - } - - if ($this->testResults[$this->testIndex]['status'] === BaseTestRunner::STATUS_PASSED) { - return true; - } - - return false; - } - - protected function flushOutputBuffer(bool $forceFlush = false): void - { - if ($this->testFlushIndex === $this->testIndex) { - return; - } - - if ($this->testFlushIndex > 0) { - if ($this->enableOutputBuffer && - isset($this->originalExecutionOrder[$this->testFlushIndex - 1])) { - $prevResult = $this->getTestResultByName($this->originalExecutionOrder[$this->testFlushIndex - 1]); - } else { - $prevResult = $this->testResults[$this->testFlushIndex - 1]; - } - } else { - $prevResult = $this->getEmptyTestResult(); - } - - if (!$this->enableOutputBuffer) { - $this->writeTestResult($prevResult, $this->testResults[$this->testFlushIndex++]); - } else { - do { - $flushed = false; - - if (!$forceFlush && isset($this->originalExecutionOrder[$this->testFlushIndex])) { - $result = $this->getTestResultByName($this->originalExecutionOrder[$this->testFlushIndex]); - } else { - // This test(name) cannot found in original execution order, - // flush result to output stream right away - $result = $this->testResults[$this->testFlushIndex]; - } - - if (!empty($result)) { - $this->hideSpinner(); - $this->writeTestResult($prevResult, $result); - $this->testFlushIndex++; - $prevResult = $result; - $flushed = true; - } else { - $this->showSpinner(); - } - } while ($flushed && $this->testFlushIndex < $this->testIndex); - } - } - - protected function showSpinner(): void - { - if (!$this->showProgress) { - return; - } - - if ($this->spinState) { - $this->undrawSpinner(); - } - - $this->spinState++; - $this->drawSpinner(); - } - - protected function hideSpinner(): void - { - if (!$this->showProgress) { - return; - } - - if ($this->spinState) { - $this->undrawSpinner(); - } - - $this->spinState = 0; - } - - protected function drawSpinner(): void - { - // optional for CLI printers: show the user a 'buffering output' spinner - } - - protected function undrawSpinner(): void - { - // remove the spinner from the current line - } - - protected function writeTestResult(array $prevResult, array $result): void - { - } - - protected function getEmptyTestResult(): array - { - return [ - 'className' => '', - 'testName' => '', - 'message' => '', - 'failed' => '', - 'verbose' => '', - ]; - } - - protected function getTestResultByName(?string $testName): array - { - if (isset($this->testNameResultIndex[$testName])) { - return $this->testResults[$this->testNameResultIndex[$testName]]; - } - - return []; - } - - protected function formatThrowable(Throwable $t, ?int $status = null): string - { - $message = trim(TestFailure::exceptionToString($t)); - - if ($message) { - $message .= PHP_EOL . PHP_EOL . $this->formatStacktrace($t); - } else { - $message = $this->formatStacktrace($t); - } - - return $message; - } - - protected function formatStacktrace(Throwable $t): string - { - return Filter::getFilteredStacktrace($t); - } - - protected function formatTestResultMessage(Throwable $t, array $result, string $prefix = '│'): string - { - $message = $this->formatThrowable($t, $result['status']); - - if ($message === '') { - return ''; - } - - if (!($this->verbose || $result['verbose'])) { - return ''; - } - - return $this->prefixLines($prefix, $message); - } - - protected function prefixLines(string $prefix, string $message): string - { - $message = trim($message); - - return implode( - PHP_EOL, - array_map( - static function (string $text) use ($prefix) - { - return ' ' . $prefix . ($text ? ' ' . $text : ''); - }, - preg_split('/\r\n|\r|\n/', $message), - ), - ); - } -} diff --git a/app/vendor/phpunit/phpunit/src/Util/TestDox/TextResultPrinter.php b/app/vendor/phpunit/phpunit/src/Util/TestDox/TextResultPrinter.php deleted file mode 100644 index 8a1893e55..000000000 --- a/app/vendor/phpunit/phpunit/src/Util/TestDox/TextResultPrinter.php +++ /dev/null @@ -1,52 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util\TestDox; - -use PHPUnit\Framework\TestResult; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class TextResultPrinter extends ResultPrinter -{ - public function printResult(TestResult $result): void - { - } - - /** - * Handler for 'start class' event. - */ - protected function startClass(string $name): void - { - $this->write($this->currentTestClassPrettified . "\n"); - } - - /** - * Handler for 'on test' event. - */ - protected function onTest(string $name, bool $success = true): void - { - if ($success) { - $this->write(' [x] '); - } else { - $this->write(' [ ] '); - } - - $this->write($name . "\n"); - } - - /** - * Handler for 'end class' event. - */ - protected function endClass(string $name): void - { - $this->write("\n"); - } -} diff --git a/app/vendor/phpunit/phpunit/src/Util/TestDox/XmlResultPrinter.php b/app/vendor/phpunit/phpunit/src/Util/TestDox/XmlResultPrinter.php deleted file mode 100644 index 10b7bff76..000000000 --- a/app/vendor/phpunit/phpunit/src/Util/TestDox/XmlResultPrinter.php +++ /dev/null @@ -1,262 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util\TestDox; - -use function array_filter; -use function get_class; -use function implode; -use function strpos; -use DOMDocument; -use DOMElement; -use PHPUnit\Framework\AssertionFailedError; -use PHPUnit\Framework\Exception; -use PHPUnit\Framework\Test; -use PHPUnit\Framework\TestCase; -use PHPUnit\Framework\TestListener; -use PHPUnit\Framework\TestSuite; -use PHPUnit\Framework\Warning; -use PHPUnit\Framework\WarningTestCase; -use PHPUnit\Util\Printer; -use PHPUnit\Util\Test as TestUtil; -use ReflectionClass; -use ReflectionException; -use SebastianBergmann\RecursionContext\InvalidArgumentException; -use Throwable; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class XmlResultPrinter extends Printer implements TestListener -{ - /** - * @var DOMDocument - */ - private $document; - - /** - * @var DOMElement - */ - private $root; - - /** - * @var NamePrettifier - */ - private $prettifier; - - /** - * @var null|Throwable - */ - private $exception; - - /** - * @param resource|string $out - * - * @throws Exception - */ - public function __construct($out = null) - { - $this->document = new DOMDocument('1.0', 'UTF-8'); - $this->document->formatOutput = true; - - $this->root = $this->document->createElement('tests'); - $this->document->appendChild($this->root); - - $this->prettifier = new NamePrettifier; - - parent::__construct($out); - } - - /** - * Flush buffer and close output. - */ - public function flush(): void - { - $this->write($this->document->saveXML()); - - parent::flush(); - } - - /** - * An error occurred. - */ - public function addError(Test $test, Throwable $t, float $time): void - { - $this->exception = $t; - } - - /** - * A warning occurred. - */ - public function addWarning(Test $test, Warning $e, float $time): void - { - } - - /** - * A failure occurred. - */ - public function addFailure(Test $test, AssertionFailedError $e, float $time): void - { - $this->exception = $e; - } - - /** - * Incomplete test. - */ - public function addIncompleteTest(Test $test, Throwable $t, float $time): void - { - } - - /** - * Risky test. - */ - public function addRiskyTest(Test $test, Throwable $t, float $time): void - { - } - - /** - * Skipped test. - */ - public function addSkippedTest(Test $test, Throwable $t, float $time): void - { - } - - /** - * A test suite started. - */ - public function startTestSuite(TestSuite $suite): void - { - } - - /** - * A test suite ended. - */ - public function endTestSuite(TestSuite $suite): void - { - } - - /** - * A test started. - */ - public function startTest(Test $test): void - { - $this->exception = null; - } - - /** - * A test ended. - * - * @throws InvalidArgumentException - */ - public function endTest(Test $test, float $time): void - { - if (!$test instanceof TestCase || $test instanceof WarningTestCase) { - return; - } - - $groups = array_filter( - $test->getGroups(), - static function ($group) - { - return !($group === 'small' || $group === 'medium' || $group === 'large' || strpos($group, '__phpunit_') === 0); - }, - ); - - $testNode = $this->document->createElement('test'); - - $testNode->setAttribute('className', get_class($test)); - $testNode->setAttribute('methodName', $test->getName()); - $testNode->setAttribute('prettifiedClassName', $this->prettifier->prettifyTestClass(get_class($test))); - $testNode->setAttribute('prettifiedMethodName', $this->prettifier->prettifyTestCase($test)); - $testNode->setAttribute('status', (string) $test->getStatus()); - $testNode->setAttribute('time', (string) $time); - $testNode->setAttribute('size', (string) $test->getSize()); - $testNode->setAttribute('groups', implode(',', $groups)); - - foreach ($groups as $group) { - $groupNode = $this->document->createElement('group'); - - $groupNode->setAttribute('name', $group); - - $testNode->appendChild($groupNode); - } - - $annotations = TestUtil::parseTestMethodAnnotations( - get_class($test), - $test->getName(false), - ); - - foreach (['class', 'method'] as $type) { - foreach ($annotations[$type] as $annotation => $values) { - if ($annotation !== 'covers' && $annotation !== 'uses') { - continue; - } - - foreach ($values as $value) { - $coversNode = $this->document->createElement($annotation); - - $coversNode->setAttribute('target', $value); - - $testNode->appendChild($coversNode); - } - } - } - - foreach ($test->doubledTypes() as $doubledType) { - $testDoubleNode = $this->document->createElement('testDouble'); - - $testDoubleNode->setAttribute('type', $doubledType); - - $testNode->appendChild($testDoubleNode); - } - - $inlineAnnotations = TestUtil::getInlineAnnotations(get_class($test), $test->getName(false)); - - if (isset($inlineAnnotations['given'], $inlineAnnotations['when'], $inlineAnnotations['then'])) { - $testNode->setAttribute('given', $inlineAnnotations['given']['value']); - $testNode->setAttribute('givenStartLine', (string) $inlineAnnotations['given']['line']); - $testNode->setAttribute('when', $inlineAnnotations['when']['value']); - $testNode->setAttribute('whenStartLine', (string) $inlineAnnotations['when']['line']); - $testNode->setAttribute('then', $inlineAnnotations['then']['value']); - $testNode->setAttribute('thenStartLine', (string) $inlineAnnotations['then']['line']); - } - - if ($this->exception !== null) { - if ($this->exception instanceof Exception) { - $steps = $this->exception->getSerializableTrace(); - } else { - $steps = $this->exception->getTrace(); - } - - try { - $file = (new ReflectionClass($test))->getFileName(); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - $e->getCode(), - $e, - ); - } - // @codeCoverageIgnoreEnd - - foreach ($steps as $step) { - if (isset($step['file']) && $step['file'] === $file) { - $testNode->setAttribute('exceptionLine', (string) $step['line']); - - break; - } - } - - $testNode->setAttribute('exceptionMessage', $this->exception->getMessage()); - } - - $this->root->appendChild($testNode); - } -} diff --git a/app/vendor/phpunit/phpunit/src/Util/TextTestListRenderer.php b/app/vendor/phpunit/phpunit/src/Util/TextTestListRenderer.php deleted file mode 100644 index c9f8af16b..000000000 --- a/app/vendor/phpunit/phpunit/src/Util/TextTestListRenderer.php +++ /dev/null @@ -1,55 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util; - -use const PHP_EOL; -use function get_class; -use function sprintf; -use function str_replace; -use PHPUnit\Framework\TestCase; -use PHPUnit\Framework\TestSuite; -use PHPUnit\Runner\PhptTestCase; -use RecursiveIteratorIterator; -use SebastianBergmann\RecursionContext\InvalidArgumentException; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class TextTestListRenderer -{ - /** - * @throws InvalidArgumentException - */ - public function render(TestSuite $suite): string - { - $buffer = 'Available test(s):' . PHP_EOL; - - foreach (new RecursiveIteratorIterator($suite->getIterator()) as $test) { - if ($test instanceof TestCase) { - $name = sprintf( - '%s::%s', - get_class($test), - str_replace(' with data set ', '', $test->getName()), - ); - } elseif ($test instanceof PhptTestCase) { - $name = $test->getName(); - } else { - continue; - } - - $buffer .= sprintf( - ' - %s' . PHP_EOL, - $name, - ); - } - - return $buffer; - } -} diff --git a/app/vendor/phpunit/phpunit/src/Util/ThrowableToStringMapper.php b/app/vendor/phpunit/phpunit/src/Util/ThrowableToStringMapper.php new file mode 100644 index 000000000..0fcf3695b --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Util/ThrowableToStringMapper.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util; + +use function trim; +use PHPUnit\Framework\ExpectationFailedException; +use PHPUnit\Framework\PhptAssertionFailedError; +use PHPUnit\Framework\SelfDescribing; +use PHPUnit\Runner\ErrorException; +use Throwable; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class ThrowableToStringMapper +{ + public static function map(Throwable $t): string + { + if ($t instanceof ErrorException) { + return $t->getMessage(); + } + + if ($t instanceof SelfDescribing) { + $buffer = $t->toString(); + + if ($t instanceof ExpectationFailedException && $t->getComparisonFailure() !== null) { + $buffer .= $t->getComparisonFailure()->getDiff(); + } + + if ($t instanceof PhptAssertionFailedError) { + $buffer .= $t->diff(); + } + + if ($buffer !== '') { + $buffer = trim($buffer) . "\n"; + } + + return $buffer; + } + + return $t::class . ': ' . $t->getMessage() . "\n"; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Util/Type.php b/app/vendor/phpunit/phpunit/src/Util/Type.php deleted file mode 100644 index ec6a1d78a..000000000 --- a/app/vendor/phpunit/phpunit/src/Util/Type.php +++ /dev/null @@ -1,39 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class Type -{ - public static function isType(string $type): bool - { - switch ($type) { - case 'numeric': - case 'integer': - case 'int': - case 'iterable': - case 'float': - case 'string': - case 'boolean': - case 'bool': - case 'null': - case 'array': - case 'object': - case 'resource': - case 'scalar': - return true; - - default: - return false; - } - } -} diff --git a/app/vendor/phpunit/phpunit/src/Util/VersionComparisonOperator.php b/app/vendor/phpunit/phpunit/src/Util/VersionComparisonOperator.php index 57ca7c320..9dcba3c32 100644 --- a/app/vendor/phpunit/phpunit/src/Util/VersionComparisonOperator.php +++ b/app/vendor/phpunit/phpunit/src/Util/VersionComparisonOperator.php @@ -10,20 +10,24 @@ namespace PHPUnit\Util; use function in_array; -use function sprintf; /** - * @internal This class is not covered by the backward compatibility promise for PHPUnit + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * - * @psalm-immutable + * @immutable */ -final class VersionComparisonOperator +final readonly class VersionComparisonOperator { /** - * @psalm-var '<'|'lt'|'<='|'le'|'>'|'gt'|'>='|'ge'|'=='|'='|'eq'|'!='|'<>'|'ne' + * @var '!='|'<'|'<='|'<>'|'='|'=='|'>'|'>='|'eq'|'ge'|'gt'|'le'|'lt'|'ne' */ - private $operator; + private string $operator; + /** + * @param '!='|'<'|'<='|'<>'|'='|'=='|'>'|'>='|'eq'|'ge'|'gt'|'le'|'lt'|'ne' $operator + * + * @throws InvalidVersionOperatorException + */ public function __construct(string $operator) { $this->ensureOperatorIsValid($operator); @@ -40,19 +44,14 @@ public function asString(): string } /** - * @throws Exception + * @param '!='|'<'|'<='|'<>'|'='|'=='|'>'|'>='|'eq'|'ge'|'gt'|'le'|'lt'|'ne' $operator * - * @psalm-assert '<'|'lt'|'<='|'le'|'>'|'gt'|'>='|'ge'|'=='|'='|'eq'|'!='|'<>'|'ne' $operator + * @throws InvalidVersionOperatorException */ private function ensureOperatorIsValid(string $operator): void { if (!in_array($operator, ['<', 'lt', '<=', 'le', '>', 'gt', '>=', 'ge', '==', '=', 'eq', '!=', '<>', 'ne'], true)) { - throw new Exception( - sprintf( - '"%s" is not a valid version_compare() operator', - $operator, - ), - ); + throw new InvalidVersionOperatorException($operator); } } } diff --git a/app/vendor/phpunit/phpunit/src/Util/XdebugFilterScriptGenerator.php b/app/vendor/phpunit/phpunit/src/Util/XdebugFilterScriptGenerator.php deleted file mode 100644 index d6366eaaa..000000000 --- a/app/vendor/phpunit/phpunit/src/Util/XdebugFilterScriptGenerator.php +++ /dev/null @@ -1,81 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util; - -use const DIRECTORY_SEPARATOR; -use function addslashes; -use function array_map; -use function implode; -use function is_string; -use function realpath; -use function sprintf; -use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\CodeCoverage as FilterConfiguration; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @deprecated - */ -final class XdebugFilterScriptGenerator -{ - public function generate(FilterConfiguration $filter): string - { - $files = array_map( - static function ($item) - { - return sprintf( - " '%s'", - $item, - ); - }, - $this->getItems($filter), - ); - - $files = implode(",\n", $files); - - return <<directories() as $directory) { - $path = realpath($directory->path()); - - if (is_string($path)) { - $files[] = sprintf( - addslashes('%s' . DIRECTORY_SEPARATOR), - $path, - ); - } - } - - foreach ($filter->files() as $file) { - $files[] = $file->path(); - } - - return $files; - } -} diff --git a/app/vendor/phpunit/phpunit/src/Util/Xml.php b/app/vendor/phpunit/phpunit/src/Util/Xml.php deleted file mode 100644 index efdd56eff..000000000 --- a/app/vendor/phpunit/phpunit/src/Util/Xml.php +++ /dev/null @@ -1,193 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util; - -use const ENT_QUOTES; -use function assert; -use function class_exists; -use function htmlspecialchars; -use function mb_convert_encoding; -use function ord; -use function preg_replace; -use function settype; -use function strlen; -use DOMCharacterData; -use DOMDocument; -use DOMElement; -use DOMNode; -use DOMText; -use ReflectionClass; -use ReflectionException; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class Xml -{ - /** - * @deprecated Only used by assertEqualXMLStructure() - */ - public static function import(DOMElement $element): DOMElement - { - return (new DOMDocument)->importNode($element, true); - } - - /** - * @deprecated Only used by assertEqualXMLStructure() - */ - public static function removeCharacterDataNodes(DOMNode $node): void - { - if ($node->hasChildNodes()) { - for ($i = $node->childNodes->length - 1; $i >= 0; $i--) { - if (($child = $node->childNodes->item($i)) instanceof DOMCharacterData) { - $node->removeChild($child); - } - } - } - } - - /** - * Escapes a string for the use in XML documents. - * - * Any Unicode character is allowed, excluding the surrogate blocks, FFFE, - * and FFFF (not even as character reference). - * - * @see https://www.w3.org/TR/xml/#charsets - */ - public static function prepareString(string $string): string - { - return preg_replace( - '/[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]/', - '', - htmlspecialchars( - self::convertToUtf8($string), - ENT_QUOTES, - ), - ); - } - - /** - * "Convert" a DOMElement object into a PHP variable. - */ - public static function xmlToVariable(DOMElement $element) - { - $variable = null; - - switch ($element->tagName) { - case 'array': - $variable = []; - - foreach ($element->childNodes as $entry) { - if (!$entry instanceof DOMElement || $entry->tagName !== 'element') { - continue; - } - $item = $entry->childNodes->item(0); - - if ($item instanceof DOMText) { - $item = $entry->childNodes->item(1); - } - - $value = self::xmlToVariable($item); - - if ($entry->hasAttribute('key')) { - $variable[(string) $entry->getAttribute('key')] = $value; - } else { - $variable[] = $value; - } - } - - break; - - case 'object': - $className = $element->getAttribute('class'); - - if ($element->hasChildNodes()) { - $arguments = $element->childNodes->item(0)->childNodes; - $constructorArgs = []; - - foreach ($arguments as $argument) { - if ($argument instanceof DOMElement) { - $constructorArgs[] = self::xmlToVariable($argument); - } - } - - try { - assert(class_exists($className)); - - $variable = (new ReflectionClass($className))->newInstanceArgs($constructorArgs); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new Exception( - $e->getMessage(), - $e->getCode(), - $e, - ); - } - // @codeCoverageIgnoreEnd - } else { - $variable = new $className; - } - - break; - - case 'boolean': - $variable = $element->textContent === 'true'; - - break; - - case 'integer': - case 'double': - case 'string': - $variable = $element->textContent; - - settype($variable, $element->tagName); - - break; - } - - return $variable; - } - - private static function convertToUtf8(string $string): string - { - if (!self::isUtf8($string)) { - $string = mb_convert_encoding($string, 'UTF-8'); - } - - return $string; - } - - private static function isUtf8(string $string): bool - { - $length = strlen($string); - - for ($i = 0; $i < $length; $i++) { - if (ord($string[$i]) < 0x80) { - $n = 0; - } elseif ((ord($string[$i]) & 0xE0) === 0xC0) { - $n = 1; - } elseif ((ord($string[$i]) & 0xF0) === 0xE0) { - $n = 2; - } elseif ((ord($string[$i]) & 0xF0) === 0xF0) { - $n = 3; - } else { - return false; - } - - for ($j = 0; $j < $n; $j++) { - if ((++$i === $length) || ((ord($string[$i]) & 0xC0) !== 0x80)) { - return false; - } - } - } - - return true; - } -} diff --git a/app/vendor/phpunit/phpunit/src/Util/Xml/Exception.php b/app/vendor/phpunit/phpunit/src/Util/Xml/Exception.php deleted file mode 100644 index 09b73d8fa..000000000 --- a/app/vendor/phpunit/phpunit/src/Util/Xml/Exception.php +++ /dev/null @@ -1,19 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util\Xml; - -use RuntimeException; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class Exception extends RuntimeException implements \PHPUnit\Exception -{ -} diff --git a/app/vendor/phpunit/phpunit/src/Util/Xml/FailedSchemaDetectionResult.php b/app/vendor/phpunit/phpunit/src/Util/Xml/FailedSchemaDetectionResult.php deleted file mode 100644 index 0949f5684..000000000 --- a/app/vendor/phpunit/phpunit/src/Util/Xml/FailedSchemaDetectionResult.php +++ /dev/null @@ -1,19 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util\Xml; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @psalm-immutable - */ -final class FailedSchemaDetectionResult extends SchemaDetectionResult -{ -} diff --git a/app/vendor/phpunit/phpunit/src/Util/Xml/Loader.php b/app/vendor/phpunit/phpunit/src/Util/Xml/Loader.php index 2ba5ace34..1289c253f 100644 --- a/app/vendor/phpunit/phpunit/src/Util/Xml/Loader.php +++ b/app/vendor/phpunit/phpunit/src/Util/Xml/Loader.php @@ -9,25 +9,25 @@ */ namespace PHPUnit\Util\Xml; -use function chdir; -use function dirname; use function error_reporting; use function file_get_contents; -use function getcwd; use function libxml_get_errors; use function libxml_use_internal_errors; use function sprintf; +use function trim; use DOMDocument; /** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ -final class Loader +final readonly class Loader { /** - * @throws Exception + * @throws XmlException */ - public function loadFile(string $filename, bool $isHtml = false, bool $xinclude = false, bool $strict = false): DOMDocument + public function loadFile(string $filename): DOMDocument { $reporting = error_reporting(0); $contents = file_get_contents($filename); @@ -35,30 +35,33 @@ public function loadFile(string $filename, bool $isHtml = false, bool $xinclude error_reporting($reporting); if ($contents === false) { - throw new Exception( + throw new XmlException( + sprintf( + 'Could not read XML from file "%s"', + $filename, + ), + ); + } + + if (trim($contents) === '') { + throw new XmlException( sprintf( - 'Could not read "%s".', + 'Could not parse XML from empty file "%s"', $filename, ), ); } - return $this->load($contents, $isHtml, $filename, $xinclude, $strict); + return $this->load($contents); } /** - * @throws Exception + * @throws XmlException */ - public function load(string $actual, bool $isHtml = false, string $filename = '', bool $xinclude = false, bool $strict = false): DOMDocument + public function load(string $actual): DOMDocument { if ($actual === '') { - throw new Exception('Could not load XML from empty string'); - } - - // Required for XInclude on Windows. - if ($xinclude) { - $cwd = getcwd(); - @chdir(dirname($filename)); + throw new XmlException('Could not parse XML from empty string'); } $document = new DOMDocument; @@ -67,21 +70,7 @@ public function load(string $actual, bool $isHtml = false, string $filename = '' $internal = libxml_use_internal_errors(true); $message = ''; $reporting = error_reporting(0); - - if ($filename !== '') { - // Required for XInclude - $document->documentURI = $filename; - } - - if ($isHtml) { - $loaded = $document->loadHTML($actual); - } else { - $loaded = $document->loadXML($actual); - } - - if (!$isHtml && $xinclude) { - $document->xinclude(); - } + $loaded = $document->loadXML($actual); foreach (libxml_get_errors() as $error) { $message .= "\n" . $error->message; @@ -90,26 +79,12 @@ public function load(string $actual, bool $isHtml = false, string $filename = '' libxml_use_internal_errors($internal); error_reporting($reporting); - if (isset($cwd)) { - @chdir($cwd); - } - - if ($loaded === false || ($strict && $message !== '')) { - if ($filename !== '') { - throw new Exception( - sprintf( - 'Could not load "%s".%s', - $filename, - $message !== '' ? "\n" . $message : '', - ), - ); - } - + if ($loaded === false) { if ($message === '') { $message = 'Could not load XML for unknown reason'; } - throw new Exception($message); + throw new XmlException($message); } return $document; diff --git a/app/vendor/phpunit/phpunit/src/Util/Xml/SchemaDetectionResult.php b/app/vendor/phpunit/phpunit/src/Util/Xml/SchemaDetectionResult.php deleted file mode 100644 index 3ae457231..000000000 --- a/app/vendor/phpunit/phpunit/src/Util/Xml/SchemaDetectionResult.php +++ /dev/null @@ -1,34 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util\Xml; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @psalm-immutable - */ -abstract class SchemaDetectionResult -{ - /** - * @psalm-assert-if-true SuccessfulSchemaDetectionResult $this - */ - public function detected(): bool - { - return false; - } - - /** - * @throws Exception - */ - public function version(): string - { - throw new Exception('No supported schema was detected'); - } -} diff --git a/app/vendor/phpunit/phpunit/src/Util/Xml/SchemaDetector.php b/app/vendor/phpunit/phpunit/src/Util/Xml/SchemaDetector.php deleted file mode 100644 index 1877a9a1f..000000000 --- a/app/vendor/phpunit/phpunit/src/Util/Xml/SchemaDetector.php +++ /dev/null @@ -1,41 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util\Xml; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class SchemaDetector -{ - /** - * @throws Exception - */ - public function detect(string $filename): SchemaDetectionResult - { - $document = (new Loader)->loadFile( - $filename, - false, - true, - true, - ); - - $schemaFinder = new SchemaFinder; - - foreach ($schemaFinder->available() as $candidate) { - $schema = (new SchemaFinder)->find($candidate); - - if (!(new Validator)->validate($document, $schema)->hasValidationErrors()) { - return new SuccessfulSchemaDetectionResult($candidate); - } - } - - return new FailedSchemaDetectionResult; - } -} diff --git a/app/vendor/phpunit/phpunit/src/Util/Xml/SuccessfulSchemaDetectionResult.php b/app/vendor/phpunit/phpunit/src/Util/Xml/SuccessfulSchemaDetectionResult.php deleted file mode 100644 index 77202c352..000000000 --- a/app/vendor/phpunit/phpunit/src/Util/Xml/SuccessfulSchemaDetectionResult.php +++ /dev/null @@ -1,47 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util\Xml; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @psalm-immutable - */ -final class SuccessfulSchemaDetectionResult extends SchemaDetectionResult -{ - /** - * @psalm-var non-empty-string - */ - private $version; - - /** - * @psalm-param non-empty-string $version - */ - public function __construct(string $version) - { - $this->version = $version; - } - - /** - * @psalm-assert-if-true SuccessfulSchemaDetectionResult $this - */ - public function detected(): bool - { - return true; - } - - /** - * @psalm-return non-empty-string - */ - public function version(): string - { - return $this->version; - } -} diff --git a/app/vendor/phpunit/phpunit/src/Util/Xml/ValidationResult.php b/app/vendor/phpunit/phpunit/src/Util/Xml/ValidationResult.php deleted file mode 100644 index a44b309ca..000000000 --- a/app/vendor/phpunit/phpunit/src/Util/Xml/ValidationResult.php +++ /dev/null @@ -1,70 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util\Xml; - -use const PHP_EOL; -use function sprintf; -use function trim; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - * - * @psalm-immutable - */ -final class ValidationResult -{ - /** - * @psalm-var array> - */ - private $validationErrors = []; - - /** - * @psalm-param array $errors - */ - public static function fromArray(array $errors): self - { - $validationErrors = []; - - foreach ($errors as $error) { - if (!isset($validationErrors[$error->line])) { - $validationErrors[$error->line] = []; - } - - $validationErrors[$error->line][] = trim($error->message); - } - - return new self($validationErrors); - } - - private function __construct(array $validationErrors) - { - $this->validationErrors = $validationErrors; - } - - public function hasValidationErrors(): bool - { - return !empty($this->validationErrors); - } - - public function asString(): string - { - $buffer = ''; - - foreach ($this->validationErrors as $line => $validationErrorsOnLine) { - $buffer .= sprintf(PHP_EOL . ' Line %d:' . PHP_EOL, $line); - - foreach ($validationErrorsOnLine as $validationError) { - $buffer .= sprintf(' - %s' . PHP_EOL, $validationError); - } - } - - return $buffer; - } -} diff --git a/app/vendor/phpunit/phpunit/src/Util/Xml/Validator.php b/app/vendor/phpunit/phpunit/src/Util/Xml/Validator.php deleted file mode 100644 index b3c4e05b1..000000000 --- a/app/vendor/phpunit/phpunit/src/Util/Xml/Validator.php +++ /dev/null @@ -1,35 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util\Xml; - -use function file_get_contents; -use function libxml_clear_errors; -use function libxml_get_errors; -use function libxml_use_internal_errors; -use DOMDocument; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class Validator -{ - public function validate(DOMDocument $document, string $xsdFilename): ValidationResult - { - $originalErrorHandling = libxml_use_internal_errors(true); - - $document->schemaValidateSource(file_get_contents($xsdFilename)); - - $errors = libxml_get_errors(); - libxml_clear_errors(); - libxml_use_internal_errors($originalErrorHandling); - - return ValidationResult::fromArray($errors); - } -} diff --git a/app/vendor/phpunit/phpunit/src/Util/Xml/Xml.php b/app/vendor/phpunit/phpunit/src/Util/Xml/Xml.php new file mode 100644 index 000000000..5e30bb43b --- /dev/null +++ b/app/vendor/phpunit/phpunit/src/Util/Xml/Xml.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace PHPUnit\Util; + +use const ENT_QUOTES; +use function htmlspecialchars; +use function mb_convert_encoding; +use function ord; +use function preg_replace; +use function strlen; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit + * + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final readonly class Xml +{ + /** + * Escapes a string for the use in XML documents. + * + * Any Unicode character is allowed, excluding the surrogate blocks, FFFE, + * and FFFF (not even as character reference). + * + * @see https://www.w3.org/TR/xml/#charsets + */ + public static function prepareString(string $string): string + { + return preg_replace( + '/[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]/', + '', + htmlspecialchars( + self::convertToUtf8($string), + ENT_QUOTES, + ), + ); + } + + private static function convertToUtf8(string $string): string + { + if (!self::isUtf8($string)) { + $string = mb_convert_encoding($string, 'UTF-8'); + } + + return $string; + } + + private static function isUtf8(string $string): bool + { + $length = strlen($string); + + for ($i = 0; $i < $length; $i++) { + if (ord($string[$i]) < 0x80) { + $n = 0; + } elseif ((ord($string[$i]) & 0xE0) === 0xC0) { + $n = 1; + } elseif ((ord($string[$i]) & 0xF0) === 0xE0) { + $n = 2; + } elseif ((ord($string[$i]) & 0xF0) === 0xF0) { + $n = 3; + } else { + return false; + } + + for ($j = 0; $j < $n; $j++) { + if ((++$i === $length) || ((ord($string[$i]) & 0xC0) !== 0x80)) { + return false; + } + } + } + + return true; + } +} diff --git a/app/vendor/phpunit/phpunit/src/Util/XmlTestListRenderer.php b/app/vendor/phpunit/phpunit/src/Util/XmlTestListRenderer.php deleted file mode 100644 index 2ed6559fe..000000000 --- a/app/vendor/phpunit/phpunit/src/Util/XmlTestListRenderer.php +++ /dev/null @@ -1,92 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace PHPUnit\Util; - -use function get_class; -use function implode; -use function str_replace; -use PHPUnit\Framework\TestCase; -use PHPUnit\Framework\TestSuite; -use PHPUnit\Runner\PhptTestCase; -use RecursiveIteratorIterator; -use SebastianBergmann\RecursionContext\InvalidArgumentException; -use XMLWriter; - -/** - * @internal This class is not covered by the backward compatibility promise for PHPUnit - */ -final class XmlTestListRenderer -{ - /** - * @throws InvalidArgumentException - */ - public function render(TestSuite $suite): string - { - $writer = new XMLWriter; - - $writer->openMemory(); - $writer->setIndent(true); - $writer->startDocument('1.0', 'UTF-8'); - $writer->startElement('tests'); - - $currentTestCase = null; - - foreach (new RecursiveIteratorIterator($suite->getIterator()) as $test) { - if ($test instanceof TestCase) { - if (get_class($test) !== $currentTestCase) { - if ($currentTestCase !== null) { - $writer->endElement(); - } - - $writer->startElement('testCaseClass'); - $writer->writeAttribute('name', get_class($test)); - - $currentTestCase = get_class($test); - } - - $writer->startElement('testCaseMethod'); - $writer->writeAttribute('name', $test->getName(false)); - $writer->writeAttribute('groups', implode(',', $test->getGroups())); - - if (!empty($test->getDataSetAsString(false))) { - $writer->writeAttribute( - 'dataSet', - str_replace( - ' with data set ', - '', - $test->getDataSetAsString(false), - ), - ); - } - - $writer->endElement(); - } elseif ($test instanceof PhptTestCase) { - if ($currentTestCase !== null) { - $writer->endElement(); - - $currentTestCase = null; - } - - $writer->startElement('phptFile'); - $writer->writeAttribute('path', $test->getName()); - $writer->endElement(); - } - } - - if ($currentTestCase !== null) { - $writer->endElement(); - } - - $writer->endElement(); - $writer->endDocument(); - - return $writer->outputMemory(); - } -} diff --git a/app/vendor/psr/clock/CHANGELOG.md b/app/vendor/psr/clock/CHANGELOG.md new file mode 100644 index 000000000..3cd6b9b75 --- /dev/null +++ b/app/vendor/psr/clock/CHANGELOG.md @@ -0,0 +1,11 @@ +# Changelog + +All notable changes to this project will be documented in this file, in reverse chronological order by release. + +## 1.0.0 + +First stable release after PSR-20 acceptance + +## 0.1.0 + +First release diff --git a/app/vendor/psr/clock/LICENSE b/app/vendor/psr/clock/LICENSE new file mode 100644 index 000000000..be6834212 --- /dev/null +++ b/app/vendor/psr/clock/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2017 PHP Framework Interoperability Group + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/app/vendor/psr/clock/README.md b/app/vendor/psr/clock/README.md new file mode 100644 index 000000000..6ca877eeb --- /dev/null +++ b/app/vendor/psr/clock/README.md @@ -0,0 +1,61 @@ +# PSR Clock + +This repository holds the interface for [PSR-20][psr-url]. + +Note that this is not a clock of its own. It is merely an interface that +describes a clock. See the specification for more details. + +## Installation + +```bash +composer require psr/clock +``` + +## Usage + +If you need a clock, you can use the interface like this: + +```php +clock = $clock; + } + + public function doSomething() + { + /** @var DateTimeImmutable $currentDateAndTime */ + $currentDateAndTime = $this->clock->now(); + // do something useful with that information + } +} +``` + +You can then pick one of the [implementations][implementation-url] of the interface to get a clock. + +If you want to implement the interface, you can require this package and +implement `Psr\Clock\ClockInterface` in your code. + +Don't forget to add `psr/clock-implementation` to your `composer.json`s `provides`-section like this: + +```json +{ + "provides": { + "psr/clock-implementation": "1.0" + } +} +``` + +And please read the [specification text][specification-url] for details on the interface. + +[psr-url]: https://www.php-fig.org/psr/psr-20 +[package-url]: https://packagist.org/packages/psr/clock +[implementation-url]: https://packagist.org/providers/psr/clock-implementation +[specification-url]: https://github.com/php-fig/fig-standards/blob/master/proposed/clock.md diff --git a/app/vendor/psr/clock/composer.json b/app/vendor/psr/clock/composer.json new file mode 100644 index 000000000..77992eda7 --- /dev/null +++ b/app/vendor/psr/clock/composer.json @@ -0,0 +1,21 @@ +{ + "name": "psr/clock", + "description": "Common interface for reading the clock.", + "keywords": ["psr", "psr-20", "time", "clock", "now"], + "homepage": "https://github.com/php-fig/clock", + "license": "MIT", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "require": { + "php": "^7.0 || ^8.0" + }, + "autoload": { + "psr-4": { + "Psr\\Clock\\": "src/" + } + } +} diff --git a/app/vendor/psr/clock/src/ClockInterface.php b/app/vendor/psr/clock/src/ClockInterface.php new file mode 100644 index 000000000..7b6d8d8aa --- /dev/null +++ b/app/vendor/psr/clock/src/ClockInterface.php @@ -0,0 +1,13 @@ +logger = $logger; } diff --git a/app/vendor/psr/log/src/LoggerInterface.php b/app/vendor/psr/log/src/LoggerInterface.php index b4d062b9b..cb4cf648b 100644 --- a/app/vendor/psr/log/src/LoggerInterface.php +++ b/app/vendor/psr/log/src/LoggerInterface.php @@ -22,12 +22,9 @@ interface LoggerInterface /** * System is unusable. * - * @param string|\Stringable $message * @param mixed[] $context - * - * @return void */ - public function emergency(string|\Stringable $message, array $context = []); + public function emergency(string|\Stringable $message, array $context = []): void; /** * Action must be taken immediately. @@ -35,35 +32,26 @@ public function emergency(string|\Stringable $message, array $context = []); * Example: Entire website down, database unavailable, etc. This should * trigger the SMS alerts and wake you up. * - * @param string|\Stringable $message * @param mixed[] $context - * - * @return void */ - public function alert(string|\Stringable $message, array $context = []); + public function alert(string|\Stringable $message, array $context = []): void; /** * Critical conditions. * * Example: Application component unavailable, unexpected exception. * - * @param string|\Stringable $message * @param mixed[] $context - * - * @return void */ - public function critical(string|\Stringable $message, array $context = []); + public function critical(string|\Stringable $message, array $context = []): void; /** * Runtime errors that do not require immediate action but should typically * be logged and monitored. * - * @param string|\Stringable $message * @param mixed[] $context - * - * @return void */ - public function error(string|\Stringable $message, array $context = []); + public function error(string|\Stringable $message, array $context = []): void; /** * Exceptional occurrences that are not errors. @@ -71,55 +59,40 @@ public function error(string|\Stringable $message, array $context = []); * Example: Use of deprecated APIs, poor use of an API, undesirable things * that are not necessarily wrong. * - * @param string|\Stringable $message * @param mixed[] $context - * - * @return void */ - public function warning(string|\Stringable $message, array $context = []); + public function warning(string|\Stringable $message, array $context = []): void; /** * Normal but significant events. * - * @param string|\Stringable $message * @param mixed[] $context - * - * @return void */ - public function notice(string|\Stringable $message, array $context = []); + public function notice(string|\Stringable $message, array $context = []): void; /** * Interesting events. * * Example: User logs in, SQL logs. * - * @param string|\Stringable $message * @param mixed[] $context - * - * @return void */ - public function info(string|\Stringable $message, array $context = []); + public function info(string|\Stringable $message, array $context = []): void; /** * Detailed debug information. * - * @param string|\Stringable $message * @param mixed[] $context - * - * @return void */ - public function debug(string|\Stringable $message, array $context = []); + public function debug(string|\Stringable $message, array $context = []): void; /** * Logs with an arbitrary level. * - * @param mixed $level - * @param string|\Stringable $message + * @param mixed $level * @param mixed[] $context * - * @return void - * * @throws \Psr\Log\InvalidArgumentException */ - public function log($level, string|\Stringable $message, array $context = []); + public function log($level, string|\Stringable $message, array $context = []): void; } diff --git a/app/vendor/psr/log/src/LoggerTrait.php b/app/vendor/psr/log/src/LoggerTrait.php index 920bda77f..a5d9980b6 100644 --- a/app/vendor/psr/log/src/LoggerTrait.php +++ b/app/vendor/psr/log/src/LoggerTrait.php @@ -14,13 +14,8 @@ trait LoggerTrait { /** * System is unusable. - * - * @param string|\Stringable $message - * @param array $context - * - * @return void */ - public function emergency(string|\Stringable $message, array $context = []) + public function emergency(string|\Stringable $message, array $context = []): void { $this->log(LogLevel::EMERGENCY, $message, $context); } @@ -30,13 +25,8 @@ public function emergency(string|\Stringable $message, array $context = []) * * Example: Entire website down, database unavailable, etc. This should * trigger the SMS alerts and wake you up. - * - * @param string|\Stringable $message - * @param array $context - * - * @return void */ - public function alert(string|\Stringable $message, array $context = []) + public function alert(string|\Stringable $message, array $context = []): void { $this->log(LogLevel::ALERT, $message, $context); } @@ -45,13 +35,8 @@ public function alert(string|\Stringable $message, array $context = []) * Critical conditions. * * Example: Application component unavailable, unexpected exception. - * - * @param string|\Stringable $message - * @param array $context - * - * @return void */ - public function critical(string|\Stringable $message, array $context = []) + public function critical(string|\Stringable $message, array $context = []): void { $this->log(LogLevel::CRITICAL, $message, $context); } @@ -59,13 +44,8 @@ public function critical(string|\Stringable $message, array $context = []) /** * Runtime errors that do not require immediate action but should typically * be logged and monitored. - * - * @param string|\Stringable $message - * @param array $context - * - * @return void */ - public function error(string|\Stringable $message, array $context = []) + public function error(string|\Stringable $message, array $context = []): void { $this->log(LogLevel::ERROR, $message, $context); } @@ -75,26 +55,16 @@ public function error(string|\Stringable $message, array $context = []) * * Example: Use of deprecated APIs, poor use of an API, undesirable things * that are not necessarily wrong. - * - * @param string|\Stringable $message - * @param array $context - * - * @return void */ - public function warning(string|\Stringable $message, array $context = []) + public function warning(string|\Stringable $message, array $context = []): void { $this->log(LogLevel::WARNING, $message, $context); } /** * Normal but significant events. - * - * @param string|\Stringable $message - * @param array $context - * - * @return void */ - public function notice(string|\Stringable $message, array $context = []) + public function notice(string|\Stringable $message, array $context = []): void { $this->log(LogLevel::NOTICE, $message, $context); } @@ -103,26 +73,16 @@ public function notice(string|\Stringable $message, array $context = []) * Interesting events. * * Example: User logs in, SQL logs. - * - * @param string|\Stringable $message - * @param array $context - * - * @return void */ - public function info(string|\Stringable $message, array $context = []) + public function info(string|\Stringable $message, array $context = []): void { $this->log(LogLevel::INFO, $message, $context); } /** * Detailed debug information. - * - * @param string|\Stringable $message - * @param array $context - * - * @return void */ - public function debug(string|\Stringable $message, array $context = []) + public function debug(string|\Stringable $message, array $context = []): void { $this->log(LogLevel::DEBUG, $message, $context); } @@ -130,13 +90,9 @@ public function debug(string|\Stringable $message, array $context = []) /** * Logs with an arbitrary level. * - * @param mixed $level - * @param string|\Stringable $message - * @param array $context - * - * @return void + * @param mixed $level * * @throws \Psr\Log\InvalidArgumentException */ - abstract public function log($level, string|\Stringable $message, array $context = []); + abstract public function log($level, string|\Stringable $message, array $context = []): void; } diff --git a/app/vendor/psr/log/src/NullLogger.php b/app/vendor/psr/log/src/NullLogger.php index 560770571..de0561e2a 100644 --- a/app/vendor/psr/log/src/NullLogger.php +++ b/app/vendor/psr/log/src/NullLogger.php @@ -15,15 +15,11 @@ class NullLogger extends AbstractLogger /** * Logs with an arbitrary level. * - * @param mixed $level - * @param string|\Stringable $message - * @param array $context - * - * @return void + * @param mixed[] $context * * @throws \Psr\Log\InvalidArgumentException */ - public function log($level, string|\Stringable $message, array $context = []) + public function log($level, string|\Stringable $message, array $context = []): void { // noop } diff --git a/app/vendor/psr/simple-cache/composer.json b/app/vendor/psr/simple-cache/composer.json index a520e7d57..f307a8456 100644 --- a/app/vendor/psr/simple-cache/composer.json +++ b/app/vendor/psr/simple-cache/composer.json @@ -19,7 +19,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "3.0.x-dev" } } } diff --git a/app/vendor/psr/simple-cache/src/CacheInterface.php b/app/vendor/psr/simple-cache/src/CacheInterface.php index cb05d2f1d..671e340c5 100644 --- a/app/vendor/psr/simple-cache/src/CacheInterface.php +++ b/app/vendor/psr/simple-cache/src/CacheInterface.php @@ -15,7 +15,7 @@ interface CacheInterface * @throws \Psr\SimpleCache\InvalidArgumentException * MUST be thrown if the $key string is not a legal value. */ - public function get(string $key, mixed $default = null); + public function get(string $key, mixed $default = null): mixed; /** * Persists data in the cache, uniquely referenced by a key with an optional expiration TTL time. @@ -31,7 +31,7 @@ public function get(string $key, mixed $default = null); * @throws \Psr\SimpleCache\InvalidArgumentException * MUST be thrown if the $key string is not a legal value. */ - public function set(string $key, mixed $value, null|int|\DateInterval $ttl = null); + public function set(string $key, mixed $value, null|int|\DateInterval $ttl = null): bool; /** * Delete an item from the cache by its unique key. @@ -43,14 +43,14 @@ public function set(string $key, mixed $value, null|int|\DateInterval $ttl = nul * @throws \Psr\SimpleCache\InvalidArgumentException * MUST be thrown if the $key string is not a legal value. */ - public function delete(string $key); + public function delete(string $key): bool; /** * Wipes clean the entire cache's keys. * * @return bool True on success and false on failure. */ - public function clear(); + public function clear(): bool; /** * Obtains multiple cache items by their unique keys. @@ -64,7 +64,7 @@ public function clear(); * MUST be thrown if $keys is neither an array nor a Traversable, * or if any of the $keys are not a legal value. */ - public function getMultiple(iterable $keys, mixed $default = null); + public function getMultiple(iterable $keys, mixed $default = null): iterable; /** * Persists a set of key => value pairs in the cache, with an optional TTL. @@ -80,7 +80,7 @@ public function getMultiple(iterable $keys, mixed $default = null); * MUST be thrown if $values is neither an array nor a Traversable, * or if any of the $values are not a legal value. */ - public function setMultiple(iterable $values, null|int|\DateInterval $ttl = null); + public function setMultiple(iterable $values, null|int|\DateInterval $ttl = null): bool; /** * Deletes multiple cache items in a single operation. @@ -93,7 +93,7 @@ public function setMultiple(iterable $values, null|int|\DateInterval $ttl = null * MUST be thrown if $keys is neither an array nor a Traversable, * or if any of the $keys are not a legal value. */ - public function deleteMultiple(iterable $keys); + public function deleteMultiple(iterable $keys): bool; /** * Determines whether an item is present in the cache. @@ -110,5 +110,5 @@ public function deleteMultiple(iterable $keys); * @throws \Psr\SimpleCache\InvalidArgumentException * MUST be thrown if the $key string is not a legal value. */ - public function has(string $key); + public function has(string $key): bool; } diff --git a/app/vendor/psy/psysh/composer.json b/app/vendor/psy/psysh/composer.json index 62295dc17..7e3ca5e83 100644 --- a/app/vendor/psy/psysh/composer.json +++ b/app/vendor/psy/psysh/composer.json @@ -3,13 +3,12 @@ "description": "An interactive shell for modern PHP.", "type": "library", "keywords": ["console", "interactive", "shell", "repl"], - "homepage": "http://psysh.org", + "homepage": "https://psysh.org", "license": "MIT", "authors": [ { "name": "Justin Hileman", - "email": "justin@justinhileman.info", - "homepage": "http://justinhileman.com" + "email": "justin@justinhileman.info" } ], "require": { diff --git a/app/vendor/psy/psysh/src/Command/ListCommand/PropertyEnumerator.php b/app/vendor/psy/psysh/src/Command/ListCommand/PropertyEnumerator.php index 17661455a..fe4a4b81f 100644 --- a/app/vendor/psy/psysh/src/Command/ListCommand/PropertyEnumerator.php +++ b/app/vendor/psy/psysh/src/Command/ListCommand/PropertyEnumerator.php @@ -168,7 +168,9 @@ protected function presentValue(\ReflectionProperty $property, $target): string return ''; } - $property->setAccessible(true); + if (\PHP_VERSION_ID < 80100) { + $property->setAccessible(true); + } $value = $property->getValue($target); return $this->presentRef($value); diff --git a/app/vendor/psy/psysh/src/Shell.php b/app/vendor/psy/psysh/src/Shell.php index 665fa0bb4..edb516340 100644 --- a/app/vendor/psy/psysh/src/Shell.php +++ b/app/vendor/psy/psysh/src/Shell.php @@ -53,7 +53,7 @@ */ class Shell extends Application { - const VERSION = 'v0.12.9'; + const VERSION = 'v0.12.10'; private Configuration $config; private CodeCleaner $cleaner; diff --git a/app/vendor/psy/psysh/src/Sudo.php b/app/vendor/psy/psysh/src/Sudo.php index 54016f933..329f3f56e 100644 --- a/app/vendor/psy/psysh/src/Sudo.php +++ b/app/vendor/psy/psysh/src/Sudo.php @@ -62,7 +62,9 @@ public static function callMethod($object, string $method, ...$args) { $refl = new \ReflectionObject($object); $reflMethod = $refl->getMethod($method); - $reflMethod->setAccessible(true); + if (\PHP_VERSION_ID < 80100) { + $reflMethod->setAccessible(true); + } return $reflMethod->invokeArgs($object, $args); } @@ -78,7 +80,9 @@ public static function callMethod($object, string $method, ...$args) public static function fetchStaticProperty($class, string $property) { $prop = self::getProperty(new \ReflectionClass($class), $property); - $prop->setAccessible(true); + if (\PHP_VERSION_ID < 80100) { + $prop->setAccessible(true); + } return $prop->getValue(); } @@ -119,7 +123,9 @@ public static function callStatic($class, string $method, ...$args) { $refl = new \ReflectionClass($class); $reflMethod = $refl->getMethod($method); - $reflMethod->setAccessible(true); + if (\PHP_VERSION_ID < 80100) { + $reflMethod->setAccessible(true); + } return $reflMethod->invokeArgs(null, $args); } @@ -164,7 +170,9 @@ public static function newInstance(string $class, ...$args) $instance = $refl->newInstanceWithoutConstructor(); $constructor = $refl->getConstructor(); - $constructor->setAccessible(true); + if (\PHP_VERSION_ID < 80100) { + $constructor->setAccessible(true); + } $constructor->invokeArgs($instance, $args); return $instance; @@ -186,7 +194,9 @@ private static function getProperty(\ReflectionClass $refl, string $property): \ do { try { $prop = $refl->getProperty($property); - $prop->setAccessible(true); + if (\PHP_VERSION_ID < 80100) { + $prop->setAccessible(true); + } return $prop; } catch (\ReflectionException $e) { diff --git a/app/vendor/psy/psysh/src/TabCompletion/Matcher/FunctionsMatcher.php b/app/vendor/psy/psysh/src/TabCompletion/Matcher/FunctionsMatcher.php index 964565997..2642a09e8 100644 --- a/app/vendor/psy/psysh/src/TabCompletion/Matcher/FunctionsMatcher.php +++ b/app/vendor/psy/psysh/src/TabCompletion/Matcher/FunctionsMatcher.php @@ -44,7 +44,7 @@ public function hasMatched(array $tokens): bool $prevToken = \array_pop($tokens); switch (true) { - case self::tokenIs($prevToken, self::T_NEW): + case self::hasToken([self::T_NEW, self::T_OBJECT_OPERATOR], $prevToken): return false; case self::hasToken([self::T_OPEN_TAG, self::T_STRING], $token): case self::isOperator($token): diff --git a/app/vendor/react/promise/composer.json b/app/vendor/react/promise/composer.json index 5d1e27710..6bf687dd3 100644 --- a/app/vendor/react/promise/composer.json +++ b/app/vendor/react/promise/composer.json @@ -28,7 +28,7 @@ "php": ">=7.1.0" }, "require-dev": { - "phpstan/phpstan": "1.10.39 || 1.4.10", + "phpstan/phpstan": "1.12.28 || 1.4.10", "phpunit/phpunit": "^9.6 || ^7.5" }, "autoload": { diff --git a/app/vendor/react/promise/src/Internal/FulfilledPromise.php b/app/vendor/react/promise/src/Internal/FulfilledPromise.php index 8664ffdb6..8a66cfff9 100644 --- a/app/vendor/react/promise/src/Internal/FulfilledPromise.php +++ b/app/vendor/react/promise/src/Internal/FulfilledPromise.php @@ -59,6 +59,7 @@ public function catch(callable $onRejected): PromiseInterface public function finally(callable $onFulfilledOrRejected): PromiseInterface { return $this->then(function ($value) use ($onFulfilledOrRejected): PromiseInterface { + /** @var T $value */ return resolve($onFulfilledOrRejected())->then(function () use ($value) { return $value; }); diff --git a/app/vendor/react/promise/src/Promise.php b/app/vendor/react/promise/src/Promise.php index 4ac27007c..08286747b 100644 --- a/app/vendor/react/promise/src/Promise.php +++ b/app/vendor/react/promise/src/Promise.php @@ -97,6 +97,7 @@ public function catch(callable $onRejected): PromiseInterface public function finally(callable $onFulfilledOrRejected): PromiseInterface { return $this->then(static function ($value) use ($onFulfilledOrRejected): PromiseInterface { + /** @var T $value */ return resolve($onFulfilledOrRejected())->then(function () use ($value) { return $value; }); diff --git a/app/vendor/react/promise/src/functions.php b/app/vendor/react/promise/src/functions.php index 2aab877e3..214aad6dc 100644 --- a/app/vendor/react/promise/src/functions.php +++ b/app/vendor/react/promise/src/functions.php @@ -297,7 +297,7 @@ function _checkTypehint(callable $callback, \Throwable $reason): bool break; case $type instanceof \ReflectionIntersectionType: $isTypeUnion = false; - case $type instanceof \ReflectionUnionType; + case $type instanceof \ReflectionUnionType: $types = $type->getTypes(); break; default: diff --git a/app/vendor/robmorgan/phinx/LICENSE b/app/vendor/robmorgan/phinx/LICENSE index 983a90682..54619b91f 100644 --- a/app/vendor/robmorgan/phinx/LICENSE +++ b/app/vendor/robmorgan/phinx/LICENSE @@ -1,9 +1,23 @@ (The MIT license) -Copyright (c) 2012-present Rob Morgan +Copyright (c) 2012-2017 Rob Morgan +Copyright (c) 2017-present, Cake Software Foundation, Inc. (https://cakefoundation.org) -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +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 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. +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/app/vendor/robmorgan/phinx/README.md b/app/vendor/robmorgan/phinx/README.md index 410ef9902..e9bcc2ecf 100644 --- a/app/vendor/robmorgan/phinx/README.md +++ b/app/vendor/robmorgan/phinx/README.md @@ -1,16 +1,16 @@ # [Phinx](https://phinx.org): Simple PHP Database Migrations -[![Build Status](https://github.com/cakephp/phinx/workflows/CI/badge.svg?branch=master&event=push)](https://github.com/cakephp/phinx/actions?query=workflow%3A%22CI%22+branch%3Amaster+event%3Apush) +[![CI](https://github.com/cakephp/phinx/actions/workflows/ci.yml/badge.svg?branch=0.x&event=push)](https://github.com/cakephp/phinx/actions/workflows/ci.yml) [![Code Coverage](https://codecov.io/gh/cakephp/phinx/branch/master/graph/badge.svg)](https://codecov.io/gh/cakephp/phinx) -[![Latest Stable Version](https://poser.pugx.org/robmorgan/phinx/version.png)](https://packagist.org/packages/robmorgan/phinx) -[![Minimum PHP Version](https://img.shields.io/badge/php-%3E%3D%207.2-8892BF.svg)](https://php.net/) -[![Total Downloads](https://poser.pugx.org/robmorgan/phinx/d/total.png)](https://packagist.org/packages/robmorgan/phinx) +![Packagist Version](https://img.shields.io/packagist/v/robmorgan/phinx) +[![Minimum PHP Version](https://img.shields.io/badge/php-%3E%3D%208.1-8892BF.svg)](https://php.net/) +![Packagist Downloads](https://img.shields.io/packagist/dt/robmorgan/phinx) ## Intro Phinx makes it ridiculously easy to manage the database migrations for your PHP app. In less than 5 minutes, you can install Phinx and create your first database migration. Phinx is just about migrations without all the bloat of a database ORM system or framework. -**Check out [book.cakephp.org/phinx](https://book.cakephp.org/phinx) ([EN](https://book.cakephp.org/phinx), [ZH](https://tsy12321.gitbooks.io/phinx-doc/)) for the comprehensive documentation.** +**Check out [book.cakephp.org/phinx](https://book.cakephp.org/phinx) for the comprehensive documentation.** ![phinxterm](https://cloud.githubusercontent.com/assets/178939/3887559/e6b5e524-21f2-11e4-8256-0ba6040725fc.gif) diff --git a/app/vendor/robmorgan/phinx/composer.json b/app/vendor/robmorgan/phinx/composer.json index 18b07b3b2..4fa95419e 100644 --- a/app/vendor/robmorgan/phinx/composer.json +++ b/app/vendor/robmorgan/phinx/composer.json @@ -36,19 +36,20 @@ } ], "require": { - "php": ">=7.2", - "cakephp/database": "^4.0", - "psr/container": "^1.0 || ^2.0", - "symfony/console": "^3.4|^4.0|^5.0|^6.0", - "symfony/config": "^3.4|^4.0|^5.0|^6.0" + "php-64bit": ">=8.1", + "composer-runtime-api": "^2.0", + "cakephp/database": "^5.0.2", + "psr/container": "^1.1|^2.0", + "symfony/config": "^4.0|^5.0|^6.0|^7.0", + "symfony/console": "^6.0|^7.0" }, "require-dev": { "ext-json": "*", "ext-pdo": "*", - "phpunit/phpunit": "^8.5|^9.3", - "sebastian/comparator": ">=1.2.3", - "cakephp/cakephp-codesniffer": "^4.0", - "symfony/yaml": "^3.4|^4.0|^5.0" + "cakephp/cakephp-codesniffer": "^5.0", + "cakephp/i18n": "^5.0", + "phpunit/phpunit": "^9.5.19", + "symfony/yaml": "^4.0|^5.0|^6.0|^7.0" }, "autoload": { "psr-4": { @@ -74,11 +75,15 @@ "cs-fix": "phpcbf", "stan": "phpstan analyse", "stan-setup": "cp composer.json composer.backup && composer require --dev phpstan/phpstan:~1.9.0 && mv composer.backup composer.json", + "lowest": "validate-prefer-lowest", + "lowest-setup": "composer update --prefer-lowest --prefer-stable --prefer-dist --no-interaction && cp composer.json composer.backup && composer require --dev dereuromark/composer-prefer-lowest && mv composer.backup composer.json", "test": "phpunit --colors=always" }, "bin": [ "bin/phinx" ], + "minimum-stability": "dev", + "prefer-stable": true, "config": { "allow-plugins": { "dealerdirect/phpcodesniffer-composer-installer": true diff --git a/app/vendor/robmorgan/phinx/docs/config/all.py b/app/vendor/robmorgan/phinx/docs/config/all.py index caebcd1e8..9fd53bce4 100644 --- a/app/vendor/robmorgan/phinx/docs/config/all.py +++ b/app/vendor/robmorgan/phinx/docs/config/all.py @@ -8,7 +8,7 @@ from cakephpsphinx.config.all import * # The full version, including alpha/beta/rc tags. -release = '0.13.x' +release = '0.16.x' # The search index version. search_version = 'phinx-0' @@ -21,7 +21,7 @@ # Other versions that display in the version picker menu. version_list = [ - {'name': '0.13', 'number': '/phinx/0', 'title': '0.13', 'current': True}, + {'name': '0.16', 'number': '/phinx/0', 'title': '0.16', 'current': True}, ] # Languages available. @@ -32,7 +32,7 @@ branch = '0.x' # Current version being built -version = '0.13' +version = '0.16' show_root_link = True diff --git a/app/vendor/robmorgan/phinx/docs/en/commands.rst b/app/vendor/robmorgan/phinx/docs/en/commands.rst index e93d7a7dc..7cedbf703 100644 --- a/app/vendor/robmorgan/phinx/docs/en/commands.rst +++ b/app/vendor/robmorgan/phinx/docs/en/commands.rst @@ -394,18 +394,23 @@ using the Manager class : $pdo = new PDO('sqlite::memory:', null, null, [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION ]); - $configArray = require('phinx.php'); + $configPath = __DIR__ . '/../phinx.php'; + $configArray = require $configPath; $configArray['environments']['test'] = [ 'adapter' => 'sqlite', - 'connection' => $pdo + 'connection' => $pdo, + 'name' => ':memory:', ]; - $config = new Config($configArray); + $config = new Config( + $configArray, + $configPath + ); $manager = new Manager($config, new StringInput(' '), new NullOutput()); $manager->migrate('test'); $manager->seed('test'); + $this->pdo = $pdo; // You can change default fetch mode after the seeding $this->pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ); - $this->pdo = $pdo; } } diff --git a/app/vendor/robmorgan/phinx/docs/en/configuration.rst b/app/vendor/robmorgan/phinx/docs/en/configuration.rst index d6271bf05..d577c092e 100644 --- a/app/vendor/robmorgan/phinx/docs/en/configuration.rst +++ b/app/vendor/robmorgan/phinx/docs/en/configuration.rst @@ -76,6 +76,16 @@ You can also provide multiple migration paths by using an array in your configur - application/module1/migrations - application/module2/migrations +Class namespaces may be specified by adding a key to each migration path: + +.. code-block:: yaml + + paths: + migrations: + App\Module1\Migrations: application/module1/migrations + App\Module2\Migrations: application/module2/migrations + + You can also use the ``%%PHINX_CONFIG_DIR%%`` token in your path. @@ -139,6 +149,16 @@ You can also use the ``%%PHINX_CONFIG_DIR%%`` token in your path. paths: seeds: '%%PHINX_CONFIG_DIR%%/your/relative/path' +Class namespaces may be specified by adding a key to each seed path: + +.. code-block:: yaml + + paths: + seeds: + App\Module1\Seeds: application/module1/seeds + App\Module2\Seeds: application/module2/seeds + + Custom Seeder Base --------------------- @@ -364,6 +384,26 @@ Phinx currently supports the following database adapters natively: * `SQLite `_: specify the ``sqlite`` adapter. * `SQL Server `_: specify the ``sqlsrv`` adapter. +The following settings are available for the adapters: + +adapter + The name of the adapter to use, e.g. ``pgsql``. +host + The database server's hostname (or IP address). +port + The database server's TCP port number. +user + The username for the database. +pass + The password for the database. +name + The name of the database for this environment. For SQLite, it's recommended to use an absolute path, + without the file extension. +suffix + The suffix to use for the SQLite database file. Defaults to ``.sqlite3``. +schema + For PostgreSQL, allows specifying the schema to use for the database. Defaults to ``public``. + For each adapter, you may configure the behavior of the underlying PDO object by setting in your config object the lowercase version of the constant name. This works for both PDO options (e.g. ``\PDO::ATTR_CASE`` would be ``attr_case``) and adapter specific options (e.g. for MySQL @@ -392,7 +432,7 @@ not recommended to override this. MySQL ````````````````` -The MySQL adapter has an unfortunate limitation in that it certain actions causes an +The MySQL adapter has an unfortunate limitation in that certain actions cause an `implicit commit `_ regardless of transaction state. Notably this list includes ``CREATE TABLE``, ``ALTER TABLE``, and ``DROP TABLE``, which are the most common operations that Phinx will run. This means that unlike other adapters which will attempt to gracefully @@ -493,7 +533,7 @@ When rolling back or printing the status of migrations, Phinx orders the execute Bootstrap Path --------------- -You can provide a path to a `bootstrap` php file that will included before any commands phinx commands are run. Note that +You can provide a path to a `bootstrap` php file that will be included before any phinx commands are run. Note that setting External Variables to modify the config will not work because the config has already been parsed by this point. .. code-block:: yaml @@ -521,6 +561,16 @@ For some breaking changes, Phinx offers a way to opt-out of new behavior. The fo * ``unsigned_primary_keys``: Should Phinx create primary keys as unsigned integers? (default: ``true``) * ``column_null_default``: Should Phinx create columns as null by default? (default: ``true``) +Since MySQL ``TIMESTAMP`` fields do not support dates past 2038-01-19, you have the option to use ``DATETIME`` field +types for fields created by the ``addTimestamps()`` function: + +* ``add_timestamps_use_datetime``: Should Phinx create created_at and updated_at fields as datetime? (default: ``false``) + +.. code-block:: yaml + + feature_flags: + unsigned_primary_keys: false + These values can also be set by modifying class fields on the ```Phinx\Config\FeatureFlags``` class, converting the flag name to ``camelCase``, for example: diff --git a/app/vendor/robmorgan/phinx/docs/en/index.rst b/app/vendor/robmorgan/phinx/docs/en/index.rst index 530c8c965..c17323068 100644 --- a/app/vendor/robmorgan/phinx/docs/en/index.rst +++ b/app/vendor/robmorgan/phinx/docs/en/index.rst @@ -3,7 +3,12 @@ Phinx Documentation =================== -Phinx makes it ridiculously easy to manage the database migrations for your PHP app. In less than 5 minutes, you can install Phinx using Composer and create your first database migration. Phinx is just about migrations without all the bloat of a database ORM system or application framework. +Phinx makes it ridiculously easy to manage the database migrations for your PHP +app. In less than 5 minutes, you can install Phinx using Composer and create +your first database migration. Phinx is just about migrations without all the +bloat of a database ORM system or application framework. + +Phinx requires a 64-bit version of PHP to function. Contents ======== diff --git a/app/vendor/robmorgan/phinx/docs/en/install.rst b/app/vendor/robmorgan/phinx/docs/en/install.rst index 66e1f8fed..10078c7a1 100644 --- a/app/vendor/robmorgan/phinx/docs/en/install.rst +++ b/app/vendor/robmorgan/phinx/docs/en/install.rst @@ -10,7 +10,7 @@ website for more information. .. note:: - Phinx requires at least PHP 7.2 (or later). + Phinx requires at least PHP 8.1 (or later). To install Phinx, simply require it using Composer: diff --git a/app/vendor/robmorgan/phinx/docs/en/migrations.rst b/app/vendor/robmorgan/phinx/docs/en/migrations.rst index 4ebb074f3..4c34b768f 100644 --- a/app/vendor/robmorgan/phinx/docs/en/migrations.rst +++ b/app/vendor/robmorgan/phinx/docs/en/migrations.rst @@ -778,7 +778,7 @@ Option Description limit set maximum length for strings, also hints column types in adapters (see note below) length alias for ``limit`` default set default value or action -null allow ``NULL`` values, defaults to false if `identity` option is set to true, else defaults to true +null allow ``NULL`` values, defaults to ``true`` (setting ``identity`` will override default to ``false``) after specify the column that a new column should be placed after, or use ``\Phinx\Db\Adapter\MysqlAdapter::FIRST`` to place the column at the start of the table *(only applies to MySQL)* comment set a text comment on the column ======= =========== @@ -806,7 +806,7 @@ For ``smallinteger``, ``integer`` and ``biginteger`` columns: ======== =========== Option Description ======== =========== -identity enable or disable automatic incrementing +identity enable or disable automatic incrementing (if enabled, will set ``null: false`` if ``null`` option is not set) signed enable or disable the ``unsigned`` option *(only applies to MySQL)* ======== =========== @@ -892,6 +892,7 @@ Option Description update set an action to be triggered when the row is updated delete set an action to be triggered when the row is deleted constraint set a name to be used by foreign key constraint +deferrable set the foreign key constraint to be deferrable *(only applies to PostgreSQL)* ========== =========== You can pass one or more of these options to any column with the optional @@ -902,11 +903,8 @@ Limit Option and MySQL When using the MySQL adapter, there are a couple things to consider when working with limits: -- When using a ``string`` primary key or index on MySQL 5.7 or below and the default charset of ``utf8mb4_unicode_ci``, you -must specify a limit less than or equal to 191, or use a different charset. -- Additional hinting of database column type can be -made for ``integer``, ``text``, ``blob``, ``tinyblob``, ``mediumblob``, ``longblob`` columns. Using ``limit`` with -one the following options will modify the column type accordingly: +- When using a ``string`` primary key or index on MySQL 5.7 or below, or the MyISAM storage engine, and the default charset of ``utf8mb4_unicode_ci``, you must specify a limit less than or equal to 191, or use a different charset. +- Additional hinting of database column type can be made for ``integer``, ``text``, ``blob``, ``tinyblob``, ``mediumblob``, ``longblob`` columns. Using ``limit`` with one the following options will modify the column type accordingly: ============ ============== Limit Column Type @@ -1195,7 +1193,7 @@ where its value is the name of the column to position it after. } } -This would create the new column ``city`` and position it after the ``email`` column. The +This would create the new column ``city`` and position it after the ``email`` column. The ``\Phinx\Db\Adapter\MysqlAdapter::FIRST`` constant can be used to specify that the new column should be created as the first column in that table. @@ -1581,6 +1579,31 @@ We can add named foreign keys using the ``constraint`` parameter. This feature i } } +For PostgreSQL, you can set if the foreign key is deferrable. The available options are ``DEFERRED`` (corresponds to +``DEFERRABLE INITIALLY DEFERRED``), ``IMMEDIATE`` (corresponds to ``DEFERRABLE INITIALLY IMMEDIATE``), and ``NOT_DEFERRED`` +(corresponds to ``NOT DEFERRABLE``). + +.. code-block:: php + + table('phones'); + $table->addColumn('name', 'string') + ->addColumn('manufacturer_name', 'string') + ->addForeignKey('manufacturer_name', + 'manufacturers ', + 'name', + ['deferrable' => 'DEFERRED']) + ->save(); + } + } + We can also easily check if a foreign key exists: .. code-block:: php @@ -1656,7 +1679,7 @@ Phinx provides access to a Query builder object, that you may use to execute com The Query builder is provided by the `cakephp/database `_ project, and should be easy to work with as it resembles very closely plain SQL. Accesing the query builder is done by calling the -``getQueryBuilder()`` function: +``getQueryBuilder(string $type)`` function. The ``string $type`` options are `'select'`, `'insert'`, `'update'` and `'delete'`: .. code-block:: php @@ -1672,12 +1695,40 @@ be easy to work with as it resembles very closely plain SQL. Accesing the query */ public function up() { - $builder = $this->getQueryBuilder(); + $builder = $this->getQueryBuilder('select'); $statement = $builder->select('*')->from('users')->execute(); var_dump($statement->fetchAll()); } } +Alternatively, the following methods are available to enhance code organization and improve clarity: + +* ``getSelectBuilder()``: Returns a SelectQuery object for building SELECT statements. +* ``getInsertBuilder()``: Returns an InsertQuery object for building INSERT statements. +* ``getUpdateBuilder()``: Returns an UpdateQuery object for building UPDATE statements. +* ``getDeleteBuilder()``: Returns a DeleteQuery object for building DELETE statements. + + +.. code-block:: php + + getSelectBuilder(); + $statement = $builder->select('*')->from('users')->execute(); + var_dump($statement->fetchAll()); + } + } + + Selecting Fields ~~~~~~~~~~~~~~~~ @@ -1867,7 +1918,7 @@ Creating insert queries is also possible: .. code-block:: php getQueryBuilder(); + $builder = $this->getQueryBuilder('insert'); $builder ->insert(['first_name', 'last_name']) ->into('users') @@ -1882,13 +1933,13 @@ For increased performance, you can use another builder object as the values for getQueryBuilder(); + $namesQuery = $this->getQueryBuilder('select'); $namesQuery ->select(['fname', 'lname']) ->from('users') ->where(['is_active' => true]); - $builder = $this->getQueryBuilder(); + $builder = $this->getQueryBuilder('insert'); $st = $builder ->insert(['first_name', 'last_name']) ->into('names') @@ -1914,7 +1965,7 @@ Creating update queries is similar to both inserting and selecting: .. code-block:: php getQueryBuilder(); + $builder = $this->getQueryBuilder('update'); $builder ->update('users') ->set('fname', 'Snow') @@ -1930,7 +1981,7 @@ Finally, delete queries: .. code-block:: php getQueryBuilder(); + $builder = $this->getQueryBuilder('delete'); $builder ->delete('users') ->where(['accepted_gdpr' => false]) diff --git a/app/vendor/robmorgan/phinx/docs/en/seeding.rst b/app/vendor/robmorgan/phinx/docs/en/seeding.rst index 7b091ef98..47e465b36 100644 --- a/app/vendor/robmorgan/phinx/docs/en/seeding.rst +++ b/app/vendor/robmorgan/phinx/docs/en/seeding.rst @@ -42,7 +42,7 @@ It is based on a skeleton template: * Write your database seeder using this method. * * More information on writing seeders is available here: - * http://docs.phinx.org/en/latest/seeding.html + * https://book.cakephp.org/phinx/0/en/seeding.html */ public function run() : void { diff --git a/app/vendor/robmorgan/phinx/phpstan-baseline.neon b/app/vendor/robmorgan/phinx/phpstan-baseline.neon deleted file mode 100644 index 03587fb2e..000000000 --- a/app/vendor/robmorgan/phinx/phpstan-baseline.neon +++ /dev/null @@ -1,27 +0,0 @@ -parameters: - ignoreErrors: - - - message: "#^Variable \\$tval on left side of \\?\\? always exists and is not nullable\\.$#" - count: 1 - path: src/Phinx/Config/Config.php - - - - message: "#^Expression on left side of \\?\\? is not nullable\\.$#" - count: 1 - path: src/Phinx/Db/Adapter/MysqlAdapter.php - - - - message: "#^Ternary operator condition is always true\\.$#" - count: 2 - path: src/Phinx/Db/Adapter/SqlServerAdapter.php - - - - message: "#^Property Phinx\\\\Migration\\\\Manager\\\\Environment\\:\\:\\$adapter \\(Phinx\\\\Db\\\\Adapter\\\\AdapterInterface\\) in isset\\(\\) is not nullable\\.$#" - count: 1 - path: src/Phinx/Migration/Manager/Environment.php - - - - message: "#^Unreachable statement \\- code above always terminates\\.$#" - count: 1 - path: src/Phinx/Migration/Manager/Environment.php - diff --git a/app/vendor/robmorgan/phinx/src/Phinx/Config/Config.php b/app/vendor/robmorgan/phinx/src/Phinx/Config/Config.php index 91cfbd499..2b143c669 100644 --- a/app/vendor/robmorgan/phinx/src/Phinx/Config/Config.php +++ b/app/vendor/robmorgan/phinx/src/Phinx/Config/Config.php @@ -12,15 +12,13 @@ use Phinx\Db\Adapter\SQLiteAdapter; use Phinx\Util\Util; use Psr\Container\ContainerInterface; +use ReturnTypeWillChange; use RuntimeException; use Symfony\Component\Yaml\Yaml; use UnexpectedValueException; /** * Phinx configuration class. - * - * @package Phinx - * @author Rob Morgan */ class Config implements ConfigInterface, NamespaceAwareInterface { @@ -42,12 +40,12 @@ class Config implements ConfigInterface, NamespaceAwareInterface /** * @var array */ - protected $values = []; + protected array $values = []; /** * @var string|null */ - protected $configFilePath; + protected ?string $configFilePath = null; /** * @param array $configArray Config array @@ -84,7 +82,7 @@ public static function fromYaml(string $configFilePath): ConfigInterface if (!is_array($configArray)) { throw new RuntimeException(sprintf( 'File \'%s\' must be valid YAML', - $configFilePath + $configFilePath, )); } @@ -110,7 +108,7 @@ public static function fromJson(string $configFilePath): ConfigInterface if (!is_array($configArray)) { throw new RuntimeException(sprintf( 'File \'%s\' must be valid JSON', - $configFilePath + $configFilePath, )); } @@ -136,7 +134,7 @@ public static function fromPhp(string $configFilePath): ConfigInterface if (!is_array($configArray)) { throw new RuntimeException(sprintf( 'PHP file \'%s\' must return an array', - $configFilePath + $configFilePath, )); } @@ -181,7 +179,7 @@ public function getEnvironment(string $name): ?array if ( isset($environments[$name]['adapter']) && $environments[$name]['adapter'] === 'sqlite' - && !empty($environments[$name]['memory']) + && SQLiteAdapter::isMemory($environments[$name]) ) { $environments[$name]['name'] = SQLiteAdapter::MEMORY; } @@ -214,7 +212,7 @@ public function getDefaultEnvironment(): string throw new RuntimeException(sprintf( 'The environment configuration (read from $PHINX_ENVIRONMENT) for \'%s\' is missing', - $env + $env, )); } @@ -233,7 +231,7 @@ public function getDefaultEnvironment(): string throw new RuntimeException(sprintf( 'The environment configuration for \'%s\' is missing', - $this->values['environments']['default_environment'] + $this->values['environments']['default_environment'], )); } @@ -328,7 +326,7 @@ public function getSeedBaseClassName(bool $dropNamespace = true): string /** * @inheritdoc */ - public function getTemplateFile() + public function getTemplateFile(): string|false { if (!isset($this->values['templates']['file'])) { return false; @@ -340,7 +338,7 @@ public function getTemplateFile() /** * @inheritdoc */ - public function getTemplateClass() + public function getTemplateClass(): string|false { if (!isset($this->values['templates']['class'])) { return false; @@ -410,7 +408,7 @@ public function isVersionOrderCreationTime(): bool /** * @inheritdoc */ - public function getBootstrapFile() + public function getBootstrapFile(): string|false { if (!isset($this->values['paths']['bootstrap'])) { return false; @@ -511,7 +509,7 @@ public function offsetSet($id, $value): void * @throws \InvalidArgumentException * @return mixed */ - #[\ReturnTypeWillChange] + #[ReturnTypeWillChange] public function offsetGet($id) { if (!array_key_exists($id, $this->values)) { diff --git a/app/vendor/robmorgan/phinx/src/Phinx/Config/ConfigInterface.php b/app/vendor/robmorgan/phinx/src/Phinx/Config/ConfigInterface.php index 3bd57f213..81382a2b9 100644 --- a/app/vendor/robmorgan/phinx/src/Phinx/Config/ConfigInterface.php +++ b/app/vendor/robmorgan/phinx/src/Phinx/Config/ConfigInterface.php @@ -12,9 +12,6 @@ /** * Phinx configuration interface. - * - * @package Phinx - * @author Woody Gilk */ interface ConfigInterface extends ArrayAccess { @@ -95,14 +92,14 @@ public function getSeedPaths(): array; * * @return string|false */ - public function getTemplateFile(); + public function getTemplateFile(): string|false; /** * Get the template class name. * * @return string|false */ - public function getTemplateClass(); + public function getTemplateClass(): string|false; /** * Get the template style to use, either change or up_down. @@ -144,7 +141,7 @@ public function isVersionOrderCreationTime(): bool; * * @return string|false */ - public function getBootstrapFile(); + public function getBootstrapFile(): string|false; /** * Gets the base class name for migrations. diff --git a/app/vendor/robmorgan/phinx/src/Phinx/Config/FeatureFlags.php b/app/vendor/robmorgan/phinx/src/Phinx/Config/FeatureFlags.php index dded535b8..67da55d29 100644 --- a/app/vendor/robmorgan/phinx/src/Phinx/Config/FeatureFlags.php +++ b/app/vendor/robmorgan/phinx/src/Phinx/Config/FeatureFlags.php @@ -17,11 +17,15 @@ class FeatureFlags /** * @var bool Should Phinx create unsigned primary keys by default? */ - public static $unsignedPrimaryKeys = true; + public static bool $unsignedPrimaryKeys = true; /** * @var bool Should Phinx create columns NULL by default? */ - public static $columnNullDefault = true; + public static bool $columnNullDefault = true; + /** + * @var bool Should Phinx create datetime columns for addTimestamps instead of timestamp? + */ + public static bool $addTimestampsUseDateTime = false; /** * Set the feature flags from the `feature_flags` section of the overall @@ -37,5 +41,8 @@ public static function setFlagsFromConfig(array $config): void if (isset($config['column_null_default'])) { self::$columnNullDefault = (bool)$config['column_null_default']; } + if (isset($config['add_timestamps_use_datetime'])) { + self::$addTimestampsUseDateTime = (bool)$config['add_timestamps_use_datetime']; + } } } diff --git a/app/vendor/robmorgan/phinx/src/Phinx/Config/NamespaceAwareInterface.php b/app/vendor/robmorgan/phinx/src/Phinx/Config/NamespaceAwareInterface.php index 2acab3dc2..d441d7785 100644 --- a/app/vendor/robmorgan/phinx/src/Phinx/Config/NamespaceAwareInterface.php +++ b/app/vendor/robmorgan/phinx/src/Phinx/Config/NamespaceAwareInterface.php @@ -9,9 +9,6 @@ /** * Config aware getNamespaceByPath method. - * - * @package Phinx\Config - * @author Andrey N. Mokhov */ interface NamespaceAwareInterface { diff --git a/app/vendor/robmorgan/phinx/src/Phinx/Config/NamespaceAwareTrait.php b/app/vendor/robmorgan/phinx/src/Phinx/Config/NamespaceAwareTrait.php index 8426070ea..28de366b3 100644 --- a/app/vendor/robmorgan/phinx/src/Phinx/Config/NamespaceAwareTrait.php +++ b/app/vendor/robmorgan/phinx/src/Phinx/Config/NamespaceAwareTrait.php @@ -9,9 +9,6 @@ /** * Trait implemented NamespaceAwareInterface. - * - * @package Phinx\Config - * @author Andrey N. Mokhov */ trait NamespaceAwareTrait { diff --git a/app/vendor/robmorgan/phinx/src/Phinx/Console/Command/AbstractCommand.php b/app/vendor/robmorgan/phinx/src/Phinx/Console/Command/AbstractCommand.php index c9490d62b..88cafc424 100644 --- a/app/vendor/robmorgan/phinx/src/Phinx/Console/Command/AbstractCommand.php +++ b/app/vendor/robmorgan/phinx/src/Phinx/Console/Command/AbstractCommand.php @@ -1,4 +1,5 @@ */ abstract class AbstractCommand extends Command { @@ -52,22 +54,22 @@ abstract class AbstractCommand extends Command /** * @var \Phinx\Config\ConfigInterface|null */ - protected $config; + protected ?ConfigInterface $config = null; /** * @var \Phinx\Db\Adapter\AdapterInterface */ - protected $adapter; + protected AdapterInterface $adapter; /** * @var \Phinx\Migration\Manager */ - protected $manager; + protected Manager $manager; /** * @var int */ - protected $verbosityLevel = OutputInterface::OUTPUT_NORMAL | OutputInterface::VERBOSITY_NORMAL; + protected int $verbosityLevel = OutputInterface::OUTPUT_NORMAL | OutputInterface::VERBOSITY_NORMAL; /** * Exit code for when command executes successfully @@ -235,7 +237,7 @@ public function setManager(Manager $manager) */ public function getManager(): ?Manager { - return $this->manager; + return $this->manager ?? null; } /** @@ -342,7 +344,7 @@ protected function loadConfig(InputInterface $input, OutputInterface $output): v */ protected function loadManager(InputInterface $input, OutputInterface $output): void { - if ($this->getManager() === null) { + if (!isset($this->manager)) { $manager = new Manager($this->getConfig(), $input, $output); $manager->setVerbosityLevel($this->verbosityLevel); $container = $this->getConfig()->getContainer(); @@ -369,14 +371,14 @@ protected function verifyMigrationDirectory(string $path): void if (!is_dir($path)) { throw new InvalidArgumentException(sprintf( 'Migration directory "%s" does not exist', - $path + $path, )); } if (!is_writable($path)) { throw new InvalidArgumentException(sprintf( 'Migration directory "%s" is not writable', - $path + $path, )); } } @@ -393,14 +395,14 @@ protected function verifySeedDirectory(string $path): void if (!is_dir($path)) { throw new InvalidArgumentException(sprintf( 'Seed directory "%s" does not exist', - $path + $path, )); } if (!is_writable($path)) { throw new InvalidArgumentException(sprintf( 'Seed directory "%s" is not writable', - $path + $path, )); } } @@ -424,4 +426,82 @@ protected function getSeedTemplateFilename(): string { return __DIR__ . self::DEFAULT_SEED_TEMPLATE; } + + /** + * Write out environment information to the OutputInterface + */ + protected function writeEnvironmentOutput(?string &$environment, OutputInterface $output): bool + { + if ($environment === null) { + $environment = $this->getConfig()->getDefaultEnvironment(); + $output->writeln('warning no environment specified, defaulting to: ' . $environment, $this->verbosityLevel); + } else { + $output->writeln('using environment ' . $environment, $this->verbosityLevel); + } + + if (!$this->getConfig()->hasEnvironment($environment)) { + self::getErrorOutput($output)->writeln(sprintf('The environment "%s" does not exist', $environment)); + + return false; + } + + return true; + } + + /** + * Write out options information to the OutputInterface + */ + protected function writeInformationOutput(?string &$environment, OutputInterface $output): bool + { + $success = $this->writeEnvironmentOutput($environment, $output); + if (!$success) { + return false; + } + + $envOptions = $this->getConfig()->getEnvironment($environment); + if (isset($envOptions['adapter'])) { + $output->writeln('using adapter ' . $envOptions['adapter'], $this->verbosityLevel); + } + + if (isset($envOptions['wrapper'])) { + $output->writeln('using wrapper ' . $envOptions['wrapper'], $this->verbosityLevel); + } + + if (isset($envOptions['name'])) { + $name = $envOptions['name']; + // We do error handling for missing adapter or connection is invalid later on running a command + $adapter = $envOptions['adapter'] ?? null; + if (isset($envOptions['connection']) && $envOptions['connection'] instanceof PDO) { + $adapter = $envOptions['connection']->getAttribute(PDO::ATTR_DRIVER_NAME); + } + if ($adapter === 'sqlite') { + $name .= SQLiteAdapter::getSuffix($envOptions); + } + $output->writeln('using database ' . $name, $this->verbosityLevel); + } else { + self::getErrorOutput($output)->writeln('Could not determine database name! Please specify a database name in your config file.'); + + return false; + } + + if (isset($envOptions['table_prefix'])) { + $output->writeln('using table prefix ' . $envOptions['table_prefix'], $this->verbosityLevel); + } + if (isset($envOptions['table_suffix'])) { + $output->writeln('using table suffix ' . $envOptions['table_suffix'], $this->verbosityLevel); + } + + return true; + } + + /** + * Returns the error output to use + * + * @param \Symfony\Component\Console\Output\OutputInterface $output Output + * @return \Symfony\Component\Console\Output\OutputInterface + */ + protected static function getErrorOutput(OutputInterface $output): OutputInterface + { + return $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output; + } } diff --git a/app/vendor/robmorgan/phinx/src/Phinx/Console/Command/Breakpoint.php b/app/vendor/robmorgan/phinx/src/Phinx/Console/Command/Breakpoint.php index f94d597a0..caf0f7656 100644 --- a/app/vendor/robmorgan/phinx/src/Phinx/Console/Command/Breakpoint.php +++ b/app/vendor/robmorgan/phinx/src/Phinx/Console/Command/Breakpoint.php @@ -1,4 +1,5 @@ phinx breakpoint -e development phinx breakpoint -e development -t 20110103081132 phinx breakpoint -e development -r -EOT +EOT, ); } @@ -69,16 +71,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int $set = $input->getOption('set'); $unset = $input->getOption('unset'); - if ($environment === null) { - $environment = $this->getConfig()->getDefaultEnvironment(); - $output->writeln('warning no environment specified, defaulting to: ' . $environment, $this->verbosityLevel); - } else { - $output->writeln('using environment ' . $environment, $this->verbosityLevel); - } - - if (!$this->getConfig()->hasEnvironment($environment)) { - $output->writeln(sprintf('The environment "%s" does not exist', $environment)); - + $success = $this->writeEnvironmentOutput($environment, $output); + if (!$success) { return self::CODE_ERROR; } diff --git a/app/vendor/robmorgan/phinx/src/Phinx/Console/Command/Create.php b/app/vendor/robmorgan/phinx/src/Phinx/Console/Command/Create.php index 1e1824b88..c28253115 100644 --- a/app/vendor/robmorgan/phinx/src/Phinx/Console/Command/Create.php +++ b/app/vendor/robmorgan/phinx/src/Phinx/Console/Command/Create.php @@ -1,4 +1,5 @@ setHelp(sprintf( '%sCreates a new database migration%s', PHP_EOL, - PHP_EOL + PHP_EOL, )); // An alternative template. @@ -116,7 +127,7 @@ protected function getMigrationPath(InputInterface $input, OutputInterface $outp throw new Exception( 'You probably used curly braces to define migration path in your Phinx configuration file, ' . 'but no directories have been matched using this pattern. ' . - 'You need to create a migration directory manually.' + 'You need to create a migration directory manually.', ); } @@ -165,27 +176,38 @@ protected function execute(InputInterface $input, OutputInterface $output): int $path = realpath($path); $className = $input->getArgument('name'); + if ($className !== null && in_array(strtolower($className), $this->keywords)) { + throw new InvalidArgumentException(sprintf( + 'The migration class name "%s" is a reserved PHP keyword. Please choose a different class name.', + $className, + )); + } + + $offset = 0; + do { + $timestamp = Util::getCurrentTimestamp($offset++); + } while (!Util::isUniqueTimestamp($path, $timestamp)); + if ($className === null) { - $currentTimestamp = Util::getCurrentTimestamp(); - $className = 'V' . $currentTimestamp; - $fileName = $currentTimestamp . '.php'; + $className = 'V' . $timestamp; + $fileName = ''; } else { if (!Util::isValidPhinxClassName($className)) { throw new InvalidArgumentException(sprintf( 'The migration class name "%s" is invalid. Please use CamelCase format.', - $className + $className, )); } - // Compute the file path - $fileName = Util::mapClassNameToFileName($className); + $fileName = Util::toSnakeCase($className); } + $fileName = $timestamp . $fileName . '.php'; if (!Util::isUniqueMigrationClassName($className, $path)) { throw new InvalidArgumentException(sprintf( 'The migration class name "%s%s" already exists', $namespace ? $namespace . '\\' : '', - $className + $className, )); } @@ -194,7 +216,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int if (is_file($filePath)) { throw new InvalidArgumentException(sprintf( 'The file "%s" already exists', - $filePath + $filePath, )); } @@ -231,7 +253,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int if ($altTemplate && !is_file($altTemplate)) { throw new InvalidArgumentException(sprintf( 'The alternative template file "%s" does not exist', - $altTemplate + $altTemplate, )); } @@ -245,12 +267,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int throw new InvalidArgumentException(sprintf( 'The class "%s" via the alias "%s" does not exist', $aliasedClassName, - $creationClassName + $creationClassName, )); } elseif (!$aliasedClassName) { throw new InvalidArgumentException(sprintf( 'The class "%s" does not exist', - $creationClassName + $creationClassName, )); } } @@ -260,14 +282,14 @@ protected function execute(InputInterface $input, OutputInterface $output): int throw new InvalidArgumentException(sprintf( 'The class "%s" does not implement the required interface "%s"', $creationClassName, - self::CREATION_INTERFACE + self::CREATION_INTERFACE, )); } elseif ($aliasedClassName && !is_subclass_of($aliasedClassName, self::CREATION_INTERFACE)) { throw new InvalidArgumentException(sprintf( 'The class "%s" via the alias "%s" does not implement the required interface "%s"', $aliasedClassName, $creationClassName, - self::CREATION_INTERFACE + self::CREATION_INTERFACE, )); } } @@ -299,7 +321,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int if (file_put_contents($filePath, $contents) === false) { throw new RuntimeException(sprintf( 'The file "%s" could not be written to', - $path + $path, )); } diff --git a/app/vendor/robmorgan/phinx/src/Phinx/Console/Command/Init.php b/app/vendor/robmorgan/phinx/src/Phinx/Console/Command/Init.php index 34568b629..3b634f72d 100644 --- a/app/vendor/robmorgan/phinx/src/Phinx/Console/Command/Init.php +++ b/app/vendor/robmorgan/phinx/src/Phinx/Console/Command/Init.php @@ -1,4 +1,5 @@ addArgument('path', InputArgument::OPTIONAL, 'Which path should we initialize for Phinx?') ->setHelp(sprintf( '%sInitializes the application for Phinx%s', PHP_EOL, - PHP_EOL + PHP_EOL, )); } @@ -92,7 +94,7 @@ protected function resolvePath(InputInterface $input, string $format): string if (!in_array($format, static::$supportedFormats, true)) { throw new InvalidArgumentException(sprintf( 'Invalid format "%s". Format must be either ' . implode(', ', static::$supportedFormats) . '.', - $format + $format, )); } @@ -116,14 +118,14 @@ protected function resolvePath(InputInterface $input, string $format): string if (is_file($path)) { throw new InvalidArgumentException(sprintf( 'Config file "%s" already exists.', - $path + $path, )); } // Dir is invalid throw new InvalidArgumentException(sprintf( 'Invalid path "%s" for config file.', - $path + $path, )); } @@ -143,7 +145,7 @@ protected function writeConfig(string $path, string $format = AbstractCommand::F if (!is_writable($dirname)) { throw new InvalidArgumentException(sprintf( 'The directory "%s" is not writable', - $dirname + $dirname, )); } @@ -157,14 +159,14 @@ protected function writeConfig(string $path, string $format = AbstractCommand::F } else { throw new RuntimeException(sprintf( 'Could not find template for format "%s".', - $format + $format, )); } if (file_put_contents($path, $contents) === false) { throw new RuntimeException(sprintf( 'The file "%s" could not be written to', - $path + $path, )); } } diff --git a/app/vendor/robmorgan/phinx/src/Phinx/Console/Command/ListAliases.php b/app/vendor/robmorgan/phinx/src/Phinx/Console/Command/ListAliases.php index 1eeac2cb3..f7b757db9 100644 --- a/app/vendor/robmorgan/phinx/src/Phinx/Console/Command/ListAliases.php +++ b/app/vendor/robmorgan/phinx/src/Phinx/Console/Command/ListAliases.php @@ -1,4 +1,5 @@ verbosityLevel + $this->verbosityLevel, ); } else { $output->writeln( - sprintf( - 'No aliases defined in %s', - Util::relativePath($this->config->getConfigFilePath()) - ) + 'warning no aliases defined in ' . Util::relativePath( + $this->config->getConfigFilePath(), + ), + $this->verbosityLevel, ); } diff --git a/app/vendor/robmorgan/phinx/src/Phinx/Console/Command/Migrate.php b/app/vendor/robmorgan/phinx/src/Phinx/Console/Command/Migrate.php index 571e24268..a8c9c501d 100644 --- a/app/vendor/robmorgan/phinx/src/Phinx/Console/Command/Migrate.php +++ b/app/vendor/robmorgan/phinx/src/Phinx/Console/Command/Migrate.php @@ -1,4 +1,5 @@ setDescription('Migrate the database') ->addOption('--target', '-t', InputOption::VALUE_REQUIRED, 'The version number to migrate to') ->addOption('--date', '-d', InputOption::VALUE_REQUIRED, 'The date to migrate to') + ->addOption('--count', '-k', InputOption::VALUE_REQUIRED, 'The number of migrations to run') ->addOption('--dry-run', '-x', InputOption::VALUE_NONE, 'Dump query to standard output instead of executing it') ->addOption('--fake', null, InputOption::VALUE_NONE, "Mark any migrations selected as run, but don't actually execute them") ->setHelp( <<migrate command runs all available migrations, optionally up to a specific version +The migrate command runs all available migrations, optionally up to a specific version, date, or count. phinx migrate -e development phinx migrate -e development -t 20110103081132 phinx migrate -e development -d 20110103 +phinx migrate -e development -k 5 phinx migrate -e development -v -EOT +EOT, ); } @@ -63,49 +66,18 @@ protected function execute(InputInterface $input, OutputInterface $output): int { $this->bootstrap($input, $output); - $version = $input->getOption('target'); + $version = $input->getOption('target') !== null ? (int)$input->getOption('target') : null; /** @var string|null $environment */ $environment = $input->getOption('environment'); $date = $input->getOption('date'); + $count = $input->getOption('count') !== null ? (int)$input->getOption('count') : null; $fake = (bool)$input->getOption('fake'); - if ($environment === null) { - $environment = $this->getConfig()->getDefaultEnvironment(); - $output->writeln('warning no environment specified, defaulting to: ' . $environment, $this->verbosityLevel); - } else { - $output->writeln('using environment ' . $environment, $this->verbosityLevel); - } - - if (!$this->getConfig()->hasEnvironment($environment)) { - $output->writeln(sprintf('The environment "%s" does not exist', $environment)); - - return self::CODE_ERROR; - } - - $envOptions = $this->getConfig()->getEnvironment($environment); - if (isset($envOptions['adapter'])) { - $output->writeln('using adapter ' . $envOptions['adapter'], $this->verbosityLevel); - } - - if (isset($envOptions['wrapper'])) { - $output->writeln('using wrapper ' . $envOptions['wrapper'], $this->verbosityLevel); - } - - if (isset($envOptions['name'])) { - $output->writeln('using database ' . $envOptions['name'], $this->verbosityLevel); - } else { - $output->writeln('Could not determine database name! Please specify a database name in your config file.'); - + $success = $this->writeInformationOutput($environment, $output); + if (!$success) { return self::CODE_ERROR; } - if (isset($envOptions['table_prefix'])) { - $output->writeln('using table prefix ' . $envOptions['table_prefix'], $this->verbosityLevel); - } - if (isset($envOptions['table_suffix'])) { - $output->writeln('using table suffix ' . $envOptions['table_suffix'], $this->verbosityLevel); - } - $versionOrder = $this->getConfig()->getVersionOrder(); $output->writeln('ordering by ' . $versionOrder . ' time', $this->verbosityLevel); @@ -116,18 +88,16 @@ protected function execute(InputInterface $input, OutputInterface $output): int try { // run the migrations $start = microtime(true); - if ($date !== null) { + if ($count !== null) { + $this->getManager()->migrateToCount($environment, $count, $fake); + } elseif ($date !== null) { $this->getManager()->migrateToDateTime($environment, new DateTime($date), $fake); } else { $this->getManager()->migrate($environment, $version, $fake); } $end = microtime(true); - } catch (Exception $e) { - $output->writeln('' . $e->__toString() . ''); - - return self::CODE_ERROR; } catch (Throwable $e) { - $output->writeln('' . $e->__toString() . ''); + self::getErrorOutput($output)->writeln('' . $e->__toString() . ''); return self::CODE_ERROR; } diff --git a/app/vendor/robmorgan/phinx/src/Phinx/Console/Command/Rollback.php b/app/vendor/robmorgan/phinx/src/Phinx/Console/Command/Rollback.php index 7e5bdc71c..ff78f04a9 100644 --- a/app/vendor/robmorgan/phinx/src/Phinx/Console/Command/Rollback.php +++ b/app/vendor/robmorgan/phinx/src/Phinx/Console/Command/Rollback.php @@ -1,4 +1,5 @@ -d|--date option to rollback to a certain date using the migration start times to order them. -EOT +EOT, ); } @@ -77,34 +79,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int $force = (bool)$input->getOption('force'); $fake = (bool)$input->getOption('fake'); - $config = $this->getConfig(); - - if ($environment === null) { - $environment = $config->getDefaultEnvironment(); - $output->writeln('warning no environment specified, defaulting to: ' . $environment, $this->verbosityLevel); - } else { - $output->writeln('using environment ' . $environment, $this->verbosityLevel); - } - - if (!$this->getConfig()->hasEnvironment($environment)) { - $output->writeln(sprintf('The environment "%s" does not exist', $environment)); - + $success = $this->writeInformationOutput($environment, $output); + if (!$success) { return self::CODE_ERROR; } - $envOptions = $config->getEnvironment($environment); - if (isset($envOptions['adapter'])) { - $output->writeln('using adapter ' . $envOptions['adapter'], $this->verbosityLevel); - } - - if (isset($envOptions['wrapper'])) { - $output->writeln('using wrapper ' . $envOptions['wrapper'], $this->verbosityLevel); - } - - if (isset($envOptions['name'])) { - $output->writeln('using database ' . $envOptions['name'], $this->verbosityLevel); - } - $versionOrder = $this->getConfig()->getVersionOrder(); $output->writeln('ordering by ' . $versionOrder . ' time', $this->verbosityLevel); diff --git a/app/vendor/robmorgan/phinx/src/Phinx/Console/Command/SeedCreate.php b/app/vendor/robmorgan/phinx/src/Phinx/Console/Command/SeedCreate.php index ae56f0bc4..3b0fe3bbe 100644 --- a/app/vendor/robmorgan/phinx/src/Phinx/Console/Command/SeedCreate.php +++ b/app/vendor/robmorgan/phinx/src/Phinx/Console/Command/SeedCreate.php @@ -1,4 +1,5 @@ setHelp(sprintf( '%sCreates a new database seeder%s', PHP_EOL, - PHP_EOL + PHP_EOL, )); // An alternative template. @@ -102,7 +104,7 @@ protected function getSeedPath(InputInterface $input, OutputInterface $output): throw new Exception( 'You probably used curly braces to define seed path in your Phinx configuration file, ' . 'but no directories have been matched using this pattern. ' . - 'You need to create a seed directory manually.' + 'You need to create a seed directory manually.', ); } @@ -153,7 +155,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int if (!Util::isValidPhinxClassName($className)) { throw new InvalidArgumentException(sprintf( 'The seed class name "%s" is invalid. Please use CamelCase format', - $className + $className, )); } @@ -163,7 +165,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int if (is_file($filePath)) { throw new InvalidArgumentException(sprintf( 'The file "%s" already exists', - basename($filePath) + basename($filePath), )); } @@ -174,7 +176,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int if ($altTemplate && !is_file($altTemplate)) { throw new InvalidArgumentException(sprintf( 'The template file "%s" does not exist', - $altTemplate + $altTemplate, )); } @@ -185,7 +187,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int if (!is_null($altTemplate) && !is_file($altTemplate)) { throw new InvalidArgumentException(sprintf( 'The template file `%s` from config does not exist', - $altTemplate + $altTemplate, )); } } @@ -196,7 +198,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $namespace = $config instanceof NamespaceAwareInterface ? $config->getSeedNamespaceByPath($path) : null; $classes = [ - '$namespaceDefinition' => $namespace !== null ? ('namespace ' . $namespace . ';') : '', + '$namespaceDefinition' => $namespace !== null ? (PHP_EOL . 'namespace ' . $namespace . ';' . PHP_EOL) : '', '$namespace' => $namespace, '$useClassName' => $config->getSeedBaseClassName(false), '$className' => $className, @@ -207,7 +209,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int if (file_put_contents($filePath, $contents) === false) { throw new RuntimeException(sprintf( 'The file "%s" could not be written to', - $path + $path, )); } diff --git a/app/vendor/robmorgan/phinx/src/Phinx/Console/Command/SeedRun.php b/app/vendor/robmorgan/phinx/src/Phinx/Console/Command/SeedRun.php index ba2b32f81..343221152 100644 --- a/app/vendor/robmorgan/phinx/src/Phinx/Console/Command/SeedRun.php +++ b/app/vendor/robmorgan/phinx/src/Phinx/Console/Command/SeedRun.php @@ -1,4 +1,5 @@ setDescription('Run database seeders') ->addOption('--seed', '-s', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'What is the name of the seeder?') + ->addOption('--dry-run', '-x', InputOption::VALUE_NONE, 'Dump query to standard output instead of executing it') ->setHelp( <<seed:run command runs all available or individual seeders @@ -42,7 +45,7 @@ protected function configure(): void phinx seed:run -e development -s UserSeeder -s PermissionSeeder -s LogSeeder phinx seed:run -e development -v -EOT +EOT, ); } @@ -62,43 +65,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int /** @var string|null $environment */ $environment = $input->getOption('environment'); - if ($environment === null) { - $environment = $this->getConfig()->getDefaultEnvironment(); - $output->writeln('warning no environment specified, defaulting to: ' . $environment, $this->verbosityLevel); - } else { - $output->writeln('using environment ' . $environment, $this->verbosityLevel); - } - - if (!$this->getConfig()->hasEnvironment($environment)) { - $output->writeln(sprintf('The environment "%s" does not exist', $environment)); - - return self::CODE_ERROR; - } - - $envOptions = $this->getConfig()->getEnvironment($environment); - if (isset($envOptions['adapter'])) { - $output->writeln('using adapter ' . $envOptions['adapter'], $this->verbosityLevel); - } - - if (isset($envOptions['wrapper'])) { - $output->writeln('using wrapper ' . $envOptions['wrapper'], $this->verbosityLevel); - } - - if (isset($envOptions['name'])) { - $output->writeln('using database ' . $envOptions['name'], $this->verbosityLevel); - } else { - $output->writeln('Could not determine database name! Please specify a database name in your config file.'); - + $success = $this->writeInformationOutput($environment, $output); + if (!$success) { return self::CODE_ERROR; } - if (isset($envOptions['table_prefix'])) { - $output->writeln('using table prefix ' . $envOptions['table_prefix'], $this->verbosityLevel); - } - if (isset($envOptions['table_suffix'])) { - $output->writeln('using table suffix ' . $envOptions['table_suffix'], $this->verbosityLevel); - } - $start = microtime(true); if (empty($seedSet)) { diff --git a/app/vendor/robmorgan/phinx/src/Phinx/Console/Command/Status.php b/app/vendor/robmorgan/phinx/src/Phinx/Console/Command/Status.php index c00846f09..ca09eb457 100644 --- a/app/vendor/robmorgan/phinx/src/Phinx/Console/Command/Status.php +++ b/app/vendor/robmorgan/phinx/src/Phinx/Console/Command/Status.php @@ -1,4 +1,5 @@ phinx status -e development -f json The version_order configuration option is used to determine the order of the status migrations. -EOT +EOT, ); } @@ -61,16 +63,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int /** @var string|null $environment */ $format = $input->getOption('format'); - if ($environment === null) { - $environment = $this->getConfig()->getDefaultEnvironment(); - $output->writeln('warning no environment specified, defaulting to: ' . $environment, $this->verbosityLevel); - } else { - $output->writeln('using environment ' . $environment, $this->verbosityLevel); - } - - if (!$this->getConfig()->hasEnvironment($environment)) { - $output->writeln(sprintf('The environment "%s" does not exist', $environment)); - + $success = $this->writeEnvironmentOutput($environment, $output); + if (!$success) { return self::CODE_ERROR; } diff --git a/app/vendor/robmorgan/phinx/src/Phinx/Console/Command/Test.php b/app/vendor/robmorgan/phinx/src/Phinx/Console/Command/Test.php index abe8036b4..7391add3e 100644 --- a/app/vendor/robmorgan/phinx/src/Phinx/Console/Command/Test.php +++ b/app/vendor/robmorgan/phinx/src/Phinx/Console/Command/Test.php @@ -1,4 +1,5 @@ phinx test -e development If the environment option is set, it will test that phinx can connect to the DB associated with that environment -EOT +EOT, ); } @@ -57,19 +59,22 @@ protected function configure(): void */ protected function execute(InputInterface $input, OutputInterface $output): int { - $this->loadConfig($input, $output); + if (!$this->hasConfig()) { + $this->loadConfig($input, $output); + } + $this->loadManager($input, $output); // Verify the migrations path(s) array_map( [$this, 'verifyMigrationDirectory'], - Util::globAll($this->getConfig()->getMigrationPaths()) + Util::globAll($this->getConfig()->getMigrationPaths()), ); // Verify the seed path(s) array_map( [$this, 'verifySeedDirectory'], - Util::globAll($this->getConfig()->getSeedPaths()) + Util::globAll($this->getConfig()->getSeedPaths()), ); $envName = $input->getOption('environment'); @@ -77,14 +82,14 @@ protected function execute(InputInterface $input, OutputInterface $output): int if (!$this->getConfig()->hasEnvironment($envName)) { throw new InvalidArgumentException(sprintf( 'The environment "%s" does not exist', - $envName + $envName, )); } $output->writeln(sprintf('validating environment %s', $envName), $this->verbosityLevel); $environment = new Environment( $envName, - $this->getConfig()->getEnvironment($envName) + $this->getConfig()->getEnvironment($envName), ); // validate environment connection $environment->getAdapter()->connect(); diff --git a/app/vendor/robmorgan/phinx/src/Phinx/Console/PhinxApplication.php b/app/vendor/robmorgan/phinx/src/Phinx/Console/PhinxApplication.php index 5ff32db98..ca71911bb 100644 --- a/app/vendor/robmorgan/phinx/src/Phinx/Console/PhinxApplication.php +++ b/app/vendor/robmorgan/phinx/src/Phinx/Console/PhinxApplication.php @@ -1,4 +1,5 @@ */ class PhinxApplication extends Application { + /** + * @var string The current application version as determined by the getVersion() function. + */ + private string $version; + /** * Initialize the Phinx console application. */ public function __construct() { - parent::__construct('Phinx by CakePHP - https://phinx.org.'); + parent::__construct('Phinx by CakePHP - https://phinx.org.', $this->getVersion()); $this->addCommands([ new Init(), @@ -69,4 +74,30 @@ public function doRun(InputInterface $input, OutputInterface $output): int return parent::doRun($input, $output); } + + /** + * Get the current application version. + * + * @return string The application version if it could be found, otherwise 'UNKNOWN' + */ + public function getVersion(): string + { + if (isset($this->version)) { + return $this->version; + } + + // humbug/box will replace this with actual version when building + // so use that if available + $gitTag = '@git_tag@'; + if (!str_starts_with($gitTag, '@')) { + return $this->version = $gitTag; + } + + // Otherwise fallback to the version as reported by composer + if (class_exists(InstalledVersions::class)) { + return $this->version = InstalledVersions::getPrettyVersion('robmorgan/phinx') ?? 'UNKNOWN'; + } + + return $this->version = 'UNKNOWN'; + } } diff --git a/app/vendor/robmorgan/phinx/src/Phinx/Db/Action/Action.php b/app/vendor/robmorgan/phinx/src/Phinx/Db/Action/Action.php index 90c3ae42b..f8263c48d 100644 --- a/app/vendor/robmorgan/phinx/src/Phinx/Db/Action/Action.php +++ b/app/vendor/robmorgan/phinx/src/Phinx/Db/Action/Action.php @@ -1,4 +1,5 @@ $options The column options * @return static */ - public static function build(Table $table, string $columnName, $type = null, array $options = []) + public static function build(Table $table, string $columnName, string|Literal $type, array $options = []): static { $column = new Column(); $column->setName($columnName); diff --git a/app/vendor/robmorgan/phinx/src/Phinx/Db/Action/AddForeignKey.php b/app/vendor/robmorgan/phinx/src/Phinx/Db/Action/AddForeignKey.php index 9121cfe44..1468539cd 100644 --- a/app/vendor/robmorgan/phinx/src/Phinx/Db/Action/AddForeignKey.php +++ b/app/vendor/robmorgan/phinx/src/Phinx/Db/Action/AddForeignKey.php @@ -1,4 +1,5 @@ $options Additional options for the index creation * @return static */ - public static function build(Table $table, $columns, array $options = []) + public static function build(Table $table, string|array|Index $columns, array $options = []): static { // create a new index object if strings or an array of strings were supplied $index = $columns; diff --git a/app/vendor/robmorgan/phinx/src/Phinx/Db/Action/ChangeColumn.php b/app/vendor/robmorgan/phinx/src/Phinx/Db/Action/ChangeColumn.php index 143baf931..f55afa8a9 100644 --- a/app/vendor/robmorgan/phinx/src/Phinx/Db/Action/ChangeColumn.php +++ b/app/vendor/robmorgan/phinx/src/Phinx/Db/Action/ChangeColumn.php @@ -1,4 +1,5 @@ $options Additional options for the column * @return static */ - public static function build(Table $table, string $columnName, $type = null, array $options = []) + public static function build(Table $table, string $columnName, string|Literal $type, array $options = []): static { $column = new Column(); $column->setName($columnName); diff --git a/app/vendor/robmorgan/phinx/src/Phinx/Db/Action/ChangeComment.php b/app/vendor/robmorgan/phinx/src/Phinx/Db/Action/ChangeComment.php index 56045306b..7718519b7 100644 --- a/app/vendor/robmorgan/phinx/src/Phinx/Db/Action/ChangeComment.php +++ b/app/vendor/robmorgan/phinx/src/Phinx/Db/Action/ChangeComment.php @@ -1,4 +1,5 @@ newColumns = $newColumns; @@ -35,7 +36,7 @@ public function __construct(Table $table, $newColumns) * * @return string|string[]|null */ - public function getNewColumns() + public function getNewColumns(): string|array|null { return $this->newColumns; } diff --git a/app/vendor/robmorgan/phinx/src/Phinx/Db/Action/CreateTable.php b/app/vendor/robmorgan/phinx/src/Phinx/Db/Action/CreateTable.php index 8f50afa5b..a9a593bbd 100644 --- a/app/vendor/robmorgan/phinx/src/Phinx/Db/Action/CreateTable.php +++ b/app/vendor/robmorgan/phinx/src/Phinx/Db/Action/CreateTable.php @@ -1,4 +1,5 @@ setColumns($columns); @@ -55,7 +56,7 @@ public static function build(Table $table, array $columns = []) * @param string $name The name of the index * @return static */ - public static function buildFromName(Table $table, string $name) + public static function buildFromName(Table $table, string $name): static { $index = new Index(); $index->setName($name); diff --git a/app/vendor/robmorgan/phinx/src/Phinx/Db/Action/DropTable.php b/app/vendor/robmorgan/phinx/src/Phinx/Db/Action/DropTable.php index 6343d0bfb..318664971 100644 --- a/app/vendor/robmorgan/phinx/src/Phinx/Db/Action/DropTable.php +++ b/app/vendor/robmorgan/phinx/src/Phinx/Db/Action/DropTable.php @@ -1,4 +1,5 @@ setName($columnName); diff --git a/app/vendor/robmorgan/phinx/src/Phinx/Db/Action/RenameColumn.php b/app/vendor/robmorgan/phinx/src/Phinx/Db/Action/RenameColumn.php index 0f1839527..3bf4ed52f 100644 --- a/app/vendor/robmorgan/phinx/src/Phinx/Db/Action/RenameColumn.php +++ b/app/vendor/robmorgan/phinx/src/Phinx/Db/Action/RenameColumn.php @@ -1,4 +1,5 @@ setName($columnName); diff --git a/app/vendor/robmorgan/phinx/src/Phinx/Db/Action/RenameTable.php b/app/vendor/robmorgan/phinx/src/Phinx/Db/Action/RenameTable.php index dd9652d95..25c4718e3 100644 --- a/app/vendor/robmorgan/phinx/src/Phinx/Db/Action/RenameTable.php +++ b/app/vendor/robmorgan/phinx/src/Phinx/Db/Action/RenameTable.php @@ -1,4 +1,5 @@ */ - protected $options = []; + protected array $options = []; /** * @var \Symfony\Component\Console\Input\InputInterface|null */ - protected $input; + protected ?InputInterface $input = null; /** * @var \Symfony\Component\Console\Output\OutputInterface */ - protected $output; + protected OutputInterface $output; /** * @var string[] */ - protected $createdTables = []; + protected array $createdTables = []; /** * @var string */ - protected $schemaTableName = 'phinxlog'; + protected string $schemaTableName = 'phinxlog'; /** * @var array */ - protected $dataDomain = []; + protected array $dataDomain = []; /** * Class Constructor. @@ -113,7 +114,7 @@ public function hasOption(string $name): bool /** * @inheritDoc */ - public function getOption(string $name) + public function getOption(string $name): mixed { if (!$this->hasOption($name)) { return null; @@ -155,7 +156,7 @@ public function setOutput(OutputInterface $output): AdapterInterface */ public function getOutput(): OutputInterface { - if ($this->output === null) { + if (!isset($this->output)) { $output = new NullOutput(); $this->setOutput($output); } @@ -222,9 +223,9 @@ public function setDataDomain(array $dataDomain) // and it is compatible with the base Phinx types. foreach ($dataDomain as $type => $options) { if (!isset($options['type'])) { - throw new \InvalidArgumentException(sprintf( + throw new InvalidArgumentException(sprintf( 'You must specify a type for data domain type "%s".', - $type + $type, )); } @@ -234,10 +235,10 @@ public function setDataDomain(array $dataDomain) } if (!in_array($options['type'], $this->getColumnTypes(), true)) { - throw new \InvalidArgumentException(sprintf( + throw new InvalidArgumentException(sprintf( 'An invalid column type "%s" was specified for data domain type "%s".', $options['type'], - $type + $type, )); } @@ -253,10 +254,10 @@ public function setDataDomain(array $dataDomain) if (isset($options['limit']) && !is_numeric($options['limit'])) { if (!defined('static::' . $options['limit'])) { - throw new \InvalidArgumentException(sprintf( + throw new InvalidArgumentException(sprintf( 'An invalid limit value "%s" was specified for data domain type "%s".', $options['limit'], - $type + $type, )); } @@ -317,7 +318,7 @@ public function createSchemaTable(): void throw new InvalidArgumentException( 'There was a problem creating the schema table: ' . $exception->getMessage(), (int)$exception->getCode(), - $exception + $exception, ); } } diff --git a/app/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/AdapterFactory.php b/app/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/AdapterFactory.php index 384369144..7413c4c13 100644 --- a/app/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/AdapterFactory.php +++ b/app/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/AdapterFactory.php @@ -1,4 +1,5 @@ */ class AdapterFactory { /** - * @var \Phinx\Db\Adapter\AdapterFactory|null + * @var static|null */ - protected static $instance; + protected static ?AdapterFactory $instance = null; /** * Get the factory singleton instance. * - * @return \Phinx\Db\Adapter\AdapterFactory + * @return static */ - public static function instance() + public static function instance(): static { if (!static::$instance) { static::$instance = new static(); @@ -43,7 +42,7 @@ public static function instance() * @var array * @phpstan-var array> */ - protected $adapters = [ + protected array $adapters = [ 'mysql' => 'Phinx\Db\Adapter\MysqlAdapter', 'pgsql' => 'Phinx\Db\Adapter\PostgresAdapter', 'sqlite' => 'Phinx\Db\Adapter\SQLiteAdapter', @@ -55,7 +54,7 @@ public static function instance() * * @var array */ - protected $wrappers = [ + protected array $wrappers = [ 'prefix' => 'Phinx\Db\Adapter\TablePrefixAdapter', 'proxy' => 'Phinx\Db\Adapter\ProxyAdapter', 'timed' => 'Phinx\Db\Adapter\TimedOutputAdapter', @@ -69,12 +68,12 @@ public static function instance() * @throws \RuntimeException * @return $this */ - public function registerAdapter(string $name, $class) + public function registerAdapter(string $name, object|string $class) { if (!is_subclass_of($class, 'Phinx\Db\Adapter\AdapterInterface')) { throw new RuntimeException(sprintf( 'Adapter class "%s" must implement Phinx\\Db\\Adapter\\AdapterInterface', - is_string($class) ? $class : get_class($class) + is_string($class) ? $class : get_class($class), )); } $this->adapters[$name] = $class; @@ -90,12 +89,12 @@ public function registerAdapter(string $name, $class) * @return object|string * @phpstan-return object|class-string<\Phinx\Db\Adapter\AdapterInterface> */ - protected function getClass(string $name) + protected function getClass(string $name): object|string { if (empty($this->adapters[$name])) { throw new RuntimeException(sprintf( 'Adapter "%s" has not been registered', - $name + $name, )); } @@ -124,12 +123,12 @@ public function getAdapter(string $name, array $options): AdapterInterface * @throws \RuntimeException * @return $this */ - public function registerWrapper(string $name, $class) + public function registerWrapper(string $name, object|string $class) { if (!is_subclass_of($class, 'Phinx\Db\Adapter\WrapperInterface')) { throw new RuntimeException(sprintf( 'Wrapper class "%s" must be implement Phinx\\Db\\Adapter\\WrapperInterface', - is_string($class) ? $class : get_class($class) + is_string($class) ? $class : get_class($class), )); } $this->wrappers[$name] = $class; @@ -144,12 +143,12 @@ public function registerWrapper(string $name, $class) * @throws \RuntimeException * @return \Phinx\Db\Adapter\WrapperInterface|string */ - protected function getWrapperClass(string $name) + protected function getWrapperClass(string $name): WrapperInterface|string { if (empty($this->wrappers[$name])) { throw new RuntimeException(sprintf( 'Wrapper "%s" has not been registered', - $name + $name, )); } diff --git a/app/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/AdapterInterface.php b/app/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/AdapterInterface.php index 65d7cc35b..ecce738af 100644 --- a/app/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/AdapterInterface.php +++ b/app/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/AdapterInterface.php @@ -1,4 +1,5 @@ * @method \PDO getConnection() */ interface AdapterInterface @@ -120,7 +125,7 @@ public function hasOption(string $name): bool; * @param string $name Name * @return mixed */ - public function getOption(string $name); + public function getOption(string $name): mixed; /** * Sets the console input. @@ -284,7 +289,35 @@ public function executeActions(Table $table, array $actions): void; * * @return \Cake\Database\Query */ - public function getQueryBuilder(): Query; + public function getQueryBuilder(string $type): Query; + + /** + * Return a new SelectQuery object + * + * @return \Cake\Database\Query\SelectQuery + */ + public function getSelectBuilder(): SelectQuery; + + /** + * Return a new InsertQuery object + * + * @return \Cake\Database\Query\InsertQuery + */ + public function getInsertBuilder(): InsertQuery; + + /** + * Return a new UpdateQuery object + * + * @return \Cake\Database\Query\UpdateQuery + */ + public function getUpdateBuilder(): UpdateQuery; + + /** + * Return a new DeleteQuery object + * + * @return \Cake\Database\Query\DeleteQuery + */ + public function getDeleteBuilder(): DeleteQuery; /** * Executes a SQL statement. @@ -295,7 +328,7 @@ public function getQueryBuilder(): Query; * @param array $params parameters to use for prepared query * @return mixed */ - public function query(string $sql, array $params = []); + public function query(string $sql, array $params = []): mixed; /** * Executes a query and returns only one row as an array. @@ -303,7 +336,7 @@ public function query(string $sql, array $params = []); * @param string $sql SQL * @return array|false */ - public function fetchRow(string $sql); + public function fetchRow(string $sql): array|false; /** * Executes a query and returns an array of rows. @@ -397,7 +430,7 @@ public function hasColumn(string $tableName, string $columnName): bool; * @param string|string[] $columns Column(s) * @return bool */ - public function hasIndex(string $tableName, $columns): bool; + public function hasIndex(string $tableName, string|array $columns): bool; /** * Checks to see if an index specified by name exists. @@ -416,7 +449,7 @@ public function hasIndexByName(string $tableName, string $indexName): bool; * @param string|null $constraint Constraint name * @return bool */ - public function hasPrimaryKey(string $tableName, $columns, ?string $constraint = null): bool; + public function hasPrimaryKey(string $tableName, string|array $columns, ?string $constraint = null): bool; /** * Checks to see if a foreign key exists. @@ -426,7 +459,7 @@ public function hasPrimaryKey(string $tableName, $columns, ?string $constraint = * @param string|null $constraint Constraint name * @return bool */ - public function hasForeignKey(string $tableName, $columns, ?string $constraint = null): bool; + public function hasForeignKey(string $tableName, string|array $columns, ?string $constraint = null): bool; /** * Returns an array of the supported Phinx column types. @@ -450,7 +483,7 @@ public function isValidColumnType(Column $column): bool; * @param int|null $limit Limit * @return array */ - public function getSqlType($type, ?int $limit = null): array; + public function getSqlType(Literal|string $type, ?int $limit = null): array; /** * Creates a new database. @@ -501,5 +534,5 @@ public function dropSchema(string $schemaName): void; * @param mixed $value The value to be cast * @return mixed */ - public function castToBool($value); + public function castToBool(mixed $value): mixed; } diff --git a/app/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/AdapterWrapper.php b/app/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/AdapterWrapper.php index 7df50a8e8..09ee619dc 100644 --- a/app/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/AdapterWrapper.php +++ b/app/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/AdapterWrapper.php @@ -1,4 +1,5 @@ */ abstract class AdapterWrapper implements AdapterInterface, WrapperInterface { /** * @var \Phinx\Db\Adapter\AdapterInterface */ - protected $adapter; + protected AdapterInterface $adapter; /** * @inheritDoc @@ -84,7 +89,7 @@ public function hasOption(string $name): bool /** * @inheritDoc */ - public function getOption(string $name) + public function getOption(string $name): mixed { return $this->adapter->getOption($name); } @@ -160,7 +165,7 @@ public function execute(string $sql, array $params = []): int /** * @inheritDoc */ - public function query(string $sql, array $params = []) + public function query(string $sql, array $params = []): mixed { return $this->getAdapter()->query($sql, $params); } @@ -184,7 +189,7 @@ public function bulkinsert(Table $table, array $rows): void /** * @inheritDoc */ - public function fetchRow(string $sql) + public function fetchRow(string $sql): array|false { return $this->getAdapter()->fetchRow($sql); } @@ -368,7 +373,7 @@ public function hasColumn(string $tableName, string $columnName): bool /** * @inheritDoc */ - public function hasIndex(string $tableName, $columns): bool + public function hasIndex(string $tableName, string|array $columns): bool { return $this->getAdapter()->hasIndex($tableName, $columns); } @@ -400,7 +405,7 @@ public function hasForeignKey(string $tableName, $columns, ?string $constraint = /** * @inheritDoc */ - public function getSqlType($type, ?int $limit = null): array + public function getSqlType(Literal|string $type, ?int $limit = null): array { return $this->getAdapter()->getSqlType($type, $limit); } @@ -456,7 +461,7 @@ public function truncateTable(string $tableName): void /** * @inheritDoc */ - public function castToBool($value) + public function castToBool($value): mixed { return $this->getAdapter()->castToBool($value); } @@ -464,7 +469,7 @@ public function castToBool($value) /** * @return \PDO */ - public function getConnection() + public function getConnection(): PDO { return $this->getAdapter()->getConnection(); } @@ -480,8 +485,40 @@ public function executeActions(Table $table, array $actions): void /** * @inheritDoc */ - public function getQueryBuilder(): Query + public function getQueryBuilder(string $type): Query + { + return $this->getAdapter()->getQueryBuilder($type); + } + + /** + * @inheritDoc + */ + public function getSelectBuilder(): SelectQuery + { + return $this->getAdapter()->getSelectBuilder(); + } + + /** + * @inheritDoc + */ + public function getInsertBuilder(): InsertQuery + { + return $this->getAdapter()->getInsertBuilder(); + } + + /** + * @inheritDoc + */ + public function getUpdateBuilder(): UpdateQuery + { + return $this->getAdapter()->getUpdateBuilder(); + } + + /** + * @inheritDoc + */ + public function getDeleteBuilder(): DeleteQuery { - return $this->getAdapter()->getQueryBuilder(); + return $this->getAdapter()->getDeleteBuilder(); } } diff --git a/app/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/DirectActionInterface.php b/app/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/DirectActionInterface.php index 64b30f68a..81985c1a5 100644 --- a/app/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/DirectActionInterface.php +++ b/app/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/DirectActionInterface.php @@ -1,4 +1,5 @@ */ class MysqlAdapter extends PdoAdapter { /** * @var string[] */ - protected static $specificColumnTypes = [ + protected static array $specificColumnTypes = [ self::PHINX_TYPE_ENUM, self::PHINX_TYPE_SET, self::PHINX_TYPE_YEAR, @@ -45,7 +45,7 @@ class MysqlAdapter extends PdoAdapter /** * @var bool[] */ - protected $signedColumnTypes = [ + protected array $signedColumnTypes = [ self::PHINX_TYPE_INTEGER => true, self::PHINX_TYPE_TINY_INTEGER => true, self::PHINX_TYPE_SMALL_INTEGER => true, @@ -149,7 +149,7 @@ public function connect(): void if (strpos($key, 'mysql_attr_') === 0) { $pdoConstant = '\PDO::' . strtoupper($key); if (!defined($pdoConstant)) { - throw new \UnexpectedValueException('Invalid PDO attribute: ' . $key . ' (' . $pdoConstant . ')'); + throw new UnexpectedValueException('Invalid PDO attribute: ' . $key . ' (' . $pdoConstant . ')'); } $driverOptions[constant($pdoConstant)] = $option; } @@ -252,7 +252,7 @@ protected function hasTableWithSchema(string $schema, string $tableName): bool FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = '%s' AND TABLE_NAME = '%s'", $schema, - $tableName + $tableName, )); return !empty($result); @@ -272,7 +272,7 @@ public function createTable(Table $table, array $columns = [], array $indexes = $options = array_merge( $defaultOptions, array_intersect_key($this->getOptions(), $defaultOptions), - $table->getOptions() + $table->getOptions(), ); // Add the default primary key @@ -385,7 +385,7 @@ protected function getChangePrimaryKeyInstructions(Table $table, $newColumns): A } else { throw new InvalidArgumentException(sprintf( 'Invalid value for primary key: %s', - json_encode($newColumns) + json_encode($newColumns), )); } $sql .= ')'; @@ -419,7 +419,7 @@ protected function getRenameTableInstructions(string $tableName, string $newTabl $sql = sprintf( 'RENAME TABLE %s TO %s', $this->quoteTableName($tableName), - $this->quoteTableName($newTableName) + $this->quoteTableName($newTableName), ); return new AlterInstructions([], [$sql]); @@ -443,7 +443,7 @@ public function truncateTable(string $tableName): void { $sql = sprintf( 'TRUNCATE TABLE %s', - $this->quoteTableName($tableName) + $this->quoteTableName($tableName), ); $this->execute($sql); @@ -455,7 +455,7 @@ public function truncateTable(string $tableName): void public function getColumns(string $tableName): array { $columns = []; - $rows = $this->fetchAll(sprintf('SHOW COLUMNS FROM %s', $this->quoteTableName($tableName))); + $rows = $this->fetchAll(sprintf('SHOW FULL COLUMNS FROM %s', $this->quoteTableName($tableName))); foreach ($rows as $columnInfo) { $phinxType = $this->getPhinxType($columnInfo['Type']); @@ -465,12 +465,17 @@ public function getColumns(string $tableName): array ->setType($phinxType['name']) ->setSigned(strpos($columnInfo['Type'], 'unsigned') === false) ->setLimit($phinxType['limit']) - ->setScale($phinxType['scale']); + ->setScale($phinxType['scale']) + ->setComment($columnInfo['Comment']); if ($columnInfo['Extra'] === 'auto_increment') { $column->setIdentity(true); } + if ($columnInfo['Extra'] === 'on update CURRENT_TIMESTAMP') { + $column->setUpdate('CURRENT_TIMESTAMP'); + } + if (isset($phinxType['values'])) { $column->setValues($phinxType['values']); } @@ -482,8 +487,8 @@ public function getColumns(string $tableName): array $column->getType(), array_merge( static::PHINX_TYPES_GEOSPATIAL, - [static::PHINX_TYPE_BLOB, static::PHINX_TYPE_JSON, static::PHINX_TYPE_TEXT] - ) + [static::PHINX_TYPE_BLOB, static::PHINX_TYPE_JSON, static::PHINX_TYPE_TEXT], + ), ) ) { // The default that comes back from MySQL for these types prefixes the collation type and @@ -523,7 +528,7 @@ protected function getAddColumnInstructions(Table $table, Column $column): Alter $alter = sprintf( 'ADD %s %s', $this->quoteColumnName($column->getName()), - $this->getColumnSqlDefinition($column) + $this->getColumnSqlDefinition($column), ); $alter .= $this->afterClause($column); @@ -564,7 +569,17 @@ protected function getRenameColumnInstructions(string $tableName, string $column if (strcasecmp($row['Field'], $columnName) === 0) { $null = $row['Null'] === 'NO' ? 'NOT NULL' : 'NULL'; $comment = isset($row['Comment']) ? ' COMMENT ' . '\'' . addslashes($row['Comment']) . '\'' : ''; - $extra = ' ' . strtoupper($row['Extra']); + + // create the extra string by also filtering out the DEFAULT_GENERATED option (MySQL 8 fix) + $extras = array_filter(explode(' ', strtoupper($row['Extra'])), function ($value) { + if ($value == 'DEFAULT_GENERATED') { + return false; + } + + return true; + }); + $extra = ' ' . implode(' ', $extras); + if (($row['Default'] !== null)) { $extra .= $this->getDefaultValueDefinition($row['Default']); } @@ -574,7 +589,7 @@ protected function getRenameColumnInstructions(string $tableName, string $column 'CHANGE COLUMN %s %s %s', $this->quoteColumnName($columnName), $this->quoteColumnName($newColumnName), - $definition + $definition, ); return new AlterInstructions([$alter]); @@ -583,7 +598,7 @@ protected function getRenameColumnInstructions(string $tableName, string $column throw new InvalidArgumentException(sprintf( "The specified column doesn't exist: " . - $columnName + $columnName, )); } @@ -597,7 +612,7 @@ protected function getChangeColumnInstructions(string $tableName, string $column $this->quoteColumnName($columnName), $this->quoteColumnName($newColumn->getName()), $this->getColumnSqlDefinition($newColumn), - $this->afterClause($newColumn) + $this->afterClause($newColumn), ); return new AlterInstructions([$alter]); @@ -636,7 +651,7 @@ protected function getIndexes(string $tableName): array /** * @inheritDoc */ - public function hasIndex(string $tableName, $columns): bool + public function hasIndex(string $tableName, string|array $columns): bool { if (is_string($columns)) { $columns = [$columns]; // str to array @@ -683,14 +698,14 @@ protected function getAddIndexInstructions(Table $table, Index $index): AlterIns $alter = sprintf( 'ALTER TABLE %s ADD %s', $this->quoteTableName($table->getName()), - $this->getIndexSqlDefinition($index) + $this->getIndexSqlDefinition($index), ); $instructions->addPostStep($alter); } else { $alter = sprintf( 'ADD %s', - $this->getIndexSqlDefinition($index) + $this->getIndexSqlDefinition($index), ); $instructions->addAlter($alter); @@ -717,14 +732,14 @@ protected function getDropIndexByColumnsInstructions(string $tableName, $columns if ($columns == $index['columns']) { return new AlterInstructions([sprintf( 'DROP INDEX %s', - $this->quoteColumnName($indexName) + $this->quoteColumnName($indexName), )]); } } throw new InvalidArgumentException(sprintf( "The specified index on columns '%s' does not exist", - implode(',', $columns) + implode(',', $columns), )); } @@ -741,14 +756,14 @@ protected function getDropIndexByNameInstructions(string $tableName, $indexName) if ($name === $indexName) { return new AlterInstructions([sprintf( 'DROP INDEX %s', - $this->quoteColumnName($indexName) + $this->quoteColumnName($indexName), )]); } } throw new InvalidArgumentException(sprintf( "The specified index name '%s' does not exist", - $indexName + $indexName, )); } @@ -765,14 +780,13 @@ public function hasPrimaryKey(string $tableName, $columns, ?string $constraint = if ($constraint) { return $primaryKey['constraint'] === $constraint; - } else { - if (is_string($columns)) { - $columns = [$columns]; // str to array - } - $missingColumns = array_diff($columns, $primaryKey['columns']); - - return empty($missingColumns); } + + // Normalize the columns for comparison + $primaryKeyColumns = array_map('mb_strtolower', $primaryKey['columns']); + $columns = array_map('mb_strtolower', (array)$columns); + + return $primaryKeyColumns === $columns; } /** @@ -795,7 +809,7 @@ public function getPrimaryKey(string $tableName): array AND t.TABLE_SCHEMA='%s' AND t.TABLE_NAME='%s'", $options['name'], - $tableName + $tableName, )); $primaryKey = [ @@ -814,9 +828,6 @@ public function getPrimaryKey(string $tableName): array */ public function hasForeignKey(string $tableName, $columns, ?string $constraint = null): bool { - if (is_string($columns)) { - $columns = [$columns]; // str to array - } $foreignKeys = $this->getForeignKeys($tableName); if ($constraint) { if (isset($foreignKeys[$constraint])) { @@ -826,8 +837,10 @@ public function hasForeignKey(string $tableName, $columns, ?string $constraint = return false; } + $columns = array_map('mb_strtolower', (array)$columns); + foreach ($foreignKeys as $key) { - if ($columns == $key['columns']) { + if (array_map('mb_strtolower', $key['columns']) === $columns) { return true; } } @@ -861,7 +874,7 @@ protected function getForeignKeys(string $tableName): array AND TABLE_NAME = '%s' ORDER BY POSITION_IN_UNIQUE_CONSTRAINT", empty($schema) ? 'DATABASE()' : "'$schema'", - $tableName + $tableName, )); foreach ($rows as $row) { $foreignKeys[$row['CONSTRAINT_NAME']]['table'] = $row['TABLE_NAME']; @@ -880,7 +893,7 @@ protected function getAddForeignKeyInstructions(Table $table, ForeignKey $foreig { $alter = sprintf( 'ADD %s', - $this->getForeignKeySqlDefinition($foreignKey) + $this->getForeignKeySqlDefinition($foreignKey), ); return new AlterInstructions([$alter]); @@ -893,7 +906,7 @@ protected function getDropForeignKeyInstructions(string $tableName, string $cons { $alter = sprintf( 'DROP FOREIGN KEY %s', - $constraint + $constraint, ); return new AlterInstructions([$alter]); @@ -908,32 +921,29 @@ protected function getDropForeignKeyByColumnsInstructions(string $tableName, arr { $instructions = new AlterInstructions(); - foreach ($columns as $column) { - $rows = $this->fetchAll(sprintf( - "SELECT - CONSTRAINT_NAME - FROM information_schema.KEY_COLUMN_USAGE - WHERE REFERENCED_TABLE_SCHEMA = DATABASE() - AND REFERENCED_TABLE_NAME IS NOT NULL - AND TABLE_NAME = '%s' - AND COLUMN_NAME = '%s' - ORDER BY POSITION_IN_UNIQUE_CONSTRAINT", - $tableName, - $column - )); + $columns = array_map('mb_strtolower', $columns); - foreach ($rows as $row) { - $instructions->merge($this->getDropForeignKeyInstructions($tableName, $row['CONSTRAINT_NAME'])); + $matches = []; + $foreignKeys = $this->getForeignKeys($tableName); + foreach ($foreignKeys as $name => $key) { + if (array_map('mb_strtolower', $key['columns']) === $columns) { + $matches[] = $name; } } - if (empty($instructions->getAlterParts())) { + if (empty($matches)) { throw new InvalidArgumentException(sprintf( - "Not foreign key on columns '%s' exist", - implode(',', $columns) + 'No foreign key on column(s) `%s` exists', + implode(', ', $columns), )); } + foreach ($matches as $name) { + $instructions->merge( + $this->getDropForeignKeyInstructions($tableName, $name), + ); + } + return $instructions; } @@ -942,8 +952,9 @@ protected function getDropForeignKeyByColumnsInstructions(string $tableName, arr * * @throws \Phinx\Db\Adapter\UnsupportedColumnTypeException */ - public function getSqlType($type, ?int $limit = null): array + public function getSqlType(Literal|string $type, ?int $limit = null): array { + $type = (string)$type; switch ($type) { case static::PHINX_TYPE_FLOAT: case static::PHINX_TYPE_DOUBLE: @@ -1116,7 +1127,7 @@ public function getSqlType($type, ?int $limit = null): array * @throws \Phinx\Db\Adapter\UnsupportedColumnTypeException * @return array Phinx type */ - public function getPhinxType($sqlTypeDef) + public function getPhinxType(string $sqlTypeDef): array { $matches = []; if (!preg_match('/^([\w]+)(\(([\d]+)*(,([\d]+))*\))*(.+)*$/', $sqlTypeDef, $matches)) { @@ -1290,13 +1301,13 @@ public function createDatabase(string $name, array $options = []): void if (isset($options['collation'])) { $this->execute(sprintf( - 'CREATE DATABASE `%s` DEFAULT CHARACTER SET `%s` COLLATE `%s`', - $name, + 'CREATE DATABASE %s DEFAULT CHARACTER SET `%s` COLLATE `%s`', + $this->quoteColumnName($name), $charset, - $options['collation'] + $options['collation'], )); } else { - $this->execute(sprintf('CREATE DATABASE `%s` DEFAULT CHARACTER SET `%s`', $name, $charset)); + $this->execute(sprintf('CREATE DATABASE %s DEFAULT CHARACTER SET `%s`', $this->quoteColumnName($name), $charset)); } } @@ -1308,8 +1319,8 @@ public function hasDatabase(string $name): bool $rows = $this->fetchAll( sprintf( 'SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = \'%s\'', - $name - ) + $name, + ), ); foreach ($rows as $row) { @@ -1326,7 +1337,7 @@ public function hasDatabase(string $name): bool */ public function dropDatabase(string $name): void { - $this->execute(sprintf('DROP DATABASE IF EXISTS `%s`', $name)); + $this->execute(sprintf('DROP DATABASE IF EXISTS %s', $this->quoteColumnName($name))); $this->createdTables = []; } @@ -1366,7 +1377,7 @@ protected function getColumnSqlDefinition(Column $column): string $def .= $column->isNull() ? ' NULL' : ' NOT NULL'; if ( - version_compare($this->getAttribute(\PDO::ATTR_SERVER_VERSION), '8', '>=') + version_compare($this->getAttribute(PDO::ATTR_SERVER_VERSION), '8', '>=') && in_array($column->getType(), static::PHINX_TYPES_GEOSPATIAL) && !is_null($column->getSrid()) ) { @@ -1378,14 +1389,14 @@ protected function getColumnSqlDefinition(Column $column): string $default = $column->getDefault(); // MySQL 8 supports setting default for the following tested types, but only if they are "cast as expressions" if ( - version_compare($this->getAttribute(\PDO::ATTR_SERVER_VERSION), '8', '>=') && + version_compare($this->getAttribute(PDO::ATTR_SERVER_VERSION), '8', '>=') && is_string($default) && in_array( $column->getType(), array_merge( static::PHINX_TYPES_GEOSPATIAL, - [static::PHINX_TYPE_BLOB, static::PHINX_TYPE_JSON, static::PHINX_TYPE_TEXT] - ) + [static::PHINX_TYPE_BLOB, static::PHINX_TYPE_JSON, static::PHINX_TYPE_TEXT], + ), ) ) { $default = Literal::from('(' . $this->getConnection()->quote($column->getDefault()) . ')'); @@ -1509,7 +1520,7 @@ public function describeTable(string $tableName): array WHERE table_schema = '%s' AND table_name = '%s'", $options['name'], - $tableName + $tableName, ); $table = $this->fetchRow($sql); @@ -1532,6 +1543,10 @@ public function getColumnTypes(): array */ public function getDecoratedConnection(): Connection { + if (isset($this->decoratedConnection)) { + return $this->decoratedConnection; + } + $options = $this->getOptions(); $options = [ 'username' => $options['user'] ?? null, @@ -1540,9 +1555,6 @@ public function getDecoratedConnection(): Connection 'quoteIdentifiers' => true, ] + $options; - $driver = new MysqlDriver($options); - $driver->setConnection($this->connection); - - return new Connection(['driver' => $driver] + $options); + return $this->decoratedConnection = $this->buildConnection(MysqlDriver::class, $options); } } diff --git a/app/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/PdoAdapter.php b/app/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/PdoAdapter.php index 73e98a666..46e6b0870 100644 --- a/app/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/PdoAdapter.php +++ b/app/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/PdoAdapter.php @@ -1,4 +1,5 @@ */ abstract class PdoAdapter extends AbstractAdapter implements DirectActionInterface { /** * @var \PDO|null */ - protected $connection; + protected ?PDO $connection = null; + + /** + * @var \Cake\Database\Connection|null + */ + protected ?Connection $decoratedConnection = null; /** * Writes a message to stdout if verbose output is on @@ -55,7 +68,7 @@ abstract class PdoAdapter extends AbstractAdapter implements DirectActionInterfa * @param string $message The message to show * @return void */ - protected function verboseLog($message): void + protected function verboseLog(string $message): void { if ( !$this->isDryRunEnabled() && @@ -76,8 +89,13 @@ protected function verboseLog($message): void * @param array $options Connection options * @return \PDO */ - protected function createPdoConnection(string $dsn, ?string $username = null, ?string $password = null, array $options = []): PDO - { + protected function createPdoConnection( + string $dsn, + ?string $username = null, + #[SensitiveParameter] + ?string $password = null, + array $options = [], + ): PDO { $adapterOptions = $this->getOptions() + [ 'attr_errmode' => PDO::ERRMODE_EXCEPTION, ]; @@ -89,7 +107,7 @@ protected function createPdoConnection(string $dsn, ?string $username = null, ?s if (strpos($key, 'attr_') === 0) { $pdoConstant = '\PDO::' . strtoupper($key); if (!defined($pdoConstant)) { - throw new \UnexpectedValueException('Invalid PDO attribute: ' . $key . ' (' . $pdoConstant . ')'); + throw new UnexpectedValueException('Invalid PDO attribute: ' . $key . ' (' . $pdoConstant . ')'); } $db->setAttribute(constant($pdoConstant), $option); } @@ -97,7 +115,7 @@ protected function createPdoConnection(string $dsn, ?string $username = null, ?s } catch (PDOException $e) { throw new InvalidArgumentException(sprintf( 'There was a problem connecting to the database: %s', - $e->getMessage() + $e->getMessage(), ), 0, $e); } @@ -138,7 +156,7 @@ public function setConnection(PDO $connection): AdapterInterface ->addColumn( 'migration_name', 'string', - ['limit' => 100, 'after' => 'version', 'default' => null, 'null' => true] + ['limit' => 100, 'after' => 'version', 'default' => null, 'null' => true], ) ->save(); } @@ -181,8 +199,8 @@ abstract public function disconnect(): void; */ public function execute(string $sql, array $params = []): int { - $sql = rtrim($sql, "; \t\n\r\0\x0B") . ';'; - $this->verboseLog($sql); + $sql = rtrim($sql, "; \t\n\r\0\x0B"); + $this->verboseLog($sql . ';'); if ($this->isDryRunEnabled()) { return 0; @@ -206,21 +224,77 @@ public function execute(string $sql, array $params = []): int */ abstract public function getDecoratedConnection(): Connection; + /** + * Build connection instance. + * + * @param class-string<\Cake\Database\Driver> $driverClass Driver class name. + * @param array $options Options. + * @return \Cake\Database\Connection + */ + protected function buildConnection(string $driverClass, array $options): Connection + { + $driver = new $driverClass($options); + $prop = new ReflectionProperty($driver, 'pdo'); + $prop->setValue($driver, $this->connection); + + return new Connection(['driver' => $driver] + $options); + } + + /** + * @inheritDoc + */ + public function getQueryBuilder(string $type): Query + { + return match ($type) { + Query::TYPE_SELECT => $this->getDecoratedConnection()->selectQuery(), + Query::TYPE_INSERT => $this->getDecoratedConnection()->insertQuery(), + Query::TYPE_UPDATE => $this->getDecoratedConnection()->updateQuery(), + Query::TYPE_DELETE => $this->getDecoratedConnection()->deleteQuery(), + default => throw new InvalidArgumentException( + 'Query type must be one of: `select`, `insert`, `update`, `delete`.', + ) + }; + } + /** * @inheritDoc */ - public function getQueryBuilder(): Query + public function getSelectBuilder(): SelectQuery { - return $this->getDecoratedConnection()->newQuery(); + return $this->getDecoratedConnection()->selectQuery(); + } + + /** + * @inheritDoc + */ + public function getInsertBuilder(): InsertQuery + { + return $this->getDecoratedConnection()->insertQuery(); + } + + /** + * @inheritDoc + */ + public function getUpdateBuilder(): UpdateQuery + { + return $this->getDecoratedConnection()->updateQuery(); + } + + /** + * @inheritDoc + */ + public function getDeleteBuilder(): DeleteQuery + { + return $this->getDecoratedConnection()->deleteQuery(); } /** * Executes a query and returns PDOStatement. * * @param string $sql SQL - * @return \PDOStatement|false + * @return mixed */ - public function query(string $sql, array $params = []) + public function query(string $sql, array $params = []): mixed { if (empty($params)) { return $this->getConnection()->query($sql); @@ -234,7 +308,7 @@ public function query(string $sql, array $params = []) /** * @inheritDoc */ - public function fetchRow(string $sql) + public function fetchRow(string $sql): array|false { return $this->query($sql)->fetch(); } @@ -247,6 +321,29 @@ public function fetchAll(string $sql): array return $this->query($sql)->fetchAll(); } + /** + * Get the parameters array for prepared insert statement + * + * @param array $params Parameters array to be filled + * @param array $row Row to be inserted into DB + */ + protected function getInsertParameters(array &$params, array $row): void + { + foreach ($row as $value) { + if ($value instanceof Literal) { + continue; + } elseif ($value instanceof DateTime) { + $params[] = $value->toDateTimeString(); + } elseif ($value instanceof Date) { + $params[] = $value->toDateString(); + } elseif (is_bool($value)) { + $params[] = $this->castToBool($value); + } else { + $params[] = $value; + } + } + } + /** * @inheritDoc */ @@ -254,10 +351,10 @@ public function insert(Table $table, array $row): void { $sql = sprintf( 'INSERT INTO %s ', - $this->quoteTableName($table->getName()) + $this->quoteTableName($table->getName()), ); $columns = array_keys($row); - $sql .= '(' . implode(', ', array_map([$this, 'quoteColumnName'], $columns)) . ')'; + $sql .= '(' . implode(', ', array_map([$this, 'quoteColumnName'], $columns)) . ') ' . $this->getInsertOverride() . 'VALUES '; foreach ($row as $column => $value) { if (is_bool($value)) { @@ -266,12 +363,19 @@ public function insert(Table $table, array $row): void } if ($this->isDryRunEnabled()) { - $sql .= ' VALUES (' . implode(', ', array_map([$this, 'quoteValue'], $row)) . ');'; + $sql .= '(' . implode(', ', array_map([$this, 'quoteValue'], $row)) . ');'; $this->output->writeln($sql); } else { - $sql .= ' VALUES (' . implode(', ', array_fill(0, count($columns), '?')) . ')'; + $sql .= '('; + $values = []; + foreach ($row as $value) { + $values[] = $value instanceof Literal ? (string)$value : '?'; + } + $params = []; + $this->getInsertParameters($params, $row); + $sql .= implode(', ', $values) . ')'; $stmt = $this->getConnection()->prepare($sql); - $stmt->execute(array_values($row)); + $stmt->execute($params); } } @@ -281,16 +385,30 @@ public function insert(Table $table, array $row): void * @param mixed $value The value to quote * @return mixed */ - protected function quoteValue($value) + protected function quoteValue(mixed $value): mixed { if (is_numeric($value)) { return $value; } + if (is_bool($value)) { + return $this->castToBool($value); + } + if ($value === null) { return 'null'; } + if ($value instanceof Literal) { + return (string)$value; + } + + if ($value instanceof DateTime) { + $value = $value->toDateTimeString(); + } elseif ($value instanceof Date) { + $value = $value->toDateString(); + } + return $this->getConnection()->quote($value); } @@ -312,11 +430,11 @@ public function bulkinsert(Table $table, array $rows): void { $sql = sprintf( 'INSERT INTO %s ', - $this->quoteTableName($table->getName()) + $this->quoteTableName($table->getName()), ); $current = current($rows); $keys = array_keys($current); - $sql .= '(' . implode(', ', array_map([$this, 'quoteColumnName'], $keys)) . ') VALUES '; + $sql .= '(' . implode(', ', array_map([$this, 'quoteColumnName'], $keys)) . ') ' . $this->getInsertOverride() . 'VALUES '; if ($this->isDryRunEnabled()) { $values = array_map(function ($row) { @@ -325,28 +443,36 @@ public function bulkinsert(Table $table, array $rows): void $sql .= implode(', ', $values) . ';'; $this->output->writeln($sql); } else { - $count_keys = count($keys); - $query = '(' . implode(', ', array_fill(0, $count_keys, '?')) . ')'; - $count_vars = count($rows); - $queries = array_fill(0, $count_vars, $query); + $queries = []; + foreach ($rows as $row) { + $values = []; + foreach ($row as $value) { + $values[] = $value instanceof Literal ? (string)$value : '?'; + } + $queries[] = '(' . implode(', ', $values) . ')'; + } $sql .= implode(',', $queries); $stmt = $this->getConnection()->prepare($sql); - $vals = []; + $params = []; foreach ($rows as $row) { - foreach ($row as $v) { - if (is_bool($v)) { - $vals[] = $this->castToBool($v); - } else { - $vals[] = $v; - } - } + $this->getInsertParameters($params, $row); } - $stmt->execute($vals); + $stmt->execute($params); } } + /** + * Returns override clause for insert operations, to be befort `VALUES` keyword. + * + * @return string + */ + protected function getInsertOverride(): string + { + return ''; + } + /** * @inheritDoc */ @@ -414,7 +540,7 @@ public function migrated(MigrationInterface $migration, string $direction, strin substr($migration->getName(), 0, 100), $startTime, $endTime, - $this->castToBool(false) + $this->castToBool(false), ); $this->execute($sql); @@ -424,7 +550,7 @@ public function migrated(MigrationInterface $migration, string $direction, strin "DELETE FROM %s WHERE %s = '%s'", $this->quoteTableName($this->getSchemaTableName()), $this->quoteColumnName('version'), - $migration->getVersion() + $migration->getVersion(), ); $this->execute($sql); @@ -447,8 +573,8 @@ public function toggleBreakpoint(MigrationInterface $migration): AdapterInterfac $this->castToBool(false), $this->quoteColumnName('version'), $migration->getVersion(), - $this->quoteColumnName('start_time') - ) + $this->quoteColumnName('start_time'), + ), ); return $this; @@ -465,8 +591,8 @@ public function resetAllBreakpoints(): int $this->quoteTableName($this->getSchemaTableName()), $this->quoteColumnName('breakpoint'), $this->castToBool(false), - $this->quoteColumnName('start_time') - ) + $this->quoteColumnName('start_time'), + ), ); } @@ -507,8 +633,8 @@ protected function markBreakpoint(MigrationInterface $migration, bool $state): A $this->castToBool($state), $this->quoteColumnName('start_time'), $this->quoteColumnName('version'), - $migration->getVersion() - ) + $migration->getVersion(), + ), ); return $this; @@ -573,7 +699,7 @@ public function getColumnTypes(): array /** * @inheritDoc */ - public function castToBool($value) + public function castToBool($value): mixed { return (bool)$value ? 1 : 0; } @@ -585,7 +711,7 @@ public function castToBool($value) * @param int $attribute One of the PDO::ATTR_* constants * @return mixed */ - public function getAttribute(int $attribute) + public function getAttribute(int $attribute): mixed { return $this->connection->getAttribute($attribute); } @@ -594,19 +720,19 @@ public function getAttribute(int $attribute) * Get the definition for a `DEFAULT` statement. * * @param mixed $default Default value - * @param string|null $columnType column type added + * @param string|\Phinx\Util\Literal|null $columnType Column type * @return string */ - protected function getDefaultValueDefinition($default, ?string $columnType = null): string + protected function getDefaultValueDefinition(mixed $default, string|Literal|null $columnType = null): string { if ($default instanceof Literal) { $default = (string)$default; - } elseif (is_string($default) && strpos($default, 'CURRENT_TIMESTAMP') !== 0) { + } elseif (is_string($default) && stripos($default, 'CURRENT_TIMESTAMP') !== 0) { // Ensure a defaults of CURRENT_TIMESTAMP(3) is not quoted. $default = $this->getConnection()->quote($default); } elseif (is_bool($default)) { $default = $this->castToBool($default); - } elseif ($default !== null && $columnType === static::PHINX_TYPE_BOOLEAN) { + } elseif ($default !== null && (string)$columnType === static::PHINX_TYPE_BOOLEAN) { $default = $this->castToBool((bool)$default); } @@ -734,7 +860,7 @@ public function dropIndex(string $tableName, $columns): void * @param string|string[] $columns Column(s) * @return \Phinx\Db\Util\AlterInstructions */ - abstract protected function getDropIndexByColumnsInstructions(string $tableName, $columns): AlterInstructions; + abstract protected function getDropIndexByColumnsInstructions(string $tableName, string|array $columns): AlterInstructions; /** * @inheritdoc @@ -855,7 +981,7 @@ public function changePrimaryKey(Table $table, $newColumns): void * @param string|string[]|null $newColumns Column name(s) to belong to the primary key, or null to drop the key * @return \Phinx\Db\Util\AlterInstructions */ - abstract protected function getChangePrimaryKeyInstructions(Table $table, $newColumns): AlterInstructions; + abstract protected function getChangePrimaryKeyInstructions(Table $table, string|array|null $newColumns): AlterInstructions; /** * @inheritdoc @@ -907,7 +1033,7 @@ public function executeActions(Table $table, array $actions): void $instructions->merge($this->getChangeColumnInstructions( $table->getName(), $action->getColumnName(), - $action->getColumn() + $action->getColumn(), )); break; @@ -915,7 +1041,7 @@ public function executeActions(Table $table, array $actions): void /** @var \Phinx\Db\Action\DropForeignKey $action */ $instructions->merge($this->getDropForeignKeyByColumnsInstructions( $table->getName(), - $action->getForeignKey()->getColumns() + $action->getForeignKey()->getColumns(), )); break; @@ -923,7 +1049,7 @@ public function executeActions(Table $table, array $actions): void /** @var \Phinx\Db\Action\DropForeignKey $action */ $instructions->merge($this->getDropForeignKeyInstructions( $table->getName(), - $action->getForeignKey()->getConstraint() + $action->getForeignKey()->getConstraint(), )); break; @@ -931,7 +1057,7 @@ public function executeActions(Table $table, array $actions): void /** @var \Phinx\Db\Action\DropIndex $action */ $instructions->merge($this->getDropIndexByNameInstructions( $table->getName(), - $action->getIndex()->getName() + $action->getIndex()->getName(), )); break; @@ -939,14 +1065,14 @@ public function executeActions(Table $table, array $actions): void /** @var \Phinx\Db\Action\DropIndex $action */ $instructions->merge($this->getDropIndexByColumnsInstructions( $table->getName(), - $action->getIndex()->getColumns() + $action->getIndex()->getColumns(), )); break; case $action instanceof DropTable: /** @var \Phinx\Db\Action\DropTable $action */ $instructions->merge($this->getDropTableInstructions( - $table->getName() + $table->getName(), )); break; @@ -954,7 +1080,7 @@ public function executeActions(Table $table, array $actions): void /** @var \Phinx\Db\Action\RemoveColumn $action */ $instructions->merge($this->getDropColumnInstructions( $table->getName(), - $action->getColumn()->getName() + $action->getColumn()->getName(), )); break; @@ -963,7 +1089,7 @@ public function executeActions(Table $table, array $actions): void $instructions->merge($this->getRenameColumnInstructions( $table->getName(), $action->getColumn()->getName(), - $action->getNewName() + $action->getNewName(), )); break; @@ -971,7 +1097,7 @@ public function executeActions(Table $table, array $actions): void /** @var \Phinx\Db\Action\RenameTable $action */ $instructions->merge($this->getRenameTableInstructions( $table->getName(), - $action->getNewName() + $action->getNewName(), )); break; @@ -979,7 +1105,7 @@ public function executeActions(Table $table, array $actions): void /** @var \Phinx\Db\Action\ChangePrimaryKey $action */ $instructions->merge($this->getChangePrimaryKeyInstructions( $table, - $action->getNewColumns() + $action->getNewColumns(), )); break; @@ -987,13 +1113,13 @@ public function executeActions(Table $table, array $actions): void /** @var \Phinx\Db\Action\ChangeComment $action */ $instructions->merge($this->getChangeCommentInstructions( $table, - $action->getNewComment() + $action->getNewComment(), )); break; default: throw new InvalidArgumentException( - sprintf("Don't know how to execute action: '%s'", get_class($action)) + sprintf("Don't know how to execute action: '%s'", get_class($action)), ); } } diff --git a/app/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/PostgresAdapter.php b/app/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/PostgresAdapter.php index 1d17e5aa6..865d36ba0 100644 --- a/app/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/PostgresAdapter.php +++ b/app/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/PostgresAdapter.php @@ -1,4 +1,5 @@ = 10.0) * * @var bool */ - protected $useIdentity; + protected bool $useIdentity; + + /** + * @var string + */ + protected string $schema = 'public'; + + /** + * {@inheritDoc} + */ + public function setOptions(array $options): AdapterInterface + { + if (!empty($options['schema'])) { + $this->schema = $options['schema']; + } + + parent::setOptions($options); + + return $this; + } + + /** + * {@inheritDoc} + */ + public function setConnection(PDO $connection): AdapterInterface + { + // always set here since connect() isn't always called + $this->useIdentity = (float)$connection->getAttribute(PDO::ATTR_SERVER_VERSION) >= 10; + + return parent::setConnection($connection); + } /** * {@inheritDoc} @@ -105,12 +141,10 @@ public function connect(): void throw new InvalidArgumentException( sprintf('Schema does not exists: %s', $options['schema']), 0, - $exception + $exception, ); } - $this->useIdentity = (float)$db->getAttribute(PDO::ATTR_SERVER_VERSION) >= 10; - $this->setConnection($db); } } @@ -201,8 +235,8 @@ public function hasTable(string $tableName): bool WHERE table_schema = %s AND table_name = %s', $this->getConnection()->quote($parts['schema']), - $this->getConnection()->quote($parts['table']) - ) + $this->getConnection()->quote($parts['table']), + ), ); return $result->rowCount() === 1; @@ -291,7 +325,7 @@ public function createTable(Table $table, array $columns = [], array $indexes = $queries[] = sprintf( 'COMMENT ON TABLE %s IS %s', $this->quoteTableName($table->getName()), - $this->getConnection()->quote($options['comment']) + $this->getConnection()->quote($options['comment']), ); } @@ -318,7 +352,7 @@ protected function getChangePrimaryKeyInstructions(Table $table, $newColumns): A if (!empty($primaryKey['constraint'])) { $sql = sprintf( 'DROP CONSTRAINT %s', - $this->quoteColumnName($primaryKey['constraint']) + $this->quoteColumnName($primaryKey['constraint']), ); $instructions->addAlter($sql); } @@ -327,7 +361,7 @@ protected function getChangePrimaryKeyInstructions(Table $table, $newColumns): A if (!empty($newColumns)) { $sql = sprintf( 'ADD CONSTRAINT %s PRIMARY KEY (', - $this->quoteColumnName($parts['table'] . '_pkey') + $this->quoteColumnName($parts['table'] . '_pkey'), ); if (is_string($newColumns)) { // handle primary_key => 'id' $sql .= $this->quoteColumnName($newColumns); @@ -336,7 +370,7 @@ protected function getChangePrimaryKeyInstructions(Table $table, $newColumns): A } else { throw new InvalidArgumentException(sprintf( 'Invalid value for primary key: %s', - json_encode($newColumns) + json_encode($newColumns), )); } $sql .= ')'; @@ -360,7 +394,7 @@ protected function getChangeCommentInstructions(Table $table, ?string $newCommen $sql = sprintf( 'COMMENT ON TABLE %s IS %s', $this->quoteTableName($table->getName()), - $newComment + $newComment, ); $instructions->addPostStep($sql); @@ -376,7 +410,7 @@ protected function getRenameTableInstructions(string $tableName, string $newTabl $sql = sprintf( 'ALTER TABLE %s RENAME TO %s', $this->quoteTableName($tableName), - $this->quoteColumnName($newTableName) + $this->quoteColumnName($newTableName), ); return new AlterInstructions([], [$sql]); @@ -400,7 +434,7 @@ public function truncateTable(string $tableName): void { $sql = sprintf( 'TRUNCATE TABLE %s RESTART IDENTITY', - $this->quoteTableName($tableName) + $this->quoteTableName($tableName), ); $this->execute($sql); @@ -423,7 +457,7 @@ public function getColumns(string $tableName): array ORDER BY ordinal_position', $this->useIdentity ? ', identity_generation' : '', $this->getConnection()->quote($parts['schema']), - $this->getConnection()->quote($parts['table']) + $this->getConnection()->quote($parts['table']), ); $columnsInfo = $this->fetchAll($sql); foreach ($columnsInfo as $columnInfo) { @@ -475,13 +509,7 @@ public function getColumns(string $tableName): array if (in_array($columnType, [static::PHINX_TYPE_TIME, static::PHINX_TYPE_DATETIME], true)) { $column->setPrecision($columnInfo['datetime_precision']); - } elseif ( - !in_array($columnType, [ - self::PHINX_TYPE_SMALL_INTEGER, - self::PHINX_TYPE_INTEGER, - self::PHINX_TYPE_BIG_INTEGER, - ], true) - ) { + } elseif ($columnType === self::PHINX_TYPE_DECIMAL) { $column->setPrecision($columnInfo['numeric_precision']); } $columns[] = $column; @@ -502,7 +530,7 @@ public function hasColumn(string $tableName, string $columnName): bool WHERE table_schema = %s AND table_name = %s AND column_name = %s', $this->getConnection()->quote($parts['schema']), $this->getConnection()->quote($parts['table']), - $this->getConnection()->quote($columnName) + $this->getConnection()->quote($columnName), ); $result = $this->fetchRow($sql); @@ -521,7 +549,7 @@ protected function getAddColumnInstructions(Table $table, Column $column): Alter $this->quoteColumnName($column->getName()), $this->getColumnSqlDefinition($column), $column->isIdentity() && $column->getGenerated() !== null && $this->useIdentity ? - sprintf('GENERATED %s AS IDENTITY', $column->getGenerated()) : '' + sprintf('GENERATED %s AS IDENTITY', $column->getGenerated()) : '', )); if ($column->getComment()) { @@ -539,7 +567,7 @@ protected function getAddColumnInstructions(Table $table, Column $column): Alter protected function getRenameColumnInstructions( string $tableName, string $columnName, - string $newColumnName + string $newColumnName, ): AlterInstructions { $parts = $this->getSchemaName($tableName); $sql = sprintf( @@ -548,7 +576,7 @@ protected function getRenameColumnInstructions( WHERE table_schema = %s AND table_name = %s AND column_name = %s', $this->getConnection()->quote($parts['schema']), $this->getConnection()->quote($parts['table']), - $this->getConnection()->quote($columnName) + $this->getConnection()->quote($columnName), ); $result = $this->fetchRow($sql); @@ -562,8 +590,8 @@ protected function getRenameColumnInstructions( 'ALTER TABLE %s RENAME COLUMN %s TO %s', $this->quoteTableName($tableName), $this->quoteColumnName($columnName), - $this->quoteColumnName($newColumnName) - ) + $this->quoteColumnName($newColumnName), + ), ); return $instructions; @@ -575,7 +603,7 @@ protected function getRenameColumnInstructions( protected function getChangeColumnInstructions( string $tableName, string $columnName, - Column $newColumn + Column $newColumn, ): AlterInstructions { $quotedColumnName = $this->quoteColumnName($columnName); $instructions = new AlterInstructions(); @@ -586,18 +614,18 @@ protected function getChangeColumnInstructions( $sql = sprintf( 'ALTER COLUMN %s TYPE %s', $quotedColumnName, - $this->getColumnSqlDefinition($newColumn) + $this->getColumnSqlDefinition($newColumn), ); if (in_array($newColumn->getType(), ['smallinteger', 'integer', 'biginteger'], true)) { $sql .= sprintf( ' USING (%s::bigint)', - $quotedColumnName + $quotedColumnName, ); } if ($newColumn->getType() === 'uuid') { $sql .= sprintf( ' USING (%s::uuid)', - $quotedColumnName + $quotedColumnName, ); } //NULL and DEFAULT cannot be set while changing column type @@ -609,7 +637,7 @@ protected function getChangeColumnInstructions( $sql .= sprintf( ' USING (CASE WHEN %s IS NULL THEN NULL WHEN %s::int=0 THEN FALSE ELSE TRUE END)', $quotedColumnName, - $quotedColumnName + $quotedColumnName, ); } $instructions->addAlter($sql); @@ -620,7 +648,7 @@ protected function getChangeColumnInstructions( // process identity $sql = sprintf( 'ALTER COLUMN %s', - $quotedColumnName + $quotedColumnName, ); if ($newColumn->isIdentity() && $newColumn->getGenerated() !== null) { if ($column->isIdentity()) { @@ -637,7 +665,7 @@ protected function getChangeColumnInstructions( // process null $sql = sprintf( 'ALTER COLUMN %s', - $quotedColumnName + $quotedColumnName, ); if (!$newColumn->getIdentity() && !$column->getIdentity() && $newColumn->isNull()) { @@ -652,13 +680,13 @@ protected function getChangeColumnInstructions( $instructions->addAlter(sprintf( 'ALTER COLUMN %s SET %s', $quotedColumnName, - $this->getDefaultValueDefinition($newColumn->getDefault(), $newColumn->getType()) + $this->getDefaultValueDefinition($newColumn->getDefault(), $newColumn->getType()), )); } elseif (!$newColumn->getIdentity()) { //drop default $instructions->addAlter(sprintf( 'ALTER COLUMN %s DROP DEFAULT', - $quotedColumnName + $quotedColumnName, )); } @@ -668,7 +696,7 @@ protected function getChangeColumnInstructions( 'ALTER TABLE %s RENAME COLUMN %s TO %s', $this->quoteTableName($tableName), $quotedColumnName, - $this->quoteColumnName($newColumn->getName()) + $this->quoteColumnName($newColumn->getName()), )); } @@ -704,7 +732,7 @@ protected function getDropColumnInstructions(string $tableName, string $columnNa { $alter = sprintf( 'DROP COLUMN %s', - $this->quoteColumnName($columnName) + $this->quoteColumnName($columnName), ); return new AlterInstructions([$alter]); @@ -716,7 +744,7 @@ protected function getDropColumnInstructions(string $tableName, string $columnNa * @param string $tableName Table name * @return array */ - protected function getIndexes($tableName) + protected function getIndexes(string $tableName): array { $parts = $this->getSchemaName($tableName); @@ -744,7 +772,7 @@ protected function getIndexes($tableName) t.relname, i.relname", $this->getConnection()->quote($parts['schema']), - $this->getConnection()->quote($parts['table']) + $this->getConnection()->quote($parts['table']), ); $rows = $this->fetchAll($sql); foreach ($rows as $row) { @@ -760,7 +788,7 @@ protected function getIndexes($tableName) /** * @inheritDoc */ - public function hasIndex(string $tableName, $columns): bool + public function hasIndex(string $tableName, string|array $columns): bool { if (is_string($columns)) { $columns = [$columns]; @@ -820,14 +848,14 @@ protected function getDropIndexByColumnsInstructions(string $tableName, $columns if (empty($a)) { return new AlterInstructions([], [sprintf( 'DROP INDEX IF EXISTS %s', - '"' . ($parts['schema'] . '".' . $this->quoteColumnName($indexName)) + '"' . ($parts['schema'] . '".' . $this->quoteColumnName($indexName)), )]); } } throw new InvalidArgumentException(sprintf( "The specified index on columns '%s' does not exist", - implode(',', $columns) + implode(',', $columns), )); } @@ -840,7 +868,7 @@ protected function getDropIndexByNameInstructions(string $tableName, string $ind $sql = sprintf( 'DROP INDEX IF EXISTS %s', - '"' . ($parts['schema'] . '".' . $this->quoteColumnName($indexName)) + '"' . ($parts['schema'] . '".' . $this->quoteColumnName($indexName)), ); return new AlterInstructions([], [$sql]); @@ -859,14 +887,9 @@ public function hasPrimaryKey(string $tableName, $columns, ?string $constraint = if ($constraint) { return $primaryKey['constraint'] === $constraint; - } else { - if (is_string($columns)) { - $columns = [$columns]; // str to array - } - $missingColumns = array_diff($columns, $primaryKey['columns']); - - return empty($missingColumns); } + + return $primaryKey['columns'] === (array)$columns; } /** @@ -890,7 +913,7 @@ public function getPrimaryKey(string $tableName): array AND tc.table_name = %s ORDER BY kcu.position_in_unique_constraint", $this->getConnection()->quote($parts['schema']), - $this->getConnection()->quote($parts['table']) + $this->getConnection()->quote($parts['table']), )); $primaryKey = [ @@ -909,9 +932,6 @@ public function getPrimaryKey(string $tableName): array */ public function hasForeignKey(string $tableName, $columns, ?string $constraint = null): bool { - if (is_string($columns)) { - $columns = [$columns]; // str to array - } $foreignKeys = $this->getForeignKeys($tableName); if ($constraint) { if (isset($foreignKeys[$constraint])) { @@ -921,9 +941,12 @@ public function hasForeignKey(string $tableName, $columns, ?string $constraint = return false; } + if (is_string($columns)) { + $columns = [$columns]; + } + foreach ($foreignKeys as $key) { - $a = array_diff($columns, $key['columns']); - if (empty($a)) { + if ($key['columns'] === $columns) { return true; } } @@ -952,9 +975,9 @@ protected function getForeignKeys(string $tableName): array JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name WHERE constraint_type = 'FOREIGN KEY' AND tc.table_schema = %s AND tc.table_name = %s - ORDER BY kcu.position_in_unique_constraint", + ORDER BY kcu.ordinal_position", $this->getConnection()->quote($parts['schema']), - $this->getConnection()->quote($parts['table']) + $this->getConnection()->quote($parts['table']), )); foreach ($rows as $row) { $foreignKeys[$row['constraint_name']]['table'] = $row['table_name']; @@ -962,6 +985,10 @@ protected function getForeignKeys(string $tableName): array $foreignKeys[$row['constraint_name']]['referenced_table'] = $row['referenced_table_name']; $foreignKeys[$row['constraint_name']]['referenced_columns'][] = $row['referenced_column_name']; } + foreach ($foreignKeys as $name => $key) { + $foreignKeys[$name]['columns'] = array_values(array_unique($key['columns'])); + $foreignKeys[$name]['referenced_columns'] = array_values(array_unique($key['referenced_columns'])); + } return $foreignKeys; } @@ -973,7 +1000,7 @@ protected function getAddForeignKeyInstructions(Table $table, ForeignKey $foreig { $alter = sprintf( 'ADD %s', - $this->getForeignKeySqlDefinition($foreignKey, $table->getName()) + $this->getForeignKeySqlDefinition($foreignKey, $table->getName()), ); return new AlterInstructions([$alter]); @@ -986,7 +1013,7 @@ protected function getDropForeignKeyInstructions($tableName, $constraint): Alter { $alter = sprintf( 'DROP CONSTRAINT %s', - $this->quoteColumnName($constraint) + $this->quoteColumnName($constraint), ); return new AlterInstructions([$alter]); @@ -999,37 +1026,25 @@ protected function getDropForeignKeyByColumnsInstructions(string $tableName, arr { $instructions = new AlterInstructions(); - $parts = $this->getSchemaName($tableName); - $sql = 'SELECT c.CONSTRAINT_NAME - FROM ( - SELECT CONSTRAINT_NAME, array_agg(COLUMN_NAME::varchar) as columns - FROM information_schema.KEY_COLUMN_USAGE - WHERE TABLE_SCHEMA = %s - AND TABLE_NAME IS NOT NULL - AND TABLE_NAME = %s - AND POSITION_IN_UNIQUE_CONSTRAINT IS NOT NULL - GROUP BY CONSTRAINT_NAME - ) c - WHERE - ARRAY[%s]::varchar[] <@ c.columns AND - ARRAY[%s]::varchar[] @> c.columns'; - - $array = []; - foreach ($columns as $col) { - $array[] = "'$col'"; + $matches = []; + $foreignKeys = $this->getForeignKeys($tableName); + foreach ($foreignKeys as $name => $key) { + if ($key['columns'] === $columns) { + $matches[] = $name; + } } - $rows = $this->fetchAll(sprintf( - $sql, - $this->getConnection()->quote($parts['schema']), - $this->getConnection()->quote($parts['table']), - implode(',', $array), - implode(',', $array) - )); + if (empty($matches)) { + throw new InvalidArgumentException(sprintf( + 'No foreign key on column(s) `%s` exists', + implode(', ', $columns), + )); + } - foreach ($rows as $row) { - $newInstr = $this->getDropForeignKeyInstructions($tableName, $row['constraint_name']); - $instructions->merge($newInstr); + foreach ($matches as $name) { + $instructions->merge( + $this->getDropForeignKeyInstructions($tableName, $name), + ); } return $instructions; @@ -1040,8 +1055,9 @@ protected function getDropForeignKeyByColumnsInstructions(string $tableName, arr * * @throws \Phinx\Db\Adapter\UnsupportedColumnTypeException */ - public function getSqlType($type, ?int $limit = null): array + public function getSqlType(Literal|string $type, ?int $limit = null): array { + $type = (string)$type; switch ($type) { case static::PHINX_TYPE_TEXT: case static::PHINX_TYPE_TIME: @@ -1170,7 +1186,7 @@ public function getPhinxType(string $sqlType): string return static::PHINX_TYPE_MACADDR; default: throw new UnsupportedColumnTypeException( - 'Column type `' . $sqlType . '` is not supported by Postgresql.' + 'Column type `' . $sqlType . '` is not supported by Postgresql.', ); } } @@ -1181,7 +1197,7 @@ public function getPhinxType(string $sqlType): string public function createDatabase(string $name, array $options = []): void { $charset = $options['charset'] ?? 'utf8'; - $this->execute(sprintf("CREATE DATABASE %s WITH ENCODING = '%s'", $name, $charset)); + $this->execute(sprintf("CREATE DATABASE %s WITH ENCODING = '%s'", $this->quoteColumnName($name), $charset)); } /** @@ -1201,7 +1217,7 @@ public function hasDatabase(string $name): bool public function dropDatabase($name): void { $this->disconnect(); - $this->execute(sprintf('DROP DATABASE IF EXISTS %s', $name)); + $this->execute(sprintf('DROP DATABASE IF EXISTS %s', $this->quoteColumnName($name))); $this->createdTables = []; $this->connect(); } @@ -1235,14 +1251,14 @@ protected function getColumnSqlDefinition(Column $column): string $buffer[] = sprintf( '(%s, %s)', $column->getPrecision() ?: $sqlType['precision'], - $column->getScale() ?: $sqlType['scale'] + $column->getScale() ?: $sqlType['scale'], ); } elseif ($sqlType['name'] === self::PHINX_TYPE_GEOMETRY) { // geography type must be written with geometry type and srid, like this: geography(POLYGON,4326) $buffer[] = sprintf( '(%s,%s)', strtoupper($sqlType['type']), - $column->getSrid() ?: $sqlType['srid'] + $column->getSrid() ?: $sqlType['srid'], ); } elseif (in_array($sqlType['name'], [self::PHINX_TYPE_TIME, self::PHINX_TYPE_TIMESTAMP], true)) { if (is_numeric($column->getPrecision())) { @@ -1296,7 +1312,7 @@ protected function getColumnCommentSqlDefinition(Column $column, string $tableNa 'COMMENT ON COLUMN %s.%s IS %s;', $this->quoteTableName($tableName), $this->quoteColumnName($column->getName()), - $comment + $comment, ); } @@ -1343,7 +1359,7 @@ protected function getIndexSqlDefinition(Index $index, string $tableName): strin $this->quoteColumnName($indexName), $this->quoteTableName($tableName), implode(',', $columnNames), - $includedColumns + $includedColumns, ); } @@ -1372,6 +1388,10 @@ protected function getForeignKeySqlDefinition(ForeignKey $foreignKey, string $ta $def .= " ON UPDATE {$foreignKey->getOnUpdate()}"; } + if ($foreignKey->getDeferrableMode()) { + $def .= " {$foreignKey->getDeferrableMode()}"; + } + return $def; } @@ -1381,8 +1401,8 @@ protected function getForeignKeySqlDefinition(ForeignKey $foreignKey, string $ta public function createSchemaTable(): void { // Create the public/custom schema if it doesn't already exist - if ($this->hasSchema($this->getGlobalSchemaName()) === false) { - $this->createSchema($this->getGlobalSchemaName()); + if ($this->hasSchema($this->schema) === false) { + $this->createSchema($this->schema); } $this->setSearchPath(); @@ -1435,7 +1455,7 @@ public function hasSchema(string $schemaName): bool 'SELECT count(*) FROM pg_namespace WHERE nspname = %s', - $this->getConnection()->quote($schemaName) + $this->getConnection()->quote($schemaName), ); $result = $this->fetchRow($sql); @@ -1514,7 +1534,7 @@ public function isValidColumnType(Column $column): bool * @param string|\Phinx\Util\Literal $columnType Column type * @return bool */ - protected function isArrayType($columnType): bool + protected function isArrayType(string|Literal $columnType): bool { if (!preg_match('/^([a-z]+)(?:\[\]){1,}$/', $columnType, $matches)) { return false; @@ -1531,7 +1551,7 @@ protected function isArrayType($columnType): bool */ protected function getSchemaName(string $tableName): array { - $schema = $this->getGlobalSchemaName(); + $schema = $this->schema; $table = $tableName; if (strpos($tableName, '.') !== false) { [$schema, $table] = explode('.', $tableName); @@ -1543,22 +1563,10 @@ protected function getSchemaName(string $tableName): array ]; } - /** - * Gets the schema name. - * - * @return string - */ - protected function getGlobalSchemaName(): string - { - $options = $this->getOptions(); - - return empty($options['schema']) ? 'public' : $options['schema']; - } - /** * @inheritDoc */ - public function castToBool($value) + public function castToBool($value): mixed { return (bool)$value ? 'TRUE' : 'FALSE'; } @@ -1568,19 +1576,20 @@ public function castToBool($value) */ public function getDecoratedConnection(): Connection { + if (isset($this->decoratedConnection)) { + return $this->decoratedConnection; + } + $options = $this->getOptions(); $options = [ 'username' => $options['user'] ?? null, 'password' => $options['pass'] ?? null, 'database' => $options['name'], + 'schema' => $this->schema, 'quoteIdentifiers' => true, ] + $options; - $driver = new PostgresDriver($options); - - $driver->setConnection($this->connection); - - return new Connection(['driver' => $driver] + $options); + return $this->decoratedConnection = $this->buildConnection(PostgresDriver::class, $options); } /** @@ -1593,8 +1602,16 @@ public function setSearchPath(): void $this->execute( sprintf( 'SET search_path TO %s,"$user",public', - $this->quoteSchemaName($this->getGlobalSchemaName()) - ) + $this->quoteSchemaName($this->schema), + ), ); } + + /** + * @inheritDoc + */ + protected function getInsertOverride(): string + { + return $this->useIdentity ? self::OVERRIDE_SYSTEM_VALUE . ' ' : ''; + } } diff --git a/app/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/ProxyAdapter.php b/app/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/ProxyAdapter.php index 979700cec..04c3492b4 100644 --- a/app/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/ProxyAdapter.php +++ b/app/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/ProxyAdapter.php @@ -1,4 +1,5 @@ */ class ProxyAdapter extends AdapterWrapper { /** * @var \Phinx\Db\Action\Action[] */ - protected $commands = []; + protected array $commands = []; /** * @inheritDoc @@ -108,7 +107,7 @@ public function getInvertedCommands(): Intent default: throw new IrreversibleMigrationException(sprintf( 'Cannot reverse a "%s" command', - get_class($command) + get_class($command), )); } } diff --git a/app/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/SQLiteAdapter.php b/app/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/SQLiteAdapter.php index f24907110..1c7c76561 100644 --- a/app/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/SQLiteAdapter.php +++ b/app/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/SQLiteAdapter.php @@ -1,4 +1,5 @@ - * @author Richard McIntyre */ class SQLiteAdapter extends PdoAdapter { public const MEMORY = ':memory:'; + public const DEFAULT_SUFFIX = '.sqlite3'; + /** * List of supported Phinx column types with their SQL equivalents * some types have an affinity appended to ensure they do not receive NUMERIC affinity * * @var string[] */ - protected static $supportedColumnTypes = [ + protected static array $supportedColumnTypes = [ self::PHINX_TYPE_BIG_INTEGER => 'biginteger', self::PHINX_TYPE_BINARY => 'binary_blob', - self::PHINX_TYPE_BINARYUUID => 'binary_blob', + self::PHINX_TYPE_BINARYUUID => 'uuid_blob', self::PHINX_TYPE_BLOB => 'blob', self::PHINX_TYPE_BOOLEAN => 'boolean_integer', self::PHINX_TYPE_CHAR => 'char', @@ -68,7 +69,7 @@ class SQLiteAdapter extends PdoAdapter * * @var string[] */ - protected static $supportedColumnTypeAliases = [ + protected static array $supportedColumnTypeAliases = [ 'varchar' => self::PHINX_TYPE_STRING, 'tinyint' => self::PHINX_TYPE_TINY_INTEGER, 'tinyinteger' => self::PHINX_TYPE_TINY_INTEGER, @@ -91,7 +92,7 @@ class SQLiteAdapter extends PdoAdapter * * @var string[] */ - protected static $unsupportedColumnTypes = [ + protected static array $unsupportedColumnTypes = [ self::PHINX_TYPE_BIT, self::PHINX_TYPE_CIDR, self::PHINX_TYPE_ENUM, @@ -109,7 +110,7 @@ class SQLiteAdapter extends PdoAdapter /** * @var string[] */ - protected $definitionsWithLimits = [ + protected array $definitionsWithLimits = [ 'CHAR', 'CHARACTER', 'VARCHAR', @@ -122,7 +123,7 @@ class SQLiteAdapter extends PdoAdapter /** * @var string */ - protected $suffix = '.sqlite3'; + protected string $suffix = self::DEFAULT_SUFFIX; /** * Indicates whether the database library version is at least the specified version @@ -130,13 +131,24 @@ class SQLiteAdapter extends PdoAdapter * @param string $ver The version to check against e.g. '3.28.0' * @return bool */ - public function databaseVersionAtLeast($ver): bool + public function databaseVersionAtLeast(string $ver): bool { $actual = $this->query('SELECT sqlite_version()')->fetchColumn(); return version_compare($actual, $ver, '>='); } + /** + * Check if the given options represent a memory database + * + * @param array $options Options to check + * @return bool + */ + public static function isMemory(array $options): bool + { + return !empty($options['memory']) || ($options['name'] ?? '') === static::MEMORY; + } + /** * {@inheritDoc} * @@ -170,7 +182,7 @@ public function connect(): void $dsn = 'sqlite:file:' . ($options['name'] ?? '') . '?' . implode('&', $params); } else { // use a memory database if the option was specified - if (!empty($options['memory']) || $options['name'] === static::MEMORY) { + if (SQLiteAdapter::isMemory($options)) { $dsn = 'sqlite:' . static::MEMORY; } else { $dsn = 'sqlite:' . $options['name'] . $this->suffix; @@ -196,21 +208,38 @@ public function connect(): void } /** - * @inheritDoc + * Get the suffix to use for the SQLite database file. + * + * @param array $options Environment options + * @return string */ - public function setOptions(array $options): AdapterInterface + public static function getSuffix(array $options): string { - parent::setOptions($options); + if (SQLiteAdapter::isMemory($options)) { + return ''; + } + $suffix = self::DEFAULT_SUFFIX; if (isset($options['suffix'])) { - $this->suffix = $options['suffix']; + $suffix = $options['suffix']; } //don't "fix" the file extension if it is blank, some people //might want a SQLITE db file with absolutely no extension. - if ($this->suffix !== '' && strpos($this->suffix, '.') !== 0) { - $this->suffix = '.' . $this->suffix; + if ($suffix !== '' && strpos($suffix, '.') !== 0) { + $suffix = '.' . $suffix; } + return $suffix; + } + + /** + * @inheritDoc + */ + public function setOptions(array $options): AdapterInterface + { + parent::setOptions($options); + $this->suffix = self::getSuffix($options); + return $this; } @@ -270,6 +299,39 @@ public function quoteColumnName($columnName): string return '`' . str_replace('`', '``', $columnName) . '`'; } + /** + * Generates a regular expression to match identifiers that may or + * may not be quoted with any of the supported quotes. + * + * @param string $identifier The identifier to match. + * @param bool $spacedNoQuotes Whether the non-quoted identifier requires to be surrounded by whitespace. + * @return string + */ + protected function possiblyQuotedIdentifierRegex(string $identifier, bool $spacedNoQuotes = true): string + { + $identifiers = []; + $identifier = preg_quote($identifier, '/'); + + $hasTick = str_contains($identifier, '`'); + $hasDoubleQuote = str_contains($identifier, '"'); + $hasSingleQuote = str_contains($identifier, "'"); + + $identifiers[] = '\[' . $identifier . '\]'; + $identifiers[] = '`' . ($hasTick ? str_replace('`', '``', $identifier) : $identifier) . '`'; + $identifiers[] = '"' . ($hasDoubleQuote ? str_replace('"', '""', $identifier) : $identifier) . '"'; + $identifiers[] = "'" . ($hasSingleQuote ? str_replace("'", "''", $identifier) : $identifier) . "'"; + + if (!$hasTick && !$hasDoubleQuote && !$hasSingleQuote) { + if ($spacedNoQuotes) { + $identifiers[] = "\s+$identifier\s+"; + } else { + $identifiers[] = $identifier; + } + } + + return '(' . implode('|', $identifiers) . ')'; + } + /** * @param string $tableName Table name * @param bool $quoted Whether to return the schema name and table name escaped and quoted. If quoted, the schema (if any) will also be appended with a dot @@ -392,6 +454,9 @@ public function createTable(Table $table, array $columns = [], array $indexes = $sql = 'CREATE TABLE '; $sql .= $this->quoteTableName($table->getName()) . ' ('; + if (isset($options['primary_key'])) { + $options['primary_key'] = (array)$options['primary_key']; + } foreach ($columns as $column) { $sql .= $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column) . ', '; @@ -414,9 +479,7 @@ public function createTable(Table $table, array $columns = [], array $indexes = if (isset($options['primary_key'])) { $sql = rtrim($sql); $sql .= ' PRIMARY KEY ('; - if (is_string($options['primary_key'])) { // handle primary_key => 'id' - $sql .= $this->quoteColumnName($options['primary_key']); - } elseif (is_array($options['primary_key'])) { // handle primary_key => array('tag_id', 'resource_id') + if (is_array($options['primary_key'])) { // handle primary_key => array('tag_id', 'resource_id') $sql .= implode(',', array_map([$this, 'quoteColumnName'], $options['primary_key'])); } $sql .= ')'; @@ -449,7 +512,7 @@ protected function getChangePrimaryKeyInstructions(Table $table, $newColumns): A if (!empty($primaryKey)) { $instructions->merge( // FIXME: array access is a hack to make this incomplete implementation work with a correct getPrimaryKey implementation - $this->getDropPrimaryKeyInstructions($table, $primaryKey[0], false) + $this->getDropPrimaryKeyInstructions($table, $primaryKey[0]), ); } @@ -458,12 +521,12 @@ protected function getChangePrimaryKeyInstructions(Table $table, $newColumns): A if (!is_string($newColumns)) { throw new InvalidArgumentException(sprintf( 'Invalid value for primary key: %s', - json_encode($newColumns) + json_encode($newColumns), )); } $instructions->merge( - $this->getAddPrimaryKeyInstructions($table, $newColumns) + $this->getAddPrimaryKeyInstructions($table, $newColumns), ); } @@ -491,7 +554,7 @@ protected function getRenameTableInstructions(string $tableName, string $newTabl $sql = sprintf( 'ALTER TABLE %s RENAME TO %s', $this->quoteTableName($tableName), - $this->quoteTableName($newTableName) + $this->quoteTableName($newTableName), ); return new AlterInstructions([], [$sql]); @@ -518,7 +581,7 @@ public function truncateTable(string $tableName): void $this->execute(sprintf( 'DELETE FROM %s.%s', $this->quoteColumnName($info['schema']), - $this->quoteColumnName($info['table']) + $this->quoteColumnName($info['table']), )); // assuming no error occurred, reset the autoincrement (if any) @@ -527,7 +590,7 @@ public function truncateTable(string $tableName): void 'DELETE FROM %s.%s where name = %s', $this->quoteColumnName($info['schema']), 'sqlite_sequence', - $this->quoteString($info['table']) + $this->quoteString($info['table']), )); } } @@ -540,7 +603,7 @@ public function truncateTable(string $tableName): void * @param string $columnType The Phinx type of the column * @return mixed */ - protected function parseDefaultValue($default, string $columnType) + protected function parseDefaultValue(mixed $default, string $columnType): mixed { if ($default === null) { return null; @@ -597,7 +660,7 @@ protected function parseDefaultValue($default, string $columnType) return null; } elseif (preg_match('/^true|false$/i', $defaultBare)) { // boolean literal - return filter_var($defaultClean, \FILTER_VALIDATE_BOOLEAN); + return filter_var($defaultClean, FILTER_VALIDATE_BOOLEAN); } else { // any other expression: return the expression with parentheses, but without comments return Expression::from($defaultClean); @@ -660,7 +723,8 @@ public function getColumns(string $tableName): array foreach ($rows as $columnInfo) { $column = new Column(); $type = $this->getPhinxType($columnInfo['type']); - $default = $this->parseDefaultValue($columnInfo['dflt_value'], $type['name']); + // $type['name'] is string|Literal, convert it to be a string + $default = $this->parseDefaultValue($columnInfo['dflt_value'], (string)$type['name']); $column->setName($columnInfo['name']) // SQLite on PHP 8.1 returns int for notnull, older versions return a string @@ -713,15 +777,15 @@ protected function getAddColumnInstructions(Table $table, Column $column): Alter $sql = preg_replace( sprintf( "/(%s(?:\/\*.*?\*\/|\([^)]+\)|'[^']*?'|[^,])+)([,)])/", - $this->quoteColumnName($finalColumnName) + $this->quoteColumnName($finalColumnName), ), sprintf( '$1, %s %s$2', $this->quoteColumnName($column->getName()), - $this->getColumnSqlDefinition($column) + $this->getColumnSqlDefinition($column), ), $state['createSQL'], - 1 + 1, ); $this->execute($sql); @@ -734,7 +798,7 @@ protected function getAddColumnInstructions(Table $table, Column $column): Alter return $newState + $state; }); - return $this->copyAndDropTmpTable($instructions, $tableName); + return $this->endAlterByCopyTable($instructions, $tableName); } /** @@ -757,11 +821,11 @@ protected function getDeclaringSql(string $tableName): string $columnsInfo = $this->getTableInfo($tableName); foreach ($columnsInfo as $column) { - $columnName = $column['name']; + $columnName = preg_quote($column['name'], '#'); $columnNamePattern = "\"$columnName\"|`$columnName`|\\[$columnName\\]|$columnName"; $columnNamePattern = "#([\(,]+\\s*)($columnNamePattern)(\\s)#iU"; - $sql = preg_replace($columnNamePattern, "$1`$columnName`$3", $sql); + $sql = preg_replace($columnNamePattern, "$1`{$column['name']}`$3", $sql); } $tableNamePattern = "\"$tableName\"|`$tableName`|\\[$tableName\\]|$tableName"; @@ -794,48 +858,24 @@ protected function getDeclaringIndexSql(string $tableName, string $indexName): s } /** - * Copies all the data from a tmp table to another table + * Obtains index and trigger information for a table. * - * @param string $tableName The table name to copy the data to - * @param string $tmpTableName The tmp table name where the data is stored - * @param string[] $writeColumns The list of columns in the target table - * @param string[] $selectColumns The list of columns in the tmp table - * @return void - */ - protected function copyDataToNewTable(string $tableName, string $tmpTableName, array $writeColumns, array $selectColumns): void - { - $sql = sprintf( - 'INSERT INTO %s(%s) SELECT %s FROM %s', - $this->quoteTableName($tableName), - implode(', ', $writeColumns), - implode(', ', $selectColumns), - $this->quoteTableName($tmpTableName) - ); - $this->execute($sql); - } - - /** - * Modifies the passed instructions to copy all data from the table into - * the provided tmp table and then drops the table and rename tmp table. + * They will be stored in the state as arrays under the `indices` and `triggers` + * keys accordingly. + * + * Index columns defined as expressions, as for example in `ON (ABS(id), other)`, + * will appear as `null`, so for the given example the columns for the index would + * look like `[null, 'other']`. * * @param \Phinx\Db\Util\AlterInstructions $instructions The instructions to modify - * @param string $tableName The table name to copy the data to - * @param bool $validateForeignKeys Whether to validate foreign keys after the copy and drop operations. Note that - * enabling this option only has an effect when the `foreign_keys` PRAGMA is set to `ON`! + * @param string $tableName The name of table being processed * @return \Phinx\Db\Util\AlterInstructions */ - protected function copyAndDropTmpTable( - AlterInstructions $instructions, - string $tableName, - bool $validateForeignKeys = true - ): AlterInstructions { - $instructions->addPostStep(function ($state) use ($tableName, $validateForeignKeys) { - $this->copyDataToNewTable( - $state['tmpTableName'], - $tableName, - $state['writeColumns'], - $state['selectColumns'] - ); + protected function bufferIndicesAndTriggers(AlterInstructions $instructions, string $tableName): AlterInstructions + { + $instructions->addPostStep(function (array $state) use ($tableName): array { + $state['indices'] = []; + $state['triggers'] = []; $rows = $this->fetchAll( sprintf( @@ -847,34 +887,140 @@ protected function copyAndDropTmpTable( AND tbl_name = %s AND sql IS NOT NULL ", - $this->quoteValue($tableName) - ) + $this->quoteValue($tableName), + ), ); - $foreignKeysEnabled = (bool)$this->fetchRow('PRAGMA foreign_keys')['foreign_keys']; - if ($foreignKeysEnabled) { - $this->execute('PRAGMA foreign_keys = OFF'); + $schema = $this->getSchemaName($tableName, true)['schema']; + + foreach ($rows as $row) { + switch ($row['type']) { + case 'index': + $info = $this->fetchAll( + sprintf('PRAGMA %sindex_info(%s)', $schema, $this->quoteValue($row['name'])), + ); + + $columns = array_map( + function ($column) { + if ($column === null) { + return null; + } + + return strtolower($column); + }, + array_column($info, 'name'), + ); + $hasExpressions = in_array(null, $columns, true); + + $index = [ + 'columns' => $columns, + 'hasExpressions' => $hasExpressions, + ]; + + $state['indices'][] = $index + $row; + break; + + case 'trigger': + $state['triggers'][] = $row; + break; + } } - $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tableName))); - if ($foreignKeysEnabled) { - $this->execute('PRAGMA foreign_keys = ON'); + + return $state; + }); + + return $instructions; + } + + /** + * Filters out indices that reference a removed column. + * + * @param \Phinx\Db\Util\AlterInstructions $instructions The instructions to modify + * @param string $columnName The name of the removed column + * @return \Phinx\Db\Util\AlterInstructions + */ + protected function filterIndicesForRemovedColumn( + AlterInstructions $instructions, + string $columnName, + ): AlterInstructions { + $instructions->addPostStep(function (array $state) use ($columnName): array { + foreach ($state['indices'] as $key => $index) { + if ( + !$index['hasExpressions'] && + in_array(strtolower($columnName), $index['columns'], true) + ) { + unset($state['indices'][$key]); + } } - $this->execute(sprintf( - 'ALTER TABLE %s RENAME TO %s', - $this->quoteTableName($state['tmpTableName']), - $this->quoteTableName($tableName) - )); + return $state; + }); - foreach ($rows as $row) { - $this->execute($row['sql']); + return $instructions; + } + + /** + * Updates indices that reference a renamed column. + * + * @param \Phinx\Db\Util\AlterInstructions $instructions The instructions to modify + * @param string $oldColumnName The old column name + * @param string $newColumnName The new column name + * @return \Phinx\Db\Util\AlterInstructions + */ + protected function updateIndicesForRenamedColumn( + AlterInstructions $instructions, + string $oldColumnName, + string $newColumnName, + ): AlterInstructions { + $instructions->addPostStep(function (array $state) use ($oldColumnName, $newColumnName): array { + foreach ($state['indices'] as $key => $index) { + if ( + !$index['hasExpressions'] && + in_array(strtolower($oldColumnName), $index['columns'], true) + ) { + $pattern = ' + / + (INDEX.+?ON\s.+?) + (\(\s*|,\s*) # opening parenthesis or comma + (?:`|"|\[)? # optional opening quote + (%s) # column name + (?:`|"|\])? # optional closing quote + (\s+COLLATE\s+.+?)? # optional collation + (\s+(?:ASC|DESC))? # optional order + (\s*,|\s*\)) # comma or closing parenthesis + /isx'; + + $newColumnName = $this->quoteColumnName($newColumnName); + + $state['indices'][$key]['sql'] = preg_replace( + sprintf($pattern, preg_quote($oldColumnName, '/')), + "\\1\\2$newColumnName\\4\\5\\6", + $index['sql'], + ); + } + } + + return $state; + }); + + return $instructions; + } + + /** + * Recreates indices and triggers. + * + * @param \Phinx\Db\Util\AlterInstructions $instructions The instructions to process + * @return \Phinx\Db\Util\AlterInstructions + */ + protected function recreateIndicesAndTriggers(AlterInstructions $instructions): AlterInstructions + { + $instructions->addPostStep(function (array $state): array { + foreach ($state['indices'] as $index) { + $this->execute($index['sql']); } - if ( - $foreignKeysEnabled && - $validateForeignKeys - ) { - $this->validateForeignKeys($tableName); + foreach ($state['triggers'] as $trigger) { + $this->execute($trigger['sql']); } return $state; @@ -884,54 +1030,113 @@ protected function copyAndDropTmpTable( } /** - * Validates the foreign key constraints of the given table, and of those - * tables whose constraints are targeting it. + * Returns instructions for validating the foreign key constraints of + * the given table, and of those tables whose constraints are + * targeting it. * + * @param \Phinx\Db\Util\AlterInstructions $instructions The instructions to process * @param string $tableName The name of the table for which to check constraints. - * @return void - * @throws \RuntimeException In case of a foreign key constraint violation. + * @return \Phinx\Db\Util\AlterInstructions */ - protected function validateForeignKeys(string $tableName): void + protected function validateForeignKeys(AlterInstructions $instructions, string $tableName): AlterInstructions { - $tablesToCheck = [ - $tableName, - ]; + $instructions->addPostStep(function ($state) use ($tableName) { + $tablesToCheck = [ + $tableName, + ]; - $otherTables = $this - ->query( - "SELECT name FROM sqlite_master WHERE type = 'table' AND name != ?", - [$tableName] - ) - ->fetchAll(); - - foreach ($otherTables as $otherTable) { - $foreignKeyList = $this->getTableInfo($otherTable['name'], 'foreign_key_list'); - foreach ($foreignKeyList as $foreignKey) { - if (strcasecmp($foreignKey['table'], $tableName) === 0) { - $tablesToCheck[] = $otherTable['name']; - break; + $otherTables = $this + ->query( + "SELECT name FROM sqlite_master WHERE type = 'table' AND name != ?", + [$tableName], + ) + ->fetchAll(); + + foreach ($otherTables as $otherTable) { + $foreignKeyList = $this->getTableInfo($otherTable['name'], 'foreign_key_list'); + foreach ($foreignKeyList as $foreignKey) { + if (strcasecmp($foreignKey['table'], $tableName) === 0) { + $tablesToCheck[] = $otherTable['name']; + break; + } } } - } - $tablesToCheck = array_unique(array_map('strtolower', $tablesToCheck)); + $tablesToCheck = array_unique(array_map('strtolower', $tablesToCheck)); - foreach ($tablesToCheck as $tableToCheck) { - $schema = $this->getSchemaName($tableToCheck, true)['schema']; + foreach ($tablesToCheck as $tableToCheck) { + $schema = $this->getSchemaName($tableToCheck, true)['schema']; - $stmt = $this->query( - sprintf('PRAGMA %sforeign_key_check(%s)', $schema, $this->quoteTableName($tableToCheck)) + $stmt = $this->query( + sprintf('PRAGMA %sforeign_key_check(%s)', $schema, $this->quoteTableName($tableToCheck)), + ); + $row = $stmt->fetch(); + $stmt->closeCursor(); + + if (is_array($row)) { + throw new RuntimeException(sprintf( + 'Integrity constraint violation: FOREIGN KEY constraint on `%s` failed.', + $tableToCheck, + )); + } + } + + return $state; + }); + + return $instructions; + } + + /** + * Copies all the data from a tmp table to another table + * + * @param string $tableName The table name to copy the data to + * @param string $tmpTableName The tmp table name where the data is stored + * @param string[] $writeColumns The list of columns in the target table + * @param string[] $selectColumns The list of columns in the tmp table + * @return void + */ + protected function copyDataToNewTable(string $tableName, string $tmpTableName, array $writeColumns, array $selectColumns): void + { + $sql = sprintf( + 'INSERT INTO %s(%s) SELECT %s FROM %s', + $this->quoteTableName($tableName), + implode(', ', $writeColumns), + implode(', ', $selectColumns), + $this->quoteTableName($tmpTableName), + ); + $this->execute($sql); + } + + /** + * Modifies the passed instructions to copy all data from the table into + * the provided tmp table and then drops the table and rename tmp table. + * + * @param \Phinx\Db\Util\AlterInstructions $instructions The instructions to modify + * @param string $tableName The table name to copy the data to + * @return \Phinx\Db\Util\AlterInstructions + */ + protected function copyAndDropTmpTable(AlterInstructions $instructions, string $tableName): AlterInstructions + { + $instructions->addPostStep(function ($state) use ($tableName) { + $this->copyDataToNewTable( + $state['tmpTableName'], + $tableName, + $state['writeColumns'], + $state['selectColumns'], ); - $row = $stmt->fetch(); - $stmt->closeCursor(); - if (is_array($row)) { - throw new RuntimeException(sprintf( - 'Integrity constraint violation: FOREIGN KEY constraint on `%s` failed.', - $tableToCheck - )); - } - } + $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tableName))); + $this->execute(sprintf( + 'ALTER TABLE %s RENAME TO %s', + $this->quoteTableName($state['tmpTableName']), + $this->quoteTableName($tableName), + )); + + return $state; + }); + + return $instructions; } /** @@ -944,7 +1149,7 @@ protected function validateForeignKeys(string $tableName): void * @throws \InvalidArgumentException * @return array */ - protected function calculateNewTableColumns(string $tableName, $columnName, $newColumnName): array + protected function calculateNewTableColumns(string $tableName, string|false $columnName, string|false $newColumnName): array { $columns = $this->fetchAll(sprintf('pragma table_info(%s)', $this->quoteTableName($tableName))); $selectColumns = []; @@ -974,7 +1179,7 @@ protected function calculateNewTableColumns(string $tableName, $columnName, $new if ($columnName && !$found) { throw new InvalidArgumentException(sprintf( - 'The specified column doesn\'t exist: ' . $columnName + 'The specified column doesn\'t exist: ' . $columnName, )); } @@ -1005,7 +1210,7 @@ protected function beginAlterByCopyTable(string $tableName): AlterInstructions $createSQL = preg_replace( "/^CREATE TABLE .* \(/Ui", '', - $createSQL + $createSQL, ); $createSQL = "CREATE TABLE {$this->quoteTableName($tmpTableName)} ({$createSQL}"; @@ -1016,6 +1221,59 @@ protected function beginAlterByCopyTable(string $tableName): AlterInstructions return $instructions; } + /** + * Returns the final instructions to alter a table using the + * create-copy-drop strategy. + * + * @param \Phinx\Db\Util\AlterInstructions $instructions The instructions to modify + * @param string $tableName The name of table being processed + * @param ?string $renamedOrRemovedColumnName The name of the renamed or removed column when part of a column + * rename/drop operation. + * @param ?string $newColumnName The new column name when part of a column rename operation. + * @param bool $validateForeignKeys Whether to validate foreign keys after the copy and drop operations. Note that + * enabling this option only has an effect when the `foreign_keys` PRAGMA is set to `ON`! + * @return \Phinx\Db\Util\AlterInstructions + */ + protected function endAlterByCopyTable( + AlterInstructions $instructions, + string $tableName, + ?string $renamedOrRemovedColumnName = null, + ?string $newColumnName = null, + bool $validateForeignKeys = true, + ): AlterInstructions { + $instructions = $this->bufferIndicesAndTriggers($instructions, $tableName); + + if ($renamedOrRemovedColumnName !== null) { + if ($newColumnName !== null) { + $this->updateIndicesForRenamedColumn($instructions, $renamedOrRemovedColumnName, $newColumnName); + } else { + $this->filterIndicesForRemovedColumn($instructions, $renamedOrRemovedColumnName); + } + } + + $foreignKeysEnabled = (bool)$this->fetchRow('PRAGMA foreign_keys')['foreign_keys']; + + if ($foreignKeysEnabled) { + $instructions->addPostStep('PRAGMA foreign_keys = OFF'); + } + + $instructions = $this->copyAndDropTmpTable($instructions, $tableName); + $instructions = $this->recreateIndicesAndTriggers($instructions); + + if ($foreignKeysEnabled) { + $instructions->addPostStep('PRAGMA foreign_keys = ON'); + } + + if ( + $foreignKeysEnabled && + $validateForeignKeys + ) { + $instructions = $this->validateForeignKeys($instructions, $tableName); + } + + return $instructions; + } + /** * @inheritDoc */ @@ -1027,7 +1285,7 @@ protected function getRenameColumnInstructions(string $tableName, string $column $sql = str_replace( $this->quoteColumnName($columnName), $this->quoteColumnName($newColumnName), - $state['createSQL'] + $state['createSQL'], ); $this->execute($sql); @@ -1040,7 +1298,7 @@ protected function getRenameColumnInstructions(string $tableName, string $column return $newState + $state; }); - return $this->copyAndDropTmpTable($instructions, $tableName); + return $this->endAlterByCopyTable($instructions, $tableName, $columnName, $newColumnName); } /** @@ -1056,7 +1314,7 @@ protected function getChangeColumnInstructions(string $tableName, string $column sprintf("/%s(?:\/\*.*?\*\/|\([^)]+\)|'[^']*?'|[^,])+([,)])/", $this->quoteColumnName($columnName)), sprintf('%s %s$1', $this->quoteColumnName($newColumn->getName()), $this->getColumnSqlDefinition($newColumn)), $state['createSQL'], - 1 + 1, ); $this->execute($sql); @@ -1069,7 +1327,7 @@ protected function getChangeColumnInstructions(string $tableName, string $column return $newState + $state; }); - return $this->copyAndDropTmpTable($instructions, $tableName); + return $this->endAlterByCopyTable($instructions, $tableName); } /** @@ -1089,7 +1347,7 @@ protected function getDropColumnInstructions(string $tableName, string $columnNa $sql = preg_replace( sprintf("/%s\s%s.*(,\s(?!')|\)$)/U", preg_quote($this->quoteColumnName($columnName)), preg_quote($state['columnType'])), '', - $state['createSQL'] + $state['createSQL'], ); if (substr($sql, -2) === ', ') { @@ -1101,7 +1359,7 @@ protected function getDropColumnInstructions(string $tableName, string $columnNa return $state; }); - return $this->copyAndDropTmpTable($instructions, $tableName); + return $this->endAlterByCopyTable($instructions, $tableName, $columnName); } /** @@ -1135,7 +1393,7 @@ protected function getIndexes(string $tableName): array * @param string|string[] $columns The columns of the index * @return array */ - protected function resolveIndex(string $tableName, $columns): array + protected function resolveIndex(string $tableName, string|array $columns): array { $columns = array_map('strtolower', (array)$columns); $indexes = $this->getIndexes($tableName); @@ -1154,7 +1412,7 @@ protected function resolveIndex(string $tableName, $columns): array /** * @inheritDoc */ - public function hasIndex(string $tableName, $columns): bool + public function hasIndex(string $tableName, string|array $columns): bool { return (bool)$this->resolveIndex($tableName, $columns); } @@ -1190,7 +1448,7 @@ protected function getAddIndexInstructions(Table $table, Index $index): AlterIns 'CREATE %s ON %s (%s)', $this->getIndexSqlDefinition($table, $index), $this->quoteTableName($table->getName()), - $indexColumns + $indexColumns, ); return new AlterInstructions([], [$sql]); @@ -1209,7 +1467,7 @@ protected function getDropIndexByColumnsInstructions(string $tableName, $columns $instructions->addPostStep(sprintf( 'DROP INDEX %s%s', $schema, - $this->quoteColumnName($indexName) + $this->quoteColumnName($indexName), )); } } @@ -1239,7 +1497,7 @@ protected function getDropIndexByNameInstructions(string $tableName, string $ind $instructions->addPostStep(sprintf( 'DROP INDEX %s%s', $schema, - $this->quoteColumnName($indexName) + $this->quoteColumnName($indexName), )); } @@ -1295,21 +1553,17 @@ public function hasForeignKey(string $tableName, $columns, ?string $constraint = { if ($constraint !== null) { return preg_match( - "/,?\sCONSTRAINT\s" . preg_quote($this->quoteColumnName($constraint)) . ' FOREIGN KEY/', - $this->getDeclaringSql($tableName) + "/,?\s*CONSTRAINT\s*" . $this->possiblyQuotedIdentifierRegex($constraint) . '\s*FOREIGN\s+KEY/is', + $this->getDeclaringSql($tableName), ) === 1; } - $columns = array_map('strtolower', (array)$columns); - $foreignKeys = $this->getForeignKeys($tableName); + $columns = array_map('mb_strtolower', (array)$columns); - foreach ($foreignKeys as $key) { - $key = array_map('strtolower', $key); - if (array_diff($key, $columns) || array_diff($columns, $key)) { - continue; + foreach ($this->getForeignKeys($tableName) as $key) { + if (array_map('mb_strtolower', $key) === $columns) { + return true; } - - return true; } return false; @@ -1377,22 +1631,18 @@ protected function getAddPrimaryKeyInstructions(Table $table, string $column): A return compact('selectColumns', 'writeColumns') + $state; }); - return $this->copyAndDropTmpTable($instructions, $tableName); + return $this->endAlterByCopyTable($instructions, $tableName); } /** * @param \Phinx\Db\Table\Table $table Table * @param string $column Column Name - * @param bool $validateForeignKeys Whether to validate foreign keys after the copy and drop operations. Note that - * enabling this option only has an effect when the `foreign_keys` PRAGMA is set to `ON`! * @return \Phinx\Db\Util\AlterInstructions */ - protected function getDropPrimaryKeyInstructions( - Table $table, - string $column, - bool $validateForeignKeys = true - ): AlterInstructions { - $instructions = $this->beginAlterByCopyTable($table->getName()); + protected function getDropPrimaryKeyInstructions(Table $table, string $column): AlterInstructions + { + $tableName = $table->getName(); + $instructions = $this->beginAlterByCopyTable($tableName); $instructions->addPostStep(function ($state) { $search = "/(,?\s*PRIMARY KEY\s*\([^\)]*\)|\s+PRIMARY KEY(\s+AUTOINCREMENT)?)/"; @@ -1411,7 +1661,7 @@ protected function getDropPrimaryKeyInstructions( return $newState + $state; }); - return $this->copyAndDropTmpTable($instructions, $table->getName(), $validateForeignKeys); + return $this->endAlterByCopyTable($instructions, $tableName, null, null, false); } /** @@ -1435,13 +1685,13 @@ protected function getAddForeignKeyInstructions(Table $table, ForeignKey $foreig $sql .= sprintf( 'DROP INDEX %s%s; ', $schema, - $this->quoteColumnName($indexName) + $this->quoteColumnName($indexName), ); $createIndexSQL = $this->getDeclaringIndexSQL($tableName, $indexName); $sql .= preg_replace( "/\b{$tableName}\b/", $tmpTableName, - $createIndexSQL + $createIndexSQL, ); } } @@ -1459,7 +1709,7 @@ protected function getAddForeignKeyInstructions(Table $table, ForeignKey $foreig return compact('selectColumns', 'writeColumns') + $state; }); - return $this->copyAndDropTmpTable($instructions, $tableName); + return $this->endAlterByCopyTable($instructions, $tableName); } /** @@ -1481,18 +1731,27 @@ protected function getDropForeignKeyInstructions(string $tableName, string $cons */ protected function getDropForeignKeyByColumnsInstructions(string $tableName, array $columns): AlterInstructions { + if (!$this->hasForeignKey($tableName, $columns)) { + throw new InvalidArgumentException(sprintf( + 'No foreign key on column(s) `%s` exists', + implode(', ', $columns), + )); + } + $instructions = $this->beginAlterByCopyTable($tableName); $instructions->addPostStep(function ($state) use ($columns) { - $sql = ''; - - foreach ($columns as $columnName) { - $search = sprintf( - "/,[^,]*\(%s(?:,`?(.*)`?)?\) REFERENCES[^,]*\([^\)]*\)[^,)]*/", - $this->quoteColumnName($columnName) - ); - $sql = preg_replace($search, '', $state['createSQL'], 1); - } + $search = sprintf( + "/,[^,]+?\(\s*%s\s*\)\s*REFERENCES[^,]*\([^\)]*\)[^,)]*/is", + implode( + '\s*,\s*', + array_map( + fn($column) => $this->possiblyQuotedIdentifierRegex($column, false), + $columns, + ), + ), + ); + $sql = preg_replace($search, '', $state['createSQL']); if ($sql) { $this->execute($sql); @@ -1501,23 +1760,13 @@ protected function getDropForeignKeyByColumnsInstructions(string $tableName, arr return $state; }); - $instructions->addPostStep(function ($state) use ($columns) { - $newState = $this->calculateNewTableColumns($state['tmpTableName'], $columns[0], $columns[0]); - - $selectColumns = $newState['selectColumns']; - $columns = array_map([$this, 'quoteColumnName'], $columns); - $diff = array_diff($columns, $selectColumns); - - if (!empty($diff)) { - throw new InvalidArgumentException(sprintf( - 'The specified columns don\'t exist: ' . implode(', ', $diff) - )); - } + $instructions->addPostStep(function ($state) { + $newState = $this->calculateNewTableColumns($state['tmpTableName'], false, false); return $newState + $state; }); - return $this->copyAndDropTmpTable($instructions, $tableName); + return $this->endAlterByCopyTable($instructions, $tableName); } /** @@ -1525,17 +1774,20 @@ protected function getDropForeignKeyByColumnsInstructions(string $tableName, arr * * @throws \Phinx\Db\Adapter\UnsupportedColumnTypeException */ - public function getSqlType($type, ?int $limit = null): array + public function getSqlType(Literal|string $type, ?int $limit = null): array { - $typeLC = strtolower($type); if ($type instanceof Literal) { $name = $type; - } elseif (isset(static::$supportedColumnTypes[$typeLC])) { - $name = static::$supportedColumnTypes[$typeLC]; - } elseif (in_array($typeLC, static::$unsupportedColumnTypes, true)) { - throw new UnsupportedColumnTypeException('Column type "' . $type . '" is not supported by SQLite.'); } else { - throw new UnsupportedColumnTypeException('Column type "' . $type . '" is not known by SQLite.'); + $typeLC = strtolower($type); + + if (isset(static::$supportedColumnTypes[$typeLC])) { + $name = static::$supportedColumnTypes[$typeLC]; + } elseif (in_array($typeLC, static::$unsupportedColumnTypes, true)) { + throw new UnsupportedColumnTypeException('Column type "' . $type . '" is not supported by SQLite.'); + } else { + throw new UnsupportedColumnTypeException('Column type "' . $type . '" is not known by SQLite.'); + } } return ['name' => $name, 'limit' => $limit]; @@ -1554,7 +1806,7 @@ public function getPhinxType(?string $sqlTypeDef): array if ($sqlTypeDef === null) { // in SQLite columns can legitimately have null as a type, which is distinct from the empty string $name = null; - } elseif (!preg_match('/^([a-z]+)(_(?:integer|float|text|blob))?(?:\((\d+)(?:,(\d+))?\))?$/i', $sqlTypeDef, $match)) { + } elseif (!preg_match('/^([a-z]+)(_(?:integer|float|text|blob))?(?:\((\d+)(?:,[ ]*(\d+))?\))?$/i', $sqlTypeDef, $match)) { // doesn't match the pattern of a type we'd know about $name = Literal::from($sqlTypeDef); } else { @@ -1744,6 +1996,10 @@ protected function getForeignKeySqlDefinition(ForeignKey $foreignKey): string */ public function getDecoratedConnection(): Connection { + if (isset($this->decoratedConnection)) { + return $this->decoratedConnection; + } + $options = $this->getOptions(); $options['quoteIdentifiers'] = true; @@ -1755,13 +2011,6 @@ public function getDecoratedConnection(): Connection } } - if ($this->connection === null) { - throw new RuntimeException('You need to connect first.'); - } - - $driver = new SqliteDriver($options); - $driver->setConnection($this->connection); - - return new Connection(['driver' => $driver] + $options); + return $this->decoratedConnection = $this->buildConnection(SqliteDriver::class, $options); } } diff --git a/app/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/SqlServerAdapter.php b/app/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/SqlServerAdapter.php index 0fe02f0a1..37b4bfd5e 100644 --- a/app/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/SqlServerAdapter.php +++ b/app/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/SqlServerAdapter.php @@ -1,4 +1,5 @@ */ class SqlServerAdapter extends PdoAdapter { /** * @var string[] */ - protected static $specificColumnTypes = [ + protected static array $specificColumnTypes = [ self::PHINX_TYPE_FILESTREAM, self::PHINX_TYPE_BINARYUUID, ]; @@ -40,12 +40,12 @@ class SqlServerAdapter extends PdoAdapter /** * @var string */ - protected $schema = 'dbo'; + protected string $schema = 'dbo'; /** * @var bool[] */ - protected $signedColumnTypes = [ + protected array $signedColumnTypes = [ self::PHINX_TYPE_INTEGER => true, self::PHINX_TYPE_BIG_INTEGER => true, self::PHINX_TYPE_FLOAT => true, @@ -106,7 +106,7 @@ public function connect(): void if (strpos($key, 'sqlsrv_attr_') === 0) { $pdoConstant = '\PDO::' . strtoupper($key); if (!defined($pdoConstant)) { - throw new \UnexpectedValueException('Invalid PDO attribute: ' . $key . ' (' . $pdoConstant . ')'); + throw new UnexpectedValueException('Invalid PDO attribute: ' . $key . ' (' . $pdoConstant . ')'); } $driverOptions[constant($pdoConstant)] = $option; } @@ -151,7 +151,7 @@ protected function connectDblib(): void } catch (PDOException $exception) { throw new InvalidArgumentException(sprintf( 'There was a problem connecting to the database: %s', - $exception->getMessage() + $exception->getMessage(), ), 0, $exception); } @@ -211,7 +211,7 @@ public function quoteTableName(string $tableName): string */ public function quoteColumnName(string $columnName): string { - return '[' . str_replace(']', '\]', $columnName) . ']'; + return '[' . str_replace(']', ']]', $columnName) . ']'; } /** @@ -224,7 +224,7 @@ public function hasTable(string $tableName): bool } /** @var array $result */ - $result = $this->fetchRow(sprintf("SELECT count(*) as [count] FROM information_schema.tables WHERE table_name = '%s';", $tableName)); + $result = $this->fetchRow(sprintf("SELECT count(*) as [count] FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = '%s';", $tableName)); return $result['count'] > 0; } @@ -270,7 +270,7 @@ public function createTable(Table $table, array $columns = [], array $indexes = // set the primary key(s) if (isset($options['primary_key'])) { - $pkSql = sprintf('CONSTRAINT PK_%s PRIMARY KEY (', $table->getName()); + $pkSql = sprintf('CONSTRAINT PK_%s PRIMARY KEY (', str_replace('.', '_', $table->getName())); if (is_string($options['primary_key'])) { // handle primary_key => 'id' $pkSql .= $this->quoteColumnName($options['primary_key']); } elseif (is_array($options['primary_key'])) { // handle primary_key => array('tag_id', 'resource_id') @@ -313,7 +313,7 @@ protected function getChangePrimaryKeyInstructions(Table $table, $newColumns): A if (!empty($primaryKey['constraint'])) { $sql = sprintf( 'DROP CONSTRAINT %s', - $this->quoteColumnName($primaryKey['constraint']) + $this->quoteColumnName($primaryKey['constraint']), ); $instructions->addAlter($sql); } @@ -323,7 +323,7 @@ protected function getChangePrimaryKeyInstructions(Table $table, $newColumns): A $sql = sprintf( 'ALTER TABLE %s ADD CONSTRAINT %s PRIMARY KEY (', $this->quoteTableName($table->getName()), - $this->quoteColumnName('PK_' . $table->getName()) + $this->quoteColumnName('PK_' . $table->getName()), ); if (is_string($newColumns)) { // handle primary_key => 'id' $sql .= $this->quoteColumnName($newColumns); @@ -332,7 +332,7 @@ protected function getChangePrimaryKeyInstructions(Table $table, $newColumns): A } else { throw new InvalidArgumentException(sprintf( 'Invalid value for primary key: %s', - json_encode($newColumns) + json_encode($newColumns), )); } $sql .= ')'; @@ -360,7 +360,7 @@ protected function getChangeCommentInstructions(Table $table, ?string $newCommen * @param string $tableName Table name * @return string */ - protected function getColumnCommentSqlDefinition(Column $column, $tableName): string + protected function getColumnCommentSqlDefinition(Column $column, string $tableName): string { // passing 'null' is to remove column comment $currentComment = $this->getColumnComment($tableName, $column->getName()); @@ -374,7 +374,7 @@ protected function getColumnCommentSqlDefinition(Column $column, $tableName): st $comment, $this->schema, $tableName, - $column->getName() + $column->getName(), ); } @@ -387,7 +387,7 @@ protected function getRenameTableInstructions(string $tableName, string $newTabl $sql = sprintf( "EXEC sp_rename '%s', '%s'", $tableName, - $newTableName + $newTableName, ); return new AlterInstructions([], [$sql]); @@ -411,7 +411,7 @@ public function truncateTable(string $tableName): void { $sql = sprintf( 'TRUNCATE TABLE %s', - $this->quoteTableName($tableName) + $this->quoteTableName($tableName), ); $this->execute($sql); @@ -460,7 +460,7 @@ public function getColumns(string $tableName): array FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '%s' ORDER BY ordinal_position", - $tableName + $tableName, ); $rows = $this->fetchAll($sql); foreach ($rows as $columnInfo) { @@ -479,7 +479,7 @@ public function getColumns(string $tableName): array ->setComment($this->getColumnComment($columnInfo['table_name'], $columnInfo['name'])); if (!empty($columnInfo['char_length'])) { - $column->setLimit($columnInfo['char_length']); + $column->setLimit((int)$columnInfo['char_length']); } $columns[$columnInfo['name']] = $column; @@ -492,7 +492,7 @@ public function getColumns(string $tableName): array * @param string|null $default Default * @return int|string|null */ - protected function parseDefault(?string $default) + protected function parseDefault(?string $default): int|string|null { // if a column is non-nullable and has no default, the value of column_default is null, // otherwise it should be a string value that we parse below, including "(NULL)" which @@ -519,10 +519,10 @@ public function hasColumn(string $tableName, string $columnName): bool { $sql = sprintf( "SELECT count(*) as [count] - FROM information_schema.columns - WHERE table_name = '%s' AND column_name = '%s'", + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_NAME = '%s' AND COLUMN_NAME = '%s'", $tableName, - $columnName + $columnName, ); /** @var array $result */ $result = $this->fetchRow($sql); @@ -539,7 +539,7 @@ protected function getAddColumnInstructions(Table $table, Column $column): Alter 'ALTER TABLE %s ADD %s %s', $table->getName(), $this->quoteColumnName($column->getName()), - $this->getColumnSqlDefinition($column) + $this->getColumnSqlDefinition($column), ); return new AlterInstructions([], [$alter]); @@ -569,14 +569,14 @@ protected function getRenameColumnInstructions(string $tableName, string $column $instructions->addPostStep(sprintf( $sql, $oldConstraintName, - $newConstraintName + $newConstraintName, )); $instructions->addPostStep(sprintf( "EXECUTE sp_rename N'%s.%s', N'%s', 'COLUMN' ", $tableName, $columnName, - $newColumnName + $newColumnName, )); return $instructions; @@ -610,7 +610,7 @@ protected function getChangeDefault(string $tableName, Column $newColumn): Alter $this->quoteTableName($tableName), $constraintName, $default, - $this->quoteColumnName($newColumn->getName()) + $this->quoteColumnName($newColumn->getName()), )); return $instructions; @@ -630,7 +630,7 @@ protected function getChangeColumnInstructions(string $tableName, string $column if ($columnName !== $newColumn->getName()) { $instructions->merge( - $this->getRenameColumnInstructions($tableName, $columnName, $newColumn->getName()) + $this->getRenameColumnInstructions($tableName, $columnName, $newColumn->getName()), ); } @@ -642,7 +642,7 @@ protected function getChangeColumnInstructions(string $tableName, string $column 'ALTER TABLE %s ALTER COLUMN %s %s', $this->quoteTableName($tableName), $this->quoteColumnName($newColumn->getName()), - $this->getColumnSqlDefinition($newColumn, false) + $this->getColumnSqlDefinition($newColumn, false), )); // change column comment if needed if ($newColumn->getComment()) { @@ -666,7 +666,7 @@ protected function getDropColumnInstructions(string $tableName, string $columnNa $instructions->addPostStep(sprintf( 'ALTER TABLE %s DROP COLUMN %s', $this->quoteTableName($tableName), - $this->quoteColumnName($columnName) + $this->quoteColumnName($columnName), )); return $instructions; @@ -693,7 +693,7 @@ protected function getDropDefaultConstraint(string $tableName, ?string $columnNa * @param string $columnName Column name * @return string|false */ - protected function getDefaultConstraint(string $tableName, string $columnName) + protected function getDefaultConstraint(string $tableName, string $columnName): string|false { $sql = "SELECT default_constraints.name @@ -723,11 +723,11 @@ protected function getDefaultConstraint(string $tableName, string $columnName) } /** - * @param int $tableId Table ID - * @param int $indexId Index ID + * @param string $tableId Table ID + * @param string $indexId Index ID * @return array */ - protected function getIndexColums(int $tableId, int $indexId): array + protected function getIndexColums(string $tableId, string $indexId): array { $sql = "SELECT AC.[name] AS [column_name] FROM sys.[index_columns] IC @@ -771,7 +771,7 @@ public function getIndexes(string $tableName): array /** * @inheritDoc */ - public function hasIndex(string $tableName, $columns): bool + public function hasIndex(string $tableName, string|array $columns): bool { if (is_string($columns)) { $columns = [$columns]; // str to array @@ -838,7 +838,7 @@ protected function getDropIndexByColumnsInstructions(string $tableName, $columns $instructions->addPostStep(sprintf( 'DROP INDEX %s ON %s', $this->quoteColumnName($indexName), - $this->quoteTableName($tableName) + $this->quoteTableName($tableName), )); return $instructions; @@ -847,7 +847,7 @@ protected function getDropIndexByColumnsInstructions(string $tableName, $columns throw new InvalidArgumentException(sprintf( "The specified index on columns '%s' does not exist", - implode(',', $columns) + implode(',', $columns), )); } @@ -866,7 +866,7 @@ protected function getDropIndexByNameInstructions(string $tableName, string $ind $instructions->addPostStep(sprintf( 'DROP INDEX %s ON %s', $this->quoteColumnName($indexName), - $this->quoteTableName($tableName) + $this->quoteTableName($tableName), )); return $instructions; @@ -875,7 +875,7 @@ protected function getDropIndexByNameInstructions(string $tableName, string $ind throw new InvalidArgumentException(sprintf( "The specified index name '%s' does not exist", - $indexName + $indexName, )); } @@ -894,12 +894,7 @@ public function hasPrimaryKey(string $tableName, $columns, ?string $constraint = return $primaryKey['constraint'] === $constraint; } - if (is_string($columns)) { - $columns = [$columns]; // str to array - } - $missingColumns = array_diff($columns, $primaryKey['columns']); - - return empty($missingColumns); + return $primaryKey['columns'] === (array)$columns; } /** @@ -912,23 +907,23 @@ public function getPrimaryKey(string $tableName): array { $rows = $this->fetchAll(sprintf( "SELECT - tc.constraint_name, - kcu.column_name - FROM information_schema.table_constraints AS tc - JOIN information_schema.key_column_usage AS kcu - ON tc.constraint_name = kcu.constraint_name - WHERE constraint_type = 'PRIMARY KEY' - AND tc.table_name = '%s' - ORDER BY kcu.ordinal_position", - $tableName + tc.CONSTRAINT_NAME, + kcu.COLUMN_NAME + FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS tc + JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS kcu + ON tc.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME + WHERE CONSTRAINT_TYPE = 'PRIMARY KEY' + AND tc.TABLE_NAME = '%s' + ORDER BY kcu.ORDINAL_POSITION", + $tableName, )); $primaryKey = [ 'columns' => [], ]; foreach ($rows as $row) { - $primaryKey['constraint'] = $row['constraint_name']; - $primaryKey['columns'][] = $row['column_name']; + $primaryKey['constraint'] = $row['CONSTRAINT_NAME']; + $primaryKey['columns'][] = $row['COLUMN_NAME']; } return $primaryKey; @@ -939,9 +934,6 @@ public function getPrimaryKey(string $tableName): array */ public function hasForeignKey(string $tableName, $columns, ?string $constraint = null): bool { - if (is_string($columns)) { - $columns = [$columns]; // str to array - } $foreignKeys = $this->getForeignKeys($tableName); if ($constraint) { if (isset($foreignKeys[$constraint])) { @@ -951,9 +943,12 @@ public function hasForeignKey(string $tableName, $columns, ?string $constraint = return false; } + if (is_string($columns)) { + $columns = [$columns]; + } + foreach ($foreignKeys as $key) { - $a = array_diff($columns, $key['columns']); - if (empty($a)) { + if ($key['columns'] === $columns) { return true; } } @@ -972,23 +967,27 @@ protected function getForeignKeys(string $tableName): array $foreignKeys = []; $rows = $this->fetchAll(sprintf( "SELECT - tc.constraint_name, - tc.table_name, kcu.column_name, - ccu.table_name AS referenced_table_name, - ccu.column_name AS referenced_column_name + tc.CONSTRAINT_NAME, + tc.TABLE_NAME, kcu.COLUMN_NAME, + ccu.TABLE_NAME AS REFERENCED_TABLE_NAME, + ccu.COLUMN_NAME AS REFERENCED_COLUMN_NAME FROM - information_schema.table_constraints AS tc - JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name - JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name - WHERE constraint_type = 'FOREIGN KEY' AND tc.table_name = '%s' - ORDER BY kcu.ordinal_position", - $tableName + INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS tc + JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS kcu ON tc.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME + JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE AS ccu ON ccu.CONSTRAINT_NAME = tc.CONSTRAINT_NAME + WHERE CONSTRAINT_TYPE = 'FOREIGN KEY' AND tc.TABLE_NAME = '%s' + ORDER BY kcu.ORDINAL_POSITION", + $tableName, )); foreach ($rows as $row) { - $foreignKeys[$row['constraint_name']]['table'] = $row['table_name']; - $foreignKeys[$row['constraint_name']]['columns'][] = $row['column_name']; - $foreignKeys[$row['constraint_name']]['referenced_table'] = $row['referenced_table_name']; - $foreignKeys[$row['constraint_name']]['referenced_columns'][] = $row['referenced_column_name']; + $foreignKeys[$row['CONSTRAINT_NAME']]['table'] = $row['TABLE_NAME']; + $foreignKeys[$row['CONSTRAINT_NAME']]['columns'][] = $row['COLUMN_NAME']; + $foreignKeys[$row['CONSTRAINT_NAME']]['referenced_table'] = $row['REFERENCED_TABLE_NAME']; + $foreignKeys[$row['CONSTRAINT_NAME']]['referenced_columns'][] = $row['REFERENCED_COLUMN_NAME']; + } + foreach ($foreignKeys as $name => $key) { + $foreignKeys[$name]['columns'] = array_values(array_unique($key['columns'])); + $foreignKeys[$name]['referenced_columns'] = array_values(array_unique($key['referenced_columns'])); } return $foreignKeys; @@ -1003,7 +1002,7 @@ protected function getAddForeignKeyInstructions(Table $table, ForeignKey $foreig $instructions->addPostStep(sprintf( 'ALTER TABLE %s ADD %s', $this->quoteTableName($table->getName()), - $this->getForeignKeySqlDefinition($foreignKey, $table->getName()) + $this->getForeignKeySqlDefinition($foreignKey, $table->getName()), )); return $instructions; @@ -1018,7 +1017,7 @@ protected function getDropForeignKeyInstructions(string $tableName, string $cons $instructions->addPostStep(sprintf( 'ALTER TABLE %s DROP CONSTRAINT %s', $this->quoteTableName($tableName), - $constraint + $this->quoteColumnName($constraint), )); return $instructions; @@ -1031,29 +1030,27 @@ protected function getDropForeignKeyByColumnsInstructions(string $tableName, arr { $instructions = new AlterInstructions(); - foreach ($columns as $column) { - $rows = $this->fetchAll(sprintf( - "SELECT - tc.constraint_name, - tc.table_name, kcu.column_name, - ccu.table_name AS referenced_table_name, - ccu.column_name AS referenced_column_name - FROM - information_schema.table_constraints AS tc - JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name - JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name - WHERE constraint_type = 'FOREIGN KEY' AND tc.table_name = '%s' and ccu.column_name='%s' - ORDER BY kcu.ordinal_position", - $tableName, - $column - )); - foreach ($rows as $row) { - $instructions->merge( - $this->getDropForeignKeyInstructions($tableName, $row['constraint_name']) - ); + $matches = []; + $foreignKeys = $this->getForeignKeys($tableName); + foreach ($foreignKeys as $name => $key) { + if ($key['columns'] === $columns) { + $matches[] = $name; } } + if (empty($matches)) { + throw new InvalidArgumentException(sprintf( + 'No foreign key on column(s) `%s` exists', + implode(', ', $columns), + )); + } + + foreach ($matches as $name) { + $instructions->merge( + $this->getDropForeignKeyInstructions($tableName, $name), + ); + } + return $instructions; } @@ -1062,8 +1059,9 @@ protected function getDropForeignKeyByColumnsInstructions(string $tableName, arr * * @throws \Phinx\Db\Adapter\UnsupportedColumnTypeException */ - public function getSqlType($type, ?int $limit = null): array + public function getSqlType(Literal|string $type, ?int $limit = null): array { + $type = (string)$type; switch ($type) { case static::PHINX_TYPE_FLOAT: case static::PHINX_TYPE_DECIMAL: @@ -1176,12 +1174,13 @@ public function getPhinxType(string $sqlType): string */ public function createDatabase(string $name, array $options = []): void { + $databaseName = $this->quoteColumnName($name); if (isset($options['collation'])) { - $this->execute(sprintf('CREATE DATABASE [%s] COLLATE [%s]', $name, $options['collation'])); + $this->execute(sprintf('CREATE DATABASE %s COLLATE %s', $databaseName, $options['collation'])); } else { - $this->execute(sprintf('CREATE DATABASE [%s]', $name)); + $this->execute(sprintf('CREATE DATABASE %s', $databaseName)); } - $this->execute(sprintf('USE [%s]', $name)); + $this->execute(sprintf('USE %s', $databaseName)); } /** @@ -1193,8 +1192,8 @@ public function hasDatabase(string $name): bool $result = $this->fetchRow( sprintf( "SELECT count(*) as [count] FROM master.dbo.sysdatabases WHERE [name] = '%s'", - $name - ) + $name, + ), ); return $result['count'] > 0; @@ -1205,12 +1204,13 @@ public function hasDatabase(string $name): bool */ public function dropDatabase(string $name): void { - $sql = <<quoteColumnName($name), + ); $this->execute($sql); $this->createdTables = []; } @@ -1241,7 +1241,7 @@ protected function getColumnSqlDefinition(Column $column, bool $create = true): $buffer[] = sprintf( '(%s, %s)', $column->getPrecision() ?: $sqlType['precision'], - $column->getScale() ?: $sqlType['scale'] + $column->getScale() ?: $sqlType['scale'], ); } elseif (!in_array($sqlType['name'], $noLimits) && ($column->getLimit() || isset($sqlType['limit']))) { $buffer[] = sprintf('(%s)', $column->getLimit() ?: $sqlType['limit']); @@ -1304,7 +1304,7 @@ protected function getIndexSqlDefinition(Index $index, string $tableName): strin $indexName, $this->quoteTableName($tableName), implode(',', $columnNames), - $includedColumns + $includedColumns, ); } @@ -1361,6 +1361,10 @@ public function migrated(MigrationInterface $migration, string $direction, strin */ public function getDecoratedConnection(): Connection { + if (isset($this->decoratedConnection)) { + return $this->decoratedConnection; + } + $options = $this->getOptions(); $options = [ 'username' => $options['user'] ?? null, @@ -1369,9 +1373,6 @@ public function getDecoratedConnection(): Connection 'quoteIdentifiers' => true, ] + $options; - $driver = new SqlServerDriver($options); - $driver->setConnection($this->connection); - - return new Connection(['driver' => $driver] + $options); + return $this->decoratedConnection = $this->buildConnection(SqlServerDriver::class, $options); } } diff --git a/app/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/TablePrefixAdapter.php b/app/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/TablePrefixAdapter.php index dafc84098..afa896003 100644 --- a/app/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/TablePrefixAdapter.php +++ b/app/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/TablePrefixAdapter.php @@ -1,4 +1,5 @@ */ class TablePrefixAdapter extends AdapterWrapper implements DirectActionInterface { @@ -60,7 +59,7 @@ public function createTable(Table $table, array $columns = [], array $indexes = { $adapterTable = new Table( $this->getAdapterTableName($table->getName()), - $table->getOptions() + $table->getOptions(), ); parent::createTable($adapterTable, $columns, $indexes); } @@ -80,7 +79,7 @@ public function changePrimaryKey(Table $table, $newColumns): void $adapterTable = new Table( $this->getAdapterTableName($table->getName()), - $table->getOptions() + $table->getOptions(), ); $adapter->changePrimaryKey($adapterTable, $newColumns); } @@ -100,7 +99,7 @@ public function changeComment(Table $table, ?string $newComment): void $adapterTable = new Table( $this->getAdapterTableName($table->getName()), - $table->getOptions() + $table->getOptions(), ); $adapter->changeComment($adapterTable, $newComment); } @@ -236,7 +235,7 @@ public function dropColumn(string $tableName, string $columnName): void /** * @inheritDoc */ - public function hasIndex(string $tableName, $columns): bool + public function hasIndex(string $tableName, string|array $columns): bool { $adapterTableName = $this->getAdapterTableName($tableName); @@ -484,7 +483,7 @@ public function executeActions(Table $table, array $actions): void default: throw new InvalidArgumentException( - sprintf("Forgot to implement table prefixing for action: '%s'", get_class($action)) + sprintf("Forgot to implement table prefixing for action: '%s'", get_class($action)), ); } } diff --git a/app/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/TimedOutputAdapter.php b/app/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/TimedOutputAdapter.php index 1ef740a36..cac673759 100644 --- a/app/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/TimedOutputAdapter.php +++ b/app/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/TimedOutputAdapter.php @@ -1,4 +1,5 @@ getOutput()->getVerbosity()) { $this->getOutput()->writeln(' -> ' . sprintf('%.4fs', $end - $started)); @@ -65,7 +66,7 @@ public function writeCommand(string $command, array $args = []): void function ($value) { return '\'' . $value . '\''; }, - $arg + $arg, ); $outArr[] = '[' . implode(', ', $arg) . ']'; continue; @@ -216,7 +217,7 @@ public function addColumn(Table $table, Column $column): void $table->getName(), $column->getName(), $column->getType(), - ] + ], ); $adapter->addColumn($table, $column); $end(); diff --git a/app/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/UnsupportedColumnTypeException.php b/app/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/UnsupportedColumnTypeException.php index 5ef75e96d..f784bf860 100644 --- a/app/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/UnsupportedColumnTypeException.php +++ b/app/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/UnsupportedColumnTypeException.php @@ -1,4 +1,5 @@ */ class UnsupportedColumnTypeException extends RuntimeException { diff --git a/app/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/WrapperInterface.php b/app/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/WrapperInterface.php index 669d0d2e3..f44601eb7 100644 --- a/app/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/WrapperInterface.php +++ b/app/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/WrapperInterface.php @@ -1,4 +1,5 @@ */ interface WrapperInterface { diff --git a/app/vendor/robmorgan/phinx/src/Phinx/Db/Plan/AlterTable.php b/app/vendor/robmorgan/phinx/src/Phinx/Db/Plan/AlterTable.php index 1746e1546..bdb675ee6 100644 --- a/app/vendor/robmorgan/phinx/src/Phinx/Db/Plan/AlterTable.php +++ b/app/vendor/robmorgan/phinx/src/Phinx/Db/Plan/AlterTable.php @@ -1,4 +1,5 @@ getNewName() === $b->getColumnName(); - } + }, ); $tableUpdates = []; foreach ($this->tableUpdates as $update) { @@ -218,13 +219,13 @@ function (RenameColumn $a, ChangeColumn $b) { AddForeignKey::class, function (DropForeignKey $a, AddForeignKey $b) { return $a->getForeignKey()->getColumns() === $b->getForeignKey()->getColumns(); - } + }, ); $constraints = []; foreach ($this->constraints as $constraint) { $constraints = array_merge( $constraints, - $splitter($this->remapContraintAndIndexConflicts($constraint)) + $splitter($this->remapContraintAndIndexConflicts($constraint)), ); } $this->constraints = $constraints; @@ -270,7 +271,7 @@ protected function remapContraintAndIndexConflicts(AlterTable $alter): AlterTabl [$this->indexes, $dropIndexActions] = $this->forgetDropIndex( $action->getTable(), $action->getForeignKey()->getColumns(), - $this->indexes + $this->indexes, ); foreach ($dropIndexActions as $dropIndexAction) { $newAlter->addAction($dropIndexAction); diff --git a/app/vendor/robmorgan/phinx/src/Phinx/Db/Plan/Solver/ActionSplitter.php b/app/vendor/robmorgan/phinx/src/Phinx/Db/Plan/Solver/ActionSplitter.php index a9a157aae..c4ea33d11 100644 --- a/app/vendor/robmorgan/phinx/src/Phinx/Db/Plan/Solver/ActionSplitter.php +++ b/app/vendor/robmorgan/phinx/src/Phinx/Db/Plan/Solver/ActionSplitter.php @@ -1,4 +1,5 @@ actions->addAction(new ChangePrimaryKey($this->table, $columns)); @@ -192,7 +195,7 @@ public function changePrimaryKey($columns) * @param string|null $constraint Constraint names * @return bool */ - public function hasPrimaryKey($columns, ?string $constraint = null): bool + public function hasPrimaryKey(string|array $columns, ?string $constraint = null): bool { return $this->getAdapter()->hasPrimaryKey($this->getName(), $columns, $constraint); } @@ -232,7 +235,7 @@ public function getColumn(string $name): ?Column $this->getColumns(), function ($column) use ($name) { return $column->getName() === $name; - } + }, ); return array_pop($columns); @@ -296,8 +299,9 @@ public function reset(): void * @throws \InvalidArgumentException * @return $this */ - public function addColumn($columnName, $type = null, array $options = []) + public function addColumn(string|Column $columnName, string|Literal|null $type = null, array $options = []) { + assert($columnName instanceof Column || $type !== null); if ($columnName instanceof Column) { $action = new AddColumn($this->table, $columnName); } elseif ($type instanceof Literal) { @@ -310,8 +314,8 @@ public function addColumn($columnName, $type = null, array $options = []) if (!$this->getAdapter()->isValidColumnType($action->getColumn())) { throw new InvalidArgumentException(sprintf( 'An invalid column type "%s" was specified for column "%s".', - $type, - $action->getColumn()->getName() + $action->getColumn()->getType(), + $action->getColumn()->getName(), )); } @@ -357,7 +361,7 @@ public function renameColumn(string $oldName, string $newName) * @param array $options Options * @return $this */ - public function changeColumn(string $columnName, $newColumnType, array $options = []) + public function changeColumn(string $columnName, string|Column|Literal $newColumnType, array $options = []) { if ($newColumnType instanceof Column) { $action = new ChangeColumn($this->table, $columnName, $newColumnType); @@ -389,7 +393,7 @@ public function hasColumn(string $columnName): bool * @param array $options Index Options * @return $this */ - public function addIndex($columns, array $options = []) + public function addIndex(string|array|Index $columns, array $options = []) { $action = AddIndex::build($this->table, $columns, $options); $this->actions->addAction($action); @@ -403,7 +407,7 @@ public function addIndex($columns, array $options = []) * @param string|string[] $columns Columns * @return $this */ - public function removeIndex($columns) + public function removeIndex(string|array $columns) { $action = DropIndex::build($this->table, is_string($columns) ? [$columns] : $columns); $this->actions->addAction($action); @@ -431,7 +435,7 @@ public function removeIndexByName(string $name) * @param string|string[] $columns Columns * @return bool */ - public function hasIndex($columns): bool + public function hasIndex(string|array $columns): bool { return $this->getAdapter()->hasIndex($this->getName(), $columns); } @@ -442,7 +446,7 @@ public function hasIndex($columns): bool * @param string $indexName Index name * @return bool */ - public function hasIndexByName($indexName): bool + public function hasIndexByName(string $indexName): bool { return $this->getAdapter()->hasIndexByName($this->getName(), $indexName); } @@ -459,7 +463,7 @@ public function hasIndexByName($indexName): bool * @param array $options Options * @return $this */ - public function addForeignKey($columns, $referencedTable, $referencedColumns = ['id'], array $options = []) + public function addForeignKey(string|array $columns, string|TableValue $referencedTable, string|array $referencedColumns = ['id'], array $options = []) { $action = AddForeignKey::build($this->table, $columns, $referencedTable, $referencedColumns, $options); $this->actions->addAction($action); @@ -480,7 +484,7 @@ public function addForeignKey($columns, $referencedTable, $referencedColumns = [ * @param array $options Options * @return $this */ - public function addForeignKeyWithName(string $name, $columns, $referencedTable, $referencedColumns = ['id'], array $options = []) + public function addForeignKeyWithName(string $name, string|array $columns, string|TableValue $referencedTable, string|array $referencedColumns = ['id'], array $options = []) { $action = AddForeignKey::build( $this->table, @@ -488,7 +492,7 @@ public function addForeignKeyWithName(string $name, $columns, $referencedTable, $referencedTable, $referencedColumns, $options, - $name + $name, ); $this->actions->addAction($action); @@ -502,7 +506,7 @@ public function addForeignKeyWithName(string $name, $columns, $referencedTable, * @param string|null $constraint Constraint names * @return $this */ - public function dropForeignKey($columns, ?string $constraint = null) + public function dropForeignKey(string|array $columns, ?string $constraint = null) { $action = DropForeignKey::build($this->table, $columns, $constraint); $this->actions->addAction($action); @@ -517,7 +521,7 @@ public function dropForeignKey($columns, ?string $constraint = null) * @param string|null $constraint Constraint names * @return bool */ - public function hasForeignKey($columns, ?string $constraint = null): bool + public function hasForeignKey(string|array $columns, ?string $constraint = null): bool { return $this->getAdapter()->hasForeignKey($this->getName(), $columns, $constraint); } @@ -530,17 +534,19 @@ public function hasForeignKey($columns, ?string $constraint = null): bool * @param bool $withTimezone Whether to set the timezone option on the added columns * @return $this */ - public function addTimestamps($createdAt = 'created_at', $updatedAt = 'updated_at', bool $withTimezone = false) + public function addTimestamps(string|false|null $createdAt = 'created_at', string|false|null $updatedAt = 'updated_at', bool $withTimezone = false) { $createdAt = $createdAt ?? 'created_at'; $updatedAt = $updatedAt ?? 'updated_at'; if (!$createdAt && !$updatedAt) { - throw new \RuntimeException('Cannot set both created_at and updated_at columns to false'); + throw new RuntimeException('Cannot set both created_at and updated_at columns to false'); } + $columnType = FeatureFlags::$addTimestampsUseDateTime ? 'datetime' : 'timestamp'; + if ($createdAt) { - $this->addColumn($createdAt, 'timestamp', [ + $this->addColumn($createdAt, $columnType, [ 'null' => false, 'default' => 'CURRENT_TIMESTAMP', 'update' => '', @@ -548,7 +554,7 @@ public function addTimestamps($createdAt = 'created_at', $updatedAt = 'updated_a ]); } if ($updatedAt) { - $this->addColumn($updatedAt, 'timestamp', [ + $this->addColumn($updatedAt, $columnType, [ 'null' => true, 'default' => null, 'update' => 'CURRENT_TIMESTAMP', @@ -567,7 +573,7 @@ public function addTimestamps($createdAt = 'created_at', $updatedAt = 'updated_a * @param string|false|null $updatedAt Alternate name for the updated_at column * @return $this */ - public function addTimestampsWithTimezone($createdAt = null, $updatedAt = null) + public function addTimestampsWithTimezone(string|false|null $createdAt = null, string|false|null $updatedAt = null) { $this->addTimestamps($createdAt, $updatedAt, true); diff --git a/app/vendor/robmorgan/phinx/src/Phinx/Db/Table/Column.php b/app/vendor/robmorgan/phinx/src/Phinx/Db/Table/Column.php index f70ffa7a6..f677c7760 100644 --- a/app/vendor/robmorgan/phinx/src/Phinx/Db/Table/Column.php +++ b/app/vendor/robmorgan/phinx/src/Phinx/Db/Table/Column.php @@ -1,4 +1,5 @@ type = $type; @@ -208,8 +210,12 @@ public function setType($type) * * @return string|\Phinx\Util\Literal */ - public function getType() + public function getType(): string|Literal { + if (!isset($this->type)) { + throw new RuntimeException('Cannot access `type` it has not been set'); + } + return $this->type; } @@ -275,7 +281,7 @@ public function isNull(): bool * @param mixed $default Default * @return $this */ - public function setDefault($default) + public function setDefault(mixed $default) { $this->default = $default; @@ -287,7 +293,7 @@ public function setDefault($default) * * @return mixed */ - public function getDefault() + public function getDefault(): mixed { return $this->default; } @@ -635,7 +641,7 @@ public function getProperties(): array * @param string[]|string $values Value(s) * @return $this */ - public function setValues($values) + public function setValues(array|string $values) { if (!is_array($values)) { $values = preg_split('/,\s*/', $values) ?: []; diff --git a/app/vendor/robmorgan/phinx/src/Phinx/Db/Table/ForeignKey.php b/app/vendor/robmorgan/phinx/src/Phinx/Db/Table/ForeignKey.php index cd14893f1..7c5d2671e 100644 --- a/app/vendor/robmorgan/phinx/src/Phinx/Db/Table/ForeignKey.php +++ b/app/vendor/robmorgan/phinx/src/Phinx/Db/Table/ForeignKey.php @@ -1,4 +1,5 @@ */ - protected static $validOptions = ['delete', 'update', 'constraint']; + protected static array $validOptions = ['delete', 'update', 'constraint', 'deferrable']; /** * @var string[] */ - protected $columns = []; + protected array $columns = []; /** * @var \Phinx\Db\Table\Table */ - protected $referencedTable; + protected Table $referencedTable; /** * @var string[] */ - protected $referencedColumns = []; + protected array $referencedColumns = []; /** * @var string|null */ - protected $onDelete; + protected ?string $onDelete = null; /** * @var string|null */ - protected $onUpdate; + protected ?string $onUpdate = null; /** * @var string|null */ - protected $constraint; + protected ?string $constraint = null; + protected ?string $deferrableMode = null; /** * Sets the foreign key columns. @@ -58,7 +63,7 @@ class ForeignKey * @param string[]|string $columns Columns * @return $this */ - public function setColumns($columns) + public function setColumns(array|string $columns) { $this->columns = is_string($columns) ? [$columns] : $columns; @@ -95,6 +100,10 @@ public function setReferencedTable(Table $table) */ public function getReferencedTable(): Table { + if (!isset($this->referencedTable)) { + throw new RuntimeException('Cannot access `referencedTable` it has not been set'); + } + return $this->referencedTable; } @@ -190,6 +199,27 @@ public function getConstraint(): ?string return $this->constraint; } + /** + * Sets deferrable mode for the foreign key. + * + * @param string $deferrableMode Constraint + * @return $this + */ + public function setDeferrableMode(string $deferrableMode) + { + $this->deferrableMode = $this->normalizeDeferrable($deferrableMode); + + return $this; + } + + /** + * Gets deferrable mode for the foreign key. + */ + public function getDeferrableMode(): ?string + { + return $this->deferrableMode; + } + /** * Utility method that maps an array of index options to this objects methods. * @@ -209,6 +239,8 @@ public function setOptions(array $options) $this->setOnDelete($value); } elseif ($option === 'update') { $this->setOnUpdate($value); + } elseif ($option === 'deferrable') { + $this->setDeferrableMode($value); } else { $method = 'set' . ucfirst($option); $this->$method($value); @@ -234,4 +266,29 @@ protected function normalizeAction(string $action): string return constant($constantName); } + + /** + * From passed value checks if it's correct and fixes if needed + * + * @param string $deferrable Deferrable + * @throws \InvalidArgumentException + * @return string + */ + protected function normalizeDeferrable(string $deferrable): string + { + $mapping = [ + 'DEFERRED' => ForeignKey::DEFERRED, + 'IMMEDIATE' => ForeignKey::IMMEDIATE, + 'NOT DEFERRED' => ForeignKey::NOT_DEFERRED, + ForeignKey::DEFERRED => ForeignKey::DEFERRED, + ForeignKey::IMMEDIATE => ForeignKey::IMMEDIATE, + ForeignKey::NOT_DEFERRED => ForeignKey::NOT_DEFERRED, + ]; + $normalized = strtoupper(str_replace('_', ' ', $deferrable)); + if (array_key_exists($normalized, $mapping)) { + return $mapping[$normalized]; + } + + throw new InvalidArgumentException('Unknown deferrable passed: ' . $deferrable); + } } diff --git a/app/vendor/robmorgan/phinx/src/Phinx/Db/Table/Index.php b/app/vendor/robmorgan/phinx/src/Phinx/Db/Table/Index.php index f405f0252..cea7b9520 100644 --- a/app/vendor/robmorgan/phinx/src/Phinx/Db/Table/Index.php +++ b/app/vendor/robmorgan/phinx/src/Phinx/Db/Table/Index.php @@ -1,4 +1,5 @@ columns = is_string($columns) ? [$columns] : $columns; @@ -131,7 +132,7 @@ public function getName(): ?string * @param int|array $limit limit value or array of limit value * @return $this */ - public function setLimit($limit) + public function setLimit(int|array $limit) { $this->limit = $limit; @@ -143,7 +144,7 @@ public function setLimit($limit) * * @return int|array|null */ - public function getLimit() + public function getLimit(): int|array|null { return $this->limit; } diff --git a/app/vendor/robmorgan/phinx/src/Phinx/Db/Table/Table.php b/app/vendor/robmorgan/phinx/src/Phinx/Db/Table/Table.php index 03521572b..30c47e277 100644 --- a/app/vendor/robmorgan/phinx/src/Phinx/Db/Table/Table.php +++ b/app/vendor/robmorgan/phinx/src/Phinx/Db/Table/Table.php @@ -1,4 +1,5 @@ */ - protected $options; + protected array $options; /** * @param string $name The table name * @param array $options The creation options for this table * @throws \InvalidArgumentException */ - public function __construct($name, array $options = []) + public function __construct(string $name, array $options = []) { if (empty($name)) { throw new InvalidArgumentException('Cannot use an empty table name'); diff --git a/app/vendor/robmorgan/phinx/src/Phinx/Db/Util/AlterInstructions.php b/app/vendor/robmorgan/phinx/src/Phinx/Db/Util/AlterInstructions.php index ddc49fc5b..bbde1b947 100644 --- a/app/vendor/robmorgan/phinx/src/Phinx/Db/Util/AlterInstructions.php +++ b/app/vendor/robmorgan/phinx/src/Phinx/Db/Util/AlterInstructions.php @@ -1,4 +1,5 @@ postSteps[] = $sql; } diff --git a/app/vendor/robmorgan/phinx/src/Phinx/Migration/AbstractMigration.php b/app/vendor/robmorgan/phinx/src/Phinx/Migration/AbstractMigration.php index 9602efefc..738303163 100644 --- a/app/vendor/robmorgan/phinx/src/Phinx/Migration/AbstractMigration.php +++ b/app/vendor/robmorgan/phinx/src/Phinx/Migration/AbstractMigration.php @@ -1,4 +1,5 @@ */ abstract class AbstractMigration implements MigrationInterface { /** * @var string */ - protected $environment; + protected string $environment; /** * @var int */ - protected $version; + protected int $version; /** * @var \Phinx\Db\Adapter\AdapterInterface|null */ - protected $adapter; + protected ?AdapterInterface $adapter = null; /** * @var \Symfony\Component\Console\Output\OutputInterface|null */ - protected $output; + protected ?OutputInterface $output = null; /** * @var \Symfony\Component\Console\Input\InputInterface|null */ - protected $input; + protected ?InputInterface $input = null; /** * Whether this migration is being applied or reverted * * @var bool */ - protected $isMigratingUp = true; + protected bool $isMigratingUp = true; /** * List of all the table objects created by this migration * * @var array<\Phinx\Db\Table> */ - protected $tables = []; + protected array $tables = []; /** * @param string $environment Environment Detected @@ -73,6 +76,8 @@ abstract class AbstractMigration implements MigrationInterface */ final public function __construct(string $environment, int $version, ?InputInterface $input = null, ?OutputInterface $output = null) { + $this->validateVersion($version); + $this->environment = $environment; $this->version = $version; @@ -98,8 +103,12 @@ public function setAdapter(AdapterInterface $adapter): MigrationInterface /** * @inheritDoc */ - public function getAdapter(): ?AdapterInterface + public function getAdapter(): AdapterInterface { + if (!isset($this->adapter)) { + throw new RuntimeException('Cannot access `adapter` it has not been set'); + } + return $this->adapter; } @@ -202,7 +211,7 @@ public function execute(string $sql, array $params = []): int /** * @inheritDoc */ - public function query(string $sql, array $params = []) + public function query(string $sql, array $params = []): mixed { return $this->getAdapter()->query($sql, $params); } @@ -210,15 +219,47 @@ public function query(string $sql, array $params = []) /** * @inheritDoc */ - public function getQueryBuilder(): Query + public function getQueryBuilder(string $type): Query + { + return $this->getAdapter()->getQueryBuilder($type); + } + + /** + * @inheritDoc + */ + public function getSelectBuilder(): SelectQuery { - return $this->getAdapter()->getQueryBuilder(); + return $this->getAdapter()->getSelectBuilder(); } /** * @inheritDoc */ - public function fetchRow(string $sql) + public function getInsertBuilder(): InsertQuery + { + return $this->getAdapter()->getInsertBuilder(); + } + + /** + * @inheritDoc + */ + public function getUpdateBuilder(): UpdateQuery + { + return $this->getAdapter()->getUpdateBuilder(); + } + + /** + * @inheritDoc + */ + public function getDeleteBuilder(): DeleteQuery + { + return $this->getAdapter()->getDeleteBuilder(); + } + + /** + * @inheritDoc + */ + public function fetchRow(string $sql): array|false { return $this->getAdapter()->fetchRow($sql); } @@ -299,7 +340,7 @@ public function preFlightCheck(): void method_exists($this, MigrationInterface::DOWN) ) { $this->output->writeln(sprintf( - 'warning Migration contains both change() and up()/down() methods. Ignoring up() and down().' + 'warning Migration contains both change() and up()/down() methods. Ignoring up() and down().', )); } } @@ -317,7 +358,7 @@ public function postFlightCheck(): void { foreach ($this->tables as $table) { if ($table->hasPendingActions()) { - throw new RuntimeException('Migration has pending actions after execution!'); + throw new RuntimeException(sprintf('Migration %s_%s has pending actions after execution!', $this->getVersion(), $this->getName())); } } } @@ -335,4 +376,21 @@ public function shouldExecute(): bool { return true; } + + /** + * Makes sure the version int is within range for valid datetime. + * This is required to have a meaningful order in the overview. + * + * @param int $version Version + * @return void + */ + protected function validateVersion(int $version): void + { + $length = strlen((string)$version); + if ($length === 14) { + return; + } + + throw new RuntimeException('Invalid version `' . $version . '`, should be in format `YYYYMMDDHHMMSS` (length of 14).'); + } } diff --git a/app/vendor/robmorgan/phinx/src/Phinx/Migration/AbstractTemplateCreation.php b/app/vendor/robmorgan/phinx/src/Phinx/Migration/AbstractTemplateCreation.php index 286bc18bd..9eea24268 100644 --- a/app/vendor/robmorgan/phinx/src/Phinx/Migration/AbstractTemplateCreation.php +++ b/app/vendor/robmorgan/phinx/src/Phinx/Migration/AbstractTemplateCreation.php @@ -1,4 +1,5 @@ */ interface CreationInterface { diff --git a/app/vendor/robmorgan/phinx/src/Phinx/Migration/IrreversibleMigrationException.php b/app/vendor/robmorgan/phinx/src/Phinx/Migration/IrreversibleMigrationException.php index 376bcdc63..575c03b3e 100644 --- a/app/vendor/robmorgan/phinx/src/Phinx/Migration/IrreversibleMigrationException.php +++ b/app/vendor/robmorgan/phinx/src/Phinx/Migration/IrreversibleMigrationException.php @@ -1,4 +1,5 @@ */ class IrreversibleMigrationException extends Exception { diff --git a/app/vendor/robmorgan/phinx/src/Phinx/Migration/Manager.php b/app/vendor/robmorgan/phinx/src/Phinx/Migration/Manager.php index 76b68a032..07c0a592a 100644 --- a/app/vendor/robmorgan/phinx/src/Phinx/Migration/Manager.php +++ b/app/vendor/robmorgan/phinx/src/Phinx/Migration/Manager.php @@ -1,4 +1,5 @@ getVersionLog(); $maxNameLength = $versions ? max(array_map(function ($version) { - return strlen($version['migration_name']); + return strlen($version['migration_name'] ?? ''); }, $versions)) : 0; $missingVersions = array_diff_key($versions, $migrations); @@ -201,9 +202,9 @@ public function printStatus(string $environment, ?string $format = null): array $migration->getVersion(), ($version ? $version['start_time'] : ''), ($version ? $version['end_time'] : ''), - $migration->getName() + $migration->getName(), ), - $this->verbosityLevel + $this->verbosityLevel, ); if ($version && $version['breakpoint']) { @@ -239,7 +240,7 @@ public function printStatus(string $environment, ?string $format = null): array 'missing_count' => $missingCount, 'total_count' => $migrationCount + $missingCount, 'migrations' => $finalMigrations, - ] + ], )); break; default: @@ -267,7 +268,7 @@ protected function printMissingVersion(array $version, int $maxNameLength): void $version['version'], $version['start_time'], $version['end_time'], - str_pad($version['migration_name'], $maxNameLength, ' ') + str_pad($version['migration_name'] ?? '', $maxNameLength, ' '), )); if ($version && $version['breakpoint']) { @@ -300,6 +301,30 @@ public function migrateToDateTime(string $environment, DateTime $dateTime, bool } } + /** + * Migrate an environment to a specific number of migrations. + * + * @param string $environment Environment + * @param int $count Number of migrations to apply + * @param bool $fake flag that if true, we just record running the migration, but not actually do the migration + * @return void + */ + public function migrateToCount(string $environment, int $count, bool $fake = false): void + { + $versions = array_keys($this->getMigrations($environment)); + $env = $this->getEnvironment($environment); + $current = $env->getCurrentVersion(); + + if ($current === 0) { + $version = $versions[$count - 1]; + } else { + $currentIdx = array_search($current, $versions, true); + $version = $versions[min($currentIdx + $count, count($versions) - 1)]; + } + + $this->migrate($environment, $version, $fake); + } + /** * Migrate an environment to the specified version. * @@ -325,7 +350,7 @@ public function migrate(string $environment, ?int $version = null, bool $fake = if ($version != 0 && !isset($migrations[$version])) { $this->output->writeln(sprintf( 'warning %s is not a valid version', - $version + $version, )); return; @@ -391,7 +416,7 @@ public function executeMigration(string $name, MigrationInterface $migration, st $this->printMigrationStatus( $migration, ($direction === MigrationInterface::UP ? 'migrated' : 'reverted'), - sprintf('%.4fs', $end - $start) + sprintf('%.4fs', $end - $start), ); } @@ -423,7 +448,7 @@ public function executeSeed(string $name, SeedInterface $seed): void $this->printSeedStatus( $seed, 'seeded', - sprintf('%.4fs', $end - $start) + sprintf('%.4fs', $end - $start), ); } @@ -440,7 +465,7 @@ protected function printMigrationStatus(MigrationInterface $migration, string $s $this->printStatusOutput( $migration->getVersion() . ' ' . $migration->getName(), $status, - $duration + $duration, ); } @@ -457,7 +482,7 @@ protected function printSeedStatus(SeedInterface $seed, string $status, ?string $this->printStatusOutput( $seed->getName(), $status, - $duration + $duration, ); } @@ -475,7 +500,7 @@ protected function printStatusOutput(string $name, string $status, ?string $dura ' ==' . ' ' . $name . ':' . ' ' . $status . ' ' . $duration . '', - $this->verbosityLevel + $this->verbosityLevel, ); } @@ -489,7 +514,7 @@ protected function printStatusOutput(string $name, string $status, ?string $dura * @param bool $fake Flag that if true, we just record running the migration, but not actually do the migration * @return void */ - public function rollback(string $environment, $target = null, bool $force = false, bool $targetMustMatchVersion = true, bool $fake = false): void + public function rollback(string $environment, int|string|null $target = null, bool $force = false, bool $targetMustMatchVersion = true, bool $fake = false): void { // note that the migrations are indexed by name (aka creation time) in ascending order $migrations = $this->getMigrations($environment); @@ -652,7 +677,7 @@ public function getEnvironment(string $name): Environment if (!$this->getConfig()->hasEnvironment($name)) { throw new InvalidArgumentException(sprintf( 'The environment "%s" does not exist', - $name + $name, )); } @@ -761,8 +786,8 @@ public function getMigrations(string $environment): array function ($phpFile) { return " {$phpFile}"; }, - $phpFiles - ) + $phpFiles, + ), ); } @@ -793,7 +818,7 @@ function ($phpFile) { throw new InvalidArgumentException(sprintf( 'Migration "%s" has the same name as "%s"', basename($filePath), - $fileNames[$class] + $fileNames[$class], )); } @@ -813,7 +838,7 @@ function ($phpFile) { throw new InvalidArgumentException(sprintf( 'Could not find class "%s" in file "%s"', $class, - $filePath + $filePath, )); } @@ -828,7 +853,7 @@ function ($phpFile) { throw new InvalidArgumentException(sprintf( 'The class "%s" in file "%s" must extend \Phinx\Migration\AbstractMigration', $class, - $filePath + $filePath, )); } @@ -946,13 +971,13 @@ public function getSeeds(string $environment): array throw new InvalidArgumentException(sprintf( 'Could not find class "%s" in file "%s"', $class, - $filePath + $filePath, )); } // instantiate it /** @var \Phinx\Seed\AbstractSeed $seed */ - if ($this->container !== null) { + if (isset($this->container)) { $seed = $this->container->get($class); } else { $seed = new $class(); @@ -971,7 +996,7 @@ public function getSeeds(string $environment): array throw new InvalidArgumentException(sprintf( 'The class "%s" in file "%s" must extend \Phinx\Seed\AbstractSeed', $class, - $filePath + $filePath, )); } @@ -1059,7 +1084,7 @@ protected function markBreakpoint(string $environment, ?int $version, int $mark) if ($version != 0 && (!isset($versions[$version]) || !isset($migrations[$version]))) { $this->output->writeln(sprintf( 'warning %s is not a valid version', - $version + $version, )); return; @@ -1086,7 +1111,7 @@ protected function markBreakpoint(string $environment, ?int $version, int $mark) $this->getOutput()->writeln( ' Breakpoint ' . ($versions[$version]['breakpoint'] ? 'set' : 'cleared') . ' for ' . $version . '' . - ' ' . $migrations[$version]->getName() . '' + ' ' . $migrations[$version]->getName() . '', ); } @@ -1100,7 +1125,7 @@ public function removeBreakpoints(string $environment): void { $this->getOutput()->writeln(sprintf( ' %d breakpoints cleared.', - $this->getEnvironment($environment)->getAdapter()->resetAllBreakpoints() + $this->getEnvironment($environment)->getAdapter()->resetAllBreakpoints(), )); } diff --git a/app/vendor/robmorgan/phinx/src/Phinx/Migration/Manager/Environment.php b/app/vendor/robmorgan/phinx/src/Phinx/Migration/Manager/Environment.php index d742e7cd4..756206586 100644 --- a/app/vendor/robmorgan/phinx/src/Phinx/Migration/Manager/Environment.php +++ b/app/vendor/robmorgan/phinx/src/Phinx/Migration/Manager/Environment.php @@ -1,4 +1,5 @@ */ - protected $options; + protected array $options; /** * @var \Symfony\Component\Console\Input\InputInterface|null */ - protected $input; + protected ?InputInterface $input = null; /** * @var \Symfony\Component\Console\Output\OutputInterface|null */ - protected $output; + protected ?OutputInterface $output = null; /** * @var int */ - protected $currentVersion; + protected int $currentVersion; /** * @var string */ - protected $schemaTableName = 'phinxlog'; + protected string $schemaTableName = 'phinxlog'; /** * @var \Phinx\Db\Adapter\AdapterInterface */ - protected $adapter; + protected AdapterInterface $adapter; /** * @param string $name Environment Name @@ -85,12 +86,12 @@ public function executeMigration(MigrationInterface $migration, string $directio $migration->{MigrationInterface::INIT}(); } - if (!$fake) { - // begin the transaction if the adapter supports it - if ($this->getAdapter()->hasTransactions()) { - $this->getAdapter()->beginTransaction(); - } + // begin the transaction if the adapter supports it + if ($this->getAdapter()->hasTransactions()) { + $this->getAdapter()->beginTransaction(); + } + if (!$fake) { // Run the migration if (method_exists($migration, MigrationInterface::CHANGE)) { if ($direction === MigrationInterface::DOWN) { @@ -110,17 +111,17 @@ public function executeMigration(MigrationInterface $migration, string $directio } else { $migration->{$direction}(); } - - // commit the transaction if the adapter supports it - if ($this->getAdapter()->hasTransactions()) { - $this->getAdapter()->commitTransaction(); - } } - $migration->postFlightCheck(); - // Record it in the database $this->getAdapter()->migrated($migration, $direction, date('Y-m-d H:i:s', $startTime), date('Y-m-d H:i:s', time())); + + $migration->postFlightCheck(); + + // commit the transaction if the adapter supports it + if ($this->getAdapter()->hasTransactions()) { + $this->getAdapter()->commitTransaction(); + } } /** @@ -379,7 +380,7 @@ public function getAdapter(): AdapterInterface * @param string $schemaTableName Schema Table Name * @return $this */ - public function setSchemaTableName($schemaTableName) + public function setSchemaTableName(string $schemaTableName) { $this->schemaTableName = $schemaTableName; diff --git a/app/vendor/robmorgan/phinx/src/Phinx/Migration/Migration.change.template.php.dist b/app/vendor/robmorgan/phinx/src/Phinx/Migration/Migration.change.template.php.dist index 12eb6fb7e..40b609baf 100644 --- a/app/vendor/robmorgan/phinx/src/Phinx/Migration/Migration.change.template.php.dist +++ b/app/vendor/robmorgan/phinx/src/Phinx/Migration/Migration.change.template.php.dist @@ -1,4 +1,5 @@ */ interface MigrationInterface { @@ -151,7 +154,7 @@ public function execute(string $sql, array $params = []): int; * @param array $params parameters to use for prepared query * @return mixed */ - public function query(string $sql, array $params = []); + public function query(string $sql, array $params = []): mixed; /** * Returns a new Query object that can be used to build complex SELECT, UPDATE, INSERT or DELETE @@ -161,9 +164,54 @@ public function query(string $sql, array $params = []); * the dry-run settings. * * @see https://api.cakephp.org/3.6/class-Cake.Database.Query.html + * @param string $type Query * @return \Cake\Database\Query */ - public function getQueryBuilder(): Query; + public function getQueryBuilder(string $type): Query; + + /** + * Returns a new SelectQuery object that can be used to build complex + * SELECT queries and execute them against the current database. + * + * Queries executed through the query builder are always sent to the database, regardless of the + * the dry-run settings. + * + * @return \Cake\Database\Query\SelectQuery + */ + public function getSelectBuilder(): SelectQuery; + + /** + * Returns a new InsertQuery object that can be used to build complex + * INSERT queries and execute them against the current database. + * + * Queries executed through the query builder are always sent to the database, regardless of the + * the dry-run settings. + * + * @return \Cake\Database\Query\InsertQuery + */ + public function getInsertBuilder(): InsertQuery; + + /** + * Returns a new UpdateQuery object that can be used to build complex + * UPDATE queries and execute them against the current database. + * + * Queries executed through the query builder are always sent to the database, regardless of the + * the dry-run settings. + * + * @return \Cake\Database\Query\UpdateQuery + */ + public function getUpdateBuilder(): UpdateQuery; + + /** + * Returns a new DeleteQuery object that can be used to build complex + * DELETE queries and execute them against the current database. + * + * Queries executed through the query builder are always sent to the database, regardless of the + * the dry-run settings. + * + * @return \Cake\Database\Query\DeleteQuery + */ + public function getDeleteBuilder(): DeleteQuery; /** * Executes a query and returns only one row as an array. @@ -171,7 +219,7 @@ public function getQueryBuilder(): Query; * @param string $sql SQL * @return array|false */ - public function fetchRow(string $sql); + public function fetchRow(string $sql): array|false; /** * Executes a query and returns an array of rows. diff --git a/app/vendor/robmorgan/phinx/src/Phinx/Seed/AbstractSeed.php b/app/vendor/robmorgan/phinx/src/Phinx/Seed/AbstractSeed.php index 7706d3013..d9cdc9df3 100644 --- a/app/vendor/robmorgan/phinx/src/Phinx/Seed/AbstractSeed.php +++ b/app/vendor/robmorgan/phinx/src/Phinx/Seed/AbstractSeed.php @@ -1,4 +1,5 @@ */ abstract class AbstractSeed implements SeedInterface { /** * @var string */ - protected $environment; + protected string $environment; /** * @var \Phinx\Db\Adapter\AdapterInterface */ - protected $adapter; + protected AdapterInterface $adapter; /** * @var \Symfony\Component\Console\Input\InputInterface */ - protected $input; + protected InputInterface $input; /** * @var \Symfony\Component\Console\Output\OutputInterface */ - protected $output; + protected OutputInterface $output; /** * Override to specify dependencies for dependency injection from the configured PSR-11 container @@ -99,6 +99,10 @@ public function setAdapter(AdapterInterface $adapter): SeedInterface */ public function getAdapter(): AdapterInterface { + if (!isset($this->adapter)) { + throw new RuntimeException('Cannot access `adapter` it has not been set'); + } + return $this->adapter; } @@ -149,7 +153,7 @@ public function getName(): string /** * @inheritDoc */ - public function execute(string $sql, array $params = []) + public function execute(string $sql, array $params = []): int { return $this->getAdapter()->execute($sql, $params); } @@ -157,7 +161,7 @@ public function execute(string $sql, array $params = []) /** * @inheritDoc */ - public function query(string $sql, array $params = []) + public function query(string $sql, array $params = []): mixed { return $this->getAdapter()->query($sql, $params); } @@ -165,7 +169,7 @@ public function query(string $sql, array $params = []) /** * @inheritDoc */ - public function fetchRow(string $sql) + public function fetchRow(string $sql): array|false { return $this->getAdapter()->fetchRow($sql); } diff --git a/app/vendor/robmorgan/phinx/src/Phinx/Seed/Seed.template.php.dist b/app/vendor/robmorgan/phinx/src/Phinx/Seed/Seed.template.php.dist index 74ba62237..ebb0affc4 100644 --- a/app/vendor/robmorgan/phinx/src/Phinx/Seed/Seed.template.php.dist +++ b/app/vendor/robmorgan/phinx/src/Phinx/Seed/Seed.template.php.dist @@ -1,6 +1,7 @@ */ interface SeedInterface { @@ -115,7 +115,7 @@ public function getName(): string; * @param array $params parameters to use for prepared query * @return int */ - public function execute(string $sql, array $params = []); + public function execute(string $sql, array $params = []): int; /** * Executes a SQL statement. @@ -129,7 +129,7 @@ public function execute(string $sql, array $params = []); * @param array $params parameters to use for prepared query * @return mixed */ - public function query(string $sql, array $params = []); + public function query(string $sql, array $params = []): mixed; /** * Executes a query and returns only one row as an array. @@ -137,7 +137,7 @@ public function query(string $sql, array $params = []); * @param string $sql SQL * @return array|false */ - public function fetchRow(string $sql); + public function fetchRow(string $sql): array|false; /** * Executes a query and returns an array of rows. @@ -173,7 +173,7 @@ public function hasTable(string $tableName): bool; * @param array $options Options * @return \Phinx\Db\Table */ - public function table(string $tableName, array $options): \Phinx\Db\Table; + public function table(string $tableName, array $options): Table; /** * Checks to see if the seed should be executed. diff --git a/app/vendor/robmorgan/phinx/src/Phinx/Util/Expression.php b/app/vendor/robmorgan/phinx/src/Phinx/Util/Expression.php index 23a6b9eff..0de52fc2f 100644 --- a/app/vendor/robmorgan/phinx/src/Phinx/Util/Expression.php +++ b/app/vendor/robmorgan/phinx/src/Phinx/Util/Expression.php @@ -1,4 +1,5 @@ modify('+' . $offset . ' seconds'); + } return $dt->format(static::DATE_FORMAT); } + /** + * Checks that the given timestamp is a unique prefix for any files in the given path. + * + * @param string $path Path to check + * @param string $timestamp Timestamp to check + * @return bool + */ + public static function isUniqueTimestamp(string $path, string $timestamp): bool + { + return !count(static::glob($path . DIRECTORY_SEPARATOR . $timestamp . '*.php')); + } + /** * Gets an array of all the existing migration class names. * @@ -98,16 +114,33 @@ public static function getVersionFromFileName(string $fileName): int return $value; } + /** + * Given a string, convert it to snake_case. + * + * @param string $string String to convert + * @return string + */ + public static function toSnakeCase(string $string): string + { + $snake = function ($matches) { + return '_' . strtolower($matches[0]); + }; + + return preg_replace_callback('/\d+|[A-Z]/', $snake, $string); + } + /** * Turn migration names like 'CreateUserTable' into file names like * '12345678901234_create_user_table.php' or 'LimitResourceNamesTo30Chars' into * '12345678901234_limit_resource_names_to_30_chars.php'. * + * @deprecated Will be removed in 0.17.0 * @param string $className Class Name * @return string */ public static function mapClassNameToFileName(string $className): string { + trigger_error('Util::mapClassNameToFileName is deprecated since 0.16.6, and will be removed in a future release.', E_USER_DEPRECATED); $snake = function ($matches) { return '_' . strtolower($matches[0]); }; @@ -236,7 +269,7 @@ public static function glob(string $path): array * @throws \Exception * @return string */ - public static function loadPhpFile(string $filename, ?InputInterface $input = null, ?OutputInterface $output = null, $context = null): string + public static function loadPhpFile(string $filename, ?InputInterface $input = null, ?OutputInterface $output = null, mixed $context = null): string { $filePath = realpath($filename); if (!file_exists($filePath)) { @@ -268,7 +301,7 @@ public static function loadPhpFile(string $filename, ?InputInterface $input = nu * @param string|string[] $paths Path or array of paths to get .php files. * @return string[] */ - public static function getFiles($paths): array + public static function getFiles(string|array $paths): array { $files = static::globAll(array_map(function ($path) { return $path . DIRECTORY_SEPARATOR . '*.php'; diff --git a/app/vendor/robmorgan/phinx/src/Phinx/Wrapper/TextWrapper.php b/app/vendor/robmorgan/phinx/src/Phinx/Wrapper/TextWrapper.php index 62b7f2d8a..1e23ea9b9 100644 --- a/app/vendor/robmorgan/phinx/src/Phinx/Wrapper/TextWrapper.php +++ b/app/vendor/robmorgan/phinx/src/Phinx/Wrapper/TextWrapper.php @@ -1,4 +1,5 @@ */ class TextWrapper { /** * @var \Phinx\Console\PhinxApplication */ - protected $app; + protected PhinxApplication $app; /** * @var array */ - protected $options; + protected array $options; /** * @var int */ - protected $exitCode; + protected int $exitCode; /** * @param \Phinx\Console\PhinxApplication $app Application @@ -93,7 +92,7 @@ public function getStatus(?string $env = null): string * @param string|null $env environment name * @return bool */ - private function hasEnvValue($env): bool + private function hasEnvValue(?string $env): bool { return $env || $this->hasOption('environment'); } @@ -132,7 +131,7 @@ public function getMigrate(?string $env = null, ?string $target = null): string * @param string[]|string|null $seed Array of seed names or seed name * @return string */ - public function getSeed(?string $env = null, ?string $target = null, $seed = null): string + public function getSeed(?string $env = null, ?string $target = null, array|string|null $seed = null): string { $command = ['seed:run']; if ($this->hasEnvValue($env)) { @@ -162,7 +161,7 @@ public function getSeed(?string $env = null, ?string $target = null, $seed = nul * @param mixed $target Target version, or 0 (zero) fully revert (optional) * @return string */ - public function getRollback(?string $env = null, $target = null): string + public function getRollback(?string $env = null, mixed $target = null): string { $command = ['rollback']; if ($this->hasEnvValue($env)) { diff --git a/app/vendor/robmorgan/phinx/src/composer_autoloader.php b/app/vendor/robmorgan/phinx/src/composer_autoloader.php index 2cde6660c..51cac42c9 100644 --- a/app/vendor/robmorgan/phinx/src/composer_autoloader.php +++ b/app/vendor/robmorgan/phinx/src/composer_autoloader.php @@ -1,4 +1,5 @@ . +Copyright (c) 2020-2025, Sebastian Bergmann All rights reserved. Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: +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. +1. 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. +2. 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 Sebastian Bergmann nor the names of his - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. +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/app/vendor/sebastian/cli-parser/README.md b/app/vendor/sebastian/cli-parser/README.md index 39c17a72c..d974c124c 100644 --- a/app/vendor/sebastian/cli-parser/README.md +++ b/app/vendor/sebastian/cli-parser/README.md @@ -1,3 +1,7 @@ +[![Latest Stable Version](https://poser.pugx.org/sebastian/cli-parser/v)](https://packagist.org/packages/sebastian/cli-parser) +[![CI Status](https://github.com/sebastianbergmann/cli-parser/workflows/CI/badge.svg)](https://github.com/sebastianbergmann/cli-parser/actions) +[![codecov](https://codecov.io/gh/sebastianbergmann/cli-parser/branch/main/graph/badge.svg)](https://codecov.io/gh/sebastianbergmann/cli-parser) + # sebastian/cli-parser Library for parsing `$_SERVER['argv']`, extracted from `phpunit/phpunit`. diff --git a/app/vendor/sebastian/cli-parser/SECURITY.md b/app/vendor/sebastian/cli-parser/SECURITY.md new file mode 100644 index 000000000..d88ff0019 --- /dev/null +++ b/app/vendor/sebastian/cli-parser/SECURITY.md @@ -0,0 +1,30 @@ +# Security Policy + +If you believe you have found a security vulnerability in the library that is developed in this repository, please report it to us through coordinated disclosure. + +**Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.** + +Instead, please email `sebastian@phpunit.de`. + +Please include as much of the information listed below as you can to help us better understand and resolve the issue: + +* The type of issue +* Full paths of source file(s) related to the manifestation of the issue +* The location of the affected source code (tag/branch/commit or direct URL) +* Any special configuration required to reproduce the issue +* Step-by-step instructions to reproduce the issue +* Proof-of-concept or exploit code (if possible) +* Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +## Web Context + +The library that is developed in this repository was either extracted from [PHPUnit](https://github.com/sebastianbergmann/phpunit) or developed specifically as a dependency for PHPUnit. + +The library is developed with a focus on development environments and the command-line. No specific testing or hardening with regard to using the library in an HTTP or web context or with untrusted input data is performed. The library might also contain functionality that intentionally exposes internal application data for debugging purposes. + +If the library is used in a web application, the application developer is responsible for filtering inputs or escaping outputs as necessary and for verifying that the used functionality is safe for use within the intended context. + +Vulnerabilities specific to the use outside a development context will be fixed as applicable, provided that the fix does not have an averse effect on the primary use case for development purposes. + diff --git a/app/vendor/sebastian/cli-parser/composer.json b/app/vendor/sebastian/cli-parser/composer.json index 34c376f91..60f28955f 100644 --- a/app/vendor/sebastian/cli-parser/composer.json +++ b/app/vendor/sebastian/cli-parser/composer.json @@ -12,18 +12,19 @@ } ], "support": { - "issues": "https://github.com/sebastianbergmann/cli-parser/issues" + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy" }, "prefer-stable": true, "require": { - "php": ">=7.3" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^12.0" }, "config": { "platform": { - "php": "7.3.0" + "php": "8.3.0" }, "optimize-autoloader": true, "sort-packages": true @@ -35,7 +36,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-main": "4.0-dev" } } } diff --git a/app/vendor/sebastian/cli-parser/infection.json b/app/vendor/sebastian/cli-parser/infection.json deleted file mode 100644 index 09546514f..000000000 --- a/app/vendor/sebastian/cli-parser/infection.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "source": { - "directories": [ - "src" - ] - }, - "mutators": { - "@default": true - }, - "minMsi": 100, - "minCoveredMsi": 100 -} diff --git a/app/vendor/sebastian/cli-parser/src/Parser.php b/app/vendor/sebastian/cli-parser/src/Parser.php index 67d8909f5..e1f02291d 100644 --- a/app/vendor/sebastian/cli-parser/src/Parser.php +++ b/app/vendor/sebastian/cli-parser/src/Parser.php @@ -19,27 +19,29 @@ use function explode; use function is_array; use function is_int; -use function is_string; use function key; use function next; use function preg_replace; use function reset; use function sort; +use function str_ends_with; +use function str_starts_with; use function strlen; -use function strpos; use function strstr; use function substr; final class Parser { /** - * @psalm-param list $argv - * @psalm-param list $longOptions + * @param list $argv + * @param list $longOptions * * @throws AmbiguousOptionException - * @throws RequiredOptionArgumentMissingException * @throws OptionDoesNotAllowArgumentException + * @throws RequiredOptionArgumentMissingException * @throws UnknownOptionException + * + * @return array{0: list, 1: list} */ public function parse(array $argv, string $shortOptions, ?array $longOptions = null): array { @@ -47,10 +49,10 @@ public function parse(array $argv, string $shortOptions, ?array $longOptions = n return [[], []]; } - $options = []; - $nonOptions = []; + $options = []; + $nonOptions = []; - if ($longOptions) { + if ($longOptions !== null) { sort($longOptions); } @@ -79,7 +81,7 @@ public function parse(array $argv, string $shortOptions, ?array $longOptions = n break; } - if ($arg[0] !== '-' || (strlen($arg) > 1 && $arg[1] === '-' && !$longOptions)) { + if ($arg[0] !== '-' || (strlen($arg) > 1 && $arg[1] === '-' && $longOptions === null)) { $nonOptions[] = $arg; continue; @@ -90,74 +92,77 @@ public function parse(array $argv, string $shortOptions, ?array $longOptions = n substr($arg, 2), $longOptions, $options, - $argv - ); - } else { - $this->parseShortOption( - substr($arg, 1), - $shortOptions, - $options, - $argv + $argv, ); + + continue; } + + $this->parseShortOption( + substr($arg, 1), + $shortOptions, + $options, + $argv, + ); } return [$options, $nonOptions]; } /** + * @param list $options + * @param list $argv + * * @throws RequiredOptionArgumentMissingException */ - private function parseShortOption(string $arg, string $shortOptions, array &$opts, array &$args): void + private function parseShortOption(string $argument, string $shortOptions, array &$options, array &$argv): void { - $argLength = strlen($arg); + $argumentLength = strlen($argument); - for ($i = 0; $i < $argLength; $i++) { - $option = $arg[$i]; + for ($i = 0; $i < $argumentLength; $i++) { + $option = $argument[$i]; $optionArgument = null; - if ($arg[$i] === ':' || ($spec = strstr($shortOptions, $option)) === false) { + if ($argument[$i] === ':' || ($spec = strstr($shortOptions, $option)) === false) { throw new UnknownOptionException('-' . $option); } - assert(is_string($spec)); - if (strlen($spec) > 1 && $spec[1] === ':') { - if ($i + 1 < $argLength) { - $opts[] = [$option, substr($arg, $i + 1)]; + if ($i + 1 < $argumentLength) { + $options[] = [$option, substr($argument, $i + 1)]; break; } if (!(strlen($spec) > 2 && $spec[2] === ':')) { - $optionArgument = current($args); + $optionArgument = current($argv); - if (!$optionArgument) { + if ($optionArgument === false) { throw new RequiredOptionArgumentMissingException('-' . $option); } - assert(is_string($optionArgument)); - - next($args); + next($argv); } } - $opts[] = [$option, $optionArgument]; + $options[] = [$option, $optionArgument]; } } /** - * @psalm-param list $longOptions + * @param list $longOptions + * @param list $options + * @param list $argv * * @throws AmbiguousOptionException - * @throws RequiredOptionArgumentMissingException * @throws OptionDoesNotAllowArgumentException + * @throws RequiredOptionArgumentMissingException * @throws UnknownOptionException */ - private function parseLongOption(string $arg, array $longOptions, array &$opts, array &$args): void + private function parseLongOption(string $argument, array $longOptions, array &$options, array &$argv): void { $count = count($longOptions); - $list = explode('=', $arg); + $list = explode('=', $argument); $option = $list[0]; $optionArgument = null; @@ -176,25 +181,24 @@ private function parseLongOption(string $arg, array $longOptions, array &$opts, $opt_rest = substr($longOption, $optionLength); - if ($opt_rest !== '' && $i + 1 < $count && $option[0] !== '=' && strpos($longOptions[$i + 1], $option) === 0) { + if ($opt_rest !== '' && $i + 1 < $count && $option[0] !== '=' && str_starts_with($longOptions[$i + 1], $option)) { throw new AmbiguousOptionException('--' . $option); } - if (substr($longOption, -1) === '=') { - /* @noinspection StrlenInEmptyStringCheckContextInspection */ - if (substr($longOption, -2) !== '==' && !strlen((string) $optionArgument)) { - if (false === $optionArgument = current($args)) { + if (str_ends_with($longOption, '=')) { + if (!str_ends_with($longOption, '==') && !strlen((string) $optionArgument)) { + if (false === $optionArgument = current($argv)) { throw new RequiredOptionArgumentMissingException('--' . $option); } - next($args); + next($argv); } - } elseif ($optionArgument) { + } elseif ($optionArgument !== null) { throw new OptionDoesNotAllowArgumentException('--' . $option); } $fullOption = '--' . preg_replace('/={1,2}$/', '', $longOption); - $opts[] = [$fullOption, $optionArgument]; + $options[] = [$fullOption, $optionArgument]; return; } diff --git a/app/vendor/sebastian/cli-parser/src/exceptions/AmbiguousOptionException.php b/app/vendor/sebastian/cli-parser/src/exceptions/AmbiguousOptionException.php index a99f63697..99eb625a7 100644 --- a/app/vendor/sebastian/cli-parser/src/exceptions/AmbiguousOptionException.php +++ b/app/vendor/sebastian/cli-parser/src/exceptions/AmbiguousOptionException.php @@ -19,8 +19,8 @@ public function __construct(string $option) parent::__construct( sprintf( 'Option "%s" is ambiguous', - $option - ) + $option, + ), ); } } diff --git a/app/vendor/sebastian/cli-parser/src/exceptions/OptionDoesNotAllowArgumentException.php b/app/vendor/sebastian/cli-parser/src/exceptions/OptionDoesNotAllowArgumentException.php index 0aad29ac0..7fea616be 100644 --- a/app/vendor/sebastian/cli-parser/src/exceptions/OptionDoesNotAllowArgumentException.php +++ b/app/vendor/sebastian/cli-parser/src/exceptions/OptionDoesNotAllowArgumentException.php @@ -19,8 +19,8 @@ public function __construct(string $option) parent::__construct( sprintf( 'Option "%s" does not allow an argument', - $option - ) + $option, + ), ); } } diff --git a/app/vendor/sebastian/cli-parser/src/exceptions/RequiredOptionArgumentMissingException.php b/app/vendor/sebastian/cli-parser/src/exceptions/RequiredOptionArgumentMissingException.php index d2a930b62..9add49a96 100644 --- a/app/vendor/sebastian/cli-parser/src/exceptions/RequiredOptionArgumentMissingException.php +++ b/app/vendor/sebastian/cli-parser/src/exceptions/RequiredOptionArgumentMissingException.php @@ -19,8 +19,8 @@ public function __construct(string $option) parent::__construct( sprintf( 'Required argument for option "%s" is missing', - $option - ) + $option, + ), ); } } diff --git a/app/vendor/sebastian/cli-parser/src/exceptions/UnknownOptionException.php b/app/vendor/sebastian/cli-parser/src/exceptions/UnknownOptionException.php index e98d9fd02..560c7ad22 100644 --- a/app/vendor/sebastian/cli-parser/src/exceptions/UnknownOptionException.php +++ b/app/vendor/sebastian/cli-parser/src/exceptions/UnknownOptionException.php @@ -19,8 +19,8 @@ public function __construct(string $option) parent::__construct( sprintf( 'Unknown option "%s"', - $option - ) + $option, + ), ); } } diff --git a/app/vendor/sebastian/code-unit-reverse-lookup/ChangeLog.md b/app/vendor/sebastian/code-unit-reverse-lookup/ChangeLog.md deleted file mode 100644 index 43a5db90d..000000000 --- a/app/vendor/sebastian/code-unit-reverse-lookup/ChangeLog.md +++ /dev/null @@ -1,38 +0,0 @@ -# Change Log - -All notable changes to `sebastianbergmann/code-unit-reverse-lookup` are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles. - -## [2.0.3] - 2020-09-28 - -### Changed - -* Changed PHP version constraint in `composer.json` from `^7.3 || ^8.0` to `>=7.3` - -## [2.0.2] - 2020-06-26 - -### Added - -* This component is now supported on PHP 8 - -## [2.0.1] - 2020-06-15 - -### Changed - -* Tests etc. are now ignored for archive exports - -## 2.0.0 - 2020-02-07 - -### Removed - -* This component is no longer supported on PHP 5.6, PHP 7.0, PHP 7.1, and PHP 7.2 - -## 1.0.0 - 2016-02-13 - -### Added - -* Initial release - -[2.0.3]: https://github.com/sebastianbergmann/code-unit-reverse-lookup/compare/2.0.2...2.0.3 -[2.0.2]: https://github.com/sebastianbergmann/code-unit-reverse-lookup/compare/2.0.1...2.0.2 -[2.0.1]: https://github.com/sebastianbergmann/code-unit-reverse-lookup/compare/2.0.0...2.0.1 -[2.0.0]: https://github.com/sebastianbergmann/code-unit-reverse-lookup/compare/1.0.0...2.0.0 diff --git a/app/vendor/sebastian/code-unit-reverse-lookup/LICENSE b/app/vendor/sebastian/code-unit-reverse-lookup/LICENSE deleted file mode 100644 index dc4bf7019..000000000 --- a/app/vendor/sebastian/code-unit-reverse-lookup/LICENSE +++ /dev/null @@ -1,33 +0,0 @@ -code-unit-reverse-lookup - -Copyright (c) 2016-2020, Sebastian Bergmann . -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 Sebastian Bergmann nor the names of his - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. diff --git a/app/vendor/sebastian/code-unit-reverse-lookup/README.md b/app/vendor/sebastian/code-unit-reverse-lookup/README.md deleted file mode 100644 index 1c0ca235e..000000000 --- a/app/vendor/sebastian/code-unit-reverse-lookup/README.md +++ /dev/null @@ -1,20 +0,0 @@ -# sebastian/code-unit-reverse-lookup - -[![CI Status](https://github.com/sebastianbergmann/code-unit-reverse-lookup/workflows/CI/badge.svg)](https://github.com/sebastianbergmann/code-unit-reverse-lookup/actions) -[![Type Coverage](https://shepherd.dev/github/sebastianbergmann/code-unit-reverse-lookup/coverage.svg)](https://shepherd.dev/github/sebastianbergmann/code-unit-reverse-lookup) - -Looks up which function or method a line of code belongs to. - -## Installation - -You can add this library as a local, per-project dependency to your project using [Composer](https://getcomposer.org/): - -``` -composer require sebastian/code-unit-reverse-lookup -``` - -If you only need this library during development, for instance to run your project's test suite, then you should add it as a development-time dependency: - -``` -composer require --dev sebastian/code-unit-reverse-lookup -``` diff --git a/app/vendor/sebastian/code-unit-reverse-lookup/composer.json b/app/vendor/sebastian/code-unit-reverse-lookup/composer.json deleted file mode 100644 index cff96167a..000000000 --- a/app/vendor/sebastian/code-unit-reverse-lookup/composer.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "name": "sebastian/code-unit-reverse-lookup", - "description": "Looks up which function or method a line of code belongs to", - "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", - "license": "BSD-3-Clause", - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "prefer-stable": true, - "config": { - "platform": { - "php": "7.3.0" - }, - "optimize-autoloader": true, - "sort-packages": true - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - } -} diff --git a/app/vendor/sebastian/code-unit-reverse-lookup/src/Wizard.php b/app/vendor/sebastian/code-unit-reverse-lookup/src/Wizard.php deleted file mode 100644 index 35de53981..000000000 --- a/app/vendor/sebastian/code-unit-reverse-lookup/src/Wizard.php +++ /dev/null @@ -1,125 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace SebastianBergmann\CodeUnitReverseLookup; - -use function array_merge; -use function assert; -use function get_declared_classes; -use function get_declared_traits; -use function get_defined_functions; -use function is_array; -use function range; -use ReflectionClass; -use ReflectionFunction; -use ReflectionFunctionAbstract; -use ReflectionMethod; - -/** - * @since Class available since Release 1.0.0 - */ -class Wizard -{ - /** - * @var array - */ - private $lookupTable = []; - - /** - * @var array - */ - private $processedClasses = []; - - /** - * @var array - */ - private $processedFunctions = []; - - /** - * @param string $filename - * @param int $lineNumber - * - * @return string - */ - public function lookup($filename, $lineNumber) - { - if (!isset($this->lookupTable[$filename][$lineNumber])) { - $this->updateLookupTable(); - } - - if (isset($this->lookupTable[$filename][$lineNumber])) { - return $this->lookupTable[$filename][$lineNumber]; - } - - return $filename . ':' . $lineNumber; - } - - private function updateLookupTable(): void - { - $this->processClassesAndTraits(); - $this->processFunctions(); - } - - private function processClassesAndTraits(): void - { - $classes = get_declared_classes(); - $traits = get_declared_traits(); - - assert(is_array($classes)); - assert(is_array($traits)); - - foreach (array_merge($classes, $traits) as $classOrTrait) { - if (isset($this->processedClasses[$classOrTrait])) { - continue; - } - - $reflector = new ReflectionClass($classOrTrait); - - foreach ($reflector->getMethods() as $method) { - $this->processFunctionOrMethod($method); - } - - $this->processedClasses[$classOrTrait] = true; - } - } - - private function processFunctions(): void - { - foreach (get_defined_functions()['user'] as $function) { - if (isset($this->processedFunctions[$function])) { - continue; - } - - $this->processFunctionOrMethod(new ReflectionFunction($function)); - - $this->processedFunctions[$function] = true; - } - } - - private function processFunctionOrMethod(ReflectionFunctionAbstract $functionOrMethod): void - { - if ($functionOrMethod->isInternal()) { - return; - } - - $name = $functionOrMethod->getName(); - - if ($functionOrMethod instanceof ReflectionMethod) { - $name = $functionOrMethod->getDeclaringClass()->getName() . '::' . $name; - } - - if (!isset($this->lookupTable[$functionOrMethod->getFileName()])) { - $this->lookupTable[$functionOrMethod->getFileName()] = []; - } - - foreach (range($functionOrMethod->getStartLine(), $functionOrMethod->getEndLine()) as $line) { - $this->lookupTable[$functionOrMethod->getFileName()][$line] = $name; - } - } -} diff --git a/app/vendor/sebastian/code-unit/.psalm/baseline.xml b/app/vendor/sebastian/code-unit/.psalm/baseline.xml deleted file mode 100644 index e44889190..000000000 --- a/app/vendor/sebastian/code-unit/.psalm/baseline.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - $firstPart - $firstPart - $firstPart - $firstPart - $firstPart - $firstPart - $firstPart - $firstPart - $firstPart - $secondPart - $unit - $unit - $unit - $unit - $unit - $unit - - - diff --git a/app/vendor/sebastian/code-unit/.psalm/config.xml b/app/vendor/sebastian/code-unit/.psalm/config.xml deleted file mode 100644 index a39e9a4c3..000000000 --- a/app/vendor/sebastian/code-unit/.psalm/config.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - diff --git a/app/vendor/sebastian/code-unit/ChangeLog.md b/app/vendor/sebastian/code-unit/ChangeLog.md deleted file mode 100644 index 0978e651e..000000000 --- a/app/vendor/sebastian/code-unit/ChangeLog.md +++ /dev/null @@ -1,65 +0,0 @@ -# ChangeLog - -All notable changes are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles. - -## [1.0.8] - 2020-10-26 - -### Fixed - -* `SebastianBergmann\CodeUnit\Exception` now correctly extends `\Throwable` - -## [1.0.7] - 2020-10-02 - -### Fixed - -* `SebastianBergmann\CodeUnit\Mapper::stringToCodeUnits()` no longer attempts to create `CodeUnit` objects for code units that are not declared in userland - -## [1.0.6] - 2020-09-28 - -### Changed - -* Changed PHP version constraint in `composer.json` from `^7.3 || ^8.0` to `>=7.3` - -## [1.0.5] - 2020-06-26 - -### Fixed - -* [#3](https://github.com/sebastianbergmann/code-unit/issues/3): Regression in 1.0.4 - -## [1.0.4] - 2020-06-26 - -### Added - -* This component is now supported on PHP 8 - -## [1.0.3] - 2020-06-15 - -### Changed - -* Tests etc. are now ignored for archive exports - -## [1.0.2] - 2020-04-30 - -### Fixed - -* `Mapper::stringToCodeUnits()` raised the wrong exception for `Class::method` when a class named `Class` exists but does not have a method named `method` - -## [1.0.1] - 2020-04-27 - -### Fixed - -* [#2](https://github.com/sebastianbergmann/code-unit/issues/2): `Mapper::stringToCodeUnits()` breaks when `ClassName` is used for class that extends built-in class - -## [1.0.0] - 2020-03-30 - -* Initial release - -[1.0.8]: https://github.com/sebastianbergmann/code-unit/compare/1.0.7...1.0.8 -[1.0.7]: https://github.com/sebastianbergmann/code-unit/compare/1.0.6...1.0.7 -[1.0.6]: https://github.com/sebastianbergmann/code-unit/compare/1.0.5...1.0.6 -[1.0.5]: https://github.com/sebastianbergmann/code-unit/compare/1.0.4...1.0.5 -[1.0.4]: https://github.com/sebastianbergmann/code-unit/compare/1.0.3...1.0.4 -[1.0.3]: https://github.com/sebastianbergmann/code-unit/compare/1.0.2...1.0.3 -[1.0.2]: https://github.com/sebastianbergmann/code-unit/compare/1.0.1...1.0.2 -[1.0.1]: https://github.com/sebastianbergmann/code-unit/compare/1.0.0...1.0.1 -[1.0.0]: https://github.com/sebastianbergmann/code-unit/compare/530c3900e5db9bcb8516da545bef0d62536cedaa...1.0.0 diff --git a/app/vendor/sebastian/code-unit/LICENSE b/app/vendor/sebastian/code-unit/LICENSE deleted file mode 100644 index b99bc8ac4..000000000 --- a/app/vendor/sebastian/code-unit/LICENSE +++ /dev/null @@ -1,33 +0,0 @@ -sebastian/code-unit - -Copyright (c) 2020, Sebastian Bergmann . -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 Sebastian Bergmann nor the names of his - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. diff --git a/app/vendor/sebastian/code-unit/README.md b/app/vendor/sebastian/code-unit/README.md deleted file mode 100644 index d20227a9b..000000000 --- a/app/vendor/sebastian/code-unit/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# sebastian/code-unit - -Collection of value objects that represent the PHP code units. - -## Installation - -You can add this library as a local, per-project dependency to your project using [Composer](https://getcomposer.org/): - -``` -composer require sebastian/code-unit -``` - -If you only need this library during development, for instance to run your project's test suite, then you should add it as a development-time dependency: - -``` -composer require --dev sebastian/code-unit -``` diff --git a/app/vendor/sebastian/code-unit/composer.json b/app/vendor/sebastian/code-unit/composer.json deleted file mode 100644 index 5b86ec589..000000000 --- a/app/vendor/sebastian/code-unit/composer.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "name": "sebastian/code-unit", - "description": "Collection of value objects that represent the PHP code units", - "type": "library", - "homepage": "https://github.com/sebastianbergmann/code-unit", - "license": "BSD-3-Clause", - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "support": { - "issues": "https://github.com/sebastianbergmann/code-unit/issues" - }, - "prefer-stable": true, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "config": { - "platform": { - "php": "7.3.0" - }, - "optimize-autoloader": true, - "sort-packages": true - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "autoload-dev": { - "classmap": [ - "tests/_fixture" - ], - "files": [ - "tests/_fixture/file_with_multiple_code_units.php", - "tests/_fixture/function.php" - ] - }, - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - } -} diff --git a/app/vendor/sebastian/code-unit/src/ClassMethodUnit.php b/app/vendor/sebastian/code-unit/src/ClassMethodUnit.php deleted file mode 100644 index f9ddac29e..000000000 --- a/app/vendor/sebastian/code-unit/src/ClassMethodUnit.php +++ /dev/null @@ -1,24 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace SebastianBergmann\CodeUnit; - -/** - * @psalm-immutable - */ -final class ClassMethodUnit extends CodeUnit -{ - /** - * @psalm-assert-if-true ClassMethodUnit $this - */ - public function isClassMethod(): bool - { - return true; - } -} diff --git a/app/vendor/sebastian/code-unit/src/ClassUnit.php b/app/vendor/sebastian/code-unit/src/ClassUnit.php deleted file mode 100644 index 3ba0ee661..000000000 --- a/app/vendor/sebastian/code-unit/src/ClassUnit.php +++ /dev/null @@ -1,24 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace SebastianBergmann\CodeUnit; - -/** - * @psalm-immutable - */ -final class ClassUnit extends CodeUnit -{ - /** - * @psalm-assert-if-true ClassUnit $this - */ - public function isClass(): bool - { - return true; - } -} diff --git a/app/vendor/sebastian/code-unit/src/CodeUnit.php b/app/vendor/sebastian/code-unit/src/CodeUnit.php deleted file mode 100644 index 9e5cceb35..000000000 --- a/app/vendor/sebastian/code-unit/src/CodeUnit.php +++ /dev/null @@ -1,445 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace SebastianBergmann\CodeUnit; - -use function range; -use function sprintf; -use ReflectionClass; -use ReflectionFunction; -use ReflectionMethod; - -/** - * @psalm-immutable - */ -abstract class CodeUnit -{ - /** - * @var string - */ - private $name; - - /** - * @var string - */ - private $sourceFileName; - - /** - * @var array - * @psalm-var list - */ - private $sourceLines; - - /** - * @psalm-param class-string $className - * - * @throws InvalidCodeUnitException - * @throws ReflectionException - */ - public static function forClass(string $className): ClassUnit - { - self::ensureUserDefinedClass($className); - - $reflector = self::reflectorForClass($className); - - return new ClassUnit( - $className, - $reflector->getFileName(), - range( - $reflector->getStartLine(), - $reflector->getEndLine() - ) - ); - } - - /** - * @psalm-param class-string $className - * - * @throws InvalidCodeUnitException - * @throws ReflectionException - */ - public static function forClassMethod(string $className, string $methodName): ClassMethodUnit - { - self::ensureUserDefinedClass($className); - - $reflector = self::reflectorForClassMethod($className, $methodName); - - return new ClassMethodUnit( - $className . '::' . $methodName, - $reflector->getFileName(), - range( - $reflector->getStartLine(), - $reflector->getEndLine() - ) - ); - } - - /** - * @psalm-param class-string $interfaceName - * - * @throws InvalidCodeUnitException - * @throws ReflectionException - */ - public static function forInterface(string $interfaceName): InterfaceUnit - { - self::ensureUserDefinedInterface($interfaceName); - - $reflector = self::reflectorForClass($interfaceName); - - return new InterfaceUnit( - $interfaceName, - $reflector->getFileName(), - range( - $reflector->getStartLine(), - $reflector->getEndLine() - ) - ); - } - - /** - * @psalm-param class-string $interfaceName - * - * @throws InvalidCodeUnitException - * @throws ReflectionException - */ - public static function forInterfaceMethod(string $interfaceName, string $methodName): InterfaceMethodUnit - { - self::ensureUserDefinedInterface($interfaceName); - - $reflector = self::reflectorForClassMethod($interfaceName, $methodName); - - return new InterfaceMethodUnit( - $interfaceName . '::' . $methodName, - $reflector->getFileName(), - range( - $reflector->getStartLine(), - $reflector->getEndLine() - ) - ); - } - - /** - * @psalm-param class-string $traitName - * - * @throws InvalidCodeUnitException - * @throws ReflectionException - */ - public static function forTrait(string $traitName): TraitUnit - { - self::ensureUserDefinedTrait($traitName); - - $reflector = self::reflectorForClass($traitName); - - return new TraitUnit( - $traitName, - $reflector->getFileName(), - range( - $reflector->getStartLine(), - $reflector->getEndLine() - ) - ); - } - - /** - * @psalm-param class-string $traitName - * - * @throws InvalidCodeUnitException - * @throws ReflectionException - */ - public static function forTraitMethod(string $traitName, string $methodName): TraitMethodUnit - { - self::ensureUserDefinedTrait($traitName); - - $reflector = self::reflectorForClassMethod($traitName, $methodName); - - return new TraitMethodUnit( - $traitName . '::' . $methodName, - $reflector->getFileName(), - range( - $reflector->getStartLine(), - $reflector->getEndLine() - ) - ); - } - - /** - * @psalm-param callable-string $functionName - * - * @throws InvalidCodeUnitException - * @throws ReflectionException - */ - public static function forFunction(string $functionName): FunctionUnit - { - $reflector = self::reflectorForFunction($functionName); - - if (!$reflector->isUserDefined()) { - throw new InvalidCodeUnitException( - sprintf( - '"%s" is not a user-defined function', - $functionName - ) - ); - } - - return new FunctionUnit( - $functionName, - $reflector->getFileName(), - range( - $reflector->getStartLine(), - $reflector->getEndLine() - ) - ); - } - - /** - * @psalm-param list $sourceLines - */ - private function __construct(string $name, string $sourceFileName, array $sourceLines) - { - $this->name = $name; - $this->sourceFileName = $sourceFileName; - $this->sourceLines = $sourceLines; - } - - public function name(): string - { - return $this->name; - } - - public function sourceFileName(): string - { - return $this->sourceFileName; - } - - /** - * @psalm-return list - */ - public function sourceLines(): array - { - return $this->sourceLines; - } - - public function isClass(): bool - { - return false; - } - - public function isClassMethod(): bool - { - return false; - } - - public function isInterface(): bool - { - return false; - } - - public function isInterfaceMethod(): bool - { - return false; - } - - public function isTrait(): bool - { - return false; - } - - public function isTraitMethod(): bool - { - return false; - } - - public function isFunction(): bool - { - return false; - } - - /** - * @psalm-param class-string $className - * - * @throws InvalidCodeUnitException - */ - private static function ensureUserDefinedClass(string $className): void - { - try { - $reflector = new ReflectionClass($className); - - if ($reflector->isInterface()) { - throw new InvalidCodeUnitException( - sprintf( - '"%s" is an interface and not a class', - $className - ) - ); - } - - if ($reflector->isTrait()) { - throw new InvalidCodeUnitException( - sprintf( - '"%s" is a trait and not a class', - $className - ) - ); - } - - if (!$reflector->isUserDefined()) { - throw new InvalidCodeUnitException( - sprintf( - '"%s" is not a user-defined class', - $className - ) - ); - } - // @codeCoverageIgnoreStart - } catch (\ReflectionException $e) { - throw new ReflectionException( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - // @codeCoverageIgnoreEnd - } - - /** - * @psalm-param class-string $interfaceName - * - * @throws InvalidCodeUnitException - */ - private static function ensureUserDefinedInterface(string $interfaceName): void - { - try { - $reflector = new ReflectionClass($interfaceName); - - if (!$reflector->isInterface()) { - throw new InvalidCodeUnitException( - sprintf( - '"%s" is not an interface', - $interfaceName - ) - ); - } - - if (!$reflector->isUserDefined()) { - throw new InvalidCodeUnitException( - sprintf( - '"%s" is not a user-defined interface', - $interfaceName - ) - ); - } - // @codeCoverageIgnoreStart - } catch (\ReflectionException $e) { - throw new ReflectionException( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - // @codeCoverageIgnoreEnd - } - - /** - * @psalm-param class-string $traitName - * - * @throws InvalidCodeUnitException - */ - private static function ensureUserDefinedTrait(string $traitName): void - { - try { - $reflector = new ReflectionClass($traitName); - - if (!$reflector->isTrait()) { - throw new InvalidCodeUnitException( - sprintf( - '"%s" is not a trait', - $traitName - ) - ); - } - - // @codeCoverageIgnoreStart - if (!$reflector->isUserDefined()) { - throw new InvalidCodeUnitException( - sprintf( - '"%s" is not a user-defined trait', - $traitName - ) - ); - } - } catch (\ReflectionException $e) { - throw new ReflectionException( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - // @codeCoverageIgnoreEnd - } - - /** - * @psalm-param class-string $className - * - * @throws ReflectionException - */ - private static function reflectorForClass(string $className): ReflectionClass - { - try { - return new ReflectionClass($className); - // @codeCoverageIgnoreStart - } catch (\ReflectionException $e) { - throw new ReflectionException( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - // @codeCoverageIgnoreEnd - } - - /** - * @psalm-param class-string $className - * - * @throws ReflectionException - */ - private static function reflectorForClassMethod(string $className, string $methodName): ReflectionMethod - { - try { - return new ReflectionMethod($className, $methodName); - // @codeCoverageIgnoreStart - } catch (\ReflectionException $e) { - throw new ReflectionException( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - // @codeCoverageIgnoreEnd - } - - /** - * @psalm-param callable-string $functionName - * - * @throws ReflectionException - */ - private static function reflectorForFunction(string $functionName): ReflectionFunction - { - try { - return new ReflectionFunction($functionName); - // @codeCoverageIgnoreStart - } catch (\ReflectionException $e) { - throw new ReflectionException( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - // @codeCoverageIgnoreEnd - } -} diff --git a/app/vendor/sebastian/code-unit/src/CodeUnitCollection.php b/app/vendor/sebastian/code-unit/src/CodeUnitCollection.php deleted file mode 100644 index f53db8a12..000000000 --- a/app/vendor/sebastian/code-unit/src/CodeUnitCollection.php +++ /dev/null @@ -1,84 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace SebastianBergmann\CodeUnit; - -use function array_merge; -use function count; -use Countable; -use IteratorAggregate; - -final class CodeUnitCollection implements Countable, IteratorAggregate -{ - /** - * @psalm-var list - */ - private $codeUnits = []; - - /** - * @psalm-param list $items - */ - public static function fromArray(array $items): self - { - $collection = new self; - - foreach ($items as $item) { - $collection->add($item); - } - - return $collection; - } - - public static function fromList(CodeUnit ...$items): self - { - return self::fromArray($items); - } - - private function __construct() - { - } - - /** - * @psalm-return list - */ - public function asArray(): array - { - return $this->codeUnits; - } - - public function getIterator(): CodeUnitCollectionIterator - { - return new CodeUnitCollectionIterator($this); - } - - public function count(): int - { - return count($this->codeUnits); - } - - public function isEmpty(): bool - { - return empty($this->codeUnits); - } - - public function mergeWith(self $other): self - { - return self::fromArray( - array_merge( - $this->asArray(), - $other->asArray() - ) - ); - } - - private function add(CodeUnit $item): void - { - $this->codeUnits[] = $item; - } -} diff --git a/app/vendor/sebastian/code-unit/src/CodeUnitCollectionIterator.php b/app/vendor/sebastian/code-unit/src/CodeUnitCollectionIterator.php deleted file mode 100644 index bdc86d888..000000000 --- a/app/vendor/sebastian/code-unit/src/CodeUnitCollectionIterator.php +++ /dev/null @@ -1,55 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace SebastianBergmann\CodeUnit; - -use Iterator; - -final class CodeUnitCollectionIterator implements Iterator -{ - /** - * @psalm-var list - */ - private $codeUnits; - - /** - * @var int - */ - private $position = 0; - - public function __construct(CodeUnitCollection $collection) - { - $this->codeUnits = $collection->asArray(); - } - - public function rewind(): void - { - $this->position = 0; - } - - public function valid(): bool - { - return isset($this->codeUnits[$this->position]); - } - - public function key(): int - { - return $this->position; - } - - public function current(): CodeUnit - { - return $this->codeUnits[$this->position]; - } - - public function next(): void - { - $this->position++; - } -} diff --git a/app/vendor/sebastian/code-unit/src/FunctionUnit.php b/app/vendor/sebastian/code-unit/src/FunctionUnit.php deleted file mode 100644 index df76cf195..000000000 --- a/app/vendor/sebastian/code-unit/src/FunctionUnit.php +++ /dev/null @@ -1,24 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace SebastianBergmann\CodeUnit; - -/** - * @psalm-immutable - */ -final class FunctionUnit extends CodeUnit -{ - /** - * @psalm-assert-if-true FunctionUnit $this - */ - public function isFunction(): bool - { - return true; - } -} diff --git a/app/vendor/sebastian/code-unit/src/InterfaceMethodUnit.php b/app/vendor/sebastian/code-unit/src/InterfaceMethodUnit.php deleted file mode 100644 index fcd44f41a..000000000 --- a/app/vendor/sebastian/code-unit/src/InterfaceMethodUnit.php +++ /dev/null @@ -1,24 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace SebastianBergmann\CodeUnit; - -/** - * @psalm-immutable - */ -final class InterfaceMethodUnit extends CodeUnit -{ - /** - * @psalm-assert-if-true InterfaceMethod $this - */ - public function isInterfaceMethod(): bool - { - return true; - } -} diff --git a/app/vendor/sebastian/code-unit/src/InterfaceUnit.php b/app/vendor/sebastian/code-unit/src/InterfaceUnit.php deleted file mode 100644 index 5cf585bfd..000000000 --- a/app/vendor/sebastian/code-unit/src/InterfaceUnit.php +++ /dev/null @@ -1,24 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace SebastianBergmann\CodeUnit; - -/** - * @psalm-immutable - */ -final class InterfaceUnit extends CodeUnit -{ - /** - * @psalm-assert-if-true InterfaceUnit $this - */ - public function isInterface(): bool - { - return true; - } -} diff --git a/app/vendor/sebastian/code-unit/src/Mapper.php b/app/vendor/sebastian/code-unit/src/Mapper.php deleted file mode 100644 index a72b3b0dd..000000000 --- a/app/vendor/sebastian/code-unit/src/Mapper.php +++ /dev/null @@ -1,414 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace SebastianBergmann\CodeUnit; - -use function array_keys; -use function array_merge; -use function array_unique; -use function array_values; -use function class_exists; -use function explode; -use function function_exists; -use function interface_exists; -use function ksort; -use function method_exists; -use function sort; -use function sprintf; -use function str_replace; -use function strpos; -use function trait_exists; -use ReflectionClass; -use ReflectionFunction; -use ReflectionMethod; - -final class Mapper -{ - /** - * @psalm-return array> - */ - public function codeUnitsToSourceLines(CodeUnitCollection $codeUnits): array - { - $result = []; - - foreach ($codeUnits as $codeUnit) { - $sourceFileName = $codeUnit->sourceFileName(); - - if (!isset($result[$sourceFileName])) { - $result[$sourceFileName] = []; - } - - $result[$sourceFileName] = array_merge($result[$sourceFileName], $codeUnit->sourceLines()); - } - - foreach (array_keys($result) as $sourceFileName) { - $result[$sourceFileName] = array_values(array_unique($result[$sourceFileName])); - - sort($result[$sourceFileName]); - } - - ksort($result); - - return $result; - } - - /** - * @throws InvalidCodeUnitException - * @throws ReflectionException - */ - public function stringToCodeUnits(string $unit): CodeUnitCollection - { - if (strpos($unit, '::') !== false) { - [$firstPart, $secondPart] = explode('::', $unit); - - if (empty($firstPart) && $this->isUserDefinedFunction($secondPart)) { - return CodeUnitCollection::fromList(CodeUnit::forFunction($secondPart)); - } - - if ($this->isUserDefinedClass($firstPart)) { - if ($secondPart === '') { - return $this->publicMethodsOfClass($firstPart); - } - - if ($secondPart === '') { - return $this->protectedAndPrivateMethodsOfClass($firstPart); - } - - if ($secondPart === '') { - return $this->protectedMethodsOfClass($firstPart); - } - - if ($secondPart === '') { - return $this->publicAndPrivateMethodsOfClass($firstPart); - } - - if ($secondPart === '') { - return $this->privateMethodsOfClass($firstPart); - } - - if ($secondPart === '') { - return $this->publicAndProtectedMethodsOfClass($firstPart); - } - - if ($this->isUserDefinedMethod($firstPart, $secondPart)) { - return CodeUnitCollection::fromList(CodeUnit::forClassMethod($firstPart, $secondPart)); - } - } - - if ($this->isUserDefinedInterface($firstPart)) { - return CodeUnitCollection::fromList(CodeUnit::forInterfaceMethod($firstPart, $secondPart)); - } - - if ($this->isUserDefinedTrait($firstPart)) { - return CodeUnitCollection::fromList(CodeUnit::forTraitMethod($firstPart, $secondPart)); - } - } else { - if ($this->isUserDefinedClass($unit)) { - $units = [CodeUnit::forClass($unit)]; - - foreach ($this->reflectorForClass($unit)->getTraits() as $trait) { - if (!$trait->isUserDefined()) { - // @codeCoverageIgnoreStart - continue; - // @codeCoverageIgnoreEnd - } - - $units[] = CodeUnit::forTrait($trait->getName()); - } - - return CodeUnitCollection::fromArray($units); - } - - if ($this->isUserDefinedInterface($unit)) { - return CodeUnitCollection::fromList(CodeUnit::forInterface($unit)); - } - - if ($this->isUserDefinedTrait($unit)) { - return CodeUnitCollection::fromList(CodeUnit::forTrait($unit)); - } - - if ($this->isUserDefinedFunction($unit)) { - return CodeUnitCollection::fromList(CodeUnit::forFunction($unit)); - } - - $unit = str_replace('', '', $unit); - - if ($this->isUserDefinedClass($unit)) { - return $this->classAndParentClassesAndTraits($unit); - } - } - - throw new InvalidCodeUnitException( - sprintf( - '"%s" is not a valid code unit', - $unit - ) - ); - } - - /** - * @psalm-param class-string $className - * - * @throws ReflectionException - */ - private function publicMethodsOfClass(string $className): CodeUnitCollection - { - return $this->methodsOfClass($className, ReflectionMethod::IS_PUBLIC); - } - - /** - * @psalm-param class-string $className - * - * @throws ReflectionException - */ - private function publicAndProtectedMethodsOfClass(string $className): CodeUnitCollection - { - return $this->methodsOfClass($className, ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED); - } - - /** - * @psalm-param class-string $className - * - * @throws ReflectionException - */ - private function publicAndPrivateMethodsOfClass(string $className): CodeUnitCollection - { - return $this->methodsOfClass($className, ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PRIVATE); - } - - /** - * @psalm-param class-string $className - * - * @throws ReflectionException - */ - private function protectedMethodsOfClass(string $className): CodeUnitCollection - { - return $this->methodsOfClass($className, ReflectionMethod::IS_PROTECTED); - } - - /** - * @psalm-param class-string $className - * - * @throws ReflectionException - */ - private function protectedAndPrivateMethodsOfClass(string $className): CodeUnitCollection - { - return $this->methodsOfClass($className, ReflectionMethod::IS_PROTECTED | ReflectionMethod::IS_PRIVATE); - } - - /** - * @psalm-param class-string $className - * - * @throws ReflectionException - */ - private function privateMethodsOfClass(string $className): CodeUnitCollection - { - return $this->methodsOfClass($className, ReflectionMethod::IS_PRIVATE); - } - - /** - * @psalm-param class-string $className - * - * @throws ReflectionException - */ - private function methodsOfClass(string $className, int $filter): CodeUnitCollection - { - $units = []; - - foreach ($this->reflectorForClass($className)->getMethods($filter) as $method) { - if (!$method->isUserDefined()) { - continue; - } - - $units[] = CodeUnit::forClassMethod($className, $method->getName()); - } - - return CodeUnitCollection::fromArray($units); - } - - /** - * @psalm-param class-string $className - * - * @throws ReflectionException - */ - private function classAndParentClassesAndTraits(string $className): CodeUnitCollection - { - $units = [CodeUnit::forClass($className)]; - - $reflector = $this->reflectorForClass($className); - - foreach ($this->reflectorForClass($className)->getTraits() as $trait) { - if (!$trait->isUserDefined()) { - // @codeCoverageIgnoreStart - continue; - // @codeCoverageIgnoreEnd - } - - $units[] = CodeUnit::forTrait($trait->getName()); - } - - while ($reflector = $reflector->getParentClass()) { - if (!$reflector->isUserDefined()) { - break; - } - - $units[] = CodeUnit::forClass($reflector->getName()); - - foreach ($reflector->getTraits() as $trait) { - if (!$trait->isUserDefined()) { - // @codeCoverageIgnoreStart - continue; - // @codeCoverageIgnoreEnd - } - - $units[] = CodeUnit::forTrait($trait->getName()); - } - } - - return CodeUnitCollection::fromArray($units); - } - - /** - * @psalm-param class-string $className - * - * @throws ReflectionException - */ - private function reflectorForClass(string $className): ReflectionClass - { - try { - return new ReflectionClass($className); - // @codeCoverageIgnoreStart - } catch (\ReflectionException $e) { - throw new ReflectionException( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - // @codeCoverageIgnoreEnd - } - - /** - * @throws ReflectionException - */ - private function isUserDefinedFunction(string $functionName): bool - { - if (!function_exists($functionName)) { - return false; - } - - try { - return (new ReflectionFunction($functionName))->isUserDefined(); - // @codeCoverageIgnoreStart - } catch (\ReflectionException $e) { - throw new ReflectionException( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - // @codeCoverageIgnoreEnd - } - - /** - * @throws ReflectionException - */ - private function isUserDefinedClass(string $className): bool - { - if (!class_exists($className)) { - return false; - } - - try { - return (new ReflectionClass($className))->isUserDefined(); - // @codeCoverageIgnoreStart - } catch (\ReflectionException $e) { - throw new ReflectionException( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - // @codeCoverageIgnoreEnd - } - - /** - * @throws ReflectionException - */ - private function isUserDefinedInterface(string $interfaceName): bool - { - if (!interface_exists($interfaceName)) { - return false; - } - - try { - return (new ReflectionClass($interfaceName))->isUserDefined(); - // @codeCoverageIgnoreStart - } catch (\ReflectionException $e) { - throw new ReflectionException( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - // @codeCoverageIgnoreEnd - } - - /** - * @throws ReflectionException - */ - private function isUserDefinedTrait(string $traitName): bool - { - if (!trait_exists($traitName)) { - return false; - } - - try { - return (new ReflectionClass($traitName))->isUserDefined(); - // @codeCoverageIgnoreStart - } catch (\ReflectionException $e) { - throw new ReflectionException( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - // @codeCoverageIgnoreEnd - } - - /** - * @throws ReflectionException - */ - private function isUserDefinedMethod(string $className, string $methodName): bool - { - if (!class_exists($className)) { - // @codeCoverageIgnoreStart - return false; - // @codeCoverageIgnoreEnd - } - - if (!method_exists($className, $methodName)) { - // @codeCoverageIgnoreStart - return false; - // @codeCoverageIgnoreEnd - } - - try { - return (new ReflectionMethod($className, $methodName))->isUserDefined(); - // @codeCoverageIgnoreStart - } catch (\ReflectionException $e) { - throw new ReflectionException( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - } - // @codeCoverageIgnoreEnd - } -} diff --git a/app/vendor/sebastian/code-unit/src/TraitMethodUnit.php b/app/vendor/sebastian/code-unit/src/TraitMethodUnit.php deleted file mode 100644 index a58f7249f..000000000 --- a/app/vendor/sebastian/code-unit/src/TraitMethodUnit.php +++ /dev/null @@ -1,24 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace SebastianBergmann\CodeUnit; - -/** - * @psalm-immutable - */ -final class TraitMethodUnit extends CodeUnit -{ - /** - * @psalm-assert-if-true TraitMethodUnit $this - */ - public function isTraitMethod(): bool - { - return true; - } -} diff --git a/app/vendor/sebastian/code-unit/src/TraitUnit.php b/app/vendor/sebastian/code-unit/src/TraitUnit.php deleted file mode 100644 index abddfc112..000000000 --- a/app/vendor/sebastian/code-unit/src/TraitUnit.php +++ /dev/null @@ -1,24 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace SebastianBergmann\CodeUnit; - -/** - * @psalm-immutable - */ -final class TraitUnit extends CodeUnit -{ - /** - * @psalm-assert-if-true TraitUnit $this - */ - public function isTrait(): bool - { - return true; - } -} diff --git a/app/vendor/sebastian/code-unit/src/exceptions/Exception.php b/app/vendor/sebastian/code-unit/src/exceptions/Exception.php deleted file mode 100644 index 74d0eeef8..000000000 --- a/app/vendor/sebastian/code-unit/src/exceptions/Exception.php +++ /dev/null @@ -1,16 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace SebastianBergmann\CodeUnit; - -use Throwable; - -interface Exception extends Throwable -{ -} diff --git a/app/vendor/sebastian/code-unit/src/exceptions/InvalidCodeUnitException.php b/app/vendor/sebastian/code-unit/src/exceptions/InvalidCodeUnitException.php deleted file mode 100644 index 60a3da82b..000000000 --- a/app/vendor/sebastian/code-unit/src/exceptions/InvalidCodeUnitException.php +++ /dev/null @@ -1,16 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace SebastianBergmann\CodeUnit; - -use RuntimeException; - -final class InvalidCodeUnitException extends RuntimeException implements Exception -{ -} diff --git a/app/vendor/sebastian/code-unit/src/exceptions/NoTraitException.php b/app/vendor/sebastian/code-unit/src/exceptions/NoTraitException.php deleted file mode 100644 index e9b9b9c7a..000000000 --- a/app/vendor/sebastian/code-unit/src/exceptions/NoTraitException.php +++ /dev/null @@ -1,16 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace SebastianBergmann\CodeUnit; - -use RuntimeException; - -final class NoTraitException extends RuntimeException implements Exception -{ -} diff --git a/app/vendor/sebastian/code-unit/src/exceptions/ReflectionException.php b/app/vendor/sebastian/code-unit/src/exceptions/ReflectionException.php deleted file mode 100644 index 232012783..000000000 --- a/app/vendor/sebastian/code-unit/src/exceptions/ReflectionException.php +++ /dev/null @@ -1,16 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace SebastianBergmann\CodeUnit; - -use RuntimeException; - -final class ReflectionException extends RuntimeException implements Exception -{ -} diff --git a/app/vendor/sebastian/comparator/ChangeLog.md b/app/vendor/sebastian/comparator/ChangeLog.md index 17557536c..2e26529af 100644 --- a/app/vendor/sebastian/comparator/ChangeLog.md +++ b/app/vendor/sebastian/comparator/ChangeLog.md @@ -2,6 +2,136 @@ All notable changes are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles. +## [7.1.3] - 2025-08-20 + +### Changed + +* [#130](https://github.com/sebastianbergmann/comparator/pull/130): Provide a diff when `ClosureComparator` fails + +## [7.1.2] - 2025-08-10 + +### Fixed + +* `SebastianBergmann\Comparator\Comparator` should not have been marked as private implementation detail of this library + +## [7.1.1] - 2025-08-10 + +### Changed + +* Do not use `SplObjectStorage` methods that will be deprecated in PHP 8.5 + +## [7.1.0] - 2025-06-17 + +### Added + +* [#127](https://github.com/sebastianbergmann/comparator/issues/127): Support for comparing `Closure` objects + +## [7.0.1] - 2025-03-07 + +### Fixed + +* [#122](https://github.com/sebastianbergmann/comparator/issues/122): `INF` is considered equal to `-INF` + +## [7.0.0] - 2025-02-07 + +### Removed + +* Removed support for PHP 8.2 + +## [6.3.2] - 2025-08-10 + +### Changed + +* Do not use `SplObjectStorage` methods that will be deprecated in PHP 8.5 + +## [6.3.1] - 2025-03-07 + +### Fixed + +* [#122](https://github.com/sebastianbergmann/comparator/issues/122): `INF` is considered equal to `-INF` + +## [6.3.0] - 2025-01-06 + +### Added + +* [#121](https://github.com/sebastianbergmann/comparator/pull/121): Support for `BcMath\Number` objects + +## [6.2.1] - 2024-10-31 + +### Fixed + +* [#119](https://github.com/sebastianbergmann/comparator/pull/119): `Uninitialized string offset -1` warning + +## [6.2.0] - 2024-10-30 + +### Changed + +* [#117](https://github.com/sebastianbergmann/comparator/pull/117): Remove common prefixes and suffixes from actual and expected single-line strings + +## [6.1.1] - 2024-10-18 + +### Fixed + +* Reverted [#113](https://github.com/sebastianbergmann/comparator/pull/113) as it broke backward compatibility + +## [6.1.0] - 2024-09-11 + +### Added + +* Specialized comparator for enumerations + +## [6.0.2] - 2024-08-12 + +### Fixed + +* [#112](https://github.com/sebastianbergmann/comparator/issues/112): Arrays with different keys and the same values are considered equal in canonicalize mode + +## [6.0.1] - 2024-07-03 + +### Changed + +* This project now uses PHPStan instead of Psalm for static analysis + +## [6.0.0] - 2024-02-02 + +### Removed + +* Removed support for PHP 8.1 + +## [5.0.3] - 2024-10-18 + +### Fixed + +* Reverted [#113](https://github.com/sebastianbergmann/comparator/pull/113) as it broke backward compatibility + +## [5.0.2] - 2024-08-12 + +### Fixed + +* [#112](https://github.com/sebastianbergmann/comparator/issues/112): Arrays with different keys and the same values are considered equal in canonicalize mode + +## [5.0.1] - 2023-08-14 + +### Fixed + +* `MockObjectComparator` only works on instances of `PHPUnit\Framework\MockObject\MockObject`, but not on instances of `PHPUnit\Framework\MockObject\Stub` +* `MockObjectComparator` only ignores the `$__phpunit_invocationMocker` property, but not other properties with names prefixed with `__phpunit_` + +## [5.0.0] - 2023-02-03 + +### Changed + +* Methods now have parameter and return type declarations +* `Comparator::$factory` is now private, use `Comparator::factory()` instead +* `ComparisonFailure`, `DOMNodeComparator`, `DateTimeComparator`, `ExceptionComparator`, `MockObjectComparator`, `NumericComparator`, `ResourceComparator`, `SplObjectStorageComparator`, and `TypeComparator` are now `final` +* `ScalarComparator` and `DOMNodeComparator` now use `mb_strtolower($string, 'UTF-8')` instead of `strtolower($string)` + +### Removed + +* Removed `$identical` parameter from `ComparisonFailure::__construct()` +* Removed `Comparator::$exporter` +* Removed support for PHP 7.3, PHP 7.4, and PHP 8.0 + ## [4.0.8] - 2022-09-14 ### Fixed @@ -122,6 +252,26 @@ All notable changes are documented in this file using the [Keep a CHANGELOG](htt * Added `SebastianBergmann\Comparator\Factory::reset()` to unregister all non-default comparators * Added support for `phpunit/phpunit-mock-objects` version `^5.0` +[7.1.3]: https://github.com/sebastianbergmann/comparator/compare/7.1.2...7.1.3 +[7.1.2]: https://github.com/sebastianbergmann/comparator/compare/7.1.1...7.1.2 +[7.1.1]: https://github.com/sebastianbergmann/comparator/compare/7.1.0...7.1.1 +[7.1.0]: https://github.com/sebastianbergmann/comparator/compare/7.0.1...7.1.0 +[7.0.1]: https://github.com/sebastianbergmann/comparator/compare/7.0.0...7.0.1 +[7.0.0]: https://github.com/sebastianbergmann/comparator/compare/6.3...7.0.0 +[6.3.2]: https://github.com/sebastianbergmann/comparator/compare/6.3.1...6.3.2 +[6.3.1]: https://github.com/sebastianbergmann/comparator/compare/6.3.0...6.3.1 +[6.3.0]: https://github.com/sebastianbergmann/comparator/compare/6.2.1...6.3.0 +[6.2.1]: https://github.com/sebastianbergmann/comparator/compare/6.2.0...6.2.1 +[6.2.0]: https://github.com/sebastianbergmann/comparator/compare/6.1.1...6.2.0 +[6.1.1]: https://github.com/sebastianbergmann/comparator/compare/6.1.0...6.1.1 +[6.1.0]: https://github.com/sebastianbergmann/comparator/compare/6.0.2...6.1.0 +[6.0.2]: https://github.com/sebastianbergmann/comparator/compare/6.0.1...6.0.2 +[6.0.1]: https://github.com/sebastianbergmann/comparator/compare/6.0.0...6.0.1 +[6.0.0]: https://github.com/sebastianbergmann/comparator/compare/5.0...6.0.0 +[5.0.3]: https://github.com/sebastianbergmann/comparator/compare/5.0.2...5.0.3 +[5.0.2]: https://github.com/sebastianbergmann/comparator/compare/5.0.1...5.0.2 +[5.0.1]: https://github.com/sebastianbergmann/comparator/compare/5.0.0...5.0.1 +[5.0.0]: https://github.com/sebastianbergmann/comparator/compare/4.0.8...5.0.0 [4.0.8]: https://github.com/sebastianbergmann/comparator/compare/4.0.7...4.0.8 [4.0.7]: https://github.com/sebastianbergmann/comparator/compare/4.0.6...4.0.7 [4.0.6]: https://github.com/sebastianbergmann/comparator/compare/4.0.5...4.0.6 diff --git a/app/vendor/sebastian/comparator/LICENSE b/app/vendor/sebastian/comparator/LICENSE index 6ad70cbaf..c5268a916 100644 --- a/app/vendor/sebastian/comparator/LICENSE +++ b/app/vendor/sebastian/comparator/LICENSE @@ -1,33 +1,29 @@ -Comparator +BSD 3-Clause License -Copyright (c) 2002-2020, Sebastian Bergmann . +Copyright (c) 2002-2025, Sebastian Bergmann All rights reserved. Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: +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. +1. 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. +2. 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 Sebastian Bergmann nor the names of his - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. +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/app/vendor/sebastian/comparator/README.md b/app/vendor/sebastian/comparator/README.md index f6002db65..50971ad34 100644 --- a/app/vendor/sebastian/comparator/README.md +++ b/app/vendor/sebastian/comparator/README.md @@ -1,7 +1,8 @@ -# sebastian/comparator - +[![Latest Stable Version](https://poser.pugx.org/sebastian/comparator/v)](https://packagist.org/packages/sebastian/comparator) [![CI Status](https://github.com/sebastianbergmann/comparator/workflows/CI/badge.svg)](https://github.com/sebastianbergmann/comparator/actions) -[![Type Coverage](https://shepherd.dev/github/sebastianbergmann/comparator/coverage.svg)](https://shepherd.dev/github/sebastianbergmann/comparator) +[![codecov](https://codecov.io/gh/sebastianbergmann/comparator/branch/main/graph/badge.svg)](https://codecov.io/gh/sebastianbergmann/comparator) + +# sebastian/comparator This component provides the functionality to compare PHP values for equality. diff --git a/app/vendor/sebastian/comparator/SECURITY.md b/app/vendor/sebastian/comparator/SECURITY.md new file mode 100644 index 000000000..d88ff0019 --- /dev/null +++ b/app/vendor/sebastian/comparator/SECURITY.md @@ -0,0 +1,30 @@ +# Security Policy + +If you believe you have found a security vulnerability in the library that is developed in this repository, please report it to us through coordinated disclosure. + +**Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.** + +Instead, please email `sebastian@phpunit.de`. + +Please include as much of the information listed below as you can to help us better understand and resolve the issue: + +* The type of issue +* Full paths of source file(s) related to the manifestation of the issue +* The location of the affected source code (tag/branch/commit or direct URL) +* Any special configuration required to reproduce the issue +* Step-by-step instructions to reproduce the issue +* Proof-of-concept or exploit code (if possible) +* Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +## Web Context + +The library that is developed in this repository was either extracted from [PHPUnit](https://github.com/sebastianbergmann/phpunit) or developed specifically as a dependency for PHPUnit. + +The library is developed with a focus on development environments and the command-line. No specific testing or hardening with regard to using the library in an HTTP or web context or with untrusted input data is performed. The library might also contain functionality that intentionally exposes internal application data for debugging purposes. + +If the library is used in a web application, the application developer is responsible for filtering inputs or escaping outputs as necessary and for verifying that the used functionality is safe for use within the intended context. + +Vulnerabilities specific to the use outside a development context will be fixed as applicable, provided that the fix does not have an averse effect on the primary use case for development purposes. + diff --git a/app/vendor/sebastian/comparator/composer.json b/app/vendor/sebastian/comparator/composer.json index b758e03c9..c673f9bc5 100644 --- a/app/vendor/sebastian/comparator/composer.json +++ b/app/vendor/sebastian/comparator/composer.json @@ -22,18 +22,27 @@ "email": "bschussek@2bepublished.at" } ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "security": "https://github.com/sebastianbergmann/comparator/security/policy" + }, "prefer-stable": true, "require": { - "php": ">=7.3", - "sebastian/diff": "^4.0", - "sebastian/exporter": "^4.0" + "php": ">=8.3", + "sebastian/diff": "^7.0", + "sebastian/exporter": "^7.0", + "ext-dom": "*", + "ext-mbstring": "*" + }, + "suggest": { + "ext-bcmath": "For comparing BcMath\\Number objects" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^12.2" }, "config": { "platform": { - "php": "7.3.0" + "php": "8.3.0" }, "optimize-autoloader": true, "sort-packages": true @@ -50,8 +59,7 @@ }, "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "7.1-dev" } } } - diff --git a/app/vendor/sebastian/comparator/src/ArrayComparator.php b/app/vendor/sebastian/comparator/src/ArrayComparator.php index 5d9fbce6e..f1e079097 100644 --- a/app/vendor/sebastian/comparator/src/ArrayComparator.php +++ b/app/vendor/sebastian/comparator/src/ArrayComparator.php @@ -10,48 +10,40 @@ namespace SebastianBergmann\Comparator; use function array_key_exists; +use function assert; use function is_array; use function sort; use function sprintf; use function str_replace; use function trim; +use SebastianBergmann\Exporter\Exporter; /** - * Compares arrays for equality. + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for sebastian/comparator * - * Arrays are equal if they contain the same key-value pairs. - * The order of the keys does not matter. - * The types of key-value pairs do not matter. + * @internal This class is not covered by the backward compatibility promise for sebastian/comparator */ class ArrayComparator extends Comparator { - /** - * Returns whether the comparator can compare two values. - * - * @param mixed $expected The first value to compare - * @param mixed $actual The second value to compare - * - * @return bool - */ - public function accepts($expected, $actual) + public function accepts(mixed $expected, mixed $actual): bool { return is_array($expected) && is_array($actual); } /** - * Asserts that two arrays are equal. + * Arrays are equal if they contain the same key-value pairs. + * The order of the keys does not matter. + * The types of key-value pairs do not matter. * - * @param mixed $expected First value to compare - * @param mixed $actual Second value to compare - * @param float $delta Allowed numerical distance between two values to consider them equal - * @param bool $canonicalize Arrays are sorted before comparison when set to true - * @param bool $ignoreCase Case is ignored when set to true - * @param array $processed List of already processed elements (used to prevent infinite recursion) + * @param array $processed * * @throws ComparisonFailure */ - public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false, array &$processed = [])/*: void*/ + public function assertEquals(mixed $expected, mixed $actual, float $delta = 0.0, bool $canonicalize = false, bool $ignoreCase = false, array &$processed = []): void { + assert(is_array($expected)); + assert(is_array($actual)); + if ($canonicalize) { sort($expected); sort($actual); @@ -61,6 +53,7 @@ public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = f $actualAsString = "Array (\n"; $expectedAsString = "Array (\n"; $equal = true; + $exporter = new Exporter; foreach ($expected as $key => $value) { unset($remaining[$key]); @@ -68,8 +61,8 @@ public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = f if (!array_key_exists($key, $actual)) { $expectedAsString .= sprintf( " %s => %s\n", - $this->exporter->export($key), - $this->exporter->shortenedExport($value) + $exporter->export($key), + $exporter->shortenedExport($value), ); $equal = false; @@ -78,31 +71,33 @@ public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = f } try { - $comparator = $this->factory->getComparatorFor($value, $actual[$key]); + $comparator = $this->factory()->getComparatorFor($value, $actual[$key]); + + /** @phpstan-ignore arguments.count */ $comparator->assertEquals($value, $actual[$key], $delta, $canonicalize, $ignoreCase, $processed); $expectedAsString .= sprintf( " %s => %s\n", - $this->exporter->export($key), - $this->exporter->shortenedExport($value) + $exporter->export($key), + $exporter->shortenedExport($value), ); $actualAsString .= sprintf( " %s => %s\n", - $this->exporter->export($key), - $this->exporter->shortenedExport($actual[$key]) + $exporter->export($key), + $exporter->shortenedExport($actual[$key]), ); } catch (ComparisonFailure $e) { $expectedAsString .= sprintf( " %s => %s\n", - $this->exporter->export($key), - $e->getExpectedAsString() ? $this->indent($e->getExpectedAsString()) : $this->exporter->shortenedExport($e->getExpected()) + $exporter->export($key), + $e->getExpectedAsString() !== '' ? $this->indent($e->getExpectedAsString()) : $exporter->shortenedExport($e->getExpected()), ); $actualAsString .= sprintf( " %s => %s\n", - $this->exporter->export($key), - $e->getActualAsString() ? $this->indent($e->getActualAsString()) : $this->exporter->shortenedExport($e->getActual()) + $exporter->export($key), + $e->getActualAsString() !== '' ? $this->indent($e->getActualAsString()) : $exporter->shortenedExport($e->getActual()), ); $equal = false; @@ -112,8 +107,8 @@ public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = f foreach ($remaining as $key => $value) { $actualAsString .= sprintf( " %s => %s\n", - $this->exporter->export($key), - $this->exporter->shortenedExport($value) + $exporter->export($key), + $exporter->shortenedExport($value), ); $equal = false; @@ -128,13 +123,12 @@ public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = f $actual, $expectedAsString, $actualAsString, - false, - 'Failed asserting that two arrays are equal.' + 'Failed asserting that two arrays are equal.', ); } } - protected function indent($lines) + private function indent(string $lines): string { return trim(str_replace("\n", "\n ", $lines)); } diff --git a/app/vendor/sebastian/comparator/src/ClosureComparator.php b/app/vendor/sebastian/comparator/src/ClosureComparator.php new file mode 100644 index 000000000..f02bb1de6 --- /dev/null +++ b/app/vendor/sebastian/comparator/src/ClosureComparator.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 SebastianBergmann\Comparator; + +use function assert; +use function spl_object_id; +use function sprintf; +use Closure; +use ReflectionFunction; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for sebastian/comparator + * + * @internal This class is not covered by the backward compatibility promise for sebastian/comparator + */ +final class ClosureComparator extends Comparator +{ + public function accepts(mixed $expected, mixed $actual): bool + { + return $expected instanceof Closure && $actual instanceof Closure; + } + + public function assertEquals(mixed $expected, mixed $actual, float $delta = 0.0, bool $canonicalize = false, bool $ignoreCase = false): void + { + assert($expected instanceof Closure); + assert($actual instanceof Closure); + + /** @phpstan-ignore equal.notAllowed */ + if ($expected == $actual) { + return; + } + + $expectedReflector = new ReflectionFunction($expected); + $actualReflector = new ReflectionFunction($actual); + + throw new ComparisonFailure( + $expected, + $actual, + 'Closure Object #' . spl_object_id($expected) . ' ()', + 'Closure Object #' . spl_object_id($actual) . ' ()', + sprintf( + 'Failed asserting that closure declared at %s:%d is equal to closure declared at %s:%d.', + $expectedReflector->getFileName(), + $expectedReflector->getStartLine(), + $actualReflector->getFileName(), + $actualReflector->getStartLine(), + ), + ); + } +} diff --git a/app/vendor/sebastian/comparator/src/Comparator.php b/app/vendor/sebastian/comparator/src/Comparator.php index e1906c167..5caa817fb 100644 --- a/app/vendor/sebastian/comparator/src/Comparator.php +++ b/app/vendor/sebastian/comparator/src/Comparator.php @@ -9,53 +9,27 @@ */ namespace SebastianBergmann\Comparator; -use SebastianBergmann\Exporter\Exporter; - /** - * Abstract base class for comparators which compare values for equality. + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for sebastian/comparator */ abstract class Comparator { - /** - * @var Factory - */ - protected $factory; - - /** - * @var Exporter - */ - protected $exporter; - - public function __construct() - { - $this->exporter = new Exporter; - } + private Factory $factory; - public function setFactory(Factory $factory)/*: void*/ + public function setFactory(Factory $factory): void { $this->factory = $factory; } - /** - * Returns whether the comparator can compare two values. - * - * @param mixed $expected The first value to compare - * @param mixed $actual The second value to compare - * - * @return bool - */ - abstract public function accepts($expected, $actual); + abstract public function accepts(mixed $expected, mixed $actual): bool; /** - * Asserts that two values are equal. - * - * @param mixed $expected First value to compare - * @param mixed $actual Second value to compare - * @param float $delta Allowed numerical distance between two values to consider them equal - * @param bool $canonicalize Arrays are sorted before comparison when set to true - * @param bool $ignoreCase Case is ignored when set to true - * * @throws ComparisonFailure */ - abstract public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false); + abstract public function assertEquals(mixed $expected, mixed $actual, float $delta = 0.0, bool $canonicalize = false, bool $ignoreCase = false): void; + + protected function factory(): Factory + { + return $this->factory; + } } diff --git a/app/vendor/sebastian/comparator/src/ComparisonFailure.php b/app/vendor/sebastian/comparator/src/ComparisonFailure.php index 857314daa..b8435ebc9 100644 --- a/app/vendor/sebastian/comparator/src/ComparisonFailure.php +++ b/app/vendor/sebastian/comparator/src/ComparisonFailure.php @@ -14,103 +14,48 @@ use SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder; /** - * Thrown when an assertion for string equality failed. + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for sebastian/comparator */ -class ComparisonFailure extends RuntimeException +final class ComparisonFailure extends RuntimeException { - /** - * Expected value of the retrieval which does not match $actual. - * - * @var mixed - */ - protected $expected; + private mixed $expected; + private mixed $actual; + private string $expectedAsString; + private string $actualAsString; - /** - * Actually retrieved value which does not match $expected. - * - * @var mixed - */ - protected $actual; - - /** - * The string representation of the expected value. - * - * @var string - */ - protected $expectedAsString; - - /** - * The string representation of the actual value. - * - * @var string - */ - protected $actualAsString; - - /** - * @var bool - */ - protected $identical; - - /** - * Optional message which is placed in front of the first line - * returned by toString(). - * - * @var string - */ - protected $message; - - /** - * Initialises with the expected value and the actual value. - * - * @param mixed $expected expected value retrieved - * @param mixed $actual actual value retrieved - * @param string $expectedAsString - * @param string $actualAsString - * @param bool $identical - * @param string $message a string which is prefixed on all returned lines - * in the difference output - */ - public function __construct($expected, $actual, $expectedAsString, $actualAsString, $identical = false, $message = '') + public function __construct(mixed $expected, mixed $actual, string $expectedAsString, string $actualAsString, string $message = '') { + parent::__construct($message); + $this->expected = $expected; $this->actual = $actual; $this->expectedAsString = $expectedAsString; $this->actualAsString = $actualAsString; - $this->message = $message; } - public function getActual() + public function getActual(): mixed { return $this->actual; } - public function getExpected() + public function getExpected(): mixed { return $this->expected; } - /** - * @return string - */ - public function getActualAsString() + public function getActualAsString(): string { return $this->actualAsString; } - /** - * @return string - */ - public function getExpectedAsString() + public function getExpectedAsString(): string { return $this->expectedAsString; } - /** - * @return string - */ - public function getDiff() + public function getDiff(): string { - if (!$this->actualAsString && !$this->expectedAsString) { + if ($this->actualAsString === '' && $this->expectedAsString === '') { return ''; } @@ -119,11 +64,8 @@ public function getDiff() return $differ->diff($this->expectedAsString, $this->actualAsString); } - /** - * @return string - */ - public function toString() + public function toString(): string { - return $this->message . $this->getDiff(); + return $this->getMessage() . $this->getDiff(); } } diff --git a/app/vendor/sebastian/comparator/src/DOMNodeComparator.php b/app/vendor/sebastian/comparator/src/DOMNodeComparator.php index 5bf854eae..2964e94db 100644 --- a/app/vendor/sebastian/comparator/src/DOMNodeComparator.php +++ b/app/vendor/sebastian/comparator/src/DOMNodeComparator.php @@ -9,46 +9,37 @@ */ namespace SebastianBergmann\Comparator; +use function assert; +use function mb_strtolower; use function sprintf; -use function strtolower; use DOMDocument; use DOMNode; use ValueError; /** - * Compares DOMNode instances for equality. + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for sebastian/comparator + * + * @internal This class is not covered by the backward compatibility promise for sebastian/comparator */ -class DOMNodeComparator extends ObjectComparator +final class DOMNodeComparator extends ObjectComparator { - /** - * Returns whether the comparator can compare two values. - * - * @param mixed $expected The first value to compare - * @param mixed $actual The second value to compare - * - * @return bool - */ - public function accepts($expected, $actual) + public function accepts(mixed $expected, mixed $actual): bool { return $expected instanceof DOMNode && $actual instanceof DOMNode; } /** - * Asserts that two values are equal. - * - * @param mixed $expected First value to compare - * @param mixed $actual Second value to compare - * @param float $delta Allowed numerical distance between two values to consider them equal - * @param bool $canonicalize Arrays are sorted before comparison when set to true - * @param bool $ignoreCase Case is ignored when set to true - * @param array $processed List of already processed elements (used to prevent infinite recursion) + * @param array $processed * * @throws ComparisonFailure */ - public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false, array &$processed = [])/*: void*/ + public function assertEquals(mixed $expected, mixed $actual, float $delta = 0.0, bool $canonicalize = false, bool $ignoreCase = false, array &$processed = []): void { - $expectedAsString = $this->nodeToText($expected, true, $ignoreCase); - $actualAsString = $this->nodeToText($actual, true, $ignoreCase); + assert($expected instanceof DOMNode); + assert($actual instanceof DOMNode); + + $expectedAsString = $this->nodeToText($expected, $ignoreCase); + $actualAsString = $this->nodeToText($actual, $ignoreCase); if ($expectedAsString !== $actualAsString) { $type = $expected instanceof DOMDocument ? 'documents' : 'nodes'; @@ -58,36 +49,43 @@ public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = f $actual, $expectedAsString, $actualAsString, - false, - sprintf("Failed asserting that two DOM %s are equal.\n", $type) + sprintf("Failed asserting that two DOM %s are equal.\n", $type), ); } } /** - * Returns the normalized, whitespace-cleaned, and indented textual - * representation of a DOMNode. + * Canonicalizes nodes, removes empty text nodes and merges adjacent text nodes, + * and optionally ignores case. + * + * @see https://github.com/sebastianbergmann/phpunit/pull/1236#issuecomment-41765023 */ - private function nodeToText(DOMNode $node, bool $canonicalize, bool $ignoreCase): string + private function nodeToText(DOMNode $node, bool $ignoreCase): string { - if ($canonicalize) { - $document = new DOMDocument; + $document = new DOMDocument; - try { - @$document->loadXML($node->C14N()); - } catch (ValueError $e) { - } + try { + $c14n = $node->C14N(); - $node = $document; - } + assert($c14n !== false && $c14n !== ''); - $document = $node instanceof DOMDocument ? $node : $node->ownerDocument; + @$document->loadXML($c14n); + // @codeCoverageIgnoreStart + } catch (ValueError) { + // @codeCoverageIgnoreEnd + } $document->formatOutput = true; $document->normalizeDocument(); - $text = $node instanceof DOMDocument ? $node->saveXML() : $document->saveXML($node); + $text = $document->saveXML(); + + assert($text !== false); + + if ($ignoreCase) { + return mb_strtolower($text, 'UTF-8'); + } - return $ignoreCase ? strtolower($text) : $text; + return $text; } } diff --git a/app/vendor/sebastian/comparator/src/DateTimeComparator.php b/app/vendor/sebastian/comparator/src/DateTimeComparator.php index 0a303b623..3b4da4c4d 100644 --- a/app/vendor/sebastian/comparator/src/DateTimeComparator.php +++ b/app/vendor/sebastian/comparator/src/DateTimeComparator.php @@ -10,50 +10,37 @@ namespace SebastianBergmann\Comparator; use function abs; +use function assert; use function floor; use function sprintf; use DateInterval; use DateTime; -use DateTimeInterface; +use DateTimeImmutable; use DateTimeZone; -use Exception; /** - * Compares DateTimeInterface instances for equality. + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for sebastian/comparator + * + * @internal This class is not covered by the backward compatibility promise for sebastian/comparator */ -class DateTimeComparator extends ObjectComparator +final class DateTimeComparator extends ObjectComparator { - /** - * Returns whether the comparator can compare two values. - * - * @param mixed $expected The first value to compare - * @param mixed $actual The second value to compare - * - * @return bool - */ - public function accepts($expected, $actual) + public function accepts(mixed $expected, mixed $actual): bool { - return ($expected instanceof DateTime || $expected instanceof DateTimeInterface) && - ($actual instanceof DateTime || $actual instanceof DateTimeInterface); + return ($expected instanceof DateTime || $expected instanceof DateTimeImmutable) && + ($actual instanceof DateTime || $actual instanceof DateTimeImmutable); } /** - * Asserts that two values are equal. - * - * @param mixed $expected First value to compare - * @param mixed $actual Second value to compare - * @param float $delta Allowed numerical distance between two values to consider them equal - * @param bool $canonicalize Arrays are sorted before comparison when set to true - * @param bool $ignoreCase Case is ignored when set to true - * @param array $processed List of already processed elements (used to prevent infinite recursion) + * @param array $processed * - * @throws Exception * @throws ComparisonFailure */ - public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false, array &$processed = [])/*: void*/ + public function assertEquals(mixed $expected, mixed $actual, float $delta = 0.0, bool $canonicalize = false, bool $ignoreCase = false, array &$processed = []): void { - /** @var DateTimeInterface $expected */ - /** @var DateTimeInterface $actual */ + assert($expected instanceof DateTime || $expected instanceof DateTimeImmutable); + assert($actual instanceof DateTime || $actual instanceof DateTimeImmutable); + $absDelta = abs($delta); $delta = new DateInterval(sprintf('PT%dS', $absDelta)); $delta->f = $absDelta - floor($absDelta); @@ -73,23 +60,10 @@ public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = f throw new ComparisonFailure( $expected, $actual, - $this->dateTimeToString($expected), - $this->dateTimeToString($actual), - false, - 'Failed asserting that two DateTime objects are equal.' + $expected->format('Y-m-d\TH:i:s.uO'), + $actual->format('Y-m-d\TH:i:s.uO'), + 'Failed asserting that two DateTime objects are equal.', ); } } - - /** - * Returns an ISO 8601 formatted string representation of a datetime or - * 'Invalid DateTimeInterface object' if the provided DateTimeInterface was not properly - * initialized. - */ - private function dateTimeToString(DateTimeInterface $datetime): string - { - $string = $datetime->format('Y-m-d\TH:i:s.uO'); - - return $string ?: 'Invalid DateTimeInterface object'; - } } diff --git a/app/vendor/sebastian/comparator/src/DoubleComparator.php b/app/vendor/sebastian/comparator/src/DoubleComparator.php deleted file mode 100644 index 423522193..000000000 --- a/app/vendor/sebastian/comparator/src/DoubleComparator.php +++ /dev/null @@ -1,61 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace SebastianBergmann\Comparator; - -use function is_float; -use function is_numeric; - -/** - * Compares doubles for equality. - * - * @deprecated since v3.0.5 and v4.0.8 - */ -class DoubleComparator extends NumericComparator -{ - /** - * Smallest value available in PHP. - * - * @var float - */ - public const EPSILON = 0.0000000001; - - /** - * Returns whether the comparator can compare two values. - * - * @param mixed $expected The first value to compare - * @param mixed $actual The second value to compare - * - * @return bool - */ - public function accepts($expected, $actual) - { - return (is_float($expected) || is_float($actual)) && is_numeric($expected) && is_numeric($actual); - } - - /** - * Asserts that two values are equal. - * - * @param mixed $expected First value to compare - * @param mixed $actual Second value to compare - * @param float $delta Allowed numerical distance between two values to consider them equal - * @param bool $canonicalize Arrays are sorted before comparison when set to true - * @param bool $ignoreCase Case is ignored when set to true - * - * @throws ComparisonFailure - */ - public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false)/*: void*/ - { - if ($delta == 0) { - $delta = self::EPSILON; - } - - parent::assertEquals($expected, $actual, $delta, $canonicalize, $ignoreCase); - } -} diff --git a/app/vendor/sebastian/comparator/src/EnumerationComparator.php b/app/vendor/sebastian/comparator/src/EnumerationComparator.php new file mode 100644 index 000000000..7341edf6c --- /dev/null +++ b/app/vendor/sebastian/comparator/src/EnumerationComparator.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 SebastianBergmann\Comparator; + +use function assert; +use function sprintf; +use UnitEnum; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for sebastian/comparator + * + * @internal This class is not covered by the backward compatibility promise for sebastian/comparator + */ +final class EnumerationComparator extends Comparator +{ + public function accepts(mixed $expected, mixed $actual): bool + { + return $expected instanceof UnitEnum && + $actual instanceof UnitEnum && + $expected::class === $actual::class; + } + + /** + * @throws ComparisonFailure + */ + public function assertEquals(mixed $expected, mixed $actual, float $delta = 0.0, bool $canonicalize = false, bool $ignoreCase = false): void + { + assert($expected instanceof UnitEnum); + assert($actual instanceof UnitEnum); + + if ($expected === $actual) { + return; + } + + throw new ComparisonFailure( + $expected, + $actual, + '', + '', + sprintf( + 'Failed asserting that two values of enumeration %s are equal, %s does not match expected %s.', + $expected::class, + $actual->name, + $expected->name, + ), + ); + } +} diff --git a/app/vendor/sebastian/comparator/src/ExceptionComparator.php b/app/vendor/sebastian/comparator/src/ExceptionComparator.php index 1fc0174ef..6d1196183 100644 --- a/app/vendor/sebastian/comparator/src/ExceptionComparator.php +++ b/app/vendor/sebastian/comparator/src/ExceptionComparator.php @@ -9,36 +9,28 @@ */ namespace SebastianBergmann\Comparator; +use function assert; use Exception; /** - * Compares Exception instances for equality. + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for sebastian/comparator + * + * @internal This class is not covered by the backward compatibility promise for sebastian/comparator */ -class ExceptionComparator extends ObjectComparator +final class ExceptionComparator extends ObjectComparator { - /** - * Returns whether the comparator can compare two values. - * - * @param mixed $expected The first value to compare - * @param mixed $actual The second value to compare - * - * @return bool - */ - public function accepts($expected, $actual) + public function accepts(mixed $expected, mixed $actual): bool { return $expected instanceof Exception && $actual instanceof Exception; } /** - * Converts an object to an array containing all of its private, protected - * and public properties. - * - * @param object $object - * - * @return array + * @return array */ - protected function toArray($object) + protected function toArray(object $object): array { + assert($object instanceof Exception); + $array = parent::toArray($object); unset( @@ -46,7 +38,7 @@ protected function toArray($object) $array['line'], $array['trace'], $array['string'], - $array['xdebug_message'] + $array['xdebug_message'], ); return $array; diff --git a/app/vendor/sebastian/comparator/src/Factory.php b/app/vendor/sebastian/comparator/src/Factory.php index 6a8b5b442..7f330d627 100644 --- a/app/vendor/sebastian/comparator/src/Factory.php +++ b/app/vendor/sebastian/comparator/src/Factory.php @@ -9,32 +9,29 @@ */ namespace SebastianBergmann\Comparator; +use const PHP_VERSION; use function array_unshift; +use function extension_loaded; +use function version_compare; /** - * Factory for comparators which compare values for equality. + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for sebastian/comparator */ -class Factory +final class Factory { - /** - * @var Factory - */ - private static $instance; + private static ?Factory $instance = null; /** - * @var Comparator[] + * @var array */ - private $customComparators = []; + private array $customComparators = []; /** - * @var Comparator[] + * @var list */ - private $defaultComparators = []; + private array $defaultComparators = []; - /** - * @return Factory - */ - public static function getInstance() + public static function getInstance(): self { if (self::$instance === null) { self::$instance = new self; // @codeCoverageIgnore @@ -43,23 +40,12 @@ public static function getInstance() return self::$instance; } - /** - * Constructs a new factory. - */ public function __construct() { $this->registerDefaultComparators(); } - /** - * Returns the correct comparator for comparing two values. - * - * @param mixed $expected The first value to compare - * @param mixed $actual The second value to compare - * - * @return Comparator - */ - public function getComparatorFor($expected, $actual) + public function getComparatorFor(mixed $expected, mixed $actual): Comparator { foreach ($this->customComparators as $comparator) { if ($comparator->accepts($expected, $actual)) { @@ -73,7 +59,9 @@ public function getComparatorFor($expected, $actual) } } + // @codeCoverageIgnoreStart throw new RuntimeException('No suitable Comparator implementation found'); + // @codeCoverageIgnoreEnd } /** @@ -83,10 +71,8 @@ public function getComparatorFor($expected, $actual) * returns TRUE for the compared values. It has higher priority than the * existing comparators, meaning that its accept() method will be invoked * before those of the other comparators. - * - * @param Comparator $comparator The comparator to be registered */ - public function register(Comparator $comparator)/*: void*/ + public function register(Comparator $comparator): void { array_unshift($this->customComparators, $comparator); @@ -97,10 +83,8 @@ public function register(Comparator $comparator)/*: void*/ * Unregisters a comparator. * * This comparator will no longer be considered by getComparatorFor(). - * - * @param Comparator $comparator The comparator to be unregistered */ - public function unregister(Comparator $comparator)/*: void*/ + public function unregister(Comparator $comparator): void { foreach ($this->customComparators as $key => $_comparator) { if ($comparator === $_comparator) { @@ -109,21 +93,25 @@ public function unregister(Comparator $comparator)/*: void*/ } } - /** - * Unregisters all non-default comparators. - */ - public function reset()/*: void*/ + public function reset(): void { $this->customComparators = []; } private function registerDefaultComparators(): void { + $this->registerDefaultComparator(new ClosureComparator); $this->registerDefaultComparator(new MockObjectComparator); $this->registerDefaultComparator(new DateTimeComparator); $this->registerDefaultComparator(new DOMNodeComparator); $this->registerDefaultComparator(new SplObjectStorageComparator); $this->registerDefaultComparator(new ExceptionComparator); + $this->registerDefaultComparator(new EnumerationComparator); + + if (extension_loaded('bcmath') && version_compare(PHP_VERSION, '8.4.0', '>=')) { + $this->registerDefaultComparator(new NumberComparator); + } + $this->registerDefaultComparator(new ObjectComparator); $this->registerDefaultComparator(new ResourceComparator); $this->registerDefaultComparator(new ArrayComparator); diff --git a/app/vendor/sebastian/comparator/src/MockObjectComparator.php b/app/vendor/sebastian/comparator/src/MockObjectComparator.php index cb6703161..f42ef4d9a 100644 --- a/app/vendor/sebastian/comparator/src/MockObjectComparator.php +++ b/app/vendor/sebastian/comparator/src/MockObjectComparator.php @@ -9,39 +9,39 @@ */ namespace SebastianBergmann\Comparator; -use PHPUnit\Framework\MockObject\MockObject; +use function array_keys; +use function assert; +use function str_starts_with; +use PHPUnit\Framework\MockObject\Stub; /** - * Compares PHPUnit\Framework\MockObject\MockObject instances for equality. + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for sebastian/comparator + * + * @internal This class is not covered by the backward compatibility promise for sebastian/comparator */ -class MockObjectComparator extends ObjectComparator +final class MockObjectComparator extends ObjectComparator { - /** - * Returns whether the comparator can compare two values. - * - * @param mixed $expected The first value to compare - * @param mixed $actual The second value to compare - * - * @return bool - */ - public function accepts($expected, $actual) + public function accepts(mixed $expected, mixed $actual): bool { - return $expected instanceof MockObject && $actual instanceof MockObject; + return $expected instanceof Stub && $actual instanceof Stub; } /** - * Converts an object to an array containing all of its private, protected - * and public properties. - * - * @param object $object - * - * @return array + * @return array */ - protected function toArray($object) + protected function toArray(object $object): array { + assert($object instanceof Stub); + $array = parent::toArray($object); - unset($array['__phpunit_invocationMocker']); + foreach (array_keys($array) as $key) { + if (!str_starts_with($key, '__phpunit_')) { + continue; + } + + unset($array[$key]); + } return $array; } diff --git a/app/vendor/sebastian/comparator/src/NumberComparator.php b/app/vendor/sebastian/comparator/src/NumberComparator.php new file mode 100644 index 000000000..8c606a757 --- /dev/null +++ b/app/vendor/sebastian/comparator/src/NumberComparator.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 SebastianBergmann\Comparator; + +use function assert; +use function is_int; +use function is_numeric; +use function is_string; +use function max; +use function number_format; +use BcMath\Number; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for sebastian/comparator + * + * @internal This class is not covered by the backward compatibility promise for sebastian/comparator + */ +final class NumberComparator extends ObjectComparator +{ + public function accepts(mixed $expected, mixed $actual): bool + { + return ($expected instanceof Number || $actual instanceof Number) && + ($expected instanceof Number || is_int($expected) || is_string($expected) && is_numeric($expected)) && + ($actual instanceof Number || is_int($actual) || is_string($actual) && is_numeric($actual)); + } + + /** + * @param array $processed + * + * @throws ComparisonFailure + */ + public function assertEquals(mixed $expected, mixed $actual, float $delta = 0.0, bool $canonicalize = false, bool $ignoreCase = false, array &$processed = []): void + { + if (!$expected instanceof Number) { + assert(is_string($expected) || is_int($expected)); + + $expected = new Number($expected); + } + + if (!$actual instanceof Number) { + assert(is_string($actual) || is_int($actual)); + + $actual = new Number($actual); + } + + $deltaNumber = new Number(number_format($delta, max($expected->scale, $actual->scale))); + + if ($actual < $expected - $deltaNumber || $actual > $expected + $deltaNumber) { + throw new ComparisonFailure( + $expected, + $actual, + (string) $expected, + (string) $actual, + 'Failed asserting that two Number objects are equal.', + ); + } + } +} diff --git a/app/vendor/sebastian/comparator/src/NumericComparator.php b/app/vendor/sebastian/comparator/src/NumericComparator.php index 841881c99..77a04a3f7 100644 --- a/app/vendor/sebastian/comparator/src/NumericComparator.php +++ b/app/vendor/sebastian/comparator/src/NumericComparator.php @@ -10,27 +10,23 @@ namespace SebastianBergmann\Comparator; use function abs; +use function assert; use function is_float; use function is_infinite; use function is_nan; use function is_numeric; use function is_string; use function sprintf; +use SebastianBergmann\Exporter\Exporter; /** - * Compares numerical values for equality. + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for sebastian/comparator + * + * @internal This class is not covered by the backward compatibility promise for sebastian/comparator */ -class NumericComparator extends ScalarComparator +final class NumericComparator extends ScalarComparator { - /** - * Returns whether the comparator can compare two values. - * - * @param mixed $expected The first value to compare - * @param mixed $actual The second value to compare - * - * @return bool - */ - public function accepts($expected, $actual) + public function accepts(mixed $expected, mixed $actual): bool { // all numerical values, but not if both of them are strings return is_numeric($expected) && is_numeric($actual) && @@ -38,46 +34,48 @@ public function accepts($expected, $actual) } /** - * Asserts that two values are equal. - * - * @param mixed $expected First value to compare - * @param mixed $actual Second value to compare - * @param float $delta Allowed numerical distance between two values to consider them equal - * @param bool $canonicalize Arrays are sorted before comparison when set to true - * @param bool $ignoreCase Case is ignored when set to true - * * @throws ComparisonFailure */ - public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false)/*: void*/ + public function assertEquals(mixed $expected, mixed $actual, float $delta = 0.0, bool $canonicalize = false, bool $ignoreCase = false): void { - if ($this->isInfinite($actual) && $this->isInfinite($expected)) { - return; + assert(is_numeric($expected)); + assert(is_numeric($actual)); + + if ($this->isInfinite($expected) && $this->isInfinite($actual)) { + if ($expected < 0 && $actual < 0) { + return; + } + + if ($expected > 0 && $actual > 0) { + return; + } } if (($this->isInfinite($actual) xor $this->isInfinite($expected)) || ($this->isNan($actual) || $this->isNan($expected)) || abs($actual - $expected) > $delta) { + $exporter = new Exporter; + throw new ComparisonFailure( $expected, $actual, '', '', - false, sprintf( 'Failed asserting that %s matches expected %s.', - $this->exporter->export($actual), - $this->exporter->export($expected) - ) + $exporter->export($actual), + $exporter->export($expected), + ), ); } } - private function isInfinite($value): bool + private function isInfinite(mixed $value): bool { return is_float($value) && is_infinite($value); } - private function isNan($value): bool + private function isNan(mixed $value): bool { return is_float($value) && is_nan($value); } diff --git a/app/vendor/sebastian/comparator/src/ObjectComparator.php b/app/vendor/sebastian/comparator/src/ObjectComparator.php index 9380ba150..da9a76cac 100644 --- a/app/vendor/sebastian/comparator/src/ObjectComparator.php +++ b/app/vendor/sebastian/comparator/src/ObjectComparator.php @@ -9,56 +9,48 @@ */ namespace SebastianBergmann\Comparator; -use function get_class; +use function assert; use function in_array; use function is_object; use function sprintf; use function substr_replace; +use SebastianBergmann\Exporter\Exporter; /** - * Compares objects for equality. + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for sebastian/comparator + * + * @internal This class is not covered by the backward compatibility promise for sebastian/comparator */ class ObjectComparator extends ArrayComparator { - /** - * Returns whether the comparator can compare two values. - * - * @param mixed $expected The first value to compare - * @param mixed $actual The second value to compare - * - * @return bool - */ - public function accepts($expected, $actual) + public function accepts(mixed $expected, mixed $actual): bool { return is_object($expected) && is_object($actual); } /** - * Asserts that two values are equal. - * - * @param mixed $expected First value to compare - * @param mixed $actual Second value to compare - * @param float $delta Allowed numerical distance between two values to consider them equal - * @param bool $canonicalize Arrays are sorted before comparison when set to true - * @param bool $ignoreCase Case is ignored when set to true - * @param array $processed List of already processed elements (used to prevent infinite recursion) + * @param array $processed * * @throws ComparisonFailure */ - public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false, array &$processed = [])/*: void*/ + public function assertEquals(mixed $expected, mixed $actual, float $delta = 0.0, bool $canonicalize = false, bool $ignoreCase = false, array &$processed = []): void { - if (get_class($actual) !== get_class($expected)) { + assert(is_object($expected)); + assert(is_object($actual)); + + if ($actual::class !== $expected::class) { + $exporter = new Exporter; + throw new ComparisonFailure( $expected, $actual, - $this->exporter->export($expected), - $this->exporter->export($actual), - false, + $exporter->export($expected), + $exporter->export($actual), sprintf( '%s is not instance of expected class "%s".', - $this->exporter->export($actual), - get_class($expected) - ) + $exporter->export($actual), + $expected::class, + ), ); } @@ -81,32 +73,26 @@ public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = f $delta, $canonicalize, $ignoreCase, - $processed + $processed, ); } catch (ComparisonFailure $e) { throw new ComparisonFailure( $expected, $actual, // replace "Array" with "MyClass object" - substr_replace($e->getExpectedAsString(), get_class($expected) . ' Object', 0, 5), - substr_replace($e->getActualAsString(), get_class($actual) . ' Object', 0, 5), - false, - 'Failed asserting that two objects are equal.' + substr_replace($e->getExpectedAsString(), $expected::class . ' Object', 0, 5), + substr_replace($e->getActualAsString(), $actual::class . ' Object', 0, 5), + 'Failed asserting that two objects are equal.', ); } } } /** - * Converts an object to an array containing all of its private, protected - * and public properties. - * - * @param object $object - * - * @return array + * @return array */ - protected function toArray($object) + protected function toArray(object $object): array { - return $this->exporter->toArray($object); + return (new Exporter)->toArray($object); } } diff --git a/app/vendor/sebastian/comparator/src/ResourceComparator.php b/app/vendor/sebastian/comparator/src/ResourceComparator.php index 7822598b1..8691e7e19 100644 --- a/app/vendor/sebastian/comparator/src/ResourceComparator.php +++ b/app/vendor/sebastian/comparator/src/ResourceComparator.php @@ -9,45 +9,39 @@ */ namespace SebastianBergmann\Comparator; +use function assert; use function is_resource; +use SebastianBergmann\Exporter\Exporter; /** - * Compares resources for equality. + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for sebastian/comparator + * + * @internal This class is not covered by the backward compatibility promise for sebastian/comparator */ -class ResourceComparator extends Comparator +final class ResourceComparator extends Comparator { - /** - * Returns whether the comparator can compare two values. - * - * @param mixed $expected The first value to compare - * @param mixed $actual The second value to compare - * - * @return bool - */ - public function accepts($expected, $actual) + public function accepts(mixed $expected, mixed $actual): bool { return is_resource($expected) && is_resource($actual); } /** - * Asserts that two values are equal. - * - * @param mixed $expected First value to compare - * @param mixed $actual Second value to compare - * @param float $delta Allowed numerical distance between two values to consider them equal - * @param bool $canonicalize Arrays are sorted before comparison when set to true - * @param bool $ignoreCase Case is ignored when set to true - * * @throws ComparisonFailure */ - public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false)/*: void*/ + public function assertEquals(mixed $expected, mixed $actual, float $delta = 0.0, bool $canonicalize = false, bool $ignoreCase = false): void { + assert(is_resource($expected)); + assert(is_resource($actual)); + + $exporter = new Exporter; + + /** @phpstan-ignore notEqual.notAllowed */ if ($actual != $expected) { throw new ComparisonFailure( $expected, $actual, - $this->exporter->export($expected), - $this->exporter->export($actual) + $exporter->export($expected), + $exporter->export($actual), ); } } diff --git a/app/vendor/sebastian/comparator/src/ScalarComparator.php b/app/vendor/sebastian/comparator/src/ScalarComparator.php index 7f131229a..f28d10220 100644 --- a/app/vendor/sebastian/comparator/src/ScalarComparator.php +++ b/app/vendor/sebastian/comparator/src/ScalarComparator.php @@ -9,77 +9,75 @@ */ namespace SebastianBergmann\Comparator; +use function assert; use function is_bool; use function is_object; use function is_scalar; use function is_string; +use function mb_strtolower; use function method_exists; use function sprintf; -use function strtolower; +use function strlen; +use function substr; +use SebastianBergmann\Exporter\Exporter; /** - * Compares scalar or NULL values for equality. + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for sebastian/comparator + * + * @internal This class is not covered by the backward compatibility promise for sebastian/comparator */ class ScalarComparator extends Comparator { - /** - * Returns whether the comparator can compare two values. - * - * @param mixed $expected The first value to compare - * @param mixed $actual The second value to compare - * - * @return bool - * - * @since Method available since Release 3.6.0 - */ - public function accepts($expected, $actual) + private const int OVERLONG_THRESHOLD = 40; + private const int KEEP_CONTEXT_CHARS = 25; + + public function accepts(mixed $expected, mixed $actual): bool { return ((is_scalar($expected) xor null === $expected) && - (is_scalar($actual) xor null === $actual)) + (is_scalar($actual) xor null === $actual)) || // allow comparison between strings and objects featuring __toString() - || (is_string($expected) && is_object($actual) && method_exists($actual, '__toString')) - || (is_object($expected) && method_exists($expected, '__toString') && is_string($actual)); + (is_string($expected) && is_object($actual) && method_exists($actual, '__toString')) || + (is_object($expected) && method_exists($expected, '__toString') && is_string($actual)); } /** - * Asserts that two values are equal. - * - * @param mixed $expected First value to compare - * @param mixed $actual Second value to compare - * @param float $delta Allowed numerical distance between two values to consider them equal - * @param bool $canonicalize Arrays are sorted before comparison when set to true - * @param bool $ignoreCase Case is ignored when set to true - * * @throws ComparisonFailure */ - public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false)/*: void*/ + public function assertEquals(mixed $expected, mixed $actual, float $delta = 0.0, bool $canonicalize = false, bool $ignoreCase = false): void { $expectedToCompare = $expected; $actualToCompare = $actual; + $exporter = new Exporter; // always compare as strings to avoid strange behaviour // otherwise 0 == 'Foobar' if ((is_string($expected) && !is_bool($actual)) || (is_string($actual) && !is_bool($expected))) { + /** @phpstan-ignore cast.string */ $expectedToCompare = (string) $expectedToCompare; - $actualToCompare = (string) $actualToCompare; + + /** @phpstan-ignore cast.string */ + $actualToCompare = (string) $actualToCompare; if ($ignoreCase) { - $expectedToCompare = strtolower($expectedToCompare); - $actualToCompare = strtolower($actualToCompare); + $expectedToCompare = mb_strtolower($expectedToCompare, 'UTF-8'); + $actualToCompare = mb_strtolower($actualToCompare, 'UTF-8'); } } if ($expectedToCompare !== $actualToCompare && is_string($expected) && is_string($actual)) { + [$cutExpected, $cutActual] = self::removeOverlongCommonPrefix($expected, $actual); + [$cutExpected, $cutActual] = self::removeOverlongCommonSuffix($cutExpected, $cutActual); + throw new ComparisonFailure( $expected, $actual, - $this->exporter->export($expected), - $this->exporter->export($actual), - false, - 'Failed asserting that two strings are equal.' + $exporter->export($cutExpected), + $exporter->export($cutActual), + 'Failed asserting that two strings are equal.', ); } + /** @phpstan-ignore notEqual.notAllowed */ if ($expectedToCompare != $actualToCompare) { throw new ComparisonFailure( $expected, @@ -87,13 +85,80 @@ public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = f // no diff is required '', '', - false, sprintf( 'Failed asserting that %s matches expected %s.', - $this->exporter->export($actual), - $this->exporter->export($expected) - ) + $exporter->export($actual), + $exporter->export($expected), + ), ); } } + + /** + * @return array{string, string} + */ + private static function removeOverlongCommonPrefix(string $string1, string $string2): array + { + $commonPrefix = self::findCommonPrefix($string1, $string2); + + if (strlen($commonPrefix) > self::OVERLONG_THRESHOLD) { + $string1 = '...' . substr($string1, strlen($commonPrefix) - self::KEEP_CONTEXT_CHARS); + $string2 = '...' . substr($string2, strlen($commonPrefix) - self::KEEP_CONTEXT_CHARS); + } + + return [$string1, $string2]; + } + + private static function findCommonPrefix(string $string1, string $string2): string + { + for ($i = 0; $i < strlen($string1); $i++) { + if (!isset($string2[$i]) || $string1[$i] !== $string2[$i]) { + break; + } + } + + assert(isset($i)); + + return substr($string1, 0, $i); + } + + /** + * @return array{string, string} + */ + private static function removeOverlongCommonSuffix(string $string1, string $string2): array + { + $commonSuffix = self::findCommonSuffix($string1, $string2); + + if (strlen($commonSuffix) > self::OVERLONG_THRESHOLD) { + $string1 = substr($string1, 0, -(strlen($commonSuffix) - self::KEEP_CONTEXT_CHARS)) . '...'; + $string2 = substr($string2, 0, -(strlen($commonSuffix) - self::KEEP_CONTEXT_CHARS)) . '...'; + } + + return [$string1, $string2]; + } + + private static function findCommonSuffix(string $string1, string $string2): string + { + if ($string1 === '' || $string2 === '') { + return ''; + } + + $lastCharIndex1 = strlen($string1) - 1; + $lastCharIndex2 = strlen($string2) - 1; + + if ($string1[$lastCharIndex1] !== $string2[$lastCharIndex2]) { + return ''; + } + + while ( + $lastCharIndex1 > 0 && + $lastCharIndex2 > 0 && + $string1[$lastCharIndex1] === $string2[$lastCharIndex2] + ) { + $lastCharIndex1--; + $lastCharIndex2--; + } + + return substr($string1, $lastCharIndex1 - strlen($string1) + 1); + } } diff --git a/app/vendor/sebastian/comparator/src/SplObjectStorageComparator.php b/app/vendor/sebastian/comparator/src/SplObjectStorageComparator.php index d9b6f541a..a32b588be 100644 --- a/app/vendor/sebastian/comparator/src/SplObjectStorageComparator.php +++ b/app/vendor/sebastian/comparator/src/SplObjectStorageComparator.php @@ -9,61 +9,52 @@ */ namespace SebastianBergmann\Comparator; +use function assert; +use SebastianBergmann\Exporter\Exporter; use SplObjectStorage; /** - * Compares \SplObjectStorage instances for equality. + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for sebastian/comparator + * + * @internal This class is not covered by the backward compatibility promise for sebastian/comparator */ -class SplObjectStorageComparator extends Comparator +final class SplObjectStorageComparator extends Comparator { - /** - * Returns whether the comparator can compare two values. - * - * @param mixed $expected The first value to compare - * @param mixed $actual The second value to compare - * - * @return bool - */ - public function accepts($expected, $actual) + public function accepts(mixed $expected, mixed $actual): bool { return $expected instanceof SplObjectStorage && $actual instanceof SplObjectStorage; } /** - * Asserts that two values are equal. - * - * @param mixed $expected First value to compare - * @param mixed $actual Second value to compare - * @param float $delta Allowed numerical distance between two values to consider them equal - * @param bool $canonicalize Arrays are sorted before comparison when set to true - * @param bool $ignoreCase Case is ignored when set to true - * * @throws ComparisonFailure */ - public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false)/*: void*/ + public function assertEquals(mixed $expected, mixed $actual, float $delta = 0.0, bool $canonicalize = false, bool $ignoreCase = false): void { + assert($expected instanceof SplObjectStorage); + assert($actual instanceof SplObjectStorage); + + $exporter = new Exporter; + foreach ($actual as $object) { - if (!$expected->contains($object)) { + if (!$expected->offsetExists($object)) { throw new ComparisonFailure( $expected, $actual, - $this->exporter->export($expected), - $this->exporter->export($actual), - false, - 'Failed asserting that two objects are equal.' + $exporter->export($expected), + $exporter->export($actual), + 'Failed asserting that two objects are equal.', ); } } foreach ($expected as $object) { - if (!$actual->contains($object)) { + if (!$actual->offsetExists($object)) { throw new ComparisonFailure( $expected, $actual, - $this->exporter->export($expected), - $this->exporter->export($actual), - false, - 'Failed asserting that two objects are equal.' + $exporter->export($expected), + $exporter->export($actual), + 'Failed asserting that two objects are equal.', ); } } diff --git a/app/vendor/sebastian/comparator/src/TypeComparator.php b/app/vendor/sebastian/comparator/src/TypeComparator.php index b0d38d72e..c54468b8e 100644 --- a/app/vendor/sebastian/comparator/src/TypeComparator.php +++ b/app/vendor/sebastian/comparator/src/TypeComparator.php @@ -11,51 +11,37 @@ use function gettype; use function sprintf; +use SebastianBergmann\Exporter\Exporter; /** - * Compares values for type equality. + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for sebastian/comparator + * + * @internal This class is not covered by the backward compatibility promise for sebastian/comparator */ -class TypeComparator extends Comparator +final class TypeComparator extends Comparator { - /** - * Returns whether the comparator can compare two values. - * - * @param mixed $expected The first value to compare - * @param mixed $actual The second value to compare - * - * @return bool - */ - public function accepts($expected, $actual) + public function accepts(mixed $expected, mixed $actual): bool { return true; } /** - * Asserts that two values are equal. - * - * @param mixed $expected First value to compare - * @param mixed $actual Second value to compare - * @param float $delta Allowed numerical distance between two values to consider them equal - * @param bool $canonicalize Arrays are sorted before comparison when set to true - * @param bool $ignoreCase Case is ignored when set to true - * * @throws ComparisonFailure */ - public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false)/*: void*/ + public function assertEquals(mixed $expected, mixed $actual, float $delta = 0.0, bool $canonicalize = false, bool $ignoreCase = false): void { - if (gettype($expected) != gettype($actual)) { + if (gettype($expected) !== gettype($actual)) { throw new ComparisonFailure( $expected, $actual, // we don't need a diff '', '', - false, sprintf( '%s does not match expected type "%s".', - $this->exporter->shortenedExport($actual), - gettype($expected) - ) + (new Exporter)->shortenedExport($actual), + gettype($expected), + ), ); } } diff --git a/app/vendor/sebastian/comparator/src/exceptions/Exception.php b/app/vendor/sebastian/comparator/src/exceptions/Exception.php index 8975aaf1b..122130962 100644 --- a/app/vendor/sebastian/comparator/src/exceptions/Exception.php +++ b/app/vendor/sebastian/comparator/src/exceptions/Exception.php @@ -11,6 +11,9 @@ use Throwable; +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for sebastian/comparator + */ interface Exception extends Throwable { } diff --git a/app/vendor/sebastian/comparator/src/exceptions/RuntimeException.php b/app/vendor/sebastian/comparator/src/exceptions/RuntimeException.php index ca726084a..1ad7d7391 100644 --- a/app/vendor/sebastian/comparator/src/exceptions/RuntimeException.php +++ b/app/vendor/sebastian/comparator/src/exceptions/RuntimeException.php @@ -9,6 +9,9 @@ */ namespace SebastianBergmann\Comparator; +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for sebastian/comparator + */ final class RuntimeException extends \RuntimeException implements Exception { } diff --git a/app/vendor/sebastian/complexity/.psalm/baseline.xml b/app/vendor/sebastian/complexity/.psalm/baseline.xml deleted file mode 100644 index 77e688e07..000000000 --- a/app/vendor/sebastian/complexity/.psalm/baseline.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/app/vendor/sebastian/complexity/.psalm/config.xml b/app/vendor/sebastian/complexity/.psalm/config.xml deleted file mode 100644 index 8172fe15a..000000000 --- a/app/vendor/sebastian/complexity/.psalm/config.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - diff --git a/app/vendor/sebastian/complexity/ChangeLog.md b/app/vendor/sebastian/complexity/ChangeLog.md index 51deba71c..6d06b2cb3 100644 --- a/app/vendor/sebastian/complexity/ChangeLog.md +++ b/app/vendor/sebastian/complexity/ChangeLog.md @@ -2,12 +2,60 @@ All notable changes are documented in this file using the [Keep a CHANGELOG](https://keepachangelog.com/) principles. -## [2.0.3] - 2023-12-22 +## [5.0.0] - 2025-02-07 + +### Removed + +* This component is no longer supported on PHP 8.2 + +## [4.0.1] - 2024-07-03 + +### Changed + +* This project now uses PHPStan instead of Psalm for static analysis + +## [4.0.0] - 2024-02-02 + +### Removed + +* This component now requires PHP-Parser 5 +* This component is no longer supported on PHP 8.1 + +## [3.2.0] - 2023-12-21 + +### Added + +* `ComplexityCollection::sortByDescendingCyclomaticComplexity()` +* Support for `match` arms ### Changed * This component is now compatible with `nikic/php-parser` 5.0 +## [3.1.0] - 2023-09-28 + +### Added + +* `Complexity::isFunction()` and `Complexity::isMethod()` +* `ComplexityCollection::isFunction()` and `ComplexityCollection::isMethod()` +* `ComplexityCollection::mergeWith()` + +### Fixed + +* Anonymous classes are not processed correctly + +## [3.0.1] - 2023-08-31 + +### Fixed + +* [#7](https://github.com/sebastianbergmann/complexity/pull/7): `ComplexityCalculatingVisitor` tries to process interface methods + +## [3.0.0] - 2023-02-03 + +### Removed + +* This component is no longer supported on PHP 7.3, PHP 7.4 and PHP 8.0 + ## [2.0.2] - 2020-10-26 ### Fixed @@ -30,7 +78,13 @@ All notable changes are documented in this file using the [Keep a CHANGELOG](htt * Initial release -[2.0.3]: https://github.com/sebastianbergmann/complexity/compare/2.0.2...2.0.3 +[5.0.0]: https://github.com/sebastianbergmann/complexity/compare/4.0...5.0.0 +[4.0.1]: https://github.com/sebastianbergmann/complexity/compare/4.0.0...4.0.1 +[4.0.0]: https://github.com/sebastianbergmann/complexity/compare/3.2...4.0.0 +[3.2.0]: https://github.com/sebastianbergmann/complexity/compare/3.1.0...3.2.0 +[3.1.0]: https://github.com/sebastianbergmann/complexity/compare/3.0.1...3.1.0 +[3.0.1]: https://github.com/sebastianbergmann/complexity/compare/3.0.0...3.0.1 +[3.0.0]: https://github.com/sebastianbergmann/complexity/compare/2.0.2...3.0.0 [2.0.2]: https://github.com/sebastianbergmann/complexity/compare/2.0.1...2.0.2 [2.0.1]: https://github.com/sebastianbergmann/complexity/compare/2.0.0...2.0.1 [2.0.0]: https://github.com/sebastianbergmann/complexity/compare/1.0.0...2.0.0 diff --git a/app/vendor/sebastian/complexity/LICENSE b/app/vendor/sebastian/complexity/LICENSE index 5f818df69..0d534da34 100644 --- a/app/vendor/sebastian/complexity/LICENSE +++ b/app/vendor/sebastian/complexity/LICENSE @@ -1,33 +1,29 @@ -sebastian/complexity +BSD 3-Clause License -Copyright (c) 2020, Sebastian Bergmann . +Copyright (c) 2020-2025, Sebastian Bergmann All rights reserved. Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: +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. +1. 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. +2. 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 Sebastian Bergmann nor the names of his - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. +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/app/vendor/sebastian/complexity/README.md b/app/vendor/sebastian/complexity/README.md index 5f53b0b5d..7293280c0 100644 --- a/app/vendor/sebastian/complexity/README.md +++ b/app/vendor/sebastian/complexity/README.md @@ -1,12 +1,11 @@ +[![Latest Stable Version](https://poser.pugx.org/sebastian/complexity/v)](https://packagist.org/packages/sebastian/complexity) +[![CI Status](https://github.com/sebastianbergmann/complexity/workflows/CI/badge.svg)](https://github.com/sebastianbergmann/complexity/actions) +[![codecov](https://codecov.io/gh/sebastianbergmann/complexity/branch/main/graph/badge.svg)](https://codecov.io/gh/sebastianbergmann/complexity) + # sebastian/complexity Library for calculating the complexity of PHP code units. -[![Latest Stable Version](https://img.shields.io/packagist/v/sebastian/complexity.svg?style=flat-square)](https://packagist.org/packages/sebastian/complexity) -[![Minimum PHP Version](https://img.shields.io/badge/php-%3E%3D%207.3-8892BF.svg?style=flat-square)](https://php.net/) -[![CI Status](https://github.com/sebastianbergmann/complexity/workflows/CI/badge.svg?branch=master&event=push)](https://phpunit.de/build-status.html) -[![Type Coverage](https://shepherd.dev/github/sebastianbergmann/complexity/coverage.svg)](https://shepherd.dev/github/sebastianbergmann/complexity) - ## Installation You can add this library as a local, per-project dependency to your project using [Composer](https://getcomposer.org/): diff --git a/app/vendor/sebastian/complexity/SECURITY.md b/app/vendor/sebastian/complexity/SECURITY.md new file mode 100644 index 000000000..d88ff0019 --- /dev/null +++ b/app/vendor/sebastian/complexity/SECURITY.md @@ -0,0 +1,30 @@ +# Security Policy + +If you believe you have found a security vulnerability in the library that is developed in this repository, please report it to us through coordinated disclosure. + +**Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.** + +Instead, please email `sebastian@phpunit.de`. + +Please include as much of the information listed below as you can to help us better understand and resolve the issue: + +* The type of issue +* Full paths of source file(s) related to the manifestation of the issue +* The location of the affected source code (tag/branch/commit or direct URL) +* Any special configuration required to reproduce the issue +* Step-by-step instructions to reproduce the issue +* Proof-of-concept or exploit code (if possible) +* Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +## Web Context + +The library that is developed in this repository was either extracted from [PHPUnit](https://github.com/sebastianbergmann/phpunit) or developed specifically as a dependency for PHPUnit. + +The library is developed with a focus on development environments and the command-line. No specific testing or hardening with regard to using the library in an HTTP or web context or with untrusted input data is performed. The library might also contain functionality that intentionally exposes internal application data for debugging purposes. + +If the library is used in a web application, the application developer is responsible for filtering inputs or escaping outputs as necessary and for verifying that the used functionality is safe for use within the intended context. + +Vulnerabilities specific to the use outside a development context will be fixed as applicable, provided that the fix does not have an averse effect on the primary use case for development purposes. + diff --git a/app/vendor/sebastian/complexity/composer.json b/app/vendor/sebastian/complexity/composer.json index 592e41850..994a5a3de 100644 --- a/app/vendor/sebastian/complexity/composer.json +++ b/app/vendor/sebastian/complexity/composer.json @@ -12,19 +12,20 @@ } ], "support": { - "issues": "https://github.com/sebastianbergmann/complexity/issues" + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "security": "https://github.com/sebastianbergmann/complexity/security/policy" }, "prefer-stable": true, "require": { - "php": ">=7.3", - "nikic/php-parser": "^4.18 || ^5.0" + "php": ">=8.3", + "nikic/php-parser": "^5.0" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^12.0" }, "config": { "platform": { - "php": "7.3.0" + "php": "8.3.0" }, "optimize-autoloader": true, "sort-packages": true @@ -36,7 +37,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "5.0-dev" } } } diff --git a/app/vendor/sebastian/complexity/src/Calculator.php b/app/vendor/sebastian/complexity/src/Calculator.php index f53819035..546cbd749 100644 --- a/app/vendor/sebastian/complexity/src/Calculator.php +++ b/app/vendor/sebastian/complexity/src/Calculator.php @@ -9,6 +9,11 @@ */ namespace SebastianBergmann\Complexity; +use function assert; +use function file_exists; +use function file_get_contents; +use function is_readable; +use function is_string; use PhpParser\Error; use PhpParser\Node; use PhpParser\NodeTraverser; @@ -19,11 +24,20 @@ final class Calculator { /** + * @param non-empty-string $sourceFile + * * @throws RuntimeException */ public function calculateForSourceFile(string $sourceFile): ComplexityCollection { - return $this->calculateForSourceString(file_get_contents($sourceFile)); + assert(file_exists($sourceFile)); + assert(is_readable($sourceFile)); + + $source = file_get_contents($sourceFile); + + assert(is_string($source)); + + return $this->calculateForSourceString($source); } /** @@ -37,13 +51,12 @@ public function calculateForSourceString(string $source): ComplexityCollection assert($nodes !== null); return $this->calculateForAbstractSyntaxTree($nodes); - // @codeCoverageIgnoreStart } catch (Error $error) { throw new RuntimeException( $error->getMessage(), - (int) $error->getCode(), - $error + $error->getCode(), + $error, ); } // @codeCoverageIgnoreEnd @@ -70,8 +83,8 @@ public function calculateForAbstractSyntaxTree(array $nodes): ComplexityCollecti } catch (Error $error) { throw new RuntimeException( $error->getMessage(), - (int) $error->getCode(), - $error + $error->getCode(), + $error, ); } // @codeCoverageIgnoreEnd diff --git a/app/vendor/sebastian/complexity/src/Complexity/Complexity.php b/app/vendor/sebastian/complexity/src/Complexity/Complexity.php index dc6708dde..554a15d89 100644 --- a/app/vendor/sebastian/complexity/src/Complexity/Complexity.php +++ b/app/vendor/sebastian/complexity/src/Complexity/Complexity.php @@ -9,34 +9,56 @@ */ namespace SebastianBergmann\Complexity; +use function str_contains; + /** - * @psalm-immutable + * @immutable */ -final class Complexity +final readonly class Complexity { /** - * @var string + * @var non-empty-string */ - private $name; + private string $name; /** - * @var int + * @var positive-int */ - private $cyclomaticComplexity; + private int $cyclomaticComplexity; + /** + * @param non-empty-string $name + * @param positive-int $cyclomaticComplexity + */ public function __construct(string $name, int $cyclomaticComplexity) { $this->name = $name; $this->cyclomaticComplexity = $cyclomaticComplexity; } + /** + * @return non-empty-string + */ public function name(): string { return $this->name; } + /** + * @return positive-int + */ public function cyclomaticComplexity(): int { return $this->cyclomaticComplexity; } + + public function isFunction(): bool + { + return !$this->isMethod(); + } + + public function isMethod(): bool + { + return str_contains($this->name, '::'); + } } diff --git a/app/vendor/sebastian/complexity/src/Complexity/ComplexityCollection.php b/app/vendor/sebastian/complexity/src/Complexity/ComplexityCollection.php index ccbddbf77..90fc4d085 100644 --- a/app/vendor/sebastian/complexity/src/Complexity/ComplexityCollection.php +++ b/app/vendor/sebastian/complexity/src/Complexity/ComplexityCollection.php @@ -9,27 +9,34 @@ */ namespace SebastianBergmann\Complexity; +use function array_filter; +use function array_merge; +use function array_reverse; +use function array_values; use function count; +use function usort; use Countable; use IteratorAggregate; /** + * @template-implements IteratorAggregate + * * @psalm-immutable */ -final class ComplexityCollection implements Countable, IteratorAggregate +final readonly class ComplexityCollection implements Countable, IteratorAggregate { /** - * @psalm-var list + * @var list */ - private $items = []; + private array $items; public static function fromList(Complexity ...$items): self { - return new self($items); + return new self(array_values($items)); } /** - * @psalm-param list $items + * @param list $items */ private function __construct(array $items) { @@ -37,7 +44,7 @@ private function __construct(array $items) } /** - * @psalm-return list + * @return list */ public function asArray(): array { @@ -49,6 +56,9 @@ public function getIterator(): ComplexityCollectionIterator return new ComplexityCollectionIterator($this); } + /** + * @return non-negative-int + */ public function count(): int { return count($this->items); @@ -59,6 +69,9 @@ public function isEmpty(): bool return empty($this->items); } + /** + * @return non-negative-int + */ public function cyclomaticComplexity(): int { $cyclomaticComplexity = 0; @@ -69,4 +82,53 @@ public function cyclomaticComplexity(): int return $cyclomaticComplexity; } + + public function isFunction(): self + { + return new self( + array_values( + array_filter( + $this->items, + static fn (Complexity $complexity): bool => $complexity->isFunction(), + ), + ), + ); + } + + public function isMethod(): self + { + return new self( + array_values( + array_filter( + $this->items, + static fn (Complexity $complexity): bool => $complexity->isMethod(), + ), + ), + ); + } + + public function mergeWith(self $other): self + { + return new self( + array_merge( + $this->asArray(), + $other->asArray(), + ), + ); + } + + public function sortByDescendingCyclomaticComplexity(): self + { + $items = $this->items; + + usort( + $items, + static function (Complexity $a, Complexity $b): int + { + return $a->cyclomaticComplexity() <=> $b->cyclomaticComplexity(); + }, + ); + + return new self(array_reverse($items)); + } } diff --git a/app/vendor/sebastian/complexity/src/Complexity/ComplexityCollectionIterator.php b/app/vendor/sebastian/complexity/src/Complexity/ComplexityCollectionIterator.php index ec39e199f..6415ce626 100644 --- a/app/vendor/sebastian/complexity/src/Complexity/ComplexityCollectionIterator.php +++ b/app/vendor/sebastian/complexity/src/Complexity/ComplexityCollectionIterator.php @@ -11,17 +11,16 @@ use Iterator; +/** + * @template-implements Iterator + */ final class ComplexityCollectionIterator implements Iterator { /** - * @psalm-var list - */ - private $items; - - /** - * @var int + * @var list */ - private $position = 0; + private readonly array $items; + private int $position = 0; public function __construct(ComplexityCollection $items) { diff --git a/app/vendor/sebastian/complexity/src/Visitor/ComplexityCalculatingVisitor.php b/app/vendor/sebastian/complexity/src/Visitor/ComplexityCalculatingVisitor.php index b69f2b09f..41ef2f036 100644 --- a/app/vendor/sebastian/complexity/src/Visitor/ComplexityCalculatingVisitor.php +++ b/app/vendor/sebastian/complexity/src/Visitor/ComplexityCalculatingVisitor.php @@ -12,26 +12,24 @@ use function assert; use function is_array; use PhpParser\Node; -use PhpParser\Node\Name; +use PhpParser\Node\Expr\New_; use PhpParser\Node\Stmt; use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Function_; +use PhpParser\Node\Stmt\Interface_; use PhpParser\Node\Stmt\Trait_; use PhpParser\NodeTraverser; +use PhpParser\NodeVisitor; use PhpParser\NodeVisitorAbstract; final class ComplexityCalculatingVisitor extends NodeVisitorAbstract { /** - * @psalm-var list + * @var list */ - private $result = []; - - /** - * @var bool - */ - private $shortCircuitTraversal; + private array $result = []; + private bool $shortCircuitTraversal; public function __construct(bool $shortCircuitTraversal) { @@ -45,6 +43,14 @@ public function enterNode(Node $node): ?int } if ($node instanceof ClassMethod) { + if ($node->getAttribute('parent') instanceof Interface_) { + return null; + } + + if ($node->isAbstract()) { + return null; + } + $name = $this->classMethodName($node); } else { $name = $this->functionName($node); @@ -56,11 +62,11 @@ public function enterNode(Node $node): ?int $this->result[] = new Complexity( $name, - $this->cyclomaticComplexity($statements) + $this->cyclomaticComplexity($statements), ); if ($this->shortCircuitTraversal) { - return NodeTraverser::DONT_TRAVERSE_CHILDREN; + return NodeVisitor::DONT_TRAVERSE_CHILDREN; } return null; @@ -73,6 +79,8 @@ public function result(): ComplexityCollection /** * @param Stmt[] $statements + * + * @return positive-int */ private function cyclomaticComplexity(array $statements): int { @@ -88,21 +96,30 @@ private function cyclomaticComplexity(array $statements): int return $cyclomaticComplexityCalculatingVisitor->cyclomaticComplexity(); } + /** + * @return non-empty-string + */ private function classMethodName(ClassMethod $node): string { $parent = $node->getAttribute('parent'); assert($parent instanceof Class_ || $parent instanceof Trait_); + + if ($parent->getAttribute('parent') instanceof New_) { + return 'anonymous class'; + } + assert(isset($parent->namespacedName)); - assert($parent->namespacedName instanceof Name); return $parent->namespacedName->toString() . '::' . $node->name->toString(); } + /** + * @return non-empty-string + */ private function functionName(Function_ $node): string { assert(isset($node->namespacedName)); - assert($node->namespacedName instanceof Name); return $node->namespacedName->toString(); } diff --git a/app/vendor/sebastian/complexity/src/Visitor/CyclomaticComplexityCalculatingVisitor.php b/app/vendor/sebastian/complexity/src/Visitor/CyclomaticComplexityCalculatingVisitor.php index d4430876d..a5dd5fb9f 100644 --- a/app/vendor/sebastian/complexity/src/Visitor/CyclomaticComplexityCalculatingVisitor.php +++ b/app/vendor/sebastian/complexity/src/Visitor/CyclomaticComplexityCalculatingVisitor.php @@ -9,7 +9,6 @@ */ namespace SebastianBergmann\Complexity; -use function get_class; use PhpParser\Node; use PhpParser\Node\Expr\BinaryOp\BooleanAnd; use PhpParser\Node\Expr\BinaryOp\BooleanOr; @@ -28,14 +27,13 @@ final class CyclomaticComplexityCalculatingVisitor extends NodeVisitorAbstract { /** - * @var int + * @var positive-int */ - private $cyclomaticComplexity = 1; + private int $cyclomaticComplexity = 1; - public function enterNode(Node $node): void + public function enterNode(Node $node): null { - /* @noinspection GetClassMissUseInspection */ - switch (get_class($node)) { + switch ($node::class) { case BooleanAnd::class: case BooleanOr::class: case Case_::class: @@ -46,12 +44,18 @@ public function enterNode(Node $node): void case If_::class: case LogicalAnd::class: case LogicalOr::class: + case Node\MatchArm::class: case Ternary::class: case While_::class: $this->cyclomaticComplexity++; } + + return null; } + /** + * @return positive-int + */ public function cyclomaticComplexity(): int { return $this->cyclomaticComplexity; diff --git a/app/vendor/sebastian/diff/ChangeLog.md b/app/vendor/sebastian/diff/ChangeLog.md index e62d8f957..4d3379ba3 100644 --- a/app/vendor/sebastian/diff/ChangeLog.md +++ b/app/vendor/sebastian/diff/ChangeLog.md @@ -2,19 +2,87 @@ All notable changes are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles. -## [4.0.6] - 2024-03-02 +## [7.0.0] - 2025-02-07 + +### Removed + +* This component is no longer supported on PHP 8.3 + +## [6.0.2] - 2024-07-03 + +### Changed + +* This project now uses PHPStan instead of Psalm for static analysis + +## [6.0.1] - 2024-03-02 + +### Changed + +* Do not use implicitly nullable parameters + +## [6.0.0] - 2024-02-02 + +### Removed + +* `SebastianBergmann\Diff\Chunk::getStart()`, `SebastianBergmann\Diff\Chunk::getStartRange()`, `SebastianBergmann\Diff\Chunk::getEnd()`, `SebastianBergmann\Diff\Chunk::getEndRange()`, and `SebastianBergmann\Diff\Chunk::getLines()` +* `SebastianBergmann\Diff\Diff::getFrom()`, `SebastianBergmann\Diff\Diff::getTo()`, and `SebastianBergmann\Diff\Diff::getChunks()` +* `SebastianBergmann\Diff\Line::getContent()` and `SebastianBergmann\Diff\Diff::getType()` +* This component is no longer supported on PHP 8.1 + +## [5.1.1] - 2024-03-02 ### Changed * Do not use implicitly nullable parameters -## [4.0.5] - 2023-05-07 +## [5.1.0] - 2023-12-22 + +### Added + +* `SebastianBergmann\Diff\Chunk::start()`, `SebastianBergmann\Diff\Chunk::startRange()`, `SebastianBergmann\Diff\Chunk::end()`, `SebastianBergmann\Diff\Chunk::endRange()`, and `SebastianBergmann\Diff\Chunk::lines()` +* `SebastianBergmann\Diff\Diff::from()`, `SebastianBergmann\Diff\Diff::to()`, and `SebastianBergmann\Diff\Diff::chunks()` +* `SebastianBergmann\Diff\Line::content()` and `SebastianBergmann\Diff\Diff::type()` +* `SebastianBergmann\Diff\Line::isAdded()`,`SebastianBergmann\Diff\Line::isRemoved()`, and `SebastianBergmann\Diff\Line::isUnchanged()` + +### Changed + +* `SebastianBergmann\Diff\Diff` now implements `IteratorAggregate`, iterating over it yields the aggregated `SebastianBergmann\Diff\Chunk` objects +* `SebastianBergmann\Diff\Chunk` now implements `IteratorAggregate`, iterating over it yields the aggregated `SebastianBergmann\Diff\Line` objects + +### Deprecated + +* `SebastianBergmann\Diff\Chunk::getStart()`, `SebastianBergmann\Diff\Chunk::getStartRange()`, `SebastianBergmann\Diff\Chunk::getEnd()`, `SebastianBergmann\Diff\Chunk::getEndRange()`, and `SebastianBergmann\Diff\Chunk::getLines()` +* `SebastianBergmann\Diff\Diff::getFrom()`, `SebastianBergmann\Diff\Diff::getTo()`, and `SebastianBergmann\Diff\Diff::getChunks()` +* `SebastianBergmann\Diff\Line::getContent()` and `SebastianBergmann\Diff\Diff::getType()` + +## [5.0.3] - 2023-05-01 ### Changed -* [#118](https://github.com/sebastianbergmann/diff/pull/118): Improve performance of `MemoryEfficientLongestCommonSubsequenceCalculator` * [#119](https://github.com/sebastianbergmann/diff/pull/119): Improve performance of `TimeEfficientLongestCommonSubsequenceCalculator` +## [5.0.2] - 2023-05-01 + +### Changed + +* [#118](https://github.com/sebastianbergmann/diff/pull/118): Improve performance of `MemoryEfficientLongestCommonSubsequenceCalculator` + +## [5.0.1] - 2023-03-23 + +### Fixed + +* [#115](https://github.com/sebastianbergmann/diff/pull/115): `Parser::parseFileDiff()` does not handle diffs correctly that only add lines or only remove lines + +## [5.0.0] - 2023-02-03 + +### Changed + +* Passing a `DiffOutputBuilderInterface` instance to `Differ::__construct()` is no longer optional + +### Removed + +* This component is no longer supported on PHP 7.3, PHP 7.4, and PHP 8.0 + ## [4.0.4] - 2020-10-26 ### Fixed @@ -43,7 +111,7 @@ All notable changes are documented in this file using the [Keep a CHANGELOG](htt ### Removed -* Removed support for PHP 7.1 and PHP 7.2 +* This component is no longer supported on PHP 7.1 and PHP 7.2 ## [3.0.2] - 2019-02-04 @@ -67,7 +135,7 @@ All notable changes are documented in this file using the [Keep a CHANGELOG](htt ### Removed -* Removed support for PHP 7.0 +* This component is no longer supported on PHP 7.0 ### Fixed @@ -89,8 +157,16 @@ All notable changes are documented in this file using the [Keep a CHANGELOG](htt * This component is no longer supported on PHP 5.6 -[4.0.6]: https://github.com/sebastianbergmann/diff/compare/4.0.5...4.0.6 -[4.0.5]: https://github.com/sebastianbergmann/diff/compare/4.0.4...4.0.5 +[7.0.0]: https://github.com/sebastianbergmann/diff/compare/6.0...7.0.0 +[6.0.2]: https://github.com/sebastianbergmann/diff/compare/6.0.1...6.0.2 +[6.0.1]: https://github.com/sebastianbergmann/diff/compare/6.0.0...6.0.1 +[6.0.0]: https://github.com/sebastianbergmann/diff/compare/5.1...6.0.0 +[5.1.1]: https://github.com/sebastianbergmann/diff/compare/5.1.0...5.1.1 +[5.1.0]: https://github.com/sebastianbergmann/diff/compare/5.0.3...5.1.0 +[5.0.3]: https://github.com/sebastianbergmann/diff/compare/5.0.2...5.0.3 +[5.0.2]: https://github.com/sebastianbergmann/diff/compare/5.0.1...5.0.2 +[5.0.1]: https://github.com/sebastianbergmann/diff/compare/5.0.0...5.0.1 +[5.0.0]: https://github.com/sebastianbergmann/diff/compare/4.0.4...5.0.0 [4.0.4]: https://github.com/sebastianbergmann/diff/compare/4.0.3...4.0.4 [4.0.3]: https://github.com/sebastianbergmann/diff/compare/4.0.2...4.0.3 [4.0.2]: https://github.com/sebastianbergmann/diff/compare/4.0.1...4.0.2 diff --git a/app/vendor/sebastian/diff/LICENSE b/app/vendor/sebastian/diff/LICENSE index f22f31cf0..c5268a916 100644 --- a/app/vendor/sebastian/diff/LICENSE +++ b/app/vendor/sebastian/diff/LICENSE @@ -1,33 +1,29 @@ -sebastian/diff +BSD 3-Clause License -Copyright (c) 2002-2020, Sebastian Bergmann . +Copyright (c) 2002-2025, Sebastian Bergmann All rights reserved. Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: +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. +1. 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. +2. 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 Sebastian Bergmann nor the names of his - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. +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/app/vendor/sebastian/diff/README.md b/app/vendor/sebastian/diff/README.md index 734b852de..6688692f1 100644 --- a/app/vendor/sebastian/diff/README.md +++ b/app/vendor/sebastian/diff/README.md @@ -1,7 +1,8 @@ -# sebastian/diff - +[![Latest Stable Version](https://poser.pugx.org/sebastian/diff/v)](https://packagist.org/packages/sebastian/diff) [![CI Status](https://github.com/sebastianbergmann/diff/workflows/CI/badge.svg)](https://github.com/sebastianbergmann/diff/actions) -[![Type Coverage](https://shepherd.dev/github/sebastianbergmann/diff/coverage.svg)](https://shepherd.dev/github/sebastianbergmann/diff) +[![codecov](https://codecov.io/gh/sebastianbergmann/diff/branch/main/graph/badge.svg)](https://codecov.io/gh/sebastianbergmann/diff) + +# sebastian/diff Diff implementation for PHP, factored out of PHPUnit into a stand-alone component. @@ -26,14 +27,17 @@ composer require --dev sebastian/diff The `Differ` class can be used to generate a textual representation of the difference between two strings: ```php -diff('foo', 'bar'); ``` The code above yields the output below: + ```diff --- Original +++ New @@ -42,73 +46,16 @@ The code above yields the output below: +bar ``` -There are three output builders available in this package: - -#### UnifiedDiffOutputBuilder - -This is default builder, which generates the output close to udiff and is used by PHPUnit. - -```php -diff('foo', 'bar'); -``` - -#### StrictUnifiedDiffOutputBuilder - -Generates (strict) Unified diff's (unidiffs) with hunks, -similar to `diff -u` and compatible with `patch` and `git apply`. - -```php - true, // ranges of length one are rendered with the trailing `,1` - 'commonLineThreshold' => 6, // number of same lines before ending a new hunk and creating a new one (if needed) - 'contextLines' => 3, // like `diff: -u, -U NUM, --unified[=NUM]`, for patch/git apply compatibility best to keep at least @ 3 - 'fromFile' => null, - 'fromFileDate' => null, - 'toFile' => null, - 'toFileDate' => null, -]); - -$differ = new Differ($builder); -print $differ->diff('foo', 'bar'); -``` +The `UnifiedDiffOutputBuilder` used in the example above generates output in "unified diff" +format and is used by PHPUnit, for example. -#### DiffOnlyOutputBuilder +The `StrictUnifiedDiffOutputBuilder` generates output in "strict unified diff" format with +hunks, similar to `diff -u` and compatible with `patch` or `git apply`. -Output only the lines that differ. +The `DiffOnlyOutputBuilder` generates output that only contains the lines that differ. -```php -diff('foo', 'bar'); -``` - -#### DiffOutputBuilderInterface - -You can pass any output builder to the `Differ` class as longs as it implements the `DiffOutputBuilderInterface`. +If none of these three output builders match your use case then you can implement +`DiffOutputBuilderInterface` to generate custom output. #### Parsing diff @@ -200,3 +147,5 @@ The code above yields the output below: ) ) ) + +Note: If the chunk size is 0 lines, i.e., `getStartRange()` or `getEndRange()` return 0, the number of line returned by `getStart()` or `getEnd()` is one lower than one would expect. It is the line number after which the chunk should be inserted or deleted; in all other cases, it gives the first line number of the replaced range of lines. diff --git a/app/vendor/sebastian/diff/SECURITY.md b/app/vendor/sebastian/diff/SECURITY.md new file mode 100644 index 000000000..d88ff0019 --- /dev/null +++ b/app/vendor/sebastian/diff/SECURITY.md @@ -0,0 +1,30 @@ +# Security Policy + +If you believe you have found a security vulnerability in the library that is developed in this repository, please report it to us through coordinated disclosure. + +**Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.** + +Instead, please email `sebastian@phpunit.de`. + +Please include as much of the information listed below as you can to help us better understand and resolve the issue: + +* The type of issue +* Full paths of source file(s) related to the manifestation of the issue +* The location of the affected source code (tag/branch/commit or direct URL) +* Any special configuration required to reproduce the issue +* Step-by-step instructions to reproduce the issue +* Proof-of-concept or exploit code (if possible) +* Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +## Web Context + +The library that is developed in this repository was either extracted from [PHPUnit](https://github.com/sebastianbergmann/phpunit) or developed specifically as a dependency for PHPUnit. + +The library is developed with a focus on development environments and the command-line. No specific testing or hardening with regard to using the library in an HTTP or web context or with untrusted input data is performed. The library might also contain functionality that intentionally exposes internal application data for debugging purposes. + +If the library is used in a web application, the application developer is responsible for filtering inputs or escaping outputs as necessary and for verifying that the used functionality is safe for use within the intended context. + +Vulnerabilities specific to the use outside a development context will be fixed as applicable, provided that the fix does not have an averse effect on the primary use case for development purposes. + diff --git a/app/vendor/sebastian/diff/composer.json b/app/vendor/sebastian/diff/composer.json index cf92202ba..124d8ed6f 100644 --- a/app/vendor/sebastian/diff/composer.json +++ b/app/vendor/sebastian/diff/composer.json @@ -14,20 +14,24 @@ "email": "mail@kore-nordmann.de" } ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "security": "https://github.com/sebastianbergmann/diff/security/policy" + }, "prefer-stable": true, "config": { "platform": { - "php": "7.3.0" + "php": "8.3.0" }, "optimize-autoloader": true, "sort-packages": true }, "require": { - "php": ">=7.3" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^9.3", - "symfony/process": "^4.2 || ^5" + "phpunit/phpunit": "^12.0", + "symfony/process": "^7.2" }, "autoload": { "classmap": [ @@ -41,7 +45,7 @@ }, "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "7.0-dev" } } } diff --git a/app/vendor/sebastian/diff/src/Chunk.php b/app/vendor/sebastian/diff/src/Chunk.php index 16ae34f41..18b5ce17c 100644 --- a/app/vendor/sebastian/diff/src/Chunk.php +++ b/app/vendor/sebastian/diff/src/Chunk.php @@ -9,33 +9,28 @@ */ namespace SebastianBergmann\Diff; -final class Chunk -{ - /** - * @var int - */ - private $start; - - /** - * @var int - */ - private $startRange; +use ArrayIterator; +use IteratorAggregate; +use Traversable; - /** - * @var int - */ - private $end; +/** + * @template-implements IteratorAggregate + */ +final class Chunk implements IteratorAggregate +{ + private int $start; + private int $startRange; + private int $end; + private int $endRange; /** - * @var int + * @var list */ - private $endRange; + private array $lines; /** - * @var Line[] + * @param list $lines */ - private $lines; - public function __construct(int $start = 0, int $startRange = 1, int $end = 0, int $endRange = 1, array $lines = []) { $this->start = $start; @@ -45,45 +40,44 @@ public function __construct(int $start = 0, int $startRange = 1, int $end = 0, i $this->lines = $lines; } - public function getStart(): int + public function start(): int { return $this->start; } - public function getStartRange(): int + public function startRange(): int { return $this->startRange; } - public function getEnd(): int + public function end(): int { return $this->end; } - public function getEndRange(): int + public function endRange(): int { return $this->endRange; } /** - * @return Line[] + * @return list */ - public function getLines(): array + public function lines(): array { return $this->lines; } /** - * @param Line[] $lines + * @param list $lines */ public function setLines(array $lines): void { - foreach ($lines as $line) { - if (!$line instanceof Line) { - throw new InvalidArgumentException; - } - } - $this->lines = $lines; } + + public function getIterator(): Traversable + { + return new ArrayIterator($this->lines); + } } diff --git a/app/vendor/sebastian/diff/src/Diff.php b/app/vendor/sebastian/diff/src/Diff.php index 17b2084f9..372eb3d5c 100644 --- a/app/vendor/sebastian/diff/src/Diff.php +++ b/app/vendor/sebastian/diff/src/Diff.php @@ -9,25 +9,34 @@ */ namespace SebastianBergmann\Diff; -final class Diff +use ArrayIterator; +use IteratorAggregate; +use Traversable; + +/** + * @template-implements IteratorAggregate + */ +final class Diff implements IteratorAggregate { /** - * @var string + * @var non-empty-string */ - private $from; + private string $from; /** - * @var string + * @var non-empty-string */ - private $to; + private string $to; /** - * @var Chunk[] + * @var list */ - private $chunks; + private array $chunks; /** - * @param Chunk[] $chunks + * @param non-empty-string $from + * @param non-empty-string $to + * @param list $chunks */ public function __construct(string $from, string $to, array $chunks = []) { @@ -36,29 +45,40 @@ public function __construct(string $from, string $to, array $chunks = []) $this->chunks = $chunks; } - public function getFrom(): string + /** + * @return non-empty-string + */ + public function from(): string { return $this->from; } - public function getTo(): string + /** + * @return non-empty-string + */ + public function to(): string { return $this->to; } /** - * @return Chunk[] + * @return list */ - public function getChunks(): array + public function chunks(): array { return $this->chunks; } /** - * @param Chunk[] $chunks + * @param list $chunks */ public function setChunks(array $chunks): void { $this->chunks = $chunks; } + + public function getIterator(): Traversable + { + return new ArrayIterator($this->chunks); + } } diff --git a/app/vendor/sebastian/diff/src/Differ.php b/app/vendor/sebastian/diff/src/Differ.php index 98c7a9b29..44a4b4455 100644 --- a/app/vendor/sebastian/diff/src/Differ.php +++ b/app/vendor/sebastian/diff/src/Differ.php @@ -18,108 +18,53 @@ use function count; use function current; use function end; -use function get_class; -use function gettype; -use function is_array; -use function is_object; use function is_string; use function key; use function min; use function preg_split; use function prev; use function reset; -use function sprintf; +use function str_ends_with; use function substr; use SebastianBergmann\Diff\Output\DiffOutputBuilderInterface; -use SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder; final class Differ { - public const OLD = 0; - - public const ADDED = 1; - - public const REMOVED = 2; - - public const DIFF_LINE_END_WARNING = 3; - - public const NO_LINE_END_EOF_WARNING = 4; - - /** - * @var DiffOutputBuilderInterface - */ - private $outputBuilder; - - /** - * @param DiffOutputBuilderInterface $outputBuilder - * - * @throws InvalidArgumentException - */ - public function __construct($outputBuilder = null) + public const int OLD = 0; + public const int ADDED = 1; + public const int REMOVED = 2; + public const int DIFF_LINE_END_WARNING = 3; + public const int NO_LINE_END_EOF_WARNING = 4; + private DiffOutputBuilderInterface $outputBuilder; + + public function __construct(DiffOutputBuilderInterface $outputBuilder) { - if ($outputBuilder instanceof DiffOutputBuilderInterface) { - $this->outputBuilder = $outputBuilder; - } elseif (null === $outputBuilder) { - $this->outputBuilder = new UnifiedDiffOutputBuilder; - } elseif (is_string($outputBuilder)) { - // PHPUnit 6.1.4, 6.2.0, 6.2.1, 6.2.2, and 6.2.3 support - // @see https://github.com/sebastianbergmann/phpunit/issues/2734#issuecomment-314514056 - // @deprecated - $this->outputBuilder = new UnifiedDiffOutputBuilder($outputBuilder); - } else { - throw new InvalidArgumentException( - sprintf( - 'Expected builder to be an instance of DiffOutputBuilderInterface, or a string, got %s.', - is_object($outputBuilder) ? 'instance of "' . get_class($outputBuilder) . '"' : gettype($outputBuilder) . ' "' . $outputBuilder . '"' - ) - ); - } + $this->outputBuilder = $outputBuilder; } /** - * Returns the diff between two arrays or strings as string. - * - * @param array|string $from - * @param array|string $to + * @param list|string $from + * @param list|string $to */ - public function diff($from, $to, ?LongestCommonSubsequenceCalculator $lcs = null): string + public function diff(array|string $from, array|string $to, ?LongestCommonSubsequenceCalculator $lcs = null): string { - $diff = $this->diffToArray( - $this->normalizeDiffInput($from), - $this->normalizeDiffInput($to), - $lcs - ); + $diff = $this->diffToArray($from, $to, $lcs); return $this->outputBuilder->getDiff($diff); } /** - * Returns the diff between two arrays or strings as array. - * - * Each array element contains two elements: - * - [0] => mixed $token - * - [1] => 2|1|0 - * - * - 2: REMOVED: $token was removed from $from - * - 1: ADDED: $token was added to $from - * - 0: OLD: $token is not changed in $to - * - * @param array|string $from - * @param array|string $to - * @param LongestCommonSubsequenceCalculator $lcs + * @param list|string $from + * @param list|string $to */ - public function diffToArray($from, $to, ?LongestCommonSubsequenceCalculator $lcs = null): array + public function diffToArray(array|string $from, array|string $to, ?LongestCommonSubsequenceCalculator $lcs = null): array { if (is_string($from)) { $from = $this->splitStringByLines($from); - } elseif (!is_array($from)) { - throw new InvalidArgumentException('"from" must be an array or string.'); } if (is_string($to)) { $to = $this->splitStringByLines($to); - } elseif (!is_array($to)) { - throw new InvalidArgumentException('"to" must be an array or string.'); } [$from, $to, $start, $end] = self::getArrayDiffParted($from, $to); @@ -139,11 +84,11 @@ public function diffToArray($from, $to, ?LongestCommonSubsequenceCalculator $lcs reset($to); foreach ($common as $token) { - while (($fromToken = reset($from)) !== $token) { + while ((/* from-token */ reset($from)) !== $token) { $diff[] = [array_shift($from), self::REMOVED]; } - while (($toToken = reset($to)) !== $token) { + while ((/* to-token */ reset($to)) !== $token) { $diff[] = [array_shift($to), self::ADDED]; } @@ -172,23 +117,6 @@ public function diffToArray($from, $to, ?LongestCommonSubsequenceCalculator $lcs return $diff; } - /** - * Casts variable to string if it is not a string or array. - * - * @return array|string - */ - private function normalizeDiffInput($input) - { - if (!is_array($input) && !is_string($input)) { - return (string) $input; - } - - return $input; - } - - /** - * Checks if input is string, if so it will split it line-by-line. - */ private function splitStringByLines(string $input): array { return preg_split('/(.*\R)/', $input, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); @@ -209,21 +137,13 @@ private function selectLcsImplementation(array $from, array $to): LongestCommonS return new TimeEfficientLongestCommonSubsequenceCalculator; } - /** - * Calculates the estimated memory footprint for the DP-based method. - * - * @return float|int - */ - private function calculateEstimatedFootprint(array $from, array $to) + private function calculateEstimatedFootprint(array $from, array $to): int { $itemSize = PHP_INT_SIZE === 4 ? 76 : 144; return $itemSize * min(count($from), count($to)) ** 2; } - /** - * Returns true if line ends don't match in a diff. - */ private function detectUnmatchedLineEndings(array $diff): bool { $newLineBreaks = ['' => true]; @@ -246,7 +166,7 @@ private function detectUnmatchedLineEndings(array $diff): bool return false; } - // two way compare + // two-way compare foreach ($newLineBreaks as $break => $set) { if (!isset($oldLineBreaks[$break])) { return true; @@ -278,7 +198,7 @@ private function getLinebreak($line): string return ''; } - if ("\r\n" === substr($line, -2)) { + if (str_ends_with($line, "\r\n")) { return "\r\n"; } diff --git a/app/vendor/sebastian/diff/src/Exception/ConfigurationException.php b/app/vendor/sebastian/diff/src/Exception/ConfigurationException.php index 8847a2e58..4bd25d896 100644 --- a/app/vendor/sebastian/diff/src/Exception/ConfigurationException.php +++ b/app/vendor/sebastian/diff/src/Exception/ConfigurationException.php @@ -9,7 +9,6 @@ */ namespace SebastianBergmann\Diff; -use function get_class; use function gettype; use function is_object; use function sprintf; @@ -17,22 +16,17 @@ final class ConfigurationException extends InvalidArgumentException { - public function __construct( - string $option, - string $expected, - $value, - int $code = 0, - ?Exception $previous = null - ) { + public function __construct(string $option, string $expected, mixed $value, int $code = 0, ?Exception $previous = null) + { parent::__construct( sprintf( 'Option "%s" must be %s, got "%s".', $option, $expected, - is_object($value) ? get_class($value) : (null === $value ? '' : gettype($value) . '#' . $value) + is_object($value) ? $value::class : (null === $value ? '' : gettype($value) . '#' . $value), ), $code, - $previous + $previous, ); } } diff --git a/app/vendor/sebastian/diff/src/Line.php b/app/vendor/sebastian/diff/src/Line.php index 3596ed264..6576beeb8 100644 --- a/app/vendor/sebastian/diff/src/Line.php +++ b/app/vendor/sebastian/diff/src/Line.php @@ -11,21 +11,11 @@ final class Line { - public const ADDED = 1; - - public const REMOVED = 2; - - public const UNCHANGED = 3; - - /** - * @var int - */ - private $type; - - /** - * @var string - */ - private $content; + public const int ADDED = 1; + public const int REMOVED = 2; + public const int UNCHANGED = 3; + private int $type; + private string $content; public function __construct(int $type = self::UNCHANGED, string $content = '') { @@ -33,13 +23,28 @@ public function __construct(int $type = self::UNCHANGED, string $content = '') $this->content = $content; } - public function getContent(): string + public function content(): string { return $this->content; } - public function getType(): int + public function type(): int { return $this->type; } + + public function isAdded(): bool + { + return $this->type === self::ADDED; + } + + public function isRemoved(): bool + { + return $this->type === self::REMOVED; + } + + public function isUnchanged(): bool + { + return $this->type === self::UNCHANGED; + } } diff --git a/app/vendor/sebastian/diff/src/MemoryEfficientLongestCommonSubsequenceCalculator.php b/app/vendor/sebastian/diff/src/MemoryEfficientLongestCommonSubsequenceCalculator.php index 489113b6b..c9489230c 100644 --- a/app/vendor/sebastian/diff/src/MemoryEfficientLongestCommonSubsequenceCalculator.php +++ b/app/vendor/sebastian/diff/src/MemoryEfficientLongestCommonSubsequenceCalculator.php @@ -15,12 +15,11 @@ use function array_slice; use function count; use function in_array; -use function max; final class MemoryEfficientLongestCommonSubsequenceCalculator implements LongestCommonSubsequenceCalculator { /** - * {@inheritdoc} + * @inheritDoc */ public function calculate(array $from, array $to): array { @@ -61,7 +60,7 @@ public function calculate(array $from, array $to): array return array_merge( $this->calculate($fromStart, $toStart), - $this->calculate($fromEnd, $toEnd) + $this->calculate($fromEnd, $toEnd), ); } @@ -78,7 +77,11 @@ private function length(array $from, array $to): array if ($from[$i] === $to[$j]) { $current[$j + 1] = $prev[$j] + 1; } else { - // don't use max() to avoid function call overhead + /** + * @noinspection PhpConditionCanBeReplacedWithMinMaxCallInspection + * + * We do not use max() here to avoid the function call overhead + */ if ($current[$j] > $prev[$j + 1]) { $current[$j + 1] = $current[$j]; } else { diff --git a/app/vendor/sebastian/diff/src/Output/AbstractChunkOutputBuilder.php b/app/vendor/sebastian/diff/src/Output/AbstractChunkOutputBuilder.php index e55757c38..a4377d300 100644 --- a/app/vendor/sebastian/diff/src/Output/AbstractChunkOutputBuilder.php +++ b/app/vendor/sebastian/diff/src/Output/AbstractChunkOutputBuilder.php @@ -16,6 +16,8 @@ abstract class AbstractChunkOutputBuilder implements DiffOutputBuilderInterface /** * Takes input of the diff array and returns the common parts. * Iterates through diff line by line. + * + * @return array */ protected function getCommonChunks(array $diff, int $lineThreshold = 5): array { @@ -25,14 +27,14 @@ protected function getCommonChunks(array $diff, int $lineThreshold = 5): array $chunkSize = 0; $commonChunks = []; - for ($i = 0; $i < $diffSize; ++$i) { + for ($i = 0; $i < $diffSize; $i++) { if ($diff[$i][1] === 0 /* OLD */) { if ($capturing === false) { $capturing = true; $chunkStart = $i; $chunkSize = 0; } else { - ++$chunkSize; + $chunkSize++; } } elseif ($capturing !== false) { if ($chunkSize >= $lineThreshold) { diff --git a/app/vendor/sebastian/diff/src/Output/DiffOnlyOutputBuilder.php b/app/vendor/sebastian/diff/src/Output/DiffOnlyOutputBuilder.php index f79a935cb..7b5d3a730 100644 --- a/app/vendor/sebastian/diff/src/Output/DiffOnlyOutputBuilder.php +++ b/app/vendor/sebastian/diff/src/Output/DiffOnlyOutputBuilder.php @@ -9,9 +9,12 @@ */ namespace SebastianBergmann\Diff\Output; +use function assert; use function fclose; use function fopen; use function fwrite; +use function is_resource; +use function str_ends_with; use function stream_get_contents; use function substr; use SebastianBergmann\Diff\Differ; @@ -22,10 +25,7 @@ */ final class DiffOnlyOutputBuilder implements DiffOutputBuilderInterface { - /** - * @var string - */ - private $header; + private string $header; public function __construct(string $header = "--- Original\n+++ New\n") { @@ -36,10 +36,12 @@ public function getDiff(array $diff): string { $buffer = fopen('php://memory', 'r+b'); + assert(is_resource($buffer)); + if ('' !== $this->header) { fwrite($buffer, $this->header); - if ("\n" !== substr($this->header, -1, 1)) { + if (!str_ends_with($this->header, "\n")) { fwrite($buffer, "\n"); } } @@ -54,7 +56,7 @@ public function getDiff(array $diff): string continue; // Warnings should not be tested for line break, it will always be there } else { /* Not changed (old) 0 */ - continue; // we didn't write the non changs line, so do not add a line break either + continue; // we didn't write the not-changed line, so do not add a line break either } $lc = substr($diffEntry[0], -1); diff --git a/app/vendor/sebastian/diff/src/Output/StrictUnifiedDiffOutputBuilder.php b/app/vendor/sebastian/diff/src/Output/StrictUnifiedDiffOutputBuilder.php index 9c55ab2aa..5c496186c 100644 --- a/app/vendor/sebastian/diff/src/Output/StrictUnifiedDiffOutputBuilder.php +++ b/app/vendor/sebastian/diff/src/Output/StrictUnifiedDiffOutputBuilder.php @@ -11,12 +11,14 @@ use function array_merge; use function array_splice; +use function assert; use function count; use function fclose; use function fopen; use function fwrite; use function is_bool; use function is_int; +use function is_resource; use function is_string; use function max; use function min; @@ -33,7 +35,7 @@ */ final class StrictUnifiedDiffOutputBuilder implements DiffOutputBuilderInterface { - private static $default = [ + private static array $default = [ 'collapseRanges' => true, // ranges of length one are rendered with the trailing `,1` 'commonLineThreshold' => 6, // number of same lines before ending a new hunk and creating a new one (if needed) 'contextLines' => 3, // like `diff: -u, -U NUM, --unified[=NUM]`, for patch/git apply compatibility best to keep at least @ 3 @@ -42,31 +44,19 @@ final class StrictUnifiedDiffOutputBuilder implements DiffOutputBuilderInterface 'toFile' => null, 'toFileDate' => null, ]; + private bool $changed; + private bool $collapseRanges; /** - * @var bool + * @var positive-int */ - private $changed; + private int $commonLineThreshold; + private string $header; /** - * @var bool + * @var positive-int */ - private $collapseRanges; - - /** - * @var int >= 0 - */ - private $commonLineThreshold; - - /** - * @var string - */ - private $header; - - /** - * @var int >= 0 - */ - private $contextLines; + private int $contextLines; public function __construct(array $options = []) { @@ -94,7 +84,7 @@ public function __construct(array $options = []) $options['fromFile'], null === $options['fromFileDate'] ? '' : "\t" . $options['fromFileDate'], $options['toFile'], - null === $options['toFileDate'] ? '' : "\t" . $options['toFileDate'] + null === $options['toFileDate'] ? '' : "\t" . $options['toFileDate'], ); $this->collapseRanges = $options['collapseRanges']; @@ -111,6 +101,9 @@ public function getDiff(array $diff): string $this->changed = false; $buffer = fopen('php://memory', 'r+b'); + + assert(is_resource($buffer)); + fwrite($buffer, $this->header); $this->writeDiffHunks($buffer, $diff); @@ -148,10 +141,10 @@ private function writeDiffHunks($output, array $diff): void } } else { // search back for the last `+` and `-` line, - // check if has trailing linebreak, else add under it warning under it + // check if it has a trailing linebreak, else add a warning under it $toFind = [1 => true, 2 => true]; - for ($i = $upperLimit - 1; $i >= 0; --$i) { + for ($i = $upperLimit - 1; $i >= 0; $i--) { if (isset($toFind[$diff[$i][1]])) { unset($toFind[$diff[$i][1]]); $lc = substr($diff[$i][0], -1); @@ -179,15 +172,15 @@ private function writeDiffHunks($output, array $diff): void foreach ($diff as $i => $entry) { if (0 === $entry[1]) { // same if (false === $hunkCapture) { - ++$fromStart; - ++$toStart; + $fromStart++; + $toStart++; continue; } - ++$sameCount; - ++$toRange; - ++$fromRange; + $sameCount++; + $toRange++; + $fromRange++; if ($sameCount === $cutOff) { $contextStartOffset = ($hunkCapture - $this->contextLines) < 0 @@ -213,11 +206,11 @@ private function writeDiffHunks($output, array $diff): void $fromRange - $cutOff + $contextStartOffset + $this->contextLines, $toStart - $contextStartOffset, $toRange - $cutOff + $contextStartOffset + $this->contextLines, - $output + $output, ); $fromStart += $fromRange; - $toStart += $toRange; + $toStart += $toRange; $hunkCapture = false; $sameCount = $toRange = $fromRange = 0; @@ -239,11 +232,11 @@ private function writeDiffHunks($output, array $diff): void } if (Differ::ADDED === $entry[1]) { // added - ++$toRange; + $toRange++; } if (Differ::REMOVED === $entry[1]) { // removed - ++$fromRange; + $fromRange++; } } @@ -251,7 +244,7 @@ private function writeDiffHunks($output, array $diff): void return; } - // we end here when cutoff (commonLineThreshold) was not reached, but we where capturing a hunk, + // we end here when cutoff (commonLineThreshold) was not reached, but we were capturing a hunk, // do not render hunk till end automatically because the number of context lines might be less than the commonLineThreshold $contextStartOffset = $hunkCapture - $this->contextLines < 0 @@ -263,7 +256,7 @@ private function writeDiffHunks($output, array $diff): void $contextEndOffset = min($sameCount, $this->contextLines); $fromRange -= $sameCount; - $toRange -= $sameCount; + $toRange -= $sameCount; $this->writeHunk( $diff, @@ -273,7 +266,7 @@ private function writeDiffHunks($output, array $diff): void $fromRange + $contextStartOffset + $contextEndOffset, $toStart - $contextStartOffset, $toRange + $contextStartOffset + $contextEndOffset, - $output + $output, ); } @@ -301,7 +294,7 @@ private function writeHunk( fwrite($output, " @@\n"); - for ($i = $diffStartIndex; $i < $diffEndIndex; ++$i) { + for ($i = $diffStartIndex; $i < $diffEndIndex; $i++) { if ($diff[$i][1] === Differ::ADDED) { $this->changed = true; fwrite($output, '+' . $diff[$i][0]); @@ -314,11 +307,11 @@ private function writeHunk( $this->changed = true; fwrite($output, $diff[$i][0]); } - //} elseif ($diff[$i][1] === Differ::DIFF_LINE_END_WARNING) { // custom comment inserted by PHPUnit/diff package - // skip - //} else { - // unknown/invalid - //} + // } elseif ($diff[$i][1] === Differ::DIFF_LINE_END_WARNING) { // custom comment inserted by PHPUnit/diff package + // skip + // } else { + // unknown/invalid + // } } } diff --git a/app/vendor/sebastian/diff/src/Output/UnifiedDiffOutputBuilder.php b/app/vendor/sebastian/diff/src/Output/UnifiedDiffOutputBuilder.php index 8aae64504..d07ace6f0 100644 --- a/app/vendor/sebastian/diff/src/Output/UnifiedDiffOutputBuilder.php +++ b/app/vendor/sebastian/diff/src/Output/UnifiedDiffOutputBuilder.php @@ -10,14 +10,16 @@ namespace SebastianBergmann\Diff\Output; use function array_splice; +use function assert; use function count; use function fclose; use function fopen; use function fwrite; +use function is_resource; use function max; use function min; +use function str_ends_with; use function stream_get_contents; -use function strlen; use function substr; use SebastianBergmann\Diff\Differ; @@ -26,30 +28,15 @@ */ final class UnifiedDiffOutputBuilder extends AbstractChunkOutputBuilder { - /** - * @var bool - */ - private $collapseRanges = true; - - /** - * @var int >= 0 - */ - private $commonLineThreshold = 6; - - /** - * @var int >= 0 - */ - private $contextLines = 3; - - /** - * @var string - */ - private $header; + private bool $collapseRanges = true; + private int $commonLineThreshold = 6; /** - * @var bool + * @var positive-int */ - private $addLineNumbers; + private int $contextLines = 3; + private string $header; + private bool $addLineNumbers; public function __construct(string $header = "--- Original\n+++ New\n", bool $addLineNumbers = false) { @@ -61,10 +48,12 @@ public function getDiff(array $diff): string { $buffer = fopen('php://memory', 'r+b'); + assert(is_resource($buffer)); + if ('' !== $this->header) { fwrite($buffer, $this->header); - if ("\n" !== substr($this->header, -1, 1)) { + if (!str_ends_with($this->header, "\n")) { fwrite($buffer, "\n"); } } @@ -81,7 +70,7 @@ public function getDiff(array $diff): string // This might happen when both the `from` and `to` do not have a trailing linebreak $last = substr($diff, -1); - return 0 !== strlen($diff) && "\n" !== $last && "\r" !== $last + return '' !== $diff && "\n" !== $last && "\r" !== $last ? $diff . "\n" : $diff; } @@ -100,10 +89,10 @@ private function writeDiffHunks($output, array $diff): void } } else { // search back for the last `+` and `-` line, - // check if has trailing linebreak, else add under it warning under it + // check if it has trailing linebreak, else add a warning under it $toFind = [1 => true, 2 => true]; - for ($i = $upperLimit - 1; $i >= 0; --$i) { + for ($i = $upperLimit - 1; $i >= 0; $i--) { if (isset($toFind[$diff[$i][1]])) { unset($toFind[$diff[$i][1]]); $lc = substr($diff[$i][0], -1); @@ -123,7 +112,7 @@ private function writeDiffHunks($output, array $diff): void $cutOff = max($this->commonLineThreshold, $this->contextLines); $hunkCapture = false; - $sameCount = $toRange = $fromRange = 0; + $sameCount = $toRange = $fromRange = 0; $toStart = $fromStart = 1; $i = 0; @@ -131,15 +120,15 @@ private function writeDiffHunks($output, array $diff): void foreach ($diff as $i => $entry) { if (0 === $entry[1]) { // same if (false === $hunkCapture) { - ++$fromStart; - ++$toStart; + $fromStart++; + $toStart++; continue; } - ++$sameCount; - ++$toRange; - ++$fromRange; + $sameCount++; + $toRange++; + $fromRange++; if ($sameCount === $cutOff) { $contextStartOffset = ($hunkCapture - $this->contextLines) < 0 @@ -165,11 +154,11 @@ private function writeDiffHunks($output, array $diff): void $fromRange - $cutOff + $contextStartOffset + $this->contextLines, $toStart - $contextStartOffset, $toRange - $cutOff + $contextStartOffset + $this->contextLines, - $output + $output, ); $fromStart += $fromRange; - $toStart += $toRange; + $toStart += $toRange; $hunkCapture = false; $sameCount = $toRange = $fromRange = 0; @@ -189,11 +178,11 @@ private function writeDiffHunks($output, array $diff): void } if (Differ::ADDED === $entry[1]) { - ++$toRange; + $toRange++; } if (Differ::REMOVED === $entry[1]) { - ++$fromRange; + $fromRange++; } } @@ -201,7 +190,7 @@ private function writeDiffHunks($output, array $diff): void return; } - // we end here when cutoff (commonLineThreshold) was not reached, but we where capturing a hunk, + // we end here when cutoff (commonLineThreshold) was not reached, but we were capturing a hunk, // do not render hunk till end automatically because the number of context lines might be less than the commonLineThreshold $contextStartOffset = $hunkCapture - $this->contextLines < 0 @@ -213,7 +202,7 @@ private function writeDiffHunks($output, array $diff): void $contextEndOffset = min($sameCount, $this->contextLines); $fromRange -= $sameCount; - $toRange -= $sameCount; + $toRange -= $sameCount; $this->writeHunk( $diff, @@ -223,7 +212,7 @@ private function writeDiffHunks($output, array $diff): void $fromRange + $contextStartOffset + $contextEndOffset, $toStart - $contextStartOffset, $toRange + $contextStartOffset + $contextEndOffset, - $output + $output, ); } @@ -255,7 +244,7 @@ private function writeHunk( fwrite($output, "@@ @@\n"); } - for ($i = $diffStartIndex; $i < $diffEndIndex; ++$i) { + for ($i = $diffStartIndex; $i < $diffEndIndex; $i++) { if ($diff[$i][1] === Differ::ADDED) { fwrite($output, '+' . $diff[$i][0]); } elseif ($diff[$i][1] === Differ::REMOVED) { diff --git a/app/vendor/sebastian/diff/src/Parser.php b/app/vendor/sebastian/diff/src/Parser.php index cc9e38871..e63e690c1 100644 --- a/app/vendor/sebastian/diff/src/Parser.php +++ b/app/vendor/sebastian/diff/src/Parser.php @@ -9,7 +9,9 @@ */ namespace SebastianBergmann\Diff; +use const PREG_UNMATCHED_AS_NULL; use function array_pop; +use function assert; use function count; use function max; use function preg_match; @@ -36,7 +38,7 @@ public function parse(string $string): array $diff = null; $collected = []; - for ($i = 0; $i < $lineCount; ++$i) { + for ($i = 0; $i < $lineCount; $i++) { if (preg_match('#^---\h+"?(?P[^\\v\\t"]+)#', $lines[$i], $fromMatch) && preg_match('#^\\+\\+\\+\\h+"?(?P[^\\v\\t"]+)#', $lines[$i + 1], $toMatch)) { if ($diff !== null) { @@ -46,11 +48,14 @@ public function parse(string $string): array $collected = []; } + assert(!empty($fromMatch['file'])); + assert(!empty($toMatch['file'])); + $diff = new Diff($fromMatch['file'], $toMatch['file']); - ++$i; + $i++; } else { - if (preg_match('/^(?:diff --git |index [\da-f\.]+|[+-]{3} [ab])/', $lines[$i])) { + if (preg_match('/^(?:diff --git |index [\da-f.]+|[+-]{3} [ab])/', $lines[$i])) { continue; } @@ -67,6 +72,9 @@ public function parse(string $string): array return $diffs; } + /** + * @param string[] $lines + */ private function parseFileDiff(Diff $diff, array $lines): void { $chunks = []; @@ -74,12 +82,12 @@ private function parseFileDiff(Diff $diff, array $lines): void $diffLines = []; foreach ($lines as $line) { - if (preg_match('/^@@\s+-(?P\d+)(?:,\s*(?P\d+))?\s+\+(?P\d+)(?:,\s*(?P\d+))?\s+@@/', $line, $match)) { + if (preg_match('/^@@\s+-(?P\d+)(?:,\s*(?P\d+))?\s+\+(?P\d+)(?:,\s*(?P\d+))?\s+@@/', $line, $match, PREG_UNMATCHED_AS_NULL)) { $chunk = new Chunk( (int) $match['start'], - isset($match['startrange']) ? max(1, (int) $match['startrange']) : 1, + isset($match['startrange']) ? max(0, (int) $match['startrange']) : 1, (int) $match['end'], - isset($match['endrange']) ? max(1, (int) $match['endrange']) : 1 + isset($match['endrange']) ? max(0, (int) $match['endrange']) : 1, ); $chunks[] = $chunk; @@ -99,9 +107,7 @@ private function parseFileDiff(Diff $diff, array $lines): void $diffLines[] = new Line($type, $match['line']); - if (null !== $chunk) { - $chunk->setLines($diffLines); - } + $chunk?->setLines($diffLines); } } diff --git a/app/vendor/sebastian/diff/src/TimeEfficientLongestCommonSubsequenceCalculator.php b/app/vendor/sebastian/diff/src/TimeEfficientLongestCommonSubsequenceCalculator.php index 4e8d951d4..98e856d53 100644 --- a/app/vendor/sebastian/diff/src/TimeEfficientLongestCommonSubsequenceCalculator.php +++ b/app/vendor/sebastian/diff/src/TimeEfficientLongestCommonSubsequenceCalculator.php @@ -11,13 +11,12 @@ use function array_reverse; use function count; -use function max; use SplFixedArray; final class TimeEfficientLongestCommonSubsequenceCalculator implements LongestCommonSubsequenceCalculator { /** - * {@inheritdoc} + * @inheritDoc */ public function calculate(array $from, array $to): array { @@ -27,16 +26,16 @@ public function calculate(array $from, array $to): array $width = $fromLength + 1; $matrix = new SplFixedArray($width * ($toLength + 1)); - for ($i = 0; $i <= $fromLength; ++$i) { + for ($i = 0; $i <= $fromLength; $i++) { $matrix[$i] = 0; } - for ($j = 0; $j <= $toLength; ++$j) { + for ($j = 0; $j <= $toLength; $j++) { $matrix[$j * $width] = 0; } - for ($i = 1; $i <= $fromLength; ++$i) { - for ($j = 1; $j <= $toLength; ++$j) { + for ($i = 1; $i <= $fromLength; $i++) { + for ($j = 1; $j <= $toLength; $j++) { $o = ($j * $width) + $i; // don't use max() to avoid function call overhead @@ -64,15 +63,15 @@ public function calculate(array $from, array $to): array while ($i > 0 && $j > 0) { if ($from[$i - 1] === $to[$j - 1]) { $common[] = $from[$i - 1]; - --$i; - --$j; + $i--; + $j--; } else { $o = ($j * $width) + $i; if ($matrix[$o - $width] > $matrix[$o - 1]) { - --$j; + $j--; } else { - --$i; + $i--; } } } diff --git a/app/vendor/sebastian/environment/ChangeLog.md b/app/vendor/sebastian/environment/ChangeLog.md index 073659514..1eee0cb4e 100644 --- a/app/vendor/sebastian/environment/ChangeLog.md +++ b/app/vendor/sebastian/environment/ChangeLog.md @@ -2,7 +2,89 @@ All notable changes in `sebastianbergmann/environment` are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles. -## [5.1.5] - 2023-02-03 +## [8.0.3] - 2025-08-12 + +### Changed + +* [#75](https://github.com/sebastianbergmann/environment/pull/75): Make `Runtime::isOpcacheActive()` public + +## [8.0.2] - 2025-05-21 + +### Fixed + +* [#74](https://github.com/sebastianbergmann/environment/pull/74): Regression introduced in version 8.0.0 + +## [8.0.1] - 2025-05-21 + +### Fixed + +* Take Xdebug mode into account for `Runtime::canCollectCodeCoverage()` + +## [8.0.0] - 2025-02-07 + +### Removed + +* This component is no longer supported on PHP 8.2 + +## [7.2.1] - 2025-05-21 + +### Fixed + +* Take Xdebug mode into account for `Runtime::canCollectCodeCoverage()` + +## [7.2.0] - 2024-07-03 + +### Changed + +* Synced `Console::hasColorSupport()` with Symfony's `StreamOutput::hasColorSupport()` implementation +* Removed code left over from a time before PHP 5.4 and when HHVM was still supported +* This project now uses PHPStan instead of Psalm for static analysis + +### Deprecated + +* The `Runtime::getBinary()` method is now deprecated, use `escapeshellarg(PHP_BINARY)` instead +* The `Runtime::getRawBinary()` method is now deprecated, use the `PHP_BINARY` constant instead + +## [7.1.0] - 2024-03-23 + +### Added + +* [#72](https://github.com/sebastianbergmann/environment/pull/72): `Runtime::getRawBinary()` + +## [7.0.0] - 2024-02-02 + +### Removed + +* This component is no longer supported on PHP 8.1 + +## [6.1.1] - 2024-MM-DD + +### Changed + +* Synced `Console::hasColorSupport()` with Symfony's `StreamOutput::hasColorSupport()` implementation + +## [6.1.0] - 2024-03-23 + +### Added + +* [#72](https://github.com/sebastianbergmann/environment/pull/72): `Runtime::getRawBinary()` + +## [6.0.1] - 2023-04-11 + +### Fixed + +* [#68](https://github.com/sebastianbergmann/environment/pull/68): The Just-in-Time compiler is disabled when `opcache.jit_buffer_size` is set to `0` +* [#70](https://github.com/sebastianbergmann/environment/pull/70): The first `0` of `opcache.jit` only disables CPU-specific optimizations, not the Just-in-Time compiler itself + +## [6.0.0] - 2023-02-03 + +### Removed + +* Removed `SebastianBergmann\Environment\OperatingSystem::getFamily()` because this component is no longer supported on PHP versions that do not have `PHP_OS_FAMILY` +* Removed `SebastianBergmann\Environment\Runtime::isHHVM()` +* This component is no longer supported on PHP 7.3, PHP 7.4, and PHP 8.0 + +## [5.1.5] - 2022-MM-DD ### Fixed @@ -157,6 +239,18 @@ All notable changes in `sebastianbergmann/environment` are documented in this fi * This component is no longer supported on PHP 5.6 +[8.0.3]: https://github.com/sebastianbergmann/environment/compare/8.0.2...8.0.3 +[8.0.2]: https://github.com/sebastianbergmann/environment/compare/8.0.1...8.0.2 +[8.0.1]: https://github.com/sebastianbergmann/environment/compare/8.0.0...8.0.1 +[8.0.0]: https://github.com/sebastianbergmann/environment/compare/7.2...8.0.0 +[7.2.1]: https://github.com/sebastianbergmann/environment/compare/7.2.0...7.2.1 +[7.2.0]: https://github.com/sebastianbergmann/environment/compare/7.1.0...7.2.0 +[7.1.0]: https://github.com/sebastianbergmann/environment/compare/7.0.0...7.1.0 +[7.0.0]: https://github.com/sebastianbergmann/environment/compare/6.1...7.0.0 +[6.1.1]: https://github.com/sebastianbergmann/environment/compare/6.1.0...6.1 +[6.1.0]: https://github.com/sebastianbergmann/environment/compare/6.0.1...6.1.0 +[6.0.1]: https://github.com/sebastianbergmann/environment/compare/6.0.0...6.0.1 +[6.0.0]: https://github.com/sebastianbergmann/environment/compare/5.1.5...6.0.0 [5.1.5]: https://github.com/sebastianbergmann/environment/compare/5.1.4...5.1.5 [5.1.4]: https://github.com/sebastianbergmann/environment/compare/5.1.3...5.1.4 [5.1.3]: https://github.com/sebastianbergmann/environment/compare/5.1.2...5.1.3 diff --git a/app/vendor/sebastian/environment/LICENSE b/app/vendor/sebastian/environment/LICENSE index 42546339c..845fd0b12 100644 --- a/app/vendor/sebastian/environment/LICENSE +++ b/app/vendor/sebastian/environment/LICENSE @@ -1,33 +1,29 @@ -sebastian/environment +BSD 3-Clause License -Copyright (c) 2014-2022, Sebastian Bergmann . +Copyright (c) 2014-2025, Sebastian Bergmann All rights reserved. Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: +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. +1. 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. +2. 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 Sebastian Bergmann nor the names of his - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. +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/app/vendor/sebastian/environment/README.md b/app/vendor/sebastian/environment/README.md index 1fead13ef..f91287e31 100644 --- a/app/vendor/sebastian/environment/README.md +++ b/app/vendor/sebastian/environment/README.md @@ -1,8 +1,8 @@ -# sebastian/environment - -[![Latest Stable Version](https://img.shields.io/packagist/v/sebastian/environment.svg?style=flat-square)](https://packagist.org/packages/sebastian/environment) +[![Latest Stable Version](https://poser.pugx.org/sebastian/environment/v)](https://packagist.org/packages/sebastian/environment) [![CI Status](https://github.com/sebastianbergmann/environment/workflows/CI/badge.svg)](https://github.com/sebastianbergmann/environment/actions) -[![Type Coverage](https://shepherd.dev/github/sebastianbergmann/environment/coverage.svg)](https://shepherd.dev/github/sebastianbergmann/environment) +[![codecov](https://codecov.io/gh/sebastianbergmann/environment/branch/main/graph/badge.svg)](https://codecov.io/gh/sebastianbergmann/environment) + +# sebastian/environment This component provides functionality that helps writing PHP code that has runtime-specific (PHP / HHVM) execution paths. diff --git a/app/vendor/sebastian/environment/SECURITY.md b/app/vendor/sebastian/environment/SECURITY.md new file mode 100644 index 000000000..d88ff0019 --- /dev/null +++ b/app/vendor/sebastian/environment/SECURITY.md @@ -0,0 +1,30 @@ +# Security Policy + +If you believe you have found a security vulnerability in the library that is developed in this repository, please report it to us through coordinated disclosure. + +**Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.** + +Instead, please email `sebastian@phpunit.de`. + +Please include as much of the information listed below as you can to help us better understand and resolve the issue: + +* The type of issue +* Full paths of source file(s) related to the manifestation of the issue +* The location of the affected source code (tag/branch/commit or direct URL) +* Any special configuration required to reproduce the issue +* Step-by-step instructions to reproduce the issue +* Proof-of-concept or exploit code (if possible) +* Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +## Web Context + +The library that is developed in this repository was either extracted from [PHPUnit](https://github.com/sebastianbergmann/phpunit) or developed specifically as a dependency for PHPUnit. + +The library is developed with a focus on development environments and the command-line. No specific testing or hardening with regard to using the library in an HTTP or web context or with untrusted input data is performed. The library might also contain functionality that intentionally exposes internal application data for debugging purposes. + +If the library is used in a web application, the application developer is responsible for filtering inputs or escaping outputs as necessary and for verifying that the used functionality is safe for use within the intended context. + +Vulnerabilities specific to the use outside a development context will be fixed as applicable, provided that the fix does not have an averse effect on the primary use case for development purposes. + diff --git a/app/vendor/sebastian/environment/composer.json b/app/vendor/sebastian/environment/composer.json index d50dcfd78..f7534d7b5 100644 --- a/app/vendor/sebastian/environment/composer.json +++ b/app/vendor/sebastian/environment/composer.json @@ -2,7 +2,7 @@ "name": "sebastian/environment", "description": "Provides functionality to handle HHVM/PHP environments", "keywords": ["environment","hhvm","xdebug"], - "homepage": "http://www.github.com/sebastianbergmann/environment", + "homepage": "https://github.com/sebastianbergmann/environment", "license": "BSD-3-Clause", "authors": [ { @@ -10,19 +10,23 @@ "email": "sebastian@phpunit.de" } ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "security": "https://github.com/sebastianbergmann/environment/security/policy" + }, "config": { "platform": { - "php": "7.3.0" + "php": "8.3.0" }, "optimize-autoloader": true, "sort-packages": true }, "prefer-stable": true, "require": { - "php": ">=7.3" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^12.0" }, "suggest": { "ext-posix": "*" @@ -34,7 +38,7 @@ }, "extra": { "branch-alias": { - "dev-master": "5.1-dev" + "dev-main": "8.0-dev" } } } diff --git a/app/vendor/sebastian/environment/src/Console.php b/app/vendor/sebastian/environment/src/Console.php index 180eb60bf..525c19391 100644 --- a/app/vendor/sebastian/environment/src/Console.php +++ b/app/vendor/sebastian/environment/src/Console.php @@ -12,11 +12,15 @@ use const DIRECTORY_SEPARATOR; use const STDIN; use const STDOUT; +use function assert; use function defined; use function fclose; use function fstat; use function function_exists; use function getenv; +use function in_array; +use function is_array; +use function is_int; use function is_resource; use function is_string; use function posix_isatty; @@ -27,6 +31,7 @@ use function shell_exec; use function stream_get_contents; use function stream_isatty; +use function strtoupper; use function trim; final class Console @@ -34,17 +39,17 @@ final class Console /** * @var int */ - public const STDIN = 0; + public const int STDIN = 0; /** * @var int */ - public const STDOUT = 1; + public const int STDOUT = 1; /** * @var int */ - public const STDERR = 2; + public const int STDERR = 2; /** * Returns true if STDOUT supports colorization. @@ -54,26 +59,37 @@ final class Console */ public function hasColorSupport(): bool { - if ('Hyper' === getenv('TERM_PROGRAM')) { + if (!defined('STDOUT')) { + return false; + } + + if (isset($_SERVER['NO_COLOR']) || false !== getenv('NO_COLOR')) { + return false; + } + + if (!@stream_isatty(STDOUT) && + !in_array(strtoupper((string) getenv('MSYSTEM')), ['MINGW32', 'MINGW64'], true)) { + return false; + } + + if ($this->isWindows() && + function_exists('sapi_windows_vt100_support') && + @sapi_windows_vt100_support(STDOUT)) { return true; } - if ($this->isWindows()) { - // @codeCoverageIgnoreStart - return (defined('STDOUT') && function_exists('sapi_windows_vt100_support') && @sapi_windows_vt100_support(STDOUT)) || - false !== getenv('ANSICON') || - 'ON' === getenv('ConEmuANSI') || - 'xterm' === getenv('TERM'); - // @codeCoverageIgnoreEnd + if ('Hyper' === getenv('TERM_PROGRAM') || + false !== getenv('COLORTERM') || + false !== getenv('ANSICON') || + 'ON' === getenv('ConEmuANSI')) { + return true; } - if (!defined('STDOUT')) { - // @codeCoverageIgnoreStart + if ('dumb' === $term = (string) getenv('TERM')) { return false; - // @codeCoverageIgnoreEnd } - return $this->isInteractive(STDOUT); + return (bool) preg_match('/^((screen|xterm|vt100|vt220|putty|rxvt|ansi|cygwin|linux).*)|(.*-256(color)?(-bce)?)$/', $term); } /** @@ -97,13 +113,15 @@ public function getNumberOfColumns(): int /** * Returns if the file descriptor is an interactive terminal or not. * - * Normally, we want to use a resource as a parameter, yet sadly it's not always awailable, + * Normally, we want to use a resource as a parameter, yet sadly it's not always available, * eg when running code in interactive console (`php -a`), STDIN/STDOUT/STDERR constants are not defined. * * @param int|resource $fileDescriptor */ - public function isInteractive($fileDescriptor = self::STDOUT): bool + public function isInteractive(mixed $fileDescriptor = self::STDOUT): bool { + assert(is_int($fileDescriptor) || is_resource($fileDescriptor)); + if (is_resource($fileDescriptor)) { if (function_exists('stream_isatty') && @stream_isatty($fileDescriptor)) { return true; @@ -112,7 +130,7 @@ public function isInteractive($fileDescriptor = self::STDOUT): bool if (function_exists('fstat')) { $stat = @fstat(STDOUT); - return $stat && 0020000 === ($stat['mode'] & 0170000); + return $stat !== false && 0o020000 === ($stat['mode'] & 0o170000); } return false; @@ -131,15 +149,29 @@ private function isWindows(): bool */ private function getNumberOfColumnsInteractive(): int { - if (function_exists('shell_exec') && preg_match('#\d+ (\d+)#', shell_exec('stty size') ?: '', $match) === 1) { - if ((int) $match[1] > 0) { - return (int) $match[1]; + if (function_exists('shell_exec')) { + $stty = shell_exec('stty size'); + + if ($stty === false || $stty === null) { + $stty = ''; + } + + if (preg_match('#\d+ (\d+)#', $stty, $match) === 1) { + if ((int) $match[1] > 0) { + return (int) $match[1]; + } + } + + $stty = shell_exec('stty'); + + if ($stty === false || $stty === null) { + $stty = ''; } - } - if (function_exists('shell_exec') && preg_match('#columns = (\d+);#', shell_exec('stty') ?: '', $match) === 1) { - if ((int) $match[1] > 0) { - return (int) $match[1]; + if (preg_match('#columns = (\d+);#', $stty, $match) === 1) { + if ((int) $match[1] > 0) { + return (int) $match[1]; + } } } @@ -154,6 +186,7 @@ private function getNumberOfColumnsWindows(): int $ansicon = getenv('ANSICON'); $columns = 80; + /** @phpstan-ignore booleanAnd.rightNotBoolean */ if (is_string($ansicon) && preg_match('/^(\d+)x\d+ \(\d+x(\d+)\)$/', trim($ansicon), $matches)) { $columns = (int) $matches[1]; } elseif (function_exists('proc_open')) { @@ -166,9 +199,13 @@ private function getNumberOfColumnsWindows(): int $pipes, null, null, - ['suppress_errors' => true] + ['suppress_errors' => true], ); + assert(is_array($pipes)); + assert(isset($pipes[1]) && is_resource($pipes[1])); + assert(isset($pipes[2]) && is_resource($pipes[2])); + if (is_resource($process)) { $info = stream_get_contents($pipes[1]); @@ -176,7 +213,8 @@ private function getNumberOfColumnsWindows(): int fclose($pipes[2]); proc_close($process); - if (preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) { + /** @phpstan-ignore if.condNotBoolean */ + if (preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', (string) $info, $matches)) { $columns = (int) $matches[2]; } } diff --git a/app/vendor/sebastian/environment/src/OperatingSystem.php b/app/vendor/sebastian/environment/src/OperatingSystem.php deleted file mode 100644 index 1f3ebca7c..000000000 --- a/app/vendor/sebastian/environment/src/OperatingSystem.php +++ /dev/null @@ -1,53 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace SebastianBergmann\Environment; - -use const DIRECTORY_SEPARATOR; -use const PHP_OS; -use const PHP_OS_FAMILY; -use function defined; - -final class OperatingSystem -{ - /** - * Returns PHP_OS_FAMILY (if defined (which it is on PHP >= 7.2)). - * Returns a string (compatible with PHP_OS_FAMILY) derived from PHP_OS otherwise. - */ - public function getFamily(): string - { - if (defined('PHP_OS_FAMILY')) { - return PHP_OS_FAMILY; - } - - if (DIRECTORY_SEPARATOR === '\\') { - return 'Windows'; - } - - switch (PHP_OS) { - case 'Darwin': - return 'Darwin'; - - case 'DragonFly': - case 'FreeBSD': - case 'NetBSD': - case 'OpenBSD': - return 'BSD'; - - case 'Linux': - return 'Linux'; - - case 'SunOS': - return 'Solaris'; - - default: - return 'Unknown'; - } - } -} diff --git a/app/vendor/sebastian/environment/src/Runtime.php b/app/vendor/sebastian/environment/src/Runtime.php index d1b92d620..e272f01a8 100644 --- a/app/vendor/sebastian/environment/src/Runtime.php +++ b/app/vendor/sebastian/environment/src/Runtime.php @@ -10,43 +10,63 @@ namespace SebastianBergmann\Environment; use const PHP_BINARY; -use const PHP_BINDIR; -use const PHP_MAJOR_VERSION; use const PHP_SAPI; use const PHP_VERSION; use function array_map; use function array_merge; -use function defined; +use function assert; use function escapeshellarg; use function explode; use function extension_loaded; -use function getenv; +use function in_array; use function ini_get; -use function is_readable; +use function is_array; use function parse_ini_file; use function php_ini_loaded_file; use function php_ini_scanned_files; use function phpversion; use function sprintf; -use function strpos; +use function strrpos; +use function version_compare; +use function xdebug_info; -/** - * Utility class for HHVM/PHP environment handling. - */ final class Runtime { - /** - * @var string - */ - private static $binary; - /** * Returns true when Xdebug or PCOV is available or * the runtime used is PHPDBG. */ public function canCollectCodeCoverage(): bool { - return $this->hasXdebug() || $this->hasPCOV() || $this->hasPHPDBGCodeCoverage(); + if ($this->hasPHPDBGCodeCoverage()) { + return true; + } + + if ($this->hasPCOV()) { + return true; + } + + if (!$this->hasXdebug()) { + return false; + } + + $xdebugVersion = phpversion('xdebug'); + + assert($xdebugVersion !== false); + + if (version_compare($xdebugVersion, '3', '<')) { + return true; + } + + $xdebugMode = xdebug_info('mode'); + + assert(is_array($xdebugMode)); + + if (in_array('coverage', $xdebugMode, true)) { + return true; + } + + return false; } /** @@ -72,15 +92,21 @@ public function discardsComments(): bool */ public function performsJustInTimeCompilation(): bool { - if (PHP_MAJOR_VERSION < 8) { + if (!$this->isOpcacheActive()) { return false; } - if (!$this->isOpcacheActive()) { + if (ini_get('opcache.jit_buffer_size') === '0') { + return false; + } + + $jit = (string) ini_get('opcache.jit'); + + if (($jit === 'disable') || ($jit === 'off')) { return false; } - if (strpos(ini_get('opcache.jit'), '0') === 0) { + if (strrpos($jit, '0') === 3) { return false; } @@ -88,52 +114,23 @@ public function performsJustInTimeCompilation(): bool } /** - * Returns the path to the binary of the current runtime. - * Appends ' --php' to the path when the runtime is HHVM. + * Returns the raw path to the binary of the current runtime. + * + * @deprecated */ - public function getBinary(): string + public function getRawBinary(): string { - // HHVM - if (self::$binary === null && $this->isHHVM()) { - // @codeCoverageIgnoreStart - if ((self::$binary = getenv('PHP_BINARY')) === false) { - self::$binary = PHP_BINARY; - } - - self::$binary = escapeshellarg(self::$binary) . ' --php' . - ' -d hhvm.php7.all=1'; - // @codeCoverageIgnoreEnd - } - - if (self::$binary === null && PHP_BINARY !== '') { - self::$binary = escapeshellarg(PHP_BINARY); - } - - if (self::$binary === null) { - // @codeCoverageIgnoreStart - $possibleBinaryLocations = [ - PHP_BINDIR . '/php', - PHP_BINDIR . '/php-cli.exe', - PHP_BINDIR . '/php.exe', - ]; - - foreach ($possibleBinaryLocations as $binary) { - if (is_readable($binary)) { - self::$binary = escapeshellarg($binary); - - break; - } - } - // @codeCoverageIgnoreEnd - } - - if (self::$binary === null) { - // @codeCoverageIgnoreStart - self::$binary = 'php'; - // @codeCoverageIgnoreEnd - } + return PHP_BINARY; + } - return self::$binary; + /** + * Returns the escaped path to the binary of the current runtime. + * + * @deprecated + */ + public function getBinary(): string + { + return escapeshellarg(PHP_BINARY); } public function getNameWithVersion(): string @@ -143,15 +140,11 @@ public function getNameWithVersion(): string public function getNameWithVersionAndCodeCoverageDriver(): string { - if (!$this->canCollectCodeCoverage() || $this->hasPHPDBGCodeCoverage()) { - return $this->getNameWithVersion(); - } - if ($this->hasPCOV()) { return sprintf( '%s with PCOV %s', $this->getNameWithVersion(), - phpversion('pcov') + phpversion('pcov'), ); } @@ -159,19 +152,15 @@ public function getNameWithVersionAndCodeCoverageDriver(): string return sprintf( '%s with Xdebug %s', $this->getNameWithVersion(), - phpversion('xdebug') + phpversion('xdebug'), ); } + + return $this->getNameWithVersion(); } public function getName(): string { - if ($this->isHHVM()) { - // @codeCoverageIgnoreStart - return 'HHVM'; - // @codeCoverageIgnoreEnd - } - if ($this->isPHPDBG()) { // @codeCoverageIgnoreStart return 'PHPDBG'; @@ -183,23 +172,11 @@ public function getName(): string public function getVendorUrl(): string { - if ($this->isHHVM()) { - // @codeCoverageIgnoreStart - return 'http://hhvm.com/'; - // @codeCoverageIgnoreEnd - } - - return 'https://secure.php.net/'; + return 'https://www.php.net/'; } public function getVersion(): string { - if ($this->isHHVM()) { - // @codeCoverageIgnoreStart - return HHVM_VERSION; - // @codeCoverageIgnoreEnd - } - return PHP_VERSION; } @@ -208,15 +185,7 @@ public function getVersion(): string */ public function hasXdebug(): bool { - return ($this->isPHP() || $this->isHHVM()) && extension_loaded('xdebug'); - } - - /** - * Returns true when the runtime used is HHVM. - */ - public function isHHVM(): bool - { - return defined('HHVM_VERSION'); + return $this->isPHP() && extension_loaded('xdebug'); } /** @@ -224,7 +193,7 @@ public function isHHVM(): bool */ public function isPHP(): bool { - return !$this->isHHVM() && !$this->isPHPDBG(); + return !$this->isPHPDBG(); } /** @@ -232,7 +201,7 @@ public function isPHP(): bool */ public function isPHPDBG(): bool { - return PHP_SAPI === 'phpdbg' && !$this->isHHVM(); + return PHP_SAPI === 'phpdbg'; } /** @@ -249,7 +218,7 @@ public function hasPHPDBGCodeCoverage(): bool */ public function hasPCOV(): bool { - return $this->isPHP() && extension_loaded('pcov') && ini_get('pcov.enabled'); + return $this->isPHP() && extension_loaded('pcov') && ini_get('pcov.enabled') === '1'; } /** @@ -262,24 +231,30 @@ public function hasPCOV(): bool * where each string has the format `key=value` denoting * the name of a changed php.ini setting with its new value. * - * @return string[] + * @param list $values + * + * @return array */ public function getCurrentSettings(array $values): array { $diff = []; $files = []; - if ($file = php_ini_loaded_file()) { + $file = php_ini_loaded_file(); + + if ($file !== false) { $files[] = $file; } - if ($scanned = php_ini_scanned_files()) { + $scanned = php_ini_scanned_files(); + + if ($scanned !== false) { $files = array_merge( $files, array_map( 'trim', - explode(",\n", $scanned) - ) + explode(",\n", $scanned), + ), ); } @@ -289,7 +264,7 @@ public function getCurrentSettings(array $values): array foreach ($values as $value) { $set = ini_get($value); - if (empty($set)) { + if ($set === false || $set === '') { continue; } @@ -302,7 +277,7 @@ public function getCurrentSettings(array $values): array return $diff; } - private function isOpcacheActive(): bool + public function isOpcacheActive(): bool { if (!extension_loaded('Zend OPcache')) { return false; diff --git a/app/vendor/sebastian/exporter/ChangeLog.md b/app/vendor/sebastian/exporter/ChangeLog.md index 32ab7051f..f437a2ebe 100644 --- a/app/vendor/sebastian/exporter/ChangeLog.md +++ b/app/vendor/sebastian/exporter/ChangeLog.md @@ -2,12 +2,124 @@ All notable changes are documented in this file using the [Keep a CHANGELOG](https://keepachangelog.com/) principles. -## [4.0.6] - 2024-03-02 +## [7.0.0] - 2025-02-07 + +### Removed + +* This component is no longer supported on PHP 8.2 + +## [6.3.0] - 2024-12-05 + +### Added + +* Optional constructor argument to control maximum string length + +### Deprecated + +* Optional argument for `shortenedRecursiveExport()` and `shortenedExport()` to control maximum string length + +## [6.2.0] - 2024-12-05 + +### Added + +* [#67](https://github.com/sebastianbergmann/exporter/issues/67): Optional argument for `shortenedRecursiveExport()` and `shortenedExport()` to control maximum string length + +### Changed + +* [#69](https://github.com/sebastianbergmann/exporter/pull/69): Do not initialize lazy objects during export + +## [6.1.3] - 2024-07-03 + +### Changed + +* [#66](https://github.com/sebastianbergmann/exporter/pull/66): Avoid using the Reflection API for some classes +* This project now uses PHPStan instead of Psalm for static analysis + +## [6.1.2] - 2024-06-18 + +### Changed + +* [#64](https://github.com/sebastianbergmann/exporter/pull/64): Improve performance of `Exporter::exportString()` +* [#65](https://github.com/sebastianbergmann/exporter/pull/65): Prevent unnecessary calls to `str_repeat()` + +### Fixed + +* [#62](https://github.com/sebastianbergmann/exporter/issues/62): Do not limit export of arrays by default (to restore BC with versions prior to 6.1.0) + +## [6.1.1] - 2024-06-18 + +### Fixed + +* [#61](https://github.com/sebastianbergmann/exporter/issues/61): `count(): Recursion detected` warning triggered + +## [6.1.0] - 2024-06-18 + +### Added + +* [#59](https://github.com/sebastianbergmann/exporter/pull/59): Option to limit export of (large) arrays (to speed up PHPUnit) + +### Changed + +* [#60](https://github.com/sebastianbergmann/exporter/pull/60): Take shortcut when exporting a string + +## [6.0.3] - 2024-06-17 + +### Fixed + +* Fixed code coverage metadata + +## [6.0.2] - 2024-06-17 [YANKED] + +### Changed + +* [#58](https://github.com/sebastianbergmann/exporter/pull/58): Remove unnecessary `sprintf()` in hot path + +## [6.0.1] - 2024-03-02 ### Changed * Do not use implicitly nullable parameters +## [6.0.0] - 2024-02-02 + +### Removed + +* This component is no longer supported on PHP 8.1 + +## [5.1.2] - 2024-03-02 + +### Changed + +* Do not use implicitly nullable parameters + +## [5.1.1] - 2023-09-24 + +### Changed + +* [#52](https://github.com/sebastianbergmann/exporter/pull/52): Optimize export of large arrays and object graphs + +## [5.1.0] - 2023-09-18 + +### Changed + +* [#51](https://github.com/sebastianbergmann/exporter/pull/51): Export arrays using short array syntax + +## [5.0.1] - 2023-09-08 + +### Fixed + +* [#49](https://github.com/sebastianbergmann/exporter/issues/49): `Exporter::toArray()` changes `SplObjectStorage` index + +## [5.0.0] - 2023-02-03 + +### Changed + +* [#42](https://github.com/sebastianbergmann/exporter/pull/42): Improve export of enumerations + +### Removed + +* This component is no longer supported on PHP 7.3, PHP 7.4 and PHP 8.0 + ## [4.0.5] - 2022-09-14 ### Fixed @@ -72,7 +184,22 @@ All notable changes are documented in this file using the [Keep a CHANGELOG](htt * Remove HHVM-specific code that is no longer needed -[4.0.6]: https://github.com/sebastianbergmann/exporter/compare/4.0.5...4.0.6 +[7.0.0]: https://github.com/sebastianbergmann/exporter/compare/6.3...7.0.0 +[6.3.0]: https://github.com/sebastianbergmann/exporter/compare/6.2.0...6.3.0 +[6.2.0]: https://github.com/sebastianbergmann/exporter/compare/6.1.3...6.2.0 +[6.1.3]: https://github.com/sebastianbergmann/exporter/compare/6.1.2...6.1.3 +[6.1.2]: https://github.com/sebastianbergmann/exporter/compare/6.1.1...6.1.2 +[6.1.1]: https://github.com/sebastianbergmann/exporter/compare/6.1.0...6.1.1 +[6.1.0]: https://github.com/sebastianbergmann/exporter/compare/6.0.3...6.1.0 +[6.0.3]: https://github.com/sebastianbergmann/exporter/compare/fe0dca49a60d34440e2f086951952dd13aa9a5d2...6.0.3 +[6.0.2]: https://github.com/sebastianbergmann/exporter/compare/6.0.1...fe0dca49a60d34440e2f086951952dd13aa9a5d2 +[6.0.1]: https://github.com/sebastianbergmann/exporter/compare/6.0.0...6.0.1 +[6.0.0]: https://github.com/sebastianbergmann/exporter/compare/5.1...6.0.0 +[5.1.2]: https://github.com/sebastianbergmann/exporter/compare/5.1.1...5.1.2 +[5.1.1]: https://github.com/sebastianbergmann/exporter/compare/5.1.0...5.1.1 +[5.1.0]: https://github.com/sebastianbergmann/exporter/compare/5.0.1...5.1.0 +[5.0.1]: https://github.com/sebastianbergmann/exporter/compare/5.0.0...5.0.1 +[5.0.0]: https://github.com/sebastianbergmann/exporter/compare/4.0.5...5.0.0 [4.0.5]: https://github.com/sebastianbergmann/exporter/compare/4.0.4...4.0.5 [4.0.4]: https://github.com/sebastianbergmann/exporter/compare/4.0.3...4.0.4 [4.0.3]: https://github.com/sebastianbergmann/exporter/compare/4.0.2...4.0.3 diff --git a/app/vendor/sebastian/exporter/LICENSE b/app/vendor/sebastian/exporter/LICENSE index 26dc7feca..c5268a916 100644 --- a/app/vendor/sebastian/exporter/LICENSE +++ b/app/vendor/sebastian/exporter/LICENSE @@ -1,33 +1,29 @@ -Exporter +BSD 3-Clause License -Copyright (c) 2002-2021, Sebastian Bergmann . +Copyright (c) 2002-2025, Sebastian Bergmann All rights reserved. Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: +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. +1. 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. +2. 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 Sebastian Bergmann nor the names of his - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. +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/app/vendor/sebastian/exporter/README.md b/app/vendor/sebastian/exporter/README.md index ed8719f56..08319dffb 100644 --- a/app/vendor/sebastian/exporter/README.md +++ b/app/vendor/sebastian/exporter/README.md @@ -1,7 +1,8 @@ -# sebastian/exporter - +[![Latest Stable Version](https://poser.pugx.org/sebastian/exporter/v)](https://packagist.org/packages/sebastian/exporter) [![CI Status](https://github.com/sebastianbergmann/exporter/workflows/CI/badge.svg)](https://github.com/sebastianbergmann/exporter/actions) -[![Type Coverage](https://shepherd.dev/github/sebastianbergmann/exporter/coverage.svg)](https://shepherd.dev/github/sebastianbergmann/exporter) +[![codecov](https://codecov.io/gh/sebastianbergmann/exporter/branch/main/graph/badge.svg)](https://codecov.io/gh/sebastianbergmann/exporter) + +# sebastian/exporter This component provides the functionality to export PHP variables for visualization. diff --git a/app/vendor/sebastian/exporter/SECURITY.md b/app/vendor/sebastian/exporter/SECURITY.md new file mode 100644 index 000000000..d88ff0019 --- /dev/null +++ b/app/vendor/sebastian/exporter/SECURITY.md @@ -0,0 +1,30 @@ +# Security Policy + +If you believe you have found a security vulnerability in the library that is developed in this repository, please report it to us through coordinated disclosure. + +**Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.** + +Instead, please email `sebastian@phpunit.de`. + +Please include as much of the information listed below as you can to help us better understand and resolve the issue: + +* The type of issue +* Full paths of source file(s) related to the manifestation of the issue +* The location of the affected source code (tag/branch/commit or direct URL) +* Any special configuration required to reproduce the issue +* Step-by-step instructions to reproduce the issue +* Proof-of-concept or exploit code (if possible) +* Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +## Web Context + +The library that is developed in this repository was either extracted from [PHPUnit](https://github.com/sebastianbergmann/phpunit) or developed specifically as a dependency for PHPUnit. + +The library is developed with a focus on development environments and the command-line. No specific testing or hardening with regard to using the library in an HTTP or web context or with untrusted input data is performed. The library might also contain functionality that intentionally exposes internal application data for debugging purposes. + +If the library is used in a web application, the application developer is responsible for filtering inputs or escaping outputs as necessary and for verifying that the used functionality is safe for use within the intended context. + +Vulnerabilities specific to the use outside a development context will be fixed as applicable, provided that the fix does not have an averse effect on the primary use case for development purposes. + diff --git a/app/vendor/sebastian/exporter/composer.json b/app/vendor/sebastian/exporter/composer.json index baa958443..1430aa199 100644 --- a/app/vendor/sebastian/exporter/composer.json +++ b/app/vendor/sebastian/exporter/composer.json @@ -26,30 +26,39 @@ "email": "bschussek@gmail.com" } ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "security": "https://github.com/sebastianbergmann/exporter/security/policy" + }, "config": { "platform": { - "php": "7.3.0" + "php": "8.3.0" }, "optimize-autoloader": true, "sort-packages": true }, "prefer-stable": true, "require": { - "php": ">=7.3", - "sebastian/recursion-context": "^4.0" + "php": ">=8.3", + "ext-mbstring": "*", + "sebastian/recursion-context": "^7.0" }, "require-dev": { - "phpunit/phpunit": "^9.3", - "ext-mbstring": "*" + "phpunit/phpunit": "^12.0" }, "autoload": { "classmap": [ "src/" ] }, + "autoload-dev": { + "classmap": [ + "tests/_fixture" + ] + }, "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "7.0-dev" } } } diff --git a/app/vendor/sebastian/exporter/src/Exporter.php b/app/vendor/sebastian/exporter/src/Exporter.php index 5d85b13a7..599a331f0 100644 --- a/app/vendor/sebastian/exporter/src/Exporter.php +++ b/app/vendor/sebastian/exporter/src/Exporter.php @@ -9,16 +9,17 @@ */ namespace SebastianBergmann\Exporter; +use const COUNT_RECURSIVE; +use function assert; use function bin2hex; use function count; -use function function_exists; -use function get_class; use function get_resource_type; use function gettype; use function implode; use function ini_get; use function ini_set; use function is_array; +use function is_bool; use function is_float; use function is_object; use function is_resource; @@ -26,29 +27,43 @@ use function mb_strlen; use function mb_substr; use function preg_match; -use function spl_object_hash; +use function spl_object_id; use function sprintf; use function str_repeat; use function str_replace; -use function strlen; -use function substr; +use function strtr; use function var_export; -use SebastianBergmann\RecursionContext\Context; +use BackedEnum; +use Google\Protobuf\Internal\Message; +use ReflectionClass; +use ReflectionObject; +use SebastianBergmann\RecursionContext\Context as RecursionContext; use SplObjectStorage; +use stdClass; +use UnitEnum; -/** - * A nifty utility for visualizing PHP variables. - * - * - * export(new Exception); - * - */ -class Exporter +final readonly class Exporter { + /** + * @var non-negative-int + */ + private int $shortenArraysLongerThan; + + /** + * @var positive-int + */ + private int $maxLengthForStrings; + + /** + * @param non-negative-int $shortenArraysLongerThan + * @param positive-int $maxLengthForStrings + */ + public function __construct(int $shortenArraysLongerThan = 0, int $maxLengthForStrings = 40) + { + $this->shortenArraysLongerThan = $shortenArraysLongerThan; + $this->maxLengthForStrings = $maxLengthForStrings; + } + /** * Exports a value as a string. * @@ -61,50 +76,37 @@ class Exporter * - Strings are always quoted with single quotes * - Carriage returns and newlines are normalized to \n * - Recursion and repeated rendering is treated properly - * - * @param int $indentation The indentation level of the 2nd+ line - * - * @return string */ - public function export($value, $indentation = 0) + public function export(mixed $value, int $indentation = 0): string { return $this->recursiveExport($value, $indentation); } /** * @param array $data - * @param Context $context - * - * @return string + * @param positive-int $maxLengthForStrings */ - public function shortenedRecursiveExport(&$data, ?Context $context = null) + public function shortenedRecursiveExport(array &$data, int $maxLengthForStrings = 40, ?RecursionContext $processed = null): string { - $result = []; - $exporter = new self(); + if ($maxLengthForStrings === 40) { + $maxLengthForStrings = $this->maxLengthForStrings; + } - if (!$context) { - $context = new Context; + if (!$processed) { + $processed = new RecursionContext; } - $array = $data; - $context->add($data); + $overallCount = @count($data, COUNT_RECURSIVE); + $counter = 0; - foreach ($array as $key => $value) { - if (is_array($value)) { - if ($context->contains($data[$key]) !== false) { - $result[] = '*RECURSION*'; - } else { - $result[] = sprintf( - 'array(%s)', - $this->shortenedRecursiveExport($data[$key], $context) - ); - } - } else { - $result[] = $exporter->shortenedExport($value); - } + $export = $this->shortenedCountedRecursiveExport($data, $processed, $counter, $maxLengthForStrings); + + if ($this->shortenArraysLongerThan > 0 && + $overallCount > $this->shortenArraysLongerThan) { + $export .= sprintf(', ...%d more elements', $overallCount - $this->shortenArraysLongerThan); } - return implode(', ', $result); + return $export; } /** @@ -116,40 +118,53 @@ public function shortenedRecursiveExport(&$data, ?Context $context = null) * Newlines are replaced by the visible string '\n'. * Contents of arrays and objects (if any) are replaced by '...'. * - * @return string - * - * @see SebastianBergmann\Exporter\Exporter::export + * @param positive-int $maxLengthForStrings */ - public function shortenedExport($value) + public function shortenedExport(mixed $value, int $maxLengthForStrings = 40): string { + if ($maxLengthForStrings === 40) { + $maxLengthForStrings = $this->maxLengthForStrings; + } + if (is_string($value)) { - $string = str_replace("\n", '', $this->export($value)); + $string = str_replace("\n", '', $this->exportString($value)); - if (function_exists('mb_strlen')) { - if (mb_strlen($string) > 40) { - $string = mb_substr($string, 0, 30) . '...' . mb_substr($string, -7); - } - } else { - if (strlen($string) > 40) { - $string = substr($string, 0, 30) . '...' . substr($string, -7); - } + if (mb_strlen($string) > $maxLengthForStrings) { + return mb_substr($string, 0, $maxLengthForStrings - 10) . '...' . mb_substr($string, -7); } return $string; } + if ($value instanceof BackedEnum) { + return sprintf( + '%s Enum (%s, %s)', + $value::class, + $value->name, + $this->export($value->value), + ); + } + + if ($value instanceof UnitEnum) { + return sprintf( + '%s Enum (%s)', + $value::class, + $value->name, + ); + } + if (is_object($value)) { return sprintf( '%s Object (%s)', - get_class($value), - count($this->toArray($value)) > 0 ? '...' : '' + $value::class, + $this->countProperties($value) > 0 ? '...' : '', ); } if (is_array($value)) { return sprintf( - 'Array (%s)', - count($value) > 0 ? '...' : '' + '[%s]', + count($value) > 0 ? '...' : '', ); } @@ -160,9 +175,9 @@ public function shortenedExport($value) * Converts an object to an array containing all of its private, protected * and public properties. * - * @return array + * @return array */ - public function toArray($value) + public function toArray(mixed $value): array { if (!is_object($value)) { return (array) $value; @@ -179,10 +194,10 @@ public function toArray($value) } // properties are transformed to keys in the following way: - // private $property => "\0Classname\0property" - // protected $property => "\0*\0property" - // public $property => "property" - if (preg_match('/^\0.+\0(.+)$/', (string) $key, $matches)) { + // private $propertyName => "\0ClassName\0propertyName" + // protected $propertyName => "\0*\0propertyName" + // public $propertyName => "propertyName" + if (preg_match('/\0.+\0(.+)/', (string) $key, $matches)) { $key = $matches[1]; } @@ -194,62 +209,90 @@ public function toArray($value) $array[$key] = $val; } - // Some internal classes like SplObjectStorage don't work with the + // Some internal classes like SplObjectStorage do not work with the // above (fast) mechanism nor with reflection in Zend. // Format the output similarly to print_r() in this case if ($value instanceof SplObjectStorage) { - foreach ($value as $key => $val) { - $array[spl_object_hash($val)] = [ - 'obj' => $val, + foreach ($value as $_value) { + $array['Object #' . spl_object_id($_value)] = [ + 'obj' => $_value, 'inf' => $value->getInfo(), ]; } + + $value->rewind(); } return $array; } - /** - * Recursive implementation of export. - * - * @param mixed $value The value to export - * @param int $indentation The indentation level of the 2nd+ line - * @param \SebastianBergmann\RecursionContext\Context $processed Previously processed objects - * - * @return string - * - * @see SebastianBergmann\Exporter\Exporter::export - */ - protected function recursiveExport(&$value, $indentation, $processed = null) + public function countProperties(object $value): int { - if ($value === null) { - return 'null'; + if (!$this->canBeReflected($value)) { + // @codeCoverageIgnoreStart + return count($this->toArray($value)); + // @codeCoverageIgnoreEnd } - if ($value === true) { - return 'true'; + if (!$value instanceof stdClass) { + // using ReflectionClass prevents initialization of potential lazy objects + return count((new ReflectionClass($value))->getProperties()); } - if ($value === false) { - return 'false'; - } + return count((new ReflectionObject($value))->getProperties()); + } - if (is_float($value)) { - $precisionBackup = ini_get('precision'); + /** + * @param array $data + * @param positive-int $maxLengthForStrings + */ + private function shortenedCountedRecursiveExport(array &$data, RecursionContext $processed, int &$counter, int $maxLengthForStrings): string + { + $result = []; - ini_set('precision', '-1'); + $array = $data; - try { - $valueStr = (string) $value; + /* @noinspection UnusedFunctionResultInspection */ + $processed->add($data); - if ((string) (int) $value === $valueStr) { - return $valueStr . '.0'; - } + foreach ($array as $key => $value) { + if ($this->shortenArraysLongerThan > 0 && + $counter > $this->shortenArraysLongerThan) { + break; + } - return $valueStr; - } finally { - ini_set('precision', $precisionBackup); + if (is_array($value)) { + assert(is_array($data[$key]) || is_object($data[$key])); + + if ($processed->contains($data[$key]) !== false) { + $result[] = '*RECURSION*'; + } else { + assert(is_array($data[$key])); + + $result[] = '[' . $this->shortenedCountedRecursiveExport($data[$key], $processed, $counter, $maxLengthForStrings) . ']'; + } + } else { + $result[] = $this->shortenedExport($value, $maxLengthForStrings); } + + $counter++; + } + + return implode(', ', $result); + } + + private function recursiveExport(mixed &$value, int $indentation = 0, ?RecursionContext $processed = null): string + { + if ($value === null) { + return 'null'; + } + + if (is_bool($value)) { + return $value ? 'true' : 'false'; + } + + if (is_float($value)) { + return $this->exportFloat($value); } if (gettype($value) === 'resource (closed)') { @@ -259,88 +302,159 @@ protected function recursiveExport(&$value, $indentation, $processed = null) if (is_resource($value)) { return sprintf( 'resource(%d) of type (%s)', - $value, - get_resource_type($value) + (int) $value, + get_resource_type($value), ); } - if (is_string($value)) { - // Match for most non printable chars somewhat taking multibyte chars into account - if (preg_match('/[^\x09-\x0d\x1b\x20-\xff]/', $value)) { - return 'Binary String: 0x' . bin2hex($value); - } + if ($value instanceof BackedEnum) { + return sprintf( + '%s Enum #%d (%s, %s)', + $value::class, + spl_object_id($value), + $value->name, + $this->export($value->value), + ); + } - return "'" . - str_replace( - '', - "\n", - str_replace( - ["\r\n", "\n\r", "\r", "\n"], - ['\r\n', '\n\r', '\r', '\n'], - $value - ) - ) . - "'"; + if ($value instanceof UnitEnum) { + return sprintf( + '%s Enum #%d (%s)', + $value::class, + spl_object_id($value), + $value->name, + ); } - $whitespace = str_repeat(' ', 4 * $indentation); + if (is_string($value)) { + return $this->exportString($value); + } if (!$processed) { - $processed = new Context; + $processed = new RecursionContext; } if (is_array($value)) { - if (($key = $processed->contains($value)) !== false) { - return 'Array &' . $key; - } + return $this->exportArray($value, $processed, $indentation); + } - $array = $value; - $key = $processed->add($value); - $values = ''; - - if (count($array) > 0) { - foreach ($array as $k => $v) { - $values .= sprintf( - '%s %s => %s' . "\n", - $whitespace, - $this->recursiveExport($k, $indentation), - $this->recursiveExport($value[$k], $indentation + 1, $processed) - ); - } + if (is_object($value)) { + return $this->exportObject($value, $processed, $indentation); + } - $values = "\n" . $values . $whitespace; - } + return var_export($value, true); + } + + private function exportFloat(float $value): string + { + $precisionBackup = ini_get('precision'); - return sprintf('Array &%s (%s)', $key, $values); + ini_set('precision', '-1'); + + $valueAsString = (string) $value; + + ini_set('precision', $precisionBackup); + + if ((string) (int) $value === $valueAsString) { + return $valueAsString . '.0'; } - if (is_object($value)) { - $class = get_class($value); + return $valueAsString; + } + + private function exportString(string $value): string + { + // Match for most non-printable chars somewhat taking multibyte chars into account + if (preg_match('/[^\x09-\x0d\x1b\x20-\xff]/', $value)) { + return 'Binary String: 0x' . bin2hex($value); + } + + return "'" . + strtr( + $value, + [ + "\r\n" => '\r\n' . "\n", + "\n\r" => '\n\r' . "\n", + "\r" => '\r' . "\n", + "\n" => '\n' . "\n", + ], + ) . + "'"; + } + + /** + * @param array $value + */ + private function exportArray(array &$value, RecursionContext $processed, int $indentation): string + { + if (($key = $processed->contains($value)) !== false) { + return 'Array &' . $key; + } - if ($hash = $processed->contains($value)) { - return sprintf('%s Object &%s', $class, $hash); + $array = $value; + $key = $processed->add($value); + $values = ''; + + if (count($array) > 0) { + $whitespace = str_repeat(' ', 4 * $indentation); + + foreach ($array as $k => $v) { + $values .= + $whitespace + . ' ' . + $this->recursiveExport($k, $indentation) + . ' => ' . + $this->recursiveExport($value[$k], $indentation + 1, $processed) + . ",\n"; } - $hash = $processed->add($value); - $values = ''; - $array = $this->toArray($value); - - if (count($array) > 0) { - foreach ($array as $k => $v) { - $values .= sprintf( - '%s %s => %s' . "\n", - $whitespace, - $this->recursiveExport($k, $indentation), - $this->recursiveExport($v, $indentation + 1, $processed) - ); - } + $values = "\n" . $values . $whitespace; + } + + return 'Array &' . (string) $key . ' [' . $values . ']'; + } + + private function exportObject(object $value, RecursionContext $processed, int $indentation): string + { + $class = $value::class; + + if ($processed->contains($value) !== false) { + return $class . ' Object #' . spl_object_id($value); + } + + $processed->add($value); - $values = "\n" . $values . $whitespace; + $array = $this->toArray($value); + $buffer = ''; + + if (count($array) > 0) { + $whitespace = str_repeat(' ', 4 * $indentation); + + foreach ($array as $k => $v) { + $buffer .= + $whitespace + . ' ' . + $this->recursiveExport($k, $indentation) + . ' => ' . + $this->recursiveExport($v, $indentation + 1, $processed) + . ",\n"; } - return sprintf('%s Object &%s (%s)', $class, $hash, $values); + $buffer = "\n" . $buffer . $whitespace; } - return var_export($value, true); + return $class . ' Object #' . spl_object_id($value) . ' (' . $buffer . ')'; + } + + private function canBeReflected(object $object): bool + { + /** @phpstan-ignore class.notFound */ + if ($object instanceof Message) { + // @codeCoverageIgnoreStart + return false; + // @codeCoverageIgnoreEnd + } + + return true; } } diff --git a/app/vendor/sebastian/global-state/ChangeLog.md b/app/vendor/sebastian/global-state/ChangeLog.md index e76f84b8f..67830261b 100644 --- a/app/vendor/sebastian/global-state/ChangeLog.md +++ b/app/vendor/sebastian/global-state/ChangeLog.md @@ -2,18 +2,68 @@ All notable changes in `sebastian/global-state` are documented in this file using the [Keep a CHANGELOG](https://keepachangelog.com/) principles. -## [5.0.7] - 2024-03-02 +## [8.0.2] - 2025-08-29 + +### Changed + +* [#39](https://github.com/sebastianbergmann/global-state/pull/39): Restore nullable variables in super-global arrays + +## [8.0.1] - 2025-08-28 + +### Changed + +* [#38](https://github.com/sebastianbergmann/global-state/pull/38): Improve performance of `Snapshot::snapshotSuperGlobalArray()` + +## [8.0.0] - 2025-02-07 + +### Removed + +* This component is no longer supported on PHP 8.2 + +## [7.0.2] - 2024-07-03 + +### Changed + +* This project now uses PHPStan instead of Psalm for static analysis + +## [7.0.1] - 2024-03-02 ### Changed * Do not use implicitly nullable parameters -## [5.0.6] - 2023-08-02 +## [7.0.0] - 2024-02-02 + +### Removed + +* This component is no longer supported on PHP 8.1 + +## [6.0.2] - 2024-03-02 + +### Changed + +* Do not use implicitly nullable parameters + +## [6.0.1] - 2023-07-19 ### Changed * Changed usage of `ReflectionProperty::setValue()` to be compatible with PHP 8.3 +## [6.0.0] - 2023-02-03 + +### Changed + +* Renamed `SebastianBergmann\GlobalState\ExcludeList::addStaticAttribute()` to `SebastianBergmann\GlobalState\ExcludeList::addStaticProperty()` +* Renamed `SebastianBergmann\GlobalState\ExcludeList::isStaticAttributeExcluded()` to `SebastianBergmann\GlobalState\ExcludeList::isStaticPropertyExcluded()` +* Renamed `SebastianBergmann\GlobalState\Restorer::restoreStaticAttributes()` to `SebastianBergmann\GlobalState\Restorer::restoreStaticProperties()` +* Renamed `SebastianBergmann\GlobalState\Snapshot::staticAttributes()` to `SebastianBergmann\GlobalState\Snapshot::staticProperties()` + +### Removed + +* Removed `SebastianBergmann\GlobalState\Restorer::restoreFunctions()` +* This component is no longer supported on PHP 7.3, PHP 7.4 and PHP 8.0 + ## [5.0.5] - 2022-02-14 ### Fixed @@ -78,8 +128,15 @@ All notable changes in `sebastian/global-state` are documented in this file usin * This component is no longer supported on PHP 7.0 and PHP 7.1 -[5.0.7]: https://github.com/sebastianbergmann/global-state/compare/5.0.6...5.0.7 -[5.0.6]: https://github.com/sebastianbergmann/global-state/compare/5.0.5...5.0.6 +[8.0.2]: https://github.com/sebastianbergmann/global-state/compare/8.0.1...8.0.2 +[8.0.1]: https://github.com/sebastianbergmann/global-state/compare/8.0.0...8.0.1 +[8.0.0]: https://github.com/sebastianbergmann/global-state/compare/7.0...8.0.0 +[7.0.2]: https://github.com/sebastianbergmann/global-state/compare/7.0.1...7.0.2 +[7.0.1]: https://github.com/sebastianbergmann/global-state/compare/7.0.0...7.0.1 +[7.0.0]: https://github.com/sebastianbergmann/global-state/compare/6.0...7.0.0 +[6.0.2]: https://github.com/sebastianbergmann/global-state/compare/6.0.1...6.0.2 +[6.0.1]: https://github.com/sebastianbergmann/global-state/compare/6.0.0...6.0.1 +[6.0.0]: https://github.com/sebastianbergmann/global-state/compare/5.0.5...6.0.0 [5.0.5]: https://github.com/sebastianbergmann/global-state/compare/5.0.4...5.0.5 [5.0.4]: https://github.com/sebastianbergmann/global-state/compare/5.0.3...5.0.4 [5.0.3]: https://github.com/sebastianbergmann/global-state/compare/5.0.2...5.0.3 diff --git a/app/vendor/sebastian/global-state/LICENSE b/app/vendor/sebastian/global-state/LICENSE index 240190bd3..b687f39ac 100644 --- a/app/vendor/sebastian/global-state/LICENSE +++ b/app/vendor/sebastian/global-state/LICENSE @@ -1,33 +1,29 @@ -sebastian/global-state +BSD 3-Clause License -Copyright (c) 2001-2022, Sebastian Bergmann . +Copyright (c) 2001-2025, Sebastian Bergmann All rights reserved. Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: +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. +1. 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. +2. 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 Sebastian Bergmann nor the names of his - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. +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/app/vendor/sebastian/global-state/README.md b/app/vendor/sebastian/global-state/README.md index af15bedde..0d1ea0fe4 100644 --- a/app/vendor/sebastian/global-state/README.md +++ b/app/vendor/sebastian/global-state/README.md @@ -1,7 +1,8 @@ -# sebastian/global-state - +[![Latest Stable Version](https://poser.pugx.org/sebastian/global-state/v)](https://packagist.org/packages/sebastian/global-state) [![CI Status](https://github.com/sebastianbergmann/global-state/workflows/CI/badge.svg)](https://github.com/sebastianbergmann/global-state/actions) -[![Type Coverage](https://shepherd.dev/github/sebastianbergmann/global-state/coverage.svg)](https://shepherd.dev/github/sebastianbergmann/global-state) +[![codecov](https://codecov.io/gh/sebastianbergmann/global-state/branch/main/graph/badge.svg)](https://codecov.io/gh/sebastianbergmann/global-state) + +# sebastian/global-state Snapshotting of global state, factored out of PHPUnit into a stand-alone component. diff --git a/app/vendor/sebastian/global-state/SECURITY.md b/app/vendor/sebastian/global-state/SECURITY.md new file mode 100644 index 000000000..d88ff0019 --- /dev/null +++ b/app/vendor/sebastian/global-state/SECURITY.md @@ -0,0 +1,30 @@ +# Security Policy + +If you believe you have found a security vulnerability in the library that is developed in this repository, please report it to us through coordinated disclosure. + +**Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.** + +Instead, please email `sebastian@phpunit.de`. + +Please include as much of the information listed below as you can to help us better understand and resolve the issue: + +* The type of issue +* Full paths of source file(s) related to the manifestation of the issue +* The location of the affected source code (tag/branch/commit or direct URL) +* Any special configuration required to reproduce the issue +* Step-by-step instructions to reproduce the issue +* Proof-of-concept or exploit code (if possible) +* Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +## Web Context + +The library that is developed in this repository was either extracted from [PHPUnit](https://github.com/sebastianbergmann/phpunit) or developed specifically as a dependency for PHPUnit. + +The library is developed with a focus on development environments and the command-line. No specific testing or hardening with regard to using the library in an HTTP or web context or with untrusted input data is performed. The library might also contain functionality that intentionally exposes internal application data for debugging purposes. + +If the library is used in a web application, the application developer is responsible for filtering inputs or escaping outputs as necessary and for verifying that the used functionality is safe for use within the intended context. + +Vulnerabilities specific to the use outside a development context will be fixed as applicable, provided that the fix does not have an averse effect on the primary use case for development purposes. + diff --git a/app/vendor/sebastian/global-state/composer.json b/app/vendor/sebastian/global-state/composer.json index 0fef446a6..ecd4b53ea 100644 --- a/app/vendor/sebastian/global-state/composer.json +++ b/app/vendor/sebastian/global-state/composer.json @@ -2,7 +2,7 @@ "name": "sebastian/global-state", "description": "Snapshotting of global state", "keywords": ["global state"], - "homepage": "http://www.github.com/sebastianbergmann/global-state", + "homepage": "https://www.github.com/sebastianbergmann/global-state", "license": "BSD-3-Clause", "authors": [ { @@ -10,25 +10,26 @@ "email": "sebastian@phpunit.de" } ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "security": "https://github.com/sebastianbergmann/global-state/security/policy" + }, "prefer-stable": true, "config": { "platform": { - "php": "7.3.0" + "php": "8.3.0" }, "optimize-autoloader": true, "sort-packages": true }, "require": { - "php": ">=7.3", - "sebastian/object-reflector": "^2.0", - "sebastian/recursion-context": "^4.0" + "php": ">=8.3", + "sebastian/object-reflector": "^5.0", + "sebastian/recursion-context": "^7.0" }, "require-dev": { "ext-dom": "*", - "phpunit/phpunit": "^9.3" - }, - "suggest": { - "ext-uopz": "*" + "phpunit/phpunit": "^12.0" }, "autoload": { "classmap": [ @@ -45,7 +46,7 @@ }, "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-main": "8.0-dev" } } } diff --git a/app/vendor/sebastian/global-state/src/CodeExporter.php b/app/vendor/sebastian/global-state/src/CodeExporter.php index 71cdbf504..d3238e58c 100644 --- a/app/vendor/sebastian/global-state/src/CodeExporter.php +++ b/app/vendor/sebastian/global-state/src/CodeExporter.php @@ -16,9 +16,6 @@ use function sprintf; use function var_export; -/** - * Exports parts of a Snapshot as PHP code. - */ final class CodeExporter { public function constants(Snapshot $snapshot): string @@ -30,7 +27,7 @@ public function constants(Snapshot $snapshot): string 'if (!defined(\'%s\')) define(\'%s\', %s);' . "\n", $name, $name, - $this->exportVariable($value) + $this->exportVariable($value), ); } @@ -56,7 +53,7 @@ function () $result .= sprintf( '$GLOBALS[%s] = %s;' . PHP_EOL, $this->exportVariable($name), - $this->exportVariable($value) + $this->exportVariable($value), ); } @@ -71,14 +68,14 @@ public function iniSettings(Snapshot $snapshot): string $result .= sprintf( '@ini_set(%s, %s);' . "\n", $this->exportVariable($key), - $this->exportVariable($value) + $this->exportVariable($value), ); } return $result; } - private function exportVariable($variable): string + private function exportVariable(mixed $variable): string { if (is_scalar($variable) || null === $variable || (is_array($variable) && $this->arrayOnlyContainsScalars($variable))) { @@ -88,6 +85,9 @@ private function exportVariable($variable): string return 'unserialize(' . var_export(serialize($variable), true) . ')'; } + /** + * @param array $array + */ private function arrayOnlyContainsScalars(array $array): bool { $result = true; diff --git a/app/vendor/sebastian/global-state/src/ExcludeList.php b/app/vendor/sebastian/global-state/src/ExcludeList.php index 5631f1186..07c29a0c8 100644 --- a/app/vendor/sebastian/global-state/src/ExcludeList.php +++ b/app/vendor/sebastian/global-state/src/ExcludeList.php @@ -10,73 +10,92 @@ namespace SebastianBergmann\GlobalState; use function in_array; -use function strpos; +use function str_starts_with; use ReflectionClass; final class ExcludeList { /** - * @var array + * @var array */ - private $globalVariables = []; + private array $globalVariables = []; /** - * @var string[] + * @var list */ - private $classes = []; + private array $classes = []; /** - * @var string[] + * @var list */ - private $classNamePrefixes = []; + private array $classNamePrefixes = []; /** - * @var string[] + * @var list */ - private $parentClasses = []; + private array $parentClasses = []; /** - * @var string[] + * @var list */ - private $interfaces = []; + private array $interfaces = []; /** - * @var array + * @var array> */ - private $staticAttributes = []; + private array $staticProperties = []; + /** + * @param non-empty-string $variableName + */ public function addGlobalVariable(string $variableName): void { $this->globalVariables[$variableName] = true; } + /** + * @param non-empty-string $className + */ public function addClass(string $className): void { $this->classes[] = $className; } + /** + * @param non-empty-string $className + */ public function addSubclassesOf(string $className): void { $this->parentClasses[] = $className; } + /** + * @param non-empty-string $interfaceName + */ public function addImplementorsOf(string $interfaceName): void { $this->interfaces[] = $interfaceName; } + /** + * @param non-empty-string $classNamePrefix + */ public function addClassNamePrefix(string $classNamePrefix): void { $this->classNamePrefixes[] = $classNamePrefix; } - public function addStaticAttribute(string $className, string $attributeName): void + /** + * @param non-empty-string $className + * @param non-empty-string $propertyName + */ + public function addStaticProperty(string $className, string $propertyName): void { - if (!isset($this->staticAttributes[$className])) { - $this->staticAttributes[$className] = []; + if (!isset($this->staticProperties[$className])) { + $this->staticProperties[$className] = []; } - $this->staticAttributes[$className][$attributeName] = true; + $this->staticProperties[$className][$propertyName] = true; } public function isGlobalVariableExcluded(string $variableName): bool @@ -84,14 +103,18 @@ public function isGlobalVariableExcluded(string $variableName): bool return isset($this->globalVariables[$variableName]); } - public function isStaticAttributeExcluded(string $className, string $attributeName): bool + /** + * @param class-string $className + * @param non-empty-string $propertyName + */ + public function isStaticPropertyExcluded(string $className, string $propertyName): bool { if (in_array($className, $this->classes, true)) { return true; } foreach ($this->classNamePrefixes as $prefix) { - if (strpos($className, $prefix) === 0) { + if (str_starts_with($className, $prefix)) { return true; } } @@ -110,10 +133,6 @@ public function isStaticAttributeExcluded(string $className, string $attributeNa } } - if (isset($this->staticAttributes[$className][$attributeName])) { - return true; - } - - return false; + return isset($this->staticProperties[$className][$propertyName]); } } diff --git a/app/vendor/sebastian/global-state/src/Restorer.php b/app/vendor/sebastian/global-state/src/Restorer.php index ab145ce23..36fcd21ae 100644 --- a/app/vendor/sebastian/global-state/src/Restorer.php +++ b/app/vendor/sebastian/global-state/src/Restorer.php @@ -13,41 +13,14 @@ use function array_key_exists; use function array_keys; use function array_merge; -use function function_exists; -use function get_defined_functions; +use function assert; use function in_array; use function is_array; use ReflectionClass; use ReflectionProperty; -/** - * Restorer of snapshots of global state. - */ -class Restorer +final class Restorer { - /** - * Deletes function definitions that are not defined in a snapshot. - * - * @throws RuntimeException when the uopz_delete() function is not available - * - * @see https://github.com/krakjoe/uopz - */ - public function restoreFunctions(Snapshot $snapshot): void - { - if (!function_exists('uopz_delete')) { - throw new RuntimeException('The uopz_delete() function is required for this operation'); - } - - $functions = get_defined_functions(); - - foreach (array_diff($functions['user'], $snapshot->functions()) as $function) { - uopz_delete($function); - } - } - - /** - * Restores all global and super-global variables from a snapshot. - */ public function restoreGlobalVariables(Snapshot $snapshot): void { $superGlobalArrays = $snapshot->superGlobalArrays(); @@ -61,7 +34,7 @@ public function restoreGlobalVariables(Snapshot $snapshot): void foreach (array_keys($GLOBALS) as $key) { if ($key !== 'GLOBALS' && !in_array($key, $superGlobalArrays, true) && - !$snapshot->excludeList()->isGlobalVariableExcluded($key)) { + !$snapshot->excludeList()->isGlobalVariableExcluded((string) $key)) { if (array_key_exists($key, $globalVariables)) { $GLOBALS[$key] = $globalVariables[$key]; } else { @@ -71,20 +44,16 @@ public function restoreGlobalVariables(Snapshot $snapshot): void } } - /** - * Restores all static attributes in user-defined classes from this snapshot. - */ - public function restoreStaticAttributes(Snapshot $snapshot): void + public function restoreStaticProperties(Snapshot $snapshot): void { $current = new Snapshot($snapshot->excludeList(), false, false, false, false, true, false, false, false, false); $newClasses = array_diff($current->classes(), $snapshot->classes()); unset($current); - foreach ($snapshot->staticAttributes() as $className => $staticAttributes) { - foreach ($staticAttributes as $name => $value) { + foreach ($snapshot->staticProperties() as $className => $staticProperties) { + foreach ($staticProperties as $name => $value) { $reflector = new ReflectionProperty($className, $name); - $reflector->setAccessible(true); $reflector->setValue(null, $value); } } @@ -93,14 +62,14 @@ public function restoreStaticAttributes(Snapshot $snapshot): void $class = new ReflectionClass($className); $defaults = $class->getDefaultProperties(); - foreach ($class->getProperties() as $attribute) { - if (!$attribute->isStatic()) { + foreach ($class->getProperties() as $property) { + if (!$property->isStatic()) { continue; } - $name = $attribute->getName(); + $name = $property->getName(); - if ($snapshot->excludeList()->isStaticAttributeExcluded($className, $name)) { + if ($snapshot->excludeList()->isStaticPropertyExcluded($className, $name)) { continue; } @@ -108,31 +77,28 @@ public function restoreStaticAttributes(Snapshot $snapshot): void continue; } - $attribute->setAccessible(true); - $attribute->setValue(null, $defaults[$name]); + $property->setValue(null, $defaults[$name]); } } } - /** - * Restores a super-global variable array from this snapshot. - */ private function restoreSuperGlobalArray(Snapshot $snapshot, string $superGlobalArray): void { $superGlobalVariables = $snapshot->superGlobalVariables(); - if (isset($GLOBALS[$superGlobalArray]) && - is_array($GLOBALS[$superGlobalArray]) && - isset($superGlobalVariables[$superGlobalArray])) { + if (isset($GLOBALS[$superGlobalArray], $superGlobalVariables[$superGlobalArray]) && + is_array($GLOBALS[$superGlobalArray])) { $keys = array_keys( array_merge( $GLOBALS[$superGlobalArray], - $superGlobalVariables[$superGlobalArray] - ) + $superGlobalVariables[$superGlobalArray], + ), ); foreach ($keys as $key) { - if (isset($superGlobalVariables[$superGlobalArray][$key])) { + assert(isset($GLOBALS[$superGlobalArray]) && is_array($GLOBALS[$superGlobalArray])); + + if (array_key_exists($key, $superGlobalVariables[$superGlobalArray])) { $GLOBALS[$superGlobalArray][$key] = $superGlobalVariables[$superGlobalArray][$key]; } else { unset($GLOBALS[$superGlobalArray][$key]); diff --git a/app/vendor/sebastian/global-state/src/Snapshot.php b/app/vendor/sebastian/global-state/src/Snapshot.php index 61219cacd..66920d8d0 100644 --- a/app/vendor/sebastian/global-state/src/Snapshot.php +++ b/app/vendor/sebastian/global-state/src/Snapshot.php @@ -9,11 +9,10 @@ */ namespace SebastianBergmann\GlobalState; -use const PHP_VERSION_ID; use function array_keys; use function array_merge; use function array_reverse; -use function func_get_args; +use function assert; use function get_declared_classes; use function get_declared_interfaces; use function get_declared_traits; @@ -36,74 +35,72 @@ /** * A snapshot of global state. */ -class Snapshot +final class Snapshot { - /** - * @var ExcludeList - */ - private $excludeList; + private ExcludeList $excludeList; /** - * @var array + * @var array */ - private $globalVariables = []; + private array $globalVariables = []; /** - * @var array + * @var list */ - private $superGlobalArrays = []; + private array $superGlobalArrays = []; /** - * @var array + * @var array> */ - private $superGlobalVariables = []; + private array $superGlobalVariables = []; /** - * @var array + * @var array> */ - private $staticAttributes = []; + private array $staticProperties = []; /** - * @var array + * @var array */ - private $iniSettings = []; + private array $iniSettings = []; /** - * @var array + * @var list */ - private $includedFiles = []; + private array $includedFiles = []; /** - * @var array + * @var array */ - private $constants = []; + private array $constants = []; /** - * @var array + * @var list */ - private $functions = []; + private array $functions = []; /** - * @var array + * @var list */ - private $interfaces = []; + private array $interfaces = []; /** - * @var array + * @var list */ - private $classes = []; + private array $classes = []; /** - * @var array + * @var list */ - private $traits = []; + private array $traits = []; - /** - * Creates a snapshot of the current global state. - */ - public function __construct(?ExcludeList $excludeList = null, bool $includeGlobalVariables = true, bool $includeStaticAttributes = true, bool $includeConstants = true, bool $includeFunctions = true, bool $includeClasses = true, bool $includeInterfaces = true, bool $includeTraits = true, bool $includeIniSettings = true, bool $includeIncludedFiles = true) + public function __construct(?ExcludeList $excludeList = null, bool $includeGlobalVariables = true, bool $includeStaticProperties = true, bool $includeConstants = true, bool $includeFunctions = true, bool $includeClasses = true, bool $includeInterfaces = true, bool $includeTraits = true, bool $includeIniSettings = true, bool $includeIncludedFiles = true) { - $this->excludeList = $excludeList ?: new ExcludeList; + if ($excludeList === null) { + $excludeList = new ExcludeList; + } + + $this->excludeList = $excludeList; if ($includeConstants) { $this->snapshotConstants(); @@ -113,7 +110,7 @@ public function __construct(?ExcludeList $excludeList = null, bool $includeGloba $this->snapshotFunctions(); } - if ($includeClasses || $includeStaticAttributes) { + if ($includeClasses || $includeStaticProperties) { $this->snapshotClasses(); } @@ -126,12 +123,17 @@ public function __construct(?ExcludeList $excludeList = null, bool $includeGloba $this->snapshotGlobals(); } - if ($includeStaticAttributes) { - $this->snapshotStaticAttributes(); + if ($includeStaticProperties) { + $this->snapshotStaticProperties(); } if ($includeIniSettings) { - $this->iniSettings = ini_get_all(null, false); + $iniSettings = ini_get_all(null, false); + + assert($iniSettings !== false); + + /* @phpstan-ignore assign.propertyType */ + $this->iniSettings = $iniSettings; } if ($includeIncludedFiles) { @@ -148,64 +150,94 @@ public function excludeList(): ExcludeList return $this->excludeList; } + /** + * @return array + */ public function globalVariables(): array { return $this->globalVariables; } + /** + * @return array> + */ public function superGlobalVariables(): array { return $this->superGlobalVariables; } + /** + * @return list + */ public function superGlobalArrays(): array { return $this->superGlobalArrays; } - public function staticAttributes(): array + /** + * @return array> + */ + public function staticProperties(): array { - return $this->staticAttributes; + return $this->staticProperties; } + /** + * @return array + */ public function iniSettings(): array { return $this->iniSettings; } + /** + * @return list + */ public function includedFiles(): array { return $this->includedFiles; } + /** + * @return array + */ public function constants(): array { return $this->constants; } + /** + * @return list + */ public function functions(): array { return $this->functions; } + /** + * @return list + */ public function interfaces(): array { return $this->interfaces; } + /** + * @return list + */ public function classes(): array { return $this->classes; } + /** + * @return list + */ public function traits(): array { return $this->traits; } - /** - * Creates a snapshot user-defined constants. - */ private function snapshotConstants(): void { $constants = get_defined_constants(true); @@ -215,9 +247,6 @@ private function snapshotConstants(): void } } - /** - * Creates a snapshot user-defined functions. - */ private function snapshotFunctions(): void { $functions = get_defined_functions(); @@ -225,9 +254,6 @@ private function snapshotFunctions(): void $this->functions = $functions['user']; } - /** - * Creates a snapshot user-defined classes. - */ private function snapshotClasses(): void { foreach (array_reverse(get_declared_classes()) as $className) { @@ -243,9 +269,6 @@ private function snapshotClasses(): void $this->classes = array_reverse($this->classes); } - /** - * Creates a snapshot user-defined interfaces. - */ private function snapshotInterfaces(): void { foreach (array_reverse(get_declared_interfaces()) as $interfaceName) { @@ -261,9 +284,6 @@ private function snapshotInterfaces(): void $this->interfaces = array_reverse($this->interfaces); } - /** - * Creates a snapshot of all global and super-global variables. - */ private function snapshotGlobals(): void { $superGlobalArrays = $this->superGlobalArrays(); @@ -275,70 +295,59 @@ private function snapshotGlobals(): void foreach (array_keys($GLOBALS) as $key) { if ($key !== 'GLOBALS' && !in_array($key, $superGlobalArrays, true) && - $this->canBeSerialized($GLOBALS[$key]) && - !$this->excludeList->isGlobalVariableExcluded($key)) { - /* @noinspection UnserializeExploitsInspection */ - $this->globalVariables[$key] = unserialize(serialize($GLOBALS[$key])); + !$this->excludeList->isGlobalVariableExcluded($key) && + $this->canBeSerialized($GLOBALS[$key]) + ) { + /* @phpstan-ignore assign.propertyType */ + $this->globalVariables[$key] = $this->copyWithSerialize($GLOBALS[$key]); } } } - /** - * Creates a snapshot a super-global variable array. - */ private function snapshotSuperGlobalArray(string $superGlobalArray): void { $this->superGlobalVariables[$superGlobalArray] = []; if (isset($GLOBALS[$superGlobalArray]) && is_array($GLOBALS[$superGlobalArray])) { foreach ($GLOBALS[$superGlobalArray] as $key => $value) { - /* @noinspection UnserializeExploitsInspection */ - $this->superGlobalVariables[$superGlobalArray][$key] = unserialize(serialize($value)); + /* @phpstan-ignore assign.propertyType */ + $this->superGlobalVariables[$superGlobalArray][$key] = $this->copyWithSerialize($value); } } } - /** - * Creates a snapshot of all static attributes in user-defined classes. - */ - private function snapshotStaticAttributes(): void + private function snapshotStaticProperties(): void { foreach ($this->classes as $className) { $class = new ReflectionClass($className); $snapshot = []; - foreach ($class->getProperties() as $attribute) { - if ($attribute->isStatic()) { - $name = $attribute->getName(); + foreach ($class->getProperties() as $property) { + if ($property->isStatic()) { + $name = $property->getName(); - if ($this->excludeList->isStaticAttributeExcluded($className, $name)) { + if ($this->excludeList->isStaticPropertyExcluded($className, $name)) { continue; } - $attribute->setAccessible(true); - - if (PHP_VERSION_ID >= 70400 && !$attribute->isInitialized()) { + if (!$property->isInitialized()) { continue; } - $value = $attribute->getValue(); + $value = $property->getValue(); if ($this->canBeSerialized($value)) { - /* @noinspection UnserializeExploitsInspection */ - $snapshot[$name] = unserialize(serialize($value)); + $snapshot[$name] = $this->copyWithSerialize($value); } } } - if (!empty($snapshot)) { - $this->staticAttributes[$className] = $snapshot; + if ($snapshot !== []) { + $this->staticProperties[$className] = $snapshot; } } } - /** - * Returns a list of all super-global variable arrays. - */ private function setupSuperGlobalArrays(): void { $this->superGlobalArrays = [ @@ -352,7 +361,17 @@ private function setupSuperGlobalArrays(): void ]; } - private function canBeSerialized($variable): bool + private function copyWithSerialize(mixed $variable): mixed + { + if (is_scalar($variable) || $variable === null) { + return $variable; + } + + /* @noinspection UnserializeExploitsInspection */ + return unserialize(serialize($variable)); + } + + private function canBeSerialized(mixed $variable): bool { if (is_scalar($variable) || $variable === null) { return true; @@ -385,24 +404,25 @@ private function canBeSerialized($variable): bool return true; } - private function enumerateObjectsAndResources($variable): array + /** + * @return array + */ + private function enumerateObjectsAndResources(mixed $variable, Context $processed = new Context): array { - if (isset(func_get_args()[1])) { - $processed = func_get_args()[1]; - } else { - $processed = new Context; - } - $result = []; - if ($processed->contains($variable)) { + /* @phpstan-ignore argument.type */ + if ($processed->contains($variable) !== false) { return $result; } $array = $variable; + + /* @noinspection UnusedFunctionResultInspection */ $processed->add($variable); if (is_array($variable)) { + /** @phpstan-ignore foreach.nonIterable */ foreach ($array as $element) { if (!is_array($element) && !is_object($element) && !is_resource($element)) { continue; @@ -412,7 +432,7 @@ private function enumerateObjectsAndResources($variable): array /** @noinspection SlowArrayOperationsInLoopInspection */ $result = array_merge( $result, - $this->enumerateObjectsAndResources($element, $processed) + $this->enumerateObjectsAndResources($element, $processed), ); } else { $result[] = $element; @@ -421,7 +441,7 @@ private function enumerateObjectsAndResources($variable): array } else { $result[] = $variable; - foreach ((new ObjectReflector)->getAttributes($variable) as $value) { + foreach ((new ObjectReflector)->getProperties($variable) as $value) { if (!is_array($value) && !is_object($value) && !is_resource($value)) { continue; } @@ -430,7 +450,7 @@ private function enumerateObjectsAndResources($variable): array /** @noinspection SlowArrayOperationsInLoopInspection */ $result = array_merge( $result, - $this->enumerateObjectsAndResources($value, $processed) + $this->enumerateObjectsAndResources($value, $processed), ); } else { $result[] = $value; diff --git a/app/vendor/sebastian/lines-of-code/.psalm/baseline.xml b/app/vendor/sebastian/lines-of-code/.psalm/baseline.xml deleted file mode 100644 index 77e688e07..000000000 --- a/app/vendor/sebastian/lines-of-code/.psalm/baseline.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/app/vendor/sebastian/lines-of-code/.psalm/config.xml b/app/vendor/sebastian/lines-of-code/.psalm/config.xml deleted file mode 100644 index 15abef058..000000000 --- a/app/vendor/sebastian/lines-of-code/.psalm/config.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - diff --git a/app/vendor/sebastian/lines-of-code/ChangeLog.md b/app/vendor/sebastian/lines-of-code/ChangeLog.md index 2c9607058..4e724be6f 100644 --- a/app/vendor/sebastian/lines-of-code/ChangeLog.md +++ b/app/vendor/sebastian/lines-of-code/ChangeLog.md @@ -2,12 +2,43 @@ All notable changes are documented in this file using the [Keep a CHANGELOG](https://keepachangelog.com/) principles. -## [1.0.4] - 2023-12-22 +## [4.0.0] - 2025-02-07 + +### Removed + +* This component is no longer supported on PHP 8.2 + +## [3.0.1] - 2024-07-03 + +### Changed + +* This project now uses PHPStan instead of Psalm for static analysis + +## [3.0.0] - 2024-02-02 + +### Removed + +* This component now requires PHP-Parser 5 +* This component is no longer supported on PHP 8.1 + +## [2.0.2] - 2023-12-21 ### Changed * This component is now compatible with `nikic/php-parser` 5.0 +## [2.0.1] - 2023-08-31 + +### Changed + +* Improved type information + +## [2.0.0] - 2023-02-03 + +### Removed + +* This component is no longer supported on PHP 7.3, PHP 7.4 and PHP 8.0 + ## [1.0.3] - 2020-11-28 ### Fixed @@ -34,7 +65,12 @@ All notable changes are documented in this file using the [Keep a CHANGELOG](htt * Initial release -[1.0.4]: https://github.com/sebastianbergmann/lines-of-code/compare/1.0.3...1.0.4 +[4.0.0]: https://github.com/sebastianbergmann/lines-of-code/compare/3.0...4.0.0 +[3.0.1]: https://github.com/sebastianbergmann/lines-of-code/compare/3.0.0...3.0.1 +[3.0.0]: https://github.com/sebastianbergmann/lines-of-code/compare/2.0...3.0.0 +[2.0.2]: https://github.com/sebastianbergmann/lines-of-code/compare/2.0.1...2.0.2 +[2.0.1]: https://github.com/sebastianbergmann/lines-of-code/compare/2.0.0...2.0.1 +[2.0.0]: https://github.com/sebastianbergmann/lines-of-code/compare/1.0.3...2.0.0 [1.0.3]: https://github.com/sebastianbergmann/lines-of-code/compare/1.0.2...1.0.3 [1.0.2]: https://github.com/sebastianbergmann/lines-of-code/compare/1.0.1...1.0.2 [1.0.1]: https://github.com/sebastianbergmann/lines-of-code/compare/1.0.0...1.0.1 diff --git a/app/vendor/sebastian/lines-of-code/LICENSE b/app/vendor/sebastian/lines-of-code/LICENSE index d170181fa..0d534da34 100644 --- a/app/vendor/sebastian/lines-of-code/LICENSE +++ b/app/vendor/sebastian/lines-of-code/LICENSE @@ -1,33 +1,29 @@ -sebastian/lines-of-code +BSD 3-Clause License -Copyright (c) 2020, Sebastian Bergmann . +Copyright (c) 2020-2025, Sebastian Bergmann All rights reserved. Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: +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. +1. 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. +2. 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 Sebastian Bergmann nor the names of his - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. +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/app/vendor/sebastian/lines-of-code/README.md b/app/vendor/sebastian/lines-of-code/README.md index 9457ef5a7..765a0ebb2 100644 --- a/app/vendor/sebastian/lines-of-code/README.md +++ b/app/vendor/sebastian/lines-of-code/README.md @@ -1,12 +1,11 @@ +[![Latest Stable Version](https://poser.pugx.org/sebastian/lines-of-code/v)](https://packagist.org/packages/sebastian/lines-of-code) +[![CI Status](https://github.com/sebastianbergmann/lines-of-code/workflows/CI/badge.svg)](https://github.com/sebastianbergmann/lines-of-code/actions) +[![codecov](https://codecov.io/gh/sebastianbergmann/lines-of-code/branch/main/graph/badge.svg)](https://codecov.io/gh/sebastianbergmann/lines-of-code) + # sebastian/lines-of-code Library for counting the lines of code in PHP source code. -[![Latest Stable Version](https://img.shields.io/packagist/v/sebastian/lines-of-code.svg?style=flat-square)](https://packagist.org/packages/sebastian/lines-of-code) -[![Minimum PHP Version](https://img.shields.io/badge/php-%3E%3D%207.3-8892BF.svg?style=flat-square)](https://php.net/) -[![CI Status](https://github.com/sebastianbergmann/lines-of-code/workflows/CI/badge.svg?branch=master&event=push)](https://phpunit.de/build-status.html) -[![Type Coverage](https://shepherd.dev/github/sebastianbergmann/lines-of-code/coverage.svg)](https://shepherd.dev/github/sebastianbergmann/lines-of-code) - ## Installation You can add this library as a local, per-project dependency to your project using [Composer](https://getcomposer.org/): diff --git a/app/vendor/sebastian/lines-of-code/SECURITY.md b/app/vendor/sebastian/lines-of-code/SECURITY.md new file mode 100644 index 000000000..d88ff0019 --- /dev/null +++ b/app/vendor/sebastian/lines-of-code/SECURITY.md @@ -0,0 +1,30 @@ +# Security Policy + +If you believe you have found a security vulnerability in the library that is developed in this repository, please report it to us through coordinated disclosure. + +**Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.** + +Instead, please email `sebastian@phpunit.de`. + +Please include as much of the information listed below as you can to help us better understand and resolve the issue: + +* The type of issue +* Full paths of source file(s) related to the manifestation of the issue +* The location of the affected source code (tag/branch/commit or direct URL) +* Any special configuration required to reproduce the issue +* Step-by-step instructions to reproduce the issue +* Proof-of-concept or exploit code (if possible) +* Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +## Web Context + +The library that is developed in this repository was either extracted from [PHPUnit](https://github.com/sebastianbergmann/phpunit) or developed specifically as a dependency for PHPUnit. + +The library is developed with a focus on development environments and the command-line. No specific testing or hardening with regard to using the library in an HTTP or web context or with untrusted input data is performed. The library might also contain functionality that intentionally exposes internal application data for debugging purposes. + +If the library is used in a web application, the application developer is responsible for filtering inputs or escaping outputs as necessary and for verifying that the used functionality is safe for use within the intended context. + +Vulnerabilities specific to the use outside a development context will be fixed as applicable, provided that the fix does not have an averse effect on the primary use case for development purposes. + diff --git a/app/vendor/sebastian/lines-of-code/composer.json b/app/vendor/sebastian/lines-of-code/composer.json index 3f2878657..7c428050f 100644 --- a/app/vendor/sebastian/lines-of-code/composer.json +++ b/app/vendor/sebastian/lines-of-code/composer.json @@ -12,19 +12,20 @@ } ], "support": { - "issues": "https://github.com/sebastianbergmann/lines-of-code/issues" + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy" }, "prefer-stable": true, "require": { - "php": ">=7.3", - "nikic/php-parser": "^4.18 || ^5.0" + "php": ">=8.3", + "nikic/php-parser": "^5.0" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^12.0" }, "config": { "platform": { - "php": "7.3.0" + "php": "8.3" }, "optimize-autoloader": true, "sort-packages": true @@ -36,7 +37,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-main": "4.0-dev" } } } diff --git a/app/vendor/sebastian/lines-of-code/src/Counter.php b/app/vendor/sebastian/lines-of-code/src/Counter.php index 8153a7b24..ed2d3ab0e 100644 --- a/app/vendor/sebastian/lines-of-code/src/Counter.php +++ b/app/vendor/sebastian/lines-of-code/src/Counter.php @@ -9,6 +9,8 @@ */ namespace SebastianBergmann\LinesOfCode; +use function assert; +use function file_get_contents; use function substr_count; use PhpParser\Error; use PhpParser\Node; @@ -22,7 +24,11 @@ final class Counter */ public function countInSourceFile(string $sourceFile): LinesOfCode { - return $this->countInSourceString(file_get_contents($sourceFile)); + $source = file_get_contents($sourceFile); + + assert($source !== false); + + return $this->countInSourceString($source); } /** @@ -42,20 +48,20 @@ public function countInSourceString(string $source): LinesOfCode assert($nodes !== null); return $this->countInAbstractSyntaxTree($linesOfCode, $nodes); - // @codeCoverageIgnoreStart } catch (Error $error) { throw new RuntimeException( $error->getMessage(), - (int) $error->getCode(), - $error + $error->getCode(), + $error, ); } // @codeCoverageIgnoreEnd } /** - * @param Node[] $nodes + * @param non-negative-int $linesOfCode + * @param Node[] $nodes * * @throws RuntimeException */ @@ -73,8 +79,8 @@ public function countInAbstractSyntaxTree(int $linesOfCode, array $nodes): Lines } catch (Error $error) { throw new RuntimeException( $error->getMessage(), - (int) $error->getCode(), - $error + $error->getCode(), + $error, ); } // @codeCoverageIgnoreEnd diff --git a/app/vendor/sebastian/lines-of-code/src/Exception/NegativeValueException.php b/app/vendor/sebastian/lines-of-code/src/Exception/NegativeValueException.php deleted file mode 100644 index 40d27e1f0..000000000 --- a/app/vendor/sebastian/lines-of-code/src/Exception/NegativeValueException.php +++ /dev/null @@ -1,16 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace SebastianBergmann\LinesOfCode; - -use InvalidArgumentException; - -final class NegativeValueException extends InvalidArgumentException implements Exception -{ -} diff --git a/app/vendor/sebastian/lines-of-code/src/LineCountingVisitor.php b/app/vendor/sebastian/lines-of-code/src/LineCountingVisitor.php index ff433b2fc..a461a98a9 100644 --- a/app/vendor/sebastian/lines-of-code/src/LineCountingVisitor.php +++ b/app/vendor/sebastian/lines-of-code/src/LineCountingVisitor.php @@ -11,6 +11,7 @@ use function array_merge; use function array_unique; +use function assert; use function count; use PhpParser\Comment; use PhpParser\Node; @@ -20,34 +21,39 @@ final class LineCountingVisitor extends NodeVisitorAbstract { /** - * @var int + * @var non-negative-int */ - private $linesOfCode; + private readonly int $linesOfCode; /** * @var Comment[] */ - private $comments = []; + private array $comments = []; /** * @var int[] */ - private $linesWithStatements = []; + private array $linesWithStatements = []; + /** + * @param non-negative-int $linesOfCode + */ public function __construct(int $linesOfCode) { $this->linesOfCode = $linesOfCode; } - public function enterNode(Node $node): void + public function enterNode(Node $node): null { $this->comments = array_merge($this->comments, $node->getComments()); if (!$node instanceof Expr) { - return; + return null; } $this->linesWithStatements[] = $node->getStartLine(); + + return null; } public function result(): LinesOfCode @@ -58,11 +64,17 @@ public function result(): LinesOfCode $commentLinesOfCode += ($comment->getEndLine() - $comment->getStartLine() + 1); } + $nonCommentLinesOfCode = $this->linesOfCode - $commentLinesOfCode; + $logicalLinesOfCode = count(array_unique($this->linesWithStatements)); + + assert($commentLinesOfCode >= 0); + assert($nonCommentLinesOfCode >= 0); + return new LinesOfCode( $this->linesOfCode, $commentLinesOfCode, - $this->linesOfCode - $commentLinesOfCode, - count(array_unique($this->linesWithStatements)) + $nonCommentLinesOfCode, + $logicalLinesOfCode, ); } diff --git a/app/vendor/sebastian/lines-of-code/src/LinesOfCode.php b/app/vendor/sebastian/lines-of-code/src/LinesOfCode.php index 41829981a..2aa5b0e64 100644 --- a/app/vendor/sebastian/lines-of-code/src/LinesOfCode.php +++ b/app/vendor/sebastian/lines-of-code/src/LinesOfCode.php @@ -10,52 +10,40 @@ namespace SebastianBergmann\LinesOfCode; /** - * @psalm-immutable + * @immutable */ -final class LinesOfCode +final readonly class LinesOfCode { /** - * @var int + * @var non-negative-int */ - private $linesOfCode; + private int $linesOfCode; /** - * @var int + * @var non-negative-int */ - private $commentLinesOfCode; + private int $commentLinesOfCode; /** - * @var int + * @var non-negative-int */ - private $nonCommentLinesOfCode; + private int $nonCommentLinesOfCode; /** - * @var int + * @var non-negative-int */ - private $logicalLinesOfCode; + private int $logicalLinesOfCode; /** + * @param non-negative-int $linesOfCode + * @param non-negative-int $commentLinesOfCode + * @param non-negative-int $nonCommentLinesOfCode + * @param non-negative-int $logicalLinesOfCode + * * @throws IllogicalValuesException - * @throws NegativeValueException */ public function __construct(int $linesOfCode, int $commentLinesOfCode, int $nonCommentLinesOfCode, int $logicalLinesOfCode) { - if ($linesOfCode < 0) { - throw new NegativeValueException('$linesOfCode must not be negative'); - } - - if ($commentLinesOfCode < 0) { - throw new NegativeValueException('$commentLinesOfCode must not be negative'); - } - - if ($nonCommentLinesOfCode < 0) { - throw new NegativeValueException('$nonCommentLinesOfCode must not be negative'); - } - - if ($logicalLinesOfCode < 0) { - throw new NegativeValueException('$logicalLinesOfCode must not be negative'); - } - if ($linesOfCode - $commentLinesOfCode !== $nonCommentLinesOfCode) { throw new IllogicalValuesException('$linesOfCode !== $commentLinesOfCode + $nonCommentLinesOfCode'); } @@ -66,21 +54,33 @@ public function __construct(int $linesOfCode, int $commentLinesOfCode, int $nonC $this->logicalLinesOfCode = $logicalLinesOfCode; } + /** + * @return non-negative-int + */ public function linesOfCode(): int { return $this->linesOfCode; } + /** + * @return non-negative-int + */ public function commentLinesOfCode(): int { return $this->commentLinesOfCode; } + /** + * @return non-negative-int + */ public function nonCommentLinesOfCode(): int { return $this->nonCommentLinesOfCode; } + /** + * @return non-negative-int + */ public function logicalLinesOfCode(): int { return $this->logicalLinesOfCode; diff --git a/app/vendor/sebastian/object-enumerator/.psalm/baseline.xml b/app/vendor/sebastian/object-enumerator/.psalm/baseline.xml deleted file mode 100644 index 180b3f803..000000000 --- a/app/vendor/sebastian/object-enumerator/.psalm/baseline.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - !is_array($variable) && !is_object($variable) - is_object($variable) - - - diff --git a/app/vendor/sebastian/object-enumerator/.psalm/config.xml b/app/vendor/sebastian/object-enumerator/.psalm/config.xml deleted file mode 100644 index 2a4b16f22..000000000 --- a/app/vendor/sebastian/object-enumerator/.psalm/config.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - diff --git a/app/vendor/sebastian/object-enumerator/ChangeLog.md b/app/vendor/sebastian/object-enumerator/ChangeLog.md index 886554189..611c42d90 100644 --- a/app/vendor/sebastian/object-enumerator/ChangeLog.md +++ b/app/vendor/sebastian/object-enumerator/ChangeLog.md @@ -2,6 +2,30 @@ All notable changes to `sebastianbergmann/object-enumerator` are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles. +## [7.0.0] - 2025-02-07 + +### Removed + +* This component is no longer supported on PHP 8.2 + +## [6.0.1] - 2024-07-03 + +### Changed + +* This project now uses PHPStan instead of Psalm for static analysis + +## [6.0.0] - 2024-02-02 + +### Removed + +* This component is no longer supported on PHP 8.1 + +## [5.0.0] - 2023-02-03 + +### Removed + +* This component is no longer supported on PHP 7.3, PHP 7.4 and PHP 8.0 + ## [4.0.4] - 2020-10-26 ### Fixed @@ -74,6 +98,10 @@ All notable changes to `sebastianbergmann/object-enumerator` are documented in t * Initial release +[7.0.0]: https://github.com/sebastianbergmann/object-enumerator/compare/6.0...7.0.0 +[6.0.1]: https://github.com/sebastianbergmann/object-enumerator/compare/6.0.0...6.0.1 +[6.0.0]: https://github.com/sebastianbergmann/object-enumerator/compare/5.0...6.0.0 +[5.0.0]: https://github.com/sebastianbergmann/object-enumerator/compare/4.0.4...5.0.0 [4.0.4]: https://github.com/sebastianbergmann/object-enumerator/compare/4.0.3...4.0.4 [4.0.3]: https://github.com/sebastianbergmann/object-enumerator/compare/4.0.2...4.0.3 [4.0.2]: https://github.com/sebastianbergmann/object-enumerator/compare/4.0.1...4.0.2 diff --git a/app/vendor/sebastian/object-enumerator/LICENSE b/app/vendor/sebastian/object-enumerator/LICENSE index 1389ad396..ec89362e1 100644 --- a/app/vendor/sebastian/object-enumerator/LICENSE +++ b/app/vendor/sebastian/object-enumerator/LICENSE @@ -1,33 +1,29 @@ -Object Enumerator +BSD 3-Clause License -Copyright (c) 2016-2020, Sebastian Bergmann . +Copyright (c) 2016-2025, Sebastian Bergmann All rights reserved. Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: +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. +1. 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. +2. 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 Sebastian Bergmann nor the names of his - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. +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/app/vendor/sebastian/object-enumerator/README.md b/app/vendor/sebastian/object-enumerator/README.md index afca0177b..bb244faee 100644 --- a/app/vendor/sebastian/object-enumerator/README.md +++ b/app/vendor/sebastian/object-enumerator/README.md @@ -1,7 +1,8 @@ -# sebastian/object-enumerator - +[![Latest Stable Version](https://poser.pugx.org/sebastian/object-enumerator/v)](https://packagist.org/packages/sebastian/object-enumerator) [![CI Status](https://github.com/sebastianbergmann/object-enumerator/workflows/CI/badge.svg)](https://github.com/sebastianbergmann/object-enumerator/actions) -[![Type Coverage](https://shepherd.dev/github/sebastianbergmann/object-enumerator/coverage.svg)](https://shepherd.dev/github/sebastianbergmann/object-enumerator) +[![codecov](https://codecov.io/gh/sebastianbergmann/object-enumerator/branch/main/graph/badge.svg)](https://codecov.io/gh/sebastianbergmann/object-enumerator) + +# sebastian/object-enumerator Traverses array structures and object graphs to enumerate all referenced objects. diff --git a/app/vendor/sebastian/object-enumerator/SECURITY.md b/app/vendor/sebastian/object-enumerator/SECURITY.md new file mode 100644 index 000000000..d88ff0019 --- /dev/null +++ b/app/vendor/sebastian/object-enumerator/SECURITY.md @@ -0,0 +1,30 @@ +# Security Policy + +If you believe you have found a security vulnerability in the library that is developed in this repository, please report it to us through coordinated disclosure. + +**Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.** + +Instead, please email `sebastian@phpunit.de`. + +Please include as much of the information listed below as you can to help us better understand and resolve the issue: + +* The type of issue +* Full paths of source file(s) related to the manifestation of the issue +* The location of the affected source code (tag/branch/commit or direct URL) +* Any special configuration required to reproduce the issue +* Step-by-step instructions to reproduce the issue +* Proof-of-concept or exploit code (if possible) +* Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +## Web Context + +The library that is developed in this repository was either extracted from [PHPUnit](https://github.com/sebastianbergmann/phpunit) or developed specifically as a dependency for PHPUnit. + +The library is developed with a focus on development environments and the command-line. No specific testing or hardening with regard to using the library in an HTTP or web context or with untrusted input data is performed. The library might also contain functionality that intentionally exposes internal application data for debugging purposes. + +If the library is used in a web application, the application developer is responsible for filtering inputs or escaping outputs as necessary and for verifying that the used functionality is safe for use within the intended context. + +Vulnerabilities specific to the use outside a development context will be fixed as applicable, provided that the fix does not have an averse effect on the primary use case for development purposes. + diff --git a/app/vendor/sebastian/object-enumerator/composer.json b/app/vendor/sebastian/object-enumerator/composer.json index d68a21330..442e002e0 100644 --- a/app/vendor/sebastian/object-enumerator/composer.json +++ b/app/vendor/sebastian/object-enumerator/composer.json @@ -9,21 +9,25 @@ "email": "sebastian@phpunit.de" } ], + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy" + }, "prefer-stable": true, "config": { "platform": { - "php": "7.3.0" + "php": "8.3.0" }, "optimize-autoloader": true, "sort-packages": true }, "require": { - "php": ">=7.3", - "sebastian/object-reflector": "^2.0", - "sebastian/recursion-context": "^4.0" + "php": ">=8.3", + "sebastian/object-reflector": "^5.0", + "sebastian/recursion-context": "^7.0" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^12.0" }, "autoload": { "classmap": [ @@ -37,7 +41,7 @@ }, "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "7.0-dev" } } } diff --git a/app/vendor/sebastian/object-enumerator/phpunit.xml b/app/vendor/sebastian/object-enumerator/phpunit.xml deleted file mode 100644 index 7be976b13..000000000 --- a/app/vendor/sebastian/object-enumerator/phpunit.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - tests - - - - - - src - - - diff --git a/app/vendor/sebastian/object-enumerator/src/Enumerator.php b/app/vendor/sebastian/object-enumerator/src/Enumerator.php index de75d17c5..d67d86347 100644 --- a/app/vendor/sebastian/object-enumerator/src/Enumerator.php +++ b/app/vendor/sebastian/object-enumerator/src/Enumerator.php @@ -10,42 +10,20 @@ namespace SebastianBergmann\ObjectEnumerator; use function array_merge; -use function func_get_args; use function is_array; use function is_object; use SebastianBergmann\ObjectReflector\ObjectReflector; use SebastianBergmann\RecursionContext\Context; -/** - * Traverses array structures and object graphs - * to enumerate all referenced objects. - */ -class Enumerator +final class Enumerator { /** - * Returns an array of all objects referenced either - * directly or indirectly by a variable. - * - * @param array|object $variable + * @param array|object $variable * - * @return object[] + * @return list */ - public function enumerate($variable) + public function enumerate(array|object $variable, Context $processed = new Context): array { - if (!is_array($variable) && !is_object($variable)) { - throw new InvalidArgumentException; - } - - if (isset(func_get_args()[1])) { - if (!func_get_args()[1] instanceof Context) { - throw new InvalidArgumentException; - } - - $processed = func_get_args()[1]; - } else { - $processed = new Context; - } - $objects = []; if ($processed->contains($variable)) { @@ -53,34 +31,39 @@ public function enumerate($variable) } $array = $variable; + + /* @noinspection UnusedFunctionResultInspection */ $processed->add($variable); if (is_array($variable)) { + /** @phpstan-ignore foreach.nonIterable */ foreach ($array as $element) { if (!is_array($element) && !is_object($element)) { continue; } + /** @noinspection SlowArrayOperationsInLoopInspection */ $objects = array_merge( $objects, - $this->enumerate($element, $processed) + $this->enumerate($element, $processed), ); } - } else { - $objects[] = $variable; - $reflector = new ObjectReflector; + return $objects; + } - foreach ($reflector->getAttributes($variable) as $value) { - if (!is_array($value) && !is_object($value)) { - continue; - } + $objects[] = $variable; - $objects = array_merge( - $objects, - $this->enumerate($value, $processed) - ); + foreach ((new ObjectReflector)->getProperties($variable) as $value) { + if (!is_array($value) && !is_object($value)) { + continue; } + + /** @noinspection SlowArrayOperationsInLoopInspection */ + $objects = array_merge( + $objects, + $this->enumerate($value, $processed), + ); } return $objects; diff --git a/app/vendor/sebastian/object-enumerator/src/Exception.php b/app/vendor/sebastian/object-enumerator/src/Exception.php deleted file mode 100644 index 2f09d70aa..000000000 --- a/app/vendor/sebastian/object-enumerator/src/Exception.php +++ /dev/null @@ -1,16 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace SebastianBergmann\ObjectEnumerator; - -use Throwable; - -interface Exception extends Throwable -{ -} diff --git a/app/vendor/sebastian/object-enumerator/src/InvalidArgumentException.php b/app/vendor/sebastian/object-enumerator/src/InvalidArgumentException.php deleted file mode 100644 index ce2037cdb..000000000 --- a/app/vendor/sebastian/object-enumerator/src/InvalidArgumentException.php +++ /dev/null @@ -1,14 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace SebastianBergmann\ObjectEnumerator; - -class InvalidArgumentException extends \InvalidArgumentException implements Exception -{ -} diff --git a/app/vendor/sebastian/object-reflector/.psalm/baseline.xml b/app/vendor/sebastian/object-reflector/.psalm/baseline.xml deleted file mode 100644 index 965c12757..000000000 --- a/app/vendor/sebastian/object-reflector/.psalm/baseline.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - is_object($object) - - - diff --git a/app/vendor/sebastian/object-reflector/.psalm/config.xml b/app/vendor/sebastian/object-reflector/.psalm/config.xml deleted file mode 100644 index 2a4b16f22..000000000 --- a/app/vendor/sebastian/object-reflector/.psalm/config.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - diff --git a/app/vendor/sebastian/object-reflector/ChangeLog.md b/app/vendor/sebastian/object-reflector/ChangeLog.md index 7fa62e90f..ddc1ed642 100644 --- a/app/vendor/sebastian/object-reflector/ChangeLog.md +++ b/app/vendor/sebastian/object-reflector/ChangeLog.md @@ -2,6 +2,34 @@ All notable changes to `sebastianbergmann/object-reflector` are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles. +## [5.0.0] - 2025-02-07 + +### Removed + +* This component is no longer supported on PHP 8.2 + +## [4.0.1] - 2024-07-03 + +### Changed + +* This project now uses PHPStan instead of Psalm for static analysis + +## [4.0.0] - 2024-02-02 + +### Removed + +* This component is no longer supported on PHP 8.1 + +## [3.0.0] - 2023-02-03 + +### Changed + +* `ObjectReflector::getAttributes()` has been renamed to `ObjectReflector::getProperties()` + +### Removed + +* This component is no longer supported on PHP 7.3, PHP 7.4 and PHP 8.0 + ## [2.0.4] - 2020-10-26 ### Fixed @@ -46,6 +74,10 @@ All notable changes to `sebastianbergmann/object-reflector` are documented in th * Initial release +[5.0.0]: https://github.com/sebastianbergmann/object-reflector/compare/4.0...5.0.0 +[4.0.1]: https://github.com/sebastianbergmann/object-reflector/compare/4.0.0...4.0.1 +[4.0.0]: https://github.com/sebastianbergmann/object-reflector/compare/3.0...4.0.0 +[3.0.0]: https://github.com/sebastianbergmann/object-reflector/compare/2.0.4...3.0.0 [2.0.4]: https://github.com/sebastianbergmann/object-reflector/compare/2.0.3...2.0.4 [2.0.3]: https://github.com/sebastianbergmann/object-reflector/compare/2.0.2...2.0.3 [2.0.2]: https://github.com/sebastianbergmann/object-reflector/compare/2.0.1...2.0.2 diff --git a/app/vendor/sebastian/object-reflector/LICENSE b/app/vendor/sebastian/object-reflector/LICENSE index a80c16192..e148db3fc 100644 --- a/app/vendor/sebastian/object-reflector/LICENSE +++ b/app/vendor/sebastian/object-reflector/LICENSE @@ -1,33 +1,29 @@ -Object Reflector +BSD 3-Clause License -Copyright (c) 2017-2020, Sebastian Bergmann . +Copyright (c) 2017-2025, Sebastian Bergmann All rights reserved. Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: +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. +1. 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. +2. 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 Sebastian Bergmann nor the names of his - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. +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/app/vendor/sebastian/object-reflector/README.md b/app/vendor/sebastian/object-reflector/README.md index b7d5ae95f..1aca57343 100644 --- a/app/vendor/sebastian/object-reflector/README.md +++ b/app/vendor/sebastian/object-reflector/README.md @@ -1,9 +1,10 @@ -# sebastian/object-reflector - +[![Latest Stable Version](https://poser.pugx.org/sebastian/object-reflector/v)](https://packagist.org/packages/sebastian/object-reflector) [![CI Status](https://github.com/sebastianbergmann/object-reflector/workflows/CI/badge.svg)](https://github.com/sebastianbergmann/object-reflector/actions) -[![Type Coverage](https://shepherd.dev/github/sebastianbergmann/object-reflector/coverage.svg)](https://shepherd.dev/github/sebastianbergmann/object-reflector) +[![codecov](https://codecov.io/gh/sebastianbergmann/object-reflector/branch/main/graph/badge.svg)](https://codecov.io/gh/sebastianbergmann/object-reflector) + +# sebastian/object-reflector -Allows reflection of object attributes, including inherited and non-public ones. +Allows reflection of object properties, including inherited and private as well as protected ones. ## Installation diff --git a/app/vendor/sebastian/object-reflector/SECURITY.md b/app/vendor/sebastian/object-reflector/SECURITY.md new file mode 100644 index 000000000..d88ff0019 --- /dev/null +++ b/app/vendor/sebastian/object-reflector/SECURITY.md @@ -0,0 +1,30 @@ +# Security Policy + +If you believe you have found a security vulnerability in the library that is developed in this repository, please report it to us through coordinated disclosure. + +**Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.** + +Instead, please email `sebastian@phpunit.de`. + +Please include as much of the information listed below as you can to help us better understand and resolve the issue: + +* The type of issue +* Full paths of source file(s) related to the manifestation of the issue +* The location of the affected source code (tag/branch/commit or direct URL) +* Any special configuration required to reproduce the issue +* Step-by-step instructions to reproduce the issue +* Proof-of-concept or exploit code (if possible) +* Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +## Web Context + +The library that is developed in this repository was either extracted from [PHPUnit](https://github.com/sebastianbergmann/phpunit) or developed specifically as a dependency for PHPUnit. + +The library is developed with a focus on development environments and the command-line. No specific testing or hardening with regard to using the library in an HTTP or web context or with untrusted input data is performed. The library might also contain functionality that intentionally exposes internal application data for debugging purposes. + +If the library is used in a web application, the application developer is responsible for filtering inputs or escaping outputs as necessary and for verifying that the used functionality is safe for use within the intended context. + +Vulnerabilities specific to the use outside a development context will be fixed as applicable, provided that the fix does not have an averse effect on the primary use case for development purposes. + diff --git a/app/vendor/sebastian/object-reflector/composer.json b/app/vendor/sebastian/object-reflector/composer.json index 36a337885..d9286e951 100644 --- a/app/vendor/sebastian/object-reflector/composer.json +++ b/app/vendor/sebastian/object-reflector/composer.json @@ -9,19 +9,23 @@ "email": "sebastian@phpunit.de" } ], + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "security": "https://github.com/sebastianbergmann/object-reflector/security/policy" + }, "prefer-stable": true, "config": { "platform": { - "php": "7.3.0" + "php": "8.3.0" }, "optimize-autoloader": true, "sort-packages": true }, "require": { - "php": ">=7.3" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^12.0" }, "autoload": { "classmap": [ @@ -35,7 +39,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "5.0-dev" } } } diff --git a/app/vendor/sebastian/object-reflector/src/Exception.php b/app/vendor/sebastian/object-reflector/src/Exception.php deleted file mode 100644 index 36f8efeca..000000000 --- a/app/vendor/sebastian/object-reflector/src/Exception.php +++ /dev/null @@ -1,16 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace SebastianBergmann\ObjectReflector; - -use Throwable; - -interface Exception extends Throwable -{ -} diff --git a/app/vendor/sebastian/object-reflector/src/InvalidArgumentException.php b/app/vendor/sebastian/object-reflector/src/InvalidArgumentException.php deleted file mode 100644 index 34b4cca19..000000000 --- a/app/vendor/sebastian/object-reflector/src/InvalidArgumentException.php +++ /dev/null @@ -1,14 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace SebastianBergmann\ObjectReflector; - -class InvalidArgumentException extends \InvalidArgumentException implements Exception -{ -} diff --git a/app/vendor/sebastian/object-reflector/src/ObjectReflector.php b/app/vendor/sebastian/object-reflector/src/ObjectReflector.php index 4abb5f55f..919118fed 100644 --- a/app/vendor/sebastian/object-reflector/src/ObjectReflector.php +++ b/app/vendor/sebastian/object-reflector/src/ObjectReflector.php @@ -11,41 +11,31 @@ use function count; use function explode; -use function get_class; -use function is_object; -class ObjectReflector +final class ObjectReflector { /** - * @param object $object - * - * @throws InvalidArgumentException + * @return array */ - public function getAttributes($object): array + public function getProperties(object $object): array { - if (!is_object($object)) { - throw new InvalidArgumentException; - } - - $attributes = []; - $className = get_class($object); + $properties = []; + $className = $object::class; foreach ((array) $object as $name => $value) { $name = explode("\0", (string) $name); if (count($name) === 1) { $name = $name[0]; + } elseif ($name[1] !== $className) { + $name = $name[1] . '::' . $name[2]; } else { - if ($name[1] !== $className) { - $name = $name[1] . '::' . $name[2]; - } else { - $name = $name[2]; - } + $name = $name[2]; } - $attributes[$name] = $value; + $properties[$name] = $value; } - return $attributes; + return $properties; } } diff --git a/app/vendor/sebastian/recursion-context/ChangeLog.md b/app/vendor/sebastian/recursion-context/ChangeLog.md index c1a76516b..854244fa4 100644 --- a/app/vendor/sebastian/recursion-context/ChangeLog.md +++ b/app/vendor/sebastian/recursion-context/ChangeLog.md @@ -2,6 +2,54 @@ All notable changes are documented in this file using the [Keep a CHANGELOG](https://keepachangelog.com/) principles. +## [7.0.1] - 2025-08-13 + +### Changed + +* Do not use `SplObjectStorage` methods that will be deprecated in PHP 8.5 + +## [7.0.0] - 2025-02-07 + +### Removed + +* This component is no longer supported on PHP 8.2 + +## [6.0.3] - 2025-08-13 + +### Changed + +* Do not use `SplObjectStorage` methods that will be deprecated in PHP 8.5 + +## [6.0.2] - 2024-07-03 + +### Changed + +* This project now uses PHPStan instead of Psalm for static analysis + +## [6.0.1] - 2024-06-17 + +### Changed + +* [#30](https://github.com/sebastianbergmann/recursion-context/pull/30): Use more efficient `spl_object_id()` over `spl_object_hash()` + +## [6.0.0] - 2024-02-02 + +### Removed + +* This component is no longer supported on PHP 8.1 + +## [5.0.1] - 2025-08-10 + +### Changed + +* Do not use `SplObjectStorage` methods that will be deprecated in PHP 8.5 + +## [5.0.0] - 2023-02-03 + +### Removed + +* This component is no longer supported on PHP 7.3, PHP 7.4 and PHP 8.0 + ## [4.0.5] - 2023-02-03 ### Fixed @@ -33,6 +81,14 @@ All notable changes are documented in this file using the [Keep a CHANGELOG](htt * Tests etc. are now ignored for archive exports +[7.0.1]: https://github.com/sebastianbergmann/recursion-context/compare/7.0.0...7.0.1 +[7.0.0]: https://github.com/sebastianbergmann/recursion-context/compare/6.0...7.0.0 +[6.0.3]: https://github.com/sebastianbergmann/recursion-context/compare/6.0.2...6.0.3 +[6.0.2]: https://github.com/sebastianbergmann/recursion-context/compare/6.0.1...6.0.2 +[6.0.1]: https://github.com/sebastianbergmann/recursion-context/compare/6.0.0...6.0.1 +[6.0.0]: https://github.com/sebastianbergmann/recursion-context/compare/5.0...6.0.0 +[5.0.1]: https://github.com/sebastianbergmann/recursion-context/compare/5.0.0...5.0.1 +[5.0.0]: https://github.com/sebastianbergmann/recursion-context/compare/4.0.5...5.0.0 [4.0.5]: https://github.com/sebastianbergmann/recursion-context/compare/4.0.4...4.0.5 [4.0.4]: https://github.com/sebastianbergmann/recursion-context/compare/4.0.3...4.0.4 [4.0.3]: https://github.com/sebastianbergmann/recursion-context/compare/4.0.2...4.0.3 diff --git a/app/vendor/sebastian/recursion-context/LICENSE b/app/vendor/sebastian/recursion-context/LICENSE index 4e9b63712..c5268a916 100644 --- a/app/vendor/sebastian/recursion-context/LICENSE +++ b/app/vendor/sebastian/recursion-context/LICENSE @@ -1,33 +1,29 @@ -Recursion Context +BSD 3-Clause License -Copyright (c) 2002-2022, Sebastian Bergmann . +Copyright (c) 2002-2025, Sebastian Bergmann All rights reserved. Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: +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. +1. 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. +2. 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 Sebastian Bergmann nor the names of his - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. +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/app/vendor/sebastian/recursion-context/README.md b/app/vendor/sebastian/recursion-context/README.md index 8e4d2a084..f1d87b614 100644 --- a/app/vendor/sebastian/recursion-context/README.md +++ b/app/vendor/sebastian/recursion-context/README.md @@ -1,7 +1,8 @@ -# sebastian/recursion-context - +[![Latest Stable Version](https://poser.pugx.org/sebastian/recursion-context/v)](https://packagist.org/packages/sebastian/recursion-context) [![CI Status](https://github.com/sebastianbergmann/recursion-context/workflows/CI/badge.svg)](https://github.com/sebastianbergmann/recursion-context/actions) -[![Type Coverage](https://shepherd.dev/github/sebastianbergmann/recursion-context/coverage.svg)](https://shepherd.dev/github/sebastianbergmann/recursion-context) +[![codecov](https://codecov.io/gh/sebastianbergmann/recursion-context/branch/main/graph/badge.svg)](https://codecov.io/gh/sebastianbergmann/recursion-context) + +# sebastian/recursion-context ## Installation diff --git a/app/vendor/sebastian/recursion-context/SECURITY.md b/app/vendor/sebastian/recursion-context/SECURITY.md new file mode 100644 index 000000000..d88ff0019 --- /dev/null +++ b/app/vendor/sebastian/recursion-context/SECURITY.md @@ -0,0 +1,30 @@ +# Security Policy + +If you believe you have found a security vulnerability in the library that is developed in this repository, please report it to us through coordinated disclosure. + +**Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.** + +Instead, please email `sebastian@phpunit.de`. + +Please include as much of the information listed below as you can to help us better understand and resolve the issue: + +* The type of issue +* Full paths of source file(s) related to the manifestation of the issue +* The location of the affected source code (tag/branch/commit or direct URL) +* Any special configuration required to reproduce the issue +* Step-by-step instructions to reproduce the issue +* Proof-of-concept or exploit code (if possible) +* Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +## Web Context + +The library that is developed in this repository was either extracted from [PHPUnit](https://github.com/sebastianbergmann/phpunit) or developed specifically as a dependency for PHPUnit. + +The library is developed with a focus on development environments and the command-line. No specific testing or hardening with regard to using the library in an HTTP or web context or with untrusted input data is performed. The library might also contain functionality that intentionally exposes internal application data for debugging purposes. + +If the library is used in a web application, the application developer is responsible for filtering inputs or escaping outputs as necessary and for verifying that the used functionality is safe for use within the intended context. + +Vulnerabilities specific to the use outside a development context will be fixed as applicable, provided that the fix does not have an averse effect on the primary use case for development purposes. + diff --git a/app/vendor/sebastian/recursion-context/composer.json b/app/vendor/sebastian/recursion-context/composer.json index cbd39f76c..f37a207b3 100644 --- a/app/vendor/sebastian/recursion-context/composer.json +++ b/app/vendor/sebastian/recursion-context/composer.json @@ -17,19 +17,23 @@ "email": "aharvey@php.net" } ], + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy" + }, "prefer-stable": true, "config": { "platform": { - "php": "7.3.0" + "php": "8.3.0" }, "optimize-autoloader": true, "sort-packages": true }, "require": { - "php": ">=7.3" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^12.0" }, "autoload": { "classmap": [ @@ -38,7 +42,7 @@ }, "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "7.0-dev" } } } diff --git a/app/vendor/sebastian/recursion-context/src/Context.php b/app/vendor/sebastian/recursion-context/src/Context.php index a647938c8..fbeac8cda 100644 --- a/app/vendor/sebastian/recursion-context/src/Context.php +++ b/app/vendor/sebastian/recursion-context/src/Context.php @@ -16,33 +16,25 @@ use function array_slice; use function count; use function is_array; -use function is_object; +use function is_int; use function random_int; -use function spl_object_hash; +use function spl_object_id; use SplObjectStorage; -/** - * A context containing previously processed arrays and objects - * when recursively processing a value. - */ final class Context { /** - * @var array[] + * @var list> */ - private $arrays; + private array $arrays = []; /** - * @var SplObjectStorage + * @var SplObjectStorage */ - private $objects; + private SplObjectStorage $objects; - /** - * Initialises the context. - */ public function __construct() { - $this->arrays = []; $this->objects = new SplObjectStorage; } @@ -60,65 +52,42 @@ public function __destruct() } /** - * Adds a value to the context. - * - * @param array|object $value the value to add - * - * @throws InvalidArgumentException Thrown if $value is not an array or object + * @template T of object|array * - * @return bool|int|string the ID of the stored value, either as a string or integer + * @param T $value * - * @psalm-template T - * @psalm-param T $value * @param-out T $value */ - public function add(&$value) + public function add(array|object &$value): int { if (is_array($value)) { + /* @phpstan-ignore paramOut.type */ return $this->addArray($value); } - if (is_object($value)) { - return $this->addObject($value); - } - - throw new InvalidArgumentException( - 'Only arrays and objects are supported' - ); + return $this->addObject($value); } /** - * Checks if the given value exists within the context. - * - * @param array|object $value the value to check + * @template T of object|array * - * @throws InvalidArgumentException Thrown if $value is not an array or object + * @param T $value * - * @return false|int|string the string or integer ID of the stored value if it has already been seen, or false if the value is not stored - * - * @psalm-template T - * @psalm-param T $value * @param-out T $value */ - public function contains(&$value) + public function contains(array|object &$value): false|int { if (is_array($value)) { return $this->containsArray($value); } - if (is_object($value)) { - return $this->containsObject($value); - } - - throw new InvalidArgumentException( - 'Only arrays and objects are supported' - ); + return $this->containsObject($value); } /** - * @return bool|int + * @param array $array */ - private function addArray(array &$array) + private function addArray(array &$array): int { $key = $this->containsArray($array); @@ -132,18 +101,22 @@ private function addArray(array &$array) if (!array_key_exists(PHP_INT_MAX, $array) && !array_key_exists(PHP_INT_MAX - 1, $array)) { $array[] = $key; $array[] = $this->objects; - } else { /* cover the improbable case too */ - /* Note that array_slice (used in containsArray) will return the - * last two values added *not necessarily* the highest integer - * keys in the array, so the order of these writes to $array - * is important, but the actual keys used is not. */ + } else { + /* Cover the improbable case, too. + * + * Note that array_slice() (used in containsArray()) will return the + * last two values added, *not necessarily* the highest integer keys + * in the array. Therefore, the order of these writes to $array is + * important, but the actual keys used is not. */ do { + /** @noinspection PhpUnhandledExceptionInspection */ $key = random_int(PHP_INT_MIN, PHP_INT_MAX); } while (array_key_exists($key, $array)); $array[$key] = $key; do { + /** @noinspection PhpUnhandledExceptionInspection */ $key = random_int(PHP_INT_MIN, PHP_INT_MAX); } while (array_key_exists($key, $array)); @@ -153,37 +126,36 @@ private function addArray(array &$array) return $key; } - /** - * @param object $object - */ - private function addObject($object): string + private function addObject(object $object): int { - if (!$this->objects->contains($object)) { - $this->objects->attach($object); + if (!$this->objects->offsetExists($object)) { + $this->objects->offsetSet($object); } - return spl_object_hash($object); + return spl_object_id($object); } /** - * @return false|int + * @param array $array */ - private function containsArray(array &$array) + private function containsArray(array $array): false|int { $end = array_slice($array, -2); - return isset($end[1]) && $end[1] === $this->objects ? $end[0] : false; + if (isset($end[1]) && + $end[1] === $this->objects && + isset($end[0]) && + is_int($end[0])) { + return $end[0]; + } + + return false; } - /** - * @param object $value - * - * @return false|string - */ - private function containsObject($value) + private function containsObject(object $value): false|int { - if ($this->objects->contains($value)) { - return spl_object_hash($value); + if ($this->objects->offsetExists($value)) { + return spl_object_id($value); } return false; diff --git a/app/vendor/sebastian/recursion-context/src/Exception.php b/app/vendor/sebastian/recursion-context/src/Exception.php deleted file mode 100644 index 9389a271f..000000000 --- a/app/vendor/sebastian/recursion-context/src/Exception.php +++ /dev/null @@ -1,16 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace SebastianBergmann\RecursionContext; - -use Throwable; - -interface Exception extends Throwable -{ -} diff --git a/app/vendor/sebastian/recursion-context/src/InvalidArgumentException.php b/app/vendor/sebastian/recursion-context/src/InvalidArgumentException.php deleted file mode 100644 index 93d150bc3..000000000 --- a/app/vendor/sebastian/recursion-context/src/InvalidArgumentException.php +++ /dev/null @@ -1,14 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace SebastianBergmann\RecursionContext; - -final class InvalidArgumentException extends \InvalidArgumentException implements Exception -{ -} diff --git a/app/vendor/sebastian/resource-operations/ChangeLog.md b/app/vendor/sebastian/resource-operations/ChangeLog.md deleted file mode 100644 index eae062908..000000000 --- a/app/vendor/sebastian/resource-operations/ChangeLog.md +++ /dev/null @@ -1,59 +0,0 @@ -# ChangeLog - -All notable changes are documented in this file using the [Keep a CHANGELOG](https://keepachangelog.com/) principles. - -## [3.0.4] - 2024-03-14 - -No functional changes. - -## [3.0.3] - 2020-09-28 - -### Changed - -* Changed PHP version constraint in `composer.json` from `^7.3 || ^8.0` to `>=7.3` - -## [3.0.2] - 2020-06-26 - -### Added - -* This component is now supported on PHP 8 - -## [3.0.1] - 2020-06-15 - -### Changed - -* Tests etc. are now ignored for archive exports - -## [3.0.0] - 2020-02-07 - -### Removed - -* This component is no longer supported on PHP 7.1 and PHP 7.2 - -## [2.0.1] - 2018-10-04 - -### Fixed - -* Functions and methods with nullable parameters of type `resource` are now also considered - -## [2.0.0] - 2018-09-27 - -### Changed - -* [FunctionSignatureMap.php](https://raw.githubusercontent.com/phan/phan/master/src/Phan/Language/Internal/FunctionSignatureMap.php) from `phan/phan` is now used instead of [arginfo.php](https://raw.githubusercontent.com/rlerdorf/phan/master/includes/arginfo.php) from `rlerdorf/phan` - -### Removed - -* This component is no longer supported on PHP 5.6 and PHP 7.0 - -## 1.0.0 - 2015-07-28 - -* Initial release - -[3.0.4]: https://github.com/sebastianbergmann/comparator/resource-operations/3.0.3...3.0.4 -[3.0.3]: https://github.com/sebastianbergmann/comparator/resource-operations/3.0.2...3.0.3 -[3.0.2]: https://github.com/sebastianbergmann/comparator/resource-operations/3.0.1...3.0.2 -[3.0.1]: https://github.com/sebastianbergmann/comparator/resource-operations/3.0.0...3.0.1 -[3.0.0]: https://github.com/sebastianbergmann/comparator/resource-operations/2.0.1...3.0.0 -[2.0.1]: https://github.com/sebastianbergmann/comparator/resource-operations/2.0.0...2.0.1 -[2.0.0]: https://github.com/sebastianbergmann/comparator/resource-operations/1.0.0...2.0.0 diff --git a/app/vendor/sebastian/resource-operations/LICENSE b/app/vendor/sebastian/resource-operations/LICENSE deleted file mode 100644 index dccd6b074..000000000 --- a/app/vendor/sebastian/resource-operations/LICENSE +++ /dev/null @@ -1,33 +0,0 @@ -Resource Operations - -Copyright (c) 2015-2020, Sebastian Bergmann . -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 Sebastian Bergmann nor the names of his - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. diff --git a/app/vendor/sebastian/resource-operations/README.md b/app/vendor/sebastian/resource-operations/README.md deleted file mode 100644 index 88b05ccb6..000000000 --- a/app/vendor/sebastian/resource-operations/README.md +++ /dev/null @@ -1,14 +0,0 @@ -# Resource Operations - -Provides a list of PHP built-in functions that operate on resources. - -## Installation - -You can add this library as a local, per-project dependency to your project using [Composer](https://getcomposer.org/): - - composer require sebastian/resource-operations - -If you only need this library during development, for instance to run your project's test suite, then you should add it as a development-time dependency: - - composer require --dev sebastian/resource-operations - diff --git a/app/vendor/sebastian/resource-operations/build/generate.php b/app/vendor/sebastian/resource-operations/build/generate.php deleted file mode 100755 index 0354dc45f..000000000 --- a/app/vendor/sebastian/resource-operations/build/generate.php +++ /dev/null @@ -1,65 +0,0 @@ -#!/usr/bin/env php - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -$functions = require __DIR__ . '/FunctionSignatureMap.php'; -$resourceFunctions = []; - -foreach ($functions as $function => $arguments) { - foreach ($arguments as $argument) { - if (strpos($argument, '?') === 0) { - $argument = substr($argument, 1); - } - - if ($argument === 'resource') { - $resourceFunctions[] = explode('\'', $function)[0]; - } - } -} - -$resourceFunctions = array_unique($resourceFunctions); -sort($resourceFunctions); - -$buffer = << - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace SebastianBergmann\ResourceOperations; - -final class ResourceOperations -{ - /** - * @return string[] - */ - public static function getFunctions(): array - { - return [ - -EOT; - -foreach ($resourceFunctions as $function) { - $buffer .= sprintf(" '%s',\n", $function); -} - -$buffer .= <<< EOT - ]; - } -} - -EOT; - -file_put_contents(__DIR__ . '/../src/ResourceOperations.php', $buffer); - diff --git a/app/vendor/sebastian/resource-operations/composer.json b/app/vendor/sebastian/resource-operations/composer.json deleted file mode 100644 index 77e4baee9..000000000 --- a/app/vendor/sebastian/resource-operations/composer.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "name": "sebastian/resource-operations", - "description": "Provides a list of PHP built-in functions that operate on resources", - "homepage": "https://www.github.com/sebastianbergmann/resource-operations", - "license": "BSD-3-Clause", - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "prefer-stable": true, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.0" - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "config": { - "platform": { - "php": "7.3.0" - }, - "optimize-autoloader": true, - "sort-packages": true - }, - "extra": { - "branch-alias": { - "dev-main": "3.0-dev" - } - }, - "abandoned": false -} - diff --git a/app/vendor/sebastian/resource-operations/src/ResourceOperations.php b/app/vendor/sebastian/resource-operations/src/ResourceOperations.php deleted file mode 100644 index f3911f36c..000000000 --- a/app/vendor/sebastian/resource-operations/src/ResourceOperations.php +++ /dev/null @@ -1,2232 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace SebastianBergmann\ResourceOperations; - -final class ResourceOperations -{ - /** - * @return string[] - */ - public static function getFunctions(): array - { - return [ - 'Directory::close', - 'Directory::read', - 'Directory::rewind', - 'DirectoryIterator::openFile', - 'FilesystemIterator::openFile', - 'Gmagick::readimagefile', - 'HttpResponse::getRequestBodyStream', - 'HttpResponse::getStream', - 'HttpResponse::setStream', - 'Imagick::pingImageFile', - 'Imagick::readImageFile', - 'Imagick::writeImageFile', - 'Imagick::writeImagesFile', - 'MongoGridFSCursor::__construct', - 'MongoGridFSFile::getResource', - 'MysqlndUhConnection::stmtInit', - 'MysqlndUhConnection::storeResult', - 'MysqlndUhConnection::useResult', - 'PDF_activate_item', - 'PDF_add_launchlink', - 'PDF_add_locallink', - 'PDF_add_nameddest', - 'PDF_add_note', - 'PDF_add_pdflink', - 'PDF_add_table_cell', - 'PDF_add_textflow', - 'PDF_add_thumbnail', - 'PDF_add_weblink', - 'PDF_arc', - 'PDF_arcn', - 'PDF_attach_file', - 'PDF_begin_document', - 'PDF_begin_font', - 'PDF_begin_glyph', - 'PDF_begin_item', - 'PDF_begin_layer', - 'PDF_begin_page', - 'PDF_begin_page_ext', - 'PDF_begin_pattern', - 'PDF_begin_template', - 'PDF_begin_template_ext', - 'PDF_circle', - 'PDF_clip', - 'PDF_close', - 'PDF_close_image', - 'PDF_close_pdi', - 'PDF_close_pdi_page', - 'PDF_closepath', - 'PDF_closepath_fill_stroke', - 'PDF_closepath_stroke', - 'PDF_concat', - 'PDF_continue_text', - 'PDF_create_3dview', - 'PDF_create_action', - 'PDF_create_annotation', - 'PDF_create_bookmark', - 'PDF_create_field', - 'PDF_create_fieldgroup', - 'PDF_create_gstate', - 'PDF_create_pvf', - 'PDF_create_textflow', - 'PDF_curveto', - 'PDF_define_layer', - 'PDF_delete', - 'PDF_delete_pvf', - 'PDF_delete_table', - 'PDF_delete_textflow', - 'PDF_encoding_set_char', - 'PDF_end_document', - 'PDF_end_font', - 'PDF_end_glyph', - 'PDF_end_item', - 'PDF_end_layer', - 'PDF_end_page', - 'PDF_end_page_ext', - 'PDF_end_pattern', - 'PDF_end_template', - 'PDF_endpath', - 'PDF_fill', - 'PDF_fill_imageblock', - 'PDF_fill_pdfblock', - 'PDF_fill_stroke', - 'PDF_fill_textblock', - 'PDF_findfont', - 'PDF_fit_image', - 'PDF_fit_pdi_page', - 'PDF_fit_table', - 'PDF_fit_textflow', - 'PDF_fit_textline', - 'PDF_get_apiname', - 'PDF_get_buffer', - 'PDF_get_errmsg', - 'PDF_get_errnum', - 'PDF_get_parameter', - 'PDF_get_pdi_parameter', - 'PDF_get_pdi_value', - 'PDF_get_value', - 'PDF_info_font', - 'PDF_info_matchbox', - 'PDF_info_table', - 'PDF_info_textflow', - 'PDF_info_textline', - 'PDF_initgraphics', - 'PDF_lineto', - 'PDF_load_3ddata', - 'PDF_load_font', - 'PDF_load_iccprofile', - 'PDF_load_image', - 'PDF_makespotcolor', - 'PDF_moveto', - 'PDF_new', - 'PDF_open_ccitt', - 'PDF_open_file', - 'PDF_open_image', - 'PDF_open_image_file', - 'PDF_open_memory_image', - 'PDF_open_pdi', - 'PDF_open_pdi_document', - 'PDF_open_pdi_page', - 'PDF_pcos_get_number', - 'PDF_pcos_get_stream', - 'PDF_pcos_get_string', - 'PDF_place_image', - 'PDF_place_pdi_page', - 'PDF_process_pdi', - 'PDF_rect', - 'PDF_restore', - 'PDF_resume_page', - 'PDF_rotate', - 'PDF_save', - 'PDF_scale', - 'PDF_set_border_color', - 'PDF_set_border_dash', - 'PDF_set_border_style', - 'PDF_set_gstate', - 'PDF_set_info', - 'PDF_set_layer_dependency', - 'PDF_set_parameter', - 'PDF_set_text_pos', - 'PDF_set_value', - 'PDF_setcolor', - 'PDF_setdash', - 'PDF_setdashpattern', - 'PDF_setflat', - 'PDF_setfont', - 'PDF_setgray', - 'PDF_setgray_fill', - 'PDF_setgray_stroke', - 'PDF_setlinecap', - 'PDF_setlinejoin', - 'PDF_setlinewidth', - 'PDF_setmatrix', - 'PDF_setmiterlimit', - 'PDF_setrgbcolor', - 'PDF_setrgbcolor_fill', - 'PDF_setrgbcolor_stroke', - 'PDF_shading', - 'PDF_shading_pattern', - 'PDF_shfill', - 'PDF_show', - 'PDF_show_boxed', - 'PDF_show_xy', - 'PDF_skew', - 'PDF_stringwidth', - 'PDF_stroke', - 'PDF_suspend_page', - 'PDF_translate', - 'PDF_utf16_to_utf8', - 'PDF_utf32_to_utf16', - 'PDF_utf8_to_utf16', - 'PDO::pgsqlLOBOpen', - 'RarEntry::getStream', - 'SQLite3::openBlob', - 'SWFMovie::saveToFile', - 'SplFileInfo::openFile', - 'SplFileObject::openFile', - 'SplTempFileObject::openFile', - 'V8Js::compileString', - 'V8Js::executeScript', - 'Vtiful\Kernel\Excel::setColumn', - 'Vtiful\Kernel\Excel::setRow', - 'Vtiful\Kernel\Format::align', - 'Vtiful\Kernel\Format::bold', - 'Vtiful\Kernel\Format::italic', - 'Vtiful\Kernel\Format::underline', - 'XMLWriter::openMemory', - 'XMLWriter::openURI', - 'ZipArchive::getStream', - 'Zookeeper::setLogStream', - 'apc_bin_dumpfile', - 'apc_bin_loadfile', - 'bbcode_add_element', - 'bbcode_add_smiley', - 'bbcode_create', - 'bbcode_destroy', - 'bbcode_parse', - 'bbcode_set_arg_parser', - 'bbcode_set_flags', - 'bcompiler_read', - 'bcompiler_write_class', - 'bcompiler_write_constant', - 'bcompiler_write_exe_footer', - 'bcompiler_write_file', - 'bcompiler_write_footer', - 'bcompiler_write_function', - 'bcompiler_write_functions_from_file', - 'bcompiler_write_header', - 'bcompiler_write_included_filename', - 'bzclose', - 'bzerrno', - 'bzerror', - 'bzerrstr', - 'bzflush', - 'bzopen', - 'bzread', - 'bzwrite', - 'cairo_surface_write_to_png', - 'closedir', - 'copy', - 'crack_closedict', - 'crack_opendict', - 'cubrid_bind', - 'cubrid_close_prepare', - 'cubrid_close_request', - 'cubrid_col_get', - 'cubrid_col_size', - 'cubrid_column_names', - 'cubrid_column_types', - 'cubrid_commit', - 'cubrid_connect', - 'cubrid_connect_with_url', - 'cubrid_current_oid', - 'cubrid_db_parameter', - 'cubrid_disconnect', - 'cubrid_drop', - 'cubrid_fetch', - 'cubrid_free_result', - 'cubrid_get', - 'cubrid_get_autocommit', - 'cubrid_get_charset', - 'cubrid_get_class_name', - 'cubrid_get_db_parameter', - 'cubrid_get_query_timeout', - 'cubrid_get_server_info', - 'cubrid_insert_id', - 'cubrid_is_instance', - 'cubrid_lob2_bind', - 'cubrid_lob2_close', - 'cubrid_lob2_export', - 'cubrid_lob2_import', - 'cubrid_lob2_new', - 'cubrid_lob2_read', - 'cubrid_lob2_seek', - 'cubrid_lob2_seek64', - 'cubrid_lob2_size', - 'cubrid_lob2_size64', - 'cubrid_lob2_tell', - 'cubrid_lob2_tell64', - 'cubrid_lob2_write', - 'cubrid_lob_export', - 'cubrid_lob_get', - 'cubrid_lob_send', - 'cubrid_lob_size', - 'cubrid_lock_read', - 'cubrid_lock_write', - 'cubrid_move_cursor', - 'cubrid_next_result', - 'cubrid_num_cols', - 'cubrid_num_rows', - 'cubrid_pconnect', - 'cubrid_pconnect_with_url', - 'cubrid_prepare', - 'cubrid_put', - 'cubrid_query', - 'cubrid_rollback', - 'cubrid_schema', - 'cubrid_seq_add', - 'cubrid_seq_drop', - 'cubrid_seq_insert', - 'cubrid_seq_put', - 'cubrid_set_add', - 'cubrid_set_autocommit', - 'cubrid_set_db_parameter', - 'cubrid_set_drop', - 'cubrid_set_query_timeout', - 'cubrid_unbuffered_query', - 'curl_close', - 'curl_copy_handle', - 'curl_errno', - 'curl_error', - 'curl_escape', - 'curl_exec', - 'curl_getinfo', - 'curl_multi_add_handle', - 'curl_multi_close', - 'curl_multi_errno', - 'curl_multi_exec', - 'curl_multi_getcontent', - 'curl_multi_info_read', - 'curl_multi_remove_handle', - 'curl_multi_select', - 'curl_multi_setopt', - 'curl_pause', - 'curl_reset', - 'curl_setopt', - 'curl_setopt_array', - 'curl_share_close', - 'curl_share_errno', - 'curl_share_init', - 'curl_share_setopt', - 'curl_unescape', - 'cyrus_authenticate', - 'cyrus_bind', - 'cyrus_close', - 'cyrus_connect', - 'cyrus_query', - 'cyrus_unbind', - 'db2_autocommit', - 'db2_bind_param', - 'db2_client_info', - 'db2_close', - 'db2_column_privileges', - 'db2_columns', - 'db2_commit', - 'db2_conn_error', - 'db2_conn_errormsg', - 'db2_connect', - 'db2_cursor_type', - 'db2_exec', - 'db2_execute', - 'db2_fetch_array', - 'db2_fetch_assoc', - 'db2_fetch_both', - 'db2_fetch_object', - 'db2_fetch_row', - 'db2_field_display_size', - 'db2_field_name', - 'db2_field_num', - 'db2_field_precision', - 'db2_field_scale', - 'db2_field_type', - 'db2_field_width', - 'db2_foreign_keys', - 'db2_free_result', - 'db2_free_stmt', - 'db2_get_option', - 'db2_last_insert_id', - 'db2_lob_read', - 'db2_next_result', - 'db2_num_fields', - 'db2_num_rows', - 'db2_pclose', - 'db2_pconnect', - 'db2_prepare', - 'db2_primary_keys', - 'db2_procedure_columns', - 'db2_procedures', - 'db2_result', - 'db2_rollback', - 'db2_server_info', - 'db2_set_option', - 'db2_special_columns', - 'db2_statistics', - 'db2_stmt_error', - 'db2_stmt_errormsg', - 'db2_table_privileges', - 'db2_tables', - 'dba_close', - 'dba_delete', - 'dba_exists', - 'dba_fetch', - 'dba_firstkey', - 'dba_insert', - 'dba_nextkey', - 'dba_open', - 'dba_optimize', - 'dba_popen', - 'dba_replace', - 'dba_sync', - 'dbplus_add', - 'dbplus_aql', - 'dbplus_close', - 'dbplus_curr', - 'dbplus_find', - 'dbplus_first', - 'dbplus_flush', - 'dbplus_freelock', - 'dbplus_freerlocks', - 'dbplus_getlock', - 'dbplus_getunique', - 'dbplus_info', - 'dbplus_last', - 'dbplus_lockrel', - 'dbplus_next', - 'dbplus_open', - 'dbplus_prev', - 'dbplus_rchperm', - 'dbplus_rcreate', - 'dbplus_rcrtexact', - 'dbplus_rcrtlike', - 'dbplus_restorepos', - 'dbplus_rkeys', - 'dbplus_ropen', - 'dbplus_rquery', - 'dbplus_rrename', - 'dbplus_rsecindex', - 'dbplus_runlink', - 'dbplus_rzap', - 'dbplus_savepos', - 'dbplus_setindex', - 'dbplus_setindexbynumber', - 'dbplus_sql', - 'dbplus_tremove', - 'dbplus_undo', - 'dbplus_undoprepare', - 'dbplus_unlockrel', - 'dbplus_unselect', - 'dbplus_update', - 'dbplus_xlockrel', - 'dbplus_xunlockrel', - 'deflate_add', - 'dio_close', - 'dio_fcntl', - 'dio_open', - 'dio_read', - 'dio_seek', - 'dio_stat', - 'dio_tcsetattr', - 'dio_truncate', - 'dio_write', - 'dir', - 'eio_busy', - 'eio_cancel', - 'eio_chmod', - 'eio_chown', - 'eio_close', - 'eio_custom', - 'eio_dup2', - 'eio_fallocate', - 'eio_fchmod', - 'eio_fchown', - 'eio_fdatasync', - 'eio_fstat', - 'eio_fstatvfs', - 'eio_fsync', - 'eio_ftruncate', - 'eio_futime', - 'eio_get_last_error', - 'eio_grp', - 'eio_grp_add', - 'eio_grp_cancel', - 'eio_grp_limit', - 'eio_link', - 'eio_lstat', - 'eio_mkdir', - 'eio_mknod', - 'eio_nop', - 'eio_open', - 'eio_read', - 'eio_readahead', - 'eio_readdir', - 'eio_readlink', - 'eio_realpath', - 'eio_rename', - 'eio_rmdir', - 'eio_seek', - 'eio_sendfile', - 'eio_stat', - 'eio_statvfs', - 'eio_symlink', - 'eio_sync', - 'eio_sync_file_range', - 'eio_syncfs', - 'eio_truncate', - 'eio_unlink', - 'eio_utime', - 'eio_write', - 'enchant_broker_describe', - 'enchant_broker_dict_exists', - 'enchant_broker_free', - 'enchant_broker_free_dict', - 'enchant_broker_get_dict_path', - 'enchant_broker_get_error', - 'enchant_broker_init', - 'enchant_broker_list_dicts', - 'enchant_broker_request_dict', - 'enchant_broker_request_pwl_dict', - 'enchant_broker_set_dict_path', - 'enchant_broker_set_ordering', - 'enchant_dict_add_to_personal', - 'enchant_dict_add_to_session', - 'enchant_dict_check', - 'enchant_dict_describe', - 'enchant_dict_get_error', - 'enchant_dict_is_in_session', - 'enchant_dict_quick_check', - 'enchant_dict_store_replacement', - 'enchant_dict_suggest', - 'event_add', - 'event_base_free', - 'event_base_loop', - 'event_base_loopbreak', - 'event_base_loopexit', - 'event_base_new', - 'event_base_priority_init', - 'event_base_reinit', - 'event_base_set', - 'event_buffer_base_set', - 'event_buffer_disable', - 'event_buffer_enable', - 'event_buffer_fd_set', - 'event_buffer_free', - 'event_buffer_new', - 'event_buffer_priority_set', - 'event_buffer_read', - 'event_buffer_set_callback', - 'event_buffer_timeout_set', - 'event_buffer_watermark_set', - 'event_buffer_write', - 'event_del', - 'event_free', - 'event_new', - 'event_priority_set', - 'event_set', - 'event_timer_add', - 'event_timer_del', - 'event_timer_pending', - 'event_timer_set', - 'expect_expectl', - 'expect_popen', - 'fam_cancel_monitor', - 'fam_close', - 'fam_monitor_collection', - 'fam_monitor_directory', - 'fam_monitor_file', - 'fam_next_event', - 'fam_open', - 'fam_pending', - 'fam_resume_monitor', - 'fam_suspend_monitor', - 'fann_cascadetrain_on_data', - 'fann_cascadetrain_on_file', - 'fann_clear_scaling_params', - 'fann_copy', - 'fann_create_from_file', - 'fann_create_shortcut_array', - 'fann_create_standard', - 'fann_create_standard_array', - 'fann_create_train', - 'fann_create_train_from_callback', - 'fann_descale_input', - 'fann_descale_output', - 'fann_descale_train', - 'fann_destroy', - 'fann_destroy_train', - 'fann_duplicate_train_data', - 'fann_get_MSE', - 'fann_get_activation_function', - 'fann_get_activation_steepness', - 'fann_get_bias_array', - 'fann_get_bit_fail', - 'fann_get_bit_fail_limit', - 'fann_get_cascade_activation_functions', - 'fann_get_cascade_activation_functions_count', - 'fann_get_cascade_activation_steepnesses', - 'fann_get_cascade_activation_steepnesses_count', - 'fann_get_cascade_candidate_change_fraction', - 'fann_get_cascade_candidate_limit', - 'fann_get_cascade_candidate_stagnation_epochs', - 'fann_get_cascade_max_cand_epochs', - 'fann_get_cascade_max_out_epochs', - 'fann_get_cascade_min_cand_epochs', - 'fann_get_cascade_min_out_epochs', - 'fann_get_cascade_num_candidate_groups', - 'fann_get_cascade_num_candidates', - 'fann_get_cascade_output_change_fraction', - 'fann_get_cascade_output_stagnation_epochs', - 'fann_get_cascade_weight_multiplier', - 'fann_get_connection_array', - 'fann_get_connection_rate', - 'fann_get_errno', - 'fann_get_errstr', - 'fann_get_layer_array', - 'fann_get_learning_momentum', - 'fann_get_learning_rate', - 'fann_get_network_type', - 'fann_get_num_input', - 'fann_get_num_layers', - 'fann_get_num_output', - 'fann_get_quickprop_decay', - 'fann_get_quickprop_mu', - 'fann_get_rprop_decrease_factor', - 'fann_get_rprop_delta_max', - 'fann_get_rprop_delta_min', - 'fann_get_rprop_delta_zero', - 'fann_get_rprop_increase_factor', - 'fann_get_sarprop_step_error_shift', - 'fann_get_sarprop_step_error_threshold_factor', - 'fann_get_sarprop_temperature', - 'fann_get_sarprop_weight_decay_shift', - 'fann_get_total_connections', - 'fann_get_total_neurons', - 'fann_get_train_error_function', - 'fann_get_train_stop_function', - 'fann_get_training_algorithm', - 'fann_init_weights', - 'fann_length_train_data', - 'fann_merge_train_data', - 'fann_num_input_train_data', - 'fann_num_output_train_data', - 'fann_randomize_weights', - 'fann_read_train_from_file', - 'fann_reset_errno', - 'fann_reset_errstr', - 'fann_run', - 'fann_save', - 'fann_save_train', - 'fann_scale_input', - 'fann_scale_input_train_data', - 'fann_scale_output', - 'fann_scale_output_train_data', - 'fann_scale_train', - 'fann_scale_train_data', - 'fann_set_activation_function', - 'fann_set_activation_function_hidden', - 'fann_set_activation_function_layer', - 'fann_set_activation_function_output', - 'fann_set_activation_steepness', - 'fann_set_activation_steepness_hidden', - 'fann_set_activation_steepness_layer', - 'fann_set_activation_steepness_output', - 'fann_set_bit_fail_limit', - 'fann_set_callback', - 'fann_set_cascade_activation_functions', - 'fann_set_cascade_activation_steepnesses', - 'fann_set_cascade_candidate_change_fraction', - 'fann_set_cascade_candidate_limit', - 'fann_set_cascade_candidate_stagnation_epochs', - 'fann_set_cascade_max_cand_epochs', - 'fann_set_cascade_max_out_epochs', - 'fann_set_cascade_min_cand_epochs', - 'fann_set_cascade_min_out_epochs', - 'fann_set_cascade_num_candidate_groups', - 'fann_set_cascade_output_change_fraction', - 'fann_set_cascade_output_stagnation_epochs', - 'fann_set_cascade_weight_multiplier', - 'fann_set_error_log', - 'fann_set_input_scaling_params', - 'fann_set_learning_momentum', - 'fann_set_learning_rate', - 'fann_set_output_scaling_params', - 'fann_set_quickprop_decay', - 'fann_set_quickprop_mu', - 'fann_set_rprop_decrease_factor', - 'fann_set_rprop_delta_max', - 'fann_set_rprop_delta_min', - 'fann_set_rprop_delta_zero', - 'fann_set_rprop_increase_factor', - 'fann_set_sarprop_step_error_shift', - 'fann_set_sarprop_step_error_threshold_factor', - 'fann_set_sarprop_temperature', - 'fann_set_sarprop_weight_decay_shift', - 'fann_set_scaling_params', - 'fann_set_train_error_function', - 'fann_set_train_stop_function', - 'fann_set_training_algorithm', - 'fann_set_weight', - 'fann_set_weight_array', - 'fann_shuffle_train_data', - 'fann_subset_train_data', - 'fann_test', - 'fann_test_data', - 'fann_train', - 'fann_train_epoch', - 'fann_train_on_data', - 'fann_train_on_file', - 'fbsql_affected_rows', - 'fbsql_autocommit', - 'fbsql_blob_size', - 'fbsql_change_user', - 'fbsql_clob_size', - 'fbsql_close', - 'fbsql_commit', - 'fbsql_connect', - 'fbsql_create_blob', - 'fbsql_create_clob', - 'fbsql_create_db', - 'fbsql_data_seek', - 'fbsql_database', - 'fbsql_database_password', - 'fbsql_db_query', - 'fbsql_db_status', - 'fbsql_drop_db', - 'fbsql_errno', - 'fbsql_error', - 'fbsql_fetch_array', - 'fbsql_fetch_assoc', - 'fbsql_fetch_field', - 'fbsql_fetch_lengths', - 'fbsql_fetch_object', - 'fbsql_fetch_row', - 'fbsql_field_flags', - 'fbsql_field_len', - 'fbsql_field_name', - 'fbsql_field_seek', - 'fbsql_field_table', - 'fbsql_field_type', - 'fbsql_free_result', - 'fbsql_get_autostart_info', - 'fbsql_hostname', - 'fbsql_insert_id', - 'fbsql_list_dbs', - 'fbsql_list_fields', - 'fbsql_list_tables', - 'fbsql_next_result', - 'fbsql_num_fields', - 'fbsql_num_rows', - 'fbsql_password', - 'fbsql_pconnect', - 'fbsql_query', - 'fbsql_read_blob', - 'fbsql_read_clob', - 'fbsql_result', - 'fbsql_rollback', - 'fbsql_rows_fetched', - 'fbsql_select_db', - 'fbsql_set_characterset', - 'fbsql_set_lob_mode', - 'fbsql_set_password', - 'fbsql_set_transaction', - 'fbsql_start_db', - 'fbsql_stop_db', - 'fbsql_table_name', - 'fbsql_username', - 'fclose', - 'fdf_add_doc_javascript', - 'fdf_add_template', - 'fdf_close', - 'fdf_create', - 'fdf_enum_values', - 'fdf_get_ap', - 'fdf_get_attachment', - 'fdf_get_encoding', - 'fdf_get_file', - 'fdf_get_flags', - 'fdf_get_opt', - 'fdf_get_status', - 'fdf_get_value', - 'fdf_get_version', - 'fdf_next_field_name', - 'fdf_open', - 'fdf_open_string', - 'fdf_remove_item', - 'fdf_save', - 'fdf_save_string', - 'fdf_set_ap', - 'fdf_set_encoding', - 'fdf_set_file', - 'fdf_set_flags', - 'fdf_set_javascript_action', - 'fdf_set_on_import_javascript', - 'fdf_set_opt', - 'fdf_set_status', - 'fdf_set_submit_form_action', - 'fdf_set_target_frame', - 'fdf_set_value', - 'fdf_set_version', - 'feof', - 'fflush', - 'ffmpeg_frame::__construct', - 'ffmpeg_frame::toGDImage', - 'fgetc', - 'fgetcsv', - 'fgets', - 'fgetss', - 'file', - 'file_get_contents', - 'file_put_contents', - 'finfo::buffer', - 'finfo::file', - 'finfo_buffer', - 'finfo_close', - 'finfo_file', - 'finfo_open', - 'finfo_set_flags', - 'flock', - 'fopen', - 'fpassthru', - 'fprintf', - 'fputcsv', - 'fputs', - 'fread', - 'fscanf', - 'fseek', - 'fstat', - 'ftell', - 'ftp_alloc', - 'ftp_append', - 'ftp_cdup', - 'ftp_chdir', - 'ftp_chmod', - 'ftp_close', - 'ftp_delete', - 'ftp_exec', - 'ftp_fget', - 'ftp_fput', - 'ftp_get', - 'ftp_get_option', - 'ftp_login', - 'ftp_mdtm', - 'ftp_mkdir', - 'ftp_mlsd', - 'ftp_nb_continue', - 'ftp_nb_fget', - 'ftp_nb_fput', - 'ftp_nb_get', - 'ftp_nb_put', - 'ftp_nlist', - 'ftp_pasv', - 'ftp_put', - 'ftp_pwd', - 'ftp_quit', - 'ftp_raw', - 'ftp_rawlist', - 'ftp_rename', - 'ftp_rmdir', - 'ftp_set_option', - 'ftp_site', - 'ftp_size', - 'ftp_systype', - 'ftruncate', - 'fwrite', - 'get_resource_type', - 'gmp_div', - 'gnupg::init', - 'gnupg_adddecryptkey', - 'gnupg_addencryptkey', - 'gnupg_addsignkey', - 'gnupg_cleardecryptkeys', - 'gnupg_clearencryptkeys', - 'gnupg_clearsignkeys', - 'gnupg_decrypt', - 'gnupg_decryptverify', - 'gnupg_encrypt', - 'gnupg_encryptsign', - 'gnupg_export', - 'gnupg_geterror', - 'gnupg_getprotocol', - 'gnupg_import', - 'gnupg_init', - 'gnupg_keyinfo', - 'gnupg_setarmor', - 'gnupg_seterrormode', - 'gnupg_setsignmode', - 'gnupg_sign', - 'gnupg_verify', - 'gupnp_context_get_host_ip', - 'gupnp_context_get_port', - 'gupnp_context_get_subscription_timeout', - 'gupnp_context_host_path', - 'gupnp_context_new', - 'gupnp_context_set_subscription_timeout', - 'gupnp_context_timeout_add', - 'gupnp_context_unhost_path', - 'gupnp_control_point_browse_start', - 'gupnp_control_point_browse_stop', - 'gupnp_control_point_callback_set', - 'gupnp_control_point_new', - 'gupnp_device_action_callback_set', - 'gupnp_device_info_get', - 'gupnp_device_info_get_service', - 'gupnp_root_device_get_available', - 'gupnp_root_device_get_relative_location', - 'gupnp_root_device_new', - 'gupnp_root_device_set_available', - 'gupnp_root_device_start', - 'gupnp_root_device_stop', - 'gupnp_service_action_get', - 'gupnp_service_action_return', - 'gupnp_service_action_return_error', - 'gupnp_service_action_set', - 'gupnp_service_freeze_notify', - 'gupnp_service_info_get', - 'gupnp_service_info_get_introspection', - 'gupnp_service_introspection_get_state_variable', - 'gupnp_service_notify', - 'gupnp_service_proxy_action_get', - 'gupnp_service_proxy_action_set', - 'gupnp_service_proxy_add_notify', - 'gupnp_service_proxy_callback_set', - 'gupnp_service_proxy_get_subscribed', - 'gupnp_service_proxy_remove_notify', - 'gupnp_service_proxy_send_action', - 'gupnp_service_proxy_set_subscribed', - 'gupnp_service_thaw_notify', - 'gzclose', - 'gzeof', - 'gzgetc', - 'gzgets', - 'gzgetss', - 'gzpassthru', - 'gzputs', - 'gzread', - 'gzrewind', - 'gzseek', - 'gztell', - 'gzwrite', - 'hash_update_stream', - 'http\Env\Response::send', - 'http_get_request_body_stream', - 'ibase_add_user', - 'ibase_affected_rows', - 'ibase_backup', - 'ibase_blob_add', - 'ibase_blob_cancel', - 'ibase_blob_close', - 'ibase_blob_create', - 'ibase_blob_get', - 'ibase_blob_open', - 'ibase_close', - 'ibase_commit', - 'ibase_commit_ret', - 'ibase_connect', - 'ibase_db_info', - 'ibase_delete_user', - 'ibase_drop_db', - 'ibase_execute', - 'ibase_fetch_assoc', - 'ibase_fetch_object', - 'ibase_fetch_row', - 'ibase_field_info', - 'ibase_free_event_handler', - 'ibase_free_query', - 'ibase_free_result', - 'ibase_gen_id', - 'ibase_maintain_db', - 'ibase_modify_user', - 'ibase_name_result', - 'ibase_num_fields', - 'ibase_num_params', - 'ibase_param_info', - 'ibase_pconnect', - 'ibase_prepare', - 'ibase_query', - 'ibase_restore', - 'ibase_rollback', - 'ibase_rollback_ret', - 'ibase_server_info', - 'ibase_service_attach', - 'ibase_service_detach', - 'ibase_set_event_handler', - 'ibase_trans', - 'ifx_affected_rows', - 'ifx_close', - 'ifx_connect', - 'ifx_do', - 'ifx_error', - 'ifx_fetch_row', - 'ifx_fieldproperties', - 'ifx_fieldtypes', - 'ifx_free_result', - 'ifx_getsqlca', - 'ifx_htmltbl_result', - 'ifx_num_fields', - 'ifx_num_rows', - 'ifx_pconnect', - 'ifx_prepare', - 'ifx_query', - 'image2wbmp', - 'imageaffine', - 'imagealphablending', - 'imageantialias', - 'imagearc', - 'imagebmp', - 'imagechar', - 'imagecharup', - 'imagecolorallocate', - 'imagecolorallocatealpha', - 'imagecolorat', - 'imagecolorclosest', - 'imagecolorclosestalpha', - 'imagecolorclosesthwb', - 'imagecolordeallocate', - 'imagecolorexact', - 'imagecolorexactalpha', - 'imagecolormatch', - 'imagecolorresolve', - 'imagecolorresolvealpha', - 'imagecolorset', - 'imagecolorsforindex', - 'imagecolorstotal', - 'imagecolortransparent', - 'imageconvolution', - 'imagecopy', - 'imagecopymerge', - 'imagecopymergegray', - 'imagecopyresampled', - 'imagecopyresized', - 'imagecrop', - 'imagecropauto', - 'imagedashedline', - 'imagedestroy', - 'imageellipse', - 'imagefill', - 'imagefilledarc', - 'imagefilledellipse', - 'imagefilledpolygon', - 'imagefilledrectangle', - 'imagefilltoborder', - 'imagefilter', - 'imageflip', - 'imagefttext', - 'imagegammacorrect', - 'imagegd', - 'imagegd2', - 'imagegetclip', - 'imagegif', - 'imagegrabscreen', - 'imagegrabwindow', - 'imageinterlace', - 'imageistruecolor', - 'imagejpeg', - 'imagelayereffect', - 'imageline', - 'imageopenpolygon', - 'imagepalettecopy', - 'imagepalettetotruecolor', - 'imagepng', - 'imagepolygon', - 'imagepsencodefont', - 'imagepsextendfont', - 'imagepsfreefont', - 'imagepsloadfont', - 'imagepsslantfont', - 'imagepstext', - 'imagerectangle', - 'imageresolution', - 'imagerotate', - 'imagesavealpha', - 'imagescale', - 'imagesetbrush', - 'imagesetclip', - 'imagesetinterpolation', - 'imagesetpixel', - 'imagesetstyle', - 'imagesetthickness', - 'imagesettile', - 'imagestring', - 'imagestringup', - 'imagesx', - 'imagesy', - 'imagetruecolortopalette', - 'imagettftext', - 'imagewbmp', - 'imagewebp', - 'imagexbm', - 'imap_append', - 'imap_body', - 'imap_bodystruct', - 'imap_check', - 'imap_clearflag_full', - 'imap_close', - 'imap_create', - 'imap_createmailbox', - 'imap_delete', - 'imap_deletemailbox', - 'imap_expunge', - 'imap_fetch_overview', - 'imap_fetchbody', - 'imap_fetchheader', - 'imap_fetchmime', - 'imap_fetchstructure', - 'imap_fetchtext', - 'imap_gc', - 'imap_get_quota', - 'imap_get_quotaroot', - 'imap_getacl', - 'imap_getmailboxes', - 'imap_getsubscribed', - 'imap_header', - 'imap_headerinfo', - 'imap_headers', - 'imap_list', - 'imap_listmailbox', - 'imap_listscan', - 'imap_listsubscribed', - 'imap_lsub', - 'imap_mail_copy', - 'imap_mail_move', - 'imap_mailboxmsginfo', - 'imap_msgno', - 'imap_num_msg', - 'imap_num_recent', - 'imap_ping', - 'imap_rename', - 'imap_renamemailbox', - 'imap_reopen', - 'imap_savebody', - 'imap_scan', - 'imap_scanmailbox', - 'imap_search', - 'imap_set_quota', - 'imap_setacl', - 'imap_setflag_full', - 'imap_sort', - 'imap_status', - 'imap_subscribe', - 'imap_thread', - 'imap_uid', - 'imap_undelete', - 'imap_unsubscribe', - 'inflate_add', - 'inflate_get_read_len', - 'inflate_get_status', - 'ingres_autocommit', - 'ingres_autocommit_state', - 'ingres_charset', - 'ingres_close', - 'ingres_commit', - 'ingres_connect', - 'ingres_cursor', - 'ingres_errno', - 'ingres_error', - 'ingres_errsqlstate', - 'ingres_escape_string', - 'ingres_execute', - 'ingres_fetch_array', - 'ingres_fetch_assoc', - 'ingres_fetch_object', - 'ingres_fetch_proc_return', - 'ingres_fetch_row', - 'ingres_field_length', - 'ingres_field_name', - 'ingres_field_nullable', - 'ingres_field_precision', - 'ingres_field_scale', - 'ingres_field_type', - 'ingres_free_result', - 'ingres_next_error', - 'ingres_num_fields', - 'ingres_num_rows', - 'ingres_pconnect', - 'ingres_prepare', - 'ingres_query', - 'ingres_result_seek', - 'ingres_rollback', - 'ingres_set_environment', - 'ingres_unbuffered_query', - 'inotify_add_watch', - 'inotify_init', - 'inotify_queue_len', - 'inotify_read', - 'inotify_rm_watch', - 'kadm5_chpass_principal', - 'kadm5_create_principal', - 'kadm5_delete_principal', - 'kadm5_destroy', - 'kadm5_flush', - 'kadm5_get_policies', - 'kadm5_get_principal', - 'kadm5_get_principals', - 'kadm5_init_with_password', - 'kadm5_modify_principal', - 'ldap_add', - 'ldap_bind', - 'ldap_close', - 'ldap_compare', - 'ldap_control_paged_result', - 'ldap_control_paged_result_response', - 'ldap_count_entries', - 'ldap_delete', - 'ldap_errno', - 'ldap_error', - 'ldap_exop', - 'ldap_exop_passwd', - 'ldap_exop_refresh', - 'ldap_exop_whoami', - 'ldap_first_attribute', - 'ldap_first_entry', - 'ldap_first_reference', - 'ldap_free_result', - 'ldap_get_attributes', - 'ldap_get_dn', - 'ldap_get_entries', - 'ldap_get_option', - 'ldap_get_values', - 'ldap_get_values_len', - 'ldap_mod_add', - 'ldap_mod_del', - 'ldap_mod_replace', - 'ldap_modify', - 'ldap_modify_batch', - 'ldap_next_attribute', - 'ldap_next_entry', - 'ldap_next_reference', - 'ldap_parse_exop', - 'ldap_parse_reference', - 'ldap_parse_result', - 'ldap_rename', - 'ldap_sasl_bind', - 'ldap_set_option', - 'ldap_set_rebind_proc', - 'ldap_sort', - 'ldap_start_tls', - 'ldap_unbind', - 'libxml_set_streams_context', - 'm_checkstatus', - 'm_completeauthorizations', - 'm_connect', - 'm_connectionerror', - 'm_deletetrans', - 'm_destroyconn', - 'm_getcell', - 'm_getcellbynum', - 'm_getcommadelimited', - 'm_getheader', - 'm_initconn', - 'm_iscommadelimited', - 'm_maxconntimeout', - 'm_monitor', - 'm_numcolumns', - 'm_numrows', - 'm_parsecommadelimited', - 'm_responsekeys', - 'm_responseparam', - 'm_returnstatus', - 'm_setblocking', - 'm_setdropfile', - 'm_setip', - 'm_setssl', - 'm_setssl_cafile', - 'm_setssl_files', - 'm_settimeout', - 'm_transactionssent', - 'm_transinqueue', - 'm_transkeyval', - 'm_transnew', - 'm_transsend', - 'm_validateidentifier', - 'm_verifyconnection', - 'm_verifysslcert', - 'mailparse_determine_best_xfer_encoding', - 'mailparse_msg_create', - 'mailparse_msg_extract_part', - 'mailparse_msg_extract_part_file', - 'mailparse_msg_extract_whole_part_file', - 'mailparse_msg_free', - 'mailparse_msg_get_part', - 'mailparse_msg_get_part_data', - 'mailparse_msg_get_structure', - 'mailparse_msg_parse', - 'mailparse_msg_parse_file', - 'mailparse_stream_encode', - 'mailparse_uudecode_all', - 'maxdb::use_result', - 'maxdb_affected_rows', - 'maxdb_connect', - 'maxdb_disable_rpl_parse', - 'maxdb_dump_debug_info', - 'maxdb_embedded_connect', - 'maxdb_enable_reads_from_master', - 'maxdb_enable_rpl_parse', - 'maxdb_errno', - 'maxdb_error', - 'maxdb_fetch_lengths', - 'maxdb_field_tell', - 'maxdb_get_host_info', - 'maxdb_get_proto_info', - 'maxdb_get_server_info', - 'maxdb_get_server_version', - 'maxdb_info', - 'maxdb_init', - 'maxdb_insert_id', - 'maxdb_master_query', - 'maxdb_more_results', - 'maxdb_next_result', - 'maxdb_num_fields', - 'maxdb_num_rows', - 'maxdb_rpl_parse_enabled', - 'maxdb_rpl_probe', - 'maxdb_select_db', - 'maxdb_sqlstate', - 'maxdb_stmt::result_metadata', - 'maxdb_stmt_affected_rows', - 'maxdb_stmt_errno', - 'maxdb_stmt_error', - 'maxdb_stmt_num_rows', - 'maxdb_stmt_param_count', - 'maxdb_stmt_result_metadata', - 'maxdb_stmt_sqlstate', - 'maxdb_thread_id', - 'maxdb_use_result', - 'maxdb_warning_count', - 'mcrypt_enc_get_algorithms_name', - 'mcrypt_enc_get_block_size', - 'mcrypt_enc_get_iv_size', - 'mcrypt_enc_get_key_size', - 'mcrypt_enc_get_modes_name', - 'mcrypt_enc_get_supported_key_sizes', - 'mcrypt_enc_is_block_algorithm', - 'mcrypt_enc_is_block_algorithm_mode', - 'mcrypt_enc_is_block_mode', - 'mcrypt_enc_self_test', - 'mcrypt_generic', - 'mcrypt_generic_deinit', - 'mcrypt_generic_end', - 'mcrypt_generic_init', - 'mcrypt_module_close', - 'mcrypt_module_open', - 'mdecrypt_generic', - 'mkdir', - 'mqseries_back', - 'mqseries_begin', - 'mqseries_close', - 'mqseries_cmit', - 'mqseries_conn', - 'mqseries_connx', - 'mqseries_disc', - 'mqseries_get', - 'mqseries_inq', - 'mqseries_open', - 'mqseries_put', - 'mqseries_put1', - 'mqseries_set', - 'msg_get_queue', - 'msg_receive', - 'msg_remove_queue', - 'msg_send', - 'msg_set_queue', - 'msg_stat_queue', - 'msql_affected_rows', - 'msql_close', - 'msql_connect', - 'msql_create_db', - 'msql_data_seek', - 'msql_db_query', - 'msql_drop_db', - 'msql_fetch_array', - 'msql_fetch_field', - 'msql_fetch_object', - 'msql_fetch_row', - 'msql_field_flags', - 'msql_field_len', - 'msql_field_name', - 'msql_field_seek', - 'msql_field_table', - 'msql_field_type', - 'msql_free_result', - 'msql_list_dbs', - 'msql_list_fields', - 'msql_list_tables', - 'msql_num_fields', - 'msql_num_rows', - 'msql_pconnect', - 'msql_query', - 'msql_result', - 'msql_select_db', - 'mssql_bind', - 'mssql_close', - 'mssql_connect', - 'mssql_data_seek', - 'mssql_execute', - 'mssql_fetch_array', - 'mssql_fetch_assoc', - 'mssql_fetch_batch', - 'mssql_fetch_field', - 'mssql_fetch_object', - 'mssql_fetch_row', - 'mssql_field_length', - 'mssql_field_name', - 'mssql_field_seek', - 'mssql_field_type', - 'mssql_free_result', - 'mssql_free_statement', - 'mssql_init', - 'mssql_next_result', - 'mssql_num_fields', - 'mssql_num_rows', - 'mssql_pconnect', - 'mssql_query', - 'mssql_result', - 'mssql_rows_affected', - 'mssql_select_db', - 'mysql_affected_rows', - 'mysql_client_encoding', - 'mysql_close', - 'mysql_connect', - 'mysql_create_db', - 'mysql_data_seek', - 'mysql_db_name', - 'mysql_db_query', - 'mysql_drop_db', - 'mysql_errno', - 'mysql_error', - 'mysql_fetch_array', - 'mysql_fetch_assoc', - 'mysql_fetch_field', - 'mysql_fetch_lengths', - 'mysql_fetch_object', - 'mysql_fetch_row', - 'mysql_field_flags', - 'mysql_field_len', - 'mysql_field_name', - 'mysql_field_seek', - 'mysql_field_table', - 'mysql_field_type', - 'mysql_free_result', - 'mysql_get_host_info', - 'mysql_get_proto_info', - 'mysql_get_server_info', - 'mysql_info', - 'mysql_insert_id', - 'mysql_list_dbs', - 'mysql_list_fields', - 'mysql_list_processes', - 'mysql_list_tables', - 'mysql_num_fields', - 'mysql_num_rows', - 'mysql_pconnect', - 'mysql_ping', - 'mysql_query', - 'mysql_real_escape_string', - 'mysql_result', - 'mysql_select_db', - 'mysql_set_charset', - 'mysql_stat', - 'mysql_tablename', - 'mysql_thread_id', - 'mysql_unbuffered_query', - 'mysqlnd_uh_convert_to_mysqlnd', - 'ncurses_bottom_panel', - 'ncurses_del_panel', - 'ncurses_delwin', - 'ncurses_getmaxyx', - 'ncurses_getyx', - 'ncurses_hide_panel', - 'ncurses_keypad', - 'ncurses_meta', - 'ncurses_move_panel', - 'ncurses_mvwaddstr', - 'ncurses_new_panel', - 'ncurses_newpad', - 'ncurses_newwin', - 'ncurses_panel_above', - 'ncurses_panel_below', - 'ncurses_panel_window', - 'ncurses_pnoutrefresh', - 'ncurses_prefresh', - 'ncurses_replace_panel', - 'ncurses_show_panel', - 'ncurses_top_panel', - 'ncurses_waddch', - 'ncurses_waddstr', - 'ncurses_wattroff', - 'ncurses_wattron', - 'ncurses_wattrset', - 'ncurses_wborder', - 'ncurses_wclear', - 'ncurses_wcolor_set', - 'ncurses_werase', - 'ncurses_wgetch', - 'ncurses_whline', - 'ncurses_wmouse_trafo', - 'ncurses_wmove', - 'ncurses_wnoutrefresh', - 'ncurses_wrefresh', - 'ncurses_wstandend', - 'ncurses_wstandout', - 'ncurses_wvline', - 'newt_button', - 'newt_button_bar', - 'newt_checkbox', - 'newt_checkbox_get_value', - 'newt_checkbox_set_flags', - 'newt_checkbox_set_value', - 'newt_checkbox_tree', - 'newt_checkbox_tree_add_item', - 'newt_checkbox_tree_find_item', - 'newt_checkbox_tree_get_current', - 'newt_checkbox_tree_get_entry_value', - 'newt_checkbox_tree_get_multi_selection', - 'newt_checkbox_tree_get_selection', - 'newt_checkbox_tree_multi', - 'newt_checkbox_tree_set_current', - 'newt_checkbox_tree_set_entry', - 'newt_checkbox_tree_set_entry_value', - 'newt_checkbox_tree_set_width', - 'newt_compact_button', - 'newt_component_add_callback', - 'newt_component_takes_focus', - 'newt_create_grid', - 'newt_draw_form', - 'newt_entry', - 'newt_entry_get_value', - 'newt_entry_set', - 'newt_entry_set_filter', - 'newt_entry_set_flags', - 'newt_form', - 'newt_form_add_component', - 'newt_form_add_components', - 'newt_form_add_hot_key', - 'newt_form_destroy', - 'newt_form_get_current', - 'newt_form_run', - 'newt_form_set_background', - 'newt_form_set_height', - 'newt_form_set_size', - 'newt_form_set_timer', - 'newt_form_set_width', - 'newt_form_watch_fd', - 'newt_grid_add_components_to_form', - 'newt_grid_basic_window', - 'newt_grid_free', - 'newt_grid_get_size', - 'newt_grid_h_close_stacked', - 'newt_grid_h_stacked', - 'newt_grid_place', - 'newt_grid_set_field', - 'newt_grid_simple_window', - 'newt_grid_v_close_stacked', - 'newt_grid_v_stacked', - 'newt_grid_wrapped_window', - 'newt_grid_wrapped_window_at', - 'newt_label', - 'newt_label_set_text', - 'newt_listbox', - 'newt_listbox_append_entry', - 'newt_listbox_clear', - 'newt_listbox_clear_selection', - 'newt_listbox_delete_entry', - 'newt_listbox_get_current', - 'newt_listbox_get_selection', - 'newt_listbox_insert_entry', - 'newt_listbox_item_count', - 'newt_listbox_select_item', - 'newt_listbox_set_current', - 'newt_listbox_set_current_by_key', - 'newt_listbox_set_data', - 'newt_listbox_set_entry', - 'newt_listbox_set_width', - 'newt_listitem', - 'newt_listitem_get_data', - 'newt_listitem_set', - 'newt_radio_get_current', - 'newt_radiobutton', - 'newt_run_form', - 'newt_scale', - 'newt_scale_set', - 'newt_scrollbar_set', - 'newt_textbox', - 'newt_textbox_get_num_lines', - 'newt_textbox_reflowed', - 'newt_textbox_set_height', - 'newt_textbox_set_text', - 'newt_vertical_scrollbar', - 'oci_bind_array_by_name', - 'oci_bind_by_name', - 'oci_cancel', - 'oci_close', - 'oci_commit', - 'oci_connect', - 'oci_define_by_name', - 'oci_error', - 'oci_execute', - 'oci_fetch', - 'oci_fetch_all', - 'oci_fetch_array', - 'oci_fetch_assoc', - 'oci_fetch_object', - 'oci_fetch_row', - 'oci_field_is_null', - 'oci_field_name', - 'oci_field_precision', - 'oci_field_scale', - 'oci_field_size', - 'oci_field_type', - 'oci_field_type_raw', - 'oci_free_cursor', - 'oci_free_statement', - 'oci_get_implicit_resultset', - 'oci_new_collection', - 'oci_new_connect', - 'oci_new_cursor', - 'oci_new_descriptor', - 'oci_num_fields', - 'oci_num_rows', - 'oci_parse', - 'oci_pconnect', - 'oci_register_taf_callback', - 'oci_result', - 'oci_rollback', - 'oci_server_version', - 'oci_set_action', - 'oci_set_client_identifier', - 'oci_set_client_info', - 'oci_set_module_name', - 'oci_set_prefetch', - 'oci_statement_type', - 'oci_unregister_taf_callback', - 'odbc_autocommit', - 'odbc_close', - 'odbc_columnprivileges', - 'odbc_columns', - 'odbc_commit', - 'odbc_connect', - 'odbc_cursor', - 'odbc_data_source', - 'odbc_do', - 'odbc_error', - 'odbc_errormsg', - 'odbc_exec', - 'odbc_execute', - 'odbc_fetch_array', - 'odbc_fetch_into', - 'odbc_fetch_row', - 'odbc_field_len', - 'odbc_field_name', - 'odbc_field_num', - 'odbc_field_precision', - 'odbc_field_scale', - 'odbc_field_type', - 'odbc_foreignkeys', - 'odbc_free_result', - 'odbc_gettypeinfo', - 'odbc_next_result', - 'odbc_num_fields', - 'odbc_num_rows', - 'odbc_pconnect', - 'odbc_prepare', - 'odbc_primarykeys', - 'odbc_procedurecolumns', - 'odbc_procedures', - 'odbc_result', - 'odbc_result_all', - 'odbc_rollback', - 'odbc_setoption', - 'odbc_specialcolumns', - 'odbc_statistics', - 'odbc_tableprivileges', - 'odbc_tables', - 'openal_buffer_create', - 'openal_buffer_data', - 'openal_buffer_destroy', - 'openal_buffer_get', - 'openal_buffer_loadwav', - 'openal_context_create', - 'openal_context_current', - 'openal_context_destroy', - 'openal_context_process', - 'openal_context_suspend', - 'openal_device_close', - 'openal_device_open', - 'openal_source_create', - 'openal_source_destroy', - 'openal_source_get', - 'openal_source_pause', - 'openal_source_play', - 'openal_source_rewind', - 'openal_source_set', - 'openal_source_stop', - 'openal_stream', - 'opendir', - 'openssl_csr_new', - 'openssl_dh_compute_key', - 'openssl_free_key', - 'openssl_pkey_export', - 'openssl_pkey_free', - 'openssl_pkey_get_details', - 'openssl_spki_new', - 'openssl_x509_free', - 'pclose', - 'pfsockopen', - 'pg_affected_rows', - 'pg_cancel_query', - 'pg_client_encoding', - 'pg_close', - 'pg_connect_poll', - 'pg_connection_busy', - 'pg_connection_reset', - 'pg_connection_status', - 'pg_consume_input', - 'pg_convert', - 'pg_copy_from', - 'pg_copy_to', - 'pg_dbname', - 'pg_delete', - 'pg_end_copy', - 'pg_escape_bytea', - 'pg_escape_identifier', - 'pg_escape_literal', - 'pg_escape_string', - 'pg_execute', - 'pg_fetch_all', - 'pg_fetch_all_columns', - 'pg_fetch_array', - 'pg_fetch_assoc', - 'pg_fetch_row', - 'pg_field_name', - 'pg_field_num', - 'pg_field_size', - 'pg_field_table', - 'pg_field_type', - 'pg_field_type_oid', - 'pg_flush', - 'pg_free_result', - 'pg_get_notify', - 'pg_get_pid', - 'pg_get_result', - 'pg_host', - 'pg_insert', - 'pg_last_error', - 'pg_last_notice', - 'pg_last_oid', - 'pg_lo_close', - 'pg_lo_create', - 'pg_lo_export', - 'pg_lo_import', - 'pg_lo_open', - 'pg_lo_read', - 'pg_lo_read_all', - 'pg_lo_seek', - 'pg_lo_tell', - 'pg_lo_truncate', - 'pg_lo_unlink', - 'pg_lo_write', - 'pg_meta_data', - 'pg_num_fields', - 'pg_num_rows', - 'pg_options', - 'pg_parameter_status', - 'pg_ping', - 'pg_port', - 'pg_prepare', - 'pg_put_line', - 'pg_query', - 'pg_query_params', - 'pg_result_error', - 'pg_result_error_field', - 'pg_result_seek', - 'pg_result_status', - 'pg_select', - 'pg_send_execute', - 'pg_send_prepare', - 'pg_send_query', - 'pg_send_query_params', - 'pg_set_client_encoding', - 'pg_set_error_verbosity', - 'pg_socket', - 'pg_trace', - 'pg_transaction_status', - 'pg_tty', - 'pg_untrace', - 'pg_update', - 'pg_version', - 'php_user_filter::filter', - 'proc_close', - 'proc_get_status', - 'proc_terminate', - 'ps_add_bookmark', - 'ps_add_launchlink', - 'ps_add_locallink', - 'ps_add_note', - 'ps_add_pdflink', - 'ps_add_weblink', - 'ps_arc', - 'ps_arcn', - 'ps_begin_page', - 'ps_begin_pattern', - 'ps_begin_template', - 'ps_circle', - 'ps_clip', - 'ps_close', - 'ps_close_image', - 'ps_closepath', - 'ps_closepath_stroke', - 'ps_continue_text', - 'ps_curveto', - 'ps_delete', - 'ps_end_page', - 'ps_end_pattern', - 'ps_end_template', - 'ps_fill', - 'ps_fill_stroke', - 'ps_findfont', - 'ps_get_buffer', - 'ps_get_parameter', - 'ps_get_value', - 'ps_hyphenate', - 'ps_include_file', - 'ps_lineto', - 'ps_makespotcolor', - 'ps_moveto', - 'ps_new', - 'ps_open_file', - 'ps_open_image', - 'ps_open_image_file', - 'ps_open_memory_image', - 'ps_place_image', - 'ps_rect', - 'ps_restore', - 'ps_rotate', - 'ps_save', - 'ps_scale', - 'ps_set_border_color', - 'ps_set_border_dash', - 'ps_set_border_style', - 'ps_set_info', - 'ps_set_parameter', - 'ps_set_text_pos', - 'ps_set_value', - 'ps_setcolor', - 'ps_setdash', - 'ps_setflat', - 'ps_setfont', - 'ps_setgray', - 'ps_setlinecap', - 'ps_setlinejoin', - 'ps_setlinewidth', - 'ps_setmiterlimit', - 'ps_setoverprintmode', - 'ps_setpolydash', - 'ps_shading', - 'ps_shading_pattern', - 'ps_shfill', - 'ps_show', - 'ps_show2', - 'ps_show_boxed', - 'ps_show_xy', - 'ps_show_xy2', - 'ps_string_geometry', - 'ps_stringwidth', - 'ps_stroke', - 'ps_symbol', - 'ps_symbol_name', - 'ps_symbol_width', - 'ps_translate', - 'px_close', - 'px_create_fp', - 'px_date2string', - 'px_delete', - 'px_delete_record', - 'px_get_field', - 'px_get_info', - 'px_get_parameter', - 'px_get_record', - 'px_get_schema', - 'px_get_value', - 'px_insert_record', - 'px_new', - 'px_numfields', - 'px_numrecords', - 'px_open_fp', - 'px_put_record', - 'px_retrieve_record', - 'px_set_blob_file', - 'px_set_parameter', - 'px_set_tablename', - 'px_set_targetencoding', - 'px_set_value', - 'px_timestamp2string', - 'px_update_record', - 'radius_acct_open', - 'radius_add_server', - 'radius_auth_open', - 'radius_close', - 'radius_config', - 'radius_create_request', - 'radius_demangle', - 'radius_demangle_mppe_key', - 'radius_get_attr', - 'radius_put_addr', - 'radius_put_attr', - 'radius_put_int', - 'radius_put_string', - 'radius_put_vendor_addr', - 'radius_put_vendor_attr', - 'radius_put_vendor_int', - 'radius_put_vendor_string', - 'radius_request_authenticator', - 'radius_salt_encrypt_attr', - 'radius_send_request', - 'radius_server_secret', - 'radius_strerror', - 'readdir', - 'readfile', - 'recode_file', - 'rename', - 'rewind', - 'rewinddir', - 'rmdir', - 'rpm_close', - 'rpm_get_tag', - 'rpm_open', - 'sapi_windows_vt100_support', - 'scandir', - 'sem_acquire', - 'sem_get', - 'sem_release', - 'sem_remove', - 'set_file_buffer', - 'shm_attach', - 'shm_detach', - 'shm_get_var', - 'shm_has_var', - 'shm_put_var', - 'shm_remove', - 'shm_remove_var', - 'shmop_close', - 'shmop_delete', - 'shmop_open', - 'shmop_read', - 'shmop_size', - 'shmop_write', - 'socket_accept', - 'socket_addrinfo_bind', - 'socket_addrinfo_connect', - 'socket_addrinfo_explain', - 'socket_bind', - 'socket_clear_error', - 'socket_close', - 'socket_connect', - 'socket_export_stream', - 'socket_get_option', - 'socket_get_status', - 'socket_getopt', - 'socket_getpeername', - 'socket_getsockname', - 'socket_import_stream', - 'socket_last_error', - 'socket_listen', - 'socket_read', - 'socket_recv', - 'socket_recvfrom', - 'socket_recvmsg', - 'socket_send', - 'socket_sendmsg', - 'socket_sendto', - 'socket_set_block', - 'socket_set_blocking', - 'socket_set_nonblock', - 'socket_set_option', - 'socket_set_timeout', - 'socket_shutdown', - 'socket_write', - 'sqlite_close', - 'sqlite_fetch_string', - 'sqlite_has_more', - 'sqlite_open', - 'sqlite_popen', - 'sqlsrv_begin_transaction', - 'sqlsrv_cancel', - 'sqlsrv_client_info', - 'sqlsrv_close', - 'sqlsrv_commit', - 'sqlsrv_connect', - 'sqlsrv_execute', - 'sqlsrv_fetch', - 'sqlsrv_fetch_array', - 'sqlsrv_fetch_object', - 'sqlsrv_field_metadata', - 'sqlsrv_free_stmt', - 'sqlsrv_get_field', - 'sqlsrv_has_rows', - 'sqlsrv_next_result', - 'sqlsrv_num_fields', - 'sqlsrv_num_rows', - 'sqlsrv_prepare', - 'sqlsrv_query', - 'sqlsrv_rollback', - 'sqlsrv_rows_affected', - 'sqlsrv_send_stream_data', - 'sqlsrv_server_info', - 'ssh2_auth_agent', - 'ssh2_auth_hostbased_file', - 'ssh2_auth_none', - 'ssh2_auth_password', - 'ssh2_auth_pubkey_file', - 'ssh2_disconnect', - 'ssh2_exec', - 'ssh2_fetch_stream', - 'ssh2_fingerprint', - 'ssh2_methods_negotiated', - 'ssh2_publickey_add', - 'ssh2_publickey_init', - 'ssh2_publickey_list', - 'ssh2_publickey_remove', - 'ssh2_scp_recv', - 'ssh2_scp_send', - 'ssh2_sftp', - 'ssh2_sftp_chmod', - 'ssh2_sftp_lstat', - 'ssh2_sftp_mkdir', - 'ssh2_sftp_readlink', - 'ssh2_sftp_realpath', - 'ssh2_sftp_rename', - 'ssh2_sftp_rmdir', - 'ssh2_sftp_stat', - 'ssh2_sftp_symlink', - 'ssh2_sftp_unlink', - 'ssh2_shell', - 'ssh2_tunnel', - 'stomp_connect', - 'streamWrapper::stream_cast', - 'stream_bucket_append', - 'stream_bucket_make_writeable', - 'stream_bucket_new', - 'stream_bucket_prepend', - 'stream_context_create', - 'stream_context_get_default', - 'stream_context_get_options', - 'stream_context_get_params', - 'stream_context_set_default', - 'stream_context_set_params', - 'stream_copy_to_stream', - 'stream_encoding', - 'stream_filter_append', - 'stream_filter_prepend', - 'stream_filter_remove', - 'stream_get_contents', - 'stream_get_line', - 'stream_get_meta_data', - 'stream_isatty', - 'stream_set_blocking', - 'stream_set_chunk_size', - 'stream_set_read_buffer', - 'stream_set_timeout', - 'stream_set_write_buffer', - 'stream_socket_accept', - 'stream_socket_client', - 'stream_socket_enable_crypto', - 'stream_socket_get_name', - 'stream_socket_recvfrom', - 'stream_socket_sendto', - 'stream_socket_server', - 'stream_socket_shutdown', - 'stream_supports_lock', - 'svn_fs_abort_txn', - 'svn_fs_apply_text', - 'svn_fs_begin_txn2', - 'svn_fs_change_node_prop', - 'svn_fs_check_path', - 'svn_fs_contents_changed', - 'svn_fs_copy', - 'svn_fs_delete', - 'svn_fs_dir_entries', - 'svn_fs_file_contents', - 'svn_fs_file_length', - 'svn_fs_is_dir', - 'svn_fs_is_file', - 'svn_fs_make_dir', - 'svn_fs_make_file', - 'svn_fs_node_created_rev', - 'svn_fs_node_prop', - 'svn_fs_props_changed', - 'svn_fs_revision_prop', - 'svn_fs_revision_root', - 'svn_fs_txn_root', - 'svn_fs_youngest_rev', - 'svn_repos_create', - 'svn_repos_fs', - 'svn_repos_fs_begin_txn_for_commit', - 'svn_repos_fs_commit_txn', - 'svn_repos_open', - 'sybase_affected_rows', - 'sybase_close', - 'sybase_connect', - 'sybase_data_seek', - 'sybase_fetch_array', - 'sybase_fetch_assoc', - 'sybase_fetch_field', - 'sybase_fetch_object', - 'sybase_fetch_row', - 'sybase_field_seek', - 'sybase_free_result', - 'sybase_num_fields', - 'sybase_num_rows', - 'sybase_pconnect', - 'sybase_query', - 'sybase_result', - 'sybase_select_db', - 'sybase_set_message_handler', - 'sybase_unbuffered_query', - 'tmpfile', - 'udm_add_search_limit', - 'udm_alloc_agent', - 'udm_alloc_agent_array', - 'udm_cat_list', - 'udm_cat_path', - 'udm_check_charset', - 'udm_clear_search_limits', - 'udm_crc32', - 'udm_errno', - 'udm_error', - 'udm_find', - 'udm_free_agent', - 'udm_free_res', - 'udm_get_doc_count', - 'udm_get_res_field', - 'udm_get_res_param', - 'udm_hash32', - 'udm_load_ispell_data', - 'udm_set_agent_param', - 'unlink', - 'vfprintf', - 'w32api_init_dtype', - 'wddx_add_vars', - 'wddx_packet_end', - 'wddx_packet_start', - 'xml_get_current_byte_index', - 'xml_get_current_column_number', - 'xml_get_current_line_number', - 'xml_get_error_code', - 'xml_parse', - 'xml_parse_into_struct', - 'xml_parser_create', - 'xml_parser_create_ns', - 'xml_parser_free', - 'xml_parser_get_option', - 'xml_parser_set_option', - 'xml_set_character_data_handler', - 'xml_set_default_handler', - 'xml_set_element_handler', - 'xml_set_end_namespace_decl_handler', - 'xml_set_external_entity_ref_handler', - 'xml_set_notation_decl_handler', - 'xml_set_object', - 'xml_set_processing_instruction_handler', - 'xml_set_start_namespace_decl_handler', - 'xml_set_unparsed_entity_decl_handler', - 'xmlrpc_server_add_introspection_data', - 'xmlrpc_server_call_method', - 'xmlrpc_server_create', - 'xmlrpc_server_destroy', - 'xmlrpc_server_register_introspection_callback', - 'xmlrpc_server_register_method', - 'xmlwriter_end_attribute', - 'xmlwriter_end_cdata', - 'xmlwriter_end_comment', - 'xmlwriter_end_document', - 'xmlwriter_end_dtd', - 'xmlwriter_end_dtd_attlist', - 'xmlwriter_end_dtd_element', - 'xmlwriter_end_dtd_entity', - 'xmlwriter_end_element', - 'xmlwriter_end_pi', - 'xmlwriter_flush', - 'xmlwriter_full_end_element', - 'xmlwriter_open_memory', - 'xmlwriter_open_uri', - 'xmlwriter_output_memory', - 'xmlwriter_set_indent', - 'xmlwriter_set_indent_string', - 'xmlwriter_start_attribute', - 'xmlwriter_start_attribute_ns', - 'xmlwriter_start_cdata', - 'xmlwriter_start_comment', - 'xmlwriter_start_document', - 'xmlwriter_start_dtd', - 'xmlwriter_start_dtd_attlist', - 'xmlwriter_start_dtd_element', - 'xmlwriter_start_dtd_entity', - 'xmlwriter_start_element', - 'xmlwriter_start_element_ns', - 'xmlwriter_start_pi', - 'xmlwriter_text', - 'xmlwriter_write_attribute', - 'xmlwriter_write_attribute_ns', - 'xmlwriter_write_cdata', - 'xmlwriter_write_comment', - 'xmlwriter_write_dtd', - 'xmlwriter_write_dtd_attlist', - 'xmlwriter_write_dtd_element', - 'xmlwriter_write_dtd_entity', - 'xmlwriter_write_element', - 'xmlwriter_write_element_ns', - 'xmlwriter_write_pi', - 'xmlwriter_write_raw', - 'xslt_create', - 'yaz_addinfo', - 'yaz_ccl_conf', - 'yaz_ccl_parse', - 'yaz_close', - 'yaz_database', - 'yaz_element', - 'yaz_errno', - 'yaz_error', - 'yaz_es', - 'yaz_es_result', - 'yaz_get_option', - 'yaz_hits', - 'yaz_itemorder', - 'yaz_present', - 'yaz_range', - 'yaz_record', - 'yaz_scan', - 'yaz_scan_result', - 'yaz_schema', - 'yaz_search', - 'yaz_sort', - 'yaz_syntax', - 'zip_close', - 'zip_entry_close', - 'zip_entry_compressedsize', - 'zip_entry_compressionmethod', - 'zip_entry_filesize', - 'zip_entry_name', - 'zip_entry_open', - 'zip_entry_read', - 'zip_open', - 'zip_read', - ]; - } -} diff --git a/app/vendor/sebastian/type/ChangeLog.md b/app/vendor/sebastian/type/ChangeLog.md index 0691a9b1e..e4a200d55 100644 --- a/app/vendor/sebastian/type/ChangeLog.md +++ b/app/vendor/sebastian/type/ChangeLog.md @@ -2,6 +2,72 @@ All notable changes are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles. +## [6.0.3] - 2025-08-09 + +### Fixed + +* [#34](https://github.com/sebastianbergmann/type/pull/34): `infection.json` is missing from `.gitattributes` + +## [6.0.2] - 2025-03-18 + +### Fixed + +* [#33](https://github.com/sebastianbergmann/type/issues/33): `ReflectionMapper` does not handle unions that contain `iterable` correctly + +## [6.0.1] - 2025-03-18 + +### Fixed + +* [#33](https://github.com/sebastianbergmann/type/issues/33): `ReflectionMapper` does not handle unions that contain `iterable` correctly + +## [6.0.0] - 2025-02-07 + +### Removed + +* This component is no longer supported on PHP 8.2 + +## [5.1.3] - 2025-08-09 + +### Fixed + +* [#34](https://github.com/sebastianbergmann/type/pull/34): `infection.json` is missing from `.gitattributes` + +## [5.1.2] - 2025-03-18 + +### Fixed + +* [#33](https://github.com/sebastianbergmann/type/issues/33): `ReflectionMapper` does not handle unions that contain `iterable` correctly + +## [5.1.1] - 2025-03-18 + +### Fixed + +* [#33](https://github.com/sebastianbergmann/type/issues/33): `ReflectionMapper` does not handle unions that contain `iterable` correctly + +## [5.1.0] - 2024-09-17 + +### Added + +* Added `ReflectionMapper::fromPropertyType()` for mapping `\ReflectionProperty` to a `Type` object + +## [5.0.1] - 2024-07-03 + +### Changed + +* This project now uses PHPStan instead of Psalm for static analysis + +## [5.0.0] - 2024-02-02 + +### Removed + +* This component is no longer supported on PHP 8.1 + +## [4.0.0] - 2023-02-03 + +### Removed + +* This component is no longer supported on PHP 7.3, PHP 7.4 and PHP 8.0 + ## [3.2.1] - 2023-02-03 ### Fixed @@ -147,6 +213,17 @@ All notable changes are documented in this file using the [Keep a CHANGELOG](htt * Initial release based on [code contributed by Michel Hartmann to PHPUnit](https://github.com/sebastianbergmann/phpunit/pull/3673) +[6.0.3]: https://github.com/sebastianbergmann/type/compare/6.0.2...6.0.3 +[6.0.2]: https://github.com/sebastianbergmann/type/compare/6.0.1...6.0.2 +[6.0.1]: https://github.com/sebastianbergmann/type/compare/6.0.0...6.0.1 +[6.0.0]: https://github.com/sebastianbergmann/type/compare/5.1...6.0.0 +[5.1.3]: https://github.com/sebastianbergmann/type/compare/5.1.2...5.1.3 +[5.1.2]: https://github.com/sebastianbergmann/type/compare/5.1.1...5.1.2 +[5.1.1]: https://github.com/sebastianbergmann/type/compare/5.1.0...5.1.1 +[5.1.0]: https://github.com/sebastianbergmann/type/compare/5.0.1...5.1.0 +[5.0.1]: https://github.com/sebastianbergmann/type/compare/5.0.0...5.0.1 +[5.0.0]: https://github.com/sebastianbergmann/type/compare/4.0...5.0.0 +[4.0.0]: https://github.com/sebastianbergmann/type/compare/3.2.1...4.0.0 [3.2.1]: https://github.com/sebastianbergmann/type/compare/3.2.0...3.2.1 [3.2.0]: https://github.com/sebastianbergmann/type/compare/3.1.0...3.2.0 [3.1.0]: https://github.com/sebastianbergmann/type/compare/3.0.0...3.1.0 diff --git a/app/vendor/sebastian/type/LICENSE b/app/vendor/sebastian/type/LICENSE index f4e4a3289..84a832212 100644 --- a/app/vendor/sebastian/type/LICENSE +++ b/app/vendor/sebastian/type/LICENSE @@ -1,33 +1,29 @@ -sebastian/type +BSD 3-Clause License -Copyright (c) 2019-2022, Sebastian Bergmann . +Copyright (c) 2019-2025, Sebastian Bergmann All rights reserved. Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: +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. +1. 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. +2. 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 Sebastian Bergmann nor the names of his - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. +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/app/vendor/sebastian/type/README.md b/app/vendor/sebastian/type/README.md index 1036ce7a7..e9c8f0a21 100644 --- a/app/vendor/sebastian/type/README.md +++ b/app/vendor/sebastian/type/README.md @@ -1,7 +1,8 @@ -# sebastian/type - +[![Latest Stable Version](https://poser.pugx.org/sebastian/type/v)](https://packagist.org/packages/sebastian/type) [![CI Status](https://github.com/sebastianbergmann/type/workflows/CI/badge.svg)](https://github.com/sebastianbergmann/type/actions) -[![Type Coverage](https://shepherd.dev/github/sebastianbergmann/type/coverage.svg)](https://shepherd.dev/github/sebastianbergmann/type) +[![codecov](https://codecov.io/gh/sebastianbergmann/type/branch/main/graph/badge.svg)](https://codecov.io/gh/sebastianbergmann/type) + +# sebastian/type Collection of value objects that represent the types of the PHP type system. diff --git a/app/vendor/sebastian/type/SECURITY.md b/app/vendor/sebastian/type/SECURITY.md new file mode 100644 index 000000000..d88ff0019 --- /dev/null +++ b/app/vendor/sebastian/type/SECURITY.md @@ -0,0 +1,30 @@ +# Security Policy + +If you believe you have found a security vulnerability in the library that is developed in this repository, please report it to us through coordinated disclosure. + +**Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.** + +Instead, please email `sebastian@phpunit.de`. + +Please include as much of the information listed below as you can to help us better understand and resolve the issue: + +* The type of issue +* Full paths of source file(s) related to the manifestation of the issue +* The location of the affected source code (tag/branch/commit or direct URL) +* Any special configuration required to reproduce the issue +* Step-by-step instructions to reproduce the issue +* Proof-of-concept or exploit code (if possible) +* Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +## Web Context + +The library that is developed in this repository was either extracted from [PHPUnit](https://github.com/sebastianbergmann/phpunit) or developed specifically as a dependency for PHPUnit. + +The library is developed with a focus on development environments and the command-line. No specific testing or hardening with regard to using the library in an HTTP or web context or with untrusted input data is performed. The library might also contain functionality that intentionally exposes internal application data for debugging purposes. + +If the library is used in a web application, the application developer is responsible for filtering inputs or escaping outputs as necessary and for verifying that the used functionality is safe for use within the intended context. + +Vulnerabilities specific to the use outside a development context will be fixed as applicable, provided that the fix does not have an averse effect on the primary use case for development purposes. + diff --git a/app/vendor/sebastian/type/composer.json b/app/vendor/sebastian/type/composer.json index a0865c93d..e5b966df5 100644 --- a/app/vendor/sebastian/type/composer.json +++ b/app/vendor/sebastian/type/composer.json @@ -12,18 +12,19 @@ } ], "support": { - "issues": "https://github.com/sebastianbergmann/type/issues" + "issues": "https://github.com/sebastianbergmann/type/issues", + "security": "https://github.com/sebastianbergmann/type/security/policy" }, "prefer-stable": true, "require": { - "php": ">=7.3" + "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^9.5" + "phpunit/phpunit": "^12.0" }, "config": { "platform": { - "php": "7.3.0" + "php": "8.3.0" }, "optimize-autoloader": true, "sort-packages": true @@ -44,7 +45,7 @@ }, "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-main": "6.0-dev" } } } diff --git a/app/vendor/sebastian/type/src/Parameter.php b/app/vendor/sebastian/type/src/Parameter.php index 1adb061e6..7898cefa9 100644 --- a/app/vendor/sebastian/type/src/Parameter.php +++ b/app/vendor/sebastian/type/src/Parameter.php @@ -9,20 +9,19 @@ */ namespace SebastianBergmann\Type; -final class Parameter +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for this library + */ +final readonly class Parameter { /** - * @psalm-var non-empty-string - */ - private $name; - - /** - * @var Type + * @var non-empty-string */ - private $type; + private string $name; + private Type $type; /** - * @psalm-param non-empty-string $name + * @param non-empty-string $name */ public function __construct(string $name, Type $type) { @@ -30,6 +29,9 @@ public function __construct(string $name, Type $type) $this->type = $type; } + /** + * @return non-empty-string + */ public function name(): string { return $this->name; diff --git a/app/vendor/sebastian/type/src/ReflectionMapper.php b/app/vendor/sebastian/type/src/ReflectionMapper.php index 32099b4bc..45db7fb48 100644 --- a/app/vendor/sebastian/type/src/ReflectionMapper.php +++ b/app/vendor/sebastian/type/src/ReflectionMapper.php @@ -9,28 +9,31 @@ */ namespace SebastianBergmann\Type; +use function array_filter; use function assert; -use ReflectionFunctionAbstract; +use ReflectionFunction; use ReflectionIntersectionType; use ReflectionMethod; use ReflectionNamedType; +use ReflectionProperty; use ReflectionType; use ReflectionUnionType; +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for this library + */ final class ReflectionMapper { /** - * @psalm-return list + * @return list */ - public function fromParameterTypes(ReflectionFunctionAbstract $functionOrMethod): array + public function fromParameterTypes(ReflectionFunction|ReflectionMethod $reflector): array { $parameters = []; - foreach ($functionOrMethod->getParameters() as $parameter) { + foreach ($reflector->getParameters() as $parameter) { $name = $parameter->getName(); - assert($name !== ''); - if (!$parameter->hasType()) { $parameters[] = new Parameter($name, new UnknownType); @@ -42,7 +45,7 @@ public function fromParameterTypes(ReflectionFunctionAbstract $functionOrMethod) if ($type instanceof ReflectionNamedType) { $parameters[] = new Parameter( $name, - $this->mapNamedType($type, $functionOrMethod) + $this->mapNamedType($type, $reflector), ); continue; @@ -51,7 +54,7 @@ public function fromParameterTypes(ReflectionFunctionAbstract $functionOrMethod) if ($type instanceof ReflectionUnionType) { $parameters[] = new Parameter( $name, - $this->mapUnionType($type, $functionOrMethod) + $this->mapUnionType($type, $reflector), ); continue; @@ -60,7 +63,7 @@ public function fromParameterTypes(ReflectionFunctionAbstract $functionOrMethod) if ($type instanceof ReflectionIntersectionType) { $parameters[] = new Parameter( $name, - $this->mapIntersectionType($type, $functionOrMethod) + $this->mapIntersectionType($type, $reflector), ); } } @@ -68,117 +71,159 @@ public function fromParameterTypes(ReflectionFunctionAbstract $functionOrMethod) return $parameters; } - public function fromReturnType(ReflectionFunctionAbstract $functionOrMethod): Type + public function fromReturnType(ReflectionFunction|ReflectionMethod $reflector): Type { - if (!$this->hasReturnType($functionOrMethod)) { + if (!$this->hasReturnType($reflector)) { return new UnknownType; } - $returnType = $this->returnType($functionOrMethod); + $returnType = $this->returnType($reflector); assert($returnType instanceof ReflectionNamedType || $returnType instanceof ReflectionUnionType || $returnType instanceof ReflectionIntersectionType); if ($returnType instanceof ReflectionNamedType) { - return $this->mapNamedType($returnType, $functionOrMethod); + return $this->mapNamedType($returnType, $reflector); } if ($returnType instanceof ReflectionUnionType) { - return $this->mapUnionType($returnType, $functionOrMethod); + return $this->mapUnionType($returnType, $reflector); + } + + return $this->mapIntersectionType($returnType, $reflector); + } + + public function fromPropertyType(ReflectionProperty $reflector): Type + { + if (!$reflector->hasType()) { + return new UnknownType; } - if ($returnType instanceof ReflectionIntersectionType) { - return $this->mapIntersectionType($returnType, $functionOrMethod); + $propertyType = $reflector->getType(); + + assert($propertyType instanceof ReflectionNamedType || $propertyType instanceof ReflectionUnionType || $propertyType instanceof ReflectionIntersectionType); + + if ($propertyType instanceof ReflectionNamedType) { + return $this->mapNamedType($propertyType, $reflector); } + + if ($propertyType instanceof ReflectionUnionType) { + return $this->mapUnionType($propertyType, $reflector); + } + + return $this->mapIntersectionType($propertyType, $reflector); } - private function mapNamedType(ReflectionNamedType $type, ReflectionFunctionAbstract $functionOrMethod): Type + private function mapNamedType(ReflectionNamedType $type, ReflectionFunction|ReflectionMethod|ReflectionProperty $reflector): Type { - if ($functionOrMethod instanceof ReflectionMethod && $type->getName() === 'self') { + $classScope = !$reflector instanceof ReflectionFunction; + $typeName = $type->getName(); + + assert($typeName !== ''); + + if ($classScope && $typeName === 'self') { return ObjectType::fromName( - $functionOrMethod->getDeclaringClass()->getName(), - $type->allowsNull() + $reflector->getDeclaringClass()->getName(), + $type->allowsNull(), ); } - if ($functionOrMethod instanceof ReflectionMethod && $type->getName() === 'static') { + if ($classScope && $typeName === 'static') { return new StaticType( - TypeName::fromReflection($functionOrMethod->getDeclaringClass()), - $type->allowsNull() + TypeName::fromReflection($reflector->getDeclaringClass()), + $type->allowsNull(), ); } - if ($type->getName() === 'mixed') { + if ($typeName === 'mixed') { return new MixedType; } - if ($functionOrMethod instanceof ReflectionMethod && $type->getName() === 'parent') { + if ($classScope && $typeName === 'parent') { + $parentClass = $reflector->getDeclaringClass()->getParentClass(); + + assert($parentClass !== false); + return ObjectType::fromName( - $functionOrMethod->getDeclaringClass()->getParentClass()->getName(), - $type->allowsNull() + $parentClass->getName(), + $type->allowsNull(), ); } return Type::fromName( - $type->getName(), - $type->allowsNull() + $typeName, + $type->allowsNull(), ); } - private function mapUnionType(ReflectionUnionType $type, ReflectionFunctionAbstract $functionOrMethod): Type + private function mapUnionType(ReflectionUnionType $type, ReflectionFunction|ReflectionMethod|ReflectionProperty $reflector): Type { - $types = []; + $types = []; + $objectType = false; + $genericObjectType = false; foreach ($type->getTypes() as $_type) { - assert($_type instanceof ReflectionNamedType || $_type instanceof ReflectionIntersectionType); - if ($_type instanceof ReflectionNamedType) { - $types[] = $this->mapNamedType($_type, $functionOrMethod); + $namedType = $this->mapNamedType($_type, $reflector); + + if ($namedType instanceof GenericObjectType) { + $genericObjectType = true; + } elseif ($namedType instanceof ObjectType) { + $objectType = true; + } + + $types[] = $namedType; continue; } - $types[] = $this->mapIntersectionType($_type, $functionOrMethod); + $types[] = $this->mapIntersectionType($_type, $reflector); + } + + if ($objectType && $genericObjectType) { + $types = array_filter( + $types, + static function (Type $type): bool + { + if ($type instanceof ObjectType) { + return false; + } + + return true; + }, + ); } return new UnionType(...$types); } - private function mapIntersectionType(ReflectionIntersectionType $type, ReflectionFunctionAbstract $functionOrMethod): Type + private function mapIntersectionType(ReflectionIntersectionType $type, ReflectionFunction|ReflectionMethod|ReflectionProperty $reflector): Type { $types = []; foreach ($type->getTypes() as $_type) { assert($_type instanceof ReflectionNamedType); - $types[] = $this->mapNamedType($_type, $functionOrMethod); + $types[] = $this->mapNamedType($_type, $reflector); } return new IntersectionType(...$types); } - private function hasReturnType(ReflectionFunctionAbstract $functionOrMethod): bool + private function hasReturnType(ReflectionFunction|ReflectionMethod $reflector): bool { - if ($functionOrMethod->hasReturnType()) { + if ($reflector->hasReturnType()) { return true; } - if (!method_exists($functionOrMethod, 'hasTentativeReturnType')) { - return false; - } - - return $functionOrMethod->hasTentativeReturnType(); + return $reflector->hasTentativeReturnType(); } - private function returnType(ReflectionFunctionAbstract $functionOrMethod): ?ReflectionType + private function returnType(ReflectionFunction|ReflectionMethod $reflector): ?ReflectionType { - if ($functionOrMethod->hasReturnType()) { - return $functionOrMethod->getReturnType(); - } - - if (!method_exists($functionOrMethod, 'getTentativeReturnType')) { - return null; + if ($reflector->hasReturnType()) { + return $reflector->getReturnType(); } - return $functionOrMethod->getTentativeReturnType(); + return $reflector->getTentativeReturnType(); } } diff --git a/app/vendor/sebastian/type/src/TypeName.php b/app/vendor/sebastian/type/src/TypeName.php index 17d477cfe..865af983c 100644 --- a/app/vendor/sebastian/type/src/TypeName.php +++ b/app/vendor/sebastian/type/src/TypeName.php @@ -10,23 +10,27 @@ namespace SebastianBergmann\Type; use function array_pop; +use function assert; use function explode; use function implode; use function substr; use ReflectionClass; -final class TypeName +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for this library + */ +final readonly class TypeName { + private ?string $namespaceName; + /** - * @var ?string + * @var non-empty-string */ - private $namespaceName; + private string $simpleName; /** - * @var string + * @param class-string $fullClassName */ - private $simpleName; - public static function fromQualifiedName(string $fullClassName): self { if ($fullClassName[0] === '\\') { @@ -38,17 +42,29 @@ public static function fromQualifiedName(string $fullClassName): self $simpleName = array_pop($classNameParts); $namespaceName = implode('\\', $classNameParts); + assert($simpleName !== ''); + return new self($namespaceName, $simpleName); } + /** + * @param ReflectionClass $type + */ public static function fromReflection(ReflectionClass $type): self { + $simpleName = $type->getShortName(); + + assert($simpleName !== ''); + return new self( $type->getNamespaceName(), - $type->getShortName() + $simpleName, ); } + /** + * @param non-empty-string $simpleName + */ public function __construct(?string $namespaceName, string $simpleName) { if ($namespaceName === '') { @@ -64,11 +80,17 @@ public function namespaceName(): ?string return $this->namespaceName; } + /** + * @return non-empty-string + */ public function simpleName(): string { return $this->simpleName; } + /** + * @return non-empty-string + */ public function qualifiedName(): string { return $this->namespaceName === null diff --git a/app/vendor/sebastian/type/src/exception/Exception.php b/app/vendor/sebastian/type/src/exception/Exception.php index e0e7ee579..07ae3fc67 100644 --- a/app/vendor/sebastian/type/src/exception/Exception.php +++ b/app/vendor/sebastian/type/src/exception/Exception.php @@ -11,6 +11,9 @@ use Throwable; +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for this library + */ interface Exception extends Throwable { } diff --git a/app/vendor/sebastian/type/src/exception/RuntimeException.php b/app/vendor/sebastian/type/src/exception/RuntimeException.php index 4dfea6a6a..dfb1db7c8 100644 --- a/app/vendor/sebastian/type/src/exception/RuntimeException.php +++ b/app/vendor/sebastian/type/src/exception/RuntimeException.php @@ -9,6 +9,9 @@ */ namespace SebastianBergmann\Type; +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for this library + */ final class RuntimeException extends \RuntimeException implements Exception { } diff --git a/app/vendor/sebastian/type/src/type/CallableType.php b/app/vendor/sebastian/type/src/type/CallableType.php index d44fb0ca9..dbe355488 100644 --- a/app/vendor/sebastian/type/src/type/CallableType.php +++ b/app/vendor/sebastian/type/src/type/CallableType.php @@ -17,26 +17,23 @@ use function is_array; use function is_object; use function is_string; +use function str_contains; use Closure; use ReflectionClass; -use ReflectionException; use ReflectionObject; +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for this library + */ final class CallableType extends Type { - /** - * @var bool - */ - private $allowsNull; + private bool $allowsNull; public function __construct(bool $nullable) { $this->allowsNull = $nullable; } - /** - * @throws RuntimeException - */ public function isAssignable(Type $other): bool { if ($this->allowsNull && $other instanceof NullType) { @@ -74,6 +71,9 @@ public function isAssignable(Type $other): bool return false; } + /** + * @return 'callable' + */ public function name(): string { return 'callable'; @@ -84,9 +84,6 @@ public function allowsNull(): bool return $this->allowsNull; } - /** - * @psalm-assert-if-true CallableType $this - */ public function isCallable(): bool { return true; @@ -94,34 +91,16 @@ public function isCallable(): bool private function isClosure(ObjectType $type): bool { - return !$type->className()->isNamespaced() && $type->className()->simpleName() === Closure::class; + return $type->className()->qualifiedName() === Closure::class; } - /** - * @throws RuntimeException - */ private function hasInvokeMethod(ObjectType $type): bool { $className = $type->className()->qualifiedName(); - assert(class_exists($className)); - try { - $class = new ReflectionClass($className); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new RuntimeException( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - // @codeCoverageIgnoreEnd - } - - if ($class->hasMethod('__invoke')) { - return true; - } + assert(class_exists($className)); - return false; + return (new ReflectionClass($className))->hasMethod('__invoke'); } private function isFunction(SimpleType $type): bool @@ -163,7 +142,7 @@ private function isClassCallback(SimpleType $type): bool } if (is_string($type->value())) { - if (strpos($type->value(), '::') === false) { + if (!str_contains($type->value(), '::')) { return false; } @@ -186,27 +165,18 @@ private function isClassCallback(SimpleType $type): bool [$className, $methodName] = $type->value(); } - assert(isset($className) && is_string($className) && class_exists($className)); - assert(isset($methodName) && is_string($methodName)); - - try { - $class = new ReflectionClass($className); + if (!class_exists($className)) { + return false; + } - if ($class->hasMethod($methodName)) { - $method = $class->getMethod($methodName); + $class = new ReflectionClass($className); - return $method->isPublic() && $method->isStatic(); - } - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new RuntimeException( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - // @codeCoverageIgnoreEnd + if (!$class->hasMethod($methodName)) { + return false; } - return false; + $method = $class->getMethod($methodName); + + return $method->isPublic() && $method->isStatic(); } } diff --git a/app/vendor/sebastian/type/src/type/FalseType.php b/app/vendor/sebastian/type/src/type/FalseType.php index f417fb699..3de720a41 100644 --- a/app/vendor/sebastian/type/src/type/FalseType.php +++ b/app/vendor/sebastian/type/src/type/FalseType.php @@ -9,6 +9,9 @@ */ namespace SebastianBergmann\Type; +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for this library + */ final class FalseType extends Type { public function isAssignable(Type $other): bool @@ -22,6 +25,9 @@ public function isAssignable(Type $other): bool $other->value() === false; } + /** + * @return 'false' + */ public function name(): string { return 'false'; @@ -32,9 +38,6 @@ public function allowsNull(): bool return false; } - /** - * @psalm-assert-if-true FalseType $this - */ public function isFalse(): bool { return true; diff --git a/app/vendor/sebastian/type/src/type/GenericObjectType.php b/app/vendor/sebastian/type/src/type/GenericObjectType.php index d06963f09..efe1ae33d 100644 --- a/app/vendor/sebastian/type/src/type/GenericObjectType.php +++ b/app/vendor/sebastian/type/src/type/GenericObjectType.php @@ -9,12 +9,12 @@ */ namespace SebastianBergmann\Type; +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for this library + */ final class GenericObjectType extends Type { - /** - * @var bool - */ - private $allowsNull; + private bool $allowsNull; public function __construct(bool $nullable) { @@ -34,6 +34,9 @@ public function isAssignable(Type $other): bool return true; } + /** + * @return 'object' + */ public function name(): string { return 'object'; @@ -44,9 +47,6 @@ public function allowsNull(): bool return $this->allowsNull; } - /** - * @psalm-assert-if-true GenericObjectType $this - */ public function isGenericObject(): bool { return true; diff --git a/app/vendor/sebastian/type/src/type/IntersectionType.php b/app/vendor/sebastian/type/src/type/IntersectionType.php index 2e133940a..f8cad8cbc 100644 --- a/app/vendor/sebastian/type/src/type/IntersectionType.php +++ b/app/vendor/sebastian/type/src/type/IntersectionType.php @@ -15,12 +15,15 @@ use function in_array; use function sort; +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for this library + */ final class IntersectionType extends Type { /** - * @psalm-var non-empty-list + * @var non-empty-list */ - private $types; + private array $types; /** * @throws RuntimeException @@ -31,6 +34,8 @@ public function __construct(Type ...$types) $this->ensureOnlyValidTypes(...$types); $this->ensureNoDuplicateTypes(...$types); + assert($types !== []); + $this->types = $types; } @@ -39,11 +44,17 @@ public function isAssignable(Type $other): bool return $other->isObject(); } + /** + * @return non-empty-string + */ public function asString(): string { return $this->name(); } + /** + * @return non-empty-string + */ public function name(): string { $types = []; @@ -62,16 +73,13 @@ public function allowsNull(): bool return false; } - /** - * @psalm-assert-if-true IntersectionType $this - */ public function isIntersection(): bool { return true; } /** - * @psalm-return non-empty-list + * @return non-empty-list */ public function types(): array { @@ -85,7 +93,7 @@ private function ensureMinimumOfTwoTypes(Type ...$types): void { if (count($types) < 2) { throw new RuntimeException( - 'An intersection type must be composed of at least two types' + 'An intersection type must be composed of at least two types', ); } } @@ -98,7 +106,7 @@ private function ensureOnlyValidTypes(Type ...$types): void foreach ($types as $type) { if (!$type->isObject()) { throw new RuntimeException( - 'An intersection type can only be composed of interfaces and classes' + 'An intersection type can only be composed of interfaces and classes', ); } } diff --git a/app/vendor/sebastian/type/src/type/IterableType.php b/app/vendor/sebastian/type/src/type/IterableType.php index 7b2a58fa4..608ddc581 100644 --- a/app/vendor/sebastian/type/src/type/IterableType.php +++ b/app/vendor/sebastian/type/src/type/IterableType.php @@ -13,14 +13,13 @@ use function class_exists; use function is_iterable; use ReflectionClass; -use ReflectionException; +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for this library + */ final class IterableType extends Type { - /** - * @var bool - */ - private $allowsNull; + private bool $allowsNull; public function __construct(bool $nullable) { @@ -46,24 +45,18 @@ public function isAssignable(Type $other): bool if ($other instanceof ObjectType) { $className = $other->className()->qualifiedName(); + assert(class_exists($className)); - try { - return (new ReflectionClass($className))->isIterable(); - // @codeCoverageIgnoreStart - } catch (ReflectionException $e) { - throw new RuntimeException( - $e->getMessage(), - (int) $e->getCode(), - $e - ); - // @codeCoverageIgnoreEnd - } + return (new ReflectionClass($className))->isIterable(); } return false; } + /** + * @return 'iterable' + */ public function name(): string { return 'iterable'; @@ -74,9 +67,6 @@ public function allowsNull(): bool return $this->allowsNull; } - /** - * @psalm-assert-if-true IterableType $this - */ public function isIterable(): bool { return true; diff --git a/app/vendor/sebastian/type/src/type/MixedType.php b/app/vendor/sebastian/type/src/type/MixedType.php index a1412e453..c449d112b 100644 --- a/app/vendor/sebastian/type/src/type/MixedType.php +++ b/app/vendor/sebastian/type/src/type/MixedType.php @@ -9,6 +9,9 @@ */ namespace SebastianBergmann\Type; +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for this library + */ final class MixedType extends Type { public function isAssignable(Type $other): bool @@ -16,11 +19,17 @@ public function isAssignable(Type $other): bool return !$other instanceof VoidType; } + /** + * @return 'mixed' + */ public function asString(): string { return 'mixed'; } + /** + * @return 'mixed' + */ public function name(): string { return 'mixed'; @@ -31,9 +40,6 @@ public function allowsNull(): bool return true; } - /** - * @psalm-assert-if-true MixedType $this - */ public function isMixed(): bool { return true; diff --git a/app/vendor/sebastian/type/src/type/NeverType.php b/app/vendor/sebastian/type/src/type/NeverType.php index 6c144743e..ae80b54ae 100644 --- a/app/vendor/sebastian/type/src/type/NeverType.php +++ b/app/vendor/sebastian/type/src/type/NeverType.php @@ -9,6 +9,9 @@ */ namespace SebastianBergmann\Type; +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for this library + */ final class NeverType extends Type { public function isAssignable(Type $other): bool @@ -16,6 +19,9 @@ public function isAssignable(Type $other): bool return $other instanceof self; } + /** + * @return 'never' + */ public function name(): string { return 'never'; @@ -26,9 +32,6 @@ public function allowsNull(): bool return false; } - /** - * @psalm-assert-if-true NeverType $this - */ public function isNever(): bool { return true; diff --git a/app/vendor/sebastian/type/src/type/NullType.php b/app/vendor/sebastian/type/src/type/NullType.php index 93834eabf..a261347e5 100644 --- a/app/vendor/sebastian/type/src/type/NullType.php +++ b/app/vendor/sebastian/type/src/type/NullType.php @@ -9,6 +9,9 @@ */ namespace SebastianBergmann\Type; +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for this library + */ final class NullType extends Type { public function isAssignable(Type $other): bool @@ -16,11 +19,17 @@ public function isAssignable(Type $other): bool return !($other instanceof VoidType); } + /** + * @return 'null' + */ public function name(): string { return 'null'; } + /** + * @return 'null' + */ public function asString(): string { return 'null'; @@ -31,9 +40,6 @@ public function allowsNull(): bool return true; } - /** - * @psalm-assert-if-true NullType $this - */ public function isNull(): bool { return true; diff --git a/app/vendor/sebastian/type/src/type/ObjectType.php b/app/vendor/sebastian/type/src/type/ObjectType.php index 44febb278..4ff4592ea 100644 --- a/app/vendor/sebastian/type/src/type/ObjectType.php +++ b/app/vendor/sebastian/type/src/type/ObjectType.php @@ -12,17 +12,13 @@ use function is_subclass_of; use function strcasecmp; +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for this library + */ final class ObjectType extends Type { - /** - * @var TypeName - */ - private $className; - - /** - * @var bool - */ - private $allowsNull; + private TypeName $className; + private bool $allowsNull; public function __construct(TypeName $className, bool $allowsNull) { @@ -49,6 +45,9 @@ public function isAssignable(Type $other): bool return false; } + /** + * @return non-empty-string + */ public function name(): string { return $this->className->qualifiedName(); @@ -64,9 +63,6 @@ public function className(): TypeName return $this->className; } - /** - * @psalm-assert-if-true ObjectType $this - */ public function isObject(): bool { return true; diff --git a/app/vendor/sebastian/type/src/type/SimpleType.php b/app/vendor/sebastian/type/src/type/SimpleType.php index 4dce75dab..b242b5cbe 100644 --- a/app/vendor/sebastian/type/src/type/SimpleType.php +++ b/app/vendor/sebastian/type/src/type/SimpleType.php @@ -11,24 +11,22 @@ use function strtolower; +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for this library + */ final class SimpleType extends Type { /** - * @var string - */ - private $name; - - /** - * @var bool + * @var non-empty-string */ - private $allowsNull; + private string $name; + private bool $allowsNull; + private mixed $value; /** - * @var mixed + * @param non-empty-string $name */ - private $value; - - public function __construct(string $name, bool $nullable, $value = null) + public function __construct(string $name, bool $nullable, mixed $value = null) { $this->name = $this->normalize($name); $this->allowsNull = $nullable; @@ -56,6 +54,9 @@ public function isAssignable(Type $other): bool return false; } + /** + * @return non-empty-string + */ public function name(): string { return $this->name; @@ -66,39 +67,31 @@ public function allowsNull(): bool return $this->allowsNull; } - public function value() + public function value(): mixed { return $this->value; } - /** - * @psalm-assert-if-true SimpleType $this - */ public function isSimple(): bool { return true; } + /** + * @param non-empty-string $name + * + * @return non-empty-string + */ private function normalize(string $name): string { $name = strtolower($name); - switch ($name) { - case 'boolean': - return 'bool'; - - case 'real': - case 'double': - return 'float'; - - case 'integer': - return 'int'; - - case '[]': - return 'array'; - - default: - return $name; - } + return match ($name) { + 'boolean' => 'bool', + 'real', 'double' => 'float', + 'integer' => 'int', + '[]' => 'array', + default => $name, + }; } } diff --git a/app/vendor/sebastian/type/src/type/StaticType.php b/app/vendor/sebastian/type/src/type/StaticType.php index cbc13f5fe..1f7ba2ece 100644 --- a/app/vendor/sebastian/type/src/type/StaticType.php +++ b/app/vendor/sebastian/type/src/type/StaticType.php @@ -9,17 +9,16 @@ */ namespace SebastianBergmann\Type; +use function is_subclass_of; +use function strcasecmp; + +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for this library + */ final class StaticType extends Type { - /** - * @var TypeName - */ - private $className; - - /** - * @var bool - */ - private $allowsNull; + private TypeName $className; + private bool $allowsNull; public function __construct(TypeName $className, bool $allowsNull) { @@ -48,6 +47,9 @@ public function isAssignable(Type $other): bool return false; } + /** + * @return 'static' + */ public function name(): string { return 'static'; @@ -58,9 +60,6 @@ public function allowsNull(): bool return $this->allowsNull; } - /** - * @psalm-assert-if-true StaticType $this - */ public function isStatic(): bool { return true; diff --git a/app/vendor/sebastian/type/src/type/TrueType.php b/app/vendor/sebastian/type/src/type/TrueType.php index 94e5be99d..d581ea232 100644 --- a/app/vendor/sebastian/type/src/type/TrueType.php +++ b/app/vendor/sebastian/type/src/type/TrueType.php @@ -9,6 +9,9 @@ */ namespace SebastianBergmann\Type; +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for this library + */ final class TrueType extends Type { public function isAssignable(Type $other): bool @@ -22,6 +25,9 @@ public function isAssignable(Type $other): bool $other->value() === true; } + /** + * @return 'true' + */ public function name(): string { return 'true'; @@ -32,9 +38,6 @@ public function allowsNull(): bool return false; } - /** - * @psalm-assert-if-true TrueType $this - */ public function isTrue(): bool { return true; diff --git a/app/vendor/sebastian/type/src/type/Type.php b/app/vendor/sebastian/type/src/type/Type.php index e75366839..0dee63f53 100644 --- a/app/vendor/sebastian/type/src/type/Type.php +++ b/app/vendor/sebastian/type/src/type/Type.php @@ -9,15 +9,16 @@ */ namespace SebastianBergmann\Type; -use const PHP_VERSION; -use function get_class; use function gettype; +use function is_object; use function strtolower; -use function version_compare; +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for this library + */ abstract class Type { - public static function fromValue($value, bool $allowsNull): self + public static function fromValue(mixed $value, bool $allowsNull): self { if ($allowsNull === false) { if ($value === true) { @@ -31,8 +32,8 @@ public static function fromValue($value, bool $allowsNull): self $typeName = gettype($value); - if ($typeName === 'object') { - return new ObjectType(TypeName::fromQualifiedName(get_class($value)), $allowsNull); + if (is_object($value)) { + return new ObjectType(TypeName::fromQualifiedName($value::class), $allowsNull); } $type = self::fromName($typeName, $allowsNull); @@ -44,53 +45,26 @@ public static function fromValue($value, bool $allowsNull): self return $type; } + /** + * @param non-empty-string $typeName + */ public static function fromName(string $typeName, bool $allowsNull): self { - if (version_compare(PHP_VERSION, '8.1.0-dev', '>=') && strtolower($typeName) === 'never') { - return new NeverType; - } - - switch (strtolower($typeName)) { - case 'callable': - return new CallableType($allowsNull); - - case 'true': - return new TrueType; - - case 'false': - return new FalseType; - - case 'iterable': - return new IterableType($allowsNull); - - case 'null': - return new NullType; - - case 'object': - return new GenericObjectType($allowsNull); - - case 'unknown type': - return new UnknownType; - - case 'void': - return new VoidType; - - case 'array': - case 'bool': - case 'boolean': - case 'double': - case 'float': - case 'int': - case 'integer': - case 'real': - case 'resource': - case 'resource (closed)': - case 'string': - return new SimpleType($typeName, $allowsNull); - - default: - return new ObjectType(TypeName::fromQualifiedName($typeName), $allowsNull); - } + return match (strtolower($typeName)) { + 'callable' => new CallableType($allowsNull), + 'true' => new TrueType, + 'false' => new FalseType, + 'iterable' => new IterableType($allowsNull), + 'never' => new NeverType, + 'null' => new NullType, + 'object' => new GenericObjectType($allowsNull), + 'unknown type' => new UnknownType, + 'void' => new VoidType, + 'array', 'bool', 'boolean', 'double', 'float', 'int', 'integer', 'real', 'resource', 'resource (closed)', 'string' => new SimpleType($typeName, $allowsNull), + 'mixed' => new MixedType, + /** @phpstan-ignore argument.type */ + default => new ObjectType(TypeName::fromQualifiedName($typeName), $allowsNull), + }; } public function asString(): string @@ -99,7 +73,7 @@ public function asString(): string } /** - * @psalm-assert-if-true CallableType $this + * @phpstan-assert-if-true CallableType $this */ public function isCallable(): bool { @@ -107,7 +81,7 @@ public function isCallable(): bool } /** - * @psalm-assert-if-true TrueType $this + * @phpstan-assert-if-true TrueType $this */ public function isTrue(): bool { @@ -115,7 +89,7 @@ public function isTrue(): bool } /** - * @psalm-assert-if-true FalseType $this + * @phpstan-assert-if-true FalseType $this */ public function isFalse(): bool { @@ -123,7 +97,7 @@ public function isFalse(): bool } /** - * @psalm-assert-if-true GenericObjectType $this + * @phpstan-assert-if-true GenericObjectType $this */ public function isGenericObject(): bool { @@ -131,7 +105,7 @@ public function isGenericObject(): bool } /** - * @psalm-assert-if-true IntersectionType $this + * @phpstan-assert-if-true IntersectionType $this */ public function isIntersection(): bool { @@ -139,7 +113,7 @@ public function isIntersection(): bool } /** - * @psalm-assert-if-true IterableType $this + * @phpstan-assert-if-true IterableType $this */ public function isIterable(): bool { @@ -147,7 +121,7 @@ public function isIterable(): bool } /** - * @psalm-assert-if-true MixedType $this + * @phpstan-assert-if-true MixedType $this */ public function isMixed(): bool { @@ -155,7 +129,7 @@ public function isMixed(): bool } /** - * @psalm-assert-if-true NeverType $this + * @phpstan-assert-if-true NeverType $this */ public function isNever(): bool { @@ -163,7 +137,7 @@ public function isNever(): bool } /** - * @psalm-assert-if-true NullType $this + * @phpstan-assert-if-true NullType $this */ public function isNull(): bool { @@ -171,7 +145,7 @@ public function isNull(): bool } /** - * @psalm-assert-if-true ObjectType $this + * @phpstan-assert-if-true ObjectType $this */ public function isObject(): bool { @@ -179,7 +153,7 @@ public function isObject(): bool } /** - * @psalm-assert-if-true SimpleType $this + * @phpstan-assert-if-true SimpleType $this */ public function isSimple(): bool { @@ -187,7 +161,7 @@ public function isSimple(): bool } /** - * @psalm-assert-if-true StaticType $this + * @phpstan-assert-if-true StaticType $this */ public function isStatic(): bool { @@ -195,7 +169,7 @@ public function isStatic(): bool } /** - * @psalm-assert-if-true UnionType $this + * @phpstan-assert-if-true UnionType $this */ public function isUnion(): bool { @@ -203,7 +177,7 @@ public function isUnion(): bool } /** - * @psalm-assert-if-true UnknownType $this + * @phpstan-assert-if-true UnknownType $this */ public function isUnknown(): bool { @@ -211,7 +185,7 @@ public function isUnknown(): bool } /** - * @psalm-assert-if-true VoidType $this + * @phpstan-assert-if-true VoidType $this */ public function isVoid(): bool { @@ -220,6 +194,9 @@ public function isVoid(): bool abstract public function isAssignable(self $other): bool; + /** + * @return non-empty-string + */ abstract public function name(): string; abstract public function allowsNull(): bool; diff --git a/app/vendor/sebastian/type/src/type/UnionType.php b/app/vendor/sebastian/type/src/type/UnionType.php index 427729c5d..82eec56e5 100644 --- a/app/vendor/sebastian/type/src/type/UnionType.php +++ b/app/vendor/sebastian/type/src/type/UnionType.php @@ -9,16 +9,20 @@ */ namespace SebastianBergmann\Type; +use function assert; use function count; use function implode; use function sort; +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for this library + */ final class UnionType extends Type { /** - * @psalm-var non-empty-list + * @var non-empty-list */ - private $types; + private array $types; /** * @throws RuntimeException @@ -28,6 +32,8 @@ public function __construct(Type ...$types) $this->ensureMinimumOfTwoTypes(...$types); $this->ensureOnlyValidTypes(...$types); + assert($types !== []); + $this->types = $types; } @@ -42,11 +48,17 @@ public function isAssignable(Type $other): bool return false; } + /** + * @return non-empty-string + */ public function asString(): string { return $this->name(); } + /** + * @return non-empty-string + */ public function name(): string { $types = []; @@ -77,9 +89,6 @@ public function allowsNull(): bool return false; } - /** - * @psalm-assert-if-true UnionType $this - */ public function isUnion(): bool { return true; @@ -97,7 +106,7 @@ public function containsIntersectionTypes(): bool } /** - * @psalm-return non-empty-list + * @return non-empty-list */ public function types(): array { @@ -111,7 +120,7 @@ private function ensureMinimumOfTwoTypes(Type ...$types): void { if (count($types) < 2) { throw new RuntimeException( - 'A union type must be composed of at least two types' + 'A union type must be composed of at least two types', ); } } @@ -124,13 +133,13 @@ private function ensureOnlyValidTypes(Type ...$types): void foreach ($types as $type) { if ($type instanceof UnknownType) { throw new RuntimeException( - 'A union type must not be composed of an unknown type' + 'A union type must not be composed of an unknown type', ); } if ($type instanceof VoidType) { throw new RuntimeException( - 'A union type must not be composed of a void type' + 'A union type must not be composed of a void type', ); } } diff --git a/app/vendor/sebastian/type/src/type/UnknownType.php b/app/vendor/sebastian/type/src/type/UnknownType.php index dc2744077..41f8221d6 100644 --- a/app/vendor/sebastian/type/src/type/UnknownType.php +++ b/app/vendor/sebastian/type/src/type/UnknownType.php @@ -9,6 +9,9 @@ */ namespace SebastianBergmann\Type; +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for this library + */ final class UnknownType extends Type { public function isAssignable(Type $other): bool @@ -16,11 +19,17 @@ public function isAssignable(Type $other): bool return true; } + /** + * @return 'unknown type' + */ public function name(): string { return 'unknown type'; } + /** + * @return '' + */ public function asString(): string { return ''; @@ -31,9 +40,6 @@ public function allowsNull(): bool return true; } - /** - * @psalm-assert-if-true UnknownType $this - */ public function isUnknown(): bool { return true; diff --git a/app/vendor/sebastian/type/src/type/VoidType.php b/app/vendor/sebastian/type/src/type/VoidType.php index f740fe29f..7d98c1330 100644 --- a/app/vendor/sebastian/type/src/type/VoidType.php +++ b/app/vendor/sebastian/type/src/type/VoidType.php @@ -9,6 +9,9 @@ */ namespace SebastianBergmann\Type; +/** + * @no-named-arguments Parameter names are not covered by the backward compatibility promise for this library + */ final class VoidType extends Type { public function isAssignable(Type $other): bool @@ -16,6 +19,9 @@ public function isAssignable(Type $other): bool return $other instanceof self; } + /** + * @return 'void' + */ public function name(): string { return 'void'; @@ -26,9 +32,6 @@ public function allowsNull(): bool return false; } - /** - * @psalm-assert-if-true VoidType $this - */ public function isVoid(): bool { return true; diff --git a/app/vendor/sebastian/version/.gitattributes b/app/vendor/sebastian/version/.gitattributes deleted file mode 100644 index 54b895305..000000000 --- a/app/vendor/sebastian/version/.gitattributes +++ /dev/null @@ -1,4 +0,0 @@ -/.github export-ignore -/.php_cs.dist export-ignore - -*.php diff=php diff --git a/app/vendor/sebastian/version/.gitignore b/app/vendor/sebastian/version/.gitignore deleted file mode 100644 index ff5ec9a0e..000000000 --- a/app/vendor/sebastian/version/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/.php_cs.cache -/.idea diff --git a/app/vendor/sebastian/version/ChangeLog.md b/app/vendor/sebastian/version/ChangeLog.md index 10fd9a1a5..53706bf0f 100644 --- a/app/vendor/sebastian/version/ChangeLog.md +++ b/app/vendor/sebastian/version/ChangeLog.md @@ -2,6 +2,46 @@ All notable changes are documented in this file using the [Keep a CHANGELOG](https://keepachangelog.com/) principles. +## [6.0.0] - 2025-02-07 + +### Removed + +* This component is no longer supported on PHP 8.2 + +## [5.0.2] - 2024-10-09 + +### Changed + +* [#21](https://github.com/sebastianbergmann/version/pull/21): Avoid spawning a shell for Git + +## [5.0.1] - 2024-07-03 + +### Changed + +* This project now uses PHPStan instead of Psalm for static analysis + +## [5.0.0] - 2024-02-02 + +### Removed + +* This component is no longer supported on PHP 8.1 + +## [4.0.1] - 2023-02-07 + +### Fixed + +* [#17](https://github.com/sebastianbergmann/version/pull/17): Release archive contains unnecessary assets + +## [4.0.0] - 2023-02-03 + +### Changed + +* `Version::getVersion()` has been renamed to `Version::asString()` + +### Removed + +* This component is no longer supported on PHP 7.3, PHP 7.4, and PHP 8.0 + ## [3.0.2] - 2020-09-28 ### Changed @@ -20,6 +60,12 @@ All notable changes are documented in this file using the [Keep a CHANGELOG](htt * This component is no longer supported on PHP 7.1 and PHP 7.2 +[6.0.0]: https://github.com/sebastianbergmann/version/compare/5.0...6.0.0 +[5.0.2]: https://github.com/sebastianbergmann/version/compare/5.0.1...5.0.2 +[5.0.1]: https://github.com/sebastianbergmann/version/compare/5.0.0...5.0.1 +[5.0.0]: https://github.com/sebastianbergmann/version/compare/4.0...5.0.0 +[4.0.1]: https://github.com/sebastianbergmann/version/compare/4.0.0...4.0.1 +[4.0.0]: https://github.com/sebastianbergmann/version/compare/3.0.2...4.0.0 [3.0.2]: https://github.com/sebastianbergmann/version/compare/3.0.1...3.0.2 [3.0.1]: https://github.com/sebastianbergmann/version/compare/3.0.0...3.0.1 [3.0.0]: https://github.com/sebastianbergmann/version/compare/2.0.1...3.0.0 diff --git a/app/vendor/sebastian/version/LICENSE b/app/vendor/sebastian/version/LICENSE index aa6bca299..42f4747e3 100644 --- a/app/vendor/sebastian/version/LICENSE +++ b/app/vendor/sebastian/version/LICENSE @@ -1,33 +1,29 @@ -Version +BSD 3-Clause License -Copyright (c) 2013-2020, Sebastian Bergmann . +Copyright (c) 2013-2025, Sebastian Bergmann All rights reserved. Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: +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. +1. 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. +2. 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 Sebastian Bergmann nor the names of his - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. +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/app/vendor/sebastian/version/README.md b/app/vendor/sebastian/version/README.md index 2864c8126..fd8820c53 100644 --- a/app/vendor/sebastian/version/README.md +++ b/app/vendor/sebastian/version/README.md @@ -1,17 +1,22 @@ -# Version +[![Latest Stable Version](https://poser.pugx.org/sebastian/version/v)](https://packagist.org/packages/sebastian/version) -**Version** is a library that helps with managing the version number of Git-hosted PHP projects. +# sebastian/version + +**sebastian/version** is a library that helps with managing the version number of Git-hosted PHP projects. ## Installation You can add this library as a local, per-project dependency to your project using [Composer](https://getcomposer.org/): - composer require sebastian/version +``` +composer require sebastian/version +``` If you only need this library during development, for instance to run your project's test suite, then you should add it as a development-time dependency: - composer require --dev sebastian/version - +``` +composer require --dev sebastian/version +``` ## Usage The constructor of the `SebastianBergmann\Version` class expects two parameters: @@ -19,23 +24,25 @@ The constructor of the `SebastianBergmann\Version` class expects two parameters: * `$release` is the version number of the latest release (`X.Y.Z`, for instance) or the name of the release series (`X.Y`) when no release has been made from that branch / for that release series yet. * `$path` is the path to the directory (or a subdirectory thereof) where the sourcecode of the project can be found. Simply passing `__DIR__` here usually suffices. -Apart from the constructor, the `SebastianBergmann\Version` class has a single public method: `getVersion()`. +Apart from the constructor, the `SebastianBergmann\Version` class has a single public method: `asString()`. Here is a contrived example that shows the basic usage: - getVersion()); - ?> +$version = new Version('1.0.0', __DIR__); - string(18) "3.7.10-17-g00f3408" +var_dump($version->asString()); +``` +``` +string(18) "1.0.0-17-g00f3408" +``` When a new release is prepared, the string that is passed to the constructor as the first argument needs to be updated. -### How SebastianBergmann\Version::getVersion() works +### How SebastianBergmann\Version::asString() works * If `$path` is not (part of) a Git repository and `$release` is in `X.Y.Z` format then `$release` is returned as-is. * If `$path` is not (part of) a Git repository and `$release` is in `X.Y` format then `$release` is returned suffixed with `-dev`. diff --git a/app/vendor/sebastian/version/SECURITY.md b/app/vendor/sebastian/version/SECURITY.md new file mode 100644 index 000000000..d88ff0019 --- /dev/null +++ b/app/vendor/sebastian/version/SECURITY.md @@ -0,0 +1,30 @@ +# Security Policy + +If you believe you have found a security vulnerability in the library that is developed in this repository, please report it to us through coordinated disclosure. + +**Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.** + +Instead, please email `sebastian@phpunit.de`. + +Please include as much of the information listed below as you can to help us better understand and resolve the issue: + +* The type of issue +* Full paths of source file(s) related to the manifestation of the issue +* The location of the affected source code (tag/branch/commit or direct URL) +* Any special configuration required to reproduce the issue +* Step-by-step instructions to reproduce the issue +* Proof-of-concept or exploit code (if possible) +* Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +## Web Context + +The library that is developed in this repository was either extracted from [PHPUnit](https://github.com/sebastianbergmann/phpunit) or developed specifically as a dependency for PHPUnit. + +The library is developed with a focus on development environments and the command-line. No specific testing or hardening with regard to using the library in an HTTP or web context or with untrusted input data is performed. The library might also contain functionality that intentionally exposes internal application data for debugging purposes. + +If the library is used in a web application, the application developer is responsible for filtering inputs or escaping outputs as necessary and for verifying that the used functionality is safe for use within the intended context. + +Vulnerabilities specific to the use outside a development context will be fixed as applicable, provided that the fix does not have an averse effect on the primary use case for development purposes. + diff --git a/app/vendor/sebastian/version/composer.json b/app/vendor/sebastian/version/composer.json index e76dbf412..9ba2b85af 100644 --- a/app/vendor/sebastian/version/composer.json +++ b/app/vendor/sebastian/version/composer.json @@ -11,18 +11,19 @@ } ], "support": { - "issues": "https://github.com/sebastianbergmann/version/issues" + "issues": "https://github.com/sebastianbergmann/version/issues", + "security": "https://github.com/sebastianbergmann/version/security/policy" }, "config": { "platform": { - "php": "7.3.0" + "php": "8.3.0" }, "optimize-autoloader": true, "sort-packages": true }, "prefer-stable": true, "require": { - "php": ">=7.3" + "php": ">=8.3" }, "autoload": { "classmap": [ @@ -31,7 +32,7 @@ }, "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "6.0-dev" } } } diff --git a/app/vendor/sebastian/version/src/Version.php b/app/vendor/sebastian/version/src/Version.php index 53ae7894e..0fd9def7f 100644 --- a/app/vendor/sebastian/version/src/Version.php +++ b/app/vendor/sebastian/version/src/Version.php @@ -1,4 +1,4 @@ -version = $this->generate($release, $path); + } /** - * @var string + * @return non-empty-string */ - private $version; - - public function __construct(string $release, string $path) + public function asString(): string { - $this->release = $release; - $this->path = $path; + return $this->version; } - public function getVersion(): string + /** + * @param non-empty-string $release + * @param non-empty-string $path + * + * @return non-empty-string + */ + private function generate(string $release, string $path): string { - if ($this->version === null) { - if (\substr_count($this->release, '.') + 1 === 3) { - $this->version = $this->release; - } else { - $this->version = $this->release . '-dev'; - } - - $git = $this->getGitInformation($this->path); - - if ($git) { - if (\substr_count($this->release, '.') + 1 === 3) { - $this->version = $git; - } else { - $git = \explode('-', $git); - - $this->version = $this->release . '-' . \end($git); - } - } + if (substr_count($release, '.') + 1 === 3) { + $version = $release; + } else { + $version = $release . '-dev'; } - return $this->version; + $git = $this->getGitInformation($path); + + if (!$git) { + return $version; + } + + if (substr_count($release, '.') + 1 === 3) { + return $git; + } + + $git = explode('-', $git); + + return $release . '-' . end($git); } /** - * @return bool|string + * @param non-empty-string $path */ - private function getGitInformation(string $path) + private function getGitInformation(string $path): false|string { - if (!\is_dir($path . DIRECTORY_SEPARATOR . '.git')) { + if (!is_dir($path . DIRECTORY_SEPARATOR . '.git')) { return false; } - $process = \proc_open( - 'git describe --tags', + $process = @proc_open( + ['git', 'describe', '--tags'], [ 1 => ['pipe', 'w'], 2 => ['pipe', 'w'], ], $pipes, - $path + $path, ); - if (!\is_resource($process)) { + if (!is_resource($process)) { return false; } - $result = \trim(\stream_get_contents($pipes[1])); + assert(is_array($pipes)); + assert(isset($pipes[1]) && is_resource($pipes[1])); + assert(isset($pipes[2]) && is_resource($pipes[2])); + + $result = trim((string) stream_get_contents($pipes[1])); - \fclose($pipes[1]); - \fclose($pipes[2]); + fclose($pipes[1]); + fclose($pipes[2]); - $returnCode = \proc_close($process); + $returnCode = proc_close($process); if ($returnCode !== 0) { return false; diff --git a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Helpers/AnnotationHelper.php b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Helpers/AnnotationHelper.php index 78973122e..ebce8e72b 100644 --- a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Helpers/AnnotationHelper.php +++ b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Helpers/AnnotationHelper.php @@ -88,66 +88,62 @@ public static function getAnnotationNodesByType(Node $node, string $type): array static $visitor; static $traverser; - if ($visitor === null) { - $visitor = new class extends AbstractNodeVisitor { - - /** @var class-string */ - private string $type; - - /** @var list */ - private array $nodes = []; - - /** @var list */ - private array $nodesToIgnore = []; - - /** - * @return Node|list|NodeTraverser::*|null - */ - public function enterNode(Node $node) - { - if ($this->type === IdentifierTypeNode::class) { - if ($node instanceof ArrayShapeItemNode || $node instanceof ObjectShapeItemNode) { - $this->nodesToIgnore[] = $node->keyName; - } elseif ($node instanceof DoctrineArgument) { - $this->nodesToIgnore[] = $node->key; - } - } - - if ($node instanceof $this->type && !in_array($node, $this->nodesToIgnore, true)) { - $this->nodes[] = $node; + $visitor ??= new class extends AbstractNodeVisitor { + + /** @var class-string */ + private string $type; + + /** @var list */ + private array $nodes = []; + + /** @var list */ + private array $nodesToIgnore = []; + + /** + * @return Node|list|NodeTraverser::*|null + */ + public function enterNode(Node $node) + { + if ($this->type === IdentifierTypeNode::class) { + if ($node instanceof ArrayShapeItemNode || $node instanceof ObjectShapeItemNode) { + $this->nodesToIgnore[] = $node->keyName; + } elseif ($node instanceof DoctrineArgument) { + $this->nodesToIgnore[] = $node->key; } - - return null; } - /** - * @param class-string $type - */ - public function setType(string $type): void - { - $this->type = $type; + if ($node instanceof $this->type && !in_array($node, $this->nodesToIgnore, true)) { + $this->nodes[] = $node; } - public function clean(): void - { - $this->nodes = []; - $this->nodesToIgnore = []; - } + return null; + } - /** - * @return list - */ - public function getNodes(): array - { - return $this->nodes; - } + /** + * @param class-string $type + */ + public function setType(string $type): void + { + $this->type = $type; + } - }; - } + public function clean(): void + { + $this->nodes = []; + $this->nodesToIgnore = []; + } - if ($traverser === null) { - $traverser = new NodeTraverser([$visitor]); - } + /** + * @return list + */ + public function getNodes(): array + { + return $this->nodes; + } + + }; + + $traverser ??= new NodeTraverser([$visitor]); $visitor->setType($type); $visitor->clean(); @@ -293,11 +289,10 @@ public static function isAnnotationUseless( return $enableStandaloneNullTrueFalseTypeHints; } - if (in_array( + if (TypeHintHelper::isSimpleUnofficialTypeHints( strtolower($annotationType->name), - ['class-string', 'trait-string', 'callable-string', 'numeric-string', 'non-empty-string', 'non-falsy-string', 'literal-string', 'positive-int', 'negative-int'], - true, - )) { + ) && !in_array($annotationType->name, ['object', 'mixed'], true) + ) { return false; } } @@ -316,38 +311,34 @@ private static function changeAnnotationNode(PhpDocTagNode $tagNode, Node $nodeT static $visitor; static $traverser; - if ($visitor === null) { - $visitor = new class extends AbstractNodeVisitor { + $visitor ??= new class extends AbstractNodeVisitor { - private Node $nodeToChange; + private Node $nodeToChange; - private Node $changedNode; + private Node $changedNode; - public function enterNode(Node $node): ?Node - { - if ($node->getAttribute(Attribute::ORIGINAL_NODE) === $this->nodeToChange) { - return $this->changedNode; - } - - return null; + public function enterNode(Node $node): ?Node + { + if ($node->getAttribute(Attribute::ORIGINAL_NODE) === $this->nodeToChange) { + return $this->changedNode; } - public function setNodeToChange(Node $nodeToChange): void - { - $this->nodeToChange = $nodeToChange; - } + return null; + } - public function setChangedNode(Node $changedNode): void - { - $this->changedNode = $changedNode; - } + public function setNodeToChange(Node $nodeToChange): void + { + $this->nodeToChange = $nodeToChange; + } - }; - } + public function setChangedNode(Node $changedNode): void + { + $this->changedNode = $changedNode; + } - if ($traverser === null) { - $traverser = new NodeTraverser([$visitor]); - } + }; + + $traverser ??= new NodeTraverser([$visitor]); $visitor->setNodeToChange($nodeToChange); $visitor->setChangedNode($changedNode); diff --git a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Helpers/AnnotationTypeHelper.php b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Helpers/AnnotationTypeHelper.php index 43cf1e216..972b47337 100644 --- a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Helpers/AnnotationTypeHelper.php +++ b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Helpers/AnnotationTypeHelper.php @@ -22,6 +22,7 @@ use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode; use function count; use function in_array; +use function preg_match; use function strtolower; /** @@ -303,18 +304,35 @@ public static function getTypeHintFromOneType( return $enableUnionTypeHint || $enableStandaloneNullTrueFalseTypeHints ? 'false' : 'bool'; } - if (in_array(strtolower($typeNode->name), ['positive-int', 'negative-int'], true)) { + if (in_array( + strtolower($typeNode->name), + ['positive-int', 'non-positive-int', 'negative-int', 'non-negative-int', 'literal-int', 'int-mask'], + true, + )) { return 'int'; } if (in_array( strtolower($typeNode->name), - ['class-string', 'trait-string', 'callable-string', 'numeric-string', 'non-empty-string', 'non-falsy-string', 'literal-string'], + ['callable-array', 'callable-string'], true, )) { + return 'callable'; + } + + // See https://psalm.dev/docs/annotating_code/type_syntax/scalar_types/#class-string-interface-string + if (preg_match('~-string$~i', $typeNode->name) === 1) { return 'string'; } + if (in_array( + strtolower($typeNode->name), + ['non-empty-array', 'list', 'non-empty-list'], + true, + )) { + return 'array'; + } + return $typeNode->name; } diff --git a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Helpers/PhpDocParserHelper.php b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Helpers/PhpDocParserHelper.php index 4ac6cf090..e1cb41437 100644 --- a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Helpers/PhpDocParserHelper.php +++ b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Helpers/PhpDocParserHelper.php @@ -22,9 +22,7 @@ public static function getLexer(): Lexer { static $lexer; - if ($lexer === null) { - $lexer = new Lexer(self::getConfig()); - } + $lexer ??= new Lexer(self::getConfig()); return $lexer; } @@ -50,9 +48,7 @@ public static function getPrinter(): Printer { static $printer; - if ($printer === null) { - $printer = new Printer(); - } + $printer ??= new Printer(); return $printer; } @@ -66,9 +62,7 @@ public static function cloneNode(Node $node): Node { static $cloningTraverser; - if ($cloningTraverser === null) { - $cloningTraverser = new NodeTraverser([new CloningVisitor()]); - } + $cloningTraverser ??= new NodeTraverser([new CloningVisitor()]); [$cloneNode] = $cloningTraverser->traverse([$node]); @@ -79,9 +73,7 @@ private static function getConfig(): ParserConfig { static $config; - if ($config === null) { - $config = new ParserConfig(['lines' => true, 'indexes' => true]); - } + $config ??= new ParserConfig(['lines' => true, 'indexes' => true]); return $config; } diff --git a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Helpers/TokenHelper.php b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Helpers/TokenHelper.php index c09892f1e..4df7abd6b 100644 --- a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Helpers/TokenHelper.php +++ b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Helpers/TokenHelper.php @@ -479,7 +479,7 @@ public static function findFirstNonWhitespaceOnPreviousLine(File $phpcsFile, int public static function getContent(File $phpcsFile, int $startPointer, ?int $endPointer = null): string { $tokens = $phpcsFile->getTokens(); - $endPointer = $endPointer ?? self::getLastTokenPointer($phpcsFile); + $endPointer ??= self::getLastTokenPointer($phpcsFile); $content = ''; for ($i = $startPointer; $i <= $endPointer; $i++) { diff --git a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Helpers/TypeHintHelper.php b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Helpers/TypeHintHelper.php index 1f276b0a6..15ce2d380 100644 --- a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Helpers/TypeHintHelper.php +++ b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Helpers/TypeHintHelper.php @@ -13,6 +13,7 @@ use function count; use function implode; use function in_array; +use function preg_match; use function preg_split; use function sort; use function sprintf; @@ -79,7 +80,7 @@ public static function convertLongSimpleTypeHintToShort(string $typeHint): strin public static function isUnofficialUnionTypeHint(string $typeHint): bool { - return in_array($typeHint, ['scalar', 'numeric'], true); + return in_array($typeHint, ['scalar', 'numeric', 'array-key'], true); } public static function isVoidTypeHint(string $typeHint): bool @@ -99,7 +100,8 @@ public static function convertUnofficialUnionTypeHintToOfficialTypeHints(string { $conversion = [ 'scalar' => ['string', 'int', 'float', 'bool'], - 'numeric' => ['int', 'float'], + 'numeric' => ['int', 'float', 'string'], + 'array-key' => ['int', 'string'], ]; return $conversion[$typeHint]; @@ -133,23 +135,21 @@ public static function getSimpleTypeHints(): array { static $simpleTypeHints; - if ($simpleTypeHints === null) { - $simpleTypeHints = [ - 'int', - 'integer', - 'false', - 'float', - 'string', - 'bool', - 'boolean', - 'callable', - 'self', - 'array', - 'iterable', - 'void', - 'never', - ]; - } + $simpleTypeHints ??= [ + 'int', + 'integer', + 'false', + 'float', + 'string', + 'bool', + 'boolean', + 'callable', + 'self', + 'array', + 'iterable', + 'void', + 'never', + ]; return $simpleTypeHints; } @@ -169,35 +169,35 @@ public static function isSimpleUnofficialTypeHints(string $typeHint): bool { static $simpleUnofficialTypeHints; - if ($simpleUnofficialTypeHints === null) { - $simpleUnofficialTypeHints = [ - 'null', - 'mixed', - 'scalar', - 'numeric', - 'true', - 'object', - 'resource', - 'static', - '$this', - 'class-string', - 'trait-string', - 'callable-string', - 'numeric-string', - 'non-empty-string', - 'non-falsy-string', - 'literal-string', - 'array-key', - 'list', - 'empty', - 'positive-int', - 'negative-int', - 'min', - 'max', - ]; - } + // See https://psalm.dev/docs/annotating_code/type_syntax/atomic_types/ + $simpleUnofficialTypeHints ??= [ + 'null', + 'mixed', + 'scalar', + 'numeric', + 'true', + 'object', + 'resource', + 'static', + '$this', + 'array-key', + 'list', + 'non-empty-array', + 'non-empty-list', + 'empty', + 'positive-int', + 'non-positive-int', + 'negative-int', + 'non-negative-int', + 'literal-int', + 'int-mask', + 'min', + 'max', + 'callable-array', + 'callable-string', + ]; - return in_array($typeHint, $simpleUnofficialTypeHints, true); + return in_array($typeHint, $simpleUnofficialTypeHints, true) || preg_match('~-string$~i', $typeHint) === 1; } /** diff --git a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Helpers/VariableHelper.php b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Helpers/VariableHelper.php index 4f9770ea7..1d195e5e5 100644 --- a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Helpers/VariableHelper.php +++ b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Helpers/VariableHelper.php @@ -126,9 +126,7 @@ private static function isUsedInScopeInternal( $firstPointerInScope = $tokens[$scopeOwnerPointer]['scope_opener'] + 1; } - if ($startCheckPointer === null) { - $startCheckPointer = $firstPointerInScope; - } + $startCheckPointer ??= $firstPointerInScope; for ($i = $startCheckPointer; $i <= $scopeCloserPointer; $i++) { if (!ScopeHelper::isInSameScope($phpcsFile, $i, $firstPointerInScope)) { diff --git a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Arrays/DisallowImplicitArrayCreationSniff.php b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Arrays/DisallowImplicitArrayCreationSniff.php index e66eb223c..1cba40a29 100644 --- a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Arrays/DisallowImplicitArrayCreationSniff.php +++ b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Arrays/DisallowImplicitArrayCreationSniff.php @@ -92,9 +92,7 @@ public function process(File $phpcsFile, $bracketOpenerPointer): void break; } - if ($scopeOwnerPointer === null) { - $scopeOwnerPointer = TokenHelper::findPrevious($phpcsFile, T_OPEN_TAG, $variablePointer - 1); - } + $scopeOwnerPointer ??= TokenHelper::findPrevious($phpcsFile, T_OPEN_TAG, $variablePointer - 1); $scopeOpenerPointer = $tokens[$scopeOwnerPointer]['code'] === T_OPEN_TAG ? $scopeOwnerPointer diff --git a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Classes/AbstractPropertyConstantAndEnumCaseSpacing.php b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Classes/AbstractPropertyConstantAndEnumCaseSpacing.php index 07ee541bf..9d163d91e 100644 --- a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Classes/AbstractPropertyConstantAndEnumCaseSpacing.php +++ b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Classes/AbstractPropertyConstantAndEnumCaseSpacing.php @@ -11,7 +11,6 @@ use function assert; use function in_array; use function max; -use function min; use function str_repeat; use const T_ATTRIBUTE; use const T_COMMENT; @@ -111,7 +110,7 @@ public function process(File $phpcsFile, $pointer): int && !$this instanceof EnumCaseSpacingSniff && $tokens[$pointer]['line'] !== $tokens[$endPointer]['line'] ) { - $maxExpectedLines = max($minExpectedLines, min($maxExpectedLines, $this->maxLinesCountBeforeMultiline)); + $maxExpectedLines = max($minExpectedLines, $this->maxLinesCountBeforeMultiline); } if ($linesBetween >= $minExpectedLines && $linesBetween <= $maxExpectedLines) { @@ -140,10 +139,10 @@ public function process(File $phpcsFile, $pointer): int FixerHelper::removeBetween($phpcsFile, $lastPointerOnLine, $firstPointerOnNextLine); $phpcsFile->fixer->endChangeset(); - } else { + } elseif ($linesBetween < $minExpectedLines) { $phpcsFile->fixer->beginChangeset(); - for ($i = 0; $i < $minExpectedLines; $i++) { + for ($i = 0; $i < $minExpectedLines - $linesBetween; $i++) { $phpcsFile->fixer->addNewlineBefore($firstOnLinePointer); } diff --git a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Classes/ClassStructureSniff.php b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Classes/ClassStructureSniff.php index 5140cf641..bd4102be6 100644 --- a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Classes/ClassStructureSniff.php +++ b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Classes/ClassStructureSniff.php @@ -658,9 +658,7 @@ private function findGroupStartPointer(File $phpcsFile, int $memberPointer, ?int { $startPointer = DocCommentHelper::findDocCommentOpenPointer($phpcsFile, $memberPointer - 1); if ($startPointer === null) { - if ($previousMemberEndPointer === null) { - $previousMemberEndPointer = $this->findPreviousMemberEndPointer($phpcsFile, $memberPointer); - } + $previousMemberEndPointer ??= $this->findPreviousMemberEndPointer($phpcsFile, $memberPointer); $startPointer = TokenHelper::findNextEffective($phpcsFile, $previousMemberEndPointer + 1); assert($startPointer !== null); diff --git a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Classes/RequireMultiLineMethodSignatureSniff.php b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Classes/RequireMultiLineMethodSignatureSniff.php index d1f6fe9ea..d152fa293 100644 --- a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Classes/RequireMultiLineMethodSignatureSniff.php +++ b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Classes/RequireMultiLineMethodSignatureSniff.php @@ -26,6 +26,8 @@ class RequireMultiLineMethodSignatureSniff extends AbstractMethodSignature public ?int $minParametersCount = null; + public bool $withPromotedProperties = false; + /** @var list */ public array $includedMethodPatterns = []; @@ -91,12 +93,24 @@ public function process(File $phpcsFile, $methodPointer): void return; } - if ($this->minLineLength !== null && $this->minLineLength !== 0 && strlen($signature) < $this->minLineLength) { - return; + $splitPromotedProperties = false; + if ($this->withPromotedProperties) { + foreach ($parameters as $parameter) { + if (isset($parameter['property_visibility'])) { + $splitPromotedProperties = true; + break; + } + } } - if ($this->minParametersCount !== null && $parametersCount < $this->minParametersCount) { - return; + if (!$splitPromotedProperties) { + if ($this->minLineLength !== null && $this->minLineLength !== 0 && strlen($signature) < $this->minLineLength) { + return; + } + + if ($this->minParametersCount !== null && $parametersCount < $this->minParametersCount) { + return; + } } $error = sprintf('Signature of method "%s" should be split to more lines so each parameter is on its own line.', $methodName); @@ -116,9 +130,7 @@ public function process(File $phpcsFile, $methodPointer): void $parameter['token'] - 1, $tokens[$methodPointer]['parenthesis_opener'], ); - if ($pointerBeforeParameter === null) { - $pointerBeforeParameter = $tokens[$methodPointer]['parenthesis_opener']; - } + $pointerBeforeParameter ??= $tokens[$methodPointer]['parenthesis_opener']; FixerHelper::add( $phpcsFile, @@ -157,9 +169,7 @@ private function isMethodNameInPatterns(string $methodName, array $normalizedPat */ private function getIncludedMethodNormalizedPatterns(): array { - if ($this->includedMethodNormalizedPatterns === null) { - $this->includedMethodNormalizedPatterns = SniffSettingsHelper::normalizeArray($this->includedMethodPatterns); - } + $this->includedMethodNormalizedPatterns ??= SniffSettingsHelper::normalizeArray($this->includedMethodPatterns); return $this->includedMethodNormalizedPatterns; } @@ -168,9 +178,7 @@ private function getIncludedMethodNormalizedPatterns(): array */ private function getExcludedMethodNormalizedPatterns(): array { - if ($this->excludedMethodNormalizedPatterns === null) { - $this->excludedMethodNormalizedPatterns = SniffSettingsHelper::normalizeArray($this->excludedMethodPatterns); - } + $this->excludedMethodNormalizedPatterns ??= SniffSettingsHelper::normalizeArray($this->excludedMethodPatterns); return $this->excludedMethodNormalizedPatterns; } diff --git a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Classes/RequireSingleLineMethodSignatureSniff.php b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Classes/RequireSingleLineMethodSignatureSniff.php index 40b0161be..84f32306c 100644 --- a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Classes/RequireSingleLineMethodSignatureSniff.php +++ b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Classes/RequireSingleLineMethodSignatureSniff.php @@ -108,9 +108,7 @@ private function isMethodNameInPatterns(string $methodName, array $normalizedPat */ private function getIncludedMethodNormalizedPatterns(): array { - if ($this->includedMethodNormalizedPatterns === null) { - $this->includedMethodNormalizedPatterns = SniffSettingsHelper::normalizeArray($this->includedMethodPatterns); - } + $this->includedMethodNormalizedPatterns ??= SniffSettingsHelper::normalizeArray($this->includedMethodPatterns); return $this->includedMethodNormalizedPatterns; } @@ -119,9 +117,7 @@ private function getIncludedMethodNormalizedPatterns(): array */ private function getExcludedMethodNormalizedPatterns(): array { - if ($this->excludedMethodNormalizedPatterns === null) { - $this->excludedMethodNormalizedPatterns = SniffSettingsHelper::normalizeArray($this->excludedMethodPatterns); - } + $this->excludedMethodNormalizedPatterns ??= SniffSettingsHelper::normalizeArray($this->excludedMethodPatterns); return $this->excludedMethodNormalizedPatterns; } diff --git a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Commenting/DocCommentSpacingSniff.php b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Commenting/DocCommentSpacingSniff.php index 3cd2ff630..2e790041f 100644 --- a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Commenting/DocCommentSpacingSniff.php +++ b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Commenting/DocCommentSpacingSniff.php @@ -603,9 +603,7 @@ private function checkAnnotationsGroupsOrder( $docCommentCloserPointer, ); - if ($docCommentContentEndPointer === null) { - $docCommentContentEndPointer = $lastAnnotation->getEndPointer(); - } + $docCommentContentEndPointer ??= $lastAnnotation->getEndPointer(); $phpcsFile->fixer->beginChangeset(); diff --git a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Commenting/ForbiddenAnnotationsSniff.php b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Commenting/ForbiddenAnnotationsSniff.php index b97261b04..8e3328d7e 100644 --- a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Commenting/ForbiddenAnnotationsSniff.php +++ b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Commenting/ForbiddenAnnotationsSniff.php @@ -123,9 +123,7 @@ public function process(File $phpcsFile, $docCommentOpenPointer): void */ private function getNormalizedForbiddenAnnotations(): array { - if ($this->normalizedForbiddenAnnotations === null) { - $this->normalizedForbiddenAnnotations = SniffSettingsHelper::normalizeArray($this->forbiddenAnnotations); - } + $this->normalizedForbiddenAnnotations ??= SniffSettingsHelper::normalizeArray($this->forbiddenAnnotations); return $this->normalizedForbiddenAnnotations; } diff --git a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Commenting/UselessFunctionDocCommentSniff.php b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Commenting/UselessFunctionDocCommentSniff.php index 180563a78..5fe0d32e2 100644 --- a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Commenting/UselessFunctionDocCommentSniff.php +++ b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Commenting/UselessFunctionDocCommentSniff.php @@ -130,14 +130,12 @@ public function process(File $phpcsFile, $functionPointer): void */ private function getTraversableTypeHints(): array { - if ($this->normalizedTraversableTypeHints === null) { - $this->normalizedTraversableTypeHints = array_map( - static fn (string $typeHint): string => NamespaceHelper::isFullyQualifiedName($typeHint) - ? $typeHint - : sprintf('%s%s', NamespaceHelper::NAMESPACE_SEPARATOR, $typeHint), - SniffSettingsHelper::normalizeArray($this->traversableTypeHints), - ); - } + $this->normalizedTraversableTypeHints ??= array_map( + static fn (string $typeHint): string => NamespaceHelper::isFullyQualifiedName($typeHint) + ? $typeHint + : sprintf('%s%s', NamespaceHelper::NAMESPACE_SEPARATOR, $typeHint), + SniffSettingsHelper::normalizeArray($this->traversableTypeHints), + ); return $this->normalizedTraversableTypeHints; } diff --git a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Exceptions/RequireNonCapturingCatchSniff.php b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Exceptions/RequireNonCapturingCatchSniff.php index 15bc1a247..37344d883 100644 --- a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Exceptions/RequireNonCapturingCatchSniff.php +++ b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Exceptions/RequireNonCapturingCatchSniff.php @@ -115,9 +115,7 @@ public function process(File $phpcsFile, $catchPointer): void $variablePointer + 1, $tokens[$catchPointer]['parenthesis_closer'], ); - if ($fixEndPointer === null) { - $fixEndPointer = $tokens[$catchPointer]['parenthesis_closer']; - } + $fixEndPointer ??= $tokens[$catchPointer]['parenthesis_closer']; $phpcsFile->fixer->beginChangeset(); diff --git a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Files/TypeNameMatchesFileNameSniff.php b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Files/TypeNameMatchesFileNameSniff.php index a4bf6ac95..14f6ccae5 100644 --- a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Files/TypeNameMatchesFileNameSniff.php +++ b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Files/TypeNameMatchesFileNameSniff.php @@ -140,9 +140,7 @@ private function getRootNamespaces(): array */ private function getSkipDirs(): array { - if ($this->normalizedSkipDirs === null) { - $this->normalizedSkipDirs = SniffSettingsHelper::normalizeArray($this->skipDirs); - } + $this->normalizedSkipDirs ??= SniffSettingsHelper::normalizeArray($this->skipDirs); return $this->normalizedSkipDirs; } @@ -152,9 +150,7 @@ private function getSkipDirs(): array */ private function getIgnoredNamespaces(): array { - if ($this->normalizedIgnoredNamespaces === null) { - $this->normalizedIgnoredNamespaces = SniffSettingsHelper::normalizeArray($this->ignoredNamespaces); - } + $this->normalizedIgnoredNamespaces ??= SniffSettingsHelper::normalizeArray($this->ignoredNamespaces); return $this->normalizedIgnoredNamespaces; } @@ -164,22 +160,18 @@ private function getIgnoredNamespaces(): array */ private function getExtensions(): array { - if ($this->normalizedExtensions === null) { - $this->normalizedExtensions = SniffSettingsHelper::normalizeArray($this->extensions); - } + $this->normalizedExtensions ??= SniffSettingsHelper::normalizeArray($this->extensions); return $this->normalizedExtensions; } private function getNamespaceExtractor(): FilepathNamespaceExtractor { - if ($this->namespaceExtractor === null) { - $this->namespaceExtractor = new FilepathNamespaceExtractor( - $this->getRootNamespaces(), - $this->getSkipDirs(), - $this->getExtensions(), - ); - } + $this->namespaceExtractor ??= new FilepathNamespaceExtractor( + $this->getRootNamespaces(), + $this->getSkipDirs(), + $this->getExtensions(), + ); return $this->namespaceExtractor; } diff --git a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Namespaces/AbstractFullyQualifiedGlobalReference.php b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Namespaces/AbstractFullyQualifiedGlobalReference.php index 4c7f1d3e1..7f9d26631 100644 --- a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Namespaces/AbstractFullyQualifiedGlobalReference.php +++ b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Namespaces/AbstractFullyQualifiedGlobalReference.php @@ -136,9 +136,7 @@ public function process(File $phpcsFile, $openTagPointer): void */ protected function getNormalizedInclude(): array { - if ($this->normalizedInclude === null) { - $this->normalizedInclude = $this->normalizeNames($this->include); - } + $this->normalizedInclude ??= $this->normalizeNames($this->include); return $this->normalizedInclude; } @@ -147,9 +145,7 @@ protected function getNormalizedInclude(): array */ private function getNormalizedExclude(): array { - if ($this->normalizedExclude === null) { - $this->normalizedExclude = $this->normalizeNames($this->exclude); - } + $this->normalizedExclude ??= $this->normalizeNames($this->exclude); return $this->normalizedExclude; } diff --git a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Namespaces/FullyQualifiedExceptionsSniff.php b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Namespaces/FullyQualifiedExceptionsSniff.php index a959b5833..b8d197a9e 100644 --- a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Namespaces/FullyQualifiedExceptionsSniff.php +++ b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Namespaces/FullyQualifiedExceptionsSniff.php @@ -140,9 +140,7 @@ public function process(File $phpcsFile, $openTagPointer): void */ private function getSpecialExceptionNames(): array { - if ($this->normalizedSpecialExceptionNames === null) { - $this->normalizedSpecialExceptionNames = SniffSettingsHelper::normalizeArray($this->specialExceptionNames); - } + $this->normalizedSpecialExceptionNames ??= SniffSettingsHelper::normalizeArray($this->specialExceptionNames); return $this->normalizedSpecialExceptionNames; } @@ -152,9 +150,7 @@ private function getSpecialExceptionNames(): array */ private function getIgnoredNames(): array { - if ($this->normalizedIgnoredNames === null) { - $this->normalizedIgnoredNames = SniffSettingsHelper::normalizeArray($this->ignoredNames); - } + $this->normalizedIgnoredNames ??= SniffSettingsHelper::normalizeArray($this->ignoredNames); return $this->normalizedIgnoredNames; } diff --git a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Namespaces/ReferenceUsedNamesOnlySniff.php b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Namespaces/ReferenceUsedNamesOnlySniff.php index 91d7a3895..7467fd8fc 100644 --- a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Namespaces/ReferenceUsedNamesOnlySniff.php +++ b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Namespaces/ReferenceUsedNamesOnlySniff.php @@ -568,9 +568,7 @@ static function (bool $carry, string $use) use ($canonicalName): bool { */ private function getSpecialExceptionNames(): array { - if ($this->normalizedSpecialExceptionNames === null) { - $this->normalizedSpecialExceptionNames = SniffSettingsHelper::normalizeArray($this->specialExceptionNames); - } + $this->normalizedSpecialExceptionNames ??= SniffSettingsHelper::normalizeArray($this->specialExceptionNames); return $this->normalizedSpecialExceptionNames; } @@ -580,9 +578,7 @@ private function getSpecialExceptionNames(): array */ private function getIgnoredNames(): array { - if ($this->normalizedIgnoredNames === null) { - $this->normalizedIgnoredNames = SniffSettingsHelper::normalizeArray($this->ignoredNames); - } + $this->normalizedIgnoredNames ??= SniffSettingsHelper::normalizeArray($this->ignoredNames); return $this->normalizedIgnoredNames; } @@ -592,9 +588,7 @@ private function getIgnoredNames(): array */ private function getNamespacesRequiredToUse(): array { - if ($this->normalizedNamespacesRequiredToUse === null) { - $this->normalizedNamespacesRequiredToUse = SniffSettingsHelper::normalizeArray($this->namespacesRequiredToUse); - } + $this->normalizedNamespacesRequiredToUse ??= SniffSettingsHelper::normalizeArray($this->namespacesRequiredToUse); return $this->normalizedNamespacesRequiredToUse; } diff --git a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Namespaces/UnusedUsesSniff.php b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Namespaces/UnusedUsesSniff.php index fbc937569..5671ccdf5 100644 --- a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Namespaces/UnusedUsesSniff.php +++ b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Namespaces/UnusedUsesSniff.php @@ -247,17 +247,15 @@ public function process(File $phpcsFile, $openTagPointer): void */ private function getIgnoredAnnotationNames(): array { - if ($this->normalizedIgnoredAnnotationNames === null) { - $this->normalizedIgnoredAnnotationNames = array_merge( - SniffSettingsHelper::normalizeArray($this->ignoredAnnotationNames), - [ - '@param', - '@throws', - '@property', - '@method', - ], - ); - } + $this->normalizedIgnoredAnnotationNames ??= array_merge( + SniffSettingsHelper::normalizeArray($this->ignoredAnnotationNames), + [ + '@param', + '@throws', + '@property', + '@method', + ], + ); return $this->normalizedIgnoredAnnotationNames; } @@ -267,9 +265,7 @@ private function getIgnoredAnnotationNames(): array */ private function getIgnoredAnnotations(): array { - if ($this->normalizedIgnoredAnnotations === null) { - $this->normalizedIgnoredAnnotations = SniffSettingsHelper::normalizeArray($this->ignoredAnnotations); - } + $this->normalizedIgnoredAnnotations ??= SniffSettingsHelper::normalizeArray($this->ignoredAnnotations); return $this->normalizedIgnoredAnnotations; } diff --git a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Namespaces/UseFromSameNamespaceSniff.php b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Namespaces/UseFromSameNamespaceSniff.php index 90519f869..461e9f346 100644 --- a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Namespaces/UseFromSameNamespaceSniff.php +++ b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Namespaces/UseFromSameNamespaceSniff.php @@ -42,9 +42,7 @@ public function process(File $phpcsFile, $usePointer): void } $namespaceName = NamespaceHelper::findCurrentNamespaceName($phpcsFile, $usePointer); - if ($namespaceName === null) { - $namespaceName = ''; - } + $namespaceName ??= ''; $usedTypeName = UseStatementHelper::getFullyQualifiedTypeNameFromUse($phpcsFile, $usePointer); if (!StringHelper::startsWith($usedTypeName, $namespaceName)) { diff --git a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Namespaces/UseOnlyWhitelistedNamespacesSniff.php b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Namespaces/UseOnlyWhitelistedNamespacesSniff.php index 97161c72e..25423fb85 100644 --- a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Namespaces/UseOnlyWhitelistedNamespacesSniff.php +++ b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/Namespaces/UseOnlyWhitelistedNamespacesSniff.php @@ -67,9 +67,7 @@ public function process(File $phpcsFile, $usePointer): void */ private function getNamespacesRequiredToUse(): array { - if ($this->normalizedNamespacesRequiredToUse === null) { - $this->normalizedNamespacesRequiredToUse = SniffSettingsHelper::normalizeArray($this->namespacesRequiredToUse); - } + $this->normalizedNamespacesRequiredToUse ??= SniffSettingsHelper::normalizeArray($this->namespacesRequiredToUse); return $this->normalizedNamespacesRequiredToUse; } diff --git a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/PHP/OptimizedFunctionsWithoutUnpackingSniff.php b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/PHP/OptimizedFunctionsWithoutUnpackingSniff.php index f6232614d..367b34912 100644 --- a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/PHP/OptimizedFunctionsWithoutUnpackingSniff.php +++ b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/PHP/OptimizedFunctionsWithoutUnpackingSniff.php @@ -84,9 +84,7 @@ public function process(File $phpcsFile, $pointer): void && $tokens[$lastArgumentSeparatorPointer]['level'] !== $tokens[$openBracketPointer]['level'] ); - if ($lastArgumentSeparatorPointer === null) { - $lastArgumentSeparatorPointer = $openBracketPointer; - } + $lastArgumentSeparatorPointer ??= $openBracketPointer; /** @var int $nextTokenAfterSeparatorPointer */ $nextTokenAfterSeparatorPointer = TokenHelper::findNextEffective( diff --git a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/PHP/RequireExplicitAssertionSniff.php b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/PHP/RequireExplicitAssertionSniff.php index bbff40f48..a07e23f58 100644 --- a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/PHP/RequireExplicitAssertionSniff.php +++ b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/PHP/RequireExplicitAssertionSniff.php @@ -27,6 +27,7 @@ use function count; use function implode; use function in_array; +use function preg_match; use function sprintf; use function strpos; use function trim; @@ -417,8 +418,7 @@ private function createConditions(string $variableName, TypeNode $typeNode): arr if ($typeNode->name === 'numeric') { return [ - sprintf('\is_int(%s)', $variableName), - sprintf('\is_float(%s)', $variableName), + sprintf('\is_numeric(%s)', $variableName), ]; } @@ -432,29 +432,33 @@ private function createConditions(string $variableName, TypeNode $typeNode): arr } if ($this->enableIntegerRanges) { - if ($typeNode->name === 'positive-int') { + if ($typeNode->name === 'positive-int' || $typeNode->name === 'non-negative-int') { return [sprintf('\is_int(%1$s) && %1$s > 0', $variableName)]; } - if ($typeNode->name === 'negative-int') { + if ($typeNode->name === 'negative-int' || $typeNode->name === 'non-positive-int') { return [sprintf('\is_int(%1$s) && %1$s < 0', $variableName)]; } + + if ($typeNode->name === 'literal-int') { + return [sprintf('\is_int(%1$s)', $variableName)]; + } } if ( $this->enableAdvancedStringTypes - && in_array($typeNode->name, ['non-empty-string', 'non-falsy-string', 'callable-string', 'numeric-string'], true) + && preg_match('~-string$~', $typeNode->name) === 1 ) { $conditions = [sprintf('\is_string(%s)', $variableName)]; - if ($typeNode->name === 'non-empty-string') { - $conditions[] = sprintf("%s !== ''", $variableName); - } elseif ($typeNode->name === 'non-falsy-string') { - $conditions[] = sprintf('(bool) %s === true', $variableName); - } elseif ($typeNode->name === 'callable-string') { + if ($typeNode->name === 'callable-string') { $conditions[] = sprintf('\is_callable(%s)', $variableName); - } else { + } elseif ($typeNode->name === 'numeric-string') { $conditions[] = sprintf('\is_numeric(%s)', $variableName); + } elseif (preg_match('~^non-empty-~i', $typeNode->name) === 1) { + $conditions[] = sprintf("%s !== ''", $variableName); + } elseif (preg_match('~^non-falsy-~i', $typeNode->name) === 1) { + $conditions[] = sprintf('(bool) %s === true', $variableName); } return [implode(' && ', $conditions)]; diff --git a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/TestCase.php b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/TestCase.php index abcb81e2e..d96c3372a 100644 --- a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/TestCase.php +++ b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/TestCase.php @@ -84,13 +84,35 @@ protected static function checkFile(string $filePath, array $sniffProperties = [ protected static function assertNoSniffErrorInFile(File $phpcsFile): void { $errors = $phpcsFile->getErrors(); - self::assertEmpty($errors, sprintf('No errors expected, but %d errors found.', count($errors))); + $text = sprintf('No errors expected, but %d errors found:', count($errors)); + foreach ($errors as $line => $error) { + $text .= sprintf( + '%sLine %d:%s%s', + PHP_EOL, + $line, + PHP_EOL, + self::getFormattedErrors($error), + ); + } + + self::assertEmpty($errors, $text); } protected static function assertNoSniffWarningInFile(File $phpcsFile): void { $warnings = $phpcsFile->getWarnings(); - self::assertEmpty($warnings, sprintf('No warnings expected, but %d warnings found.', count($warnings))); + $text = sprintf('No warnings expected, but %d warnings found:', count($warnings)); + foreach ($warnings as $line => $warning) { + $text .= sprintf( + '%sLine %d:%s%s', + PHP_EOL, + $line, + PHP_EOL, + self::getFormattedErrors($warning), + ); + } + + self::assertEmpty($warnings, $text); } protected static function assertSniffError(File $phpcsFile, int $line, string $code, ?string $message = null): void diff --git a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/TypeHints/DisallowArrayTypeHintSyntaxSniff.php b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/TypeHints/DisallowArrayTypeHintSyntaxSniff.php index 7a06b5e43..316c11cf9 100644 --- a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/TypeHints/DisallowArrayTypeHintSyntaxSniff.php +++ b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/TypeHints/DisallowArrayTypeHintSyntaxSniff.php @@ -152,47 +152,43 @@ public function getArrayTypeNodes(Node $node): array static $visitor; static $traverser; - if ($visitor === null) { - $visitor = new class extends AbstractNodeVisitor { - - /** @var list */ - private array $nodes = []; - - /** - * @return NodeTraverser::DONT_TRAVERSE_CHILDREN|null - */ - public function enterNode(Node $node) - { - if ($node instanceof ArrayTypeNode) { - $this->nodes[] = $node; - - if ($node->type instanceof ArrayTypeNode) { - return NodeTraverser::DONT_TRAVERSE_CHILDREN; - } - } + $visitor ??= new class extends AbstractNodeVisitor { - return null; - } + /** @var list */ + private array $nodes = []; - public function cleanNodes(): void - { - $this->nodes = []; - } + /** + * @return NodeTraverser::DONT_TRAVERSE_CHILDREN|null + */ + public function enterNode(Node $node) + { + if ($node instanceof ArrayTypeNode) { + $this->nodes[] = $node; - /** - * @return list - */ - public function getNodes(): array - { - return $this->nodes; + if ($node->type instanceof ArrayTypeNode) { + return NodeTraverser::DONT_TRAVERSE_CHILDREN; + } } - }; - } + return null; + } - if ($traverser === null) { - $traverser = new NodeTraverser([$visitor]); - } + public function cleanNodes(): void + { + $this->nodes = []; + } + + /** + * @return list + */ + public function getNodes(): array + { + return $this->nodes; + } + + }; + + $traverser ??= new NodeTraverser([$visitor]); $visitor->cleanNodes(); @@ -293,15 +289,13 @@ private function isTraversableType(string $type): bool */ private function getNormalizedTraversableTypeHints(): array { - if ($this->normalizedTraversableTypeHints === null) { - $this->normalizedTraversableTypeHints = array_flip( - array_map(static fn (string $typeHint): string => NamespaceHelper::isFullyQualifiedName($typeHint) - ? $typeHint - : sprintf('%s%s', NamespaceHelper::NAMESPACE_SEPARATOR, $typeHint), SniffSettingsHelper::normalizeArray( - $this->traversableTypeHints, - )), - ); - } + $this->normalizedTraversableTypeHints ??= array_flip( + array_map(static fn (string $typeHint): string => NamespaceHelper::isFullyQualifiedName($typeHint) + ? $typeHint + : sprintf('%s%s', NamespaceHelper::NAMESPACE_SEPARATOR, $typeHint), SniffSettingsHelper::normalizeArray( + $this->traversableTypeHints, + )), + ); return $this->normalizedTraversableTypeHints; } diff --git a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/TypeHints/ParameterTypeHintSniff.php b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/TypeHints/ParameterTypeHintSniff.php index 504afb53d..4e363381b 100644 --- a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/TypeHints/ParameterTypeHintSniff.php +++ b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/TypeHints/ParameterTypeHintSniff.php @@ -674,14 +674,12 @@ private function getSniffName(string $sniffName): string */ private function getTraversableTypeHints(): array { - if ($this->normalizedTraversableTypeHints === null) { - $this->normalizedTraversableTypeHints = array_map( - static fn (string $typeHint): string => NamespaceHelper::isFullyQualifiedName($typeHint) - ? $typeHint - : sprintf('%s%s', NamespaceHelper::NAMESPACE_SEPARATOR, $typeHint), - SniffSettingsHelper::normalizeArray($this->traversableTypeHints), - ); - } + $this->normalizedTraversableTypeHints ??= array_map( + static fn (string $typeHint): string => NamespaceHelper::isFullyQualifiedName($typeHint) + ? $typeHint + : sprintf('%s%s', NamespaceHelper::NAMESPACE_SEPARATOR, $typeHint), + SniffSettingsHelper::normalizeArray($this->traversableTypeHints), + ); return $this->normalizedTraversableTypeHints; } diff --git a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/TypeHints/ParameterTypeHintSpacingSniff.php b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/TypeHints/ParameterTypeHintSpacingSniff.php index a1901b0fa..2eb6688aa 100644 --- a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/TypeHints/ParameterTypeHintSpacingSniff.php +++ b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/TypeHints/ParameterTypeHintSpacingSniff.php @@ -54,14 +54,10 @@ public function process(File $phpcsFile, $functionPointer): void $parameterName = $tokens[$parameterPointer]['content']; $parameterStartPointer = TokenHelper::findPrevious($phpcsFile, T_COMMA, $parameterPointer - 1, $parametersStartPointer); - if ($parameterStartPointer === null) { - $parameterStartPointer = $parametersStartPointer; - } + $parameterStartPointer ??= $parametersStartPointer; $parameterEndPointer = TokenHelper::findNext($phpcsFile, T_COMMA, $parameterPointer + 1, $parametersEndPointer + 1); - if ($parameterEndPointer === null) { - $parameterEndPointer = $parametersEndPointer; - } + $parameterEndPointer ??= $parametersEndPointer; $attributeCloserPointer = TokenHelper::findPrevious($phpcsFile, T_ATTRIBUTE_END, $parameterPointer - 1, $parameterStartPointer); diff --git a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/TypeHints/PropertyTypeHintSniff.php b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/TypeHints/PropertyTypeHintSniff.php index bdf33fa5e..9373cf731 100644 --- a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/TypeHints/PropertyTypeHintSniff.php +++ b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/TypeHints/PropertyTypeHintSniff.php @@ -604,14 +604,12 @@ private function getSniffName(string $sniffName): string */ private function getTraversableTypeHints(): array { - if ($this->normalizedTraversableTypeHints === null) { - $this->normalizedTraversableTypeHints = array_map( - static fn (string $typeHint): string => NamespaceHelper::isFullyQualifiedName($typeHint) - ? $typeHint - : sprintf('%s%s', NamespaceHelper::NAMESPACE_SEPARATOR, $typeHint), - SniffSettingsHelper::normalizeArray($this->traversableTypeHints), - ); - } + $this->normalizedTraversableTypeHints ??= array_map( + static fn (string $typeHint): string => NamespaceHelper::isFullyQualifiedName($typeHint) + ? $typeHint + : sprintf('%s%s', NamespaceHelper::NAMESPACE_SEPARATOR, $typeHint), + SniffSettingsHelper::normalizeArray($this->traversableTypeHints), + ); return $this->normalizedTraversableTypeHints; } diff --git a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/TypeHints/ReturnTypeHintSniff.php b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/TypeHints/ReturnTypeHintSniff.php index bde658758..3973e2b33 100644 --- a/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/TypeHints/ReturnTypeHintSniff.php +++ b/app/vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/TypeHints/ReturnTypeHintSniff.php @@ -406,9 +406,12 @@ private function checkFunctionTypeHint( return; } - $typeHintsWithConvertedUnion[$typeHintNo] = TypeHintHelper::isVoidTypeHint($typeHint) - ? 'null' - : TypeHintHelper::convertLongSimpleTypeHintToShort($typeHint); + if (TypeHintHelper::isVoidTypeHint($typeHint) || TypeHintHelper::isNeverTypeHint($typeHint)) { + $this->reportUselessSuppress($phpcsFile, $functionPointer, $isSuppressedNativeTypeHint, $suppressNameNativeTypeHint); + return; + } + + $typeHintsWithConvertedUnion[$typeHintNo] = TypeHintHelper::convertLongSimpleTypeHintToShort($typeHint); } if ($originalReturnTypeNode instanceof NullableTypeNode) { @@ -711,14 +714,12 @@ private function getSniffName(string $sniffName): string */ private function getTraversableTypeHints(): array { - if ($this->normalizedTraversableTypeHints === null) { - $this->normalizedTraversableTypeHints = array_map( - static fn (string $typeHint): string => NamespaceHelper::isFullyQualifiedName($typeHint) - ? $typeHint - : sprintf('%s%s', NamespaceHelper::NAMESPACE_SEPARATOR, $typeHint), - SniffSettingsHelper::normalizeArray($this->traversableTypeHints), - ); - } + $this->normalizedTraversableTypeHints ??= array_map( + static fn (string $typeHint): string => NamespaceHelper::isFullyQualifiedName($typeHint) + ? $typeHint + : sprintf('%s%s', NamespaceHelper::NAMESPACE_SEPARATOR, $typeHint), + SniffSettingsHelper::normalizeArray($this->traversableTypeHints), + ); return $this->normalizedTraversableTypeHints; } diff --git a/app/vendor/slevomat/coding-standard/composer.json b/app/vendor/slevomat/coding-standard/composer.json index 477075737..b36a174a3 100644 --- a/app/vendor/slevomat/coding-standard/composer.json +++ b/app/vendor/slevomat/coding-standard/composer.json @@ -15,17 +15,17 @@ "require": { "php": "^7.4 || ^8.0", "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7 || ^1.1.2", - "phpstan/phpdoc-parser": "^2.2.0", + "phpstan/phpdoc-parser": "^2.3.0", "squizlabs/php_codesniffer": "^3.13.2" }, "require-dev": { "phing/phing": "3.0.1|3.1.0", "php-parallel-lint/php-parallel-lint": "1.4.0", - "phpstan/phpstan": "2.1.19", + "phpstan/phpstan": "2.1.22", "phpstan/phpstan-deprecation-rules": "2.0.3", "phpstan/phpstan-phpunit": "2.0.7", "phpstan/phpstan-strict-rules": "2.0.6", - "phpunit/phpunit": "9.6.8|10.5.48|11.4.4|11.5.27|12.2.7" + "phpunit/phpunit": "9.6.8|10.5.48|11.4.4|11.5.27|12.3.7" }, "autoload": { "psr-4": { diff --git a/app/vendor/slevomat/coding-standard/doc/classes.md b/app/vendor/slevomat/coding-standard/doc/classes.md index 71cc19fd0..5f64a4ff1 100644 --- a/app/vendor/slevomat/coding-standard/doc/classes.md +++ b/app/vendor/slevomat/coding-standard/doc/classes.md @@ -246,6 +246,8 @@ Sniff provides the following settings: * `excludedMethodPatterns`: allows to configure which methods are excluded from sniff detection. This is an array of regular expressions (PCRE) with delimiters. You should not use this with `includedMethodPatterns`, as it will not work properly. +* `withPromotedProperties`: always require multiline signatures for methods with promoted properties. + #### SlevomatCodingStandard.Classes.RequireSelfReference 🔧 Requires `self` for local reference. diff --git a/app/vendor/staabm/side-effects-detector/LICENSE b/app/vendor/staabm/side-effects-detector/LICENSE new file mode 100644 index 000000000..91188b682 --- /dev/null +++ b/app/vendor/staabm/side-effects-detector/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Markus Staab + +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/app/vendor/staabm/side-effects-detector/README.md b/app/vendor/staabm/side-effects-detector/README.md new file mode 100644 index 000000000..5ef8ffeaf --- /dev/null +++ b/app/vendor/staabm/side-effects-detector/README.md @@ -0,0 +1,57 @@ +Analyzes php-code for side-effects. + +When code has no side-effects it can e.g. be used with `eval($code)` in the same process without interfering. +[Side-effects are classified](https://github.com/staabm/side-effects-detector/blob/main/lib/SideEffect.php) into categories to filter them more easily depending on your use-case. + +## Install + +`composer require staabm/side-effects-detector` + +## Usage + +Example: + +```php +use staabm\SideEffectsDetector\SideEffectsDetector; + +$code = '=") or echo("skip because attributes are only available since PHP 8.0");'; + +$detector = new SideEffectsDetector(); +// [SideEffect::STANDARD_OUTPUT] +var_dump($detector->getSideEffects($code)); +``` + +In case functions are called which are not known to have side-effects - e.g. userland functions - `null` is returned. + +```php +use staabm\SideEffectsDetector\SideEffectsDetector; + +$code = 'getSideEffects($code)); +``` + +Code might have multiple side-effects: + +```php +use staabm\SideEffectsDetector\SideEffectsDetector; + +$code = 'getSideEffects($code)); +``` + + +## Disclaimer + +Non goals are: +- find the best possible answer for all cases +- add runtime dependencies + +If you are in need of a fully fledged side-effect analysis, use more advanced tools like PHPStan. + +Look at the test-suite to get an idea of [supported use-cases](https://github.com/staabm/side-effects-detector/blob/main/tests/SideEffectsDetectorTest.php). diff --git a/app/vendor/staabm/side-effects-detector/composer.json b/app/vendor/staabm/side-effects-detector/composer.json new file mode 100644 index 000000000..0ef6413e8 --- /dev/null +++ b/app/vendor/staabm/side-effects-detector/composer.json @@ -0,0 +1,38 @@ +{ + "name": "staabm/side-effects-detector", + "license": "MIT", + "description": "A static analysis tool to detect side effects in PHP code", + "keywords": ["static analysis"], + "autoload": { + "classmap": ["lib/"] + }, + "autoload-dev": { + "classmap": [ + "tests/" + ] + }, + "require": { + "php": "^7.4 || ^8.0", + "ext-tokenizer": "*" + }, + "require-dev": { + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.6", + "phpunit/phpunit": "^9.6.21", + "symfony/var-dumper": "^5.4.43", + "tomasvotruba/type-coverage": "1.0.0", + "tomasvotruba/unused-public": "1.0.0" + }, + "config": { + "optimize-autoloader": true, + "sort-packages": true, + "allow-plugins": { + "phpstan/extension-installer": true + } + }, + "scripts": { + "qa": ["@test", "@phpstan"], + "phpstan": "phpstan analyze", + "test": "phpunit" + } +} diff --git a/app/vendor/staabm/side-effects-detector/lib/SideEffect.php b/app/vendor/staabm/side-effects-detector/lib/SideEffect.php new file mode 100644 index 000000000..045a3aae8 --- /dev/null +++ b/app/vendor/staabm/side-effects-detector/lib/SideEffect.php @@ -0,0 +1,42 @@ + + */ + private array $scopePollutingTokens = [ + T_CLASS, + T_FUNCTION, + T_NEW, + T_EVAL, + T_GLOBAL, + T_GOTO, + T_HALT_COMPILER, + T_INCLUDE, + T_INCLUDE_ONCE, + T_REQUIRE, + T_REQUIRE_ONCE, + T_THROW, + T_UNSET, + T_UNSET_CAST + ]; + + private const PROCESS_EXIT_TOKENS = [ + T_EXIT + ]; + + private const OUTPUT_TOKENS = [ + T_PRINT, + T_ECHO, + T_INLINE_HTML + ]; + + private const SCOPE_POLLUTING_FUNCTIONS = [ + 'putenv', + 'setlocale', + 'class_exists', + 'ini_set', + ]; + + private const STANDARD_OUTPUT_FUNCTIONS = [ + 'printf', + 'vprintf' + ]; + + private const INPUT_OUTPUT_FUNCTIONS = [ + 'fopen', + 'file_get_contents', + 'file_put_contents', + 'fwrite', + 'fputs', + 'fread', + 'unlink' + ]; + + /** + * @var array + */ + private array $functionMetadata; + + public function __construct() { + $functionMeta = require __DIR__ . '/functionMetadata.php'; + if (!is_array($functionMeta)) { + throw new \RuntimeException('Invalid function metadata'); + } + $this->functionMetadata = $functionMeta; + + if (defined('T_ENUM')) { + $this->scopePollutingTokens[] = T_ENUM; + } + } + + /** + * @api + * + * @return array + */ + public function getSideEffects(string $code): array { + $tokens = token_get_all($code); + + $sideEffects = []; + for ($i = 0; $i < count($tokens); $i++) { + $token = $tokens[$i]; + + if (!is_array($token)) { + continue; + } + + if ($this->isAnonymousFunction($tokens, $i)) { + continue; + } + + if (in_array($token[0], self::OUTPUT_TOKENS, true)) { + $sideEffects[] = SideEffect::STANDARD_OUTPUT; + continue; + } + if (in_array($token[0], self::PROCESS_EXIT_TOKENS, true)) { + $sideEffects[] = SideEffect::PROCESS_EXIT; + continue; + } + if (in_array($token[0], $this->scopePollutingTokens, true)) { + $sideEffects[] = SideEffect::SCOPE_POLLUTION; + + $i++; + if (in_array($token[0], [T_FUNCTION, T_CLASS], true)) { + $this->consumeWhitespaces($tokens, $i); + } + + // consume function/class-name + if ( + !array_key_exists($i, $tokens) + || !is_array($tokens[$i]) + || $tokens[$i][0] !== T_STRING + ) { + continue; + } + + $i++; + continue; + } + + $functionCall = $this->getFunctionCall($tokens, $i); + if ($functionCall !== null) { + $callSideEffect = $this->getFunctionCallSideEffect($functionCall); + if ($callSideEffect !== null) { + $sideEffects[] = $callSideEffect; + } + continue; + } + + $methodCall = $this->getMethodCall($tokens, $i); + if ($methodCall !== null) { + $sideEffects[] = SideEffect::MAYBE; + continue; + } + + $propertyAccess = $this->getPropertyAccess($tokens, $i); + if ($propertyAccess !== null) { + $sideEffects[] = SideEffect::SCOPE_POLLUTION; + continue; + } + + if ($this->isNonLocalVariable($tokens, $i)) { + $sideEffects[] = SideEffect::SCOPE_POLLUTION; + continue; + } + } + + return array_values(array_unique($sideEffects)); + } + + /** + * @return SideEffect::*|null + */ + private function getFunctionCallSideEffect(string $functionName): ?string { // @phpstan-ignore return.unusedType + if (in_array($functionName, self::STANDARD_OUTPUT_FUNCTIONS, true)) { + return SideEffect::STANDARD_OUTPUT; + } + + if (in_array($functionName, self::INPUT_OUTPUT_FUNCTIONS, true)) { + return SideEffect::INPUT_OUTPUT; + } + + if (in_array($functionName, self::SCOPE_POLLUTING_FUNCTIONS, true)) { + return SideEffect::SCOPE_POLLUTION; + } + + if (array_key_exists($functionName, $this->functionMetadata)) { + if ($this->functionMetadata[$functionName]['hasSideEffects'] === true) { + return SideEffect::UNKNOWN_CLASS; + } + } else { + try { + $reflectionFunction = new \ReflectionFunction($functionName); + $returnType = $reflectionFunction->getReturnType(); + if ($returnType === null) { + return SideEffect::MAYBE; // no reflection information -> we don't know + } + if ((string)$returnType === 'void') { + return SideEffect::UNKNOWN_CLASS; // functions with void return type must have side-effects + } + } catch (\ReflectionException $e) { + return SideEffect::MAYBE; // function does not exist -> we don't know + } + } + + return null; + } + + /** + * @param array $tokens + */ + private function getFunctionCall(array $tokens, int $index): ?string { + if ( + !array_key_exists($index, $tokens) + || !is_array($tokens[$index]) + || $tokens[$index][0] !== T_STRING + ) { + return null; + } + $functionName = $tokens[$index][1]; + + $index++; + $this->consumeWhitespaces($tokens, $index); + + if ( + array_key_exists($index, $tokens) + && $tokens[$index] === '(' + ) { + return $functionName; + } + + return null; + } + + /** + * @param array $tokens + */ + private function getMethodCall(array $tokens, int $index): ?string { + if ( + !array_key_exists($index, $tokens) + || !is_array($tokens[$index]) + || !in_array($tokens[$index][0], [T_VARIABLE, T_STRING], true) + ) { + return null; + } + $callee = $tokens[$index][1]; + + $index++; + $this->consumeWhitespaces($tokens, $index); + + if ( + !array_key_exists($index, $tokens) + || !is_array($tokens[$index]) + || !in_array($tokens[$index][0], [T_OBJECT_OPERATOR , T_DOUBLE_COLON ], true) + ) { + return null; + } + $operator = $tokens[$index][1]; + + $index++; + $this->consumeWhitespaces($tokens, $index); + + if ( + !array_key_exists($index, $tokens) + || !is_array($tokens[$index]) + || !in_array($tokens[$index][0], [T_STRING], true) + ) { + return null; + } + $method = $tokens[$index][1]; + + $index++; + $this->consumeWhitespaces($tokens, $index); + + if ( + array_key_exists($index, $tokens) + && $tokens[$index] !== '(' + ) { + return null; + } + + return $callee . $operator . $method; + } + + /** + * @param array $tokens + */ + private function getPropertyAccess(array $tokens, int $index): ?string { + if ( + !array_key_exists($index, $tokens) + || !is_array($tokens[$index]) + || !in_array($tokens[$index][0], [T_VARIABLE, T_STRING], true) + ) { + return null; + } + $objectOrClass = $tokens[$index][1]; + + $index++; + $this->consumeWhitespaces($tokens, $index); + + if ( + !array_key_exists($index, $tokens) + || !is_array($tokens[$index]) + || !in_array($tokens[$index][0], [T_OBJECT_OPERATOR , T_DOUBLE_COLON ], true) + ) { + return null; + } + $operator = $tokens[$index][1]; + + $index++; + $this->consumeWhitespaces($tokens, $index); + + if ( + !array_key_exists($index, $tokens) + || !is_array($tokens[$index]) + || !in_array($tokens[$index][0], [T_STRING, T_VARIABLE], true) + ) { + return null; + } + $propName = $tokens[$index][1]; + + return $objectOrClass . $operator . $propName; + } + + /** + * @param array $tokens + */ + private function isAnonymousFunction(array $tokens, int $index): bool + { + if ( + !array_key_exists($index, $tokens) + || !is_array($tokens[$index]) + || $tokens[$index][0] !== T_FUNCTION + ) { + return false; + } + + $index++; + $this->consumeWhitespaces($tokens, $index); + + if ( + array_key_exists($index, $tokens) + && $tokens[$index] === '(' + ) { + return true; + } + + return false; + } + + /** + * @param array $tokens + */ + private function isNonLocalVariable(array $tokens, int $index): bool + { + if ( + array_key_exists($index, $tokens) + && is_array($tokens[$index]) + && $tokens[$index][0] === T_VARIABLE + ) { + if ( + in_array( + $tokens[$index][1], + [ + '$this', + '$GLOBALS', '$_SERVER', '$_GET', '$_POST', '$_FILES', '$_COOKIE', '$_SESSION', '$_REQUEST', '$_ENV', + ], + true) + ) { + return true; + } + } + + return false; + } + + /** + * @param array $tokens + */ + private function consumeWhitespaces(array $tokens, int &$index): void { + while ( + array_key_exists($index, $tokens) + && is_array($tokens[$index]) + && $tokens[$index][0] === T_WHITESPACE + ) { + $index++; + } + } +} diff --git a/app/vendor/staabm/side-effects-detector/lib/functionMetadata.php b/app/vendor/staabm/side-effects-detector/lib/functionMetadata.php new file mode 100644 index 000000000..8a1748ab3 --- /dev/null +++ b/app/vendor/staabm/side-effects-detector/lib/functionMetadata.php @@ -0,0 +1,1588 @@ + ['hasSideEffects' => true], + 'trigger_error' => ['hasSideEffects' => true], + 'putenv' => ['hasSideEffects' => true], + 'version_compare' => ['hasSideEffects' => false], + + // Intially copied from PHPStan + 'BackedEnum::from' => ['hasSideEffects' => false], + 'BackedEnum::tryFrom' => ['hasSideEffects' => false], + 'CURLFile::getFilename' => ['hasSideEffects' => false], + 'CURLFile::getMimeType' => ['hasSideEffects' => false], + 'CURLFile::getPostFilename' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\AlreadyExistsException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\AuthenticationException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\ConfigurationException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\DivideByZeroException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\DomainException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\ExecutionException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\InvalidArgumentException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\InvalidQueryException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\InvalidSyntaxException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\IsBootstrappingException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\LogicException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\OverloadedException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\ProtocolException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\RangeException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\ReadTimeoutException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\RuntimeException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\ServerException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\TimeoutException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\TruncateException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\UnauthorizedException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\UnavailableException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\UnpreparedException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\ValidationException::__construct' => ['hasSideEffects' => false], + 'Cassandra\\Exception\\WriteTimeoutException::__construct' => ['hasSideEffects' => false], + 'Closure::bind' => ['hasSideEffects' => false], + 'Closure::bindTo' => ['hasSideEffects' => false], + 'Collator::__construct' => ['hasSideEffects' => false], + 'Collator::compare' => ['hasSideEffects' => false], + 'Collator::getAttribute' => ['hasSideEffects' => false], + 'Collator::getErrorCode' => ['hasSideEffects' => false], + 'Collator::getErrorMessage' => ['hasSideEffects' => false], + 'Collator::getLocale' => ['hasSideEffects' => false], + 'Collator::getSortKey' => ['hasSideEffects' => false], + 'Collator::getStrength' => ['hasSideEffects' => false], + 'DateTime::add' => ['hasSideEffects' => true], + 'DateTime::createFromFormat' => ['hasSideEffects' => false], + 'DateTime::createFromImmutable' => ['hasSideEffects' => false], + 'DateTime::diff' => ['hasSideEffects' => false], + 'DateTime::format' => ['hasSideEffects' => false], + 'DateTime::getLastErrors' => ['hasSideEffects' => false], + 'DateTime::getOffset' => ['hasSideEffects' => false], + 'DateTime::getTimestamp' => ['hasSideEffects' => false], + 'DateTime::getTimezone' => ['hasSideEffects' => false], + 'DateTime::modify' => ['hasSideEffects' => true], + 'DateTime::setDate' => ['hasSideEffects' => true], + 'DateTime::setISODate' => ['hasSideEffects' => true], + 'DateTime::setTime' => ['hasSideEffects' => true], + 'DateTime::setTimestamp' => ['hasSideEffects' => true], + 'DateTime::setTimezone' => ['hasSideEffects' => true], + 'DateTime::sub' => ['hasSideEffects' => true], + 'DateTimeImmutable::add' => ['hasSideEffects' => false], + 'DateTimeImmutable::createFromFormat' => ['hasSideEffects' => false], + 'DateTimeImmutable::createFromMutable' => ['hasSideEffects' => false], + 'DateTimeImmutable::diff' => ['hasSideEffects' => false], + 'DateTimeImmutable::format' => ['hasSideEffects' => false], + 'DateTimeImmutable::getLastErrors' => ['hasSideEffects' => false], + 'DateTimeImmutable::getOffset' => ['hasSideEffects' => false], + 'DateTimeImmutable::getTimestamp' => ['hasSideEffects' => false], + 'DateTimeImmutable::getTimezone' => ['hasSideEffects' => false], + 'DateTimeImmutable::modify' => ['hasSideEffects' => false], + 'DateTimeImmutable::setDate' => ['hasSideEffects' => false], + 'DateTimeImmutable::setISODate' => ['hasSideEffects' => false], + 'DateTimeImmutable::setTime' => ['hasSideEffects' => false], + 'DateTimeImmutable::setTimestamp' => ['hasSideEffects' => false], + 'DateTimeImmutable::setTimezone' => ['hasSideEffects' => false], + 'DateTimeImmutable::sub' => ['hasSideEffects' => false], + 'Error::__construct' => ['hasSideEffects' => false], + 'ErrorException::__construct' => ['hasSideEffects' => false], + 'Event::__construct' => ['hasSideEffects' => false], + 'EventBase::getFeatures' => ['hasSideEffects' => false], + 'EventBase::getMethod' => ['hasSideEffects' => false], + 'EventBase::getTimeOfDayCached' => ['hasSideEffects' => false], + 'EventBase::gotExit' => ['hasSideEffects' => false], + 'EventBase::gotStop' => ['hasSideEffects' => false], + 'EventBuffer::__construct' => ['hasSideEffects' => false], + 'EventBufferEvent::__construct' => ['hasSideEffects' => false], + 'EventBufferEvent::getDnsErrorString' => ['hasSideEffects' => false], + 'EventBufferEvent::getEnabled' => ['hasSideEffects' => false], + 'EventBufferEvent::getInput' => ['hasSideEffects' => false], + 'EventBufferEvent::getOutput' => ['hasSideEffects' => false], + 'EventConfig::__construct' => ['hasSideEffects' => false], + 'EventDnsBase::__construct' => ['hasSideEffects' => false], + 'EventHttpConnection::__construct' => ['hasSideEffects' => false], + 'EventHttpRequest::__construct' => ['hasSideEffects' => false], + 'EventHttpRequest::getCommand' => ['hasSideEffects' => false], + 'EventHttpRequest::getConnection' => ['hasSideEffects' => false], + 'EventHttpRequest::getHost' => ['hasSideEffects' => false], + 'EventHttpRequest::getInputBuffer' => ['hasSideEffects' => false], + 'EventHttpRequest::getInputHeaders' => ['hasSideEffects' => false], + 'EventHttpRequest::getOutputBuffer' => ['hasSideEffects' => false], + 'EventHttpRequest::getOutputHeaders' => ['hasSideEffects' => false], + 'EventHttpRequest::getResponseCode' => ['hasSideEffects' => false], + 'EventHttpRequest::getUri' => ['hasSideEffects' => false], + 'EventSslContext::__construct' => ['hasSideEffects' => false], + 'Exception::__construct' => ['hasSideEffects' => false], + 'Exception::getCode' => ['hasSideEffects' => false], + 'Exception::getFile' => ['hasSideEffects' => false], + 'Exception::getLine' => ['hasSideEffects' => false], + 'Exception::getMessage' => ['hasSideEffects' => false], + 'Exception::getPrevious' => ['hasSideEffects' => false], + 'Exception::getTrace' => ['hasSideEffects' => false], + 'Exception::getTraceAsString' => ['hasSideEffects' => false], + 'Gmagick::getcopyright' => ['hasSideEffects' => false], + 'Gmagick::getfilename' => ['hasSideEffects' => false], + 'Gmagick::getimagebackgroundcolor' => ['hasSideEffects' => false], + 'Gmagick::getimageblueprimary' => ['hasSideEffects' => false], + 'Gmagick::getimagebordercolor' => ['hasSideEffects' => false], + 'Gmagick::getimagechanneldepth' => ['hasSideEffects' => false], + 'Gmagick::getimagecolors' => ['hasSideEffects' => false], + 'Gmagick::getimagecolorspace' => ['hasSideEffects' => false], + 'Gmagick::getimagecompose' => ['hasSideEffects' => false], + 'Gmagick::getimagedelay' => ['hasSideEffects' => false], + 'Gmagick::getimagedepth' => ['hasSideEffects' => false], + 'Gmagick::getimagedispose' => ['hasSideEffects' => false], + 'Gmagick::getimageextrema' => ['hasSideEffects' => false], + 'Gmagick::getimagefilename' => ['hasSideEffects' => false], + 'Gmagick::getimageformat' => ['hasSideEffects' => false], + 'Gmagick::getimagegamma' => ['hasSideEffects' => false], + 'Gmagick::getimagegreenprimary' => ['hasSideEffects' => false], + 'Gmagick::getimageheight' => ['hasSideEffects' => false], + 'Gmagick::getimagehistogram' => ['hasSideEffects' => false], + 'Gmagick::getimageindex' => ['hasSideEffects' => false], + 'Gmagick::getimageinterlacescheme' => ['hasSideEffects' => false], + 'Gmagick::getimageiterations' => ['hasSideEffects' => false], + 'Gmagick::getimagematte' => ['hasSideEffects' => false], + 'Gmagick::getimagemattecolor' => ['hasSideEffects' => false], + 'Gmagick::getimageprofile' => ['hasSideEffects' => false], + 'Gmagick::getimageredprimary' => ['hasSideEffects' => false], + 'Gmagick::getimagerenderingintent' => ['hasSideEffects' => false], + 'Gmagick::getimageresolution' => ['hasSideEffects' => false], + 'Gmagick::getimagescene' => ['hasSideEffects' => false], + 'Gmagick::getimagesignature' => ['hasSideEffects' => false], + 'Gmagick::getimagetype' => ['hasSideEffects' => false], + 'Gmagick::getimageunits' => ['hasSideEffects' => false], + 'Gmagick::getimagewhitepoint' => ['hasSideEffects' => false], + 'Gmagick::getimagewidth' => ['hasSideEffects' => false], + 'Gmagick::getpackagename' => ['hasSideEffects' => false], + 'Gmagick::getquantumdepth' => ['hasSideEffects' => false], + 'Gmagick::getreleasedate' => ['hasSideEffects' => false], + 'Gmagick::getsamplingfactors' => ['hasSideEffects' => false], + 'Gmagick::getsize' => ['hasSideEffects' => false], + 'Gmagick::getversion' => ['hasSideEffects' => false], + 'GmagickDraw::getfillcolor' => ['hasSideEffects' => false], + 'GmagickDraw::getfillopacity' => ['hasSideEffects' => false], + 'GmagickDraw::getfont' => ['hasSideEffects' => false], + 'GmagickDraw::getfontsize' => ['hasSideEffects' => false], + 'GmagickDraw::getfontstyle' => ['hasSideEffects' => false], + 'GmagickDraw::getfontweight' => ['hasSideEffects' => false], + 'GmagickDraw::getstrokecolor' => ['hasSideEffects' => false], + 'GmagickDraw::getstrokeopacity' => ['hasSideEffects' => false], + 'GmagickDraw::getstrokewidth' => ['hasSideEffects' => false], + 'GmagickDraw::gettextdecoration' => ['hasSideEffects' => false], + 'GmagickDraw::gettextencoding' => ['hasSideEffects' => false], + 'GmagickPixel::getcolor' => ['hasSideEffects' => false], + 'GmagickPixel::getcolorcount' => ['hasSideEffects' => false], + 'GmagickPixel::getcolorvalue' => ['hasSideEffects' => false], + 'HttpMessage::getBody' => ['hasSideEffects' => false], + 'HttpMessage::getHeader' => ['hasSideEffects' => false], + 'HttpMessage::getHeaders' => ['hasSideEffects' => false], + 'HttpMessage::getHttpVersion' => ['hasSideEffects' => false], + 'HttpMessage::getInfo' => ['hasSideEffects' => false], + 'HttpMessage::getParentMessage' => ['hasSideEffects' => false], + 'HttpMessage::getRequestMethod' => ['hasSideEffects' => false], + 'HttpMessage::getRequestUrl' => ['hasSideEffects' => false], + 'HttpMessage::getResponseCode' => ['hasSideEffects' => false], + 'HttpMessage::getResponseStatus' => ['hasSideEffects' => false], + 'HttpMessage::getType' => ['hasSideEffects' => false], + 'HttpQueryString::get' => ['hasSideEffects' => false], + 'HttpQueryString::getArray' => ['hasSideEffects' => false], + 'HttpQueryString::getBool' => ['hasSideEffects' => false], + 'HttpQueryString::getFloat' => ['hasSideEffects' => false], + 'HttpQueryString::getInt' => ['hasSideEffects' => false], + 'HttpQueryString::getObject' => ['hasSideEffects' => false], + 'HttpQueryString::getString' => ['hasSideEffects' => false], + 'HttpRequest::getBody' => ['hasSideEffects' => false], + 'HttpRequest::getContentType' => ['hasSideEffects' => false], + 'HttpRequest::getCookies' => ['hasSideEffects' => false], + 'HttpRequest::getHeaders' => ['hasSideEffects' => false], + 'HttpRequest::getHistory' => ['hasSideEffects' => false], + 'HttpRequest::getMethod' => ['hasSideEffects' => false], + 'HttpRequest::getOptions' => ['hasSideEffects' => false], + 'HttpRequest::getPostFields' => ['hasSideEffects' => false], + 'HttpRequest::getPostFiles' => ['hasSideEffects' => false], + 'HttpRequest::getPutData' => ['hasSideEffects' => false], + 'HttpRequest::getPutFile' => ['hasSideEffects' => false], + 'HttpRequest::getQueryData' => ['hasSideEffects' => false], + 'HttpRequest::getRawPostData' => ['hasSideEffects' => false], + 'HttpRequest::getRawRequestMessage' => ['hasSideEffects' => false], + 'HttpRequest::getRawResponseMessage' => ['hasSideEffects' => false], + 'HttpRequest::getRequestMessage' => ['hasSideEffects' => false], + 'HttpRequest::getResponseBody' => ['hasSideEffects' => false], + 'HttpRequest::getResponseCode' => ['hasSideEffects' => false], + 'HttpRequest::getResponseCookies' => ['hasSideEffects' => false], + 'HttpRequest::getResponseData' => ['hasSideEffects' => false], + 'HttpRequest::getResponseHeader' => ['hasSideEffects' => false], + 'HttpRequest::getResponseInfo' => ['hasSideEffects' => false], + 'HttpRequest::getResponseMessage' => ['hasSideEffects' => false], + 'HttpRequest::getResponseStatus' => ['hasSideEffects' => false], + 'HttpRequest::getSslOptions' => ['hasSideEffects' => false], + 'HttpRequest::getUrl' => ['hasSideEffects' => false], + 'HttpRequestPool::getAttachedRequests' => ['hasSideEffects' => false], + 'HttpRequestPool::getFinishedRequests' => ['hasSideEffects' => false], + 'Imagick::getColorspace' => ['hasSideEffects' => false], + 'Imagick::getCompression' => ['hasSideEffects' => false], + 'Imagick::getCompressionQuality' => ['hasSideEffects' => false], + 'Imagick::getConfigureOptions' => ['hasSideEffects' => false], + 'Imagick::getFeatures' => ['hasSideEffects' => false], + 'Imagick::getFilename' => ['hasSideEffects' => false], + 'Imagick::getFont' => ['hasSideEffects' => false], + 'Imagick::getFormat' => ['hasSideEffects' => false], + 'Imagick::getGravity' => ['hasSideEffects' => false], + 'Imagick::getHDRIEnabled' => ['hasSideEffects' => false], + 'Imagick::getImage' => ['hasSideEffects' => false], + 'Imagick::getImageAlphaChannel' => ['hasSideEffects' => false], + 'Imagick::getImageArtifact' => ['hasSideEffects' => false], + 'Imagick::getImageAttribute' => ['hasSideEffects' => false], + 'Imagick::getImageBackgroundColor' => ['hasSideEffects' => false], + 'Imagick::getImageBlob' => ['hasSideEffects' => false], + 'Imagick::getImageBluePrimary' => ['hasSideEffects' => false], + 'Imagick::getImageBorderColor' => ['hasSideEffects' => false], + 'Imagick::getImageChannelDepth' => ['hasSideEffects' => false], + 'Imagick::getImageChannelDistortion' => ['hasSideEffects' => false], + 'Imagick::getImageChannelDistortions' => ['hasSideEffects' => false], + 'Imagick::getImageChannelExtrema' => ['hasSideEffects' => false], + 'Imagick::getImageChannelKurtosis' => ['hasSideEffects' => false], + 'Imagick::getImageChannelMean' => ['hasSideEffects' => false], + 'Imagick::getImageChannelRange' => ['hasSideEffects' => false], + 'Imagick::getImageChannelStatistics' => ['hasSideEffects' => false], + 'Imagick::getImageClipMask' => ['hasSideEffects' => false], + 'Imagick::getImageColormapColor' => ['hasSideEffects' => false], + 'Imagick::getImageColors' => ['hasSideEffects' => false], + 'Imagick::getImageColorspace' => ['hasSideEffects' => false], + 'Imagick::getImageCompose' => ['hasSideEffects' => false], + 'Imagick::getImageCompression' => ['hasSideEffects' => false], + 'Imagick::getImageCompressionQuality' => ['hasSideEffects' => false], + 'Imagick::getImageDelay' => ['hasSideEffects' => false], + 'Imagick::getImageDepth' => ['hasSideEffects' => false], + 'Imagick::getImageDispose' => ['hasSideEffects' => false], + 'Imagick::getImageDistortion' => ['hasSideEffects' => false], + 'Imagick::getImageExtrema' => ['hasSideEffects' => false], + 'Imagick::getImageFilename' => ['hasSideEffects' => false], + 'Imagick::getImageFormat' => ['hasSideEffects' => false], + 'Imagick::getImageGamma' => ['hasSideEffects' => false], + 'Imagick::getImageGeometry' => ['hasSideEffects' => false], + 'Imagick::getImageGravity' => ['hasSideEffects' => false], + 'Imagick::getImageGreenPrimary' => ['hasSideEffects' => false], + 'Imagick::getImageHeight' => ['hasSideEffects' => false], + 'Imagick::getImageHistogram' => ['hasSideEffects' => false], + 'Imagick::getImageIndex' => ['hasSideEffects' => false], + 'Imagick::getImageInterlaceScheme' => ['hasSideEffects' => false], + 'Imagick::getImageInterpolateMethod' => ['hasSideEffects' => false], + 'Imagick::getImageIterations' => ['hasSideEffects' => false], + 'Imagick::getImageLength' => ['hasSideEffects' => false], + 'Imagick::getImageMatte' => ['hasSideEffects' => false], + 'Imagick::getImageMatteColor' => ['hasSideEffects' => false], + 'Imagick::getImageMimeType' => ['hasSideEffects' => false], + 'Imagick::getImageOrientation' => ['hasSideEffects' => false], + 'Imagick::getImagePage' => ['hasSideEffects' => false], + 'Imagick::getImagePixelColor' => ['hasSideEffects' => false], + 'Imagick::getImageProfile' => ['hasSideEffects' => false], + 'Imagick::getImageProfiles' => ['hasSideEffects' => false], + 'Imagick::getImageProperties' => ['hasSideEffects' => false], + 'Imagick::getImageProperty' => ['hasSideEffects' => false], + 'Imagick::getImageRedPrimary' => ['hasSideEffects' => false], + 'Imagick::getImageRegion' => ['hasSideEffects' => false], + 'Imagick::getImageRenderingIntent' => ['hasSideEffects' => false], + 'Imagick::getImageResolution' => ['hasSideEffects' => false], + 'Imagick::getImageScene' => ['hasSideEffects' => false], + 'Imagick::getImageSignature' => ['hasSideEffects' => false], + 'Imagick::getImageSize' => ['hasSideEffects' => false], + 'Imagick::getImageTicksPerSecond' => ['hasSideEffects' => false], + 'Imagick::getImageTotalInkDensity' => ['hasSideEffects' => false], + 'Imagick::getImageType' => ['hasSideEffects' => false], + 'Imagick::getImageUnits' => ['hasSideEffects' => false], + 'Imagick::getImageVirtualPixelMethod' => ['hasSideEffects' => false], + 'Imagick::getImageWhitePoint' => ['hasSideEffects' => false], + 'Imagick::getImageWidth' => ['hasSideEffects' => false], + 'Imagick::getImagesBlob' => ['hasSideEffects' => false], + 'Imagick::getInterlaceScheme' => ['hasSideEffects' => false], + 'Imagick::getIteratorIndex' => ['hasSideEffects' => false], + 'Imagick::getNumberImages' => ['hasSideEffects' => false], + 'Imagick::getOption' => ['hasSideEffects' => false], + 'Imagick::getPage' => ['hasSideEffects' => false], + 'Imagick::getPixelIterator' => ['hasSideEffects' => false], + 'Imagick::getPixelRegionIterator' => ['hasSideEffects' => false], + 'Imagick::getPointSize' => ['hasSideEffects' => false], + 'Imagick::getSamplingFactors' => ['hasSideEffects' => false], + 'Imagick::getSize' => ['hasSideEffects' => false], + 'Imagick::getSizeOffset' => ['hasSideEffects' => false], + 'ImagickDraw::getBorderColor' => ['hasSideEffects' => false], + 'ImagickDraw::getClipPath' => ['hasSideEffects' => false], + 'ImagickDraw::getClipRule' => ['hasSideEffects' => false], + 'ImagickDraw::getClipUnits' => ['hasSideEffects' => false], + 'ImagickDraw::getDensity' => ['hasSideEffects' => false], + 'ImagickDraw::getFillColor' => ['hasSideEffects' => false], + 'ImagickDraw::getFillOpacity' => ['hasSideEffects' => false], + 'ImagickDraw::getFillRule' => ['hasSideEffects' => false], + 'ImagickDraw::getFont' => ['hasSideEffects' => false], + 'ImagickDraw::getFontFamily' => ['hasSideEffects' => false], + 'ImagickDraw::getFontResolution' => ['hasSideEffects' => false], + 'ImagickDraw::getFontSize' => ['hasSideEffects' => false], + 'ImagickDraw::getFontStretch' => ['hasSideEffects' => false], + 'ImagickDraw::getFontStyle' => ['hasSideEffects' => false], + 'ImagickDraw::getFontWeight' => ['hasSideEffects' => false], + 'ImagickDraw::getGravity' => ['hasSideEffects' => false], + 'ImagickDraw::getOpacity' => ['hasSideEffects' => false], + 'ImagickDraw::getStrokeAntialias' => ['hasSideEffects' => false], + 'ImagickDraw::getStrokeColor' => ['hasSideEffects' => false], + 'ImagickDraw::getStrokeDashArray' => ['hasSideEffects' => false], + 'ImagickDraw::getStrokeDashOffset' => ['hasSideEffects' => false], + 'ImagickDraw::getStrokeLineCap' => ['hasSideEffects' => false], + 'ImagickDraw::getStrokeLineJoin' => ['hasSideEffects' => false], + 'ImagickDraw::getStrokeMiterLimit' => ['hasSideEffects' => false], + 'ImagickDraw::getStrokeOpacity' => ['hasSideEffects' => false], + 'ImagickDraw::getStrokeWidth' => ['hasSideEffects' => false], + 'ImagickDraw::getTextAlignment' => ['hasSideEffects' => false], + 'ImagickDraw::getTextAntialias' => ['hasSideEffects' => false], + 'ImagickDraw::getTextDecoration' => ['hasSideEffects' => false], + 'ImagickDraw::getTextDirection' => ['hasSideEffects' => false], + 'ImagickDraw::getTextEncoding' => ['hasSideEffects' => false], + 'ImagickDraw::getTextInterLineSpacing' => ['hasSideEffects' => false], + 'ImagickDraw::getTextInterWordSpacing' => ['hasSideEffects' => false], + 'ImagickDraw::getTextKerning' => ['hasSideEffects' => false], + 'ImagickDraw::getTextUnderColor' => ['hasSideEffects' => false], + 'ImagickDraw::getVectorGraphics' => ['hasSideEffects' => false], + 'ImagickKernel::getMatrix' => ['hasSideEffects' => false], + 'ImagickPixel::getColor' => ['hasSideEffects' => false], + 'ImagickPixel::getColorAsString' => ['hasSideEffects' => false], + 'ImagickPixel::getColorCount' => ['hasSideEffects' => false], + 'ImagickPixel::getColorQuantum' => ['hasSideEffects' => false], + 'ImagickPixel::getColorValue' => ['hasSideEffects' => false], + 'ImagickPixel::getColorValueQuantum' => ['hasSideEffects' => false], + 'ImagickPixel::getHSL' => ['hasSideEffects' => false], + 'ImagickPixel::getIndex' => ['hasSideEffects' => false], + 'ImagickPixelIterator::getCurrentIteratorRow' => ['hasSideEffects' => false], + 'ImagickPixelIterator::getIteratorRow' => ['hasSideEffects' => false], + 'ImagickPixelIterator::getNextIteratorRow' => ['hasSideEffects' => false], + 'ImagickPixelIterator::getPreviousIteratorRow' => ['hasSideEffects' => false], + 'IntBackedEnum::from' => ['hasSideEffects' => false], + 'IntBackedEnum::tryFrom' => ['hasSideEffects' => false], + 'IntlBreakIterator::current' => ['hasSideEffects' => false], + 'IntlBreakIterator::getErrorCode' => ['hasSideEffects' => false], + 'IntlBreakIterator::getErrorMessage' => ['hasSideEffects' => false], + 'IntlBreakIterator::getIterator' => ['hasSideEffects' => false], + 'IntlBreakIterator::getLocale' => ['hasSideEffects' => false], + 'IntlBreakIterator::getPartsIterator' => ['hasSideEffects' => false], + 'IntlBreakIterator::getText' => ['hasSideEffects' => false], + 'IntlBreakIterator::isBoundary' => ['hasSideEffects' => false], + 'IntlCalendar::after' => ['hasSideEffects' => false], + 'IntlCalendar::before' => ['hasSideEffects' => false], + 'IntlCalendar::equals' => ['hasSideEffects' => false], + 'IntlCalendar::fieldDifference' => ['hasSideEffects' => false], + 'IntlCalendar::get' => ['hasSideEffects' => false], + 'IntlCalendar::getActualMaximum' => ['hasSideEffects' => false], + 'IntlCalendar::getActualMinimum' => ['hasSideEffects' => false], + 'IntlCalendar::getDayOfWeekType' => ['hasSideEffects' => false], + 'IntlCalendar::getErrorCode' => ['hasSideEffects' => false], + 'IntlCalendar::getErrorMessage' => ['hasSideEffects' => false], + 'IntlCalendar::getFirstDayOfWeek' => ['hasSideEffects' => false], + 'IntlCalendar::getGreatestMinimum' => ['hasSideEffects' => false], + 'IntlCalendar::getLeastMaximum' => ['hasSideEffects' => false], + 'IntlCalendar::getLocale' => ['hasSideEffects' => false], + 'IntlCalendar::getMaximum' => ['hasSideEffects' => false], + 'IntlCalendar::getMinimalDaysInFirstWeek' => ['hasSideEffects' => false], + 'IntlCalendar::getMinimum' => ['hasSideEffects' => false], + 'IntlCalendar::getRepeatedWallTimeOption' => ['hasSideEffects' => false], + 'IntlCalendar::getSkippedWallTimeOption' => ['hasSideEffects' => false], + 'IntlCalendar::getTime' => ['hasSideEffects' => false], + 'IntlCalendar::getTimeZone' => ['hasSideEffects' => false], + 'IntlCalendar::getType' => ['hasSideEffects' => false], + 'IntlCalendar::getWeekendTransition' => ['hasSideEffects' => false], + 'IntlCalendar::inDaylightTime' => ['hasSideEffects' => false], + 'IntlCalendar::isEquivalentTo' => ['hasSideEffects' => false], + 'IntlCalendar::isLenient' => ['hasSideEffects' => false], + 'IntlCalendar::isWeekend' => ['hasSideEffects' => false], + 'IntlCalendar::toDateTime' => ['hasSideEffects' => false], + 'IntlChar::hasBinaryProperty' => ['hasSideEffects' => false], + 'IntlCodePointBreakIterator::getLastCodePoint' => ['hasSideEffects' => false], + 'IntlDateFormatter::__construct' => ['hasSideEffects' => false], + 'IntlDateFormatter::getCalendar' => ['hasSideEffects' => false], + 'IntlDateFormatter::getCalendarObject' => ['hasSideEffects' => false], + 'IntlDateFormatter::getDateType' => ['hasSideEffects' => false], + 'IntlDateFormatter::getErrorCode' => ['hasSideEffects' => false], + 'IntlDateFormatter::getErrorMessage' => ['hasSideEffects' => false], + 'IntlDateFormatter::getLocale' => ['hasSideEffects' => false], + 'IntlDateFormatter::getPattern' => ['hasSideEffects' => false], + 'IntlDateFormatter::getTimeType' => ['hasSideEffects' => false], + 'IntlDateFormatter::getTimeZone' => ['hasSideEffects' => false], + 'IntlDateFormatter::getTimeZoneId' => ['hasSideEffects' => false], + 'IntlDateFormatter::isLenient' => ['hasSideEffects' => false], + 'IntlGregorianCalendar::getGregorianChange' => ['hasSideEffects' => false], + 'IntlGregorianCalendar::isLeapYear' => ['hasSideEffects' => false], + 'IntlPartsIterator::getBreakIterator' => ['hasSideEffects' => false], + 'IntlRuleBasedBreakIterator::__construct' => ['hasSideEffects' => false], + 'IntlRuleBasedBreakIterator::getBinaryRules' => ['hasSideEffects' => false], + 'IntlRuleBasedBreakIterator::getRuleStatus' => ['hasSideEffects' => false], + 'IntlRuleBasedBreakIterator::getRuleStatusVec' => ['hasSideEffects' => false], + 'IntlRuleBasedBreakIterator::getRules' => ['hasSideEffects' => false], + 'IntlTimeZone::getDSTSavings' => ['hasSideEffects' => false], + 'IntlTimeZone::getDisplayName' => ['hasSideEffects' => false], + 'IntlTimeZone::getErrorCode' => ['hasSideEffects' => false], + 'IntlTimeZone::getErrorMessage' => ['hasSideEffects' => false], + 'IntlTimeZone::getID' => ['hasSideEffects' => false], + 'IntlTimeZone::getRawOffset' => ['hasSideEffects' => false], + 'IntlTimeZone::hasSameRules' => ['hasSideEffects' => false], + 'IntlTimeZone::toDateTimeZone' => ['hasSideEffects' => false], + 'JsonIncrementalParser::__construct' => ['hasSideEffects' => false], + 'JsonIncrementalParser::get' => ['hasSideEffects' => false], + 'JsonIncrementalParser::getError' => ['hasSideEffects' => false], + 'MemcachedException::__construct' => ['hasSideEffects' => false], + 'MessageFormatter::__construct' => ['hasSideEffects' => false], + 'MessageFormatter::format' => ['hasSideEffects' => false], + 'MessageFormatter::getErrorCode' => ['hasSideEffects' => false], + 'MessageFormatter::getErrorMessage' => ['hasSideEffects' => false], + 'MessageFormatter::getLocale' => ['hasSideEffects' => false], + 'MessageFormatter::getPattern' => ['hasSideEffects' => false], + 'MessageFormatter::parse' => ['hasSideEffects' => false], + 'NumberFormatter::__construct' => ['hasSideEffects' => false], + 'NumberFormatter::format' => ['hasSideEffects' => false], + 'NumberFormatter::formatCurrency' => ['hasSideEffects' => false], + 'NumberFormatter::getAttribute' => ['hasSideEffects' => false], + 'NumberFormatter::getErrorCode' => ['hasSideEffects' => false], + 'NumberFormatter::getErrorMessage' => ['hasSideEffects' => false], + 'NumberFormatter::getLocale' => ['hasSideEffects' => false], + 'NumberFormatter::getPattern' => ['hasSideEffects' => false], + 'NumberFormatter::getSymbol' => ['hasSideEffects' => false], + 'NumberFormatter::getTextAttribute' => ['hasSideEffects' => false], + 'ReflectionAttribute::getArguments' => ['hasSideEffects' => false], + 'ReflectionAttribute::getName' => ['hasSideEffects' => false], + 'ReflectionAttribute::getTarget' => ['hasSideEffects' => false], + 'ReflectionAttribute::isRepeated' => ['hasSideEffects' => false], + 'ReflectionClass::getAttributes' => ['hasSideEffects' => false], + 'ReflectionClass::getConstant' => ['hasSideEffects' => false], + 'ReflectionClass::getConstants' => ['hasSideEffects' => false], + 'ReflectionClass::getConstructor' => ['hasSideEffects' => false], + 'ReflectionClass::getDefaultProperties' => ['hasSideEffects' => false], + 'ReflectionClass::getDocComment' => ['hasSideEffects' => false], + 'ReflectionClass::getEndLine' => ['hasSideEffects' => false], + 'ReflectionClass::getExtension' => ['hasSideEffects' => false], + 'ReflectionClass::getExtensionName' => ['hasSideEffects' => false], + 'ReflectionClass::getFileName' => ['hasSideEffects' => false], + 'ReflectionClass::getInterfaceNames' => ['hasSideEffects' => false], + 'ReflectionClass::getInterfaces' => ['hasSideEffects' => false], + 'ReflectionClass::getMethod' => ['hasSideEffects' => false], + 'ReflectionClass::getMethods' => ['hasSideEffects' => false], + 'ReflectionClass::getModifiers' => ['hasSideEffects' => false], + 'ReflectionClass::getName' => ['hasSideEffects' => false], + 'ReflectionClass::getNamespaceName' => ['hasSideEffects' => false], + 'ReflectionClass::getParentClass' => ['hasSideEffects' => false], + 'ReflectionClass::getProperties' => ['hasSideEffects' => false], + 'ReflectionClass::getProperty' => ['hasSideEffects' => false], + 'ReflectionClass::getReflectionConstant' => ['hasSideEffects' => false], + 'ReflectionClass::getReflectionConstants' => ['hasSideEffects' => false], + 'ReflectionClass::getShortName' => ['hasSideEffects' => false], + 'ReflectionClass::getStartLine' => ['hasSideEffects' => false], + 'ReflectionClass::getStaticProperties' => ['hasSideEffects' => false], + 'ReflectionClass::getStaticPropertyValue' => ['hasSideEffects' => false], + 'ReflectionClass::getTraitAliases' => ['hasSideEffects' => false], + 'ReflectionClass::getTraitNames' => ['hasSideEffects' => false], + 'ReflectionClass::getTraits' => ['hasSideEffects' => false], + 'ReflectionClass::isAbstract' => ['hasSideEffects' => false], + 'ReflectionClass::isAnonymous' => ['hasSideEffects' => false], + 'ReflectionClass::isCloneable' => ['hasSideEffects' => false], + 'ReflectionClass::isFinal' => ['hasSideEffects' => false], + 'ReflectionClass::isInstance' => ['hasSideEffects' => false], + 'ReflectionClass::isInstantiable' => ['hasSideEffects' => false], + 'ReflectionClass::isInterface' => ['hasSideEffects' => false], + 'ReflectionClass::isInternal' => ['hasSideEffects' => false], + 'ReflectionClass::isIterable' => ['hasSideEffects' => false], + 'ReflectionClass::isIterateable' => ['hasSideEffects' => false], + 'ReflectionClass::isReadOnly' => ['hasSideEffects' => false], + 'ReflectionClass::isSubclassOf' => ['hasSideEffects' => false], + 'ReflectionClass::isTrait' => ['hasSideEffects' => false], + 'ReflectionClass::isUserDefined' => ['hasSideEffects' => false], + 'ReflectionClassConstant::getAttributes' => ['hasSideEffects' => false], + 'ReflectionClassConstant::getDeclaringClass' => ['hasSideEffects' => false], + 'ReflectionClassConstant::getDocComment' => ['hasSideEffects' => false], + 'ReflectionClassConstant::getModifiers' => ['hasSideEffects' => false], + 'ReflectionClassConstant::getName' => ['hasSideEffects' => false], + 'ReflectionClassConstant::getValue' => ['hasSideEffects' => false], + 'ReflectionClassConstant::isPrivate' => ['hasSideEffects' => false], + 'ReflectionClassConstant::isProtected' => ['hasSideEffects' => false], + 'ReflectionClassConstant::isPublic' => ['hasSideEffects' => false], + 'ReflectionEnumBackedCase::getBackingValue' => ['hasSideEffects' => false], + 'ReflectionEnumUnitCase::getEnum' => ['hasSideEffects' => false], + 'ReflectionEnumUnitCase::getValue' => ['hasSideEffects' => false], + 'ReflectionExtension::getClassNames' => ['hasSideEffects' => false], + 'ReflectionExtension::getClasses' => ['hasSideEffects' => false], + 'ReflectionExtension::getConstants' => ['hasSideEffects' => false], + 'ReflectionExtension::getDependencies' => ['hasSideEffects' => false], + 'ReflectionExtension::getFunctions' => ['hasSideEffects' => false], + 'ReflectionExtension::getINIEntries' => ['hasSideEffects' => false], + 'ReflectionExtension::getName' => ['hasSideEffects' => false], + 'ReflectionExtension::getVersion' => ['hasSideEffects' => false], + 'ReflectionExtension::isPersistent' => ['hasSideEffects' => false], + 'ReflectionExtension::isTemporary' => ['hasSideEffects' => false], + 'ReflectionFunction::getClosure' => ['hasSideEffects' => false], + 'ReflectionFunction::isDisabled' => ['hasSideEffects' => false], + 'ReflectionFunctionAbstract::getAttributes' => ['hasSideEffects' => false], + 'ReflectionFunctionAbstract::getClosureCalledClass' => ['hasSideEffects' => false], + 'ReflectionFunctionAbstract::getClosureScopeClass' => ['hasSideEffects' => false], + 'ReflectionFunctionAbstract::getClosureThis' => ['hasSideEffects' => false], + 'ReflectionFunctionAbstract::getClosureUsedVariables' => ['hasSideEffects' => false], + 'ReflectionFunctionAbstract::getDocComment' => ['hasSideEffects' => false], + 'ReflectionFunctionAbstract::getEndLine' => ['hasSideEffects' => false], + 'ReflectionFunctionAbstract::getExtension' => ['hasSideEffects' => false], + 'ReflectionFunctionAbstract::getExtensionName' => ['hasSideEffects' => false], + 'ReflectionFunctionAbstract::getFileName' => ['hasSideEffects' => false], + 'ReflectionFunctionAbstract::getName' => ['hasSideEffects' => false], + 'ReflectionFunctionAbstract::getNamespaceName' => ['hasSideEffects' => false], + 'ReflectionFunctionAbstract::getNumberOfParameters' => ['hasSideEffects' => false], + 'ReflectionFunctionAbstract::getNumberOfRequiredParameters' => ['hasSideEffects' => false], + 'ReflectionFunctionAbstract::getParameters' => ['hasSideEffects' => false], + 'ReflectionFunctionAbstract::getReturnType' => ['hasSideEffects' => false], + 'ReflectionFunctionAbstract::getShortName' => ['hasSideEffects' => false], + 'ReflectionFunctionAbstract::getStartLine' => ['hasSideEffects' => false], + 'ReflectionFunctionAbstract::getStaticVariables' => ['hasSideEffects' => false], + 'ReflectionFunctionAbstract::getTentativeReturnType' => ['hasSideEffects' => false], + 'ReflectionFunctionAbstract::hasTentativeReturnType' => ['hasSideEffects' => false], + 'ReflectionFunctionAbstract::isClosure' => ['hasSideEffects' => false], + 'ReflectionFunctionAbstract::isDeprecated' => ['hasSideEffects' => false], + 'ReflectionFunctionAbstract::isGenerator' => ['hasSideEffects' => false], + 'ReflectionFunctionAbstract::isInternal' => ['hasSideEffects' => false], + 'ReflectionFunctionAbstract::isStatic' => ['hasSideEffects' => false], + 'ReflectionFunctionAbstract::isUserDefined' => ['hasSideEffects' => false], + 'ReflectionFunctionAbstract::isVariadic' => ['hasSideEffects' => false], + 'ReflectionGenerator::getExecutingFile' => ['hasSideEffects' => false], + 'ReflectionGenerator::getExecutingGenerator' => ['hasSideEffects' => false], + 'ReflectionGenerator::getExecutingLine' => ['hasSideEffects' => false], + 'ReflectionGenerator::getFunction' => ['hasSideEffects' => false], + 'ReflectionGenerator::getThis' => ['hasSideEffects' => false], + 'ReflectionGenerator::getTrace' => ['hasSideEffects' => false], + 'ReflectionIntersectionType::getTypes' => ['hasSideEffects' => false], + 'ReflectionMethod::getClosure' => ['hasSideEffects' => false], + 'ReflectionMethod::getDeclaringClass' => ['hasSideEffects' => false], + 'ReflectionMethod::getModifiers' => ['hasSideEffects' => false], + 'ReflectionMethod::getPrototype' => ['hasSideEffects' => false], + 'ReflectionMethod::isAbstract' => ['hasSideEffects' => false], + 'ReflectionMethod::isConstructor' => ['hasSideEffects' => false], + 'ReflectionMethod::isDestructor' => ['hasSideEffects' => false], + 'ReflectionMethod::isFinal' => ['hasSideEffects' => false], + 'ReflectionMethod::isPrivate' => ['hasSideEffects' => false], + 'ReflectionMethod::isProtected' => ['hasSideEffects' => false], + 'ReflectionMethod::isPublic' => ['hasSideEffects' => false], + 'ReflectionMethod::isStatic' => ['hasSideEffects' => false], + 'ReflectionMethod::setAccessible' => ['hasSideEffects' => false], + 'ReflectionNamedType::getName' => ['hasSideEffects' => false], + 'ReflectionNamedType::isBuiltin' => ['hasSideEffects' => false], + 'ReflectionParameter::getAttributes' => ['hasSideEffects' => false], + 'ReflectionParameter::getClass' => ['hasSideEffects' => false], + 'ReflectionParameter::getDeclaringClass' => ['hasSideEffects' => false], + 'ReflectionParameter::getDeclaringFunction' => ['hasSideEffects' => false], + 'ReflectionParameter::getDefaultValue' => ['hasSideEffects' => false], + 'ReflectionParameter::getDefaultValueConstantName' => ['hasSideEffects' => false], + 'ReflectionParameter::getName' => ['hasSideEffects' => false], + 'ReflectionParameter::getPosition' => ['hasSideEffects' => false], + 'ReflectionParameter::getType' => ['hasSideEffects' => false], + 'ReflectionParameter::isArray' => ['hasSideEffects' => false], + 'ReflectionParameter::isCallable' => ['hasSideEffects' => false], + 'ReflectionParameter::isDefaultValueAvailable' => ['hasSideEffects' => false], + 'ReflectionParameter::isDefaultValueConstant' => ['hasSideEffects' => false], + 'ReflectionParameter::isOptional' => ['hasSideEffects' => false], + 'ReflectionParameter::isPassedByReference' => ['hasSideEffects' => false], + 'ReflectionParameter::isPromoted' => ['hasSideEffects' => false], + 'ReflectionParameter::isVariadic' => ['hasSideEffects' => false], + 'ReflectionProperty::getAttributes' => ['hasSideEffects' => false], + 'ReflectionProperty::getDeclaringClass' => ['hasSideEffects' => false], + 'ReflectionProperty::getDefaultValue' => ['hasSideEffects' => false], + 'ReflectionProperty::getDocComment' => ['hasSideEffects' => false], + 'ReflectionProperty::getModifiers' => ['hasSideEffects' => false], + 'ReflectionProperty::getName' => ['hasSideEffects' => false], + 'ReflectionProperty::getType' => ['hasSideEffects' => false], + 'ReflectionProperty::getValue' => ['hasSideEffects' => false], + 'ReflectionProperty::isDefault' => ['hasSideEffects' => false], + 'ReflectionProperty::isInitialized' => ['hasSideEffects' => false], + 'ReflectionProperty::isPrivate' => ['hasSideEffects' => false], + 'ReflectionProperty::isPromoted' => ['hasSideEffects' => false], + 'ReflectionProperty::isProtected' => ['hasSideEffects' => false], + 'ReflectionProperty::isPublic' => ['hasSideEffects' => false], + 'ReflectionProperty::isStatic' => ['hasSideEffects' => false], + 'ReflectionProperty::setAccessible' => ['hasSideEffects' => false], + 'ReflectionReference::getId' => ['hasSideEffects' => false], + 'ReflectionType::isBuiltin' => ['hasSideEffects' => false], + 'ReflectionUnionType::getTypes' => ['hasSideEffects' => false], + 'ReflectionZendExtension::getAuthor' => ['hasSideEffects' => false], + 'ReflectionZendExtension::getCopyright' => ['hasSideEffects' => false], + 'ReflectionZendExtension::getName' => ['hasSideEffects' => false], + 'ReflectionZendExtension::getURL' => ['hasSideEffects' => false], + 'ReflectionZendExtension::getVersion' => ['hasSideEffects' => false], + 'ResourceBundle::__construct' => ['hasSideEffects' => false], + 'ResourceBundle::count' => ['hasSideEffects' => false], + 'ResourceBundle::get' => ['hasSideEffects' => false], + 'ResourceBundle::getErrorCode' => ['hasSideEffects' => false], + 'ResourceBundle::getErrorMessage' => ['hasSideEffects' => false], + 'ResourceBundle::getIterator' => ['hasSideEffects' => false], + 'SQLiteException::__construct' => ['hasSideEffects' => false], + 'SimpleXMLElement::__construct' => ['hasSideEffects' => false], + 'SimpleXMLElement::children' => ['hasSideEffects' => false], + 'SimpleXMLElement::count' => ['hasSideEffects' => false], + 'SimpleXMLElement::current' => ['hasSideEffects' => false], + 'SimpleXMLElement::getChildren' => ['hasSideEffects' => false], + 'SimpleXMLElement::getDocNamespaces' => ['hasSideEffects' => false], + 'SimpleXMLElement::getName' => ['hasSideEffects' => false], + 'SimpleXMLElement::getNamespaces' => ['hasSideEffects' => false], + 'SimpleXMLElement::hasChildren' => ['hasSideEffects' => false], + 'SimpleXMLElement::offsetExists' => ['hasSideEffects' => false], + 'SimpleXMLElement::offsetGet' => ['hasSideEffects' => false], + 'SimpleXMLElement::valid' => ['hasSideEffects' => false], + 'SimpleXMLIterator::count' => ['hasSideEffects' => false], + 'SimpleXMLIterator::current' => ['hasSideEffects' => false], + 'SimpleXMLIterator::getChildren' => ['hasSideEffects' => false], + 'SimpleXMLIterator::hasChildren' => ['hasSideEffects' => false], + 'SimpleXMLIterator::valid' => ['hasSideEffects' => false], + 'SoapFault::__construct' => ['hasSideEffects' => false], + 'SplFileObject::fflush' => ['hasSideEffects' => true], + 'SplFileObject::fgetc' => ['hasSideEffects' => true], + 'SplFileObject::fgetcsv' => ['hasSideEffects' => true], + 'SplFileObject::fgets' => ['hasSideEffects' => true], + 'SplFileObject::fgetss' => ['hasSideEffects' => true], + 'SplFileObject::fpassthru' => ['hasSideEffects' => true], + 'SplFileObject::fputcsv' => ['hasSideEffects' => true], + 'SplFileObject::fread' => ['hasSideEffects' => true], + 'SplFileObject::fscanf' => ['hasSideEffects' => true], + 'SplFileObject::fseek' => ['hasSideEffects' => true], + 'SplFileObject::ftruncate' => ['hasSideEffects' => true], + 'SplFileObject::fwrite' => ['hasSideEffects' => true], + 'Spoofchecker::__construct' => ['hasSideEffects' => false], + 'StringBackedEnum::from' => ['hasSideEffects' => false], + 'StringBackedEnum::tryFrom' => ['hasSideEffects' => false], + 'StubTests\\CodeStyle\\BracesOneLineFixer::getDefinition' => ['hasSideEffects' => false], + 'StubTests\\Parsers\\ExpectedFunctionArgumentsInfo::__toString' => ['hasSideEffects' => false], + 'StubTests\\Parsers\\Visitors\\CoreStubASTVisitor::__construct' => ['hasSideEffects' => false], + 'StubTests\\StubsMetaExpectedArgumentsTest::getClassMemberFqn' => ['hasSideEffects' => false], + 'StubTests\\StubsParameterNamesTest::printParameters' => ['hasSideEffects' => false], + 'Transliterator::createInverse' => ['hasSideEffects' => false], + 'Transliterator::getErrorCode' => ['hasSideEffects' => false], + 'Transliterator::getErrorMessage' => ['hasSideEffects' => false], + 'Transliterator::transliterate' => ['hasSideEffects' => false], + 'UConverter::__construct' => ['hasSideEffects' => false], + 'UConverter::convert' => ['hasSideEffects' => false], + 'UConverter::getDestinationEncoding' => ['hasSideEffects' => false], + 'UConverter::getDestinationType' => ['hasSideEffects' => false], + 'UConverter::getErrorCode' => ['hasSideEffects' => false], + 'UConverter::getErrorMessage' => ['hasSideEffects' => false], + 'UConverter::getSourceEncoding' => ['hasSideEffects' => false], + 'UConverter::getSourceType' => ['hasSideEffects' => false], + 'UConverter::getStandards' => ['hasSideEffects' => false], + 'UConverter::getSubstChars' => ['hasSideEffects' => false], + 'UConverter::reasonText' => ['hasSideEffects' => false], + 'UnitEnum::cases' => ['hasSideEffects' => false], + 'WeakMap::count' => ['hasSideEffects' => false], + 'WeakMap::getIterator' => ['hasSideEffects' => false], + 'WeakMap::offsetExists' => ['hasSideEffects' => false], + 'WeakMap::offsetGet' => ['hasSideEffects' => false], + 'WeakReference::create' => ['hasSideEffects' => false], + 'WeakReference::get' => ['hasSideEffects' => false], + 'XmlReader::next' => ['hasSideEffects' => true], + 'XmlReader::read' => ['hasSideEffects' => true], + 'Zookeeper::getAcl' => ['hasSideEffects' => false], + 'Zookeeper::getChildren' => ['hasSideEffects' => false], + 'Zookeeper::getClientId' => ['hasSideEffects' => false], + 'Zookeeper::getRecvTimeout' => ['hasSideEffects' => false], + 'Zookeeper::getState' => ['hasSideEffects' => false], + '_' => ['hasSideEffects' => false], + 'abs' => ['hasSideEffects' => false], + 'acos' => ['hasSideEffects' => false], + 'acosh' => ['hasSideEffects' => false], + 'addcslashes' => ['hasSideEffects' => false], + 'addslashes' => ['hasSideEffects' => false], + 'apache_get_modules' => ['hasSideEffects' => false], + 'apache_get_version' => ['hasSideEffects' => false], + 'apache_getenv' => ['hasSideEffects' => false], + 'apache_request_headers' => ['hasSideEffects' => false], + 'array_change_key_case' => ['hasSideEffects' => false], + 'array_chunk' => ['hasSideEffects' => false], + 'array_column' => ['hasSideEffects' => false], + 'array_combine' => ['hasSideEffects' => false], + 'array_count_values' => ['hasSideEffects' => false], + 'array_diff' => ['hasSideEffects' => false], + 'array_diff_assoc' => ['hasSideEffects' => false], + 'array_diff_key' => ['hasSideEffects' => false], + 'array_diff_uassoc' => ['hasSideEffects' => false], + 'array_diff_ukey' => ['hasSideEffects' => false], + 'array_fill' => ['hasSideEffects' => false], + 'array_fill_keys' => ['hasSideEffects' => false], + 'array_flip' => ['hasSideEffects' => false], + 'array_intersect' => ['hasSideEffects' => false], + 'array_intersect_assoc' => ['hasSideEffects' => false], + 'array_intersect_key' => ['hasSideEffects' => false], + 'array_intersect_uassoc' => ['hasSideEffects' => false], + 'array_intersect_ukey' => ['hasSideEffects' => false], + 'array_is_list' => ['hasSideEffects' => false], + 'array_key_exists' => ['hasSideEffects' => false], + 'array_key_first' => ['hasSideEffects' => false], + 'array_key_last' => ['hasSideEffects' => false], + 'array_keys' => ['hasSideEffects' => false], + 'array_merge' => ['hasSideEffects' => false], + 'array_merge_recursive' => ['hasSideEffects' => false], + 'array_pad' => ['hasSideEffects' => false], + 'array_pop' => ['hasSideEffects' => true], + 'array_product' => ['hasSideEffects' => false], + 'array_push' => ['hasSideEffects' => true], + 'array_rand' => ['hasSideEffects' => false], + 'array_replace' => ['hasSideEffects' => false], + 'array_replace_recursive' => ['hasSideEffects' => false], + 'array_reverse' => ['hasSideEffects' => false], + 'array_search' => ['hasSideEffects' => false], + 'array_shift' => ['hasSideEffects' => true], + 'array_slice' => ['hasSideEffects' => false], + 'array_sum' => ['hasSideEffects' => false], + 'array_udiff' => ['hasSideEffects' => false], + 'array_udiff_assoc' => ['hasSideEffects' => false], + 'array_udiff_uassoc' => ['hasSideEffects' => false], + 'array_uintersect' => ['hasSideEffects' => false], + 'array_uintersect_assoc' => ['hasSideEffects' => false], + 'array_uintersect_uassoc' => ['hasSideEffects' => false], + 'array_unique' => ['hasSideEffects' => false], + 'array_unshift' => ['hasSideEffects' => true], + 'array_values' => ['hasSideEffects' => false], + 'asin' => ['hasSideEffects' => false], + 'asinh' => ['hasSideEffects' => false], + 'atan' => ['hasSideEffects' => false], + 'atan2' => ['hasSideEffects' => false], + 'atanh' => ['hasSideEffects' => false], + 'base64_decode' => ['hasSideEffects' => false], + 'base64_encode' => ['hasSideEffects' => false], + 'base_convert' => ['hasSideEffects' => false], + 'basename' => ['hasSideEffects' => false], + 'bcadd' => ['hasSideEffects' => false], + 'bccomp' => ['hasSideEffects' => false], + 'bcdiv' => ['hasSideEffects' => false], + 'bcmod' => ['hasSideEffects' => false], + 'bcmul' => ['hasSideEffects' => false], + 'bcpow' => ['hasSideEffects' => false], + 'bcpowmod' => ['hasSideEffects' => false], + 'bcsqrt' => ['hasSideEffects' => false], + 'bcsub' => ['hasSideEffects' => false], + 'bin2hex' => ['hasSideEffects' => false], + 'bindec' => ['hasSideEffects' => false], + 'boolval' => ['hasSideEffects' => false], + 'bzcompress' => ['hasSideEffects' => false], + 'bzdecompress' => ['hasSideEffects' => false], + 'bzerrno' => ['hasSideEffects' => false], + 'bzerror' => ['hasSideEffects' => false], + 'bzerrstr' => ['hasSideEffects' => false], + 'bzopen' => ['hasSideEffects' => false], + 'ceil' => ['hasSideEffects' => false], + 'checkdate' => ['hasSideEffects' => false], + 'checkdnsrr' => ['hasSideEffects' => false], + 'chgrp' => ['hasSideEffects' => true], + 'chmod' => ['hasSideEffects' => true], + 'chop' => ['hasSideEffects' => false], + 'chown' => ['hasSideEffects' => true], + 'chr' => ['hasSideEffects' => false], + 'chunk_split' => ['hasSideEffects' => false], + 'class_implements' => ['hasSideEffects' => false], + 'class_parents' => ['hasSideEffects' => false], + 'cli_get_process_title' => ['hasSideEffects' => false], + 'collator_compare' => ['hasSideEffects' => false], + 'collator_create' => ['hasSideEffects' => false], + 'collator_get_attribute' => ['hasSideEffects' => false], + 'collator_get_error_code' => ['hasSideEffects' => false], + 'collator_get_error_message' => ['hasSideEffects' => false], + 'collator_get_locale' => ['hasSideEffects' => false], + 'collator_get_sort_key' => ['hasSideEffects' => false], + 'collator_get_strength' => ['hasSideEffects' => false], + 'compact' => ['hasSideEffects' => false], + 'connection_aborted' => ['hasSideEffects' => true], + 'connection_status' => ['hasSideEffects' => true], + 'constant' => ['hasSideEffects' => false], + 'convert_cyr_string' => ['hasSideEffects' => false], + 'convert_uudecode' => ['hasSideEffects' => false], + 'convert_uuencode' => ['hasSideEffects' => false], + 'copy' => ['hasSideEffects' => true], + 'cos' => ['hasSideEffects' => false], + 'cosh' => ['hasSideEffects' => false], + 'count' => ['hasSideEffects' => false], + 'count_chars' => ['hasSideEffects' => false], + 'crc32' => ['hasSideEffects' => false], + 'crypt' => ['hasSideEffects' => false], + 'ctype_alnum' => ['hasSideEffects' => false], + 'ctype_alpha' => ['hasSideEffects' => false], + 'ctype_cntrl' => ['hasSideEffects' => false], + 'ctype_digit' => ['hasSideEffects' => false], + 'ctype_graph' => ['hasSideEffects' => false], + 'ctype_lower' => ['hasSideEffects' => false], + 'ctype_print' => ['hasSideEffects' => false], + 'ctype_punct' => ['hasSideEffects' => false], + 'ctype_space' => ['hasSideEffects' => false], + 'ctype_upper' => ['hasSideEffects' => false], + 'ctype_xdigit' => ['hasSideEffects' => false], + 'curl_copy_handle' => ['hasSideEffects' => false], + 'curl_errno' => ['hasSideEffects' => false], + 'curl_error' => ['hasSideEffects' => false], + 'curl_escape' => ['hasSideEffects' => false], + 'curl_file_create' => ['hasSideEffects' => false], + 'curl_getinfo' => ['hasSideEffects' => false], + 'curl_multi_errno' => ['hasSideEffects' => false], + 'curl_multi_getcontent' => ['hasSideEffects' => false], + 'curl_multi_info_read' => ['hasSideEffects' => false], + 'curl_share_errno' => ['hasSideEffects' => false], + 'curl_share_strerror' => ['hasSideEffects' => false], + 'curl_strerror' => ['hasSideEffects' => false], + 'curl_unescape' => ['hasSideEffects' => false], + 'curl_version' => ['hasSideEffects' => false], + 'current' => ['hasSideEffects' => false], + 'date' => ['hasSideEffects' => false], + 'date_create' => ['hasSideEffects' => false], + 'date_create_from_format' => ['hasSideEffects' => false], + 'date_create_immutable' => ['hasSideEffects' => false], + 'date_create_immutable_from_format' => ['hasSideEffects' => false], + 'date_default_timezone_get' => ['hasSideEffects' => false], + 'date_diff' => ['hasSideEffects' => false], + 'date_format' => ['hasSideEffects' => false], + 'date_get_last_errors' => ['hasSideEffects' => false], + 'date_interval_create_from_date_string' => ['hasSideEffects' => false], + 'date_interval_format' => ['hasSideEffects' => false], + 'date_offset_get' => ['hasSideEffects' => false], + 'date_parse' => ['hasSideEffects' => false], + 'date_parse_from_format' => ['hasSideEffects' => false], + 'date_sun_info' => ['hasSideEffects' => false], + 'date_sunrise' => ['hasSideEffects' => false], + 'date_sunset' => ['hasSideEffects' => false], + 'date_timestamp_get' => ['hasSideEffects' => false], + 'date_timezone_get' => ['hasSideEffects' => false], + 'datefmt_create' => ['hasSideEffects' => false], + 'datefmt_format' => ['hasSideEffects' => false], + 'datefmt_format_object' => ['hasSideEffects' => false], + 'datefmt_get_calendar' => ['hasSideEffects' => false], + 'datefmt_get_calendar_object' => ['hasSideEffects' => false], + 'datefmt_get_datetype' => ['hasSideEffects' => false], + 'datefmt_get_error_code' => ['hasSideEffects' => false], + 'datefmt_get_error_message' => ['hasSideEffects' => false], + 'datefmt_get_locale' => ['hasSideEffects' => false], + 'datefmt_get_pattern' => ['hasSideEffects' => false], + 'datefmt_get_timetype' => ['hasSideEffects' => false], + 'datefmt_get_timezone' => ['hasSideEffects' => false], + 'datefmt_get_timezone_id' => ['hasSideEffects' => false], + 'datefmt_is_lenient' => ['hasSideEffects' => false], + 'dcngettext' => ['hasSideEffects' => false], + 'decbin' => ['hasSideEffects' => false], + 'dechex' => ['hasSideEffects' => false], + 'decoct' => ['hasSideEffects' => false], + 'defined' => ['hasSideEffects' => false], + 'deflate_init' => ['hasSideEffects' => false], + 'deg2rad' => ['hasSideEffects' => false], + 'dirname' => ['hasSideEffects' => false], + 'disk_free_space' => ['hasSideEffects' => false], + 'disk_total_space' => ['hasSideEffects' => false], + 'diskfreespace' => ['hasSideEffects' => false], + 'dngettext' => ['hasSideEffects' => false], + 'doubleval' => ['hasSideEffects' => false], + 'error_get_last' => ['hasSideEffects' => false], + 'error_log' => ['hasSideEffects' => true], + 'escapeshellarg' => ['hasSideEffects' => false], + 'escapeshellcmd' => ['hasSideEffects' => false], + 'exp' => ['hasSideEffects' => false], + 'explode' => ['hasSideEffects' => false], + 'expm1' => ['hasSideEffects' => false], + 'extension_loaded' => ['hasSideEffects' => false], + 'fclose' => ['hasSideEffects' => true], + 'fdiv' => ['hasSideEffects' => false], + 'feof' => ['hasSideEffects' => false], + 'fflush' => ['hasSideEffects' => true], + 'fgetc' => ['hasSideEffects' => true], + 'fgetcsv' => ['hasSideEffects' => true], + 'fgets' => ['hasSideEffects' => true], + 'fgetss' => ['hasSideEffects' => true], + 'file' => ['hasSideEffects' => false], + 'file_exists' => ['hasSideEffects' => false], + 'file_get_contents' => ['hasSideEffects' => true], + 'file_put_contents' => ['hasSideEffects' => true], + 'fileatime' => ['hasSideEffects' => false], + 'filectime' => ['hasSideEffects' => false], + 'filegroup' => ['hasSideEffects' => false], + 'fileinode' => ['hasSideEffects' => false], + 'filemtime' => ['hasSideEffects' => false], + 'fileowner' => ['hasSideEffects' => false], + 'fileperms' => ['hasSideEffects' => false], + 'filesize' => ['hasSideEffects' => false], + 'filetype' => ['hasSideEffects' => false], + 'filter_has_var' => ['hasSideEffects' => false], + 'filter_id' => ['hasSideEffects' => false], + 'filter_input' => ['hasSideEffects' => false], + 'filter_input_array' => ['hasSideEffects' => false], + 'filter_list' => ['hasSideEffects' => false], + 'filter_var' => ['hasSideEffects' => false], + 'filter_var_array' => ['hasSideEffects' => false], + 'finfo::buffer' => ['hasSideEffects' => false], + 'finfo::file' => ['hasSideEffects' => false], + 'floatval' => ['hasSideEffects' => false], + 'flock' => ['hasSideEffects' => true], + 'floor' => ['hasSideEffects' => false], + 'fmod' => ['hasSideEffects' => false], + 'fnmatch' => ['hasSideEffects' => false], + 'fopen' => ['hasSideEffects' => true], + 'fpassthru' => ['hasSideEffects' => true], + 'fputcsv' => ['hasSideEffects' => true], + 'fputs' => ['hasSideEffects' => true], + 'fread' => ['hasSideEffects' => true], + 'fscanf' => ['hasSideEffects' => true], + 'fseek' => ['hasSideEffects' => true], + 'fstat' => ['hasSideEffects' => false], + 'ftell' => ['hasSideEffects' => false], + 'ftok' => ['hasSideEffects' => false], + 'ftruncate' => ['hasSideEffects' => true], + 'func_get_arg' => ['hasSideEffects' => false], + 'func_get_args' => ['hasSideEffects' => false], + 'func_num_args' => ['hasSideEffects' => false], + 'function_exists' => ['hasSideEffects' => false], + 'fwrite' => ['hasSideEffects' => true], + 'gc_enabled' => ['hasSideEffects' => false], + 'gc_status' => ['hasSideEffects' => false], + 'gd_info' => ['hasSideEffects' => false], + 'geoip_continent_code_by_name' => ['hasSideEffects' => false], + 'geoip_country_code3_by_name' => ['hasSideEffects' => false], + 'geoip_country_code_by_name' => ['hasSideEffects' => false], + 'geoip_country_name_by_name' => ['hasSideEffects' => false], + 'geoip_database_info' => ['hasSideEffects' => false], + 'geoip_db_avail' => ['hasSideEffects' => false], + 'geoip_db_filename' => ['hasSideEffects' => false], + 'geoip_db_get_all_info' => ['hasSideEffects' => false], + 'geoip_id_by_name' => ['hasSideEffects' => false], + 'geoip_isp_by_name' => ['hasSideEffects' => false], + 'geoip_org_by_name' => ['hasSideEffects' => false], + 'geoip_record_by_name' => ['hasSideEffects' => false], + 'geoip_region_by_name' => ['hasSideEffects' => false], + 'geoip_region_name_by_code' => ['hasSideEffects' => false], + 'geoip_time_zone_by_country_and_region' => ['hasSideEffects' => false], + 'get_browser' => ['hasSideEffects' => false], + 'get_called_class' => ['hasSideEffects' => false], + 'get_cfg_var' => ['hasSideEffects' => false], + 'get_class' => ['hasSideEffects' => false], + 'get_class_methods' => ['hasSideEffects' => false], + 'get_class_vars' => ['hasSideEffects' => false], + 'get_current_user' => ['hasSideEffects' => false], + 'get_debug_type' => ['hasSideEffects' => false], + 'get_declared_classes' => ['hasSideEffects' => false], + 'get_declared_interfaces' => ['hasSideEffects' => false], + 'get_declared_traits' => ['hasSideEffects' => false], + 'get_defined_constants' => ['hasSideEffects' => false], + 'get_defined_functions' => ['hasSideEffects' => false], + 'get_defined_vars' => ['hasSideEffects' => false], + 'get_extension_funcs' => ['hasSideEffects' => false], + 'get_headers' => ['hasSideEffects' => false], + 'get_html_translation_table' => ['hasSideEffects' => false], + 'get_include_path' => ['hasSideEffects' => false], + 'get_included_files' => ['hasSideEffects' => false], + 'get_loaded_extensions' => ['hasSideEffects' => false], + 'get_meta_tags' => ['hasSideEffects' => false], + 'get_object_vars' => ['hasSideEffects' => false], + 'get_parent_class' => ['hasSideEffects' => false], + 'get_required_files' => ['hasSideEffects' => false], + 'get_resource_id' => ['hasSideEffects' => false], + 'get_resources' => ['hasSideEffects' => false], + 'getallheaders' => ['hasSideEffects' => false], + 'getcwd' => ['hasSideEffects' => false], + 'getdate' => ['hasSideEffects' => false], + 'getenv' => ['hasSideEffects' => false], + 'gethostbyaddr' => ['hasSideEffects' => false], + 'gethostbyname' => ['hasSideEffects' => false], + 'gethostbynamel' => ['hasSideEffects' => false], + 'gethostname' => ['hasSideEffects' => false], + 'getlastmod' => ['hasSideEffects' => false], + 'getmygid' => ['hasSideEffects' => false], + 'getmyinode' => ['hasSideEffects' => false], + 'getmypid' => ['hasSideEffects' => false], + 'getmyuid' => ['hasSideEffects' => false], + 'getprotobyname' => ['hasSideEffects' => false], + 'getprotobynumber' => ['hasSideEffects' => false], + 'getrandmax' => ['hasSideEffects' => false], + 'getrusage' => ['hasSideEffects' => false], + 'getservbyname' => ['hasSideEffects' => false], + 'getservbyport' => ['hasSideEffects' => false], + 'gettext' => ['hasSideEffects' => false], + 'gettimeofday' => ['hasSideEffects' => false], + 'gettype' => ['hasSideEffects' => false], + 'glob' => ['hasSideEffects' => false], + 'gmdate' => ['hasSideEffects' => false], + 'gmmktime' => ['hasSideEffects' => false], + 'gmp_abs' => ['hasSideEffects' => false], + 'gmp_add' => ['hasSideEffects' => false], + 'gmp_and' => ['hasSideEffects' => false], + 'gmp_binomial' => ['hasSideEffects' => false], + 'gmp_cmp' => ['hasSideEffects' => false], + 'gmp_com' => ['hasSideEffects' => false], + 'gmp_div' => ['hasSideEffects' => false], + 'gmp_div_q' => ['hasSideEffects' => false], + 'gmp_div_qr' => ['hasSideEffects' => false], + 'gmp_div_r' => ['hasSideEffects' => false], + 'gmp_divexact' => ['hasSideEffects' => false], + 'gmp_export' => ['hasSideEffects' => false], + 'gmp_fact' => ['hasSideEffects' => false], + 'gmp_gcd' => ['hasSideEffects' => false], + 'gmp_gcdext' => ['hasSideEffects' => false], + 'gmp_hamdist' => ['hasSideEffects' => false], + 'gmp_import' => ['hasSideEffects' => false], + 'gmp_init' => ['hasSideEffects' => false], + 'gmp_intval' => ['hasSideEffects' => false], + 'gmp_invert' => ['hasSideEffects' => false], + 'gmp_jacobi' => ['hasSideEffects' => false], + 'gmp_kronecker' => ['hasSideEffects' => false], + 'gmp_lcm' => ['hasSideEffects' => false], + 'gmp_legendre' => ['hasSideEffects' => false], + 'gmp_mod' => ['hasSideEffects' => false], + 'gmp_mul' => ['hasSideEffects' => false], + 'gmp_neg' => ['hasSideEffects' => false], + 'gmp_nextprime' => ['hasSideEffects' => false], + 'gmp_or' => ['hasSideEffects' => false], + 'gmp_perfect_power' => ['hasSideEffects' => false], + 'gmp_perfect_square' => ['hasSideEffects' => false], + 'gmp_popcount' => ['hasSideEffects' => false], + 'gmp_pow' => ['hasSideEffects' => false], + 'gmp_powm' => ['hasSideEffects' => false], + 'gmp_prob_prime' => ['hasSideEffects' => false], + 'gmp_root' => ['hasSideEffects' => false], + 'gmp_rootrem' => ['hasSideEffects' => false], + 'gmp_scan0' => ['hasSideEffects' => false], + 'gmp_scan1' => ['hasSideEffects' => false], + 'gmp_sign' => ['hasSideEffects' => false], + 'gmp_sqrt' => ['hasSideEffects' => false], + 'gmp_sqrtrem' => ['hasSideEffects' => false], + 'gmp_strval' => ['hasSideEffects' => false], + 'gmp_sub' => ['hasSideEffects' => false], + 'gmp_testbit' => ['hasSideEffects' => false], + 'gmp_xor' => ['hasSideEffects' => false], + 'grapheme_stripos' => ['hasSideEffects' => false], + 'grapheme_stristr' => ['hasSideEffects' => false], + 'grapheme_strlen' => ['hasSideEffects' => false], + 'grapheme_strpos' => ['hasSideEffects' => false], + 'grapheme_strripos' => ['hasSideEffects' => false], + 'grapheme_strrpos' => ['hasSideEffects' => false], + 'grapheme_strstr' => ['hasSideEffects' => false], + 'grapheme_substr' => ['hasSideEffects' => false], + 'gzcompress' => ['hasSideEffects' => false], + 'gzdecode' => ['hasSideEffects' => false], + 'gzdeflate' => ['hasSideEffects' => false], + 'gzencode' => ['hasSideEffects' => false], + 'gzinflate' => ['hasSideEffects' => false], + 'gzuncompress' => ['hasSideEffects' => false], + 'hash' => ['hasSideEffects' => false], + 'hash_algos' => ['hasSideEffects' => false], + 'hash_copy' => ['hasSideEffects' => false], + 'hash_equals' => ['hasSideEffects' => false], + 'hash_file' => ['hasSideEffects' => false], + 'hash_hkdf' => ['hasSideEffects' => false], + 'hash_hmac' => ['hasSideEffects' => false], + 'hash_hmac_algos' => ['hasSideEffects' => false], + 'hash_hmac_file' => ['hasSideEffects' => false], + 'hash_init' => ['hasSideEffects' => false], + 'hash_pbkdf2' => ['hasSideEffects' => false], + 'headers_list' => ['hasSideEffects' => false], + 'hebrev' => ['hasSideEffects' => false], + 'hexdec' => ['hasSideEffects' => false], + 'hrtime' => ['hasSideEffects' => false], + 'html_entity_decode' => ['hasSideEffects' => false], + 'htmlentities' => ['hasSideEffects' => false], + 'htmlspecialchars' => ['hasSideEffects' => false], + 'htmlspecialchars_decode' => ['hasSideEffects' => false], + 'http_build_cookie' => ['hasSideEffects' => false], + 'http_build_query' => ['hasSideEffects' => false], + 'http_build_str' => ['hasSideEffects' => false], + 'http_cache_etag' => ['hasSideEffects' => false], + 'http_cache_last_modified' => ['hasSideEffects' => false], + 'http_chunked_decode' => ['hasSideEffects' => false], + 'http_date' => ['hasSideEffects' => false], + 'http_deflate' => ['hasSideEffects' => false], + 'http_get_request_body' => ['hasSideEffects' => false], + 'http_get_request_body_stream' => ['hasSideEffects' => false], + 'http_get_request_headers' => ['hasSideEffects' => false], + 'http_inflate' => ['hasSideEffects' => false], + 'http_match_etag' => ['hasSideEffects' => false], + 'http_match_modified' => ['hasSideEffects' => false], + 'http_match_request_header' => ['hasSideEffects' => false], + 'http_parse_cookie' => ['hasSideEffects' => false], + 'http_parse_headers' => ['hasSideEffects' => false], + 'http_parse_message' => ['hasSideEffects' => false], + 'http_parse_params' => ['hasSideEffects' => false], + 'http_request_body_encode' => ['hasSideEffects' => false], + 'http_request_method_exists' => ['hasSideEffects' => false], + 'http_request_method_name' => ['hasSideEffects' => false], + 'http_support' => ['hasSideEffects' => false], + 'hypot' => ['hasSideEffects' => false], + 'iconv' => ['hasSideEffects' => false], + 'iconv_get_encoding' => ['hasSideEffects' => false], + 'iconv_mime_decode' => ['hasSideEffects' => false], + 'iconv_mime_decode_headers' => ['hasSideEffects' => false], + 'iconv_mime_encode' => ['hasSideEffects' => false], + 'iconv_strlen' => ['hasSideEffects' => false], + 'iconv_strpos' => ['hasSideEffects' => false], + 'iconv_strrpos' => ['hasSideEffects' => false], + 'iconv_substr' => ['hasSideEffects' => false], + 'idate' => ['hasSideEffects' => false], + 'image_type_to_extension' => ['hasSideEffects' => false], + 'image_type_to_mime_type' => ['hasSideEffects' => false], + 'imagecolorat' => ['hasSideEffects' => false], + 'imagecolorclosest' => ['hasSideEffects' => false], + 'imagecolorclosestalpha' => ['hasSideEffects' => false], + 'imagecolorclosesthwb' => ['hasSideEffects' => false], + 'imagecolorexact' => ['hasSideEffects' => false], + 'imagecolorexactalpha' => ['hasSideEffects' => false], + 'imagecolorresolve' => ['hasSideEffects' => false], + 'imagecolorresolvealpha' => ['hasSideEffects' => false], + 'imagecolorsforindex' => ['hasSideEffects' => false], + 'imagecolorstotal' => ['hasSideEffects' => false], + 'imagecreate' => ['hasSideEffects' => false], + 'imagecreatefromstring' => ['hasSideEffects' => false], + 'imagecreatetruecolor' => ['hasSideEffects' => false], + 'imagefontheight' => ['hasSideEffects' => false], + 'imagefontwidth' => ['hasSideEffects' => false], + 'imageftbbox' => ['hasSideEffects' => false], + 'imagegetinterpolation' => ['hasSideEffects' => false], + 'imagegrabscreen' => ['hasSideEffects' => false], + 'imagegrabwindow' => ['hasSideEffects' => false], + 'imageistruecolor' => ['hasSideEffects' => false], + 'imagesx' => ['hasSideEffects' => false], + 'imagesy' => ['hasSideEffects' => false], + 'imagettfbbox' => ['hasSideEffects' => false], + 'imagetypes' => ['hasSideEffects' => false], + 'implode' => ['hasSideEffects' => false], + 'in_array' => ['hasSideEffects' => false], + 'inet_ntop' => ['hasSideEffects' => false], + 'inet_pton' => ['hasSideEffects' => false], + 'inflate_get_read_len' => ['hasSideEffects' => false], + 'inflate_get_status' => ['hasSideEffects' => false], + 'inflate_init' => ['hasSideEffects' => false], + 'ini_get' => ['hasSideEffects' => false], + 'ini_get_all' => ['hasSideEffects' => false], + 'intcal_get_maximum' => ['hasSideEffects' => false], + 'intdiv' => ['hasSideEffects' => false], + 'intl_error_name' => ['hasSideEffects' => false], + 'intl_get' => ['hasSideEffects' => false], + 'intl_get_error_code' => ['hasSideEffects' => false], + 'intl_get_error_message' => ['hasSideEffects' => false], + 'intl_is_failure' => ['hasSideEffects' => false], + 'intlcal_after' => ['hasSideEffects' => false], + 'intlcal_before' => ['hasSideEffects' => false], + 'intlcal_create_instance' => ['hasSideEffects' => false], + 'intlcal_equals' => ['hasSideEffects' => false], + 'intlcal_field_difference' => ['hasSideEffects' => false], + 'intlcal_from_date_time' => ['hasSideEffects' => false], + 'intlcal_get' => ['hasSideEffects' => false], + 'intlcal_get_actual_maximum' => ['hasSideEffects' => false], + 'intlcal_get_actual_minimum' => ['hasSideEffects' => false], + 'intlcal_get_available_locales' => ['hasSideEffects' => false], + 'intlcal_get_day_of_week_type' => ['hasSideEffects' => false], + 'intlcal_get_error_code' => ['hasSideEffects' => false], + 'intlcal_get_error_message' => ['hasSideEffects' => false], + 'intlcal_get_first_day_of_week' => ['hasSideEffects' => false], + 'intlcal_get_greatest_minimum' => ['hasSideEffects' => false], + 'intlcal_get_keyword_values_for_locale' => ['hasSideEffects' => false], + 'intlcal_get_least_maximum' => ['hasSideEffects' => false], + 'intlcal_get_locale' => ['hasSideEffects' => false], + 'intlcal_get_maximum' => ['hasSideEffects' => false], + 'intlcal_get_minimal_days_in_first_week' => ['hasSideEffects' => false], + 'intlcal_get_minimum' => ['hasSideEffects' => false], + 'intlcal_get_now' => ['hasSideEffects' => false], + 'intlcal_get_repeated_wall_time_option' => ['hasSideEffects' => false], + 'intlcal_get_skipped_wall_time_option' => ['hasSideEffects' => false], + 'intlcal_get_time' => ['hasSideEffects' => false], + 'intlcal_get_time_zone' => ['hasSideEffects' => false], + 'intlcal_get_type' => ['hasSideEffects' => false], + 'intlcal_get_weekend_transition' => ['hasSideEffects' => false], + 'intlcal_greates_minimum' => ['hasSideEffects' => false], + 'intlcal_in_daylight_time' => ['hasSideEffects' => false], + 'intlcal_is_equivalent_to' => ['hasSideEffects' => false], + 'intlcal_is_lenient' => ['hasSideEffects' => false], + 'intlcal_is_set' => ['hasSideEffects' => false], + 'intlcal_is_weekend' => ['hasSideEffects' => false], + 'intlcal_to_date_time' => ['hasSideEffects' => false], + 'intlgregcal_create_instance' => ['hasSideEffects' => false], + 'intlgregcal_get_gregorian_change' => ['hasSideEffects' => false], + 'intlgregcal_is_leap_year' => ['hasSideEffects' => false], + 'intltz_count_equivalent_ids' => ['hasSideEffects' => false], + 'intltz_create_default' => ['hasSideEffects' => false], + 'intltz_create_enumeration' => ['hasSideEffects' => false], + 'intltz_create_time_zone' => ['hasSideEffects' => false], + 'intltz_create_time_zone_id_enumeration' => ['hasSideEffects' => false], + 'intltz_from_date_time_zone' => ['hasSideEffects' => false], + 'intltz_get_canonical_id' => ['hasSideEffects' => false], + 'intltz_get_display_name' => ['hasSideEffects' => false], + 'intltz_get_dst_savings' => ['hasSideEffects' => false], + 'intltz_get_equivalent_id' => ['hasSideEffects' => false], + 'intltz_get_error_code' => ['hasSideEffects' => false], + 'intltz_get_error_message' => ['hasSideEffects' => false], + 'intltz_get_gmt' => ['hasSideEffects' => false], + 'intltz_get_id' => ['hasSideEffects' => false], + 'intltz_get_offset' => ['hasSideEffects' => false], + 'intltz_get_raw_offset' => ['hasSideEffects' => false], + 'intltz_get_region' => ['hasSideEffects' => false], + 'intltz_get_tz_data_version' => ['hasSideEffects' => false], + 'intltz_get_unknown' => ['hasSideEffects' => false], + 'intltz_getgmt' => ['hasSideEffects' => false], + 'intltz_has_same_rules' => ['hasSideEffects' => false], + 'intltz_to_date_time_zone' => ['hasSideEffects' => false], + 'intltz_use_daylight_time' => ['hasSideEffects' => false], + 'intlz_create_default' => ['hasSideEffects' => false], + 'intval' => ['hasSideEffects' => false], + 'ip2long' => ['hasSideEffects' => false], + 'iptcparse' => ['hasSideEffects' => false], + 'is_a' => ['hasSideEffects' => false], + 'is_array' => ['hasSideEffects' => false], + 'is_bool' => ['hasSideEffects' => false], + 'is_countable' => ['hasSideEffects' => false], + 'is_dir' => ['hasSideEffects' => false], + 'is_double' => ['hasSideEffects' => false], + 'is_executable' => ['hasSideEffects' => false], + 'is_file' => ['hasSideEffects' => false], + 'is_finite' => ['hasSideEffects' => false], + 'is_float' => ['hasSideEffects' => false], + 'is_infinite' => ['hasSideEffects' => false], + 'is_int' => ['hasSideEffects' => false], + 'is_integer' => ['hasSideEffects' => false], + 'is_iterable' => ['hasSideEffects' => false], + 'is_link' => ['hasSideEffects' => false], + 'is_long' => ['hasSideEffects' => false], + 'is_nan' => ['hasSideEffects' => false], + 'is_null' => ['hasSideEffects' => false], + 'is_numeric' => ['hasSideEffects' => false], + 'is_object' => ['hasSideEffects' => false], + 'is_readable' => ['hasSideEffects' => false], + 'is_real' => ['hasSideEffects' => false], + 'is_resource' => ['hasSideEffects' => false], + 'is_scalar' => ['hasSideEffects' => false], + 'is_string' => ['hasSideEffects' => false], + 'is_subclass_of' => ['hasSideEffects' => false], + 'is_uploaded_file' => ['hasSideEffects' => false], + 'is_writable' => ['hasSideEffects' => false], + 'is_writeable' => ['hasSideEffects' => false], + 'iterator_count' => ['hasSideEffects' => false], + 'join' => ['hasSideEffects' => false], + 'json_last_error' => ['hasSideEffects' => false], + 'json_last_error_msg' => ['hasSideEffects' => false], + 'json_validate' => ['hasSideEffects' => false], + 'key' => ['hasSideEffects' => false], + 'key_exists' => ['hasSideEffects' => false], + 'lcfirst' => ['hasSideEffects' => false], + 'lchgrp' => ['hasSideEffects' => true], + 'lchown' => ['hasSideEffects' => true], + 'libxml_get_errors' => ['hasSideEffects' => false], + 'libxml_get_last_error' => ['hasSideEffects' => false], + 'link' => ['hasSideEffects' => true], + 'linkinfo' => ['hasSideEffects' => false], + 'locale_accept_from_http' => ['hasSideEffects' => false], + 'locale_canonicalize' => ['hasSideEffects' => false], + 'locale_compose' => ['hasSideEffects' => false], + 'locale_filter_matches' => ['hasSideEffects' => false], + 'locale_get_all_variants' => ['hasSideEffects' => false], + 'locale_get_default' => ['hasSideEffects' => false], + 'locale_get_display_language' => ['hasSideEffects' => false], + 'locale_get_display_name' => ['hasSideEffects' => false], + 'locale_get_display_region' => ['hasSideEffects' => false], + 'locale_get_display_script' => ['hasSideEffects' => false], + 'locale_get_display_variant' => ['hasSideEffects' => false], + 'locale_get_keywords' => ['hasSideEffects' => false], + 'locale_get_primary_language' => ['hasSideEffects' => false], + 'locale_get_region' => ['hasSideEffects' => false], + 'locale_get_script' => ['hasSideEffects' => false], + 'locale_lookup' => ['hasSideEffects' => false], + 'locale_parse' => ['hasSideEffects' => false], + 'localeconv' => ['hasSideEffects' => false], + 'localtime' => ['hasSideEffects' => false], + 'log' => ['hasSideEffects' => false], + 'log10' => ['hasSideEffects' => false], + 'log1p' => ['hasSideEffects' => false], + 'long2ip' => ['hasSideEffects' => false], + 'lstat' => ['hasSideEffects' => false], + 'ltrim' => ['hasSideEffects' => false], + 'max' => ['hasSideEffects' => false], + 'mb_check_encoding' => ['hasSideEffects' => false], + 'mb_chr' => ['hasSideEffects' => false], + 'mb_convert_case' => ['hasSideEffects' => false], + 'mb_convert_encoding' => ['hasSideEffects' => false], + 'mb_convert_kana' => ['hasSideEffects' => false], + 'mb_decode_mimeheader' => ['hasSideEffects' => false], + 'mb_decode_numericentity' => ['hasSideEffects' => false], + 'mb_detect_encoding' => ['hasSideEffects' => false], + 'mb_encode_mimeheader' => ['hasSideEffects' => false], + 'mb_encode_numericentity' => ['hasSideEffects' => false], + 'mb_encoding_aliases' => ['hasSideEffects' => false], + 'mb_ereg_match' => ['hasSideEffects' => false], + 'mb_ereg_replace' => ['hasSideEffects' => false], + 'mb_ereg_search' => ['hasSideEffects' => false], + 'mb_ereg_search_getpos' => ['hasSideEffects' => false], + 'mb_ereg_search_getregs' => ['hasSideEffects' => false], + 'mb_ereg_search_pos' => ['hasSideEffects' => false], + 'mb_ereg_search_regs' => ['hasSideEffects' => false], + 'mb_ereg_search_setpos' => ['hasSideEffects' => false], + 'mb_eregi_replace' => ['hasSideEffects' => false], + 'mb_get_info' => ['hasSideEffects' => false], + 'mb_http_input' => ['hasSideEffects' => false], + 'mb_list_encodings' => ['hasSideEffects' => false], + 'mb_ord' => ['hasSideEffects' => false], + 'mb_output_handler' => ['hasSideEffects' => false], + 'mb_preferred_mime_name' => ['hasSideEffects' => false], + 'mb_scrub' => ['hasSideEffects' => false], + 'mb_split' => ['hasSideEffects' => false], + 'mb_str_pad' => ['hasSideEffects' => false], + 'mb_str_split' => ['hasSideEffects' => false], + 'mb_strcut' => ['hasSideEffects' => false], + 'mb_strimwidth' => ['hasSideEffects' => false], + 'mb_stripos' => ['hasSideEffects' => false], + 'mb_stristr' => ['hasSideEffects' => false], + 'mb_strlen' => ['hasSideEffects' => false], + 'mb_strpos' => ['hasSideEffects' => false], + 'mb_strrchr' => ['hasSideEffects' => false], + 'mb_strrichr' => ['hasSideEffects' => false], + 'mb_strripos' => ['hasSideEffects' => false], + 'mb_strrpos' => ['hasSideEffects' => false], + 'mb_strstr' => ['hasSideEffects' => false], + 'mb_strtolower' => ['hasSideEffects' => false], + 'mb_strtoupper' => ['hasSideEffects' => false], + 'mb_strwidth' => ['hasSideEffects' => false], + 'mb_substr' => ['hasSideEffects' => false], + 'mb_substr_count' => ['hasSideEffects' => false], + 'mbereg_search_setpos' => ['hasSideEffects' => false], + 'md5' => ['hasSideEffects' => false], + 'md5_file' => ['hasSideEffects' => false], + 'memory_get_peak_usage' => ['hasSideEffects' => false], + 'memory_get_usage' => ['hasSideEffects' => false], + 'metaphone' => ['hasSideEffects' => false], + 'method_exists' => ['hasSideEffects' => false], + 'mhash' => ['hasSideEffects' => false], + 'mhash_count' => ['hasSideEffects' => false], + 'mhash_get_block_size' => ['hasSideEffects' => false], + 'mhash_get_hash_name' => ['hasSideEffects' => false], + 'mhash_keygen_s2k' => ['hasSideEffects' => false], + 'microtime' => ['hasSideEffects' => false], + 'min' => ['hasSideEffects' => false], + 'mkdir' => ['hasSideEffects' => true], + 'mktime' => ['hasSideEffects' => false], + 'move_uploaded_file' => ['hasSideEffects' => true], + 'msgfmt_create' => ['hasSideEffects' => false], + 'msgfmt_format' => ['hasSideEffects' => false], + 'msgfmt_format_message' => ['hasSideEffects' => false], + 'msgfmt_get_error_code' => ['hasSideEffects' => false], + 'msgfmt_get_error_message' => ['hasSideEffects' => false], + 'msgfmt_get_locale' => ['hasSideEffects' => false], + 'msgfmt_get_pattern' => ['hasSideEffects' => false], + 'msgfmt_parse' => ['hasSideEffects' => false], + 'msgfmt_parse_message' => ['hasSideEffects' => false], + 'mt_getrandmax' => ['hasSideEffects' => false], + 'mt_rand' => ['hasSideEffects' => true], + 'net_get_interfaces' => ['hasSideEffects' => false], + 'ngettext' => ['hasSideEffects' => false], + 'nl2br' => ['hasSideEffects' => false], + 'nl_langinfo' => ['hasSideEffects' => false], + 'normalizer_get_raw_decomposition' => ['hasSideEffects' => false], + 'normalizer_is_normalized' => ['hasSideEffects' => false], + 'normalizer_normalize' => ['hasSideEffects' => false], + 'number_format' => ['hasSideEffects' => false], + 'numfmt_create' => ['hasSideEffects' => false], + 'numfmt_format' => ['hasSideEffects' => false], + 'numfmt_format_currency' => ['hasSideEffects' => false], + 'numfmt_get_attribute' => ['hasSideEffects' => false], + 'numfmt_get_error_code' => ['hasSideEffects' => false], + 'numfmt_get_error_message' => ['hasSideEffects' => false], + 'numfmt_get_locale' => ['hasSideEffects' => false], + 'numfmt_get_pattern' => ['hasSideEffects' => false], + 'numfmt_get_symbol' => ['hasSideEffects' => false], + 'numfmt_get_text_attribute' => ['hasSideEffects' => false], + 'numfmt_parse' => ['hasSideEffects' => false], + 'ob_etaghandler' => ['hasSideEffects' => false], + 'ob_get_contents' => ['hasSideEffects' => false], + 'ob_iconv_handler' => ['hasSideEffects' => false], + 'octdec' => ['hasSideEffects' => false], + 'ord' => ['hasSideEffects' => false], + 'pack' => ['hasSideEffects' => false], + 'pam_auth' => ['hasSideEffects' => false], + 'pam_chpass' => ['hasSideEffects' => false], + 'parse_ini_file' => ['hasSideEffects' => false], + 'parse_ini_string' => ['hasSideEffects' => false], + 'parse_url' => ['hasSideEffects' => false], + 'pathinfo' => ['hasSideEffects' => false], + 'pclose' => ['hasSideEffects' => true], + 'pcntl_errno' => ['hasSideEffects' => false], + 'pcntl_get_last_error' => ['hasSideEffects' => false], + 'pcntl_getpriority' => ['hasSideEffects' => false], + 'pcntl_strerror' => ['hasSideEffects' => false], + 'pcntl_wexitstatus' => ['hasSideEffects' => false], + 'pcntl_wifcontinued' => ['hasSideEffects' => false], + 'pcntl_wifexited' => ['hasSideEffects' => false], + 'pcntl_wifsignaled' => ['hasSideEffects' => false], + 'pcntl_wifstopped' => ['hasSideEffects' => false], + 'pcntl_wstopsig' => ['hasSideEffects' => false], + 'pcntl_wtermsig' => ['hasSideEffects' => false], + 'pdo_drivers' => ['hasSideEffects' => false], + 'php_ini_loaded_file' => ['hasSideEffects' => false], + 'php_ini_scanned_files' => ['hasSideEffects' => false], + 'php_logo_guid' => ['hasSideEffects' => false], + 'php_sapi_name' => ['hasSideEffects' => false], + 'php_strip_whitespace' => ['hasSideEffects' => false], + 'php_uname' => ['hasSideEffects' => false], + 'phpversion' => ['hasSideEffects' => false], + 'pi' => ['hasSideEffects' => false], + 'popen' => ['hasSideEffects' => true], + 'pos' => ['hasSideEffects' => false], + 'posix_ctermid' => ['hasSideEffects' => false], + 'posix_errno' => ['hasSideEffects' => false], + 'posix_get_last_error' => ['hasSideEffects' => false], + 'posix_getcwd' => ['hasSideEffects' => false], + 'posix_getegid' => ['hasSideEffects' => false], + 'posix_geteuid' => ['hasSideEffects' => false], + 'posix_getgid' => ['hasSideEffects' => false], + 'posix_getgrgid' => ['hasSideEffects' => false], + 'posix_getgrnam' => ['hasSideEffects' => false], + 'posix_getgroups' => ['hasSideEffects' => false], + 'posix_getlogin' => ['hasSideEffects' => false], + 'posix_getpgid' => ['hasSideEffects' => false], + 'posix_getpgrp' => ['hasSideEffects' => false], + 'posix_getpid' => ['hasSideEffects' => false], + 'posix_getppid' => ['hasSideEffects' => false], + 'posix_getpwnam' => ['hasSideEffects' => false], + 'posix_getpwuid' => ['hasSideEffects' => false], + 'posix_getrlimit' => ['hasSideEffects' => false], + 'posix_getsid' => ['hasSideEffects' => false], + 'posix_getuid' => ['hasSideEffects' => false], + 'posix_initgroups' => ['hasSideEffects' => false], + 'posix_isatty' => ['hasSideEffects' => false], + 'posix_strerror' => ['hasSideEffects' => false], + 'posix_times' => ['hasSideEffects' => false], + 'posix_ttyname' => ['hasSideEffects' => false], + 'posix_uname' => ['hasSideEffects' => false], + 'pow' => ['hasSideEffects' => false], + 'preg_grep' => ['hasSideEffects' => false], + 'preg_last_error' => ['hasSideEffects' => false], + 'preg_last_error_msg' => ['hasSideEffects' => false], + 'preg_quote' => ['hasSideEffects' => false], + 'preg_split' => ['hasSideEffects' => false], + 'property_exists' => ['hasSideEffects' => false], + 'quoted_printable_decode' => ['hasSideEffects' => false], + 'quoted_printable_encode' => ['hasSideEffects' => false], + 'quotemeta' => ['hasSideEffects' => false], + 'rad2deg' => ['hasSideEffects' => false], + 'rand' => ['hasSideEffects' => true], + 'random_bytes' => ['hasSideEffects' => true], + 'random_int' => ['hasSideEffects' => true], + 'range' => ['hasSideEffects' => false], + 'rawurldecode' => ['hasSideEffects' => false], + 'rawurlencode' => ['hasSideEffects' => false], + 'readfile' => ['hasSideEffects' => true], + 'readlink' => ['hasSideEffects' => false], + 'realpath' => ['hasSideEffects' => false], + 'realpath_cache_get' => ['hasSideEffects' => false], + 'realpath_cache_size' => ['hasSideEffects' => false], + 'rename' => ['hasSideEffects' => true], + 'resourcebundle_count' => ['hasSideEffects' => false], + 'resourcebundle_create' => ['hasSideEffects' => false], + 'resourcebundle_get' => ['hasSideEffects' => false], + 'resourcebundle_get_error_code' => ['hasSideEffects' => false], + 'resourcebundle_get_error_message' => ['hasSideEffects' => false], + 'resourcebundle_locales' => ['hasSideEffects' => false], + 'rewind' => ['hasSideEffects' => true], + 'rmdir' => ['hasSideEffects' => true], + 'round' => ['hasSideEffects' => false], + 'rtrim' => ['hasSideEffects' => false], + 'sha1' => ['hasSideEffects' => false], + 'sha1_file' => ['hasSideEffects' => false], + 'sin' => ['hasSideEffects' => false], + 'sinh' => ['hasSideEffects' => false], + 'sizeof' => ['hasSideEffects' => false], + 'soundex' => ['hasSideEffects' => false], + 'spl_classes' => ['hasSideEffects' => false], + 'spl_object_hash' => ['hasSideEffects' => false], + 'sprintf' => ['hasSideEffects' => false], + 'sqrt' => ['hasSideEffects' => false], + 'stat' => ['hasSideEffects' => false], + 'str_contains' => ['hasSideEffects' => false], + 'str_decrement' => ['hasSideEffects' => false], + 'str_ends_with' => ['hasSideEffects' => false], + 'str_getcsv' => ['hasSideEffects' => false], + 'str_increment' => ['hasSideEffects' => false], + 'str_pad' => ['hasSideEffects' => false], + 'str_repeat' => ['hasSideEffects' => false], + 'str_rot13' => ['hasSideEffects' => false], + 'str_split' => ['hasSideEffects' => false], + 'str_starts_with' => ['hasSideEffects' => false], + 'str_word_count' => ['hasSideEffects' => false], + 'strcasecmp' => ['hasSideEffects' => false], + 'strchr' => ['hasSideEffects' => false], + 'strcmp' => ['hasSideEffects' => false], + 'strcoll' => ['hasSideEffects' => false], + 'strcspn' => ['hasSideEffects' => false], + 'stream_get_filters' => ['hasSideEffects' => false], + 'stream_get_transports' => ['hasSideEffects' => false], + 'stream_get_wrappers' => ['hasSideEffects' => false], + 'stream_is_local' => ['hasSideEffects' => false], + 'stream_isatty' => ['hasSideEffects' => false], + 'strip_tags' => ['hasSideEffects' => false], + 'stripcslashes' => ['hasSideEffects' => false], + 'stripos' => ['hasSideEffects' => false], + 'stripslashes' => ['hasSideEffects' => false], + 'stristr' => ['hasSideEffects' => false], + 'strlen' => ['hasSideEffects' => false], + 'strnatcasecmp' => ['hasSideEffects' => false], + 'strnatcmp' => ['hasSideEffects' => false], + 'strncasecmp' => ['hasSideEffects' => false], + 'strncmp' => ['hasSideEffects' => false], + 'strpbrk' => ['hasSideEffects' => false], + 'strpos' => ['hasSideEffects' => false], + 'strptime' => ['hasSideEffects' => false], + 'strrchr' => ['hasSideEffects' => false], + 'strrev' => ['hasSideEffects' => false], + 'strripos' => ['hasSideEffects' => false], + 'strrpos' => ['hasSideEffects' => false], + 'strspn' => ['hasSideEffects' => false], + 'strstr' => ['hasSideEffects' => false], + 'strtolower' => ['hasSideEffects' => false], + 'strtotime' => ['hasSideEffects' => false], + 'strtoupper' => ['hasSideEffects' => false], + 'strtr' => ['hasSideEffects' => false], + 'strval' => ['hasSideEffects' => false], + 'substr' => ['hasSideEffects' => false], + 'substr_compare' => ['hasSideEffects' => false], + 'substr_count' => ['hasSideEffects' => false], + 'substr_replace' => ['hasSideEffects' => false], + 'symlink' => ['hasSideEffects' => true], + 'sys_getloadavg' => ['hasSideEffects' => false], + 'tan' => ['hasSideEffects' => false], + 'tanh' => ['hasSideEffects' => false], + 'tempnam' => ['hasSideEffects' => true], + 'timezone_abbreviations_list' => ['hasSideEffects' => false], + 'timezone_identifiers_list' => ['hasSideEffects' => false], + 'timezone_location_get' => ['hasSideEffects' => false], + 'timezone_name_from_abbr' => ['hasSideEffects' => false], + 'timezone_name_get' => ['hasSideEffects' => false], + 'timezone_offset_get' => ['hasSideEffects' => false], + 'timezone_open' => ['hasSideEffects' => false], + 'timezone_transitions_get' => ['hasSideEffects' => false], + 'timezone_version_get' => ['hasSideEffects' => false], + 'tmpfile' => ['hasSideEffects' => true], + 'token_get_all' => ['hasSideEffects' => false], + 'token_name' => ['hasSideEffects' => false], + 'touch' => ['hasSideEffects' => true], + 'transliterator_create' => ['hasSideEffects' => false], + 'transliterator_create_from_rules' => ['hasSideEffects' => false], + 'transliterator_create_inverse' => ['hasSideEffects' => false], + 'transliterator_get_error_code' => ['hasSideEffects' => false], + 'transliterator_get_error_message' => ['hasSideEffects' => false], + 'transliterator_list_ids' => ['hasSideEffects' => false], + 'transliterator_transliterate' => ['hasSideEffects' => false], + 'trim' => ['hasSideEffects' => false], + 'ucfirst' => ['hasSideEffects' => false], + 'ucwords' => ['hasSideEffects' => false], + 'umask' => ['hasSideEffects' => true], + 'unlink' => ['hasSideEffects' => true], + 'unpack' => ['hasSideEffects' => false], + 'urldecode' => ['hasSideEffects' => false], + 'urlencode' => ['hasSideEffects' => false], + 'utf8_decode' => ['hasSideEffects' => false], + 'utf8_encode' => ['hasSideEffects' => false], + 'vsprintf' => ['hasSideEffects' => false], + 'wordwrap' => ['hasSideEffects' => false], + 'xml_error_string' => ['hasSideEffects' => false], + 'xml_get_current_byte_index' => ['hasSideEffects' => false], + 'xml_get_current_column_number' => ['hasSideEffects' => false], + 'xml_get_current_line_number' => ['hasSideEffects' => false], + 'xml_get_error_code' => ['hasSideEffects' => false], + 'xml_parser_create' => ['hasSideEffects' => false], + 'xml_parser_create_ns' => ['hasSideEffects' => false], + 'xml_parser_get_option' => ['hasSideEffects' => false], + 'zend_version' => ['hasSideEffects' => false], + 'zlib_decode' => ['hasSideEffects' => false], + 'zlib_encode' => ['hasSideEffects' => false], + 'zlib_get_coding_type' => ['hasSideEffects' => false], + +]; diff --git a/app/vendor/symfony/config/Builder/ClassBuilder.php b/app/vendor/symfony/config/Builder/ClassBuilder.php index c4fd9bc0f..5ae8bda16 100644 --- a/app/vendor/symfony/config/Builder/ClassBuilder.php +++ b/app/vendor/symfony/config/Builder/ClassBuilder.php @@ -20,7 +20,6 @@ */ class ClassBuilder { - private string $namespace; private string $name; /** @var Property[] */ @@ -33,9 +32,10 @@ class ClassBuilder private array $implements = []; private bool $allowExtraKeys = false; - public function __construct(string $namespace, string $name) - { - $this->namespace = $namespace; + public function __construct( + private string $namespace, + string $name, + ) { $this->name = ucfirst($this->camelCase($name)).'Config'; } @@ -82,7 +82,7 @@ public function build(): string } } - $content = strtr(' $this->namespace, 'REQUIRE' => $require, 'USE' => $use, 'CLASS' => $this->getName(), 'IMPLEMENTS' => $implements, 'BODY' => $body]); - - return $content; } public function addRequire(self $class): void diff --git a/app/vendor/symfony/config/Builder/ConfigBuilderGenerator.php b/app/vendor/symfony/config/Builder/ConfigBuilderGenerator.php index cfd2810f7..d76899016 100644 --- a/app/vendor/symfony/config/Builder/ConfigBuilderGenerator.php +++ b/app/vendor/symfony/config/Builder/ConfigBuilderGenerator.php @@ -37,11 +37,10 @@ class ConfigBuilderGenerator implements ConfigBuilderGeneratorInterface * @var ClassBuilder[] */ private array $classes = []; - private string $outputDir; - public function __construct(string $outputDir) - { - $this->outputDir = $outputDir; + public function __construct( + private string $outputDir, + ) { } /** diff --git a/app/vendor/symfony/config/Builder/Method.php b/app/vendor/symfony/config/Builder/Method.php index c97c98649..8fba068a9 100644 --- a/app/vendor/symfony/config/Builder/Method.php +++ b/app/vendor/symfony/config/Builder/Method.php @@ -20,11 +20,9 @@ */ class Method { - private string $content; - - public function __construct(string $content) - { - $this->content = $content; + public function __construct( + private string $content, + ) { } public function getContent(): string diff --git a/app/vendor/symfony/config/Builder/Property.php b/app/vendor/symfony/config/Builder/Property.php index cf2f8d549..ede5351c7 100644 --- a/app/vendor/symfony/config/Builder/Property.php +++ b/app/vendor/symfony/config/Builder/Property.php @@ -20,17 +20,15 @@ */ class Property { - private string $name; - private string $originalName; private bool $array = false; private bool $scalarsAllowed = false; private ?string $type = null; private ?string $content = null; - public function __construct(string $originalName, string $name) - { - $this->name = $name; - $this->originalName = $originalName; + public function __construct( + private string $originalName, + private string $name, + ) { } public function getName(): string diff --git a/app/vendor/symfony/config/CHANGELOG.md b/app/vendor/symfony/config/CHANGELOG.md index 094d5abba..6ee63f82c 100644 --- a/app/vendor/symfony/config/CHANGELOG.md +++ b/app/vendor/symfony/config/CHANGELOG.md @@ -1,6 +1,36 @@ CHANGELOG ========= +7.3 +--- + + * Add `ExprBuilder::ifFalse()` + * Add support for info on `ArrayNodeDefinition::canBeEnabled()` and `ArrayNodeDefinition::canBeDisabled()` + * Allow using an enum FQCN with `EnumNode` + * Add `NodeDefinition::docUrl()` + +7.2 +--- + + * Add `#[WhenNot]` attribute to prevent service from being registered in a specific environment + * Generate a meta file in JSON format for resource tracking + * Add `SkippingResourceChecker` + * Add support for `defaultNull()` on `BooleanNode` + * Add `StringNode` and `StringNodeDefinition` + * Add `ArrayNodeDefinition::stringPrototype()` method + * Add `NodeBuilder::stringNode()` method + +7.1 +--- + + * Allow custom meta location in `ResourceCheckerConfigCache` + * Allow custom meta location in `ConfigCache` + +7.0 +--- + + * Require explicit argument when calling `NodeBuilder::setParent()` + 6.3 --- diff --git a/app/vendor/symfony/config/ConfigCache.php b/app/vendor/symfony/config/ConfigCache.php index 89519417d..cee286f48 100644 --- a/app/vendor/symfony/config/ConfigCache.php +++ b/app/vendor/symfony/config/ConfigCache.php @@ -11,7 +11,9 @@ namespace Symfony\Component\Config; +use Symfony\Component\Config\Resource\ResourceInterface; use Symfony\Component\Config\Resource\SelfCheckingResourceChecker; +use Symfony\Component\Config\Resource\SkippingResourceChecker; /** * ConfigCache caches arbitrary content in files on disk. @@ -25,22 +27,27 @@ */ class ConfigCache extends ResourceCheckerConfigCache { - private bool $debug; - /** - * @param string $file The absolute cache path - * @param bool $debug Whether debugging is enabled or not + * @param string $file The absolute cache path + * @param bool $debug Whether debugging is enabled or not + * @param string|null $metaFile The absolute path to the meta file + * @param class-string[]|null $skippedResourceTypes */ - public function __construct(string $file, bool $debug) - { - $this->debug = $debug; - + public function __construct( + string $file, + private bool $debug, + ?string $metaFile = null, + ?array $skippedResourceTypes = null, + ) { $checkers = []; - if (true === $this->debug) { - $checkers = [new SelfCheckingResourceChecker()]; + if ($this->debug) { + if (null !== $skippedResourceTypes) { + $checkers[] = new SkippingResourceChecker($skippedResourceTypes); + } + $checkers[] = new SelfCheckingResourceChecker(); } - parent::__construct($file, $checkers); + parent::__construct($file, $checkers, $metaFile); } /** diff --git a/app/vendor/symfony/config/ConfigCacheFactory.php b/app/vendor/symfony/config/ConfigCacheFactory.php index 39adad1e1..27a18e751 100644 --- a/app/vendor/symfony/config/ConfigCacheFactory.php +++ b/app/vendor/symfony/config/ConfigCacheFactory.php @@ -22,14 +22,12 @@ */ class ConfigCacheFactory implements ConfigCacheFactoryInterface { - private bool $debug; - /** * @param bool $debug The debug flag to pass to ConfigCache */ - public function __construct(bool $debug) - { - $this->debug = $debug; + public function __construct( + private bool $debug, + ) { } public function cache(string $file, callable $callback): ConfigCacheInterface diff --git a/app/vendor/symfony/config/ConfigCacheInterface.php b/app/vendor/symfony/config/ConfigCacheInterface.php index f8d270634..7b9d38897 100644 --- a/app/vendor/symfony/config/ConfigCacheInterface.php +++ b/app/vendor/symfony/config/ConfigCacheInterface.php @@ -39,9 +39,7 @@ public function isFresh(): bool; * @param string $content The content to write into the cache * @param ResourceInterface[]|null $metadata An array of ResourceInterface instances * - * @return void - * * @throws \RuntimeException When the cache file cannot be written */ - public function write(string $content, ?array $metadata = null); + public function write(string $content, ?array $metadata = null): void; } diff --git a/app/vendor/symfony/config/Definition/ArrayNode.php b/app/vendor/symfony/config/Definition/ArrayNode.php index 44912da41..5301b7243 100644 --- a/app/vendor/symfony/config/Definition/ArrayNode.php +++ b/app/vendor/symfony/config/Definition/ArrayNode.php @@ -22,20 +22,17 @@ */ class ArrayNode extends BaseNode implements PrototypeNodeInterface { - protected $xmlRemappings = []; - protected $children = []; - protected $allowFalse = false; - protected $allowNewKeys = true; - protected $addIfNotSet = false; - protected $performDeepMerging = true; - protected $ignoreExtraKeys = false; - protected $removeExtraKeys = true; - protected $normalizeKeys = true; - - /** - * @return void - */ - public function setNormalizeKeys(bool $normalizeKeys) + protected array $xmlRemappings = []; + protected array $children = []; + protected bool $allowFalse = false; + protected bool $allowNewKeys = true; + protected bool $addIfNotSet = false; + protected bool $performDeepMerging = true; + protected bool $ignoreExtraKeys = false; + protected bool $removeExtraKeys = true; + protected bool $normalizeKeys = true; + + public function setNormalizeKeys(bool $normalizeKeys): void { $this->normalizeKeys = $normalizeKeys; } @@ -80,10 +77,8 @@ public function getChildren(): array * Sets the xml remappings that should be performed. * * @param array $remappings An array of the form [[string, string]] - * - * @return void */ - public function setXmlRemappings(array $remappings) + public function setXmlRemappings(array $remappings): void { $this->xmlRemappings = $remappings; } @@ -101,40 +96,32 @@ public function getXmlRemappings(): array /** * Sets whether to add default values for this array if it has not been * defined in any of the configuration files. - * - * @return void */ - public function setAddIfNotSet(bool $boolean) + public function setAddIfNotSet(bool $boolean): void { $this->addIfNotSet = $boolean; } /** * Sets whether false is allowed as value indicating that the array should be unset. - * - * @return void */ - public function setAllowFalse(bool $allow) + public function setAllowFalse(bool $allow): void { $this->allowFalse = $allow; } /** * Sets whether new keys can be defined in subsequent configurations. - * - * @return void */ - public function setAllowNewKeys(bool $allow) + public function setAllowNewKeys(bool $allow): void { $this->allowNewKeys = $allow; } /** * Sets if deep merging should occur. - * - * @return void */ - public function setPerformDeepMerging(bool $boolean) + public function setPerformDeepMerging(bool $boolean): void { $this->performDeepMerging = $boolean; } @@ -144,10 +131,8 @@ public function setPerformDeepMerging(bool $boolean) * * @param bool $boolean To allow extra keys * @param bool $remove To remove extra keys - * - * @return void */ - public function setIgnoreExtraKeys(bool $boolean, bool $remove = true) + public function setIgnoreExtraKeys(bool $boolean, bool $remove = true): void { $this->ignoreExtraKeys = $boolean; $this->removeExtraKeys = $this->ignoreExtraKeys && $remove; @@ -161,10 +146,7 @@ public function shouldIgnoreExtraKeys(): bool return $this->ignoreExtraKeys; } - /** - * @return void - */ - public function setName(string $name) + public function setName(string $name): void { $this->name = $name; } @@ -193,12 +175,10 @@ public function getDefaultValue(): mixed /** * Adds a child node. * - * @return void - * * @throws \InvalidArgumentException when the child node has no name * @throws \InvalidArgumentException when the child node's name is not unique */ - public function addChild(NodeInterface $node) + public function addChild(NodeInterface $node): void { $name = $node->getName(); if ('' === $name) { @@ -218,7 +198,7 @@ public function addChild(NodeInterface $node) protected function finalizeValue(mixed $value): mixed { if (false === $value) { - throw new UnsetKeyException(\sprintf('Unsetting key for path "%s", value: %s.', $this->getPath(), json_encode($value))); + throw new UnsetKeyException(\sprintf('Unsetting key for path "%s", value: false.', $this->getPath())); } foreach ($this->children as $name => $child) { @@ -258,10 +238,7 @@ protected function finalizeValue(mixed $value): mixed return $value; } - /** - * @return void - */ - protected function validateType(mixed $value) + protected function validateType(mixed $value): void { if (!\is_array($value) && (!$this->allowFalse || false !== $value)) { $ex = new InvalidTypeException(\sprintf('Invalid type for path "%s". Expected "array", but got "%s"', $this->getPath(), get_debug_type($value))); diff --git a/app/vendor/symfony/config/Definition/BaseNode.php b/app/vendor/symfony/config/Definition/BaseNode.php index e8b6bf1ff..9cfd69239 100644 --- a/app/vendor/symfony/config/Definition/BaseNode.php +++ b/app/vendor/symfony/config/Definition/BaseNode.php @@ -29,32 +29,31 @@ abstract class BaseNode implements NodeInterface private static array $placeholderUniquePrefixes = []; private static array $placeholders = []; - protected $name; - protected $parent; - protected $normalizationClosures = []; - protected $normalizedTypes = []; - protected $finalValidationClosures = []; - protected $allowOverwrite = true; - protected $required = false; - protected $deprecation = []; - protected $equivalentValues = []; - protected $attributes = []; - protected $pathSeparator; + protected string $name; + protected array $normalizationClosures = []; + protected array $normalizedTypes = []; + protected array $finalValidationClosures = []; + protected bool $allowOverwrite = true; + protected bool $required = false; + protected array $deprecation = []; + protected array $equivalentValues = []; + protected array $attributes = []; private mixed $handlingPlaceholder = null; /** * @throws \InvalidArgumentException if the name contains a period */ - public function __construct(?string $name, ?NodeInterface $parent = null, string $pathSeparator = self::DEFAULT_PATH_SEPARATOR) - { + public function __construct( + ?string $name, + protected ?NodeInterface $parent = null, + protected string $pathSeparator = self::DEFAULT_PATH_SEPARATOR, + ) { if (str_contains($name = (string) $name, $pathSeparator)) { throw new \InvalidArgumentException('The name must not contain ".'.$pathSeparator.'".'); } $this->name = $name; - $this->parent = $parent; - $this->pathSeparator = $pathSeparator; } /** @@ -98,10 +97,7 @@ public static function resetPlaceholders(): void self::$placeholders = []; } - /** - * @return void - */ - public function setAttribute(string $key, mixed $value) + public function setAttribute(string $key, mixed $value): void { $this->attributes[$key] = $value; } @@ -121,28 +117,20 @@ public function getAttributes(): array return $this->attributes; } - /** - * @return void - */ - public function setAttributes(array $attributes) + public function setAttributes(array $attributes): void { $this->attributes = $attributes; } - /** - * @return void - */ - public function removeAttribute(string $key) + public function removeAttribute(string $key): void { unset($this->attributes[$key]); } /** * Sets an info message. - * - * @return void */ - public function setInfo(string $info) + public function setInfo(string $info): void { $this->setAttribute('info', $info); } @@ -157,10 +145,8 @@ public function getInfo(): ?string /** * Sets the example configuration for this node. - * - * @return void */ - public function setExample(string|array $example) + public function setExample(string|array $example): void { $this->setAttribute('example', $example); } @@ -175,20 +161,16 @@ public function getExample(): string|array|null /** * Adds an equivalent value. - * - * @return void */ - public function addEquivalentValue(mixed $originalValue, mixed $equivalentValue) + public function addEquivalentValue(mixed $originalValue, mixed $equivalentValue): void { $this->equivalentValues[] = [$originalValue, $equivalentValue]; } /** * Set this node as required. - * - * @return void */ - public function setRequired(bool $boolean) + public function setRequired(bool $boolean): void { $this->required = $boolean; } @@ -202,10 +184,8 @@ public function setRequired(bool $boolean) * @param string $package The name of the composer package that is triggering the deprecation * @param string $version The version of the package that introduced the deprecation * @param string $message the deprecation message to use - * - * @return void */ - public function setDeprecated(string $package, string $version, string $message = 'The child node "%node%" at path "%path%" is deprecated.') + public function setDeprecated(string $package, string $version, string $message = 'The child node "%node%" at path "%path%" is deprecated.'): void { $this->deprecation = [ 'package' => $package, @@ -216,10 +196,8 @@ public function setDeprecated(string $package, string $version, string $message /** * Sets if this node can be overridden. - * - * @return void */ - public function setAllowOverwrite(bool $allow) + public function setAllowOverwrite(bool $allow): void { $this->allowOverwrite = $allow; } @@ -228,10 +206,8 @@ public function setAllowOverwrite(bool $allow) * Sets the closures used for normalization. * * @param \Closure[] $closures An array of Closures used for normalization - * - * @return void */ - public function setNormalizationClosures(array $closures) + public function setNormalizationClosures(array $closures): void { $this->normalizationClosures = $closures; } @@ -240,10 +216,8 @@ public function setNormalizationClosures(array $closures) * Sets the list of types supported by normalization. * * see ExprBuilder::TYPE_* constants. - * - * @return void */ - public function setNormalizedTypes(array $types) + public function setNormalizedTypes(array $types): void { $this->normalizedTypes = $types; } @@ -262,10 +236,8 @@ public function getNormalizedTypes(): array * Sets the closures used for final validation. * * @param \Closure[] $closures An array of Closures used for final validation - * - * @return void */ - public function setFinalValidationClosures(array $closures) + public function setFinalValidationClosures(array $closures): void { $this->finalValidationClosures = $closures; } @@ -442,11 +414,9 @@ final public function finalize(mixed $value): mixed /** * Validates the type of a Node. * - * @return void - * * @throws InvalidTypeException when the value is invalid */ - abstract protected function validateType(mixed $value); + abstract protected function validateType(mixed $value): void; /** * Normalizes the value. diff --git a/app/vendor/symfony/config/Definition/BooleanNode.php b/app/vendor/symfony/config/Definition/BooleanNode.php index 5d7c573e9..b4ed0f0eb 100644 --- a/app/vendor/symfony/config/Definition/BooleanNode.php +++ b/app/vendor/symfony/config/Definition/BooleanNode.php @@ -20,13 +20,23 @@ */ class BooleanNode extends ScalarNode { - /** - * @return void - */ - protected function validateType(mixed $value) + public function __construct( + ?string $name, + ?NodeInterface $parent = null, + string $pathSeparator = self::DEFAULT_PATH_SEPARATOR, + private bool $nullable = false, + ) { + parent::__construct($name, $parent, $pathSeparator); + } + + protected function validateType(mixed $value): void { if (!\is_bool($value)) { - $ex = new InvalidTypeException(\sprintf('Invalid type for path "%s". Expected "bool", but got "%s".', $this->getPath(), get_debug_type($value))); + if (null === $value && $this->nullable) { + return; + } + + $ex = new InvalidTypeException(\sprintf('Invalid type for path "%s". Expected "bool%s", but got "%s".', $this->getPath(), $this->nullable ? '" or "null' : '', get_debug_type($value))); if ($hint = $this->getInfo()) { $ex->addHint($hint); } diff --git a/app/vendor/symfony/config/Definition/Builder/ArrayNodeDefinition.php b/app/vendor/symfony/config/Definition/Builder/ArrayNodeDefinition.php index ec0e12ab6..e9872d7ee 100644 --- a/app/vendor/symfony/config/Definition/Builder/ArrayNodeDefinition.php +++ b/app/vendor/symfony/config/Definition/Builder/ArrayNodeDefinition.php @@ -23,19 +23,19 @@ */ class ArrayNodeDefinition extends NodeDefinition implements ParentNodeDefinitionInterface { - protected $performDeepMerging = true; - protected $ignoreExtraKeys = false; - protected $removeExtraKeys = true; - protected $children = []; - protected $prototype; - protected $atLeastOne = false; - protected $allowNewKeys = true; - protected $key; - protected $removeKeyItem; - protected $addDefaults = false; - protected $addDefaultChildren = false; - protected $nodeBuilder; - protected $normalizeKeys = true; + protected bool $performDeepMerging = true; + protected bool $ignoreExtraKeys = false; + protected bool $removeExtraKeys = true; + protected array $children = []; + protected NodeDefinition $prototype; + protected bool $atLeastOne = false; + protected bool $allowNewKeys = true; + protected ?string $key = null; + protected bool $removeKeyItem = false; + protected bool $addDefaults = false; + protected int|string|array|false|null $addDefaultChildren = false; + protected NodeBuilder $nodeBuilder; + protected bool $normalizeKeys = true; public function __construct(?string $name, ?NodeParentInterface $parent = null) { @@ -45,10 +45,7 @@ public function __construct(?string $name, ?NodeParentInterface $parent = null) $this->trueEquivalent = []; } - /** - * @return void - */ - public function setBuilder(NodeBuilder $builder) + public function setBuilder(NodeBuilder $builder): void { $this->nodeBuilder = $builder; } @@ -76,6 +73,11 @@ public function scalarPrototype(): ScalarNodeDefinition return $this->prototype('scalar'); } + public function stringPrototype(): StringNodeDefinition + { + return $this->prototype('string'); + } + public function booleanPrototype(): BooleanNodeDefinition { return $this->prototype('boolean'); @@ -237,11 +239,13 @@ public function canBeUnset(bool $allow = true): static * enableableArrayNode: {enabled: false, ...} # The config is disabled * enableableArrayNode: false # The config is disabled * + * @param string|null $info A description of what happens when the node is enabled or disabled + * * @return $this */ - public function canBeEnabled(): static + public function canBeEnabled(/* ?string $info = null */): static { - $this + $disabledNode = $this ->addDefaultsIfNotSet() ->treatFalseLike(['enabled' => false]) ->treatTrueLike(['enabled' => true]) @@ -259,6 +263,11 @@ public function canBeEnabled(): static ->defaultFalse() ; + $info = 1 <= \func_num_args() ? func_get_arg(0) : null; + if ($info) { + $disabledNode->info($info); + } + return $this; } @@ -267,11 +276,13 @@ public function canBeEnabled(): static * * By default, the section is enabled. * + * @param string|null $info A description of what happens when the node is enabled or disabled + * * @return $this */ - public function canBeDisabled(): static + public function canBeDisabled(/* ?string $info = null */): static { - $this + $enabledNode = $this ->addDefaultsIfNotSet() ->treatFalseLike(['enabled' => false]) ->treatTrueLike(['enabled' => true]) @@ -281,6 +292,11 @@ public function canBeDisabled(): static ->defaultTrue() ; + $info = 1 <= \func_num_args() ? func_get_arg(0) : null; + if ($info) { + $enabledNode->info($info); + } + return $this; } @@ -425,11 +441,9 @@ protected function createNode(): NodeInterface /** * Validate the configuration of a concrete node. * - * @return void - * * @throws InvalidDefinitionException */ - protected function validateConcreteNode(ArrayNode $node) + protected function validateConcreteNode(ArrayNode $node): void { $path = $node->getPath(); @@ -457,11 +471,9 @@ protected function validateConcreteNode(ArrayNode $node) /** * Validate the configuration of a prototype node. * - * @return void - * * @throws InvalidDefinitionException */ - protected function validatePrototypeNode(PrototypedArrayNode $node) + protected function validatePrototypeNode(PrototypedArrayNode $node): void { $path = $node->getPath(); diff --git a/app/vendor/symfony/config/Definition/Builder/BooleanNodeDefinition.php b/app/vendor/symfony/config/Definition/Builder/BooleanNodeDefinition.php index 15e63961a..bc03c9c94 100644 --- a/app/vendor/symfony/config/Definition/Builder/BooleanNodeDefinition.php +++ b/app/vendor/symfony/config/Definition/Builder/BooleanNodeDefinition.php @@ -33,7 +33,7 @@ public function __construct(?string $name, ?NodeParentInterface $parent = null) */ protected function instantiateNode(): BooleanNode { - return new BooleanNode($this->name, $this->parent, $this->pathSeparator); + return new BooleanNode($this->name, $this->parent, $this->pathSeparator, null === $this->nullEquivalent); } /** @@ -43,4 +43,20 @@ public function cannotBeEmpty(): static { throw new InvalidDefinitionException('->cannotBeEmpty() is not applicable to BooleanNodeDefinition.'); } + + public function defaultNull(): static + { + $this->nullEquivalent = null; + + return parent::defaultNull(); + } + + public function defaultValue(mixed $value): static + { + if (null === $value) { + $this->nullEquivalent = null; + } + + return parent::defaultValue($value); + } } diff --git a/app/vendor/symfony/config/Definition/Builder/BuilderAwareInterface.php b/app/vendor/symfony/config/Definition/Builder/BuilderAwareInterface.php index bb40307e1..cf646a140 100644 --- a/app/vendor/symfony/config/Definition/Builder/BuilderAwareInterface.php +++ b/app/vendor/symfony/config/Definition/Builder/BuilderAwareInterface.php @@ -20,8 +20,6 @@ interface BuilderAwareInterface { /** * Sets a custom children builder. - * - * @return void */ - public function setBuilder(NodeBuilder $builder); + public function setBuilder(NodeBuilder $builder): void; } diff --git a/app/vendor/symfony/config/Definition/Builder/EnumNodeDefinition.php b/app/vendor/symfony/config/Definition/Builder/EnumNodeDefinition.php index 99f318123..a020c5f27 100644 --- a/app/vendor/symfony/config/Definition/Builder/EnumNodeDefinition.php +++ b/app/vendor/symfony/config/Definition/Builder/EnumNodeDefinition.php @@ -21,6 +21,7 @@ class EnumNodeDefinition extends ScalarNodeDefinition { private array $values; + private string $enumFqcn; /** * @return $this @@ -36,6 +37,22 @@ public function values(array $values): static return $this; } + /** + * @param class-string<\UnitEnum> $enumFqcn + * + * @return $this + */ + public function enumFqcn(string $enumFqcn): static + { + if (!enum_exists($enumFqcn)) { + throw new \InvalidArgumentException(\sprintf('The enum class "%s" does not exist.', $enumFqcn)); + } + + $this->enumFqcn = $enumFqcn; + + return $this; + } + /** * Instantiate a Node. * @@ -43,10 +60,14 @@ public function values(array $values): static */ protected function instantiateNode(): EnumNode { - if (!isset($this->values)) { - throw new \RuntimeException('You must call ->values() on enum nodes.'); + if (!isset($this->values) && !isset($this->enumFqcn)) { + throw new \RuntimeException('You must call either ->values() or ->enumFqcn() on enum nodes.'); + } + + if (isset($this->values) && isset($this->enumFqcn)) { + throw new \RuntimeException('You must call either ->values() or ->enumFqcn() on enum nodes but not both.'); } - return new EnumNode($this->name, $this->parent, $this->values, $this->pathSeparator); + return new EnumNode($this->name, $this->parent, $this->values ?? [], $this->pathSeparator, $this->enumFqcn ?? null); } } diff --git a/app/vendor/symfony/config/Definition/Builder/ExprBuilder.php b/app/vendor/symfony/config/Definition/Builder/ExprBuilder.php index 0323428f7..624e22d5c 100644 --- a/app/vendor/symfony/config/Definition/Builder/ExprBuilder.php +++ b/app/vendor/symfony/config/Definition/Builder/ExprBuilder.php @@ -26,15 +26,13 @@ class ExprBuilder public const TYPE_NULL = 'null'; public const TYPE_ARRAY = 'array'; - protected $node; + public string $allowedTypes; + public ?\Closure $ifPart = null; + public ?\Closure $thenPart = null; - public $allowedTypes; - public $ifPart; - public $thenPart; - - public function __construct(NodeDefinition $node) - { - $this->node = $node; + public function __construct( + protected NodeDefinition $node, + ) { } /** @@ -69,6 +67,21 @@ public function ifTrue(?\Closure $closure = null): static return $this; } + /** + * Sets a closure to use as tests. + * + * The default one tests if the value is false. + * + * @return $this + */ + public function ifFalse(?\Closure $closure = null): static + { + $this->ifPart = $closure ? static fn ($v) => !$closure($v) : static fn ($v) => false === $v; + $this->allowedTypes = self::TYPE_ANY; + + return $this; + } + /** * Tests if the value is a string. * @@ -102,7 +115,7 @@ public function ifNull(): static */ public function ifEmpty(): static { - $this->ifPart = static fn ($v) => empty($v); + $this->ifPart = static fn ($v) => !$v; $this->allowedTypes = self::TYPE_ANY; return $this; diff --git a/app/vendor/symfony/config/Definition/Builder/MergeBuilder.php b/app/vendor/symfony/config/Definition/Builder/MergeBuilder.php index f8980a6e0..b90865cfc 100644 --- a/app/vendor/symfony/config/Definition/Builder/MergeBuilder.php +++ b/app/vendor/symfony/config/Definition/Builder/MergeBuilder.php @@ -18,13 +18,12 @@ */ class MergeBuilder { - protected $node; - public $allowFalse = false; - public $allowOverwrite = true; + public bool $allowFalse = false; + public bool $allowOverwrite = true; - public function __construct(NodeDefinition $node) - { - $this->node = $node; + public function __construct( + protected NodeDefinition $node, + ) { } /** diff --git a/app/vendor/symfony/config/Definition/Builder/NodeBuilder.php b/app/vendor/symfony/config/Definition/Builder/NodeBuilder.php index d9c03dd9d..b3fa7bfd5 100644 --- a/app/vendor/symfony/config/Definition/Builder/NodeBuilder.php +++ b/app/vendor/symfony/config/Definition/Builder/NodeBuilder.php @@ -18,8 +18,8 @@ */ class NodeBuilder implements NodeParentInterface { - protected $parent; - protected $nodeMapping; + protected (NodeDefinition&ParentNodeDefinitionInterface)|null $parent = null; + protected array $nodeMapping; public function __construct() { @@ -31,6 +31,7 @@ public function __construct() 'float' => FloatNodeDefinition::class, 'array' => ArrayNodeDefinition::class, 'enum' => EnumNodeDefinition::class, + 'string' => StringNodeDefinition::class, ]; } @@ -39,11 +40,8 @@ public function __construct() * * @return $this */ - public function setParent(?ParentNodeDefinitionInterface $parent = null): static + public function setParent((NodeDefinition&ParentNodeDefinitionInterface)|null $parent): static { - if (1 > \func_num_args()) { - trigger_deprecation('symfony/form', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__); - } $this->parent = $parent; return $this; @@ -105,12 +103,18 @@ public function variableNode(string $name): VariableNodeDefinition return $this->node($name, 'variable'); } + /** + * Creates a child string node. + */ + public function stringNode(string $name): StringNodeDefinition + { + return $this->node($name, 'string'); + } + /** * Returns the parent node. - * - * @return NodeDefinition&ParentNodeDefinitionInterface */ - public function end() + public function end(): NodeDefinition&ParentNodeDefinitionInterface { return $this->parent; } diff --git a/app/vendor/symfony/config/Definition/Builder/NodeDefinition.php b/app/vendor/symfony/config/Definition/Builder/NodeDefinition.php index cf2173e17..fdfbdabd2 100644 --- a/app/vendor/symfony/config/Definition/Builder/NodeDefinition.php +++ b/app/vendor/symfony/config/Definition/Builder/NodeDefinition.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Config\Definition\Builder; +use Composer\InstalledVersions; use Symfony\Component\Config\Definition\BaseNode; use Symfony\Component\Config\Definition\Exception\InvalidDefinitionException; use Symfony\Component\Config\Definition\NodeInterface; @@ -22,21 +23,21 @@ */ abstract class NodeDefinition implements NodeParentInterface { - protected $name; - protected $normalization; - protected $validation; - protected $defaultValue; - protected $default = false; - protected $required = false; - protected $deprecation = []; - protected $merge; - protected $allowEmptyValue = true; - protected $nullEquivalent; - protected $trueEquivalent = true; - protected $falseEquivalent = false; - protected $pathSeparator = BaseNode::DEFAULT_PATH_SEPARATOR; - protected $parent; - protected $attributes = []; + protected ?string $name = null; + protected NormalizationBuilder $normalization; + protected ValidationBuilder $validation; + protected mixed $defaultValue; + protected bool $default = false; + protected bool $required = false; + protected array $deprecation = []; + protected MergeBuilder $merge; + protected bool $allowEmptyValue = true; + protected mixed $nullEquivalent = null; + protected mixed $trueEquivalent = true; + protected mixed $falseEquivalent = false; + protected string $pathSeparator = BaseNode::DEFAULT_PATH_SEPARATOR; + protected NodeParentInterface|NodeInterface|null $parent; + protected array $attributes = []; public function __construct(?string $name, ?NodeParentInterface $parent = null) { @@ -76,6 +77,26 @@ public function example(string|array $example): static return $this->attribute('example', $example); } + /** + * Sets the documentation URI, as usually put in the "@see" tag of a doc block. This + * can either be a URL or a file path. You can use the placeholders {package}, + * {version:major} and {version:minor} in the URI. + * + * @return $this + */ + public function docUrl(string $uri, ?string $package = null): static + { + if ($package) { + preg_match('/^(\d+)\.(\d+)\.(\d+)/', InstalledVersions::getVersion($package) ?? '', $m); + } + + return $this->attribute('docUrl', strtr($uri, [ + '{package}' => $package ?? '', + '{version:major}' => $m[1] ?? '', + '{version:minor}' => $m[2] ?? '', + ])); + } + /** * Sets an attribute on the node. * @@ -90,8 +111,10 @@ public function attribute(string $key, mixed $value): static /** * Returns the parent node. + * + * @return NodeParentInterface|NodeBuilder|self|ArrayNodeDefinition|VariableNodeDefinition */ - public function end(): NodeParentInterface|NodeBuilder|self|ArrayNodeDefinition|VariableNodeDefinition|null + public function end(): NodeParentInterface { return $this->parent; } diff --git a/app/vendor/symfony/config/Definition/Builder/NormalizationBuilder.php b/app/vendor/symfony/config/Definition/Builder/NormalizationBuilder.php index 1f6b34441..8a8141c19 100644 --- a/app/vendor/symfony/config/Definition/Builder/NormalizationBuilder.php +++ b/app/vendor/symfony/config/Definition/Builder/NormalizationBuilder.php @@ -18,14 +18,13 @@ */ class NormalizationBuilder { - protected $node; - public $before = []; - public $declaredTypes = []; - public $remappings = []; + public array $before = []; + public array $declaredTypes = []; + public array $remappings = []; - public function __construct(NodeDefinition $node) - { - $this->node = $node; + public function __construct( + protected NodeDefinition $node, + ) { } /** diff --git a/app/vendor/symfony/config/Definition/Builder/NumericNodeDefinition.php b/app/vendor/symfony/config/Definition/Builder/NumericNodeDefinition.php index fc89df55b..06dc97994 100644 --- a/app/vendor/symfony/config/Definition/Builder/NumericNodeDefinition.php +++ b/app/vendor/symfony/config/Definition/Builder/NumericNodeDefinition.php @@ -20,8 +20,8 @@ */ abstract class NumericNodeDefinition extends ScalarNodeDefinition { - protected $min; - protected $max; + protected int|float|null $min = null; + protected int|float|null $max = null; /** * Ensures that the value is smaller than the given reference. diff --git a/app/vendor/symfony/config/Definition/Builder/StringNodeDefinition.php b/app/vendor/symfony/config/Definition/Builder/StringNodeDefinition.php new file mode 100644 index 000000000..c86f1bfd7 --- /dev/null +++ b/app/vendor/symfony/config/Definition/Builder/StringNodeDefinition.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Builder; + +use Symfony\Component\Config\Definition\StringNode; + +/** + * This class provides a fluent interface for defining a node. + * + * @author Raffaele Carelle + */ +class StringNodeDefinition extends ScalarNodeDefinition +{ + public function __construct(?string $name, ?NodeParentInterface $parent = null) + { + parent::__construct($name, $parent); + + $this->nullEquivalent = ''; + } + + protected function instantiateNode(): StringNode + { + return new StringNode($this->name, $this->parent, $this->pathSeparator); + } +} diff --git a/app/vendor/symfony/config/Definition/Builder/TreeBuilder.php b/app/vendor/symfony/config/Definition/Builder/TreeBuilder.php index f7da3e794..5170e19d1 100644 --- a/app/vendor/symfony/config/Definition/Builder/TreeBuilder.php +++ b/app/vendor/symfony/config/Definition/Builder/TreeBuilder.php @@ -20,15 +20,8 @@ */ class TreeBuilder implements NodeParentInterface { - /** - * @var NodeInterface|null - */ - protected $tree; - - /** - * @var NodeDefinition - */ - protected $root; + protected ?NodeInterface $tree = null; + protected ?NodeDefinition $root = null; public function __construct(string $name, string $type = 'array', ?NodeBuilder $builder = null) { @@ -54,10 +47,7 @@ public function buildTree(): NodeInterface return $this->tree ??= $this->root->getNode(true); } - /** - * @return void - */ - public function setPathSeparator(string $separator) + public function setPathSeparator(string $separator): void { // unset last built as changing path separator changes all nodes $this->tree = null; diff --git a/app/vendor/symfony/config/Definition/Builder/ValidationBuilder.php b/app/vendor/symfony/config/Definition/Builder/ValidationBuilder.php index 64623d6d6..ad2239349 100644 --- a/app/vendor/symfony/config/Definition/Builder/ValidationBuilder.php +++ b/app/vendor/symfony/config/Definition/Builder/ValidationBuilder.php @@ -18,12 +18,11 @@ */ class ValidationBuilder { - protected $node; - public $rules = []; + public array $rules = []; - public function __construct(NodeDefinition $node) - { - $this->node = $node; + public function __construct( + protected NodeDefinition $node, + ) { } /** diff --git a/app/vendor/symfony/config/Definition/ConfigurationInterface.php b/app/vendor/symfony/config/Definition/ConfigurationInterface.php index 7b5d443fe..97a325bb6 100644 --- a/app/vendor/symfony/config/Definition/ConfigurationInterface.php +++ b/app/vendor/symfony/config/Definition/ConfigurationInterface.php @@ -22,8 +22,6 @@ interface ConfigurationInterface { /** * Generates the configuration tree builder. - * - * @return TreeBuilder */ - public function getConfigTreeBuilder(); + public function getConfigTreeBuilder(): TreeBuilder; } diff --git a/app/vendor/symfony/config/Definition/Dumper/XmlReferenceDumper.php b/app/vendor/symfony/config/Definition/Dumper/XmlReferenceDumper.php index 32317b1db..2131c7131 100644 --- a/app/vendor/symfony/config/Definition/Dumper/XmlReferenceDumper.php +++ b/app/vendor/symfony/config/Definition/Dumper/XmlReferenceDumper.php @@ -31,18 +31,12 @@ class XmlReferenceDumper { private ?string $reference = null; - /** - * @return string - */ - public function dump(ConfigurationInterface $configuration, ?string $namespace = null) + public function dump(ConfigurationInterface $configuration, ?string $namespace = null): string { return $this->dumpNode($configuration->getConfigTreeBuilder()->buildTree(), $namespace); } - /** - * @return string - */ - public function dumpNode(NodeInterface $node, ?string $namespace = null) + public function dumpNode(NodeInterface $node, ?string $namespace = null): string { $this->reference = ''; $this->writeNode($node, 0, true, $namespace); @@ -286,7 +280,7 @@ private function writeValue(mixed $value): string return 'null'; } - if (empty($value)) { + if (!$value) { return ''; } diff --git a/app/vendor/symfony/config/Definition/Dumper/YamlReferenceDumper.php b/app/vendor/symfony/config/Definition/Dumper/YamlReferenceDumper.php index 0813ab1e5..267444d92 100644 --- a/app/vendor/symfony/config/Definition/Dumper/YamlReferenceDumper.php +++ b/app/vendor/symfony/config/Definition/Dumper/YamlReferenceDumper.php @@ -29,18 +29,12 @@ class YamlReferenceDumper { private ?string $reference = null; - /** - * @return string - */ - public function dump(ConfigurationInterface $configuration) + public function dump(ConfigurationInterface $configuration): string { return $this->dumpNode($configuration->getConfigTreeBuilder()->buildTree()); } - /** - * @return string - */ - public function dumpAtPath(ConfigurationInterface $configuration, string $path) + public function dumpAtPath(ConfigurationInterface $configuration, string $path): string { $rootNode = $node = $configuration->getConfigTreeBuilder()->buildTree(); @@ -66,10 +60,7 @@ public function dumpAtPath(ConfigurationInterface $configuration, string $path) return $this->dumpNode($node); } - /** - * @return string - */ - public function dumpNode(NodeInterface $node) + public function dumpNode(NodeInterface $node): string { $this->reference = ''; $this->writeNode($node); diff --git a/app/vendor/symfony/config/Definition/EnumNode.php b/app/vendor/symfony/config/Definition/EnumNode.php index afe509d9a..401bb48fc 100644 --- a/app/vendor/symfony/config/Definition/EnumNode.php +++ b/app/vendor/symfony/config/Definition/EnumNode.php @@ -21,13 +21,30 @@ class EnumNode extends ScalarNode { private array $values; + private ?string $enumFqcn = null; - public function __construct(?string $name, ?NodeInterface $parent = null, array $values = [], string $pathSeparator = BaseNode::DEFAULT_PATH_SEPARATOR) + /** + * @param class-string<\UnitEnum>|null $enumFqcn + */ + public function __construct(?string $name, ?NodeInterface $parent = null, array $values = [], string $pathSeparator = BaseNode::DEFAULT_PATH_SEPARATOR, ?string $enumFqcn = null) { - if (!$values) { + if (!$values && !$enumFqcn) { throw new \InvalidArgumentException('$values must contain at least one element.'); } + if ($values && $enumFqcn) { + throw new \InvalidArgumentException('$values or $enumFqcn cannot be both set.'); + } + + if (null !== $enumFqcn) { + if (!enum_exists($enumFqcn)) { + throw new \InvalidArgumentException(\sprintf('The "%s" enum does not exist.', $enumFqcn)); + } + + $values = $enumFqcn::cases(); + $this->enumFqcn = $enumFqcn; + } + foreach ($values as $value) { if (null === $value || \is_scalar($value)) { continue; @@ -46,19 +63,25 @@ public function __construct(?string $name, ?NodeInterface $parent = null, array $this->values = $values; } - /** - * @return array - */ - public function getValues() + public function getValues(): array { return $this->values; } + public function getEnumFqcn(): ?string + { + return $this->enumFqcn; + } + /** * @internal */ public function getPermissibleValues(string $separator): string { + if (is_subclass_of($this->enumFqcn, \BackedEnum::class)) { + return implode($separator, array_column($this->enumFqcn::cases(), 'value')); + } + return implode($separator, array_unique(array_map(static function (mixed $value): string { if (!$value instanceof \UnitEnum) { return json_encode($value); @@ -68,10 +91,7 @@ public function getPermissibleValues(string $separator): string }, $this->values))); } - /** - * @return void - */ - protected function validateType(mixed $value) + protected function validateType(mixed $value): void { if ($value instanceof \UnitEnum) { return; @@ -84,13 +104,55 @@ protected function finalizeValue(mixed $value): mixed { $value = parent::finalizeValue($value); - if (!\in_array($value, $this->values, true)) { - $ex = new InvalidConfigurationException(\sprintf('The value %s is not allowed for path "%s". Permissible values: %s', json_encode($value), $this->getPath(), $this->getPermissibleValues(', '))); - $ex->setPath($this->getPath()); + if (!$this->enumFqcn) { + if (!\in_array($value, $this->values, true)) { + throw $this->createInvalidValueException($value); + } + + return $value; + } + + if ($value instanceof $this->enumFqcn) { + return $value; + } + + if (!is_subclass_of($this->enumFqcn, \BackedEnum::class)) { + // value is not an instance of the enum, and the enum is not + // backed, meaning no cast is possible + throw $this->createInvalidValueException($value); + } + + if ($value instanceof \UnitEnum && !$value instanceof $this->enumFqcn) { + throw new InvalidConfigurationException(\sprintf('The value should be part of the "%s" enum, got a value from the "%s" enum.', $this->enumFqcn, get_debug_type($value))); + } + + if (!\is_string($value) && !\is_int($value)) { + throw new InvalidConfigurationException(\sprintf('Only strings and integers can be cast to a case of the "%s" enum, got value of type "%s".', $this->enumFqcn, get_debug_type($value))); + } + + try { + return $this->enumFqcn::from($value); + } catch (\TypeError|\ValueError) { + throw $this->createInvalidValueException($value); + } + } - throw $ex; + private function createInvalidValueException(mixed $value): InvalidConfigurationException + { + $displayValue = match (true) { + \is_int($value) => $value, + \is_string($value) => \sprintf('"%s"', $value), + default => \sprintf('of type "%s"', get_debug_type($value)), + }; + + $message = \sprintf('The value %s is not allowed for path "%s". Permissible values: %s.', $displayValue, $this->getPath(), $this->getPermissibleValues(', ')); + if ($this->enumFqcn) { + $message = substr_replace($message, \sprintf(' (cases of the "%s" enum)', $this->enumFqcn), -1, 0); } - return $value; + $e = new InvalidConfigurationException($message); + $e->setPath($this->getPath()); + + return $e; } } diff --git a/app/vendor/symfony/config/Definition/Exception/InvalidConfigurationException.php b/app/vendor/symfony/config/Definition/Exception/InvalidConfigurationException.php index 794447bf8..e3f29f8e6 100644 --- a/app/vendor/symfony/config/Definition/Exception/InvalidConfigurationException.php +++ b/app/vendor/symfony/config/Definition/Exception/InvalidConfigurationException.php @@ -22,10 +22,7 @@ class InvalidConfigurationException extends Exception private ?string $path = null; private bool $containsHints = false; - /** - * @return void - */ - public function setPath(string $path) + public function setPath(string $path): void { $this->path = $path; } @@ -37,10 +34,8 @@ public function getPath(): ?string /** * Adds extra information that is suffixed to the original exception message. - * - * @return void */ - public function addHint(string $hint) + public function addHint(string $hint): void { if (!$this->containsHints) { $this->message .= "\nHint: ".$hint; diff --git a/app/vendor/symfony/config/Definition/FloatNode.php b/app/vendor/symfony/config/Definition/FloatNode.php index 9da243427..8b922804b 100644 --- a/app/vendor/symfony/config/Definition/FloatNode.php +++ b/app/vendor/symfony/config/Definition/FloatNode.php @@ -20,10 +20,7 @@ */ class FloatNode extends NumericNode { - /** - * @return void - */ - protected function validateType(mixed $value) + protected function validateType(mixed $value): void { // Integers are also accepted, we just cast them if (\is_int($value)) { diff --git a/app/vendor/symfony/config/Definition/IntegerNode.php b/app/vendor/symfony/config/Definition/IntegerNode.php index f5a500605..6fa3e6032 100644 --- a/app/vendor/symfony/config/Definition/IntegerNode.php +++ b/app/vendor/symfony/config/Definition/IntegerNode.php @@ -20,10 +20,7 @@ */ class IntegerNode extends NumericNode { - /** - * @return void - */ - protected function validateType(mixed $value) + protected function validateType(mixed $value): void { if (!\is_int($value)) { $ex = new InvalidTypeException(\sprintf('Invalid type for path "%s". Expected "int", but got "%s".', $this->getPath(), get_debug_type($value))); diff --git a/app/vendor/symfony/config/Definition/NumericNode.php b/app/vendor/symfony/config/Definition/NumericNode.php index 98c99c01e..a97850c9d 100644 --- a/app/vendor/symfony/config/Definition/NumericNode.php +++ b/app/vendor/symfony/config/Definition/NumericNode.php @@ -20,14 +20,14 @@ */ class NumericNode extends ScalarNode { - protected $min; - protected $max; - - public function __construct(?string $name, ?NodeInterface $parent = null, int|float|null $min = null, int|float|null $max = null, string $pathSeparator = BaseNode::DEFAULT_PATH_SEPARATOR) - { + public function __construct( + ?string $name, + ?NodeInterface $parent = null, + protected int|float|null $min = null, + protected int|float|null $max = null, + string $pathSeparator = BaseNode::DEFAULT_PATH_SEPARATOR, + ) { parent::__construct($name, $parent, $pathSeparator); - $this->min = $min; - $this->max = $max; } protected function finalizeValue(mixed $value): mixed diff --git a/app/vendor/symfony/config/Definition/PrototypeNodeInterface.php b/app/vendor/symfony/config/Definition/PrototypeNodeInterface.php index 9dce7444b..109889f37 100644 --- a/app/vendor/symfony/config/Definition/PrototypeNodeInterface.php +++ b/app/vendor/symfony/config/Definition/PrototypeNodeInterface.php @@ -20,8 +20,6 @@ interface PrototypeNodeInterface extends NodeInterface { /** * Sets the name of the node. - * - * @return void */ - public function setName(string $name); + public function setName(string $name): void; } diff --git a/app/vendor/symfony/config/Definition/PrototypedArrayNode.php b/app/vendor/symfony/config/Definition/PrototypedArrayNode.php index 781408b28..a901dab78 100644 --- a/app/vendor/symfony/config/Definition/PrototypedArrayNode.php +++ b/app/vendor/symfony/config/Definition/PrototypedArrayNode.php @@ -23,12 +23,12 @@ */ class PrototypedArrayNode extends ArrayNode { - protected $prototype; - protected $keyAttribute; - protected $removeKeyAttribute = false; - protected $minNumberOfElements = 0; - protected $defaultValue = []; - protected $defaultChildren; + protected PrototypeNodeInterface $prototype; + protected ?string $keyAttribute = null; + protected bool $removeKeyAttribute = false; + protected int $minNumberOfElements = 0; + protected array $defaultValue = []; + protected ?array $defaultChildren = null; /** * @var NodeInterface[] An array of the prototypes of the simplified value children */ @@ -37,10 +37,8 @@ class PrototypedArrayNode extends ArrayNode /** * Sets the minimum number of elements that a prototype based node must * contain. By default this is zero, meaning no elements. - * - * @return void */ - public function setMinNumberOfElements(int $number) + public function setMinNumberOfElements(int $number): void { $this->minNumberOfElements = $number; } @@ -68,10 +66,8 @@ public function setMinNumberOfElements(int $number) * * @param string $attribute The name of the attribute which value is to be used as a key * @param bool $remove Whether or not to remove the key - * - * @return void */ - public function setKeyAttribute(string $attribute, bool $remove = true) + public function setKeyAttribute(string $attribute, bool $remove = true): void { $this->keyAttribute = $attribute; $this->removeKeyAttribute = $remove; @@ -87,10 +83,8 @@ public function getKeyAttribute(): ?string /** * Sets the default value of this node. - * - * @return void */ - public function setDefaultValue(array $value) + public function setDefaultValue(array $value): void { $this->defaultValue = $value; } @@ -104,10 +98,8 @@ public function hasDefaultValue(): bool * Adds default children when none are set. * * @param int|string|array|null $children The number of children|The child name|The children names to be added - * - * @return void */ - public function setAddChildrenIfNoneSet(int|string|array|null $children = ['defaults']) + public function setAddChildrenIfNoneSet(int|string|array|null $children = ['defaults']): void { if (null === $children) { $this->defaultChildren = ['defaults']; @@ -137,10 +129,8 @@ public function getDefaultValue(): mixed /** * Sets the node prototype. - * - * @return void */ - public function setPrototype(PrototypeNodeInterface $node) + public function setPrototype(PrototypeNodeInterface $node): void { $this->prototype = $node; } @@ -156,11 +146,9 @@ public function getPrototype(): PrototypeNodeInterface /** * Disable adding concrete children for prototyped nodes. * - * @return never - * * @throws Exception */ - public function addChild(NodeInterface $node) + public function addChild(NodeInterface $node): never { throw new Exception('A prototyped array node cannot have concrete children.'); } @@ -168,7 +156,7 @@ public function addChild(NodeInterface $node) protected function finalizeValue(mixed $value): mixed { if (false === $value) { - throw new UnsetKeyException(\sprintf('Unsetting key for path "%s", value: %s.', $this->getPath(), json_encode($value))); + throw new UnsetKeyException(\sprintf('Unsetting key for path "%s", value: false.', $this->getPath())); } foreach ($value as $k => $v) { @@ -229,10 +217,8 @@ protected function normalizeValue(mixed $value): mixed $valuePrototype = current($this->valuePrototypes) ?: clone $children['value']; $valuePrototype->parent = $this; $originalClosures = $this->prototype->normalizationClosures; - if (\is_array($originalClosures)) { - $valuePrototypeClosures = $valuePrototype->normalizationClosures; - $valuePrototype->normalizationClosures = \is_array($valuePrototypeClosures) ? array_merge($originalClosures, $valuePrototypeClosures) : $originalClosures; - } + $valuePrototypeClosures = $valuePrototype->normalizationClosures; + $valuePrototype->normalizationClosures = array_merge($originalClosures, $valuePrototypeClosures); $this->valuePrototypes[$k] = $valuePrototype; } } diff --git a/app/vendor/symfony/config/Definition/ScalarNode.php b/app/vendor/symfony/config/Definition/ScalarNode.php index 3e348672d..341769249 100644 --- a/app/vendor/symfony/config/Definition/ScalarNode.php +++ b/app/vendor/symfony/config/Definition/ScalarNode.php @@ -27,10 +27,7 @@ */ class ScalarNode extends VariableNode { - /** - * @return void - */ - protected function validateType(mixed $value) + protected function validateType(mixed $value): void { if (!\is_scalar($value) && null !== $value) { $ex = new InvalidTypeException(\sprintf('Invalid type for path "%s". Expected "scalar", but got "%s".', $this->getPath(), get_debug_type($value))); diff --git a/app/vendor/symfony/config/Definition/StringNode.php b/app/vendor/symfony/config/Definition/StringNode.php new file mode 100644 index 000000000..6687b8825 --- /dev/null +++ b/app/vendor/symfony/config/Definition/StringNode.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition; + +use Symfony\Component\Config\Definition\Exception\InvalidTypeException; + +/** + * This node represents a String value in the config tree. + * + * @author Raffaele Carelle + */ +class StringNode extends ScalarNode +{ + protected function validateType(mixed $value): void + { + if (!\is_string($value)) { + $ex = new InvalidTypeException(\sprintf('Invalid type for path "%s". Expected "string", but got "%s".', $this->getPath(), get_debug_type($value))); + if ($hint = $this->getInfo()) { + $ex->addHint($hint); + } + $ex->setPath($this->getPath()); + + throw $ex; + } + } + + protected function getValidPlaceholderTypes(): array + { + return ['string']; + } +} diff --git a/app/vendor/symfony/config/Definition/VariableNode.php b/app/vendor/symfony/config/Definition/VariableNode.php index 094962aa1..ed1b903a1 100644 --- a/app/vendor/symfony/config/Definition/VariableNode.php +++ b/app/vendor/symfony/config/Definition/VariableNode.php @@ -23,14 +23,11 @@ */ class VariableNode extends BaseNode implements PrototypeNodeInterface { - protected $defaultValueSet = false; - protected $defaultValue; - protected $allowEmptyValue = true; + protected bool $defaultValueSet = false; + protected mixed $defaultValue = null; + protected bool $allowEmptyValue = true; - /** - * @return void - */ - public function setDefaultValue(mixed $value) + public function setDefaultValue(mixed $value): void { $this->defaultValueSet = true; $this->defaultValue = $value; @@ -52,26 +49,18 @@ public function getDefaultValue(): mixed * Sets if this node is allowed to have an empty value. * * @param bool $boolean True if this entity will accept empty values - * - * @return void */ - public function setAllowEmptyValue(bool $boolean) + public function setAllowEmptyValue(bool $boolean): void { $this->allowEmptyValue = $boolean; } - /** - * @return void - */ - public function setName(string $name) + public function setName(string $name): void { $this->name = $name; } - /** - * @return void - */ - protected function validateType(mixed $value) + protected function validateType(mixed $value): void { } @@ -123,6 +112,6 @@ protected function mergeValues(mixed $leftSide, mixed $rightSide): mixed */ protected function isValueEmpty(mixed $value): bool { - return empty($value); + return !$value; } } diff --git a/app/vendor/symfony/config/Exception/FileLocatorFileNotFoundException.php b/app/vendor/symfony/config/Exception/FileLocatorFileNotFoundException.php index a3fcc901b..d9e5b4f36 100644 --- a/app/vendor/symfony/config/Exception/FileLocatorFileNotFoundException.php +++ b/app/vendor/symfony/config/Exception/FileLocatorFileNotFoundException.php @@ -18,19 +18,16 @@ */ class FileLocatorFileNotFoundException extends \InvalidArgumentException { - private array $paths; - - public function __construct(string $message = '', int $code = 0, ?\Throwable $previous = null, array $paths = []) - { + public function __construct( + string $message = '', + int $code = 0, + ?\Throwable $previous = null, + private array $paths = [], + ) { parent::__construct($message, $code, $previous); - - $this->paths = $paths; } - /** - * @return array - */ - public function getPaths() + public function getPaths(): array { return $this->paths; } diff --git a/app/vendor/symfony/config/Exception/LoaderLoadException.php b/app/vendor/symfony/config/Exception/LoaderLoadException.php index 89f7fd719..ce486a35f 100644 --- a/app/vendor/symfony/config/Exception/LoaderLoadException.php +++ b/app/vendor/symfony/config/Exception/LoaderLoadException.php @@ -76,10 +76,7 @@ public function __construct(mixed $resource, ?string $sourceResource = null, int parent::__construct($message, $code, $previous); } - /** - * @return string - */ - protected function varToString(mixed $var) + protected function varToString(mixed $var): string { if (\is_object($var)) { return \sprintf('Object(%s)', $var::class); diff --git a/app/vendor/symfony/config/Exception/LogicException.php b/app/vendor/symfony/config/Exception/LogicException.php new file mode 100644 index 000000000..d227aece4 --- /dev/null +++ b/app/vendor/symfony/config/Exception/LogicException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Exception; + +class LogicException extends \LogicException +{ +} diff --git a/app/vendor/symfony/config/FileLocator.php b/app/vendor/symfony/config/FileLocator.php index 6e08a5fb0..3a5064edc 100644 --- a/app/vendor/symfony/config/FileLocator.php +++ b/app/vendor/symfony/config/FileLocator.php @@ -20,7 +20,7 @@ */ class FileLocator implements FileLocatorInterface { - protected $paths; + protected array $paths; /** * @param string|string[] $paths A path or an array of paths where to look for resources @@ -35,7 +35,7 @@ public function __construct(string|array $paths = []) * * @psalm-return ($first is true ? string : string[]) */ - public function locate(string $name, ?string $currentPath = null, bool $first = true) + public function locate(string $name, ?string $currentPath = null, bool $first = true): string|array { if ('' === $name) { throw new \InvalidArgumentException('An empty file name is not valid to be located.'); diff --git a/app/vendor/symfony/config/FileLocatorInterface.php b/app/vendor/symfony/config/FileLocatorInterface.php index 32d10d0cb..24bc70964 100644 --- a/app/vendor/symfony/config/FileLocatorInterface.php +++ b/app/vendor/symfony/config/FileLocatorInterface.php @@ -32,5 +32,5 @@ interface FileLocatorInterface * @throws \InvalidArgumentException If $name is empty * @throws FileLocatorFileNotFoundException If a file is not found */ - public function locate(string $name, ?string $currentPath = null, bool $first = true); + public function locate(string $name, ?string $currentPath = null, bool $first = true): string|array; } diff --git a/app/vendor/symfony/config/Loader/FileLoader.php b/app/vendor/symfony/config/Loader/FileLoader.php index 8275ffcd3..8d9d84627 100644 --- a/app/vendor/symfony/config/Loader/FileLoader.php +++ b/app/vendor/symfony/config/Loader/FileLoader.php @@ -25,24 +25,21 @@ */ abstract class FileLoader extends Loader { - protected static $loading = []; - - protected $locator; + protected static array $loading = []; private ?string $currentDir = null; - public function __construct(FileLocatorInterface $locator, ?string $env = null) - { - $this->locator = $locator; + public function __construct( + protected FileLocatorInterface $locator, + ?string $env = null, + ) { parent::__construct($env); } /** * Sets the current directory. - * - * @return void */ - public function setCurrentDir(string $dir) + public function setCurrentDir(string $dir): void { $this->currentDir = $dir; } @@ -64,13 +61,11 @@ public function getLocator(): FileLocatorInterface * @param string|null $sourceResource The original resource importing the new resource * @param string|string[]|null $exclude Glob patterns to exclude from the import * - * @return mixed - * * @throws LoaderLoadException * @throws FileLoaderImportCircularReferenceException * @throws FileLocatorFileNotFoundException */ - public function import(mixed $resource, ?string $type = null, bool $ignoreErrors = false, ?string $sourceResource = null, string|array|null $exclude = null) + public function import(mixed $resource, ?string $type = null, bool $ignoreErrors = false, ?string $sourceResource = null, string|array|null $exclude = null): mixed { if (\is_string($resource) && \strlen($resource) !== ($i = strcspn($resource, '*?{[')) && !str_contains($resource, "\n")) { $excluded = []; diff --git a/app/vendor/symfony/config/Loader/Loader.php b/app/vendor/symfony/config/Loader/Loader.php index 66c38bbea..28e5877bf 100644 --- a/app/vendor/symfony/config/Loader/Loader.php +++ b/app/vendor/symfony/config/Loader/Loader.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Config\Loader; use Symfony\Component\Config\Exception\LoaderLoadException; +use Symfony\Component\Config\Exception\LogicException; /** * Loader is the abstract class used by all built-in loaders. @@ -20,33 +21,31 @@ */ abstract class Loader implements LoaderInterface { - protected $resolver; - protected $env; + protected ?LoaderResolverInterface $resolver = null; - public function __construct(?string $env = null) - { - $this->env = $env; + public function __construct( + protected ?string $env = null, + ) { } public function getResolver(): LoaderResolverInterface { + if (null === $this->resolver) { + throw new LogicException('Cannot get a resolver if none was set.'); + } + return $this->resolver; } - /** - * @return void - */ - public function setResolver(LoaderResolverInterface $resolver) + public function setResolver(LoaderResolverInterface $resolver): void { $this->resolver = $resolver; } /** * Imports a resource. - * - * @return mixed */ - public function import(mixed $resource, ?string $type = null) + public function import(mixed $resource, ?string $type = null): mixed { return $this->resolve($resource, $type)->load($resource, $type); } diff --git a/app/vendor/symfony/config/Loader/LoaderInterface.php b/app/vendor/symfony/config/Loader/LoaderInterface.php index 190d2c630..6ed1893a5 100644 --- a/app/vendor/symfony/config/Loader/LoaderInterface.php +++ b/app/vendor/symfony/config/Loader/LoaderInterface.php @@ -21,32 +21,24 @@ interface LoaderInterface /** * Loads a resource. * - * @return mixed - * * @throws \Exception If something went wrong */ - public function load(mixed $resource, ?string $type = null); + public function load(mixed $resource, ?string $type = null): mixed; /** * Returns whether this class supports the given resource. * * @param mixed $resource A resource - * - * @return bool */ - public function supports(mixed $resource, ?string $type = null); + public function supports(mixed $resource, ?string $type = null): bool; /** * Gets the loader resolver. - * - * @return LoaderResolverInterface */ - public function getResolver(); + public function getResolver(): LoaderResolverInterface; /** * Sets the loader resolver. - * - * @return void */ - public function setResolver(LoaderResolverInterface $resolver); + public function setResolver(LoaderResolverInterface $resolver): void; } diff --git a/app/vendor/symfony/config/Loader/LoaderResolver.php b/app/vendor/symfony/config/Loader/LoaderResolver.php index 72ab33411..8308d7e89 100644 --- a/app/vendor/symfony/config/Loader/LoaderResolver.php +++ b/app/vendor/symfony/config/Loader/LoaderResolver.php @@ -47,10 +47,7 @@ public function resolve(mixed $resource, ?string $type = null): LoaderInterface| return false; } - /** - * @return void - */ - public function addLoader(LoaderInterface $loader) + public function addLoader(LoaderInterface $loader): void { $this->loaders[] = $loader; $loader->setResolver($this); diff --git a/app/vendor/symfony/config/Loader/ParamConfigurator.php b/app/vendor/symfony/config/Loader/ParamConfigurator.php index d91de6a73..ed1045270 100644 --- a/app/vendor/symfony/config/Loader/ParamConfigurator.php +++ b/app/vendor/symfony/config/Loader/ParamConfigurator.php @@ -18,11 +18,9 @@ */ class ParamConfigurator { - private string $name; - - public function __construct(string $name) - { - $this->name = $name; + public function __construct( + private string $name, + ) { } public function __toString(): string diff --git a/app/vendor/symfony/config/Resource/ClassExistenceResource.php b/app/vendor/symfony/config/Resource/ClassExistenceResource.php index aa9177d29..e7851740c 100644 --- a/app/vendor/symfony/config/Resource/ClassExistenceResource.php +++ b/app/vendor/symfony/config/Resource/ClassExistenceResource.php @@ -23,7 +23,6 @@ */ class ClassExistenceResource implements SelfCheckingResourceInterface { - private string $resource; private ?array $exists = null; private static int $autoloadLevel = 0; @@ -34,9 +33,10 @@ class ClassExistenceResource implements SelfCheckingResourceInterface * @param string $resource The fully-qualified class name * @param bool|null $exists Boolean when the existence check has already been done */ - public function __construct(string $resource, ?bool $exists = null) - { - $this->resource = $resource; + public function __construct( + private string $resource, + ?bool $exists = null, + ) { if (null !== $exists) { $this->exists = [$exists, null]; } diff --git a/app/vendor/symfony/config/Resource/DirectoryResource.php b/app/vendor/symfony/config/Resource/DirectoryResource.php index dd43742b7..5fd5f65c9 100644 --- a/app/vendor/symfony/config/Resource/DirectoryResource.php +++ b/app/vendor/symfony/config/Resource/DirectoryResource.php @@ -21,7 +21,6 @@ class DirectoryResource implements SelfCheckingResourceInterface { private string $resource; - private ?string $pattern; /** * @param string $resource The file path to the resource @@ -29,10 +28,11 @@ class DirectoryResource implements SelfCheckingResourceInterface * * @throws \InvalidArgumentException */ - public function __construct(string $resource, ?string $pattern = null) - { + public function __construct( + string $resource, + private ?string $pattern = null, + ) { $resolvedResource = realpath($resource) ?: (file_exists($resource) ? $resource : false); - $this->pattern = $pattern; if (false === $resolvedResource || !is_dir($resolvedResource)) { throw new \InvalidArgumentException(\sprintf('The directory "%s" does not exist.', $resource)); diff --git a/app/vendor/symfony/config/Resource/FileExistenceResource.php b/app/vendor/symfony/config/Resource/FileExistenceResource.php index 666866ee4..4722537d8 100644 --- a/app/vendor/symfony/config/Resource/FileExistenceResource.php +++ b/app/vendor/symfony/config/Resource/FileExistenceResource.php @@ -23,16 +23,14 @@ */ class FileExistenceResource implements SelfCheckingResourceInterface { - private string $resource; - private bool $exists; /** * @param string $resource The file path to the resource */ - public function __construct(string $resource) - { - $this->resource = $resource; + public function __construct( + private string $resource, + ) { $this->exists = file_exists($resource); } diff --git a/app/vendor/symfony/config/Resource/GlobResource.php b/app/vendor/symfony/config/Resource/GlobResource.php index 3e381a411..e6a348e11 100644 --- a/app/vendor/symfony/config/Resource/GlobResource.php +++ b/app/vendor/symfony/config/Resource/GlobResource.php @@ -28,10 +28,7 @@ class GlobResource implements \IteratorAggregate, SelfCheckingResourceInterface { private string $prefix; - private string $pattern; - private bool $recursive; private string $hash; - private bool $forExclusion; private array $excludedPrefixes; private int $globBrace; @@ -42,13 +39,15 @@ class GlobResource implements \IteratorAggregate, SelfCheckingResourceInterface * * @throws \InvalidArgumentException */ - public function __construct(string $prefix, string $pattern, bool $recursive, bool $forExclusion = false, array $excludedPrefixes = []) - { + public function __construct( + string $prefix, + private string $pattern, + private bool $recursive, + private bool $forExclusion = false, + array $excludedPrefixes = [], + ) { ksort($excludedPrefixes); $resolvedPrefix = realpath($prefix) ?: (file_exists($prefix) ? $prefix : false); - $this->pattern = $pattern; - $this->recursive = $recursive; - $this->forExclusion = $forExclusion; $this->excludedPrefixes = $excludedPrefixes; $this->globBrace = \defined('GLOB_BRACE') ? \GLOB_BRACE : 0; diff --git a/app/vendor/symfony/config/Resource/ReflectionClassResource.php b/app/vendor/symfony/config/Resource/ReflectionClassResource.php index cfd96135d..5bf026639 100644 --- a/app/vendor/symfony/config/Resource/ReflectionClassResource.php +++ b/app/vendor/symfony/config/Resource/ReflectionClassResource.php @@ -12,7 +12,6 @@ namespace Symfony\Component\Config\Resource; use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Symfony\Component\Messenger\Handler\MessageSubscriberInterface; use Symfony\Contracts\Service\ServiceSubscriberInterface; /** @@ -24,14 +23,14 @@ class ReflectionClassResource implements SelfCheckingResourceInterface { private array $files = []; private string $className; - private \ReflectionClass $classReflector; private array $excludedVendors = []; private string $hash; - public function __construct(\ReflectionClass $classReflector, array $excludedVendors = []) - { + public function __construct( + private \ReflectionClass $classReflector, + array $excludedVendors = [], + ) { $this->className = $classReflector->name; - $this->classReflector = $classReflector; $this->excludedVendors = $excludedVendors; } @@ -82,7 +81,7 @@ private function loadFiles(\ReflectionClass $class): void $file = $class->getFileName(); if (false !== $file && is_file($file)) { foreach ($this->excludedVendors as $vendor) { - if (str_starts_with($file, $vendor) && false !== strpbrk(substr($file, \strlen($vendor), 1), '/'.\DIRECTORY_SEPARATOR)) { + if (\in_array($file[\strlen($vendor)] ?? '', ['/', \DIRECTORY_SEPARATOR], true) && str_starts_with($file, $vendor)) { $file = false; break; } @@ -163,6 +162,13 @@ private function generateSignature(\ReflectionClass $class): iterable } foreach ($class->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) as $m) { + foreach ($this->excludedVendors as $vendor) { + $file = $m->getFileName(); + if (\in_array($file[\strlen($vendor)] ?? '', ['/', \DIRECTORY_SEPARATOR], true) && str_starts_with($file, $vendor)) { + continue 2; + } + } + foreach ($m->getAttributes() as $a) { $attributes[] = [$a->getName(), (string) $a]; } @@ -199,13 +205,6 @@ private function generateSignature(\ReflectionClass $class): iterable yield print_r($class->name::getSubscribedEvents(), true); } - if (interface_exists(MessageSubscriberInterface::class, false) && $class->isSubclassOf(MessageSubscriberInterface::class)) { - yield MessageSubscriberInterface::class; - foreach ($class->name::getHandledMessages() as $key => $value) { - yield $key.print_r($value, true); - } - } - if (interface_exists(ServiceSubscriberInterface::class, false) && $class->isSubclassOf(ServiceSubscriberInterface::class)) { yield ServiceSubscriberInterface::class; yield print_r($class->name::getSubscribedServices(), true); diff --git a/app/vendor/symfony/config/Resource/SkippingResourceChecker.php b/app/vendor/symfony/config/Resource/SkippingResourceChecker.php new file mode 100644 index 000000000..0f0934c82 --- /dev/null +++ b/app/vendor/symfony/config/Resource/SkippingResourceChecker.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Resource; + +use Symfony\Component\Config\ResourceCheckerInterface; + +class SkippingResourceChecker implements ResourceCheckerInterface +{ + private array $skippedResourceTypes; + + /** + * @param class-string[] $skippedResourceTypes + */ + public function __construct(array $skippedResourceTypes = []) + { + $this->skippedResourceTypes = array_flip($skippedResourceTypes); + } + + public function supports(ResourceInterface $metadata): bool + { + return !$this->skippedResourceTypes || isset($this->skippedResourceTypes[$metadata::class]); + } + + public function isFresh(ResourceInterface $resource, int $timestamp): bool + { + return true; + } +} diff --git a/app/vendor/symfony/config/ResourceCheckerConfigCache.php b/app/vendor/symfony/config/ResourceCheckerConfigCache.php index 1e58d21ed..955aee7e5 100644 --- a/app/vendor/symfony/config/ResourceCheckerConfigCache.php +++ b/app/vendor/symfony/config/ResourceCheckerConfigCache.php @@ -23,21 +23,19 @@ */ class ResourceCheckerConfigCache implements ConfigCacheInterface { - private string $file; - - /** - * @var iterable - */ - private iterable $resourceCheckers; + private string $metaFile; /** * @param string $file The absolute cache path * @param iterable $resourceCheckers The ResourceCheckers to use for the freshness check + * @param string|null $metaFile The absolute path to the meta file, defaults to $file.meta if null */ - public function __construct(string $file, iterable $resourceCheckers = []) - { - $this->file = $file; - $this->resourceCheckers = $resourceCheckers; + public function __construct( + private string $file, + private iterable $resourceCheckers = [], + ?string $metaFile = null, + ) { + $this->metaFile = $metaFile ?? $file.'.meta'; } public function getPath(): string @@ -68,7 +66,7 @@ public function isFresh(): bool return true; // shortcut - if we don't have any checkers we don't need to bother with the meta file at all } - $metadata = $this->getMetaFile(); + $metadata = $this->metaFile; if (!is_file($metadata)) { return false; @@ -105,11 +103,9 @@ public function isFresh(): bool * @param string $content The content to write in the cache * @param ResourceInterface[] $metadata An array of metadata * - * @return void - * * @throws \RuntimeException When cache file can't be written */ - public function write(string $content, ?array $metadata = null) + public function write(string $content, ?array $metadata = null): void { $mode = 0666; $umask = umask(); @@ -122,9 +118,23 @@ public function write(string $content, ?array $metadata = null) } if (null !== $metadata) { - $filesystem->dumpFile($this->getMetaFile(), serialize($metadata)); + $filesystem->dumpFile($this->metaFile, $ser = serialize($metadata)); + try { + $filesystem->chmod($this->metaFile, $mode, $umask); + } catch (IOException) { + // discard chmod failure (some filesystem may not support it) + } + + $ser = preg_replace_callback('/;O:(\d+):"/', static fn ($m) => ';O:'.(9 + $m[1]).':"Tracking\\', $ser); + $ser = preg_replace_callback('/s:(\d+):"\0[^\0]++\0/', static fn ($m) => 's:'.($m[1] - \strlen($m[0]) + 6).':"', $ser); + $ser = unserialize($ser, ['allowed_classes' => false]); + $ser = @json_encode($ser, \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE) ?: []; + $ser = str_replace('"__PHP_Incomplete_Class_Name":"Tracking\\\\', '"@type":"', $ser); + $ser = \sprintf('{"resources":%s}', $ser); + + $filesystem->dumpFile($this->metaFile.'.json', $ser); try { - $filesystem->chmod($this->getMetaFile(), $mode, $umask); + $filesystem->chmod($this->metaFile.'.json', $mode, $umask); } catch (IOException) { // discard chmod failure (some filesystem may not support it) } @@ -135,18 +145,10 @@ public function write(string $content, ?array $metadata = null) } } - /** - * Gets the meta file path. - */ - private function getMetaFile(): string - { - return $this->file.'.meta'; - } - private function safelyUnserialize(string $file): mixed { $meta = false; - $content = file_get_contents($file); + $content = (new Filesystem())->readFile($file); $signalingException = new \UnexpectedValueException(); $prevUnserializeHandler = ini_set('unserialize_callback_func', self::class.'::handleUnserializeCallback'); $prevErrorHandler = set_error_handler(function ($type, $msg, $file, $line, $context = []) use (&$prevErrorHandler, $signalingException) { diff --git a/app/vendor/symfony/config/ResourceCheckerConfigCacheFactory.php b/app/vendor/symfony/config/ResourceCheckerConfigCacheFactory.php index 97d52006f..595855ccb 100644 --- a/app/vendor/symfony/config/ResourceCheckerConfigCacheFactory.php +++ b/app/vendor/symfony/config/ResourceCheckerConfigCacheFactory.php @@ -19,14 +19,12 @@ */ class ResourceCheckerConfigCacheFactory implements ConfigCacheFactoryInterface { - private iterable $resourceCheckers = []; - /** * @param iterable $resourceCheckers */ - public function __construct(iterable $resourceCheckers = []) - { - $this->resourceCheckers = $resourceCheckers; + public function __construct( + private iterable $resourceCheckers = [], + ) { } public function cache(string $file, callable $callable): ConfigCacheInterface diff --git a/app/vendor/symfony/config/ResourceCheckerInterface.php b/app/vendor/symfony/config/ResourceCheckerInterface.php index 6b1c6c5fb..13ae03f45 100644 --- a/app/vendor/symfony/config/ResourceCheckerInterface.php +++ b/app/vendor/symfony/config/ResourceCheckerInterface.php @@ -29,17 +29,13 @@ interface ResourceCheckerInterface /** * Queries the ResourceChecker whether it can validate a given * resource or not. - * - * @return bool */ - public function supports(ResourceInterface $metadata); + public function supports(ResourceInterface $metadata): bool; /** * Validates the resource. * * @param int $timestamp The timestamp at which the cache associated with this resource was created - * - * @return bool */ - public function isFresh(ResourceInterface $resource, int $timestamp); + public function isFresh(ResourceInterface $resource, int $timestamp): bool; } diff --git a/app/vendor/symfony/config/Util/XmlUtils.php b/app/vendor/symfony/config/Util/XmlUtils.php index 7a6ef9acc..02ebe7dbe 100644 --- a/app/vendor/symfony/config/Util/XmlUtils.php +++ b/app/vendor/symfony/config/Util/XmlUtils.php @@ -13,6 +13,7 @@ use Symfony\Component\Config\Util\Exception\InvalidXmlException; use Symfony\Component\Config\Util\Exception\XmlParsingException; +use Symfony\Component\Filesystem\Filesystem; /** * XMLUtils is a bunch of utility methods to XML operations. @@ -79,7 +80,7 @@ public static function parse(string $content, string|callable|null $schemaOrCall $valid = false; } } elseif (is_file($schemaOrCallable)) { - $schemaSource = file_get_contents((string) $schemaOrCallable); + $schemaSource = (new Filesystem())->readFile($schemaOrCallable); $valid = @$dom->schemaValidateSource($schemaSource); } else { libxml_use_internal_errors($internalErrors); @@ -122,7 +123,7 @@ public static function loadFile(string $file, string|callable|null $schemaOrCall throw new \InvalidArgumentException(\sprintf('File "%s" is not readable.', $file)); } - $content = @file_get_contents($file); + $content = (new Filesystem())->readFile($file); if ('' === trim($content)) { throw new \InvalidArgumentException(\sprintf('File "%s" does not contain valid XML, it is empty.', $file)); @@ -155,7 +156,7 @@ public static function loadFile(string $file, string|callable|null $schemaOrCall */ public static function convertDomElementToArray(\DOMElement $element, bool $checkPrefix = true): mixed { - $prefix = (string) $element->prefix; + $prefix = $element->prefix; $empty = true; $config = []; foreach ($element->attributes as $name => $node) { @@ -173,7 +174,7 @@ public static function convertDomElementToArray(\DOMElement $element, bool $chec $nodeValue = trim($node->nodeValue); $empty = false; } - } elseif ($checkPrefix && $prefix != (string) $node->prefix) { + } elseif ($checkPrefix && $prefix != $node->prefix) { continue; } elseif (!$node instanceof \DOMComment) { $value = static::convertDomElementToArray($node, $checkPrefix); @@ -238,10 +239,7 @@ public static function phpize(string|\Stringable $value): mixed } } - /** - * @return array - */ - protected static function getXmlErrors(bool $internalErrors) + protected static function getXmlErrors(bool $internalErrors): array { $errors = []; foreach (libxml_get_errors() as $error) { diff --git a/app/vendor/symfony/config/composer.json b/app/vendor/symfony/config/composer.json index dbd34f13b..37206042a 100644 --- a/app/vendor/symfony/config/composer.json +++ b/app/vendor/symfony/config/composer.json @@ -16,20 +16,20 @@ } ], "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/filesystem": "^5.4|^6.0|^7.0", + "symfony/filesystem": "^7.1", "symfony/polyfill-ctype": "~1.8" }, "require-dev": { - "symfony/event-dispatcher": "^5.4|^6.0|^7.0", - "symfony/finder": "^5.4|^6.0|^7.0", - "symfony/messenger": "^5.4|^6.0|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", "symfony/service-contracts": "^2.5|^3", - "symfony/yaml": "^5.4|^6.0|^7.0" + "symfony/yaml": "^6.4|^7.0" }, "conflict": { - "symfony/finder": "<5.4", + "symfony/finder": "<6.4", "symfony/service-contracts": "<2.5" }, "autoload": { diff --git a/app/vendor/symfony/console/Application.php b/app/vendor/symfony/console/Application.php index c18d482b4..1ea644df0 100644 --- a/app/vendor/symfony/console/Application.php +++ b/app/vendor/symfony/console/Application.php @@ -17,11 +17,11 @@ use Symfony\Component\Console\Command\HelpCommand; use Symfony\Component\Console\Command\LazyCommand; use Symfony\Component\Console\Command\ListCommand; -use Symfony\Component\Console\Command\SignalableCommandInterface; use Symfony\Component\Console\CommandLoader\CommandLoaderInterface; use Symfony\Component\Console\Completion\CompletionInput; use Symfony\Component\Console\Completion\CompletionSuggestions; use Symfony\Component\Console\Completion\Suggestion; +use Symfony\Component\Console\Event\ConsoleAlarmEvent; use Symfony\Component\Console\Event\ConsoleCommandEvent; use Symfony\Component\Console\Event\ConsoleErrorEvent; use Symfony\Component\Console\Event\ConsoleSignalEvent; @@ -75,8 +75,6 @@ class Application implements ResetInterface private array $commands = []; private bool $wantHelps = false; private ?Command $runningCommand = null; - private string $name; - private string $version; private ?CommandLoaderInterface $commandLoader = null; private bool $catchExceptions = true; private bool $catchErrors = false; @@ -90,16 +88,17 @@ class Application implements ResetInterface private bool $initialized = false; private ?SignalRegistry $signalRegistry = null; private array $signalsToDispatchEvent = []; + private ?int $alarmInterval = null; - public function __construct(string $name = 'UNKNOWN', string $version = 'UNKNOWN') - { - $this->name = $name; - $this->version = $version; + public function __construct( + private string $name = 'UNKNOWN', + private string $version = 'UNKNOWN', + ) { $this->terminal = new Terminal(); $this->defaultCommand = 'list'; if (\defined('SIGINT') && SignalRegistry::isSupported()) { $this->signalRegistry = new SignalRegistry(); - $this->signalsToDispatchEvent = [\SIGINT, \SIGTERM, \SIGUSR1, \SIGUSR2]; + $this->signalsToDispatchEvent = [\SIGINT, \SIGQUIT, \SIGTERM, \SIGUSR1, \SIGUSR2, \SIGALRM]; } } @@ -111,10 +110,7 @@ public function setDispatcher(EventDispatcherInterface $dispatcher): void $this->dispatcher = $dispatcher; } - /** - * @return void - */ - public function setCommandLoader(CommandLoaderInterface $commandLoader) + public function setCommandLoader(CommandLoaderInterface $commandLoader): void { $this->commandLoader = $commandLoader; } @@ -128,12 +124,33 @@ public function getSignalRegistry(): SignalRegistry return $this->signalRegistry; } + public function setSignalsToDispatchEvent(int ...$signalsToDispatchEvent): void + { + $this->signalsToDispatchEvent = $signalsToDispatchEvent; + } + /** - * @return void + * Sets the interval to schedule a SIGALRM signal in seconds. */ - public function setSignalsToDispatchEvent(int ...$signalsToDispatchEvent) + public function setAlarmInterval(?int $seconds): void { - $this->signalsToDispatchEvent = $signalsToDispatchEvent; + $this->alarmInterval = $seconds; + $this->scheduleAlarm(); + } + + /** + * Gets the interval in seconds on which a SIGALRM signal is dispatched. + */ + public function getAlarmInterval(): ?int + { + return $this->alarmInterval; + } + + private function scheduleAlarm(): void + { + if (null !== $this->alarmInterval) { + $this->getSignalRegistry()->scheduleAlarm($this->alarmInterval); + } } /** @@ -169,6 +186,8 @@ public function run(?InputInterface $input = null, ?OutputInterface $output = nu } } + $prevShellVerbosity = getenv('SHELL_VERBOSITY'); + try { $this->configureIO($input, $output); @@ -206,6 +225,22 @@ public function run(?InputInterface $input = null, ?OutputInterface $output = nu $phpHandler[0]->setExceptionHandler($finalHandler); } } + + // SHELL_VERBOSITY is set by Application::configureIO so we need to unset/reset it + // to its previous value to avoid one command verbosity to spread to other commands + if (false === $prevShellVerbosity) { + if (\function_exists('putenv')) { + @putenv('SHELL_VERBOSITY'); + } + unset($_ENV['SHELL_VERBOSITY']); + unset($_SERVER['SHELL_VERBOSITY']); + } else { + if (\function_exists('putenv')) { + @putenv('SHELL_VERBOSITY='.$prevShellVerbosity); + } + $_ENV['SHELL_VERBOSITY'] = $prevShellVerbosity; + $_SERVER['SHELL_VERBOSITY'] = $prevShellVerbosity; + } } if ($this->autoExit) { @@ -224,7 +259,7 @@ public function run(?InputInterface $input = null, ?OutputInterface $output = nu * * @return int 0 if everything went fine, or an error code */ - public function doRun(InputInterface $input, OutputInterface $output) + public function doRun(InputInterface $input, OutputInterface $output): int { if (true === $input->hasParameterOption(['--version', '-V'], true)) { $output->writeln($this->getLongVersion()); @@ -327,17 +362,11 @@ public function doRun(InputInterface $input, OutputInterface $output) return $exitCode; } - /** - * @return void - */ - public function reset() + public function reset(): void { } - /** - * @return void - */ - public function setHelperSet(HelperSet $helperSet) + public function setHelperSet(HelperSet $helperSet): void { $this->helperSet = $helperSet; } @@ -350,10 +379,7 @@ public function getHelperSet(): HelperSet return $this->helperSet ??= $this->getDefaultHelperSet(); } - /** - * @return void - */ - public function setDefinition(InputDefinition $definition) + public function setDefinition(InputDefinition $definition): void { $this->definition = $definition; } @@ -400,8 +426,6 @@ public function complete(CompletionInput $input, CompletionSuggestions $suggesti if (CompletionInput::TYPE_OPTION_NAME === $input->getCompletionType()) { $suggestions->suggestOptions($this->getDefinition()->getOptions()); - - return; } } @@ -423,10 +447,8 @@ public function areExceptionsCaught(): bool /** * Sets whether to catch exceptions or not during commands execution. - * - * @return void */ - public function setCatchExceptions(bool $boolean) + public function setCatchExceptions(bool $boolean): void { $this->catchExceptions = $boolean; } @@ -449,10 +471,8 @@ public function isAutoExitEnabled(): bool /** * Sets whether to automatically exit after a command execution or not. - * - * @return void */ - public function setAutoExit(bool $boolean) + public function setAutoExit(bool $boolean): void { $this->autoExit = $boolean; } @@ -467,10 +487,8 @@ public function getName(): string /** * Sets the application name. - * - * @return void */ - public function setName(string $name) + public function setName(string $name): void { $this->name = $name; } @@ -485,20 +503,16 @@ public function getVersion(): string /** * Sets the application version. - * - * @return void */ - public function setVersion(string $version) + public function setVersion(string $version): void { $this->version = $version; } /** * Returns the long version of the application. - * - * @return string */ - public function getLongVersion() + public function getLongVersion(): string { if ('UNKNOWN' !== $this->getName()) { if ('UNKNOWN' !== $this->getVersion()) { @@ -525,10 +539,8 @@ public function register(string $name): Command * If a Command is not enabled it will not be added. * * @param Command[] $commands An array of commands - * - * @return void */ - public function addCommands(array $commands) + public function addCommands(array $commands): void { foreach ($commands as $command) { $this->add($command); @@ -540,10 +552,8 @@ public function addCommands(array $commands) * * If a command with the same name already exists, it will be overridden. * If the command is not enabled it will not be added. - * - * @return Command|null */ - public function add(Command $command) + public function add(Command $command): ?Command { $this->init(); @@ -576,11 +586,9 @@ public function add(Command $command) /** * Returns a registered command by name or alias. * - * @return Command - * * @throws CommandNotFoundException When given command name does not exist */ - public function get(string $name) + public function get(string $name): Command { $this->init(); @@ -653,7 +661,7 @@ public function findNamespace(string $namespace): string $expr = implode('[^:]*:', array_map('preg_quote', explode(':', $namespace))).'[^:]*'; $namespaces = preg_grep('{^'.$expr.'}', $allNamespaces); - if (empty($namespaces)) { + if (!$namespaces) { $message = \sprintf('There are no commands defined in the "%s" namespace.', $namespace); if ($alternatives = $this->findAlternatives($namespace, $allNamespaces)) { @@ -683,11 +691,9 @@ public function findNamespace(string $namespace): string * Contrary to get, this command tries to find the best * match if you give it an abbreviation of a name or alias. * - * @return Command - * * @throws CommandNotFoundException When command name is incorrect or ambiguous */ - public function find(string $name) + public function find(string $name): Command { $this->init(); @@ -709,12 +715,12 @@ public function find(string $name) $expr = implode('[^:]*:', array_map('preg_quote', explode(':', $name))).'[^:]*'; $commands = preg_grep('{^'.$expr.'}', $allCommands); - if (empty($commands)) { + if (!$commands) { $commands = preg_grep('{^'.$expr.'}i', $allCommands); } // if no commands matched or we just matched namespaces - if (empty($commands) || \count(preg_grep('{^'.$expr.'$}i', $commands)) < 1) { + if (!$commands || \count(preg_grep('{^'.$expr.'$}i', $commands)) < 1) { if (false !== $pos = strrpos($name, ':')) { // check if a namespace exists and contains commands $this->findNamespace(substr($name, 0, $pos)); @@ -749,7 +755,7 @@ public function find(string $name) $aliases[$nameOrAlias] = $commandName; - return $commandName === $nameOrAlias || !\in_array($commandName, $commands); + return $commandName === $nameOrAlias || !\in_array($commandName, $commands, true); })); } @@ -795,7 +801,7 @@ public function find(string $name) * * @return Command[] */ - public function all(?string $namespace = null) + public function all(?string $namespace = null): array { $this->init(); @@ -936,10 +942,8 @@ protected function doRenderThrowable(\Throwable $e, OutputInterface $output): vo /** * Configures the input and output instances based on the user arguments and options. - * - * @return void */ - protected function configureIO(InputInterface $input, OutputInterface $output) + protected function configureIO(InputInterface $input, OutputInterface $output): void { if (true === $input->hasParameterOption(['--ansi'], true)) { $output->setDecorated(true); @@ -952,6 +956,9 @@ protected function configureIO(InputInterface $input, OutputInterface $output) } switch ($shellVerbosity = (int) getenv('SHELL_VERBOSITY')) { + case -2: + $output->setVerbosity(OutputInterface::VERBOSITY_SILENT); + break; case -1: $output->setVerbosity(OutputInterface::VERBOSITY_QUIET); break; @@ -969,7 +976,10 @@ protected function configureIO(InputInterface $input, OutputInterface $output) break; } - if (true === $input->hasParameterOption(['--quiet', '-q'], true)) { + if (true === $input->hasParameterOption(['--silent'], true)) { + $output->setVerbosity(OutputInterface::VERBOSITY_SILENT); + $shellVerbosity = -2; + } elseif (true === $input->hasParameterOption(['--quiet', '-q'], true)) { $output->setVerbosity(OutputInterface::VERBOSITY_QUIET); $shellVerbosity = -1; } else { @@ -985,7 +995,7 @@ protected function configureIO(InputInterface $input, OutputInterface $output) } } - if (-1 === $shellVerbosity) { + if (0 > $shellVerbosity) { $input->setInteractive(false); } @@ -1004,7 +1014,7 @@ protected function configureIO(InputInterface $input, OutputInterface $output) * * @return int 0 if everything went fine, or an error code */ - protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output) + protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output): int { foreach ($command->getHelperSet() as $helper) { if ($helper instanceof InputAwareInterface) { @@ -1012,41 +1022,48 @@ protected function doRunCommand(Command $command, InputInterface $input, OutputI } } - $commandSignals = $command instanceof SignalableCommandInterface ? $command->getSubscribedSignals() : []; - if ($commandSignals || $this->dispatcher && $this->signalsToDispatchEvent) { - if (!$this->signalRegistry) { - throw new RuntimeException('Unable to subscribe to signal events. Make sure that the "pcntl" extension is installed and that "pcntl_*" functions are not disabled by your php.ini\'s "disable_functions" directive.'); - } + if (($commandSignals = $command->getSubscribedSignals()) || $this->dispatcher && $this->signalsToDispatchEvent) { + $signalRegistry = $this->getSignalRegistry(); if (Terminal::hasSttyAvailable()) { $sttyMode = shell_exec('stty -g'); - foreach ([\SIGINT, \SIGTERM] as $signal) { - $this->signalRegistry->register($signal, static fn () => shell_exec('stty '.$sttyMode)); + foreach ([\SIGINT, \SIGQUIT, \SIGTERM] as $signal) { + $signalRegistry->register($signal, static fn () => shell_exec('stty '.$sttyMode)); } } if ($this->dispatcher) { // We register application signals, so that we can dispatch the event foreach ($this->signalsToDispatchEvent as $signal) { - $event = new ConsoleSignalEvent($command, $input, $output, $signal); - - $this->signalRegistry->register($signal, function ($signal) use ($event, $command, $commandSignals) { - $this->dispatcher->dispatch($event, ConsoleEvents::SIGNAL); - $exitCode = $event->getExitCode(); + $signalEvent = new ConsoleSignalEvent($command, $input, $output, $signal); + $alarmEvent = \SIGALRM === $signal ? new ConsoleAlarmEvent($command, $input, $output) : null; + + $signalRegistry->register($signal, function ($signal) use ($signalEvent, $alarmEvent, $command, $commandSignals, $input, $output) { + $this->dispatcher->dispatch($signalEvent, ConsoleEvents::SIGNAL); + $exitCode = $signalEvent->getExitCode(); + + if (null !== $alarmEvent) { + if (false !== $exitCode) { + $alarmEvent->setExitCode($exitCode); + } else { + $alarmEvent->abortExit(); + } + $this->dispatcher->dispatch($alarmEvent); + $exitCode = $alarmEvent->getExitCode(); + } // If the command is signalable, we call the handleSignal() method if (\in_array($signal, $commandSignals, true)) { $exitCode = $command->handleSignal($signal, $exitCode); - // BC layer for Symfony <= 5 - if (null === $exitCode) { - trigger_deprecation('symfony/console', '6.3', 'Not returning an exit code from "%s::handleSignal()" is deprecated, return "false" to keep the command running or "0" to exit successfully.', get_debug_type($command)); - $exitCode = 0; - } + } + + if (\SIGALRM === $signal) { + $this->scheduleAlarm(); } if (false !== $exitCode) { - $event = new ConsoleTerminateEvent($command, $event->getInput(), $event->getOutput(), $exitCode, $signal); + $event = new ConsoleTerminateEvent($command, $input, $output, $exitCode, $signal); $this->dispatcher->dispatch($event, ConsoleEvents::TERMINATE); exit($event->getExitCode()); @@ -1059,15 +1076,12 @@ protected function doRunCommand(Command $command, InputInterface $input, OutputI } foreach ($commandSignals as $signal) { - $this->signalRegistry->register($signal, function (int $signal) use ($command): void { - $exitCode = $command->handleSignal($signal); - // BC layer for Symfony <= 5 - if (null === $exitCode) { - trigger_deprecation('symfony/console', '6.3', 'Not returning an exit code from "%s::handleSignal()" is deprecated, return "false" to keep the command running or "0" to exit successfully.', get_debug_type($command)); - $exitCode = 0; + $signalRegistry->register($signal, function (int $signal) use ($command): void { + if (\SIGALRM === $signal) { + $this->scheduleAlarm(); } - if (false !== $exitCode) { + if (false !== $exitCode = $command->handleSignal($signal)) { exit($exitCode); } }); @@ -1133,7 +1147,8 @@ protected function getDefaultInputDefinition(): InputDefinition return new InputDefinition([ new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'), new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display help for the given command. When no command is given display help for the '.$this->defaultCommand.' command'), - new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message'), + new InputOption('--silent', null, InputOption::VALUE_NONE, 'Do not output any message'), + new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Only errors are displayed. All other output is suppressed'), new InputOption('--verbose', '-v|vv|vvv', InputOption::VALUE_NONE, 'Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug'), new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this application version'), new InputOption('--ansi', '', InputOption::VALUE_NEGATABLE, 'Force (or disable --no-ansi) ANSI output', null), diff --git a/app/vendor/symfony/console/Attribute/Argument.php b/app/vendor/symfony/console/Attribute/Argument.php new file mode 100644 index 000000000..e6a94d2f1 --- /dev/null +++ b/app/vendor/symfony/console/Attribute/Argument.php @@ -0,0 +1,110 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Attribute; + +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\Suggestion; +use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\String\UnicodeString; + +#[\Attribute(\Attribute::TARGET_PARAMETER)] +class Argument +{ + private const ALLOWED_TYPES = ['string', 'bool', 'int', 'float', 'array']; + + private string|bool|int|float|array|null $default = null; + private array|\Closure $suggestedValues; + private ?int $mode = null; + private string $function = ''; + + /** + * Represents a console command definition. + * + * If unset, the `name` value will be inferred from the parameter definition. + * + * @param array|callable(CompletionInput):list $suggestedValues The values used for input completion + */ + public function __construct( + public string $description = '', + public string $name = '', + array|callable $suggestedValues = [], + ) { + $this->suggestedValues = \is_callable($suggestedValues) ? $suggestedValues(...) : $suggestedValues; + } + + /** + * @internal + */ + public static function tryFrom(\ReflectionParameter $parameter): ?self + { + /** @var self $self */ + if (null === $self = ($parameter->getAttributes(self::class, \ReflectionAttribute::IS_INSTANCEOF)[0] ?? null)?->newInstance()) { + return null; + } + + if (($function = $parameter->getDeclaringFunction()) instanceof \ReflectionMethod) { + $self->function = $function->class.'::'.$function->name; + } else { + $self->function = $function->name; + } + + $type = $parameter->getType(); + $name = $parameter->getName(); + + if (!$type instanceof \ReflectionNamedType) { + throw new LogicException(\sprintf('The parameter "$%s" of "%s()" must have a named type. Untyped, Union or Intersection types are not supported for command arguments.', $name, $self->function)); + } + + $parameterTypeName = $type->getName(); + + if (!\in_array($parameterTypeName, self::ALLOWED_TYPES, true)) { + throw new LogicException(\sprintf('The type "%s" on parameter "$%s" of "%s()" is not supported as a command argument. Only "%s" types are allowed.', $parameterTypeName, $name, $self->function, implode('", "', self::ALLOWED_TYPES))); + } + + if (!$self->name) { + $self->name = (new UnicodeString($name))->kebab(); + } + + $self->default = $parameter->isDefaultValueAvailable() ? $parameter->getDefaultValue() : null; + + $self->mode = $parameter->isDefaultValueAvailable() || $parameter->allowsNull() ? InputArgument::OPTIONAL : InputArgument::REQUIRED; + if ('array' === $parameterTypeName) { + $self->mode |= InputArgument::IS_ARRAY; + } + + if (\is_array($self->suggestedValues) && !\is_callable($self->suggestedValues) && 2 === \count($self->suggestedValues) && ($instance = $parameter->getDeclaringFunction()->getClosureThis()) && $instance::class === $self->suggestedValues[0] && \is_callable([$instance, $self->suggestedValues[1]])) { + $self->suggestedValues = [$instance, $self->suggestedValues[1]]; + } + + return $self; + } + + /** + * @internal + */ + public function toInputArgument(): InputArgument + { + $suggestedValues = \is_callable($this->suggestedValues) ? ($this->suggestedValues)(...) : $this->suggestedValues; + + return new InputArgument($this->name, $this->mode, $this->description, $this->default, $suggestedValues); + } + + /** + * @internal + */ + public function resolveValue(InputInterface $input): mixed + { + return $input->getArgument($this->name); + } +} diff --git a/app/vendor/symfony/console/Attribute/AsCommand.php b/app/vendor/symfony/console/Attribute/AsCommand.php index b337f548f..767d46ebb 100644 --- a/app/vendor/symfony/console/Attribute/AsCommand.php +++ b/app/vendor/symfony/console/Attribute/AsCommand.php @@ -13,15 +13,25 @@ /** * Service tag to autoconfigure commands. + * + * @final since Symfony 7.3 */ #[\Attribute(\Attribute::TARGET_CLASS)] class AsCommand { + /** + * @param string $name The name of the command, used when calling it (i.e. "cache:clear") + * @param string|null $description The description of the command, displayed with the help page + * @param string[] $aliases The list of aliases of the command. The command will be executed when using one of them (i.e. "cache:clean") + * @param bool $hidden If true, the command won't be shown when listing all the available commands, but it can still be run as any other command + * @param string|null $help The help content of the command, displayed with the help page + */ public function __construct( public string $name, public ?string $description = null, array $aliases = [], bool $hidden = false, + public ?string $help = null, ) { if (!$hidden && !$aliases) { return; diff --git a/app/vendor/symfony/console/Attribute/Option.php b/app/vendor/symfony/console/Attribute/Option.php new file mode 100644 index 000000000..788353463 --- /dev/null +++ b/app/vendor/symfony/console/Attribute/Option.php @@ -0,0 +1,181 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Attribute; + +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\Suggestion; +use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\String\UnicodeString; + +#[\Attribute(\Attribute::TARGET_PARAMETER)] +class Option +{ + private const ALLOWED_TYPES = ['string', 'bool', 'int', 'float', 'array']; + private const ALLOWED_UNION_TYPES = ['bool|string', 'bool|int', 'bool|float']; + + private string|bool|int|float|array|null $default = null; + private array|\Closure $suggestedValues; + private ?int $mode = null; + private string $typeName = ''; + private bool $allowNull = false; + private string $function = ''; + + /** + * Represents a console command --option definition. + * + * If unset, the `name` value will be inferred from the parameter definition. + * + * @param array|string|null $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts + * @param array|callable(CompletionInput):list $suggestedValues The values used for input completion + */ + public function __construct( + public string $description = '', + public string $name = '', + public array|string|null $shortcut = null, + array|callable $suggestedValues = [], + ) { + $this->suggestedValues = \is_callable($suggestedValues) ? $suggestedValues(...) : $suggestedValues; + } + + /** + * @internal + */ + public static function tryFrom(\ReflectionParameter $parameter): ?self + { + /** @var self $self */ + if (null === $self = ($parameter->getAttributes(self::class, \ReflectionAttribute::IS_INSTANCEOF)[0] ?? null)?->newInstance()) { + return null; + } + + if (($function = $parameter->getDeclaringFunction()) instanceof \ReflectionMethod) { + $self->function = $function->class.'::'.$function->name; + } else { + $self->function = $function->name; + } + + $name = $parameter->getName(); + $type = $parameter->getType(); + + if (!$parameter->isDefaultValueAvailable()) { + throw new LogicException(\sprintf('The option parameter "$%s" of "%s()" must declare a default value.', $name, $self->function)); + } + + if (!$self->name) { + $self->name = (new UnicodeString($name))->kebab(); + } + + $self->default = $parameter->getDefaultValue(); + $self->allowNull = $parameter->allowsNull(); + + if ($type instanceof \ReflectionUnionType) { + return $self->handleUnion($type); + } + + if (!$type instanceof \ReflectionNamedType) { + throw new LogicException(\sprintf('The parameter "$%s" of "%s()" must have a named type. Untyped or Intersection types are not supported for command options.', $name, $self->function)); + } + + $self->typeName = $type->getName(); + + if (!\in_array($self->typeName, self::ALLOWED_TYPES, true)) { + throw new LogicException(\sprintf('The type "%s" on parameter "$%s" of "%s()" is not supported as a command option. Only "%s" types are allowed.', $self->typeName, $name, $self->function, implode('", "', self::ALLOWED_TYPES))); + } + + if ('bool' === $self->typeName && $self->allowNull && \in_array($self->default, [true, false], true)) { + throw new LogicException(\sprintf('The option parameter "$%s" of "%s()" must not be nullable when it has a default boolean value.', $name, $self->function)); + } + + if ($self->allowNull && null !== $self->default) { + throw new LogicException(\sprintf('The option parameter "$%s" of "%s()" must either be not-nullable or have a default of null.', $name, $self->function)); + } + + if ('bool' === $self->typeName) { + $self->mode = InputOption::VALUE_NONE; + if (false !== $self->default) { + $self->mode |= InputOption::VALUE_NEGATABLE; + } + } elseif ('array' === $self->typeName) { + $self->mode = InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY; + } else { + $self->mode = InputOption::VALUE_REQUIRED; + } + + if (\is_array($self->suggestedValues) && !\is_callable($self->suggestedValues) && 2 === \count($self->suggestedValues) && ($instance = $parameter->getDeclaringFunction()->getClosureThis()) && $instance::class === $self->suggestedValues[0] && \is_callable([$instance, $self->suggestedValues[1]])) { + $self->suggestedValues = [$instance, $self->suggestedValues[1]]; + } + + return $self; + } + + /** + * @internal + */ + public function toInputOption(): InputOption + { + $default = InputOption::VALUE_NONE === (InputOption::VALUE_NONE & $this->mode) ? null : $this->default; + $suggestedValues = \is_callable($this->suggestedValues) ? ($this->suggestedValues)(...) : $this->suggestedValues; + + return new InputOption($this->name, $this->shortcut, $this->mode, $this->description, $default, $suggestedValues); + } + + /** + * @internal + */ + public function resolveValue(InputInterface $input): mixed + { + $value = $input->getOption($this->name); + + if (null === $value && \in_array($this->typeName, self::ALLOWED_UNION_TYPES, true)) { + return true; + } + + if ('array' === $this->typeName && $this->allowNull && [] === $value) { + return null; + } + + if ('bool' !== $this->typeName) { + return $value; + } + + if ($this->allowNull && null === $value) { + return null; + } + + return $value ?? $this->default; + } + + private function handleUnion(\ReflectionUnionType $type): self + { + $types = array_map( + static fn (\ReflectionType $t) => $t instanceof \ReflectionNamedType ? $t->getName() : null, + $type->getTypes(), + ); + + sort($types); + + $this->typeName = implode('|', array_filter($types)); + + if (!\in_array($this->typeName, self::ALLOWED_UNION_TYPES, true)) { + throw new LogicException(\sprintf('The union type for parameter "$%s" of "%s()" is not supported as a command option. Only "%s" types are allowed.', $this->name, $this->function, implode('", "', self::ALLOWED_UNION_TYPES))); + } + + if (false !== $this->default) { + throw new LogicException(\sprintf('The option parameter "$%s" of "%s()" must have a default value of false.', $this->name, $this->function)); + } + + $this->mode = InputOption::VALUE_OPTIONAL; + + return $this; + } +} diff --git a/app/vendor/symfony/console/CHANGELOG.md b/app/vendor/symfony/console/CHANGELOG.md index 9ccb41d94..9f3ae3d7d 100644 --- a/app/vendor/symfony/console/CHANGELOG.md +++ b/app/vendor/symfony/console/CHANGELOG.md @@ -1,6 +1,44 @@ CHANGELOG ========= +7.3 +--- + + * Add `TreeHelper` and `TreeStyle` to display tree-like structures + * Add `SymfonyStyle::createTree()` + * Add support for invokable commands and add `#[Argument]` and `#[Option]` attributes to define input arguments and options + * Deprecate not declaring the parameter type in callable commands defined through `setCode` method + * Add support for help definition via `AsCommand` attribute + * Deprecate methods `Command::getDefaultName()` and `Command::getDefaultDescription()` in favor of the `#[AsCommand]` attribute + * Add support for Markdown format in `Table` + * Add support for `LockableTrait` in invokable commands + * Deprecate returning a non-integer value from a `\Closure` function set via `Command::setCode()` + * Mark `#[AsCommand]` attribute as `@final` + * Add support for `SignalableCommandInterface` with invokable commands + +7.2 +--- + + * Add support for `FORCE_COLOR` environment variable + * Add `verbosity` argument to `mustRun` process helper method + * [BC BREAK] Add silent verbosity (`--silent`/`SHELL_VERBOSITY=-2`) to suppress all output, including errors + * Add `OutputInterface::isSilent()`, `Output::isSilent()`, `OutputStyle::isSilent()` methods + * Add a configurable finished indicator to the progress indicator to show that the progress is finished + * Add ability to schedule alarm signals and a `ConsoleAlarmEvent` + +7.1 +--- + + * Add `ArgvInput::getRawTokens()` + +7.0 +--- + + * Add method `__toString()` to `InputInterface` + * Remove `Command::$defaultName` and `Command::$defaultDescription`, use the `AsCommand` attribute instead + * Require explicit argument when calling `*Command::setApplication()`, `*FormatterStyle::setForeground/setBackground()`, `Helper::setHelpSet()`, `Input*::setDefault()` and `Question::setAutocompleterCallback/setValidator()` + * Remove `StringInput::REGEX_STRING` + 6.4 --- diff --git a/app/vendor/symfony/console/CI/GithubActionReporter.php b/app/vendor/symfony/console/CI/GithubActionReporter.php index 28112c2a2..952d380d5 100644 --- a/app/vendor/symfony/console/CI/GithubActionReporter.php +++ b/app/vendor/symfony/console/CI/GithubActionReporter.php @@ -20,8 +20,6 @@ */ class GithubActionReporter { - private OutputInterface $output; - /** * @see https://github.com/actions/toolkit/blob/5e5e1b7aacba68a53836a34db4a288c3c1c1585b/packages/core/src/command.ts#L80-L85 */ @@ -42,9 +40,9 @@ class GithubActionReporter ',' => '%2C', ]; - public function __construct(OutputInterface $output) - { - $this->output = $output; + public function __construct( + private OutputInterface $output, + ) { } public static function isGithubActionEnvironment(): bool diff --git a/app/vendor/symfony/console/Command/Command.php b/app/vendor/symfony/console/Command/Command.php index 3ede6ca6b..72a10cf76 100644 --- a/app/vendor/symfony/console/Command/Command.php +++ b/app/vendor/symfony/console/Command/Command.php @@ -32,27 +32,13 @@ * * @author Fabien Potencier */ -class Command +class Command implements SignalableCommandInterface { // see https://tldp.org/LDP/abs/html/exitcodes.html public const SUCCESS = 0; public const FAILURE = 1; public const INVALID = 2; - /** - * @var string|null The default command name - * - * @deprecated since Symfony 6.1, use the AsCommand attribute instead - */ - protected static $defaultName; - - /** - * @var string|null The default command description - * - * @deprecated since Symfony 6.1, use the AsCommand attribute instead - */ - protected static $defaultDescription; - private ?Application $application = null; private ?string $name = null; private ?string $processTitle = null; @@ -63,47 +49,37 @@ class Command private string $description = ''; private ?InputDefinition $fullDefinition = null; private bool $ignoreValidationErrors = false; - private ?\Closure $code = null; + private ?InvokableCommand $code = null; private array $synopsis = []; private array $usages = []; private ?HelperSet $helperSet = null; + /** + * @deprecated since Symfony 7.3, use the #[AsCommand] attribute instead + */ public static function getDefaultName(): ?string { - $class = static::class; + trigger_deprecation('symfony/console', '7.3', 'Method "%s()" is deprecated and will be removed in Symfony 8.0, use the #[AsCommand] attribute instead.', __METHOD__); - if ($attribute = (new \ReflectionClass($class))->getAttributes(AsCommand::class)) { + if ($attribute = (new \ReflectionClass(static::class))->getAttributes(AsCommand::class)) { return $attribute[0]->newInstance()->name; } - $r = new \ReflectionProperty($class, 'defaultName'); - - if ($class !== $r->class || null === static::$defaultName) { - return null; - } - - trigger_deprecation('symfony/console', '6.1', 'Relying on the static property "$defaultName" for setting a command name is deprecated. Add the "%s" attribute to the "%s" class instead.', AsCommand::class, static::class); - - return static::$defaultName; + return null; } + /** + * @deprecated since Symfony 7.3, use the #[AsCommand] attribute instead + */ public static function getDefaultDescription(): ?string { - $class = static::class; + trigger_deprecation('symfony/console', '7.3', 'Method "%s()" is deprecated and will be removed in Symfony 8.0, use the #[AsCommand] attribute instead.', __METHOD__); - if ($attribute = (new \ReflectionClass($class))->getAttributes(AsCommand::class)) { + if ($attribute = (new \ReflectionClass(static::class))->getAttributes(AsCommand::class)) { return $attribute[0]->newInstance()->description; } - $r = new \ReflectionProperty($class, 'defaultDescription'); - - if ($class !== $r->class || null === static::$defaultDescription) { - return null; - } - - trigger_deprecation('symfony/console', '6.1', 'Relying on the static property "$defaultDescription" for setting a command description is deprecated. Add the "%s" attribute to the "%s" class instead.', AsCommand::class, static::class); - - return static::$defaultDescription; + return null; } /** @@ -115,7 +91,19 @@ public function __construct(?string $name = null) { $this->definition = new InputDefinition(); - if (null === $name && null !== $name = static::getDefaultName()) { + $attribute = ((new \ReflectionClass(static::class))->getAttributes(AsCommand::class)[0] ?? null)?->newInstance(); + + if (null === $name) { + if (self::class !== (new \ReflectionMethod($this, 'getDefaultName'))->class) { + trigger_deprecation('symfony/console', '7.3', 'Overriding "Command::getDefaultName()" in "%s" is deprecated and will be removed in Symfony 8.0, use the #[AsCommand] attribute instead.', static::class); + + $defaultName = static::getDefaultName(); + } else { + $defaultName = $attribute?->name; + } + } + + if (null === $name && null !== $name = $defaultName) { $aliases = explode('|', $name); if ('' === $name = array_shift($aliases)) { @@ -131,7 +119,23 @@ public function __construct(?string $name = null) } if ('' === $this->description) { - $this->setDescription(static::getDefaultDescription() ?? ''); + if (self::class !== (new \ReflectionMethod($this, 'getDefaultDescription'))->class) { + trigger_deprecation('symfony/console', '7.3', 'Overriding "Command::getDefaultDescription()" in "%s" is deprecated and will be removed in Symfony 8.0, use the #[AsCommand] attribute instead.', static::class); + + $defaultDescription = static::getDefaultDescription(); + } else { + $defaultDescription = $attribute?->description; + } + + $this->setDescription($defaultDescription ?? ''); + } + + if ('' === $this->help) { + $this->setHelp($attribute?->help ?? ''); + } + + if (\is_callable($this) && self::class === (new \ReflectionMethod($this, 'execute'))->getDeclaringClass()->name) { + $this->code = new InvokableCommand($this, $this(...)); } $this->configure(); @@ -141,22 +145,14 @@ public function __construct(?string $name = null) * Ignores validation errors. * * This is mainly useful for the help command. - * - * @return void */ - public function ignoreValidationErrors() + public function ignoreValidationErrors(): void { $this->ignoreValidationErrors = true; } - /** - * @return void - */ - public function setApplication(?Application $application = null) + public function setApplication(?Application $application): void { - if (1 > \func_num_args()) { - trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__); - } $this->application = $application; if ($application) { $this->setHelperSet($application->getHelperSet()); @@ -167,10 +163,7 @@ public function setApplication(?Application $application = null) $this->fullDefinition = null; } - /** - * @return void - */ - public function setHelperSet(HelperSet $helperSet) + public function setHelperSet(HelperSet $helperSet): void { $this->helperSet = $helperSet; } @@ -196,10 +189,8 @@ public function getApplication(): ?Application * * Override this to check for x or y and return false if the command cannot * run properly under the current conditions. - * - * @return bool */ - public function isEnabled() + public function isEnabled(): bool { return true; } @@ -227,7 +218,7 @@ protected function configure() * * @see setCode() */ - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { throw new LogicException('You must override the execute() method in the concrete command class.'); } @@ -321,20 +312,14 @@ public function run(InputInterface $input, OutputInterface $output): int $input->validate(); if ($this->code) { - $statusCode = ($this->code)($input, $output); - } else { - $statusCode = $this->execute($input, $output); - - if (!\is_int($statusCode)) { - throw new \TypeError(\sprintf('Return value of "%s::execute()" must be of the type int, "%s" returned.', static::class, get_debug_type($statusCode))); - } + return ($this->code)($input, $output); } - return is_numeric($statusCode) ? (int) $statusCode : 0; + return $this->execute($input, $output); } /** - * Adds suggestions to $suggestions for the current completion input (e.g. option or argument). + * Supplies suggestions when resolving possible completion options for input (e.g. option or argument). */ public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void { @@ -362,23 +347,7 @@ public function complete(CompletionInput $input, CompletionSuggestions $suggesti */ public function setCode(callable $code): static { - if ($code instanceof \Closure) { - $r = new \ReflectionFunction($code); - if (null === $r->getClosureThis()) { - set_error_handler(static function () {}); - try { - if ($c = \Closure::bind($code, $this)) { - $code = $c; - } - } finally { - restore_error_handler(); - } - } - } else { - $code = $code(...); - } - - $this->code = $code; + $this->code = new InvokableCommand($this, $code); return $this; } @@ -446,26 +415,28 @@ public function getDefinition(): InputDefinition */ public function getNativeDefinition(): InputDefinition { - return $this->definition ?? throw new LogicException(\sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', static::class)); + $definition = $this->definition ?? throw new LogicException(\sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', static::class)); + + if ($this->code && !$definition->getArguments() && !$definition->getOptions()) { + $this->code->configure($definition); + } + + return $definition; } /** * Adds an argument. * - * @param $mode The argument mode: InputArgument::REQUIRED or InputArgument::OPTIONAL - * @param $default The default value (for InputArgument::OPTIONAL mode only) + * @param $mode The argument mode: InputArgument::REQUIRED or InputArgument::OPTIONAL + * @param $default The default value (for InputArgument::OPTIONAL mode only) * @param array|\Closure(CompletionInput,CompletionSuggestions):list $suggestedValues The values used for input completion * * @return $this * * @throws InvalidArgumentException When argument mode is not valid */ - public function addArgument(string $name, ?int $mode = null, string $description = '', mixed $default = null /* array|\Closure $suggestedValues = null */): static + public function addArgument(string $name, ?int $mode = null, string $description = '', mixed $default = null, array|\Closure $suggestedValues = []): static { - $suggestedValues = 5 <= \func_num_args() ? func_get_arg(4) : []; - if (!\is_array($suggestedValues) && !$suggestedValues instanceof \Closure) { - throw new \TypeError(\sprintf('Argument 5 passed to "%s()" must be array or \Closure, "%s" given.', __METHOD__, get_debug_type($suggestedValues))); - } $this->definition->addArgument(new InputArgument($name, $mode, $description, $default, $suggestedValues)); $this->fullDefinition?->addArgument(new InputArgument($name, $mode, $description, $default, $suggestedValues)); @@ -475,21 +446,17 @@ public function addArgument(string $name, ?int $mode = null, string $description /** * Adds an option. * - * @param $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts - * @param $mode The option mode: One of the InputOption::VALUE_* constants - * @param $default The default value (must be null for InputOption::VALUE_NONE) + * @param $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts + * @param $mode The option mode: One of the InputOption::VALUE_* constants + * @param $default The default value (must be null for InputOption::VALUE_NONE) * @param array|\Closure(CompletionInput,CompletionSuggestions):list $suggestedValues The values used for input completion * * @return $this * * @throws InvalidArgumentException If option mode is invalid or incompatible */ - public function addOption(string $name, string|array|null $shortcut = null, ?int $mode = null, string $description = '', mixed $default = null /* array|\Closure $suggestedValues = [] */): static + public function addOption(string $name, string|array|null $shortcut = null, ?int $mode = null, string $description = '', mixed $default = null, array|\Closure $suggestedValues = []): static { - $suggestedValues = 6 <= \func_num_args() ? func_get_arg(5) : []; - if (!\is_array($suggestedValues) && !$suggestedValues instanceof \Closure) { - throw new \TypeError(\sprintf('Argument 5 passed to "%s()" must be array or \Closure, "%s" given.', __METHOD__, get_debug_type($suggestedValues))); - } $this->definition->addOption(new InputOption($name, $shortcut, $mode, $description, $default, $suggestedValues)); $this->fullDefinition?->addOption(new InputOption($name, $shortcut, $mode, $description, $default, $suggestedValues)); @@ -695,12 +662,10 @@ public function getUsages(): array /** * Gets a helper instance by name. * - * @return HelperInterface - * * @throws LogicException if no HelperSet is defined * @throws InvalidArgumentException if the helper is not defined */ - public function getHelper(string $name): mixed + public function getHelper(string $name): HelperInterface { if (null === $this->helperSet) { throw new LogicException(\sprintf('Cannot retrieve helper "%s" because there is no HelperSet defined. Did you forget to add your command to the application or to set the application on the command using the setApplication() method? You can also set the HelperSet directly using the setHelperSet() method.', $name)); @@ -709,6 +674,16 @@ public function getHelper(string $name): mixed return $this->helperSet->get($name); } + public function getSubscribedSignals(): array + { + return $this->code?->getSubscribedSignals() ?? []; + } + + public function handleSignal(int $signal, int|false $previousExitCode = 0): int|false + { + return $this->code?->handleSignal($signal, $previousExitCode) ?? false; + } + /** * Validates a command name. * diff --git a/app/vendor/symfony/console/Command/CompleteCommand.php b/app/vendor/symfony/console/Command/CompleteCommand.php index 33f7f93c8..15eeea16a 100644 --- a/app/vendor/symfony/console/Command/CompleteCommand.php +++ b/app/vendor/symfony/console/Command/CompleteCommand.php @@ -34,18 +34,7 @@ final class CompleteCommand extends Command { public const COMPLETION_API_VERSION = '1'; - /** - * @deprecated since Symfony 6.1 - */ - protected static $defaultName = '|_complete'; - - /** - * @deprecated since Symfony 6.1 - */ - protected static $defaultDescription = 'Internal command to provide shell completion suggestions'; - private array $completionOutputs; - private bool $isDebug = false; /** @@ -109,13 +98,13 @@ protected function execute(InputInterface $input, OutputInterface $output): int '', ''.date('Y-m-d H:i:s').'', 'Input: ("|" indicates the cursor position)', - ' '.(string) $completionInput, + ' '.$completionInput, 'Command:', - ' '.(string) implode(' ', $_SERVER['argv']), + ' '.implode(' ', $_SERVER['argv']), 'Messages:', ]); - $command = $this->findCommand($completionInput, $output); + $command = $this->findCommand($completionInput); if (null === $command) { $this->log(' No command found, completing using the Application class.'); @@ -196,7 +185,7 @@ private function createCompletionInput(InputInterface $input): CompletionInput return $completionInput; } - private function findCommand(CompletionInput $completionInput, OutputInterface $output): ?Command + private function findCommand(CompletionInput $completionInput): ?Command { try { $inputName = $completionInput->getFirstArgument(); diff --git a/app/vendor/symfony/console/Command/DumpCompletionCommand.php b/app/vendor/symfony/console/Command/DumpCompletionCommand.php index 571425b88..2853fc5f4 100644 --- a/app/vendor/symfony/console/Command/DumpCompletionCommand.php +++ b/app/vendor/symfony/console/Command/DumpCompletionCommand.php @@ -27,16 +27,6 @@ #[AsCommand(name: 'completion', description: 'Dump the shell completion script')] final class DumpCompletionCommand extends Command { - /** - * @deprecated since Symfony 6.1 - */ - protected static $defaultName = 'completion'; - - /** - * @deprecated since Symfony 6.1 - */ - protected static $defaultDescription = 'Dump the shell completion script'; - private array $supportedShells; protected function configure(): void @@ -45,7 +35,7 @@ protected function configure(): void $commandName = basename($fullCommand); $fullCommand = @realpath($fullCommand) ?: $fullCommand; - $shell = $this->guessShell(); + $shell = self::guessShell(); [$rcFile, $completionFile] = match ($shell) { 'fish' => ['~/.config/fish/config.fish', "/etc/fish/completions/$commandName.fish"], 'zsh' => ['~/.zshrc', '$fpath[1]/_'.$commandName], diff --git a/app/vendor/symfony/console/Command/HelpCommand.php b/app/vendor/symfony/console/Command/HelpCommand.php index e6447b050..a2a72dab4 100644 --- a/app/vendor/symfony/console/Command/HelpCommand.php +++ b/app/vendor/symfony/console/Command/HelpCommand.php @@ -27,10 +27,7 @@ class HelpCommand extends Command { private Command $command; - /** - * @return void - */ - protected function configure() + protected function configure(): void { $this->ignoreValidationErrors(); @@ -57,10 +54,7 @@ protected function configure() ; } - /** - * @return void - */ - public function setCommand(Command $command) + public function setCommand(Command $command): void { $this->command = $command; } diff --git a/app/vendor/symfony/console/Command/InvokableCommand.php b/app/vendor/symfony/console/Command/InvokableCommand.php new file mode 100644 index 000000000..72ff407c8 --- /dev/null +++ b/app/vendor/symfony/console/Command/InvokableCommand.php @@ -0,0 +1,157 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Command; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Attribute\Argument; +use Symfony\Component\Console\Attribute\Option; +use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Console\Exception\RuntimeException; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +/** + * Represents an invokable command. + * + * @author Yonel Ceruto + * + * @internal + */ +class InvokableCommand implements SignalableCommandInterface +{ + private readonly \Closure $code; + private readonly ?SignalableCommandInterface $signalableCommand; + private readonly \ReflectionFunction $reflection; + private bool $triggerDeprecations = false; + + public function __construct( + private readonly Command $command, + callable $code, + ) { + $this->code = $this->getClosure($code); + $this->signalableCommand = $code instanceof SignalableCommandInterface ? $code : null; + $this->reflection = new \ReflectionFunction($this->code); + } + + /** + * Invokes a callable with parameters generated from the input interface. + */ + public function __invoke(InputInterface $input, OutputInterface $output): int + { + $statusCode = ($this->code)(...$this->getParameters($input, $output)); + + if (!\is_int($statusCode)) { + if ($this->triggerDeprecations) { + trigger_deprecation('symfony/console', '7.3', \sprintf('Returning a non-integer value from the command "%s" is deprecated and will throw an exception in Symfony 8.0.', $this->command->getName())); + + return 0; + } + + throw new \TypeError(\sprintf('The command "%s" must return an integer value in the "%s" method, but "%s" was returned.', $this->command->getName(), $this->reflection->getName(), get_debug_type($statusCode))); + } + + return $statusCode; + } + + /** + * Configures the input definition from an invokable-defined function. + * + * Processes the parameters of the reflection function to extract and + * add arguments or options to the provided input definition. + */ + public function configure(InputDefinition $definition): void + { + foreach ($this->reflection->getParameters() as $parameter) { + if ($argument = Argument::tryFrom($parameter)) { + $definition->addArgument($argument->toInputArgument()); + } elseif ($option = Option::tryFrom($parameter)) { + $definition->addOption($option->toInputOption()); + } + } + } + + private function getClosure(callable $code): \Closure + { + if (!$code instanceof \Closure) { + return $code(...); + } + + $this->triggerDeprecations = true; + + if (null !== (new \ReflectionFunction($code))->getClosureThis()) { + return $code; + } + + set_error_handler(static function () {}); + try { + if ($c = \Closure::bind($code, $this->command)) { + $code = $c; + } + } finally { + restore_error_handler(); + } + + return $code; + } + + private function getParameters(InputInterface $input, OutputInterface $output): array + { + $parameters = []; + foreach ($this->reflection->getParameters() as $parameter) { + if ($argument = Argument::tryFrom($parameter)) { + $parameters[] = $argument->resolveValue($input); + + continue; + } + + if ($option = Option::tryFrom($parameter)) { + $parameters[] = $option->resolveValue($input); + + continue; + } + + $type = $parameter->getType(); + + if (!$type instanceof \ReflectionNamedType) { + if ($this->triggerDeprecations) { + trigger_deprecation('symfony/console', '7.3', \sprintf('Omitting the type declaration for the parameter "$%s" is deprecated and will throw an exception in Symfony 8.0.', $parameter->getName())); + + continue; + } + + throw new LogicException(\sprintf('The parameter "$%s" must have a named type. Untyped, Union or Intersection types are not supported.', $parameter->getName())); + } + + $parameters[] = match ($type->getName()) { + InputInterface::class => $input, + OutputInterface::class => $output, + SymfonyStyle::class => new SymfonyStyle($input, $output), + Application::class => $this->command->getApplication(), + default => throw new RuntimeException(\sprintf('Unsupported type "%s" for parameter "$%s".', $type->getName(), $parameter->getName())), + }; + } + + return $parameters ?: [$input, $output]; + } + + public function getSubscribedSignals(): array + { + return $this->signalableCommand?->getSubscribedSignals() ?? []; + } + + public function handleSignal(int $signal, int|false $previousExitCode = 0): int|false + { + return $this->signalableCommand?->handleSignal($signal, $previousExitCode) ?? false; + } +} diff --git a/app/vendor/symfony/console/Command/LazyCommand.php b/app/vendor/symfony/console/Command/LazyCommand.php index b94da6665..fd2c300d7 100644 --- a/app/vendor/symfony/console/Command/LazyCommand.php +++ b/app/vendor/symfony/console/Command/LazyCommand.php @@ -27,17 +27,21 @@ final class LazyCommand extends Command { private \Closure|Command $command; - private ?bool $isEnabled; - public function __construct(string $name, array $aliases, string $description, bool $isHidden, \Closure $commandFactory, ?bool $isEnabled = true) - { + public function __construct( + string $name, + array $aliases, + string $description, + bool $isHidden, + \Closure $commandFactory, + private ?bool $isEnabled = true, + ) { $this->setName($name) ->setAliases($aliases) ->setHidden($isHidden) ->setDescription($description); $this->command = $commandFactory; - $this->isEnabled = $isEnabled; } public function ignoreValidationErrors(): void @@ -45,11 +49,8 @@ public function ignoreValidationErrors(): void $this->getCommand()->ignoreValidationErrors(); } - public function setApplication(?Application $application = null): void + public function setApplication(?Application $application): void { - if (1 > \func_num_args()) { - trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__); - } if ($this->command instanceof parent) { $this->command->setApplication($application); } @@ -116,9 +117,8 @@ public function getNativeDefinition(): InputDefinition /** * @param array|\Closure(CompletionInput,CompletionSuggestions):list $suggestedValues The values used for input completion */ - public function addArgument(string $name, ?int $mode = null, string $description = '', mixed $default = null /* array|\Closure $suggestedValues = [] */): static + public function addArgument(string $name, ?int $mode = null, string $description = '', mixed $default = null, array|\Closure $suggestedValues = []): static { - $suggestedValues = 5 <= \func_num_args() ? func_get_arg(4) : []; $this->getCommand()->addArgument($name, $mode, $description, $default, $suggestedValues); return $this; @@ -127,9 +127,8 @@ public function addArgument(string $name, ?int $mode = null, string $description /** * @param array|\Closure(CompletionInput,CompletionSuggestions):list $suggestedValues The values used for input completion */ - public function addOption(string $name, string|array|null $shortcut = null, ?int $mode = null, string $description = '', mixed $default = null /* array|\Closure $suggestedValues = [] */): static + public function addOption(string $name, string|array|null $shortcut = null, ?int $mode = null, string $description = '', mixed $default = null, array|\Closure $suggestedValues = []): static { - $suggestedValues = 6 <= \func_num_args() ? func_get_arg(5) : []; $this->getCommand()->addOption($name, $shortcut, $mode, $description, $default, $suggestedValues); return $this; diff --git a/app/vendor/symfony/console/Command/ListCommand.php b/app/vendor/symfony/console/Command/ListCommand.php index 5850c3d7b..61b4b1b3e 100644 --- a/app/vendor/symfony/console/Command/ListCommand.php +++ b/app/vendor/symfony/console/Command/ListCommand.php @@ -25,10 +25,7 @@ */ class ListCommand extends Command { - /** - * @return void - */ - protected function configure() + protected function configure(): void { $this ->setName('list') diff --git a/app/vendor/symfony/console/Command/LockableTrait.php b/app/vendor/symfony/console/Command/LockableTrait.php index cd7548f02..b7abd2fdc 100644 --- a/app/vendor/symfony/console/Command/LockableTrait.php +++ b/app/vendor/symfony/console/Command/LockableTrait.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Console\Command; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Exception\LogicException; use Symfony\Component\Lock\LockFactory; use Symfony\Component\Lock\LockInterface; @@ -26,6 +27,8 @@ trait LockableTrait { private ?LockInterface $lock = null; + private ?LockFactory $lockFactory = null; + /** * Locks a command. */ @@ -39,13 +42,27 @@ private function lock(?string $name = null, bool $blocking = false): bool throw new LogicException('A lock is already in place.'); } - if (SemaphoreStore::isSupported()) { - $store = new SemaphoreStore(); - } else { - $store = new FlockStore(); + if (null === $this->lockFactory) { + if (SemaphoreStore::isSupported()) { + $store = new SemaphoreStore(); + } else { + $store = new FlockStore(); + } + + $this->lockFactory = new LockFactory($store); + } + + if (!$name) { + if ($this instanceof Command) { + $name = $this->getName(); + } elseif ($attribute = (new \ReflectionClass($this::class))->getAttributes(AsCommand::class)) { + $name = $attribute[0]->newInstance()->name; + } else { + throw new LogicException(\sprintf('Lock name missing: provide it via "%s()", #[AsCommand] attribute, or by extending Command class.', __METHOD__)); + } } - $this->lock = (new LockFactory($store))->createLock($name ?: $this->getName()); + $this->lock = $this->lockFactory->createLock($name); if (!$this->lock->acquire($blocking)) { $this->lock = null; diff --git a/app/vendor/symfony/console/Command/SignalableCommandInterface.php b/app/vendor/symfony/console/Command/SignalableCommandInterface.php index 74d59b086..40b301d18 100644 --- a/app/vendor/symfony/console/Command/SignalableCommandInterface.php +++ b/app/vendor/symfony/console/Command/SignalableCommandInterface.php @@ -26,9 +26,7 @@ public function getSubscribedSignals(): array; /** * The method will be called when the application is signaled. * - * @param int|false $previousExitCode - * * @return int|false The exit code to return or false to continue the normal execution */ - public function handleSignal(int $signal/* , int|false $previousExitCode = 0 */); + public function handleSignal(int $signal, int|false $previousExitCode = 0): int|false; } diff --git a/app/vendor/symfony/console/Command/TraceableCommand.php b/app/vendor/symfony/console/Command/TraceableCommand.php index 9df467b0d..ed11cc29f 100644 --- a/app/vendor/symfony/console/Command/TraceableCommand.php +++ b/app/vendor/symfony/console/Command/TraceableCommand.php @@ -27,7 +27,7 @@ * * @author Jules Pietri */ -final class TraceableCommand extends Command implements SignalableCommandInterface +final class TraceableCommand extends Command { public readonly Command $command; public int $exitCode; @@ -45,6 +45,7 @@ final class TraceableCommand extends Command implements SignalableCommandInterfa /** @var array */ public array $interactiveInputs = []; public array $handledSignals = []; + public ?array $invokableCommandInfo = null; public function __construct( Command $command, @@ -88,15 +89,11 @@ public function __call(string $name, array $arguments): mixed public function getSubscribedSignals(): array { - return $this->command instanceof SignalableCommandInterface ? $this->command->getSubscribedSignals() : []; + return $this->command->getSubscribedSignals(); } public function handleSignal(int $signal, int|false $previousExitCode = 0): int|false { - if (!$this->command instanceof SignalableCommandInterface) { - return false; - } - $event = $this->stopwatch->start($this->getName().'.handle_signal'); $exit = $this->command->handleSignal($signal, $previousExitCode); @@ -171,6 +168,18 @@ public function complete(CompletionInput $input, CompletionSuggestions $suggesti */ public function setCode(callable $code): static { + if ($code instanceof InvokableCommand) { + $r = new \ReflectionFunction(\Closure::bind(function () { + return $this->code; + }, $code, InvokableCommand::class)()); + + $this->invokableCommandInfo = [ + 'class' => $r->getClosureScopeClass()->name, + 'file' => $r->getFileName(), + 'line' => $r->getStartLine(), + ]; + } + $this->command->setCode($code); return parent::setCode(function (InputInterface $input, OutputInterface $output) use ($code): int { diff --git a/app/vendor/symfony/console/CommandLoader/ContainerCommandLoader.php b/app/vendor/symfony/console/CommandLoader/ContainerCommandLoader.php index 1638f2fd0..eb4945135 100644 --- a/app/vendor/symfony/console/CommandLoader/ContainerCommandLoader.php +++ b/app/vendor/symfony/console/CommandLoader/ContainerCommandLoader.php @@ -22,16 +22,13 @@ */ class ContainerCommandLoader implements CommandLoaderInterface { - private ContainerInterface $container; - private array $commandMap; - /** * @param array $commandMap An array with command names as keys and service ids as values */ - public function __construct(ContainerInterface $container, array $commandMap) - { - $this->container = $container; - $this->commandMap = $commandMap; + public function __construct( + private ContainerInterface $container, + private array $commandMap, + ) { } public function get(string $name): Command diff --git a/app/vendor/symfony/console/CommandLoader/FactoryCommandLoader.php b/app/vendor/symfony/console/CommandLoader/FactoryCommandLoader.php index ffe1b520c..2d13139c2 100644 --- a/app/vendor/symfony/console/CommandLoader/FactoryCommandLoader.php +++ b/app/vendor/symfony/console/CommandLoader/FactoryCommandLoader.php @@ -21,14 +21,12 @@ */ class FactoryCommandLoader implements CommandLoaderInterface { - private array $factories; - /** * @param callable[] $factories Indexed by command names */ - public function __construct(array $factories) - { - $this->factories = $factories; + public function __construct( + private array $factories, + ) { } public function has(string $name): bool diff --git a/app/vendor/symfony/console/Completion/CompletionInput.php b/app/vendor/symfony/console/Completion/CompletionInput.php index 79c2f659a..9f9619e18 100644 --- a/app/vendor/symfony/console/Completion/CompletionInput.php +++ b/app/vendor/symfony/console/Completion/CompletionInput.php @@ -123,13 +123,13 @@ public function bind(InputDefinition $definition): void if ($this->currentIndex >= \count($this->tokens)) { if (!isset($this->arguments[$argumentName]) || $this->definition->getArgument($argumentName)->isArray()) { $this->completionName = $argumentName; - $this->completionValue = ''; } else { // we've reached the end $this->completionType = self::TYPE_NONE; $this->completionName = null; - $this->completionValue = ''; } + + $this->completionValue = ''; } } @@ -226,7 +226,7 @@ private function isCursorFree(): bool return $this->currentIndex >= $nrOfTokens; } - public function __toString() + public function __toString(): string { $str = ''; foreach ($this->tokens as $i => $token) { diff --git a/app/vendor/symfony/console/Completion/Output/FishCompletionOutput.php b/app/vendor/symfony/console/Completion/Output/FishCompletionOutput.php index d2c414e48..356a974ea 100644 --- a/app/vendor/symfony/console/Completion/Output/FishCompletionOutput.php +++ b/app/vendor/symfony/console/Completion/Output/FishCompletionOutput.php @@ -21,11 +21,14 @@ class FishCompletionOutput implements CompletionOutputInterface { public function write(CompletionSuggestions $suggestions, OutputInterface $output): void { - $values = $suggestions->getValueSuggestions(); + $values = []; + foreach ($suggestions->getValueSuggestions() as $value) { + $values[] = $value->getValue().($value->getDescription() ? "\t".$value->getDescription() : ''); + } foreach ($suggestions->getOptionSuggestions() as $option) { - $values[] = '--'.$option->getName(); + $values[] = '--'.$option->getName().($option->getDescription() ? "\t".$option->getDescription() : ''); if ($option->isNegatable()) { - $values[] = '--no-'.$option->getName(); + $values[] = '--no-'.$option->getName().($option->getDescription() ? "\t".$option->getDescription() : ''); } } $output->write(implode("\n", $values)); diff --git a/app/vendor/symfony/console/Cursor.php b/app/vendor/symfony/console/Cursor.php index 45243c796..e2618cf1d 100644 --- a/app/vendor/symfony/console/Cursor.php +++ b/app/vendor/symfony/console/Cursor.php @@ -18,16 +18,16 @@ */ final class Cursor { - private OutputInterface $output; /** @var resource */ private $input; /** * @param resource|null $input */ - public function __construct(OutputInterface $output, $input = null) - { - $this->output = $output; + public function __construct( + private OutputInterface $output, + $input = null, + ) { $this->input = $input ?? (\defined('STDIN') ? \STDIN : fopen('php://input', 'r+')); } diff --git a/app/vendor/symfony/console/DataCollector/CommandDataCollector.php b/app/vendor/symfony/console/DataCollector/CommandDataCollector.php index 3cbe72b59..6dcac66bb 100644 --- a/app/vendor/symfony/console/DataCollector/CommandDataCollector.php +++ b/app/vendor/symfony/console/DataCollector/CommandDataCollector.php @@ -37,7 +37,7 @@ public function collect(Request $request, Response $response, ?\Throwable $excep $application = $command->getApplication(); $this->data = [ - 'command' => $this->cloneVar($command->command), + 'command' => $command->invokableCommandInfo ?? $this->cloneVar($command->command), 'exit_code' => $command->exitCode, 'interrupted_by_signal' => $command->interruptedBySignal, 'duration' => $command->duration, @@ -95,6 +95,10 @@ public function getName(): string */ public function getCommand(): array { + if (\is_array($this->data['command'])) { + return $this->data['command']; + } + $class = $this->data['command']->getType(); $r = new \ReflectionMethod($class, 'execute'); diff --git a/app/vendor/symfony/console/DependencyInjection/AddConsoleCommandPass.php b/app/vendor/symfony/console/DependencyInjection/AddConsoleCommandPass.php index 3cf05734b..562627f4b 100644 --- a/app/vendor/symfony/console/DependencyInjection/AddConsoleCommandPass.php +++ b/app/vendor/symfony/console/DependencyInjection/AddConsoleCommandPass.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Console\DependencyInjection; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Command\LazyCommand; use Symfony\Component\Console\CommandLoader\ContainerCommandLoader; @@ -29,10 +30,7 @@ */ class AddConsoleCommandPass implements CompilerPassInterface { - /** - * @return void - */ - public function process(ContainerBuilder $container) + public function process(ContainerBuilder $container): void { $commandServices = $container->findTaggedServiceIds('console.command', true); $lazyCommandMap = []; @@ -41,22 +39,39 @@ public function process(ContainerBuilder $container) foreach ($commandServices as $id => $tags) { $definition = $container->getDefinition($id); - $definition->addTag('container.no_preload'); $class = $container->getParameterBag()->resolveValue($definition->getClass()); - if (isset($tags[0]['command'])) { - $aliases = $tags[0]['command']; - } else { - if (!$r = $container->getReflectionClass($class)) { - throw new InvalidArgumentException(\sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); - } - if (!$r->isSubclassOf(Command::class)) { - throw new InvalidArgumentException(\sprintf('The service "%s" tagged "%s" must be a subclass of "%s".', $id, 'console.command', Command::class)); + if (!$r = $container->getReflectionClass($class)) { + throw new InvalidArgumentException(\sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); + } + + if (!$r->isSubclassOf(Command::class)) { + if (!$r->hasMethod('__invoke')) { + throw new InvalidArgumentException(\sprintf('The service "%s" tagged "%s" must either be a subclass of "%s" or have an "__invoke()" method.', $id, 'console.command', Command::class)); } - $aliases = str_replace('%', '%%', $class::getDefaultName() ?? ''); + + $invokableRef = new Reference($id); + $definition = $container->register($id .= '.command', $class = Command::class) + ->addMethodCall('setCode', [$invokableRef]); + } else { + $invokableRef = null; + } + + $definition->addTag('container.no_preload'); + + /** @var AsCommand|null $attribute */ + $attribute = ($r->getAttributes(AsCommand::class)[0] ?? null)?->newInstance(); + + if (Command::class !== (new \ReflectionMethod($class, 'getDefaultName'))->class) { + trigger_deprecation('symfony/console', '7.3', 'Overriding "Command::getDefaultName()" in "%s" is deprecated and will be removed in Symfony 8.0, use the #[AsCommand] attribute instead.', $class); + + $defaultName = $class::getDefaultName(); + } else { + $defaultName = $attribute?->name; } - $aliases = explode('|', $aliases ?? ''); + $aliases = str_replace('%', '%%', $tags[0]['command'] ?? $defaultName ?? ''); + $aliases = explode('|', $aliases); $commandName = array_shift($aliases); if ($isHidden = '' === $commandName) { @@ -75,6 +90,7 @@ public function process(ContainerBuilder $container) } $description = $tags[0]['description'] ?? null; + $help = $tags[0]['help'] ?? null; unset($tags[0]); $lazyCommandMap[$commandName] = $id; @@ -91,6 +107,7 @@ public function process(ContainerBuilder $container) } $description ??= $tag['description'] ?? null; + $help ??= $tag['help'] ?? null; } $definition->addMethodCall('setName', [$commandName]); @@ -103,18 +120,22 @@ public function process(ContainerBuilder $container) $definition->addMethodCall('setHidden', [true]); } + if ($help && $invokableRef) { + $definition->addMethodCall('setHelp', [str_replace('%', '%%', $help)]); + } + if (!$description) { - if (!$r = $container->getReflectionClass($class)) { - throw new InvalidArgumentException(\sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); - } - if (!$r->isSubclassOf(Command::class)) { - throw new InvalidArgumentException(\sprintf('The service "%s" tagged "%s" must be a subclass of "%s".', $id, 'console.command', Command::class)); + if (Command::class !== (new \ReflectionMethod($class, 'getDefaultDescription'))->class) { + trigger_deprecation('symfony/console', '7.3', 'Overriding "Command::getDefaultDescription()" in "%s" is deprecated and will be removed in Symfony 8.0, use the #[AsCommand] attribute instead.', $class); + + $description = $class::getDefaultDescription(); + } else { + $description = $attribute?->description; } - $description = str_replace('%', '%%', $class::getDefaultDescription() ?? ''); } if ($description) { - $definition->addMethodCall('setDescription', [$description]); + $definition->addMethodCall('setDescription', [str_replace('%', '%%', $description)]); $container->register('.'.$id.'.lazy', LazyCommand::class) ->setArguments([$commandName, $aliases, $description, $isHidden, new ServiceClosureArgument($lazyCommandRefs[$id])]); diff --git a/app/vendor/symfony/console/Descriptor/ApplicationDescription.php b/app/vendor/symfony/console/Descriptor/ApplicationDescription.php index 3f38379c9..802d68560 100644 --- a/app/vendor/symfony/console/Descriptor/ApplicationDescription.php +++ b/app/vendor/symfony/console/Descriptor/ApplicationDescription.php @@ -24,9 +24,6 @@ class ApplicationDescription { public const GLOBAL_NAMESPACE = '_global'; - private Application $application; - private ?string $namespace; - private bool $showHidden; private array $namespaces; /** @@ -39,11 +36,11 @@ class ApplicationDescription */ private array $aliases = []; - public function __construct(Application $application, ?string $namespace = null, bool $showHidden = false) - { - $this->application = $application; - $this->namespace = $namespace; - $this->showHidden = $showHidden; + public function __construct( + private Application $application, + private ?string $namespace = null, + private bool $showHidden = false, + ) { } public function getNamespaces(): array diff --git a/app/vendor/symfony/console/Descriptor/DescriptorInterface.php b/app/vendor/symfony/console/Descriptor/DescriptorInterface.php index ab468a256..04e5a7c86 100644 --- a/app/vendor/symfony/console/Descriptor/DescriptorInterface.php +++ b/app/vendor/symfony/console/Descriptor/DescriptorInterface.php @@ -20,8 +20,5 @@ */ interface DescriptorInterface { - /** - * @return void - */ - public function describe(OutputInterface $output, object $object, array $options = []); + public function describe(OutputInterface $output, object $object, array $options = []): void; } diff --git a/app/vendor/symfony/console/Descriptor/ReStructuredTextDescriptor.php b/app/vendor/symfony/console/Descriptor/ReStructuredTextDescriptor.php index a2b754276..d2dde6fba 100644 --- a/app/vendor/symfony/console/Descriptor/ReStructuredTextDescriptor.php +++ b/app/vendor/symfony/console/Descriptor/ReStructuredTextDescriptor.php @@ -92,7 +92,7 @@ protected function describeInputOption(InputOption $option, array $options = []) protected function describeInputDefinition(InputDefinition $definition, array $options = []): void { if ($showArguments = ((bool) $definition->getArguments())) { - $this->write("Arguments\n".str_repeat($this->subsubsectionChar, 9))."\n\n"; + $this->write("Arguments\n".str_repeat($this->subsubsectionChar, 9)); foreach ($definition->getArguments() as $argument) { $this->write("\n\n"); $this->describeInputArgument($argument); @@ -217,6 +217,7 @@ private function getNonDefaultOptions(InputDefinition $definition): array { $globalOptions = [ 'help', + 'silent', 'quiet', 'verbose', 'version', @@ -226,7 +227,7 @@ private function getNonDefaultOptions(InputDefinition $definition): array $nonDefaultOptions = []; foreach ($definition->getOptions() as $option) { // Skip global options. - if (!\in_array($option->getName(), $globalOptions)) { + if (!\in_array($option->getName(), $globalOptions, true)) { $nonDefaultOptions[] = $option; } } diff --git a/app/vendor/symfony/console/Descriptor/XmlDescriptor.php b/app/vendor/symfony/console/Descriptor/XmlDescriptor.php index 866c71856..00055557c 100644 --- a/app/vendor/symfony/console/Descriptor/XmlDescriptor.php +++ b/app/vendor/symfony/console/Descriptor/XmlDescriptor.php @@ -208,11 +208,9 @@ private function getInputOptionDocument(InputOption $option): \DOMDocument $defaults = \is_array($option->getDefault()) ? $option->getDefault() : (\is_bool($option->getDefault()) ? [var_export($option->getDefault(), true)] : ($option->getDefault() ? [$option->getDefault()] : [])); $objectXML->appendChild($defaultsXML = $dom->createElement('defaults')); - if (!empty($defaults)) { - foreach ($defaults as $default) { - $defaultsXML->appendChild($defaultXML = $dom->createElement('default')); - $defaultXML->appendChild($dom->createTextNode($default)); - } + foreach ($defaults as $default) { + $defaultsXML->appendChild($defaultXML = $dom->createElement('default')); + $defaultXML->appendChild($dom->createTextNode($default)); } } diff --git a/app/vendor/symfony/console/Event/ConsoleAlarmEvent.php b/app/vendor/symfony/console/Event/ConsoleAlarmEvent.php new file mode 100644 index 000000000..876ab59b9 --- /dev/null +++ b/app/vendor/symfony/console/Event/ConsoleAlarmEvent.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 Symfony\Component\Console\Event; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +final class ConsoleAlarmEvent extends ConsoleEvent +{ + public function __construct( + Command $command, + InputInterface $input, + OutputInterface $output, + private int|false $exitCode = 0, + ) { + parent::__construct($command, $input, $output); + } + + public function setExitCode(int $exitCode): void + { + if ($exitCode < 0 || $exitCode > 255) { + throw new \InvalidArgumentException('Exit code must be between 0 and 255.'); + } + + $this->exitCode = $exitCode; + } + + public function abortExit(): void + { + $this->exitCode = false; + } + + public function getExitCode(): int|false + { + return $this->exitCode; + } +} diff --git a/app/vendor/symfony/console/Event/ConsoleErrorEvent.php b/app/vendor/symfony/console/Event/ConsoleErrorEvent.php index 7be2ff83e..1c0d62652 100644 --- a/app/vendor/symfony/console/Event/ConsoleErrorEvent.php +++ b/app/vendor/symfony/console/Event/ConsoleErrorEvent.php @@ -22,14 +22,15 @@ */ final class ConsoleErrorEvent extends ConsoleEvent { - private \Throwable $error; private int $exitCode; - public function __construct(InputInterface $input, OutputInterface $output, \Throwable $error, ?Command $command = null) - { + public function __construct( + InputInterface $input, + OutputInterface $output, + private \Throwable $error, + ?Command $command = null, + ) { parent::__construct($command, $input, $output); - - $this->error = $error; } public function getError(): \Throwable diff --git a/app/vendor/symfony/console/Event/ConsoleEvent.php b/app/vendor/symfony/console/Event/ConsoleEvent.php index 6ba1615fe..2f9f0778e 100644 --- a/app/vendor/symfony/console/Event/ConsoleEvent.php +++ b/app/vendor/symfony/console/Event/ConsoleEvent.php @@ -23,16 +23,11 @@ */ class ConsoleEvent extends Event { - protected $command; - - private InputInterface $input; - private OutputInterface $output; - - public function __construct(?Command $command, InputInterface $input, OutputInterface $output) - { - $this->command = $command; - $this->input = $input; - $this->output = $output; + public function __construct( + protected ?Command $command, + private InputInterface $input, + private OutputInterface $output, + ) { } /** diff --git a/app/vendor/symfony/console/Event/ConsoleSignalEvent.php b/app/vendor/symfony/console/Event/ConsoleSignalEvent.php index 95af1f915..b27f08a18 100644 --- a/app/vendor/symfony/console/Event/ConsoleSignalEvent.php +++ b/app/vendor/symfony/console/Event/ConsoleSignalEvent.php @@ -20,14 +20,14 @@ */ final class ConsoleSignalEvent extends ConsoleEvent { - private int $handlingSignal; - private int|false $exitCode; - - public function __construct(Command $command, InputInterface $input, OutputInterface $output, int $handlingSignal, int|false $exitCode = 0) - { + public function __construct( + Command $command, + InputInterface $input, + OutputInterface $output, + private int $handlingSignal, + private int|false $exitCode = 0, + ) { parent::__construct($command, $input, $output); - $this->handlingSignal = $handlingSignal; - $this->exitCode = $exitCode; } public function getHandlingSignal(): int diff --git a/app/vendor/symfony/console/EventListener/ErrorListener.php b/app/vendor/symfony/console/EventListener/ErrorListener.php index c9ec24434..9acb0e41d 100644 --- a/app/vendor/symfony/console/EventListener/ErrorListener.php +++ b/app/vendor/symfony/console/EventListener/ErrorListener.php @@ -24,17 +24,12 @@ */ class ErrorListener implements EventSubscriberInterface { - private ?LoggerInterface $logger; - - public function __construct(?LoggerInterface $logger = null) - { - $this->logger = $logger; + public function __construct( + private ?LoggerInterface $logger = null, + ) { } - /** - * @return void - */ - public function onConsoleError(ConsoleErrorEvent $event) + public function onConsoleError(ConsoleErrorEvent $event): void { if (null === $this->logger) { return; @@ -42,7 +37,7 @@ public function onConsoleError(ConsoleErrorEvent $event) $error = $event->getError(); - if (!$inputString = $this->getInputString($event)) { + if (!$inputString = self::getInputString($event)) { $this->logger->critical('An error occurred while using the console. Message: "{message}"', ['exception' => $error, 'message' => $error->getMessage()]); return; @@ -51,10 +46,7 @@ public function onConsoleError(ConsoleErrorEvent $event) $this->logger->critical('Error thrown while running command "{command}". Message: "{message}"', ['exception' => $error, 'command' => $inputString, 'message' => $error->getMessage()]); } - /** - * @return void - */ - public function onConsoleTerminate(ConsoleTerminateEvent $event) + public function onConsoleTerminate(ConsoleTerminateEvent $event): void { if (null === $this->logger) { return; @@ -66,7 +58,7 @@ public function onConsoleTerminate(ConsoleTerminateEvent $event) return; } - if (!$inputString = $this->getInputString($event)) { + if (!$inputString = self::getInputString($event)) { $this->logger->debug('The console exited with code "{code}"', ['code' => $exitCode]); return; @@ -83,19 +75,15 @@ public static function getSubscribedEvents(): array ]; } - private static function getInputString(ConsoleEvent $event): ?string + private static function getInputString(ConsoleEvent $event): string { $commandName = $event->getCommand()?->getName(); - $input = $event->getInput(); - - if ($input instanceof \Stringable) { - if ($commandName) { - return str_replace(["'$commandName'", "\"$commandName\""], $commandName, (string) $input); - } + $inputString = (string) $event->getInput(); - return (string) $input; + if ($commandName) { + return str_replace(["'$commandName'", "\"$commandName\""], $commandName, $inputString); } - return $commandName; + return $inputString; } } diff --git a/app/vendor/symfony/console/Exception/CommandNotFoundException.php b/app/vendor/symfony/console/Exception/CommandNotFoundException.php index 541b32b23..246f04fa2 100644 --- a/app/vendor/symfony/console/Exception/CommandNotFoundException.php +++ b/app/vendor/symfony/console/Exception/CommandNotFoundException.php @@ -18,19 +18,19 @@ */ class CommandNotFoundException extends \InvalidArgumentException implements ExceptionInterface { - private array $alternatives; - /** * @param string $message Exception message to throw * @param string[] $alternatives List of similar defined names * @param int $code Exception code * @param \Throwable|null $previous Previous exception used for the exception chaining */ - public function __construct(string $message, array $alternatives = [], int $code = 0, ?\Throwable $previous = null) - { + public function __construct( + string $message, + private array $alternatives = [], + int $code = 0, + ?\Throwable $previous = null, + ) { parent::__construct($message, $code, $previous); - - $this->alternatives = $alternatives; } /** diff --git a/app/vendor/symfony/console/Formatter/NullOutputFormatterStyle.php b/app/vendor/symfony/console/Formatter/NullOutputFormatterStyle.php index ae23decb1..06fa6e40b 100644 --- a/app/vendor/symfony/console/Formatter/NullOutputFormatterStyle.php +++ b/app/vendor/symfony/console/Formatter/NullOutputFormatterStyle.php @@ -21,19 +21,13 @@ public function apply(string $text): string return $text; } - public function setBackground(?string $color = null): void + public function setBackground(?string $color): void { - if (1 > \func_num_args()) { - trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__); - } // do nothing } - public function setForeground(?string $color = null): void + public function setForeground(?string $color): void { - if (1 > \func_num_args()) { - trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__); - } // do nothing } diff --git a/app/vendor/symfony/console/Formatter/OutputFormatter.php b/app/vendor/symfony/console/Formatter/OutputFormatter.php index c37a4d452..c72728b27 100644 --- a/app/vendor/symfony/console/Formatter/OutputFormatter.php +++ b/app/vendor/symfony/console/Formatter/OutputFormatter.php @@ -24,7 +24,6 @@ */ class OutputFormatter implements WrappableOutputFormatterInterface { - private bool $decorated; private array $styles = []; private OutputFormatterStyleStack $styleStack; @@ -68,10 +67,10 @@ public static function escapeTrailingBackslash(string $text): string * * @param OutputFormatterStyleInterface[] $styles Array of "name => FormatterStyle" instances */ - public function __construct(bool $decorated = false, array $styles = []) - { - $this->decorated = $decorated; - + public function __construct( + private bool $decorated = false, + array $styles = [], + ) { $this->setStyle('error', new OutputFormatterStyle('white', 'red')); $this->setStyle('info', new OutputFormatterStyle('green')); $this->setStyle('comment', new OutputFormatterStyle('yellow')); @@ -84,10 +83,7 @@ public function __construct(bool $decorated = false, array $styles = []) $this->styleStack = new OutputFormatterStyleStack(); } - /** - * @return void - */ - public function setDecorated(bool $decorated) + public function setDecorated(bool $decorated): void { $this->decorated = $decorated; } @@ -97,10 +93,7 @@ public function isDecorated(): bool return $this->decorated; } - /** - * @return void - */ - public function setStyle(string $name, OutputFormatterStyleInterface $style) + public function setStyle(string $name, OutputFormatterStyleInterface $style): void { $this->styles[strtolower($name)] = $style; } @@ -124,10 +117,7 @@ public function format(?string $message): ?string return $this->formatAndWrap($message, 0); } - /** - * @return string - */ - public function formatAndWrap(?string $message, int $width) + public function formatAndWrap(?string $message, int $width): string { if (null === $message) { return ''; @@ -285,6 +275,6 @@ private function addLineBreaks(string $text, int $width): string { $encoding = mb_detect_encoding($text, null, true) ?: 'UTF-8'; - return b($text)->toCodePointString($encoding)->wordwrap($width, "\n", true)->toByteString($encoding); + return b($text)->toUnicodeString($encoding)->wordwrap($width, "\n", true)->toByteString($encoding); } } diff --git a/app/vendor/symfony/console/Formatter/OutputFormatterInterface.php b/app/vendor/symfony/console/Formatter/OutputFormatterInterface.php index 433cd4197..947347fa7 100644 --- a/app/vendor/symfony/console/Formatter/OutputFormatterInterface.php +++ b/app/vendor/symfony/console/Formatter/OutputFormatterInterface.php @@ -20,10 +20,8 @@ interface OutputFormatterInterface { /** * Sets the decorated flag. - * - * @return void */ - public function setDecorated(bool $decorated); + public function setDecorated(bool $decorated): void; /** * Whether the output will decorate messages. @@ -32,10 +30,8 @@ public function isDecorated(): bool; /** * Sets a new style. - * - * @return void */ - public function setStyle(string $name, OutputFormatterStyleInterface $style); + public function setStyle(string $name, OutputFormatterStyleInterface $style): void; /** * Checks if output formatter has style with specified name. diff --git a/app/vendor/symfony/console/Formatter/OutputFormatterStyle.php b/app/vendor/symfony/console/Formatter/OutputFormatterStyle.php index 21e7f5ab0..20a65b517 100644 --- a/app/vendor/symfony/console/Formatter/OutputFormatterStyle.php +++ b/app/vendor/symfony/console/Formatter/OutputFormatterStyle.php @@ -38,25 +38,13 @@ public function __construct(?string $foreground = null, ?string $background = nu $this->color = new Color($this->foreground = $foreground ?: '', $this->background = $background ?: '', $this->options = $options); } - /** - * @return void - */ - public function setForeground(?string $color = null) + public function setForeground(?string $color): void { - if (1 > \func_num_args()) { - trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__); - } $this->color = new Color($this->foreground = $color ?: '', $this->background, $this->options); } - /** - * @return void - */ - public function setBackground(?string $color = null) + public function setBackground(?string $color): void { - if (1 > \func_num_args()) { - trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__); - } $this->color = new Color($this->foreground, $this->background = $color ?: '', $this->options); } @@ -65,19 +53,13 @@ public function setHref(string $url): void $this->href = $url; } - /** - * @return void - */ - public function setOption(string $option) + public function setOption(string $option): void { $this->options[] = $option; $this->color = new Color($this->foreground, $this->background, $this->options); } - /** - * @return void - */ - public function unsetOption(string $option) + public function unsetOption(string $option): void { $pos = array_search($option, $this->options); if (false !== $pos) { @@ -87,10 +69,7 @@ public function unsetOption(string $option) $this->color = new Color($this->foreground, $this->background, $this->options); } - /** - * @return void - */ - public function setOptions(array $options) + public function setOptions(array $options): void { $this->color = new Color($this->foreground, $this->background, $this->options = $options); } diff --git a/app/vendor/symfony/console/Formatter/OutputFormatterStyleInterface.php b/app/vendor/symfony/console/Formatter/OutputFormatterStyleInterface.php index 3b15098cb..037419277 100644 --- a/app/vendor/symfony/console/Formatter/OutputFormatterStyleInterface.php +++ b/app/vendor/symfony/console/Formatter/OutputFormatterStyleInterface.php @@ -20,38 +20,28 @@ interface OutputFormatterStyleInterface { /** * Sets style foreground color. - * - * @return void */ - public function setForeground(?string $color); + public function setForeground(?string $color): void; /** * Sets style background color. - * - * @return void */ - public function setBackground(?string $color); + public function setBackground(?string $color): void; /** * Sets some specific style option. - * - * @return void */ - public function setOption(string $option); + public function setOption(string $option): void; /** * Unsets some specific style option. - * - * @return void */ - public function unsetOption(string $option); + public function unsetOption(string $option): void; /** * Sets multiple style options at once. - * - * @return void */ - public function setOptions(array $options); + public function setOptions(array $options): void; /** * Applies the style to a given text. diff --git a/app/vendor/symfony/console/Formatter/OutputFormatterStyleStack.php b/app/vendor/symfony/console/Formatter/OutputFormatterStyleStack.php index 62d2ca0e7..4985213ab 100644 --- a/app/vendor/symfony/console/Formatter/OutputFormatterStyleStack.php +++ b/app/vendor/symfony/console/Formatter/OutputFormatterStyleStack.php @@ -34,20 +34,16 @@ public function __construct(?OutputFormatterStyleInterface $emptyStyle = null) /** * Resets stack (ie. empty internal arrays). - * - * @return void */ - public function reset() + public function reset(): void { $this->styles = []; } /** * Pushes a style in the stack. - * - * @return void */ - public function push(OutputFormatterStyleInterface $style) + public function push(OutputFormatterStyleInterface $style): void { $this->styles[] = $style; } diff --git a/app/vendor/symfony/console/Formatter/WrappableOutputFormatterInterface.php b/app/vendor/symfony/console/Formatter/WrappableOutputFormatterInterface.php index 746cd27e7..412d9976f 100644 --- a/app/vendor/symfony/console/Formatter/WrappableOutputFormatterInterface.php +++ b/app/vendor/symfony/console/Formatter/WrappableOutputFormatterInterface.php @@ -20,8 +20,6 @@ interface WrappableOutputFormatterInterface extends OutputFormatterInterface { /** * Formats a message according to the given styles, wrapping at `$width` (0 means no wrapping). - * - * @return string */ - public function formatAndWrap(?string $message, int $width); + public function formatAndWrap(?string $message, int $width): string; } diff --git a/app/vendor/symfony/console/Helper/DescriptorHelper.php b/app/vendor/symfony/console/Helper/DescriptorHelper.php index fda6779b9..9422271fb 100644 --- a/app/vendor/symfony/console/Helper/DescriptorHelper.php +++ b/app/vendor/symfony/console/Helper/DescriptorHelper.php @@ -50,11 +50,9 @@ public function __construct() * * format: string, the output format name * * raw_text: boolean, sets output type as raw * - * @return void - * * @throws InvalidArgumentException when the given format is not supported */ - public function describe(OutputInterface $output, ?object $object, array $options = []) + public function describe(OutputInterface $output, ?object $object, array $options = []): void { $options = array_merge([ 'raw_text' => false, diff --git a/app/vendor/symfony/console/Helper/Dumper.php b/app/vendor/symfony/console/Helper/Dumper.php index a3b8e3952..0cd01e616 100644 --- a/app/vendor/symfony/console/Helper/Dumper.php +++ b/app/vendor/symfony/console/Helper/Dumper.php @@ -21,17 +21,13 @@ */ final class Dumper { - private OutputInterface $output; - private ?CliDumper $dumper; - private ?ClonerInterface $cloner; private \Closure $handler; - public function __construct(OutputInterface $output, ?CliDumper $dumper = null, ?ClonerInterface $cloner = null) - { - $this->output = $output; - $this->dumper = $dumper; - $this->cloner = $cloner; - + public function __construct( + private OutputInterface $output, + private ?CliDumper $dumper = null, + private ?ClonerInterface $cloner = null, + ) { if (class_exists(CliDumper::class)) { $this->handler = function ($var): string { $dumper = $this->dumper ??= new CliDumper(null, null, CliDumper::DUMP_LIGHT_ARRAY | CliDumper::DUMP_COMMA_SEPARATOR); diff --git a/app/vendor/symfony/console/Helper/Helper.php b/app/vendor/symfony/console/Helper/Helper.php index 468d06689..46e7e2f58 100644 --- a/app/vendor/symfony/console/Helper/Helper.php +++ b/app/vendor/symfony/console/Helper/Helper.php @@ -21,16 +21,10 @@ */ abstract class Helper implements HelperInterface { - protected $helperSet; + protected ?HelperSet $helperSet = null; - /** - * @return void - */ - public function setHelperSet(?HelperSet $helperSet = null) + public function setHelperSet(?HelperSet $helperSet): void { - if (1 > \func_num_args()) { - trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__); - } $this->helperSet = $helperSet; } @@ -97,53 +91,49 @@ public static function substr(?string $string, int $from, ?int $length = null): return mb_substr($string, $from, $length, $encoding); } - /** - * @return string - */ - public static function formatTime(int|float $secs, int $precision = 1) + public static function formatTime(int|float $secs, int $precision = 1): string { + $ms = (int) ($secs * 1000); $secs = (int) floor($secs); - if (0 === $secs) { - return '< 1 sec'; + if (0 === $ms) { + return '< 1 ms'; } static $timeFormats = [ - [1, '1 sec', 'secs'], - [60, '1 min', 'mins'], - [3600, '1 hr', 'hrs'], - [86400, '1 day', 'days'], + [1, 'ms'], + [1000, 's'], + [60000, 'min'], + [3600000, 'h'], + [86_400_000, 'd'], ]; $times = []; foreach ($timeFormats as $index => $format) { - $seconds = isset($timeFormats[$index + 1]) ? $secs % $timeFormats[$index + 1][0] : $secs; + $milliSeconds = isset($timeFormats[$index + 1]) ? $ms % $timeFormats[$index + 1][0] : $ms; if (isset($times[$index - $precision])) { unset($times[$index - $precision]); } - if (0 === $seconds) { + if (0 === $milliSeconds) { continue; } - $unitCount = ($seconds / $format[0]); - $times[$index] = 1 === $unitCount ? $format[1] : $unitCount.' '.$format[2]; + $unitCount = ($milliSeconds / $format[0]); + $times[$index] = $unitCount.' '.$format[1]; - if ($secs === $seconds) { + if ($ms === $milliSeconds) { break; } - $secs -= $seconds; + $ms -= $milliSeconds; } return implode(', ', array_reverse($times)); } - /** - * @return string - */ - public static function formatMemory(int $memory) + public static function formatMemory(int $memory): string { if ($memory >= 1024 * 1024 * 1024) { return \sprintf('%.1f GiB', $memory / 1024 / 1024 / 1024); @@ -160,10 +150,7 @@ public static function formatMemory(int $memory) return \sprintf('%d B', $memory); } - /** - * @return string - */ - public static function removeDecoration(OutputFormatterInterface $formatter, ?string $string) + public static function removeDecoration(OutputFormatterInterface $formatter, ?string $string): string { $isDecorated = $formatter->isDecorated(); $formatter->setDecorated(false); diff --git a/app/vendor/symfony/console/Helper/HelperInterface.php b/app/vendor/symfony/console/Helper/HelperInterface.php index ab626c938..8c4da3c91 100644 --- a/app/vendor/symfony/console/Helper/HelperInterface.php +++ b/app/vendor/symfony/console/Helper/HelperInterface.php @@ -20,10 +20,8 @@ interface HelperInterface { /** * Sets the helper set associated with this helper. - * - * @return void */ - public function setHelperSet(?HelperSet $helperSet); + public function setHelperSet(?HelperSet $helperSet): void; /** * Gets the helper set associated with this helper. @@ -32,8 +30,6 @@ public function getHelperSet(): ?HelperSet; /** * Returns the canonical name of this helper. - * - * @return string */ - public function getName(); + public function getName(): string; } diff --git a/app/vendor/symfony/console/Helper/HelperSet.php b/app/vendor/symfony/console/Helper/HelperSet.php index 8deb22ee7..ffe756c9d 100644 --- a/app/vendor/symfony/console/Helper/HelperSet.php +++ b/app/vendor/symfony/console/Helper/HelperSet.php @@ -35,10 +35,7 @@ public function __construct(array $helpers = []) } } - /** - * @return void - */ - public function set(HelperInterface $helper, ?string $alias = null) + public function set(HelperInterface $helper, ?string $alias = null): void { $this->helpers[$helper->getName()] = $helper; if (null !== $alias) { diff --git a/app/vendor/symfony/console/Helper/InputAwareHelper.php b/app/vendor/symfony/console/Helper/InputAwareHelper.php index 6f8225973..47126bdaa 100644 --- a/app/vendor/symfony/console/Helper/InputAwareHelper.php +++ b/app/vendor/symfony/console/Helper/InputAwareHelper.php @@ -21,12 +21,9 @@ */ abstract class InputAwareHelper extends Helper implements InputAwareInterface { - protected $input; + protected InputInterface $input; - /** - * @return void - */ - public function setInput(InputInterface $input) + public function setInput(InputInterface $input): void { $this->input = $input; } diff --git a/app/vendor/symfony/console/Helper/ProcessHelper.php b/app/vendor/symfony/console/Helper/ProcessHelper.php index ae55a83c2..4a8cfc9d9 100644 --- a/app/vendor/symfony/console/Helper/ProcessHelper.php +++ b/app/vendor/symfony/console/Helper/ProcessHelper.php @@ -94,9 +94,9 @@ public function run(OutputInterface $output, array|Process $cmd, ?string $error * * @see run() */ - public function mustRun(OutputInterface $output, array|Process $cmd, ?string $error = null, ?callable $callback = null): Process + public function mustRun(OutputInterface $output, array|Process $cmd, ?string $error = null, ?callable $callback = null, int $verbosity = OutputInterface::VERBOSITY_VERY_VERBOSE): Process { - $process = $this->run($output, $cmd, $error, $callback); + $process = $this->run($output, $cmd, $error, $callback, $verbosity); if (!$process->isSuccessful()) { throw new ProcessFailedException($process); diff --git a/app/vendor/symfony/console/Helper/ProgressBar.php b/app/vendor/symfony/console/Helper/ProgressBar.php index 8143acaff..dc3605ad2 100644 --- a/app/vendor/symfony/console/Helper/ProgressBar.php +++ b/app/vendor/symfony/console/Helper/ProgressBar.php @@ -195,7 +195,7 @@ public function getStartTime(): int public function getMaxSteps(): int { - return $this->max; + return $this->max ?? 0; } public function getProgress(): int @@ -215,7 +215,7 @@ public function getProgressPercent(): float public function getBarOffset(): float { - return floor($this->max ? $this->percent * $this->barWidth : (null === $this->redrawFreq ? (int) (min(5, $this->barWidth / 15) * $this->writeCount) : $this->step) % $this->barWidth); + return floor(null !== $this->max ? $this->percent * $this->barWidth : (null === $this->redrawFreq ? (int) (min(5, $this->barWidth / 15) * $this->writeCount) : $this->step) % $this->barWidth); } public function getEstimated(): float @@ -253,7 +253,7 @@ public function setBarCharacter(string $char): void public function getBarCharacter(): string { - return $this->barChar ?? ($this->max ? '=' : $this->emptyBarChar); + return $this->barChar ?? (null !== $this->max ? '=' : $this->emptyBarChar); } public function setEmptyBarCharacter(string $char): void @@ -315,7 +315,21 @@ public function maxSecondsBetweenRedraws(float $seconds): void */ public function iterate(iterable $iterable, ?int $max = null): iterable { - $this->start($max ?? (is_countable($iterable) ? \count($iterable) : 0)); + if (0 === $max) { + $max = null; + } + + $max ??= is_countable($iterable) ? \count($iterable) : null; + + if (0 === $max) { + $this->max = 0; + $this->stepWidth = 2; + $this->finish(); + + return; + } + + $this->start($max); foreach ($iterable as $key => $value) { yield $key => $value; @@ -373,11 +387,15 @@ public function setProgress(int $step): void $step = 0; } - $redrawFreq = $this->redrawFreq ?? (($this->max ?: 10) / 10); - $prevPeriod = (int) ($this->step / $redrawFreq); - $currPeriod = (int) ($step / $redrawFreq); + $redrawFreq = $this->redrawFreq ?? (($this->max ?? 10) / 10); + $prevPeriod = $redrawFreq ? (int) ($this->step / $redrawFreq) : 0; + $currPeriod = $redrawFreq ? (int) ($step / $redrawFreq) : 0; $this->step = $step; - $this->percent = $this->max ? (float) $this->step / $this->max : 0; + $this->percent = match ($this->max) { + null => 0, + 0 => 1, + default => (float) $this->step / $this->max, + }; $timeInterval = microtime(true) - $this->lastWriteTime; // Draw regardless of other limits @@ -398,11 +416,20 @@ public function setProgress(int $step): void } } - public function setMaxSteps(int $max): void + public function setMaxSteps(?int $max): void { + if (0 === $max) { + $max = null; + } + $this->format = null; - $this->max = max(0, $max); - $this->stepWidth = $this->max ? Helper::width((string) $this->max) : 4; + if (null === $max) { + $this->max = null; + $this->stepWidth = 4; + } else { + $this->max = max(0, $max); + $this->stepWidth = Helper::width((string) $this->max); + } } /** @@ -410,16 +437,16 @@ public function setMaxSteps(int $max): void */ public function finish(): void { - if (!$this->max) { + if (null === $this->max) { $this->max = $this->step; } - if ($this->step === $this->max && !$this->overwrite) { + if (($this->step === $this->max || null === $this->max) && !$this->overwrite) { // prevent double 100% output return; } - $this->setProgress($this->max); + $this->setProgress($this->max ?? $this->step); } /** @@ -551,14 +578,14 @@ private static function initPlaceholderFormatters(): array }, 'elapsed' => fn (self $bar) => Helper::formatTime(time() - $bar->getStartTime(), 2), 'remaining' => function (self $bar) { - if (!$bar->getMaxSteps()) { + if (null === $bar->getMaxSteps()) { throw new LogicException('Unable to display the remaining time if the maximum number of steps is not set.'); } return Helper::formatTime($bar->getRemaining(), 2); }, 'estimated' => function (self $bar) { - if (!$bar->getMaxSteps()) { + if (null === $bar->getMaxSteps()) { throw new LogicException('Unable to display the estimated time if the maximum number of steps is not set.'); } @@ -592,7 +619,7 @@ private function buildLine(): string { \assert(null !== $this->format); - $regex = "{%([a-z\-_]+)(?:\:([^%]+))?%}i"; + $regex = '{%([a-z\-_]+)(?:\:([^%]+))?%}i'; $callback = function ($matches) { if ($formatter = $this->getPlaceholderFormatter($matches[1])) { $text = $formatter($this, $this->output); diff --git a/app/vendor/symfony/console/Helper/ProgressIndicator.php b/app/vendor/symfony/console/Helper/ProgressIndicator.php index 92106caf6..b6bbd0cfa 100644 --- a/app/vendor/symfony/console/Helper/ProgressIndicator.php +++ b/app/vendor/symfony/console/Helper/ProgressIndicator.php @@ -31,15 +31,15 @@ class ProgressIndicator 'very_verbose_no_ansi' => ' %message% (%elapsed:6s%, %memory:6s%)', ]; - private OutputInterface $output; private int $startTime; private ?string $format = null; private ?string $message = null; private array $indicatorValues; private int $indicatorCurrent; - private int $indicatorChangeInterval; + private string $finishedIndicatorValue; private float $indicatorUpdateTime; private bool $started = false; + private bool $finished = false; /** * @var array @@ -50,30 +50,32 @@ class ProgressIndicator * @param int $indicatorChangeInterval Change interval in milliseconds * @param array|null $indicatorValues Animated indicator characters */ - public function __construct(OutputInterface $output, ?string $format = null, int $indicatorChangeInterval = 100, ?array $indicatorValues = null) - { - $this->output = $output; - + public function __construct( + private OutputInterface $output, + ?string $format = null, + private int $indicatorChangeInterval = 100, + ?array $indicatorValues = null, + ?string $finishedIndicatorValue = null, + ) { $format ??= $this->determineBestFormat(); $indicatorValues ??= ['-', '\\', '|', '/']; $indicatorValues = array_values($indicatorValues); + $finishedIndicatorValue ??= '✔'; if (2 > \count($indicatorValues)) { throw new InvalidArgumentException('Must have at least 2 indicator value characters.'); } $this->format = self::getFormatDefinition($format); - $this->indicatorChangeInterval = $indicatorChangeInterval; $this->indicatorValues = $indicatorValues; + $this->finishedIndicatorValue = $finishedIndicatorValue; $this->startTime = time(); } /** * Sets the current indicator message. - * - * @return void */ - public function setMessage(?string $message) + public function setMessage(?string $message): void { $this->message = $message; @@ -82,10 +84,8 @@ public function setMessage(?string $message) /** * Starts the indicator output. - * - * @return void */ - public function start(string $message) + public function start(string $message): void { if ($this->started) { throw new LogicException('Progress indicator already started.'); @@ -93,6 +93,7 @@ public function start(string $message) $this->message = $message; $this->started = true; + $this->finished = false; $this->startTime = time(); $this->indicatorUpdateTime = $this->getCurrentTimeInMilliseconds() + $this->indicatorChangeInterval; $this->indicatorCurrent = 0; @@ -102,10 +103,8 @@ public function start(string $message) /** * Advances the indicator. - * - * @return void */ - public function advance() + public function advance(): void { if (!$this->started) { throw new LogicException('Progress indicator has not yet been started.'); @@ -130,14 +129,24 @@ public function advance() /** * Finish the indicator with message. * - * @return void + * @param ?string $finishedIndicator */ - public function finish(string $message) + public function finish(string $message/* , ?string $finishedIndicator = null */): void { + $finishedIndicator = 1 < \func_num_args() ? func_get_arg(1) : null; + if (null !== $finishedIndicator && !\is_string($finishedIndicator)) { + throw new \TypeError(\sprintf('Argument 2 passed to "%s()" must be of the type string or null, "%s" given.', __METHOD__, get_debug_type($finishedIndicator))); + } + if (!$this->started) { throw new LogicException('Progress indicator has not yet been started.'); } + if (null !== $finishedIndicator) { + $this->finishedIndicatorValue = $finishedIndicator; + } + + $this->finished = true; $this->message = $message; $this->display(); $this->output->writeln(''); @@ -156,10 +165,8 @@ public static function getFormatDefinition(string $name): ?string * Sets a placeholder formatter for a given name. * * This method also allow you to override an existing placeholder. - * - * @return void */ - public static function setPlaceholderFormatterDefinition(string $name, callable $callable) + public static function setPlaceholderFormatterDefinition(string $name, callable $callable): void { self::$formatters ??= self::initPlaceholderFormatters(); @@ -182,7 +189,7 @@ private function display(): void return; } - $this->overwrite(preg_replace_callback("{%([a-z\-_]+)(?:\:([^%]+))?%}i", function ($matches) { + $this->overwrite(preg_replace_callback('{%([a-z\-_]+)(?:\:([^%]+))?%}i', function ($matches) { if ($formatter = self::getPlaceholderFormatterDefinition($matches[1])) { return $formatter($this); } @@ -226,7 +233,7 @@ private function getCurrentTimeInMilliseconds(): float private static function initPlaceholderFormatters(): array { return [ - 'indicator' => fn (self $indicator) => $indicator->indicatorValues[$indicator->indicatorCurrent % \count($indicator->indicatorValues)], + 'indicator' => fn (self $indicator) => $indicator->finished ? $indicator->finishedIndicatorValue : $indicator->indicatorValues[$indicator->indicatorCurrent % \count($indicator->indicatorValues)], 'message' => fn (self $indicator) => $indicator->message, 'elapsed' => fn (self $indicator) => Helper::formatTime(time() - $indicator->startTime, 2), 'memory' => fn () => Helper::formatMemory(memory_get_usage(true)), diff --git a/app/vendor/symfony/console/Helper/QuestionHelper.php b/app/vendor/symfony/console/Helper/QuestionHelper.php index 3d9091d2b..09b65bbf9 100644 --- a/app/vendor/symfony/console/Helper/QuestionHelper.php +++ b/app/vendor/symfony/console/Helper/QuestionHelper.php @@ -34,11 +34,6 @@ */ class QuestionHelper extends Helper { - /** - * @var resource|null - */ - private $inputStream; - private static bool $stty = true; private static bool $stdinIsInteractive; @@ -59,16 +54,15 @@ public function ask(InputInterface $input, OutputInterface $output, Question $qu return $this->getDefaultAnswer($question); } - if ($input instanceof StreamableInputInterface && $stream = $input->getStream()) { - $this->inputStream = $stream; - } + $inputStream = $input instanceof StreamableInputInterface ? $input->getStream() : null; + $inputStream ??= \STDIN; try { if (!$question->getValidator()) { - return $this->doAsk($output, $question); + return $this->doAsk($inputStream, $output, $question); } - $interviewer = fn () => $this->doAsk($output, $question); + $interviewer = fn () => $this->doAsk($inputStream, $output, $question); return $this->validateAttempts($interviewer, $output, $question); } catch (MissingInputException $exception) { @@ -89,10 +83,8 @@ public function getName(): string /** * Prevents usage of stty. - * - * @return void */ - public static function disableStty() + public static function disableStty(): void { self::$stty = false; } @@ -100,13 +92,14 @@ public static function disableStty() /** * Asks the question to the user. * + * @param resource $inputStream + * * @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden */ - private function doAsk(OutputInterface $output, Question $question): mixed + private function doAsk($inputStream, OutputInterface $output, Question $question): mixed { $this->writePrompt($output, $question); - $inputStream = $this->inputStream ?: \STDIN; $autocomplete = $question->getAutocompleterCallback(); if (null === $autocomplete || !self::$stty || !Terminal::hasSttyAvailable()) { @@ -190,10 +183,8 @@ private function getDefaultAnswer(Question $question): mixed /** * Outputs the question prompt. - * - * @return void */ - protected function writePrompt(OutputInterface $output, Question $question) + protected function writePrompt(OutputInterface $output, Question $question): void { $message = $question->getQuestion(); @@ -228,10 +219,8 @@ protected function formatChoiceQuestionChoices(ChoiceQuestion $question, string /** * Outputs an error message. - * - * @return void */ - protected function writeError(OutputInterface $output, \Exception $error) + protected function writeError(OutputInterface $output, \Exception $error): void { if (null !== $this->getHelperSet() && $this->getHelperSet()->has('formatter')) { $message = $this->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error'); @@ -527,12 +516,16 @@ private function readInput($inputStream, Question $question): string|false $ret = ''; $cp = $this->setIOCodepage(); while (false !== ($char = fgetc($multiLineStreamReader))) { - if (\PHP_EOL === "{$ret}{$char}") { + if ("\x4" === $char || \PHP_EOL === "{$ret}{$char}") { break; } $ret .= $char; } + if (stream_get_meta_data($inputStream)['seekable']) { + fseek($inputStream, ftell($multiLineStreamReader)); + } + return $this->resetIOCodepage($cp, $ret); } diff --git a/app/vendor/symfony/console/Helper/SymfonyQuestionHelper.php b/app/vendor/symfony/console/Helper/SymfonyQuestionHelper.php index 11b4e4238..b452bf047 100644 --- a/app/vendor/symfony/console/Helper/SymfonyQuestionHelper.php +++ b/app/vendor/symfony/console/Helper/SymfonyQuestionHelper.php @@ -25,10 +25,7 @@ */ class SymfonyQuestionHelper extends QuestionHelper { - /** - * @return void - */ - protected function writePrompt(OutputInterface $output, Question $question) + protected function writePrompt(OutputInterface $output, Question $question): void { $text = OutputFormatter::escapeTrailingBackslash($question->getQuestion()); $default = $question->getDefault(); @@ -83,10 +80,7 @@ protected function writePrompt(OutputInterface $output, Question $question) $output->write($prompt); } - /** - * @return void - */ - protected function writeError(OutputInterface $output, \Exception $error) + protected function writeError(OutputInterface $output, \Exception $error): void { if ($output instanceof SymfonyStyle) { $output->newLine(); diff --git a/app/vendor/symfony/console/Helper/Table.php b/app/vendor/symfony/console/Helper/Table.php index 469d228d6..8c3d0a521 100644 --- a/app/vendor/symfony/console/Helper/Table.php +++ b/app/vendor/symfony/console/Helper/Table.php @@ -45,7 +45,6 @@ class Table private array $rows = []; private array $effectiveColumnWidths = []; private int $numberOfColumns; - private OutputInterface $output; private TableStyle $style; private array $columnStyles = []; private array $columnWidths = []; @@ -55,10 +54,9 @@ class Table private static array $styles; - public function __construct(OutputInterface $output) - { - $this->output = $output; - + public function __construct( + private OutputInterface $output, + ) { self::$styles ??= self::initStyles(); $this->setStyle('default'); @@ -66,10 +64,8 @@ public function __construct(OutputInterface $output) /** * Sets a style definition. - * - * @return void */ - public static function setStyleDefinition(string $name, TableStyle $style) + public static function setStyleDefinition(string $name, TableStyle $style): void { self::$styles ??= self::initStyles(); @@ -194,7 +190,7 @@ public function setHeaders(array $headers): static /** * @return $this */ - public function setRows(array $rows) + public function setRows(array $rows): static { $this->rows = []; @@ -312,10 +308,8 @@ public function setVertical(bool $vertical = true): static * | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | * | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien | * +---------------+-----------------------+------------------+ - * - * @return void */ - public function render() + public function render(): void { $divider = new TableSeparator(); $isCellWithColspan = static fn ($cell) => $cell instanceof TableCell && $cell->getColspan() >= 2; @@ -423,7 +417,7 @@ public function render() continue; } - if ($isHeader && !$isHeaderSeparatorRendered) { + if ($isHeader && !$isHeaderSeparatorRendered && $this->style->displayOutsideBorder()) { $this->renderRowSeparator( self::SEPARATOR_TOP, $hasTitle ? $this->headerTitle : null, @@ -455,7 +449,10 @@ public function render() } } } - $this->renderRowSeparator(self::SEPARATOR_BOTTOM, $this->footerTitle, $this->style->getFooterTitleFormat()); + + if ($this->getStyle()->displayOutsideBorder()) { + $this->renderRowSeparator(self::SEPARATOR_BOTTOM, $this->footerTitle, $this->style->getFooterTitleFormat()); + } $this->cleanup(); $this->rendered = true; @@ -762,7 +759,7 @@ private function fillNextRows(array $rows, int $line): array foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) { // we need to know if $unmergedRow will be merged or inserted into $rows - if (isset($rows[$unmergedRowKey]) && \is_array($rows[$unmergedRowKey]) && ($this->getNumberOfColumns($rows[$unmergedRowKey]) + $this->getNumberOfColumns($unmergedRows[$unmergedRowKey]) <= $this->numberOfColumns)) { + if (isset($rows[$unmergedRowKey]) && \is_array($rows[$unmergedRowKey]) && ($this->getNumberOfColumns($rows[$unmergedRowKey]) + $this->getNumberOfColumns($unmergedRow) <= $this->numberOfColumns)) { foreach ($unmergedRow as $cellKey => $cell) { // insert cell into row at cellKey position array_splice($rows[$unmergedRowKey], $cellKey, 0, [$cell]); @@ -770,8 +767,8 @@ private function fillNextRows(array $rows, int $line): array } else { $row = $this->copyRow($rows, $unmergedRowKey - 1); foreach ($unmergedRow as $column => $cell) { - if (!empty($cell)) { - $row[$column] = $unmergedRow[$column]; + if ($cell) { + $row[$column] = $cell; } } array_splice($rows, $unmergedRowKey, 0, [$row]); @@ -911,6 +908,12 @@ private function cleanup(): void */ private static function initStyles(): array { + $markdown = new TableStyle(); + $markdown + ->setDefaultCrossingChar('|') + ->setDisplayOutsideBorder(false) + ; + $borderless = new TableStyle(); $borderless ->setHorizontalBorderChars('=') @@ -948,6 +951,7 @@ private static function initStyles(): array return [ 'default' => new TableStyle(), + 'markdown' => $markdown, 'borderless' => $borderless, 'compact' => $compact, 'symfony-style-guide' => $styleGuide, diff --git a/app/vendor/symfony/console/Helper/TableCell.php b/app/vendor/symfony/console/Helper/TableCell.php index ead32f283..ab8339204 100644 --- a/app/vendor/symfony/console/Helper/TableCell.php +++ b/app/vendor/symfony/console/Helper/TableCell.php @@ -18,17 +18,16 @@ */ class TableCell { - private string $value; private array $options = [ 'rowspan' => 1, 'colspan' => 1, 'style' => null, ]; - public function __construct(string $value = '', array $options = []) - { - $this->value = $value; - + public function __construct( + private string $value = '', + array $options = [], + ) { // check option names if ($diff = array_diff(array_keys($options), array_keys($this->options))) { throw new InvalidArgumentException(\sprintf('The TableCell does not support the following options: \'%s\'.', implode('\', \'', $diff))); diff --git a/app/vendor/symfony/console/Helper/TableCellStyle.php b/app/vendor/symfony/console/Helper/TableCellStyle.php index 1b1ef276e..af1a17e96 100644 --- a/app/vendor/symfony/console/Helper/TableCellStyle.php +++ b/app/vendor/symfony/console/Helper/TableCellStyle.php @@ -67,7 +67,7 @@ public function getTagOptions(): array { return array_filter( $this->getOptions(), - fn ($key) => \in_array($key, self::TAG_OPTIONS) && isset($this->options[$key]), + fn ($key) => \in_array($key, self::TAG_OPTIONS, true) && isset($this->options[$key]), \ARRAY_FILTER_USE_KEY ); } diff --git a/app/vendor/symfony/console/Helper/TableRows.php b/app/vendor/symfony/console/Helper/TableRows.php index 97d07726e..fb2dc2789 100644 --- a/app/vendor/symfony/console/Helper/TableRows.php +++ b/app/vendor/symfony/console/Helper/TableRows.php @@ -16,11 +16,9 @@ */ class TableRows implements \IteratorAggregate { - private \Closure $generator; - - public function __construct(\Closure $generator) - { - $this->generator = $generator; + public function __construct( + private \Closure $generator, + ) { } public function getIterator(): \Traversable diff --git a/app/vendor/symfony/console/Helper/TableStyle.php b/app/vendor/symfony/console/Helper/TableStyle.php index be956c109..74ac58925 100644 --- a/app/vendor/symfony/console/Helper/TableStyle.php +++ b/app/vendor/symfony/console/Helper/TableStyle.php @@ -46,6 +46,7 @@ class TableStyle private string $cellRowFormat = '%s'; private string $cellRowContentFormat = ' %s '; private string $borderFormat = '%s'; + private bool $displayOutsideBorder = true; private int $padType = \STR_PAD_RIGHT; /** @@ -359,4 +360,16 @@ public function setFooterTitleFormat(string $format): static return $this; } + + public function setDisplayOutsideBorder($displayOutSideBorder): static + { + $this->displayOutsideBorder = $displayOutSideBorder; + + return $this; + } + + public function displayOutsideBorder(): bool + { + return $this->displayOutsideBorder; + } } diff --git a/app/vendor/symfony/console/Helper/TreeHelper.php b/app/vendor/symfony/console/Helper/TreeHelper.php new file mode 100644 index 000000000..d188afe98 --- /dev/null +++ b/app/vendor/symfony/console/Helper/TreeHelper.php @@ -0,0 +1,111 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Output\OutputInterface; + +/** + * The TreeHelper class provides methods to display tree-like structures. + * + * @author Simon André + * + * @implements \RecursiveIterator + */ +final class TreeHelper implements \RecursiveIterator +{ + /** + * @var \Iterator + */ + private \Iterator $children; + + private function __construct( + private readonly OutputInterface $output, + private readonly TreeNode $node, + private readonly TreeStyle $style, + ) { + $this->children = new \IteratorIterator($this->node->getChildren()); + $this->children->rewind(); + } + + public static function createTree(OutputInterface $output, string|TreeNode|null $root = null, iterable $values = [], ?TreeStyle $style = null): self + { + $node = $root instanceof TreeNode ? $root : new TreeNode($root ?? ''); + + return new self($output, TreeNode::fromValues($values, $node), $style ?? TreeStyle::default()); + } + + public function current(): TreeNode + { + return $this->children->current(); + } + + public function key(): int + { + return $this->children->key(); + } + + public function next(): void + { + $this->children->next(); + } + + public function rewind(): void + { + $this->children->rewind(); + } + + public function valid(): bool + { + return $this->children->valid(); + } + + public function hasChildren(): bool + { + if (null === $current = $this->current()) { + return false; + } + + foreach ($current->getChildren() as $child) { + return true; + } + + return false; + } + + public function getChildren(): \RecursiveIterator + { + return new self($this->output, $this->current(), $this->style); + } + + /** + * Recursively renders the tree to the output, applying the tree style. + */ + public function render(): void + { + $treeIterator = new \RecursiveTreeIterator($this); + + $this->style->applyPrefixes($treeIterator); + + $this->output->writeln($this->node->getValue()); + + $visited = new \SplObjectStorage(); + foreach ($treeIterator as $node) { + $currentNode = $node instanceof TreeNode ? $node : $treeIterator->getInnerIterator()->current(); + if (isset($visited[$currentNode])) { + throw new \LogicException(\sprintf('Cycle detected at node: "%s".', $currentNode->getValue())); + } + $visited[$currentNode] = true; + + $this->output->writeln($node); + } + } +} diff --git a/app/vendor/symfony/console/Helper/TreeNode.php b/app/vendor/symfony/console/Helper/TreeNode.php new file mode 100644 index 000000000..8c35266c1 --- /dev/null +++ b/app/vendor/symfony/console/Helper/TreeNode.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +/** + * @implements \IteratorAggregate + * + * @author Simon André + */ +final class TreeNode implements \Countable, \IteratorAggregate +{ + /** + * @var array + */ + private array $children = []; + + public function __construct( + private readonly string $value = '', + iterable $children = [], + ) { + foreach ($children as $child) { + $this->addChild($child); + } + } + + public static function fromValues(iterable $nodes, ?self $node = null): self + { + $node ??= new self(); + foreach ($nodes as $key => $value) { + if (is_iterable($value)) { + $child = new self($key); + self::fromValues($value, $child); + $node->addChild($child); + } elseif ($value instanceof self) { + $node->addChild($value); + } else { + $node->addChild(new self($value)); + } + } + + return $node; + } + + public function getValue(): string + { + return $this->value; + } + + public function addChild(self|string|callable $node): self + { + if (\is_string($node)) { + $node = new self($node); + } + + $this->children[] = $node; + + return $this; + } + + /** + * @return \Traversable + */ + public function getChildren(): \Traversable + { + foreach ($this->children as $child) { + if (\is_callable($child)) { + yield from $child(); + } elseif ($child instanceof self) { + yield $child; + } + } + } + + /** + * @return \Traversable + */ + public function getIterator(): \Traversable + { + return $this->getChildren(); + } + + public function count(): int + { + $count = 0; + foreach ($this->getChildren() as $child) { + ++$count; + } + + return $count; + } + + public function __toString(): string + { + return $this->value; + } +} diff --git a/app/vendor/symfony/console/Helper/TreeStyle.php b/app/vendor/symfony/console/Helper/TreeStyle.php new file mode 100644 index 000000000..21cc04b3c --- /dev/null +++ b/app/vendor/symfony/console/Helper/TreeStyle.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +/** + * Configures the output of the Tree helper. + * + * @author Simon André + */ +final class TreeStyle +{ + public function __construct( + private readonly string $prefixEndHasNext, + private readonly string $prefixEndLast, + private readonly string $prefixLeft, + private readonly string $prefixMidHasNext, + private readonly string $prefixMidLast, + private readonly string $prefixRight, + ) { + } + + public static function box(): self + { + return new self('┃╸ ', '┗╸ ', '', '┃ ', ' ', ''); + } + + public static function boxDouble(): self + { + return new self('╠═ ', '╚═ ', '', '║ ', ' ', ''); + } + + public static function compact(): self + { + return new self('├ ', '└ ', '', '│ ', ' ', ''); + } + + public static function default(): self + { + return new self('├── ', '└── ', '', '│ ', ' ', ''); + } + + public static function light(): self + { + return new self('|-- ', '`-- ', '', '| ', ' ', ''); + } + + public static function minimal(): self + { + return new self('. ', '. ', '', '. ', ' ', ''); + } + + public static function rounded(): self + { + return new self('├─ ', '╰─ ', '', '│ ', ' ', ''); + } + + /** + * @internal + */ + public function applyPrefixes(\RecursiveTreeIterator $iterator): void + { + $iterator->setPrefixPart(\RecursiveTreeIterator::PREFIX_LEFT, $this->prefixLeft); + $iterator->setPrefixPart(\RecursiveTreeIterator::PREFIX_MID_HAS_NEXT, $this->prefixMidHasNext); + $iterator->setPrefixPart(\RecursiveTreeIterator::PREFIX_MID_LAST, $this->prefixMidLast); + $iterator->setPrefixPart(\RecursiveTreeIterator::PREFIX_END_HAS_NEXT, $this->prefixEndHasNext); + $iterator->setPrefixPart(\RecursiveTreeIterator::PREFIX_END_LAST, $this->prefixEndLast); + $iterator->setPrefixPart(\RecursiveTreeIterator::PREFIX_RIGHT, $this->prefixRight); + } +} diff --git a/app/vendor/symfony/console/Input/ArgvInput.php b/app/vendor/symfony/console/Input/ArgvInput.php index a33092aee..fe25b861a 100644 --- a/app/vendor/symfony/console/Input/ArgvInput.php +++ b/app/vendor/symfony/console/Input/ArgvInput.php @@ -40,13 +40,21 @@ */ class ArgvInput extends Input { + /** @var list */ private array $tokens; private array $parsed; + /** @param list|null $argv */ public function __construct(?array $argv = null, ?InputDefinition $definition = null) { $argv ??= $_SERVER['argv'] ?? []; + foreach ($argv as $arg) { + if (!\is_scalar($arg) && !$arg instanceof \Stringable) { + throw new RuntimeException(\sprintf('Argument values expected to be all scalars, got "%s".', get_debug_type($arg))); + } + } + // strip the application name array_shift($argv); @@ -55,18 +63,13 @@ public function __construct(?array $argv = null, ?InputDefinition $definition = parent::__construct($definition); } - /** - * @return void - */ - protected function setTokens(array $tokens) + /** @param list $tokens */ + protected function setTokens(array $tokens): void { $this->tokens = $tokens; } - /** - * @return void - */ - protected function parse() + protected function parse(): void { $parseOptions = true; $this->parsed = $this->tokens; @@ -130,9 +133,9 @@ private function parseShortOptionSet(string $name): void $this->addLongOption($option->getName(), $i === $len - 1 ? null : substr($name, $i + 1)); break; - } else { - $this->addLongOption($option->getName(), null); } + + $this->addLongOption($option->getName(), null); } } @@ -348,6 +351,35 @@ public function getParameterOption(string|array $values, string|bool|int|float|a return $default; } + /** + * Returns un-parsed and not validated tokens. + * + * @param bool $strip Whether to return the raw parameters (false) or the values after the command name (true) + * + * @return list + */ + public function getRawTokens(bool $strip = false): array + { + if (!$strip) { + return $this->tokens; + } + + $parameters = []; + $keep = false; + foreach ($this->tokens as $value) { + if (!$keep && $value === $this->getFirstArgument()) { + $keep = true; + + continue; + } + if ($keep) { + $parameters[] = $value; + } + } + + return $parameters; + } + /** * Returns a stringified representation of the args passed to the command. */ diff --git a/app/vendor/symfony/console/Input/ArrayInput.php b/app/vendor/symfony/console/Input/ArrayInput.php index b9f753394..7335632bf 100644 --- a/app/vendor/symfony/console/Input/ArrayInput.php +++ b/app/vendor/symfony/console/Input/ArrayInput.php @@ -25,12 +25,10 @@ */ class ArrayInput extends Input { - private array $parameters; - - public function __construct(array $parameters, ?InputDefinition $definition = null) - { - $this->parameters = $parameters; - + public function __construct( + private array $parameters, + ?InputDefinition $definition = null, + ) { parent::__construct($definition); } @@ -113,10 +111,7 @@ public function __toString(): string return implode(' ', $params); } - /** - * @return void - */ - protected function parse() + protected function parse(): void { foreach ($this->parameters as $key => $value) { if ('--' === $key) { diff --git a/app/vendor/symfony/console/Input/Input.php b/app/vendor/symfony/console/Input/Input.php index d3a3c7fd2..d2881c60f 100644 --- a/app/vendor/symfony/console/Input/Input.php +++ b/app/vendor/symfony/console/Input/Input.php @@ -27,12 +27,12 @@ */ abstract class Input implements InputInterface, StreamableInputInterface { - protected $definition; + protected InputDefinition $definition; /** @var resource */ protected $stream; - protected $options = []; - protected $arguments = []; - protected $interactive = true; + protected array $options = []; + protected array $arguments = []; + protected bool $interactive = true; public function __construct(?InputDefinition $definition = null) { @@ -44,10 +44,7 @@ public function __construct(?InputDefinition $definition = null) } } - /** - * @return void - */ - public function bind(InputDefinition $definition) + public function bind(InputDefinition $definition): void { $this->arguments = []; $this->options = []; @@ -58,15 +55,10 @@ public function bind(InputDefinition $definition) /** * Processes command line arguments. - * - * @return void */ - abstract protected function parse(); + abstract protected function parse(): void; - /** - * @return void - */ - public function validate() + public function validate(): void { $definition = $this->definition; $givenArguments = $this->arguments; @@ -83,10 +75,7 @@ public function isInteractive(): bool return $this->interactive; } - /** - * @return void - */ - public function setInteractive(bool $interactive) + public function setInteractive(bool $interactive): void { $this->interactive = $interactive; } @@ -105,10 +94,7 @@ public function getArgument(string $name): mixed return $this->arguments[$name] ?? $this->definition->getArgument($name)->getDefault(); } - /** - * @return void - */ - public function setArgument(string $name, mixed $value) + public function setArgument(string $name, mixed $value): void { if (!$this->definition->hasArgument($name)) { throw new InvalidArgumentException(\sprintf('The "%s" argument does not exist.', $name)); @@ -144,10 +130,7 @@ public function getOption(string $name): mixed return \array_key_exists($name, $this->options) ? $this->options[$name] : $this->definition->getOption($name)->getDefault(); } - /** - * @return void - */ - public function setOption(string $name, mixed $value) + public function setOption(string $name, mixed $value): void { if ($this->definition->hasNegation($name)) { $this->options[$this->definition->negationToName($name)] = !$value; @@ -175,10 +158,8 @@ public function escapeToken(string $token): string /** * @param resource $stream - * - * @return void */ - public function setStream($stream) + public function setStream($stream): void { $this->stream = $stream; } diff --git a/app/vendor/symfony/console/Input/InputArgument.php b/app/vendor/symfony/console/Input/InputArgument.php index fd203919f..6fbb64ed0 100644 --- a/app/vendor/symfony/console/Input/InputArgument.php +++ b/app/vendor/symfony/console/Input/InputArgument.php @@ -25,37 +25,47 @@ */ class InputArgument { + /** + * Providing an argument is required (e.g. just 'app:foo' is not allowed). + */ public const REQUIRED = 1; + + /** + * Providing an argument is optional (e.g. 'app:foo' and 'app:foo bar' are both allowed). This is the default behavior of arguments. + */ public const OPTIONAL = 2; + + /** + * The argument accepts multiple values and turn them into an array (e.g. 'app:foo bar baz' will result in value ['bar', 'baz']). + */ public const IS_ARRAY = 4; - private string $name; private int $mode; private string|int|bool|array|float|null $default; - private array|\Closure $suggestedValues; - private string $description; /** * @param string $name The argument name - * @param int|null $mode The argument mode: a bit mask of self::REQUIRED, self::OPTIONAL and self::IS_ARRAY + * @param int-mask-of|null $mode The argument mode: a bit mask of self::REQUIRED, self::OPTIONAL and self::IS_ARRAY * @param string $description A description text * @param string|bool|int|float|array|null $default The default value (for self::OPTIONAL mode only) * @param array|\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 = '', string|bool|int|float|array|null $default = null, \Closure|array $suggestedValues = []) - { + public function __construct( + private string $name, + ?int $mode = null, + private string $description = '', + string|bool|int|float|array|null $default = null, + private \Closure|array $suggestedValues = [], + ) { if (null === $mode) { $mode = self::OPTIONAL; - } elseif ($mode > 7 || $mode < 1) { + } elseif ($mode >= (self::IS_ARRAY << 1) || $mode < 1) { throw new InvalidArgumentException(\sprintf('Argument mode "%s" is not valid.', $mode)); } - $this->name = $name; $this->mode = $mode; - $this->description = $description; - $this->suggestedValues = $suggestedValues; $this->setDefault($default); } @@ -90,16 +100,9 @@ public function isArray(): bool /** * Sets the default value. - * - * @return void - * - * @throws LogicException When incorrect default value is given */ - public function setDefault(string|bool|int|float|array|null $default = null) + public function setDefault(string|bool|int|float|array|null $default): void { - if (1 > \func_num_args()) { - trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__); - } if ($this->isRequired() && null !== $default) { throw new LogicException('Cannot set a default value except for InputArgument::OPTIONAL mode.'); } @@ -123,13 +126,16 @@ public function getDefault(): string|bool|int|float|array|null return $this->default; } + /** + * Returns true if the argument has values for input completion. + */ public function hasCompletion(): bool { return [] !== $this->suggestedValues; } /** - * Adds suggestions to $suggestions for the current completion input. + * Supplies suggestions when command resolves possible completion options for input. * * @see Command::complete() */ diff --git a/app/vendor/symfony/console/Input/InputAwareInterface.php b/app/vendor/symfony/console/Input/InputAwareInterface.php index 0ad27b455..ba4664cdb 100644 --- a/app/vendor/symfony/console/Input/InputAwareInterface.php +++ b/app/vendor/symfony/console/Input/InputAwareInterface.php @@ -21,8 +21,6 @@ interface InputAwareInterface { /** * Sets the Console Input. - * - * @return void */ - public function setInput(InputInterface $input); + public function setInput(InputInterface $input): void; } diff --git a/app/vendor/symfony/console/Input/InputDefinition.php b/app/vendor/symfony/console/Input/InputDefinition.php index b5c202838..a8b006d48 100644 --- a/app/vendor/symfony/console/Input/InputDefinition.php +++ b/app/vendor/symfony/console/Input/InputDefinition.php @@ -46,10 +46,8 @@ public function __construct(array $definition = []) /** * Sets the definition of the input. - * - * @return void */ - public function setDefinition(array $definition) + public function setDefinition(array $definition): void { $arguments = []; $options = []; @@ -69,10 +67,8 @@ public function setDefinition(array $definition) * Sets the InputArgument objects. * * @param InputArgument[] $arguments An array of InputArgument objects - * - * @return void */ - public function setArguments(array $arguments = []) + public function setArguments(array $arguments = []): void { $this->arguments = []; $this->requiredCount = 0; @@ -85,10 +81,8 @@ public function setArguments(array $arguments = []) * Adds an array of InputArgument objects. * * @param InputArgument[] $arguments An array of InputArgument objects - * - * @return void */ - public function addArguments(?array $arguments = []) + public function addArguments(?array $arguments = []): void { if (null !== $arguments) { foreach ($arguments as $argument) { @@ -98,11 +92,9 @@ public function addArguments(?array $arguments = []) } /** - * @return void - * * @throws LogicException When incorrect argument is given */ - public function addArgument(InputArgument $argument) + public function addArgument(InputArgument $argument): void { if (isset($this->arguments[$argument->getName()])) { throw new LogicException(\sprintf('An argument with name "%s" already exists.', $argument->getName())); @@ -198,10 +190,8 @@ public function getArgumentDefaults(): array * Sets the InputOption objects. * * @param InputOption[] $options An array of InputOption objects - * - * @return void */ - public function setOptions(array $options = []) + public function setOptions(array $options = []): void { $this->options = []; $this->shortcuts = []; @@ -213,10 +203,8 @@ public function setOptions(array $options = []) * Adds an array of InputOption objects. * * @param InputOption[] $options An array of InputOption objects - * - * @return void */ - public function addOptions(array $options = []) + public function addOptions(array $options = []): void { foreach ($options as $option) { $this->addOption($option); @@ -224,11 +212,9 @@ public function addOptions(array $options = []) } /** - * @return void - * * @throws LogicException When option given already exist */ - public function addOption(InputOption $option) + public function addOption(InputOption $option): void { if (isset($this->options[$option->getName()]) && !$option->equals($this->options[$option->getName()])) { throw new LogicException(\sprintf('An option named "%s" already exists.', $option->getName())); diff --git a/app/vendor/symfony/console/Input/InputInterface.php b/app/vendor/symfony/console/Input/InputInterface.php index aaed5fd01..c177d960b 100644 --- a/app/vendor/symfony/console/Input/InputInterface.php +++ b/app/vendor/symfony/console/Input/InputInterface.php @@ -18,9 +18,6 @@ * InputInterface is the interface implemented by all input classes. * * @author Fabien Potencier - * - * @method string __toString() Returns a stringified representation of the args passed to the command. - * InputArguments MUST be escaped as well as the InputOption values passed to the command. */ interface InputInterface { @@ -53,28 +50,22 @@ public function hasParameterOption(string|array $values, bool $onlyParams = fals * @param string|array $values The value(s) to look for in the raw parameters (can be an array) * @param string|bool|int|float|array|null $default The default value to return if no result is found * @param bool $onlyParams Only check real parameters, skip those following an end of options (--) signal - * - * @return mixed */ - public function getParameterOption(string|array $values, string|bool|int|float|array|null $default = false, bool $onlyParams = false); + public function getParameterOption(string|array $values, string|bool|int|float|array|null $default = false, bool $onlyParams = false): mixed; /** * Binds the current Input instance with the given arguments and options. * - * @return void - * * @throws RuntimeException */ - public function bind(InputDefinition $definition); + public function bind(InputDefinition $definition): void; /** * Validates the input. * - * @return void - * * @throws RuntimeException When not enough arguments are given */ - public function validate(); + public function validate(): void; /** * Returns all the given arguments merged with the default values. @@ -86,20 +77,16 @@ public function getArguments(): array; /** * Returns the argument value for a given argument name. * - * @return mixed - * * @throws InvalidArgumentException When argument given doesn't exist */ - public function getArgument(string $name); + public function getArgument(string $name): mixed; /** * Sets an argument value by name. * - * @return void - * * @throws InvalidArgumentException When argument given doesn't exist */ - public function setArgument(string $name, mixed $value); + public function setArgument(string $name, mixed $value): void; /** * Returns true if an InputArgument object exists by name or position. @@ -116,20 +103,16 @@ public function getOptions(): array; /** * Returns the option value for a given option name. * - * @return mixed - * * @throws InvalidArgumentException When option given doesn't exist */ - public function getOption(string $name); + public function getOption(string $name): mixed; /** * Sets an option value by name. * - * @return void - * * @throws InvalidArgumentException When option given doesn't exist */ - public function setOption(string $name, mixed $value); + public function setOption(string $name, mixed $value): void; /** * Returns true if an InputOption object exists by name. @@ -143,8 +126,13 @@ public function isInteractive(): bool; /** * Sets the input interactivity. + */ + public function setInteractive(bool $interactive): void; + + /** + * Returns a stringified representation of the args passed to the command. * - * @return void + * InputArguments MUST be escaped as well as the InputOption values passed to the command. */ - public function setInteractive(bool $interactive); + public function __toString(): string; } diff --git a/app/vendor/symfony/console/Input/InputOption.php b/app/vendor/symfony/console/Input/InputOption.php index 3adc8c424..25fb91782 100644 --- a/app/vendor/symfony/console/Input/InputOption.php +++ b/app/vendor/symfony/console/Input/InputOption.php @@ -46,32 +46,36 @@ class InputOption public const VALUE_IS_ARRAY = 8; /** - * The option may have either positive or negative value (e.g. --ansi or --no-ansi). + * The option allows passing a negated variant (e.g. --ansi or --no-ansi). */ public const VALUE_NEGATABLE = 16; private string $name; - private string|array|null $shortcut; + private ?string $shortcut; private int $mode; private string|int|bool|array|float|null $default; - private array|\Closure $suggestedValues; - private string $description; /** * @param string|array|null $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts - * @param int|null $mode The option mode: One of the VALUE_* constants + * @param int-mask-of|null $mode The option mode: One of the VALUE_* constants * @param string|bool|int|float|array|null $default The default value (must be null for self::VALUE_NONE) * @param array|\Closure(CompletionInput,CompletionSuggestions):list $suggestedValues The values used for input completion * * @throws InvalidArgumentException If option mode is invalid or incompatible */ - public function __construct(string $name, string|array|null $shortcut = null, ?int $mode = null, string $description = '', string|bool|int|float|array|null $default = null, array|\Closure $suggestedValues = []) - { + public function __construct( + string $name, + string|array|null $shortcut = null, + ?int $mode = null, + private string $description = '', + string|bool|int|float|array|null $default = null, + private array|\Closure $suggestedValues = [], + ) { if (str_starts_with($name, '--')) { $name = substr($name, 2); } - if (empty($name)) { + if (!$name) { throw new InvalidArgumentException('An option name cannot be empty.'); } @@ -101,8 +105,6 @@ public function __construct(string $name, string|array|null $shortcut = null, ?i $this->name = $name; $this->shortcut = $shortcut; $this->mode = $mode; - $this->description = $description; - $this->suggestedValues = $suggestedValues; if ($suggestedValues && !$this->acceptValue()) { throw new LogicException('Cannot set suggested values if the option does not accept a value.'); @@ -173,19 +175,21 @@ public function isArray(): bool return self::VALUE_IS_ARRAY === (self::VALUE_IS_ARRAY & $this->mode); } + /** + * Returns true if the option allows passing a negated variant. + * + * @return bool true if mode is self::VALUE_NEGATABLE, false otherwise + */ public function isNegatable(): bool { return self::VALUE_NEGATABLE === (self::VALUE_NEGATABLE & $this->mode); } /** - * @return void + * Sets the default value. */ - public function setDefault(string|bool|int|float|array|null $default = null) + public function setDefault(string|bool|int|float|array|null $default): void { - if (1 > \func_num_args()) { - trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__); - } if (self::VALUE_NONE === (self::VALUE_NONE & $this->mode) && null !== $default) { throw new LogicException('Cannot set a default value when using InputOption::VALUE_NONE mode.'); } @@ -217,13 +221,16 @@ public function getDescription(): string return $this->description; } + /** + * Returns true if the option has values for input completion. + */ public function hasCompletion(): bool { return [] !== $this->suggestedValues; } /** - * Adds suggestions to $suggestions for the current completion input. + * Supplies suggestions when command resolves possible completion options for input. * * @see Command::complete() */ diff --git a/app/vendor/symfony/console/Input/StreamableInputInterface.php b/app/vendor/symfony/console/Input/StreamableInputInterface.php index 4b95fcb11..4a0dc017f 100644 --- a/app/vendor/symfony/console/Input/StreamableInputInterface.php +++ b/app/vendor/symfony/console/Input/StreamableInputInterface.php @@ -25,10 +25,8 @@ interface StreamableInputInterface extends InputInterface * This is mainly useful for testing purpose. * * @param resource $stream The input stream - * - * @return void */ - public function setStream($stream); + public function setStream($stream): void; /** * Returns the input stream. diff --git a/app/vendor/symfony/console/Input/StringInput.php b/app/vendor/symfony/console/Input/StringInput.php index 9b94784af..a70f048f9 100644 --- a/app/vendor/symfony/console/Input/StringInput.php +++ b/app/vendor/symfony/console/Input/StringInput.php @@ -24,10 +24,6 @@ */ class StringInput extends ArgvInput { - /** - * @deprecated since Symfony 6.1 - */ - public const REGEX_STRING = '([^\s]+?)(?:\s|(? + * * @throws InvalidArgumentException When unable to parse input (should never happen) */ private function tokenize(string $input): array diff --git a/app/vendor/symfony/console/Logger/ConsoleLogger.php b/app/vendor/symfony/console/Logger/ConsoleLogger.php index 70432b7de..a6ef49ea9 100644 --- a/app/vendor/symfony/console/Logger/ConsoleLogger.php +++ b/app/vendor/symfony/console/Logger/ConsoleLogger.php @@ -29,7 +29,6 @@ class ConsoleLogger extends AbstractLogger public const INFO = 'info'; public const ERROR = 'error'; - private OutputInterface $output; private array $verbosityLevelMap = [ LogLevel::EMERGENCY => OutputInterface::VERBOSITY_NORMAL, LogLevel::ALERT => OutputInterface::VERBOSITY_NORMAL, @@ -52,9 +51,11 @@ class ConsoleLogger extends AbstractLogger ]; private bool $errored = false; - public function __construct(OutputInterface $output, array $verbosityLevelMap = [], array $formatLevelMap = []) - { - $this->output = $output; + public function __construct( + private OutputInterface $output, + array $verbosityLevelMap = [], + array $formatLevelMap = [], + ) { $this->verbosityLevelMap = $verbosityLevelMap + $this->verbosityLevelMap; $this->formatLevelMap = $formatLevelMap + $this->formatLevelMap; } diff --git a/app/vendor/symfony/console/Messenger/RunCommandMessageHandler.php b/app/vendor/symfony/console/Messenger/RunCommandMessageHandler.php index 3f4286542..df5f48af0 100644 --- a/app/vendor/symfony/console/Messenger/RunCommandMessageHandler.php +++ b/app/vendor/symfony/console/Messenger/RunCommandMessageHandler.php @@ -24,8 +24,9 @@ */ final class RunCommandMessageHandler { - public function __construct(private readonly Application $application) - { + public function __construct( + private readonly Application $application, + ) { } public function __invoke(RunCommandMessage $message): RunCommandContext diff --git a/app/vendor/symfony/console/Output/AnsiColorMode.php b/app/vendor/symfony/console/Output/AnsiColorMode.php index dcdd1d5c4..0e1422a27 100644 --- a/app/vendor/symfony/console/Output/AnsiColorMode.php +++ b/app/vendor/symfony/console/Output/AnsiColorMode.php @@ -62,7 +62,7 @@ public function convertFromHexToAnsiColorCode(string $hexColor): string return match ($this) { self::Ansi4 => (string) $this->convertFromRGB($r, $g, $b), - self::Ansi8 => '8;5;'.((string) $this->convertFromRGB($r, $g, $b)), + self::Ansi8 => '8;5;'.$this->convertFromRGB($r, $g, $b), self::Ansi24 => \sprintf('8;2;%d;%d;%d', $r, $g, $b), }; } @@ -96,11 +96,11 @@ private function degradeHexColorToAnsi8(int $r, int $g, int $b): int } return (int) round(($r - 8) / 247 * 24) + 232; - } else { - return 16 + - (36 * (int) round($r / 255 * 5)) + - (6 * (int) round($g / 255 * 5)) + - (int) round($b / 255 * 5); } + + return 16 + + (36 * (int) round($r / 255 * 5)) + + (6 * (int) round($g / 255 * 5)) + + (int) round($b / 255 * 5); } } diff --git a/app/vendor/symfony/console/Output/BufferedOutput.php b/app/vendor/symfony/console/Output/BufferedOutput.php index ef5099bfd..3c8d3906f 100644 --- a/app/vendor/symfony/console/Output/BufferedOutput.php +++ b/app/vendor/symfony/console/Output/BufferedOutput.php @@ -29,10 +29,7 @@ public function fetch(): string return $content; } - /** - * @return void - */ - protected function doWrite(string $message, bool $newline) + protected function doWrite(string $message, bool $newline): void { $this->buffer .= $message; diff --git a/app/vendor/symfony/console/Output/ConsoleOutput.php b/app/vendor/symfony/console/Output/ConsoleOutput.php index 5837e74a3..2ad3dbcf3 100644 --- a/app/vendor/symfony/console/Output/ConsoleOutput.php +++ b/app/vendor/symfony/console/Output/ConsoleOutput.php @@ -64,28 +64,19 @@ public function section(): ConsoleSectionOutput return new ConsoleSectionOutput($this->getStream(), $this->consoleSectionOutputs, $this->getVerbosity(), $this->isDecorated(), $this->getFormatter()); } - /** - * @return void - */ - public function setDecorated(bool $decorated) + public function setDecorated(bool $decorated): void { parent::setDecorated($decorated); $this->stderr->setDecorated($decorated); } - /** - * @return void - */ - public function setFormatter(OutputFormatterInterface $formatter) + public function setFormatter(OutputFormatterInterface $formatter): void { parent::setFormatter($formatter); $this->stderr->setFormatter($formatter); } - /** - * @return void - */ - public function setVerbosity(int $level) + public function setVerbosity(int $level): void { parent::setVerbosity($level); $this->stderr->setVerbosity($level); @@ -96,10 +87,7 @@ public function getErrorOutput(): OutputInterface return $this->stderr; } - /** - * @return void - */ - public function setErrorOutput(OutputInterface $error) + public function setErrorOutput(OutputInterface $error): void { $this->stderr = $error; } diff --git a/app/vendor/symfony/console/Output/ConsoleOutputInterface.php b/app/vendor/symfony/console/Output/ConsoleOutputInterface.php index 9c0049c8f..1f8f147ce 100644 --- a/app/vendor/symfony/console/Output/ConsoleOutputInterface.php +++ b/app/vendor/symfony/console/Output/ConsoleOutputInterface.php @@ -24,10 +24,7 @@ interface ConsoleOutputInterface extends OutputInterface */ public function getErrorOutput(): OutputInterface; - /** - * @return void - */ - public function setErrorOutput(OutputInterface $error); + public function setErrorOutput(OutputInterface $error): void; public function section(): ConsoleSectionOutput; } diff --git a/app/vendor/symfony/console/Output/ConsoleSectionOutput.php b/app/vendor/symfony/console/Output/ConsoleSectionOutput.php index 04d587cf2..44728dfd4 100644 --- a/app/vendor/symfony/console/Output/ConsoleSectionOutput.php +++ b/app/vendor/symfony/console/Output/ConsoleSectionOutput.php @@ -60,12 +60,10 @@ public function setMaxHeight(int $maxHeight): void * Clears previous output for this section. * * @param int $lines Number of lines to clear. If null, then the entire output of this section is cleared - * - * @return void */ - public function clear(?int $lines = null) + public function clear(?int $lines = null): void { - if (empty($this->content) || !$this->isDecorated()) { + if (!$this->content || !$this->isDecorated()) { return; } @@ -83,10 +81,8 @@ public function clear(?int $lines = null) /** * Overwrites the previous output with a new message. - * - * @return void */ - public function overwrite(string|iterable $message) + public function overwrite(string|iterable $message): void { $this->clear(); $this->writeln($message); @@ -162,10 +158,7 @@ public function addNewLineOfInputSubmit(): void ++$this->lines; } - /** - * @return void - */ - protected function doWrite(string $message, bool $newline) + protected function doWrite(string $message, bool $newline): void { // Simulate newline behavior for consistent output formatting, avoiding extra logic if (!$newline && str_ends_with($message, \PHP_EOL)) { diff --git a/app/vendor/symfony/console/Output/NullOutput.php b/app/vendor/symfony/console/Output/NullOutput.php index f3aa15b1d..8bec706d4 100644 --- a/app/vendor/symfony/console/Output/NullOutput.php +++ b/app/vendor/symfony/console/Output/NullOutput.php @@ -26,10 +26,7 @@ class NullOutput implements OutputInterface { private NullOutputFormatter $formatter; - /** - * @return void - */ - public function setFormatter(OutputFormatterInterface $formatter) + public function setFormatter(OutputFormatterInterface $formatter): void { // do nothing } @@ -40,10 +37,7 @@ public function getFormatter(): OutputFormatterInterface return $this->formatter ??= new NullOutputFormatter(); } - /** - * @return void - */ - public function setDecorated(bool $decorated) + public function setDecorated(bool $decorated): void { // do nothing } @@ -53,24 +47,26 @@ public function isDecorated(): bool return false; } - /** - * @return void - */ - public function setVerbosity(int $level) + public function setVerbosity(int $level): void { // do nothing } public function getVerbosity(): int { - return self::VERBOSITY_QUIET; + return self::VERBOSITY_SILENT; } - public function isQuiet(): bool + public function isSilent(): bool { return true; } + public function isQuiet(): bool + { + return false; + } + public function isVerbose(): bool { return false; @@ -86,18 +82,12 @@ public function isDebug(): bool return false; } - /** - * @return void - */ - public function writeln(string|iterable $messages, int $options = self::OUTPUT_NORMAL) + public function writeln(string|iterable $messages, int $options = self::OUTPUT_NORMAL): void { // do nothing } - /** - * @return void - */ - public function write(string|iterable $messages, bool $newline = false, int $options = self::OUTPUT_NORMAL) + public function write(string|iterable $messages, bool $newline = false, int $options = self::OUTPUT_NORMAL): void { // do nothing } diff --git a/app/vendor/symfony/console/Output/Output.php b/app/vendor/symfony/console/Output/Output.php index 00f481e03..32e6cb241 100644 --- a/app/vendor/symfony/console/Output/Output.php +++ b/app/vendor/symfony/console/Output/Output.php @@ -17,13 +17,14 @@ /** * Base class for output classes. * - * There are five levels of verbosity: + * There are six levels of verbosity: * * * normal: no option passed (normal output) * * verbose: -v (more output) * * very verbose: -vv (highly extended output) * * debug: -vvv (all debug output) - * * quiet: -q (no output) + * * quiet: -q (only output errors) + * * silent: --silent (no output) * * @author Fabien Potencier */ @@ -44,10 +45,7 @@ public function __construct(?int $verbosity = self::VERBOSITY_NORMAL, bool $deco $this->formatter->setDecorated($decorated); } - /** - * @return void - */ - public function setFormatter(OutputFormatterInterface $formatter) + public function setFormatter(OutputFormatterInterface $formatter): void { $this->formatter = $formatter; } @@ -57,10 +55,7 @@ public function getFormatter(): OutputFormatterInterface return $this->formatter; } - /** - * @return void - */ - public function setDecorated(bool $decorated) + public function setDecorated(bool $decorated): void { $this->formatter->setDecorated($decorated); } @@ -70,10 +65,7 @@ public function isDecorated(): bool return $this->formatter->isDecorated(); } - /** - * @return void - */ - public function setVerbosity(int $level) + public function setVerbosity(int $level): void { $this->verbosity = $level; } @@ -83,6 +75,11 @@ public function getVerbosity(): int return $this->verbosity; } + public function isSilent(): bool + { + return self::VERBOSITY_SILENT === $this->verbosity; + } + public function isQuiet(): bool { return self::VERBOSITY_QUIET === $this->verbosity; @@ -103,18 +100,12 @@ public function isDebug(): bool return self::VERBOSITY_DEBUG <= $this->verbosity; } - /** - * @return void - */ - public function writeln(string|iterable $messages, int $options = self::OUTPUT_NORMAL) + public function writeln(string|iterable $messages, int $options = self::OUTPUT_NORMAL): void { $this->write($messages, true, $options); } - /** - * @return void - */ - public function write(string|iterable $messages, bool $newline = false, int $options = self::OUTPUT_NORMAL) + public function write(string|iterable $messages, bool $newline = false, int $options = self::OUTPUT_NORMAL): void { if (!is_iterable($messages)) { $messages = [$messages]; @@ -148,8 +139,6 @@ public function write(string|iterable $messages, bool $newline = false, int $opt /** * Writes a message to the output. - * - * @return void */ - abstract protected function doWrite(string $message, bool $newline); + abstract protected function doWrite(string $message, bool $newline): void; } diff --git a/app/vendor/symfony/console/Output/OutputInterface.php b/app/vendor/symfony/console/Output/OutputInterface.php index 19a817901..969a3b022 100644 --- a/app/vendor/symfony/console/Output/OutputInterface.php +++ b/app/vendor/symfony/console/Output/OutputInterface.php @@ -17,9 +17,12 @@ * OutputInterface is the interface implemented by all Output classes. * * @author Fabien Potencier + * + * @method bool isSilent() */ interface OutputInterface { + public const VERBOSITY_SILENT = 8; public const VERBOSITY_QUIET = 16; public const VERBOSITY_NORMAL = 32; public const VERBOSITY_VERBOSE = 64; @@ -36,29 +39,23 @@ interface OutputInterface * @param bool $newline Whether to add a newline * @param int $options A bitmask of options (one of the OUTPUT or VERBOSITY constants), * 0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL - * - * @return void */ - public function write(string|iterable $messages, bool $newline = false, int $options = 0); + public function write(string|iterable $messages, bool $newline = false, int $options = 0): void; /** * Writes a message to the output and adds a newline at the end. * * @param int $options A bitmask of options (one of the OUTPUT or VERBOSITY constants), * 0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL - * - * @return void */ - public function writeln(string|iterable $messages, int $options = 0); + public function writeln(string|iterable $messages, int $options = 0): void; /** * Sets the verbosity of the output. * * @param self::VERBOSITY_* $level - * - * @return void */ - public function setVerbosity(int $level); + public function setVerbosity(int $level): void; /** * Gets the current verbosity of the output. @@ -89,20 +86,15 @@ public function isDebug(): bool; /** * Sets the decorated flag. - * - * @return void */ - public function setDecorated(bool $decorated); + public function setDecorated(bool $decorated): void; /** * Gets the decorated flag. */ public function isDecorated(): bool; - /** - * @return void - */ - public function setFormatter(OutputFormatterInterface $formatter); + public function setFormatter(OutputFormatterInterface $formatter): void; /** * Returns current output formatter instance. diff --git a/app/vendor/symfony/console/Output/StreamOutput.php b/app/vendor/symfony/console/Output/StreamOutput.php index f51d03763..ce5a825e8 100644 --- a/app/vendor/symfony/console/Output/StreamOutput.php +++ b/app/vendor/symfony/console/Output/StreamOutput.php @@ -63,10 +63,7 @@ public function getStream() return $this->stream; } - /** - * @return void - */ - protected function doWrite(string $message, bool $newline) + protected function doWrite(string $message, bool $newline): void { if ($newline) { $message .= \PHP_EOL; @@ -97,6 +94,11 @@ protected function hasColorSupport(): bool return false; } + // Follow https://force-color.org/ + if ('' !== (($_SERVER['FORCE_COLOR'] ?? getenv('FORCE_COLOR'))[0] ?? '')) { + return true; + } + // Detect msysgit/mingw and assume this is a tty because detection // does not work correctly, see https://github.com/composer/composer/issues/9690 if (!@stream_isatty($this->stream) && !\in_array(strtoupper((string) getenv('MSYSTEM')), ['MINGW32', 'MINGW64'], true)) { diff --git a/app/vendor/symfony/console/Output/TrimmedBufferOutput.php b/app/vendor/symfony/console/Output/TrimmedBufferOutput.php index 90ee45aae..33db072c5 100644 --- a/app/vendor/symfony/console/Output/TrimmedBufferOutput.php +++ b/app/vendor/symfony/console/Output/TrimmedBufferOutput.php @@ -45,10 +45,7 @@ public function fetch(): string return $content; } - /** - * @return void - */ - protected function doWrite(string $message, bool $newline) + protected function doWrite(string $message, bool $newline): void { $this->buffer .= $message; @@ -56,6 +53,6 @@ protected function doWrite(string $message, bool $newline) $this->buffer .= \PHP_EOL; } - $this->buffer = substr($this->buffer, 0 - $this->maxLength); + $this->buffer = substr($this->buffer, -$this->maxLength); } } diff --git a/app/vendor/symfony/console/Question/ChoiceQuestion.php b/app/vendor/symfony/console/Question/ChoiceQuestion.php index 9445ccc0c..36c240d37 100644 --- a/app/vendor/symfony/console/Question/ChoiceQuestion.php +++ b/app/vendor/symfony/console/Question/ChoiceQuestion.php @@ -20,7 +20,6 @@ */ class ChoiceQuestion extends Question { - private array $choices; private bool $multiselect = false; private string $prompt = ' > '; private string $errorMessage = 'Value "%s" is invalid'; @@ -30,15 +29,17 @@ class ChoiceQuestion extends Question * @param array $choices The list of available choices * @param string|bool|int|float|null $default The default answer to return */ - public function __construct(string $question, array $choices, string|bool|int|float|null $default = null) - { + public function __construct( + string $question, + private array $choices, + string|bool|int|float|null $default = null, + ) { if (!$choices) { throw new \LogicException('Choice question must have at least 1 choice available.'); } parent::__construct($question, $default); - $this->choices = $choices; $this->setValidator($this->getDefaultValidator()); $this->setAutocompleterValues($choices); } diff --git a/app/vendor/symfony/console/Question/ConfirmationQuestion.php b/app/vendor/symfony/console/Question/ConfirmationQuestion.php index 40eab2429..951d68140 100644 --- a/app/vendor/symfony/console/Question/ConfirmationQuestion.php +++ b/app/vendor/symfony/console/Question/ConfirmationQuestion.php @@ -18,18 +18,18 @@ */ class ConfirmationQuestion extends Question { - private string $trueAnswerRegex; - /** * @param string $question The question to ask to the user * @param bool $default The default answer to return, true or false * @param string $trueAnswerRegex A regex to match the "yes" answer */ - public function __construct(string $question, bool $default = true, string $trueAnswerRegex = '/^y/i') - { + public function __construct( + string $question, + bool $default = true, + private string $trueAnswerRegex = '/^y/i', + ) { parent::__construct($question, $default); - $this->trueAnswerRegex = $trueAnswerRegex; $this->setNormalizer($this->getDefaultNormalizer()); } diff --git a/app/vendor/symfony/console/Question/Question.php b/app/vendor/symfony/console/Question/Question.php index ecbde56d5..46a60c798 100644 --- a/app/vendor/symfony/console/Question/Question.php +++ b/app/vendor/symfony/console/Question/Question.php @@ -21,13 +21,11 @@ */ class Question { - private string $question; private ?int $attempts = null; private bool $hidden = false; private bool $hiddenFallback = true; private ?\Closure $autocompleterCallback = null; private ?\Closure $validator = null; - private string|int|bool|float|null $default; private ?\Closure $normalizer = null; private bool $trimmable = true; private bool $multiline = false; @@ -36,10 +34,10 @@ class Question * @param string $question The question to ask to the user * @param string|bool|int|float|null $default The default answer to return if the user enters nothing */ - public function __construct(string $question, string|bool|int|float|null $default = null) - { - $this->question = $question; - $this->default = $default; + public function __construct( + private string $question, + private string|bool|int|float|null $default = null, + ) { } /** @@ -175,11 +173,8 @@ public function getAutocompleterCallback(): ?callable * * @return $this */ - public function setAutocompleterCallback(?callable $callback = null): static + public function setAutocompleterCallback(?callable $callback): static { - if (1 > \func_num_args()) { - trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__); - } if ($this->hidden && null !== $callback) { throw new LogicException('A hidden question cannot use the autocompleter.'); } @@ -194,11 +189,8 @@ public function setAutocompleterCallback(?callable $callback = null): static * * @return $this */ - public function setValidator(?callable $validator = null): static + public function setValidator(?callable $validator): static { - if (1 > \func_num_args()) { - trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__); - } $this->validator = null === $validator ? null : $validator(...); return $this; @@ -266,10 +258,7 @@ public function getNormalizer(): ?callable return $this->normalizer; } - /** - * @return bool - */ - protected function isAssoc(array $array) + protected function isAssoc(array $array): bool { return (bool) \count(array_filter(array_keys($array), 'is_string')); } diff --git a/app/vendor/symfony/console/README.md b/app/vendor/symfony/console/README.md index e9013182a..92f70e714 100644 --- a/app/vendor/symfony/console/README.md +++ b/app/vendor/symfony/console/README.md @@ -7,14 +7,7 @@ interfaces. Sponsor ------- -The Console component for Symfony 6.4 is [backed][1] by [Les-Tilleuls.coop][2]. - -Les-Tilleuls.coop is a team of 70+ Symfony experts who can help you design, develop and -fix your projects. They provide a wide range of professional services including development, -consulting, coaching, training and audits. They also are highly skilled in JS, Go and DevOps. -They are a worker cooperative! - -Help Symfony by [sponsoring][3] its development! +Help Symfony by [sponsoring][1] its development! Resources --------- @@ -31,6 +24,4 @@ Credits `Resources/bin/hiddeninput.exe` is a third party binary provided within this component. Find sources and license at https://github.com/Seldaek/hidden-input. -[1]: https://symfony.com/backers -[2]: https://les-tilleuls.coop -[3]: https://symfony.com/sponsor +[1]: https://symfony.com/sponsor diff --git a/app/vendor/symfony/console/Resources/completion.fish b/app/vendor/symfony/console/Resources/completion.fish index 1c34292ae..1853dd80f 100644 --- a/app/vendor/symfony/console/Resources/completion.fish +++ b/app/vendor/symfony/console/Resources/completion.fish @@ -19,11 +19,7 @@ function _sf_{{ COMMAND_NAME }} set completecmd $completecmd "-c$c" - set sfcomplete ($completecmd) - - for i in $sfcomplete - echo $i - end + $completecmd end complete -c '{{ COMMAND_NAME }}' -a '(_sf_{{ COMMAND_NAME }})' -f diff --git a/app/vendor/symfony/console/SignalRegistry/SignalRegistry.php b/app/vendor/symfony/console/SignalRegistry/SignalRegistry.php index ef2e5f04e..8c2939eec 100644 --- a/app/vendor/symfony/console/SignalRegistry/SignalRegistry.php +++ b/app/vendor/symfony/console/SignalRegistry/SignalRegistry.php @@ -54,4 +54,12 @@ public function handle(int $signal): void $signalHandler($signal, $hasNext); } } + + /** + * @internal + */ + public function scheduleAlarm(int $seconds): void + { + pcntl_alarm($seconds); + } } diff --git a/app/vendor/symfony/console/SingleCommandApplication.php b/app/vendor/symfony/console/SingleCommandApplication.php index ff1c17247..2b54fb870 100644 --- a/app/vendor/symfony/console/SingleCommandApplication.php +++ b/app/vendor/symfony/console/SingleCommandApplication.php @@ -67,6 +67,6 @@ public function run(?InputInterface $input = null, ?OutputInterface $output = nu $this->running = false; } - return $ret ?? 1; + return $ret; } } diff --git a/app/vendor/symfony/console/Style/OutputStyle.php b/app/vendor/symfony/console/Style/OutputStyle.php index ddfa8decc..89a3a4177 100644 --- a/app/vendor/symfony/console/Style/OutputStyle.php +++ b/app/vendor/symfony/console/Style/OutputStyle.php @@ -23,17 +23,12 @@ */ abstract class OutputStyle implements OutputInterface, StyleInterface { - private OutputInterface $output; - - public function __construct(OutputInterface $output) - { - $this->output = $output; + public function __construct( + private OutputInterface $output, + ) { } - /** - * @return void - */ - public function newLine(int $count = 1) + public function newLine(int $count = 1): void { $this->output->write(str_repeat(\PHP_EOL, $count)); } @@ -43,26 +38,17 @@ public function createProgressBar(int $max = 0): ProgressBar return new ProgressBar($this->output, $max); } - /** - * @return void - */ - public function write(string|iterable $messages, bool $newline = false, int $type = self::OUTPUT_NORMAL) + public function write(string|iterable $messages, bool $newline = false, int $type = self::OUTPUT_NORMAL): void { $this->output->write($messages, $newline, $type); } - /** - * @return void - */ - public function writeln(string|iterable $messages, int $type = self::OUTPUT_NORMAL) + public function writeln(string|iterable $messages, int $type = self::OUTPUT_NORMAL): void { $this->output->writeln($messages, $type); } - /** - * @return void - */ - public function setVerbosity(int $level) + public function setVerbosity(int $level): void { $this->output->setVerbosity($level); } @@ -72,10 +58,7 @@ public function getVerbosity(): int return $this->output->getVerbosity(); } - /** - * @return void - */ - public function setDecorated(bool $decorated) + public function setDecorated(bool $decorated): void { $this->output->setDecorated($decorated); } @@ -85,10 +68,7 @@ public function isDecorated(): bool return $this->output->isDecorated(); } - /** - * @return void - */ - public function setFormatter(OutputFormatterInterface $formatter) + public function setFormatter(OutputFormatterInterface $formatter): void { $this->output->setFormatter($formatter); } @@ -98,6 +78,12 @@ public function getFormatter(): OutputFormatterInterface return $this->output->getFormatter(); } + public function isSilent(): bool + { + // @deprecated since Symfony 7.2, change to $this->output->isSilent() in 8.0 + return method_exists($this->output, 'isSilent') ? $this->output->isSilent() : self::VERBOSITY_SILENT === $this->output->getVerbosity(); + } + public function isQuiet(): bool { return $this->output->isQuiet(); @@ -118,10 +104,7 @@ public function isDebug(): bool return $this->output->isDebug(); } - /** - * @return OutputInterface - */ - protected function getErrorOutput() + protected function getErrorOutput(): OutputInterface { if (!$this->output instanceof ConsoleOutputInterface) { return $this->output; diff --git a/app/vendor/symfony/console/Style/StyleInterface.php b/app/vendor/symfony/console/Style/StyleInterface.php index 6bced158a..fcc5bc775 100644 --- a/app/vendor/symfony/console/Style/StyleInterface.php +++ b/app/vendor/symfony/console/Style/StyleInterface.php @@ -20,73 +20,53 @@ interface StyleInterface { /** * Formats a command title. - * - * @return void */ - public function title(string $message); + public function title(string $message): void; /** * Formats a section title. - * - * @return void */ - public function section(string $message); + public function section(string $message): void; /** * Formats a list. - * - * @return void */ - public function listing(array $elements); + public function listing(array $elements): void; /** * Formats informational text. - * - * @return void */ - public function text(string|array $message); + public function text(string|array $message): void; /** * Formats a success result bar. - * - * @return void */ - public function success(string|array $message); + public function success(string|array $message): void; /** * Formats an error result bar. - * - * @return void */ - public function error(string|array $message); + public function error(string|array $message): void; /** * Formats an warning result bar. - * - * @return void */ - public function warning(string|array $message); + public function warning(string|array $message): void; /** * Formats a note admonition. - * - * @return void */ - public function note(string|array $message); + public function note(string|array $message): void; /** * Formats a caution admonition. - * - * @return void */ - public function caution(string|array $message); + public function caution(string|array $message): void; /** * Formats a table. - * - * @return void */ - public function table(array $headers, array $rows); + public function table(array $headers, array $rows): void; /** * Asks a question. @@ -110,29 +90,21 @@ public function choice(string $question, array $choices, mixed $default = null): /** * Add newline(s). - * - * @return void */ - public function newLine(int $count = 1); + public function newLine(int $count = 1): void; /** * Starts the progress output. - * - * @return void */ - public function progressStart(int $max = 0); + public function progressStart(int $max = 0): void; /** * Advances the progress output X steps. - * - * @return void */ - public function progressAdvance(int $step = 1); + public function progressAdvance(int $step = 1): void; /** * Finishes the progress output. - * - * @return void */ - public function progressFinish(); + public function progressFinish(): void; } diff --git a/app/vendor/symfony/console/Style/SymfonyStyle.php b/app/vendor/symfony/console/Style/SymfonyStyle.php index 135f4fd0e..d0788e88d 100644 --- a/app/vendor/symfony/console/Style/SymfonyStyle.php +++ b/app/vendor/symfony/console/Style/SymfonyStyle.php @@ -21,6 +21,9 @@ use Symfony\Component\Console\Helper\Table; use Symfony\Component\Console\Helper\TableCell; use Symfony\Component\Console\Helper\TableSeparator; +use Symfony\Component\Console\Helper\TreeHelper; +use Symfony\Component\Console\Helper\TreeNode; +use Symfony\Component\Console\Helper\TreeStyle; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\ConsoleOutputInterface; use Symfony\Component\Console\Output\ConsoleSectionOutput; @@ -40,30 +43,27 @@ class SymfonyStyle extends OutputStyle { public const MAX_LINE_LENGTH = 120; - private InputInterface $input; - private OutputInterface $output; private SymfonyQuestionHelper $questionHelper; private ProgressBar $progressBar; private int $lineLength; private TrimmedBufferOutput $bufferedOutput; - public function __construct(InputInterface $input, OutputInterface $output) - { - $this->input = $input; + public function __construct( + private InputInterface $input, + private OutputInterface $output, + ) { $this->bufferedOutput = new TrimmedBufferOutput(\DIRECTORY_SEPARATOR === '\\' ? 4 : 2, $output->getVerbosity(), false, clone $output->getFormatter()); // Windows cmd wraps lines as soon as the terminal width is reached, whether there are following chars or not. $width = (new Terminal())->getWidth() ?: self::MAX_LINE_LENGTH; $this->lineLength = min($width - (int) (\DIRECTORY_SEPARATOR === '\\'), self::MAX_LINE_LENGTH); - parent::__construct($this->output = $output); + parent::__construct($output); } /** * Formats a message as a block of text. - * - * @return void */ - public function block(string|array $messages, ?string $type = null, ?string $style = null, string $prefix = ' ', bool $padding = false, bool $escape = true) + public function block(string|array $messages, ?string $type = null, ?string $style = null, string $prefix = ' ', bool $padding = false, bool $escape = true): void { $messages = \is_array($messages) ? array_values($messages) : [$messages]; @@ -72,10 +72,7 @@ public function block(string|array $messages, ?string $type = null, ?string $sty $this->newLine(); } - /** - * @return void - */ - public function title(string $message) + public function title(string $message): void { $this->autoPrependBlock(); $this->writeln([ @@ -85,10 +82,7 @@ public function title(string $message) $this->newLine(); } - /** - * @return void - */ - public function section(string $message) + public function section(string $message): void { $this->autoPrependBlock(); $this->writeln([ @@ -98,10 +92,7 @@ public function section(string $message) $this->newLine(); } - /** - * @return void - */ - public function listing(array $elements) + public function listing(array $elements): void { $this->autoPrependText(); $elements = array_map(fn ($element) => \sprintf(' * %s', $element), $elements); @@ -110,10 +101,7 @@ public function listing(array $elements) $this->newLine(); } - /** - * @return void - */ - public function text(string|array $message) + public function text(string|array $message): void { $this->autoPrependText(); @@ -125,68 +113,46 @@ public function text(string|array $message) /** * Formats a command comment. - * - * @return void */ - public function comment(string|array $message) + public function comment(string|array $message): void { $this->block($message, null, null, ' // ', false, false); } - /** - * @return void - */ - public function success(string|array $message) + public function success(string|array $message): void { $this->block($message, 'OK', 'fg=black;bg=green', ' ', true); } - /** - * @return void - */ - public function error(string|array $message) + public function error(string|array $message): void { $this->block($message, 'ERROR', 'fg=white;bg=red', ' ', true); } - /** - * @return void - */ - public function warning(string|array $message) + public function warning(string|array $message): void { $this->block($message, 'WARNING', 'fg=black;bg=yellow', ' ', true); } - /** - * @return void - */ - public function note(string|array $message) + public function note(string|array $message): void { $this->block($message, 'NOTE', 'fg=yellow', ' ! '); } /** * Formats an info message. - * - * @return void */ - public function info(string|array $message) + public function info(string|array $message): void { $this->block($message, 'INFO', 'fg=green', ' ', true); } - /** - * @return void - */ - public function caution(string|array $message) + public function caution(string|array $message): void { $this->block($message, 'CAUTION', 'fg=white;bg=red', ' ! ', true); } - /** - * @return void - */ - public function table(array $headers, array $rows) + public function table(array $headers, array $rows): void { $this->createTable() ->setHeaders($headers) @@ -199,10 +165,8 @@ public function table(array $headers, array $rows) /** * Formats a horizontal table. - * - * @return void */ - public function horizontalTable(array $headers, array $rows) + public function horizontalTable(array $headers, array $rows): void { $this->createTable() ->setHorizontal(true) @@ -221,10 +185,8 @@ public function horizontalTable(array $headers, array $rows) * * 'A title' * * ['key' => 'value'] * * new TableSeparator() - * - * @return void */ - public function definitionList(string|array|TableSeparator ...$list) + public function definitionList(string|array|TableSeparator ...$list): void { $headers = []; $row = []; @@ -285,27 +247,18 @@ public function choice(string $question, array $choices, mixed $default = null, return $this->askQuestion($questionChoice); } - /** - * @return void - */ - public function progressStart(int $max = 0) + public function progressStart(int $max = 0): void { $this->progressBar = $this->createProgressBar($max); $this->progressBar->start(); } - /** - * @return void - */ - public function progressAdvance(int $step = 1) + public function progressAdvance(int $step = 1): void { $this->getProgressBar()->advance($step); } - /** - * @return void - */ - public function progressFinish() + public function progressFinish(): void { $this->getProgressBar()->finish(); $this->newLine(2); @@ -366,10 +319,7 @@ public function askQuestion(Question $question): mixed return $answer; } - /** - * @return void - */ - public function writeln(string|iterable $messages, int $type = self::OUTPUT_NORMAL) + public function writeln(string|iterable $messages, int $type = self::OUTPUT_NORMAL): void { if (!is_iterable($messages)) { $messages = [$messages]; @@ -381,10 +331,7 @@ public function writeln(string|iterable $messages, int $type = self::OUTPUT_NORM } } - /** - * @return void - */ - public function write(string|iterable $messages, bool $newline = false, int $type = self::OUTPUT_NORMAL) + public function write(string|iterable $messages, bool $newline = false, int $type = self::OUTPUT_NORMAL): void { if (!is_iterable($messages)) { $messages = [$messages]; @@ -396,10 +343,7 @@ public function write(string|iterable $messages, bool $newline = false, int $typ } } - /** - * @return void - */ - public function newLine(int $count = 1) + public function newLine(int $count = 1): void { parent::newLine($count); $this->bufferedOutput->write(str_repeat("\n", $count)); @@ -428,6 +372,24 @@ private function getProgressBar(): ProgressBar ?? throw new RuntimeException('The ProgressBar is not started.'); } + /** + * @param iterable $nodes + */ + public function tree(iterable $nodes, string $root = ''): void + { + $this->createTree($nodes, $root)->render(); + } + + /** + * @param iterable $nodes + */ + public function createTree(iterable $nodes, string $root = ''): TreeHelper + { + $output = $this->output instanceof ConsoleOutputInterface ? $this->output->section() : $this->output; + + return TreeHelper::createTree($output, $root, $nodes, TreeStyle::default()); + } + private function autoPrependBlock(): void { $chars = substr(str_replace(\PHP_EOL, "\n", $this->bufferedOutput->fetch()), -2); diff --git a/app/vendor/symfony/console/Terminal.php b/app/vendor/symfony/console/Terminal.php index f094adedc..80f254434 100644 --- a/app/vendor/symfony/console/Terminal.php +++ b/app/vendor/symfony/console/Terminal.php @@ -140,7 +140,7 @@ private static function initDimensions(): void // or [w, h] from "wxh" self::$width = (int) $matches[1]; self::$height = isset($matches[4]) ? (int) $matches[4] : (int) $matches[2]; - } elseif (!self::hasVt100Support() && self::hasSttyAvailable()) { + } elseif (!sapi_windows_vt100_support(fopen('php://stdout', 'w')) && self::hasSttyAvailable()) { // only use stty on Windows if the terminal does not support vt100 (e.g. Windows 7 + git-bash) // testing for stty in a Windows 10 vt100-enabled console will implicitly disable vt100 support on STDOUT self::initDimensionsUsingStty(); @@ -154,14 +154,6 @@ private static function initDimensions(): void } } - /** - * Returns whether STDOUT has vt100 support (some Windows 10+ configurations). - */ - private static function hasVt100Support(): bool - { - return \function_exists('sapi_windows_vt100_support') && sapi_windows_vt100_support(fopen('php://stdout', 'w')); - } - /** * Initializes dimensions using the output of an stty columns line. */ diff --git a/app/vendor/symfony/console/Tester/ApplicationTester.php b/app/vendor/symfony/console/Tester/ApplicationTester.php index 58aee54d6..a6dc8e1ce 100644 --- a/app/vendor/symfony/console/Tester/ApplicationTester.php +++ b/app/vendor/symfony/console/Tester/ApplicationTester.php @@ -28,11 +28,9 @@ class ApplicationTester { use TesterTrait; - private Application $application; - - public function __construct(Application $application) - { - $this->application = $application; + public function __construct( + private Application $application, + ) { } /** @@ -49,37 +47,17 @@ public function __construct(Application $application) */ public function run(array $input, array $options = []): int { - $prevShellVerbosity = getenv('SHELL_VERBOSITY'); - - try { - $this->input = new ArrayInput($input); - if (isset($options['interactive'])) { - $this->input->setInteractive($options['interactive']); - } + $this->input = new ArrayInput($input); + if (isset($options['interactive'])) { + $this->input->setInteractive($options['interactive']); + } - if ($this->inputs) { - $this->input->setStream(self::createStream($this->inputs)); - } + if ($this->inputs) { + $this->input->setStream(self::createStream($this->inputs)); + } - $this->initOutput($options); + $this->initOutput($options); - return $this->statusCode = $this->application->run($this->input, $this->output); - } finally { - // SHELL_VERBOSITY is set by Application::configureIO so we need to unset/reset it - // to its previous value to avoid one test's verbosity to spread to the following tests - if (false === $prevShellVerbosity) { - if (\function_exists('putenv')) { - @putenv('SHELL_VERBOSITY'); - } - unset($_ENV['SHELL_VERBOSITY']); - unset($_SERVER['SHELL_VERBOSITY']); - } else { - if (\function_exists('putenv')) { - @putenv('SHELL_VERBOSITY='.$prevShellVerbosity); - } - $_ENV['SHELL_VERBOSITY'] = $prevShellVerbosity; - $_SERVER['SHELL_VERBOSITY'] = $prevShellVerbosity; - } - } + return $this->statusCode = $this->application->run($this->input, $this->output); } } diff --git a/app/vendor/symfony/console/Tester/CommandCompletionTester.php b/app/vendor/symfony/console/Tester/CommandCompletionTester.php index a90fe52ef..76cbaf14f 100644 --- a/app/vendor/symfony/console/Tester/CommandCompletionTester.php +++ b/app/vendor/symfony/console/Tester/CommandCompletionTester.php @@ -22,11 +22,9 @@ */ class CommandCompletionTester { - private Command $command; - - public function __construct(Command $command) - { - $this->command = $command; + public function __construct( + private Command $command, + ) { } /** diff --git a/app/vendor/symfony/console/Tester/CommandTester.php b/app/vendor/symfony/console/Tester/CommandTester.php index 2ff813b7d..d39cde7f6 100644 --- a/app/vendor/symfony/console/Tester/CommandTester.php +++ b/app/vendor/symfony/console/Tester/CommandTester.php @@ -24,11 +24,9 @@ class CommandTester { use TesterTrait; - private Command $command; - - public function __construct(Command $command) - { - $this->command = $command; + public function __construct( + private Command $command, + ) { } /** diff --git a/app/vendor/symfony/console/Tester/TesterTrait.php b/app/vendor/symfony/console/Tester/TesterTrait.php index 1ab7a70aa..127556d1d 100644 --- a/app/vendor/symfony/console/Tester/TesterTrait.php +++ b/app/vendor/symfony/console/Tester/TesterTrait.php @@ -169,6 +169,10 @@ private static function createStream(array $inputs) foreach ($inputs as $input) { fwrite($stream, $input.\PHP_EOL); + + if (str_contains($input, \PHP_EOL)) { + fwrite($stream, "\x4"); + } } rewind($stream); diff --git a/app/vendor/symfony/console/composer.json b/app/vendor/symfony/console/composer.json index 1610f7341..65d69913a 100644 --- a/app/vendor/symfony/console/composer.json +++ b/app/vendor/symfony/console/composer.json @@ -16,34 +16,34 @@ } ], "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.0", "symfony/service-contracts": "^2.5|^3", - "symfony/string": "^5.4|^6.0|^7.0" + "symfony/string": "^7.2" }, "require-dev": { - "symfony/config": "^5.4|^6.0|^7.0", - "symfony/event-dispatcher": "^5.4|^6.0|^7.0", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", "symfony/http-foundation": "^6.4|^7.0", "symfony/http-kernel": "^6.4|^7.0", - "symfony/lock": "^5.4|^6.0|^7.0", - "symfony/messenger": "^5.4|^6.0|^7.0", - "symfony/process": "^5.4|^6.0|^7.0", - "symfony/stopwatch": "^5.4|^6.0|^7.0", - "symfony/var-dumper": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/lock": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0", "psr/log": "^1|^2|^3" }, "provide": { "psr/log-implementation": "1.0|2.0|3.0" }, "conflict": { - "symfony/dependency-injection": "<5.4", - "symfony/dotenv": "<5.4", - "symfony/event-dispatcher": "<5.4", - "symfony/lock": "<5.4", - "symfony/process": "<5.4" + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" }, "autoload": { "psr-4": { "Symfony\\Component\\Console\\": "" }, diff --git a/app/vendor/symfony/html-sanitizer/Visitor/DomVisitor.php b/app/vendor/symfony/html-sanitizer/Visitor/DomVisitor.php index e6d34a096..fa381e310 100644 --- a/app/vendor/symfony/html-sanitizer/Visitor/DomVisitor.php +++ b/app/vendor/symfony/html-sanitizer/Visitor/DomVisitor.php @@ -129,7 +129,7 @@ private function enterNode(string $domNodeName, \DOMNode $domNode, Cursor $curso // Force configured attributes foreach ($this->forcedAttributes[$domNodeName] ?? [] as $attribute => $value) { - $node->setAttribute($attribute, $value); + $node->setAttribute($attribute, $value, true); } $cursor->node->addChild($node); diff --git a/app/vendor/symfony/html-sanitizer/Visitor/Node/Node.php b/app/vendor/symfony/html-sanitizer/Visitor/Node/Node.php index d25803776..1b5818dd3 100644 --- a/app/vendor/symfony/html-sanitizer/Visitor/Node/Node.php +++ b/app/vendor/symfony/html-sanitizer/Visitor/Node/Node.php @@ -56,10 +56,10 @@ public function getAttribute(string $name): ?string return $this->attributes[$name] ?? null; } - public function setAttribute(string $name, ?string $value): void + public function setAttribute(string $name, ?string $value, bool $override = false): void { // Always use only the first declaration (ease sanitization) - if (!\array_key_exists($name, $this->attributes)) { + if ($override || !\array_key_exists($name, $this->attributes)) { $this->attributes[$name] = $value; } } diff --git a/app/vendor/symfony/polyfill-intl-grapheme/Grapheme.php b/app/vendor/symfony/polyfill-intl-grapheme/Grapheme.php index 5373f1685..f9e9e5741 100644 --- a/app/vendor/symfony/polyfill-intl-grapheme/Grapheme.php +++ b/app/vendor/symfony/polyfill-intl-grapheme/Grapheme.php @@ -26,6 +26,7 @@ * - grapheme_strrpos - Find position (in grapheme units) of last occurrence of a string * - grapheme_strstr - Returns part of haystack string from the first occurrence of needle to the end of haystack * - grapheme_substr - Return part of a string + * - grapheme_str_split - Splits a string into an array of individual or chunks of graphemes * * @author Nicolas Grekas * @@ -191,6 +192,37 @@ public static function grapheme_strstr($s, $needle, $beforeNeedle = false) return mb_strstr($s, $needle, $beforeNeedle, 'UTF-8'); } + public static function grapheme_str_split($s, $len = 1) + { + if (0 > $len || 1073741823 < $len) { + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError('grapheme_str_split(): Argument #2 ($length) must be greater than 0 and less than or equal to 1073741823.'); + } + + if ('' === $s) { + return []; + } + + if (!preg_match_all('/('.SYMFONY_GRAPHEME_CLUSTER_RX.')/u', $s, $matches)) { + return false; + } + + if (1 === $len) { + return $matches[0]; + } + + $chunks = array_chunk($matches[0], $len); + + foreach ($chunks as &$chunk) { + $chunk = implode('', $chunk); + } + + return $chunks; + } + private static function grapheme_position($s, $needle, $offset, $mode) { $needle = (string) $needle; diff --git a/app/vendor/symfony/polyfill-intl-grapheme/README.md b/app/vendor/symfony/polyfill-intl-grapheme/README.md index f55d92c5c..5d7c67875 100644 --- a/app/vendor/symfony/polyfill-intl-grapheme/README.md +++ b/app/vendor/symfony/polyfill-intl-grapheme/README.md @@ -21,6 +21,7 @@ This component provides a partial, native PHP implementation of the - [`grapheme_strstr`](https://php.net/grapheme_strstr): Returns part of haystack string from the first occurrence of needle to the end of haystack - [`grapheme_substr`](https://php.net/grapheme_substr): Return part of a string +- [`grapheme_str_split`](https://php.net/grapheme_str_split): Splits a string into an array of individual or chunks of graphemes More information can be found in the [main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). diff --git a/app/vendor/symfony/polyfill-intl-grapheme/bootstrap.php b/app/vendor/symfony/polyfill-intl-grapheme/bootstrap.php index a9ea03c7e..374dbd3a7 100644 --- a/app/vendor/symfony/polyfill-intl-grapheme/bootstrap.php +++ b/app/vendor/symfony/polyfill-intl-grapheme/bootstrap.php @@ -11,10 +11,6 @@ use Symfony\Polyfill\Intl\Grapheme as p; -if (extension_loaded('intl')) { - return; -} - if (\PHP_VERSION_ID >= 80000) { return require __DIR__.'/bootstrap80.php'; } @@ -56,3 +52,6 @@ function grapheme_strstr($haystack, $needle, $beforeNeedle = false) { return p\G if (!function_exists('grapheme_substr')) { function grapheme_substr($string, $offset, $length = null) { return p\Grapheme::grapheme_substr($string, $offset, $length); } } +if (!function_exists('grapheme_str_split')) { + function grapheme_str_split($string, $length = 1) { return p\Grapheme::grapheme_str_split($string, $length); } +} diff --git a/app/vendor/symfony/polyfill-intl-grapheme/bootstrap80.php b/app/vendor/symfony/polyfill-intl-grapheme/bootstrap80.php index b8c078677..d71175530 100644 --- a/app/vendor/symfony/polyfill-intl-grapheme/bootstrap80.php +++ b/app/vendor/symfony/polyfill-intl-grapheme/bootstrap80.php @@ -11,6 +11,14 @@ use Symfony\Polyfill\Intl\Grapheme as p; +if (!function_exists('grapheme_str_split')) { + function grapheme_str_split(string $string, int $length = 1): array|false { return p\Grapheme::grapheme_str_split($string, $length); } +} + +if (extension_loaded('intl')) { + return; +} + if (!defined('GRAPHEME_EXTR_COUNT')) { define('GRAPHEME_EXTR_COUNT', 0); } diff --git a/app/vendor/symfony/process/Pipes/AbstractPipes.php b/app/vendor/symfony/process/Pipes/AbstractPipes.php index 51a566f3b..158f0487f 100644 --- a/app/vendor/symfony/process/Pipes/AbstractPipes.php +++ b/app/vendor/symfony/process/Pipes/AbstractPipes.php @@ -52,14 +52,26 @@ public function close(): void /** * Returns true if a system call has been interrupted. + * + * stream_select() returns false when the `select` system call is interrupted by an incoming signal. */ protected function hasSystemCallBeenInterrupted(): bool { $lastError = $this->lastError; $this->lastError = null; - // stream_select returns false when the `select` system call is interrupted by an incoming signal - return null !== $lastError && false !== stripos($lastError, 'interrupted system call'); + if (null === $lastError) { + return false; + } + + if (false !== stripos($lastError, 'interrupted system call')) { + return true; + } + + // on applications with a different locale than english, the message above is not found because + // it's translated. So we also check for the SOCKET_EINTR constant which is defined under + // Windows and UNIX-like platforms (if available on the platform). + return \defined('SOCKET_EINTR') && str_starts_with($lastError, 'stream_select(): Unable to select ['.\SOCKET_EINTR.']'); } /** @@ -72,10 +84,10 @@ protected function unblock(): void } foreach ($this->pipes as $pipe) { - stream_set_blocking($pipe, 0); + stream_set_blocking($pipe, false); } if (\is_resource($this->input)) { - stream_set_blocking($this->input, 0); + stream_set_blocking($this->input, false); } $this->blocked = false; @@ -97,7 +109,7 @@ protected function write(): ?array if (!$input->valid()) { $input = null; } elseif (\is_resource($input = $input->current())) { - stream_set_blocking($input, 0); + stream_set_blocking($input, false); } elseif (!isset($this->inputBuffer[0])) { if (!\is_string($input)) { if (!\is_scalar($input)) { diff --git a/app/vendor/symfony/string/Inflector/EnglishInflector.php b/app/vendor/symfony/string/Inflector/EnglishInflector.php index 73db80c6f..b9d74c004 100644 --- a/app/vendor/symfony/string/Inflector/EnglishInflector.php +++ b/app/vendor/symfony/string/Inflector/EnglishInflector.php @@ -25,9 +25,15 @@ final class EnglishInflector implements InflectorInterface // Fourth entry: Whether the suffix may succeed a consonant // Fifth entry: singular suffix, normal + // nodes (node) + ['sedon', 5, true, true, 'node'], + // bacteria (bacterium) ['airetcab', 8, true, true, 'bacterium'], + // issues (issue) + ['seussi', 6, true, true, 'issue'], + // corpora (corpus) ['aroproc', 7, true, true, 'corpus'], @@ -166,6 +172,9 @@ final class EnglishInflector implements InflectorInterface // edges (edge) ['segd', 4, true, true, 'dge'], + // outages (outage) - specific fix to avoid 'outag' + ['segatuo', 7, true, true, 'outage'], + // roses (rose), garages (garage), cassettes (cassette), // waltzes (waltz), heroes (hero), bushes (bush), arches (arch), // shoes (shoe) @@ -196,6 +205,9 @@ final class EnglishInflector implements InflectorInterface // Fourth entry: Whether the suffix may succeed a consonant // Fifth entry: plural suffix, normal + // nodes (node) + ['edon', 4, true, true, 'nodes'], + // axes (axis) ['sixa', 4, false, false, 'axes'], diff --git a/app/vendor/symfony/var-dumper/Caster/DOMCaster.php b/app/vendor/symfony/var-dumper/Caster/DOMCaster.php index e16b33d42..10fefed3a 100644 --- a/app/vendor/symfony/var-dumper/Caster/DOMCaster.php +++ b/app/vendor/symfony/var-dumper/Caster/DOMCaster.php @@ -77,10 +77,6 @@ public static function castException(\DOMException|\Dom\Exception $e, array $a, public static function castLength($dom, array $a, Stub $stub, bool $isNested): array { - $a += [ - 'length' => $dom->length, - ]; - return $a; } @@ -96,69 +92,16 @@ public static function castImplementation(\DOMImplementation|\Dom\Implementation public static function castNode(\DOMNode|\Dom\Node $dom, array $a, Stub $stub, bool $isNested): array { - $a += [ - 'nodeName' => $dom->nodeName, - 'nodeValue' => new CutStub($dom->nodeValue), - 'nodeType' => new ConstStub(self::NODE_TYPES[$dom->nodeType], $dom->nodeType), - 'parentNode' => new CutStub($dom->parentNode), - 'childNodes' => $dom->childNodes, - 'firstChild' => new CutStub($dom->firstChild), - 'lastChild' => new CutStub($dom->lastChild), - 'previousSibling' => new CutStub($dom->previousSibling), - 'nextSibling' => new CutStub($dom->nextSibling), - 'ownerDocument' => new CutStub($dom->ownerDocument), - 'baseURI' => $dom->baseURI ? new LinkStub($dom->baseURI) : $dom->baseURI, - 'textContent' => new CutStub($dom->textContent), - ]; - - if ($dom instanceof \DOMNode || $dom instanceof \Dom\Element) { - $a += [ - 'attributes' => $dom->attributes, - 'namespaceURI' => $dom->namespaceURI, - 'prefix' => $dom->prefix, - 'localName' => $dom->localName, - ]; - } - - return $a; + return self::castDom($dom, $a, $stub, $isNested); } public static function castNameSpaceNode(\DOMNameSpaceNode $dom, array $a, Stub $stub, bool $isNested): array { - $a += [ - 'nodeName' => $dom->nodeName, - 'nodeValue' => new CutStub($dom->nodeValue), - 'nodeType' => new ConstStub(self::NODE_TYPES[$dom->nodeType], $dom->nodeType), - 'prefix' => $dom->prefix, - 'localName' => $dom->localName, - 'namespaceURI' => $dom->namespaceURI, - 'ownerDocument' => new CutStub($dom->ownerDocument), - 'parentNode' => new CutStub($dom->parentNode), - ]; - - return $a; + return self::castDom($dom, $a, $stub, $isNested); } public static function castDocument(\DOMDocument $dom, array $a, Stub $stub, bool $isNested, int $filter = 0): array { - $a += [ - 'doctype' => $dom->doctype, - 'implementation' => $dom->implementation, - 'documentElement' => new CutStub($dom->documentElement), - 'encoding' => $dom->encoding, - 'xmlEncoding' => $dom->xmlEncoding, - 'xmlStandalone' => $dom->xmlStandalone, - 'xmlVersion' => $dom->xmlVersion, - 'strictErrorChecking' => $dom->strictErrorChecking, - 'documentURI' => $dom->documentURI ? new LinkStub($dom->documentURI) : $dom->documentURI, - 'formatOutput' => $dom->formatOutput, - 'validateOnParse' => $dom->validateOnParse, - 'resolveExternals' => $dom->resolveExternals, - 'preserveWhiteSpace' => $dom->preserveWhiteSpace, - 'recover' => $dom->recover, - 'substituteEntities' => $dom->substituteEntities, - ]; - if (!($filter & Caster::EXCLUDE_VERBOSE)) { $formatOutput = $dom->formatOutput; $dom->formatOutput = true; @@ -171,18 +114,6 @@ public static function castDocument(\DOMDocument $dom, array $a, Stub $stub, boo public static function castXMLDocument(\Dom\XMLDocument $dom, array $a, Stub $stub, bool $isNested, int $filter = 0): array { - $a += [ - 'doctype' => $dom->doctype, - 'implementation' => $dom->implementation, - 'documentElement' => new CutStub($dom->documentElement), - 'inputEncoding' => $dom->inputEncoding, - 'xmlEncoding' => $dom->xmlEncoding, - 'xmlStandalone' => $dom->xmlStandalone, - 'xmlVersion' => $dom->xmlVersion, - 'documentURI' => $dom->documentURI ? new LinkStub($dom->documentURI) : $dom->documentURI, - 'formatOutput' => $dom->formatOutput, - ]; - if (!($filter & Caster::EXCLUDE_VERBOSE)) { $formatOutput = $dom->formatOutput; $dom->formatOutput = true; @@ -195,14 +126,6 @@ public static function castXMLDocument(\Dom\XMLDocument $dom, array $a, Stub $st public static function castHTMLDocument(\Dom\HTMLDocument $dom, array $a, Stub $stub, bool $isNested, int $filter = 0): array { - $a += [ - 'doctype' => $dom->doctype, - 'implementation' => $dom->implementation, - 'documentElement' => new CutStub($dom->documentElement), - 'inputEncoding' => $dom->inputEncoding, - 'documentURI' => $dom->documentURI ? new LinkStub($dom->documentURI) : $dom->documentURI, - ]; - if (!($filter & Caster::EXCLUDE_VERBOSE)) { $a += [Caster::PREFIX_VIRTUAL.'html' => $dom->saveHTML()]; } @@ -212,106 +135,74 @@ public static function castHTMLDocument(\Dom\HTMLDocument $dom, array $a, Stub $ public static function castCharacterData(\DOMCharacterData|\Dom\CharacterData $dom, array $a, Stub $stub, bool $isNested): array { - $a += [ - 'data' => $dom->data, - 'length' => $dom->length, - ]; - return $a; } public static function castAttr(\DOMAttr|\Dom\Attr $dom, array $a, Stub $stub, bool $isNested): array { - $a += [ - 'name' => $dom->name, - 'specified' => $dom->specified, - 'value' => $dom->value, - 'ownerElement' => $dom->ownerElement, - ]; - - if ($dom instanceof \DOMAttr) { - $a += [ - 'schemaTypeInfo' => $dom->schemaTypeInfo, - ]; - } - return $a; } public static function castElement(\DOMElement|\Dom\Element $dom, array $a, Stub $stub, bool $isNested): array { - $a += [ - 'tagName' => $dom->tagName, - ]; - - if ($dom instanceof \DOMElement) { - $a += [ - 'schemaTypeInfo' => $dom->schemaTypeInfo, - ]; - } - return $a; } public static function castText(\DOMText|\Dom\Text $dom, array $a, Stub $stub, bool $isNested): array { - $a += [ - 'wholeText' => $dom->wholeText, - ]; - return $a; } public static function castDocumentType(\DOMDocumentType|\Dom\DocumentType $dom, array $a, Stub $stub, bool $isNested): array { - $a += [ - 'name' => $dom->name, - 'entities' => $dom->entities, - 'notations' => $dom->notations, - 'publicId' => $dom->publicId, - 'systemId' => $dom->systemId, - 'internalSubset' => $dom->internalSubset, - ]; - return $a; } public static function castNotation(\DOMNotation|\Dom\Notation $dom, array $a, Stub $stub, bool $isNested): array { - $a += [ - 'publicId' => $dom->publicId, - 'systemId' => $dom->systemId, - ]; - return $a; } public static function castEntity(\DOMEntity|\Dom\Entity $dom, array $a, Stub $stub, bool $isNested): array { - $a += [ - 'publicId' => $dom->publicId, - 'systemId' => $dom->systemId, - 'notationName' => $dom->notationName, - ]; - return $a; } public static function castProcessingInstruction(\DOMProcessingInstruction|\Dom\ProcessingInstruction $dom, array $a, Stub $stub, bool $isNested): array { - $a += [ - 'target' => $dom->target, - 'data' => $dom->data, - ]; - return $a; } public static function castXPath(\DOMXPath|\Dom\XPath $dom, array $a, Stub $stub, bool $isNested): array { - $a += [ - 'document' => $dom->document, - ]; + return self::castDom($dom, $a, $stub, $isNested); + } + + public static function castDom($dom, array $a, Stub $stub, bool $isNested, int $filter = 0): array + { + foreach ($a as $k => $v) { + if ('encoding' === $k && $dom instanceof \DOMEntity + || \in_array($k, ['actualEncoding', 'config', 'standalone', 'version'], true) + ) { + continue; // deprecated properties + } + + $v = $dom->$k; + + $a[$k] = match (true) { + $v instanceof \DOMNode || $v instanceof \Dom\Node => new CutStub($v), + 'nodeType' === $k => new ConstStub(self::NODE_TYPES[$v], $v), + 'baseURI' === $k && $v, + 'documentURI' === $k && $v => new LinkStub($v), + default => $v, + }; + } + + if ($dom instanceof \IteratorAggregate) { + foreach ($dom as $k => $v) { + $a[Caster::PREFIX_VIRTUAL.$k] = $v; + } + } return $a; } diff --git a/app/vendor/symfony/var-dumper/Cloner/AbstractCloner.php b/app/vendor/symfony/var-dumper/Cloner/AbstractCloner.php index b49560913..592e61252 100644 --- a/app/vendor/symfony/var-dumper/Cloner/AbstractCloner.php +++ b/app/vendor/symfony/var-dumper/Cloner/AbstractCloner.php @@ -58,38 +58,25 @@ abstract class AbstractCloner implements ClonerInterface 'DOMException' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castException'], 'Dom\Exception' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castException'], - 'DOMStringList' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'], - 'DOMNameList' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'], + 'DOMStringList' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castDom'], + 'DOMNameList' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castDom'], 'DOMImplementation' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castImplementation'], 'Dom\Implementation' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castImplementation'], - 'DOMImplementationList' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'], - 'DOMNode' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castNode'], - 'Dom\Node' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castNode'], - 'DOMNameSpaceNode' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castNameSpaceNode'], + 'DOMImplementationList' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castDom'], + 'DOMNode' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castDom'], + 'Dom\Node' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castDom'], + 'DOMNameSpaceNode' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castDom'], 'DOMDocument' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castDocument'], 'Dom\XMLDocument' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castXMLDocument'], 'Dom\HTMLDocument' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castHTMLDocument'], - 'DOMNodeList' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'], - 'Dom\NodeList' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'], - 'DOMNamedNodeMap' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'], - 'Dom\DTDNamedNodeMap' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'], - 'DOMCharacterData' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castCharacterData'], - 'Dom\CharacterData' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castCharacterData'], - 'DOMAttr' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castAttr'], - 'Dom\Attr' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castAttr'], - 'DOMElement' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castElement'], - 'Dom\Element' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castElement'], - 'DOMText' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castText'], - 'Dom\Text' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castText'], - 'DOMDocumentType' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castDocumentType'], - 'Dom\DocumentType' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castDocumentType'], - 'DOMNotation' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castNotation'], - 'Dom\Notation' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castNotation'], - 'DOMEntity' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castEntity'], - 'Dom\Entity' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castEntity'], - 'DOMProcessingInstruction' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castProcessingInstruction'], - 'Dom\ProcessingInstruction' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castProcessingInstruction'], - 'DOMXPath' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castXPath'], + 'DOMNodeList' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castDom'], + 'Dom\NodeList' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castDom'], + 'DOMNamedNodeMap' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castDom'], + 'Dom\DTDNamedNodeMap' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castDom'], + 'DOMXPath' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castDom'], + 'Dom\XPath' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castDom'], + 'Dom\HTMLCollection' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castDom'], + 'Dom\TokenList' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castDom'], 'XMLReader' => ['Symfony\Component\VarDumper\Caster\XmlReaderCaster', 'castXmlReader'],